Compare commits

..
Author SHA1 Message Date
Matthew RussellandDexter 4ed623c84c feat#873): wallet service v2 (#1349)
* feat: update connect dialog to handle api v2

* feat: better error handling

* feat: update to only use strings for pubkey, add json rpc connector

* feat: make json connector follow same patterns as rest connector

* feat: add ability to change wallet location

* feat: add chain id fetch

* feat: improve types

* feat: adjust send tx types

* chore: remove dialog title and chain check temporarily

* feat: additional UI and error handling

* feat: rename keypair and keypairs to pubKey and pubKeys

* feat: make rest and json rpc connectors return consistent data

* feat: rename derived ids more clearly

* feat: update send_transaction response type

* chore: fix tests after context name change

* feat: add utils for wallet, add encode tx func

* feat: return null if tx is rejected

* feat: fix up styles for connect buttons

* feat: handle wallet version check

* feat: add chain id check

* chore: rename select pub key function to match, fix tests

* fix: tests for rest connector form

* feat: add tests for json rpc flow

* feat: connect dialog changes

* chore: change status to enum to avoid magic strings

* feat: add custom icons and handle provided key name

* chore: update global wallet connection e2d tests

* chore: change zod validation to only expected required values

* chore: ignore new generated code files

* chore: fix typos and add translations

* chore: set hosted wallet via env var and only show if not mainnet

* feat: add functionality for try again button

* test: fix failing tests

* chore: fix lint and test

* chore: remove double import

* chore: make console-lite-e2e strict so json connector compiles correctly

* chore: make token e2e tsconfig strict

* chore: make stats-e2e tsconfig strict

* feat: update json rpc request namespace

* feat: simplify connector setup, support try again

* chore: remove comment

* fix: build errors

* chore: make chainId check optional based on presence of appChainId, mock request for tests

* chore: mock chain id request for all apps on all pages

* fix: footer border on small screens

* fix: use beforeEach for chainId query mock

* chore: remove optional chain check, prevent rendering until fetch is complete

* chore: update NX_VEGA_WALLET_URLs as the application now appends the base path, adjust token tests

* fix: token e2e test that checks for pubkey name

* chore: remove duplicated test, update wallet title assertion

* fix: token tests

* fix: token e2e assertions

* fix: withdraw test

* feat: enable json RPC for token

* fix: sendTx command now accpets pubkey as separate arg

* fix: test to use gui option temporarily

Co-authored-by: Dexter <dexter.edwards93@gmail.com>
2022-10-03 11:12:34 -07:00
mattrussell36 fe6c5520a9 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-03 18:09:25 +00:00
Bartłomiej Głownia 40527e9bc8 Chore/markets-list: move queries to .graphql, combine data in marketListProvider (#1554)
* chore: combine data instead of providing three lists in marketListProvider

* chore: move markets-libs lib queries to .graphql files

* chore: fix markets queries names in e2e tests

* chore: add missing variables to simple market list candles query

* chore: fix trading  home e2e

* chore: change liquidity-provision-dashboard serve port to allow parell e2e tests
2022-10-03 18:28:32 +01:00
Radosław Szpiech 6681c01b43 chore: run nightly run on demand (#1592) 2022-10-03 16:57:23 +02:00
Joe Tsang ae8ef6001c Test/1484 update token smoke tests (#1571)
* test: added smoke tests from flows

* chore: move failing test
2022-10-03 16:29:52 +02:00
Elmar 620bf1bab4 fix(explorer): add party to consolidated search (#1588)
* fix(explorer): add party to consolidated search

* chore(explorer): add missing tests for detect-search.spec.ts
2022-10-03 13:20:00 +01:00
mattrussell36 09476eea38 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-03 12:05:16 +00:00
Bartłomiej Głownia 9486db134c chore: replace Order type with existing OrderSubmissionBody['orderSubission'] (#1518)
* chore: replace Order type with existing OrderSubmissionBody['orderSubmission']

* chore: drop WithMarket from Order, Trade and Fill types

* chore: fix order list mock
2022-10-03 13:31:46 +02:00
AndyWhiteVega edb31e0f46 Test/expand explorer tests to include parties page (#1530)
* test: parties test suite

* test: lint

* test: improve error handling

* test: lint

* test: extra error handling and deletion of redundant curl params

* test: lint

* test: lint

* test: update

* test: skip test

* test: tweaks

* test: lint

* test: lint
2022-10-03 09:50:36 +01:00
m.ray f743828cb5 feat: 991 deal ticket should show estimated crossing price for market orders when market is in auction (#1561)
* feat: #991 move order validation back in deal ticket, add tooltip

* fix: align view full market list link styling

* feat: #991 move tooltip add for monitoring liq auction

* feat: #991 order validation messages, LP table sortable, padding sparkline, warning instead of errro

* feat: #991 check only in monitoring auction to add tooltips, re-use isMarketInAuction

* fix: #991 fix import for isMarketInAuction

* fix: #991 console-lite imports

* fix: formatting in console-lite deal-ticket-steps.tsx

* fix: add market depth

* fix: add market depth and fix generate orders

* fix: revert market list and generate orders

* fix: fix trading-e2e build

* feat: #991 in monitoring auction don't show a price input color update based on intent

* fix: #991 update docs links to docs.vega.xyz in tooltips
2022-10-03 09:41:34 +01:00
Michael b10844147e Chore/1535 update ac code refrences to new (#1577)
* 1002-STKE to 2001-STKE

* ASSO from 1000 to 1004
2022-10-03 09:29:28 +01:00
m.rayandBartłomiej Głownia 361f8fa870 fix: 1569 navigation link active on market selected refresh (#1580)
* chore: ignore apollo errors - to be reverted after API will be fixed

* fix: navigtaion link market - no global local storage init

* fix: update active link check

* fix: fix any hydration errors and default fallback to markets

Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
2022-10-03 09:29:07 +01:00
Bartłomiej Głownia 1d87eab47c chore: fix generic data provider isUpdate (#1560) 2022-10-03 09:28:52 +01:00
Dexter Edwards 96811b45e3 fix: do not show success state if user cancels tx (#1562) 2022-10-03 09:17:40 +01:00
Sam Keen 520f767d09 Feat/1279: Make validator profiles visible on token front end without wallet connected (#1578) 2022-10-03 09:09:35 +01:00
Matthew Russell 219c9e1b41 feat: disable scrolling to change value for size and price fields (#1584) 2022-10-03 09:05:26 +01:00
Dexter Edwards 0a5a87f432 Feat/1478 better proposal title (#1544)
* chore: generate types

* feat: add asset id to title

* test: add test for new functionality
2022-10-03 09:01:45 +01:00
mattrussell36 eb7b261ec4 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-03 06:09:39 +00:00
mattrussell36 d70059a0d4 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-03 00:12:42 +00:00
mattrussell36 b7c3b9cdb0 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-02 18:04:37 +00:00
mattrussell36 6473738758 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-02 12:04:57 +00:00
mattrussell36 1a2130abbd chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-02 06:09:07 +00:00
mattrussell36 1ec7cc2ed5 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-02 00:13:49 +00:00
Radosław Szpiech dade1e502e fix: wallet connection issue in PR check (#1583) 2022-10-01 12:09:50 -07:00
mattrussell36 335d1bef38 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-01 18:04:23 +00:00
mattrussell36 009a6cc2e1 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-01 12:05:38 +00:00
mattrussell36 2aa23ae202 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-01 06:16:26 +00:00
mattrussell36 1a7ad58199 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-01 00:14:40 +00:00
mattrussell36 682049d17c chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-30 18:06:57 +00:00
m.ray c221d0772c fix: 1569 trading tab navigation update (#1573)
* fix: #1569 trading tab navigation update

* fix: #1569 get market id value from init
2022-09-30 16:50:48 +01:00
Radosław Szpiech efcd128240 fix: console-lite e2e tests (#1576) 2022-09-30 16:51:41 +02:00
Radosław Szpiechandasiaznik 9a88c33458 Fix/console failing tests (#1572)
* fix: market and trading tests

* fix: trading tests

* fix: balance DP

* fix: select asset

* fix: withdrawal tests

Co-authored-by: asiaznik <artur@vegaprotocol.io>
2022-09-30 15:42:42 +02:00
mattrussell36 d2a5adf6ce chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-30 12:04:57 +00:00
Radosław Szpiech ab0065bd9a chore: env setup fixed (#1567) 2022-09-30 11:02:55 +02:00
Radosław Szpiech 5600c56da1 fix: fix (#1565) 2022-09-30 10:59:15 +02:00
Radosław Szpiech 3d7267f496 fix: fix capsule workflow (#1563) 2022-09-30 10:31:38 +02:00
mattrussell36 c87beb4050 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-30 06:25:43 +00:00
m.rayandBartłomiej Głownia 907a4e256e feat(#847): collateral table, add breakdown (#1442)
* feat: #847 show progress bar, margin accounts, no used/deposited

* feat: #847 add collateral tables

* fix: #847 add deposit asset type and fix tests

* feat: #847 show deposited value, avaliable and percentage used

* fix: #847 add styling fixes

* fix: #847 add deposit new asset button

* fix: #847 remove disabledSelect
to fix withdraw and deposit dialogs

* fix: #847 remove global reward from incoming - needs to be party specific

* fix: #847 integration tests

* fix: #847 default select deposit & withdraw

* fix: #847 default select deposit & withdraw

* fix: #847 pass asset id as default value

* fix:  #847 use only bigint no bignumber, remove NaN check

* fix:  #847 update deposit-form.spec.tsx

* fix: revert update on account fields

* feat: add storybook set up

* chore: ignore apollo errors - to be reverted after API will be fixed

* fix: container moved, progress bar in helpers

* fix: #847 UI tweaks around accounts container

* feat: #847 added useDepositAsset and useWithdrawAsset

* fix: #847 fix progress bar in accounts and positions

* feat: #847 add storybook

* fix: #847 added tooltip and updated filtering

* chore: #847 add get account data test

* fix: #847 fix lint and type in account story

* fix:  #847 update data provider

* fix: #847 fix get account data dry & lp link

* fix: #847 fix breakdown table test

* fix: #847 account data provider test

* fix: #847 remove deposit new asset button - subscription does not display a
sset data

* fix: #847 add defaultValue in select otherwise default is not set up

* feat: #847 update data provider update method and tables

* fix: #847 fix accounts tests

* fix: #847 remove unused getRows

* fix: add decimals

* fix: #847 fix imports

* fix: update ids

* Update apps/trading/pages/liquidity/[marketId].page.tsx

* fix: #847 accounts update method check delta

* fix: #847 use vega value formatters and cell renderers

* fix: #847 fix imports

* fix: #847 handle new account else block comment

* fix: accounts and breakdown tables

* fix(#847): account data provider improvments

* fix: #847 fix formatters null check and add param

* fix: #847 fix withdraw test and mock the hook

* fix: #847 fix console lite grid select market test

* fix: console lite build

* fix: revert withdraw limits

* fix: remove redundant waitFor use vega cell renderer

* fix: breakdown display only use accounts

* fix: breakdown display only use accounts

* fix: updated accounts table

* fix: move update inside try useWithdrawAsset

* fix: update trading-accounts test

* fix: portfolio-page.test.ts

Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
2022-09-29 17:40:44 -07:00
mattrussell36 835bf36393 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-30 00:17:39 +00:00
John Walley 57863a7167 chore(deps): update dependency pennant to v0.4.18 (#1557) 2022-09-29 16:47:36 -07:00
Sam KeenandDexter 4272fefb0b feat(#807): adds app for validator add and remove signer (#1402)
* Feat/807: ABI and classes for the contract methods

* Feat/807: Added a new multisig-signer app

* Feat/807: Added a new multisig-signer app

* Feat/800: Untested signer forms

* Feat/800: Moved reused bg video into ui-toolkit to use in multisig-signer project, and cleaned up some spacing that was overlooked in the stats theme changes

* Feat/800: Componentised a bit, made the app look ok

* Feat/800: Linting, prettifying, removing some unneeded tests, ensuring e2e tests run

* Feat/800: Bit of translation

* chore: fix type errors

* chore: some parts error handling

* feat: handle error and not found cases

* feat: add changes to remove signer form as well

* chore: rename component

* chore: fix type issues

* feat: add web3 connector logic

* feat: allow disconnecting and show connected eth wallet info

* Feat/800: Removed unused 'useApolloClient'

* Feat/800: Ensure bundle.nonce and bundle.signatures have '0x' prepended

* Feat/800: Removed unused e2e directory

* Feat/800: Removed unnecessary app test

* Feat/800: Removed unnecessary router

* Feat/800: Capturing GQL errors in Sentry

* Feat/800: Removing references to the unused e2e test directory

* Feat/807: Consistent react hook imports

* Feat/807: Removed unnecessary spreads

Co-authored-by: Dexter <dexter.edwards93@gmail.com>
2022-09-29 12:10:53 -07:00
mattrussell36 8fee3ca080 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-29 18:06:47 +00:00
Radosław Szpiech 98abe2910d chore: fix smoke flag meaning (#1553) 2022-09-29 19:11:52 +02:00
AndyWhiteVega 22f4edff82 test: fix mocks for console light to include id (#1551) 2022-09-29 18:06:33 +01:00
Radosław Szpiech 28aa5cd060 Chore/add skip nx cache flag (#1549)
* chore: add skip-nx-cache flag to manual flow

* chore: set --skip-nx-cache flag

* chore: set --skip-nx-cache flag

* chore: set --skip-nx-cache flag

* chore: remove log
2022-09-29 18:27:08 +02:00
Radosław Szpiech 7b0d41b4a6 fix: typo (#1547) 2022-09-29 18:02:40 +02:00
Radosław Szpiech 83ea12b4cb Fix/smoke test flag (#1545)
* fix: fix smoke test flag logic

* fix: fix smoke test flag logic
2022-09-29 16:32:43 +01:00
Dexter Edwards a7768f154b chore: relabel total associated (#1531) 2022-09-29 15:47:09 +01:00
Matthew Russell 65af6bc0a8 fix: make sparkline strokes the same thickness (#1527)
* fix: make sparkline strokes the same thickness

* fix: data test id in sparkline
2022-09-29 07:20:04 -07:00
Radosław Szpiech 93c1835cd6 Chore/1495 workflows minor improvements fixes and cleanup (#1538)
* test: try with shared actions

* test: fix env

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: fix path

* test: create action for capsule build

* test: create action for capsule build

* test: create action for capsule build

* test: create action for capsule build

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* chore: create action for vegawallet setup

* chore: create action for vegawallet setup

* chore: fix

* chore: fix

* chore: fix

* chore: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* chore: apply changes to all workflows

* chore: apply changes to all workflows
2022-09-29 14:13:09 +01:00
Art 446af23d9c fix: orderbook sticky behaviour and precision options (#1519)
* fix: orderbook sticky behaviour and precision options

* chore: add handling of y-axis resize

* fix: addressed PR comments

* fix: addressed PR comments

* added cy.log

* Update apps/trading-e2e/src/support/vega-wallet.ts
2022-09-29 14:23:14 +02:00
mattrussell36 2cfe8608fb chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-29 12:05:36 +00:00
Matthew Russell 032805b5b9 Feat/1500 cancel and edit buttons conditional rendering (#1528)
* feat: allow second type arg to be generic as you dont always need the value

* feat: update order data provider to include fields required to determine if editable or cancellable

* feat: use ag grid type helpers and add rendering logic to cancel and amend buttons

* feat: combine cancel/edit buttons into single cell for better spacing

* feat: add test cases for dispaly of amend/cancel buttons

* chore: add missing fields to mock generate function

* chore: remove unnecessary wait for in fills test that was sporadically failing

* fix: add missing fields to generate order function

* fix: add missing fields to generate order function for console-lite
2022-09-29 11:00:39 +02:00
Joe Tsang 7a7c4ad452 Test/governance flows update (#1523)
* test: update governance flows with new ui change

* chore: existing governance flow tests passing

* chore: remove unused import

* chore: remove unused var
2022-09-29 09:18:06 +01:00
mattrussell36 0c5b2f5c29 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-29 06:27:20 +00:00
mattrussell36 a8667eea31 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-29 00:15:10 +00:00
Bartłomiej GłowniaandRado 8dd63d7cac chore: fix mocks and hydration error (#1520)
* chore: fix DealTicket mock

* chore: fix React Hydration Error

* test: change tag

Co-authored-by: Rado <szpiechrados@gmail.com>
2022-09-28 14:40:21 -07:00
Matthew Russell 8bffc05be2 fix: ensure sub is cancelled if dialog is closed (#1472)
* fix: ensure sub is cancelled if dialog is closed

* fix: pass transaction object in use proposal hooks
2022-09-28 12:34:49 -07:00
Matthew Russell 26e3596c5a fix: align market dropdown cells for both my markets and all markets (#1492) 2022-09-28 12:15:09 -07:00
mattrussell36 6ad68f87cc chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-28 18:08:30 +00:00
Radosław Szpiech 86fdd75d6c fix: disabled test (#1525) 2022-09-28 18:56:45 +02:00
435 changed files with 12239 additions and 6299 deletions
@@ -0,0 +1,31 @@
inputs:
all:
description: 'Install all binaries'
default: false
version:
description: 'Vega version'
gobin:
description: 'GOBIN path'
default: '/home/runner/go/bin'
runs:
using: 'composite'
steps:
- name: Install Vega binaries
if: ${{ inputs.all }}
shell: bash
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vega-linux-amd64.zip' -q
unzip vega-linux-amd64.zip -d ${{ inputs.gobin }}
- name: Install date-node binaries
if: ${{ inputs.all }}
shell: bash
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/data-node-linux-amd64.zip' -q
unzip data-node-linux-amd64.zip -d ${{ inputs.gobin }}
- name: Install Vega wallet binaries
shell: bash
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vegawallet-linux-amd64.zip' -q
unzip vegawallet-linux-amd64.zip -d ${{ inputs.gobin }}
@@ -0,0 +1,30 @@
inputs:
github-token:
description: 'github token'
runs:
using: 'composite'
steps:
- name: Checkout capsule
uses: actions/checkout@v2
with:
repository: vegaprotocol/vegacapsule
ref: main
token: ${{ inputs.github-token }}
path: './capsule'
- name: Build capsule
run: go install
shell: bash
working-directory: capsule
- name: Login to docker
shell: bash
run: echo -n ${{ inputs.github-token }} | docker login https://ghcr.io -u vega-ci-bot --password-stdin
- name: Start nomad
shell: bash
run: vegacapsule nomad &
- name: Bootstrap network
shell: bash
run: vegacapsule network bootstrap --config-path=./frontend-monorepo/vegacapsule/config.hcl --force
@@ -0,0 +1,45 @@
inputs:
recovery:
description: 'Recovery phrase'
passphrase:
description: 'Wallet password'
capsule:
description: 'Is Capsule network used'
default: false
runs:
using: 'composite'
steps:
- name: Create passphrase
shell: bash
run: echo "${{ inputs.passphrase }}" > ./passphrase
- name: Create recovery
shell: bash
run: echo "${{ inputs.recovery }}" > ./recovery
- name: Initialize wallet
shell: bash
run: vegawallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
shell: bash
run: vegawallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
shell: bash
run: vegawallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import fairground network
shell: bash
if: ${{ inputs.capsule==false }}
run: vegawallet network import --from-url="https://raw.githubusercontent.com/vegaprotocol/networks/master/fairground/fairground.toml" --force --home ~/.vegacapsule/testnet/wallet
- name: Start service using fairground network
shell: bash
if: ${{ inputs.capsule==false }}
run: vegawallet service run --network fairground --automatic-consent --home ~/.vegacapsule/testnet/wallet &
- name: Start service using capsule network
shell: bash
if: ${{ inputs.capsule }}
run: vegawallet service run --network DV --automatic-consent --home ~/.vegacapsule/testnet/wallet &
@@ -1,4 +1,4 @@
name: Capsule tests -- manual trigger
name: Cypress tests -- manual trigger
# This workflow runs the frontend tests against chosen branch
@@ -15,20 +15,43 @@ on:
- stats-e2e
- token-e2e
- trading-e2e
runAlltests:
smokeOnly:
description: 'Run only smoke tests?'
required: true
type: boolean
default: false
skip-nx-cache:
description: 'Add --skip-nx-cache to cypress test'
required: false
type: boolean
default: false
jobs:
manual:
name: Run capsule tests -- manual trigger
name: Run Cypress tests -- manual trigger
runs-on: self-hosted
env:
GO111MODULE: 'on'
GOBIN: /home/runner/go/bin
VEGA_VERSION: 'v0.57.0'
steps:
#######
## Setup flags
#######
- name: Set smoke tag
if: ${{ github.event.inputs.smokeOnly == 'true' }}
run: echo TAGS="--env.grepTags @smoke" >> $GITHUB_ENV
- name: Set --skip-nx-cache flag
if: ${{ github.event.inputs.skip-nx-cache == 'true' }}
run: echo SKIP_NX_CACHE="--skip-nx-cache" >> $GITHUB_ENV
# See if we capsule is needed for this project
- name: Set capsule flag
if: ${{ github.event.inputs.project == 'explorer-e2e' || github.event.inputs.project == 'token-e2e' }}
run: echo RUN_CAPSULE=true >> $GITHUB_ENV
#######
## Setup langs
#######
@@ -37,6 +60,7 @@ jobs:
id: go
with:
go-version: 1.19
- name: Set up Node 16
uses: actions/setup-node@v2
id: npm
@@ -57,25 +81,9 @@ jobs:
- name: Checkout frontend mono repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
path: './frontend-monorepo'
# See if we capsule is needed for this project
- name: See if capsule is necessary
if: ${{ github.event.inputs.project == 'explorer-e2e' || github.event.inputs.project == 'token-e2e' }}
run: echo RUN_CAPSULE=true >> $GITHUB_ENV
# Checkout capsule to build local network
- name: Checkout capsule
if: ${{ env.RUN_CAPSULE }}
uses: actions/checkout@v2
with:
repository: vegaprotocol/vegacapsule
ref: main
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './capsule'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
@@ -91,75 +99,32 @@ jobs:
working-directory: frontend-monorepo
#######
## Build binaries
## Build and run Vegacapsule network
#######
- name: Build capsule
if: ${{ env.RUN_CAPSULE }}
run: go install
working-directory: capsule
- name: Set GOBIN
run: echo GOBIN=$(go env GOPATH)/bin >> $GITHUB_ENV
- name: Install Vega binaries
if: ${{ env.RUN_CAPSULE }}
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vega-linux-amd64.zip'
unzip vega-linux-amd64.zip -d ${{ env.GOBIN }}
uses: ./frontend-monorepo/.github/actions/install-vega-binaries
with:
all: ${{ env.RUN_CAPSULE }}
version: ${{ env.VEGA_VERSION }}
gobin: ${{ env.GOBIN }}
- name: Install date-node binaries
- name: Build and run Vegacapsule network
if: ${{ env.RUN_CAPSULE }}
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/data-node-linux-amd64.zip'
unzip data-node-linux-amd64.zip -d ${{ env.GOBIN }}
- name: Install Vega wallet binaries
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vegawallet-linux-amd64.zip'
unzip vegawallet-linux-amd64.zip -d ${{ env.GOBIN }}
######
## Start capsule
######
- name: Login to docker
if: ${{ env.RUN_CAPSULE }}
run: echo -n ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} | docker login https://ghcr.io -u vega-ci-bot --password-stdin
- name: Start nomad
if: ${{ env.RUN_CAPSULE }}
run: vegacapsule nomad &
- name: Bootstrap network
if: ${{ env.RUN_CAPSULE }}
run: vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force
working-directory: capsule
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
with:
github-token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
######
## Setup a Vega wallet for our user
######
- name: Create passphrase
run: echo "${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}" > ./passphrase
- name: Create recovery
run: echo "${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}" > ./recovery
- name: Initialize wallet
run: vegawallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
run: vegawallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
run: vegawallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import fairground network
if: ${{ env.RUN_CAPSULE==false }}
run: vegawallet network import --from-url="https://raw.githubusercontent.com/vegaprotocol/networks/master/fairground/fairground.toml" --force --home ~/.vegacapsule/testnet/wallet
- name: Start service using fairground network
if: ${{ env.RUN_CAPSULE==false }}
run: vegawallet service run --network fairground --automatic-consent --home ~/.vegacapsule/testnet/wallet &
- name: Start service using capsule network
if: ${{ env.RUN_CAPSULE }}
run: vegawallet service run --network DV --automatic-consent --home ~/.vegacapsule/testnet/wallet &
- name: Set up Vegawallet
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
with:
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
capsule: ${{ env.RUN_CAPSULE }}
######
## Run some tests
@@ -170,24 +135,13 @@ jobs:
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run smoke Cypress tests
if: ${{ github.event.inputs.runAlltests == 'true' }}
run: yarn nx run ${{ github.event.inputs.project }}:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --env.grepTags='@smoke' --browser chrome
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPESS_ETH_WALLET_MNEMONIC }}
CYPRESS_TEARDOWN_NETWORK_AFTER_FLOWS: true
- name: Run Cypress tests
if: ${{ github.event.inputs.runAlltests == 'false' }}
run: yarn nx run ${{ github.event.inputs.project }}:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run ${{ github.event.inputs.project }}:e2e ${{ env.SKIP_NX_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ env.TAGS }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPESS_ETH_WALLET_MNEMONIC }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_TEARDOWN_NETWORK_AFTER_FLOWS: true
######
+25 -58
View File
@@ -1,18 +1,22 @@
name: Capsule tests -- night run
name: Cypress tests -- night run
# This workflow runs the frontend tests against latest develop of the core to preempt breaking changes
on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:
jobs:
nightly:
name: Run capsule tests -- nightly
name: Run Cypress tests -- nightly
runs-on: self-hosted
timeout-minutes: 60
env:
GO111MODULE: 'on'
GOBIN: /home/runner/go/bin
VEGA_VERSION: 'v0.57.0'
RUN_CAPSULE: true
steps:
#######
## Setup langs
@@ -42,19 +46,9 @@ jobs:
- name: Checkout frontend mono repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
path: './frontend-monorepo'
# Checkout capsule to build local network
- name: Checkout capsule
uses: actions/checkout@v2
with:
repository: vegaprotocol/vegacapsule
ref: main
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './capsule'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
@@ -70,59 +64,32 @@ jobs:
working-directory: frontend-monorepo
#######
## Build binaries
## Build and run Vegacapsule network
#######
- name: Build capsule
run: go install
working-directory: capsule
- name: Set GOBIN
run: echo GOBIN=$(go env GOPATH)/bin >> $GITHUB_ENV
- name: Install Vega binaries
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vega-linux-amd64.zip'
unzip vega-linux-amd64.zip -d ${{ env.GOBIN }}
uses: ./frontend-monorepo/.github/actions/install-vega-binaries
with:
all: ${{ env.RUN_CAPSULE }}
version: ${{ env.VEGA_VERSION }}
gobin: ${{ env.GOBIN }}
- name: Install date-node binaries
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/data-node-linux-amd64.zip'
unzip data-node-linux-amd64.zip -d ${{ env.GOBIN }}
- name: Install Vega wallet binaries
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vegawallet-linux-amd64.zip'
unzip vegawallet-linux-amd64.zip -d ${{ env.GOBIN }}
######
## Start capsule
######
- name: Login to docker
run: echo -n ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} | docker login https://ghcr.io -u vega-ci-bot --password-stdin
- name: Start nomad
run: vegacapsule nomad &
- name: Bootstrap network
run: vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force
working-directory: capsule
- name: Build and run Vegacapsule network
if: ${{ env.RUN_CAPSULE }}
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
with:
github-token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
######
## Setup a Vega wallet for our user
######
- name: Create passphrase
run: echo "${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}" > ./passphrase
- name: Create recovery
run: echo "${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}" > ./recovery
- name: Initialize wallet
run: vegawallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
run: vegawallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
run: vegawallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Start service
run: vegawallet service run --network DV --automatic-consent --home ~/.vegacapsule/testnet/wallet &
- name: Set up Vegawallet
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
with:
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
capsule: ${{ env.RUN_CAPSULE }}
######
## Run some tests
@@ -134,12 +101,12 @@ jobs:
working-directory: frontend-monorepo
- name: Run Cypress tests
run: yarn nx run-many --target=e2e --all --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run-many --skip-nx-cache --target=e2e --all --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPESS_ETH_WALLET_MNEMONIC }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_NIGHTLY_RUN: true
CYPRESS_TEARDOWN_NETWORK_AFTER_FLOWS: true
+33 -82
View File
@@ -1,4 +1,4 @@
name: Capsule tests
name: Cypress tests
on:
push:
@@ -14,10 +14,12 @@ on:
jobs:
pr:
name: Run capsule tests - PR
name: Run Cypress tests - PR
runs-on: self-hosted
timeout-minutes: 30
env:
GO111MODULE: 'on'
GOBIN: /home/runner/go/bin
VEGA_VERSION: 'v0.57.0'
steps:
#######
@@ -28,6 +30,7 @@ jobs:
id: go
with:
go-version: 1.19
- name: Set up Node 16
uses: actions/setup-node@v2
id: npm
@@ -48,7 +51,6 @@ jobs:
- name: Checkout frontend mono repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
path: './frontend-monorepo'
@@ -66,106 +68,55 @@ jobs:
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
# See affected apps to see if building all binaries is necessary
# Check SHAs
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
with:
working-directory: frontend-monorepo
main-branch-name: ${{ github.base_ref || github.ref_name }}
set-environment-variables-for-job: true
# See if we capsule is needed for this project
- name: See affected apps
run: echo AFFECTED=$(yarn nx print-affected --base=origin/${{github.base_ref}} --head=${{github.head_ref}} --select=projects) >> $GITHUB_ENV
run: echo AFFECTED=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects) >> $GITHUB_ENV
working-directory: frontend-monorepo
- name: See if capsule is necessary
if: ${{ contains(env.AFFECTED, 'token') || contains(env.AFFECTED, 'token-e2e') || contains(env.AFFECTED, 'explorer') || contains(env.AFFECTED, 'explorer-e2e') }}
run: echo RUN_CAPSULE=true >> $GITHUB_ENV
# Checkout capsule to build local network
- name: Checkout capsule
if: ${{ env.RUN_CAPSULE }}
uses: actions/checkout@v2
with:
repository: vegaprotocol/vegacapsule
ref: main
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './capsule'
#######
## Build binaries
## Build and run Vegacapsule network
#######
- name: Build capsule
if: ${{ env.RUN_CAPSULE }}
run: go install
working-directory: capsule
- name: Set GOBIN
run: echo GOBIN=$(go env GOPATH)/bin >> $GITHUB_ENV
- name: Install Vega binaries
if: ${{ env.RUN_CAPSULE }}
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vega-linux-amd64.zip'
unzip vega-linux-amd64.zip -d ${{ env.GOBIN }}
uses: ./frontend-monorepo/.github/actions/install-vega-binaries
with:
all: ${{ env.RUN_CAPSULE }}
version: ${{ env.VEGA_VERSION }}
gobin: ${{ env.GOBIN }}
- name: Install date-node binaries
- name: Build and run Vegacapsule network
if: ${{ env.RUN_CAPSULE }}
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/data-node-linux-amd64.zip'
unzip data-node-linux-amd64.zip -d ${{ env.GOBIN }}
- name: Install Vega wallet binaries
run: |
wget 'https://github.com/vegaprotocol/vega/releases/download/${{ env.VEGA_VERSION }}/vegawallet-linux-amd64.zip'
unzip vegawallet-linux-amd64.zip -d ${{ env.GOBIN }}
######
## Start capsule
######
- name: Login to docker
if: ${{ env.RUN_CAPSULE }}
run: echo -n ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} | docker login https://ghcr.io -u vega-ci-bot --password-stdin
- name: Start nomad
if: ${{ env.RUN_CAPSULE }}
run: vegacapsule nomad &
- name: Bootstrap network
if: ${{ env.RUN_CAPSULE }}
run: vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force
working-directory: capsule
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
with:
github-token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
######
## Setup a Vega wallet for our user
######
- name: Create passphrase
run: echo "${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}" > ./passphrase
- name: Create recovery
run: echo "${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}" > ./recovery
- name: Initialize wallet
run: vegawallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
run: vegawallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
run: vegawallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import fairground network
if: ${{ env.RUN_CAPSULE==false }}
run: vegawallet network import --from-url="https://raw.githubusercontent.com/vegaprotocol/networks/master/fairground/fairground.toml" --force --home ~/.vegacapsule/testnet/wallet
- name: Start service using fairground network
if: ${{ env.RUN_CAPSULE==false }}
run: vegawallet service run --network fairground --automatic-consent --home ~/.vegacapsule/testnet/wallet &
- name: Start service using capsule network
if: ${{ env.RUN_CAPSULE }}
run: vegawallet service run --network DV --automatic-consent --home ~/.vegacapsule/testnet/wallet &
- name: Set up Vegawallet
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
with:
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
capsule: ${{ env.RUN_CAPSULE }}
######
## Run some tests
######
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
with:
working-directory: frontend-monorepo
main-branch-name: ${{github.base_ref}}
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
@@ -177,8 +128,8 @@ jobs:
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPESS_ETH_WALLET_MNEMONIC }}
CYPRESS_TEARDOWN_NETWORK_AFTER_FLOWS: true
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_TEARDOWN_NETWORK_AFTER_FLOWS: false
######
## Upload logs
+4
View File
@@ -30,6 +30,10 @@ An application for the status of the Vega network. Showing block height and othe
Hosting for static content being shared across apps, for example fonts.
### [Multisig-signer](./apps/multisig-signer)
The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract.
# 🧱 Libraries in this repo
### [UI toolkit](./libs/ui-toolkit)
+1 -1
View File
@@ -23,4 +23,4 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_URL=https://api.n11.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
NX_VEGA_WALLET_URL=http://localhost:1789
@@ -1,5 +1,17 @@
import { aliasQuery } from '@vegaprotocol/cypress';
import {
generateSimpleMarkets,
generateMarketsCandles,
} from '../support/mocks/generate-markets';
describe('simple trading app', { tags: '@smoke' }, () => {
beforeEach(() => cy.visit('/'));
beforeEach(() => {
cy.mockGQL((req) => {
aliasQuery(req, 'Markets', generateSimpleMarkets());
aliasQuery(req, 'MarketsCandles', generateMarketsCandles());
});
cy.visit('/');
});
it('render', () => {
cy.get('#root').should('exist');
@@ -1,9 +1,8 @@
import { aliasQuery } from '@vegaprotocol/cypress';
import type { Markets } from '@vegaprotocol/market-list';
import type { MarketsQuery } from '@vegaprotocol/market-list';
import {
generateLongListMarkets,
generateSimpleMarkets,
generateMarketsData,
generateMarketsCandles,
} from '../support/mocks/generate-markets';
@@ -12,8 +11,7 @@ describe('market list', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockGQL((req) => {
aliasQuery(req, 'Markets', generateSimpleMarkets());
aliasQuery(req, 'MarketsDataQuery', generateMarketsData());
aliasQuery(req, 'MarketsCandlesQuery', generateMarketsCandles());
aliasQuery(req, 'MarketsCandles', generateMarketsCandles());
});
cy.visit('/markets');
});
@@ -68,8 +66,7 @@ describe('market list', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockGQL((req) => {
aliasQuery(req, 'Markets', generateSimpleMarkets());
aliasQuery(req, 'MarketsDataQuery', generateMarketsData());
aliasQuery(req, 'MarketsCandlesQuery', generateMarketsCandles());
aliasQuery(req, 'MarketsCandles', generateMarketsCandles());
});
});
@@ -81,10 +78,10 @@ describe('market list', { tags: '@smoke' }, () => {
it('last asset (if exists)', () => {
cy.visit('/markets');
cy.wait('@Markets').then((filters) => {
const data: Markets | undefined = filters?.response?.body?.data;
if (data.marketsConnection.edges.length) {
const data: MarketsQuery | undefined = filters?.response?.body?.data;
if (data?.marketsConnection?.edges.length) {
const asset =
data.marketsConnection.edges[0].node.tradableInstrument.instrument
data?.marketsConnection?.edges[0].node.tradableInstrument.instrument
.product.settlementAsset.symbol;
cy.visit(`/markets/Suspended/Future/${asset}`);
cy.getByTestId('market-assets-menu')
@@ -107,8 +104,7 @@ describe('market list', { tags: '@smoke' }, () => {
cy.viewport(1440, 900);
cy.mockGQL((req) => {
aliasQuery(req, 'Markets', generateLongListMarkets(1000));
aliasQuery(req, 'MarketsDataQuery', generateMarketsData());
aliasQuery(req, 'MarketsCandlesQuery', generateMarketsCandles());
aliasQuery(req, 'MarketsCandles', generateMarketsCandles());
});
performance.mark('start-1k');
cy.visit('/markets');
@@ -14,15 +14,15 @@ import { generatePartyMarketData } from '../support/mocks/generate-party-market-
import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price';
import { generateMarketNames } from '../support/mocks/generate-market-names';
import { generateMarketDepth } from '../support/mocks/generate-market-depth';
import type { Market, Markets } from '@vegaprotocol/market-list';
import type { Market, MarketsQuery } from '@vegaprotocol/market-list';
describe('market selector', { tags: '@smoke' }, () => {
let markets: Market[];
beforeEach(() => {
cy.mockGQL((req) => {
aliasQuery(req, 'Markets', generateSimpleMarkets());
aliasQuery(req, 'MarketsCandlesQuery', generateMarketsCandles());
aliasQuery(req, 'MarketsDataQuery', generateMarketsData());
aliasQuery(req, 'MarketsCandles', generateMarketsCandles());
aliasQuery(req, 'MarketsData', generateMarketsData());
aliasQuery(req, 'DealTicket', generateDealTicket());
aliasQuery(req, 'MarketTags', generateMarketTags());
aliasQuery(req, 'MarketPositions', generateMarketPositions());
@@ -36,9 +36,9 @@ describe('market selector', { tags: '@smoke' }, () => {
cy.visit('/markets');
cy.wait('@Markets').then((response) => {
const data: Markets | undefined = response?.response?.body?.data;
if (data.marketsConnection.edges.length) {
markets = data.marketsConnection.edges.map((edge) => edge.node);
const data: MarketsQuery | undefined = response?.response?.body?.data;
if (data?.marketsConnection?.edges.length) {
markets = data?.marketsConnection?.edges.map((edge) => edge.node);
}
});
});
@@ -13,15 +13,15 @@ import { generatePartyMarketData } from '../support/mocks/generate-party-market-
import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price';
import { generateMarketDepth } from '../support/mocks/generate-market-depth';
import { connectVegaWallet } from '../support/connect-wallet';
import type { Markets, Market } from '@vegaprotocol/market-list';
import type { MarketsQuery, Market } from '@vegaprotocol/market-list';
describe('Market trade', { tags: '@smoke' }, () => {
let markets: Market[];
beforeEach(() => {
cy.mockGQL((req) => {
aliasQuery(req, 'Markets', generateSimpleMarkets());
aliasQuery(req, 'MarketsCandlesQuery', generateMarketsCandles());
aliasQuery(req, 'MarketsDataQuery', generateMarketsData());
aliasQuery(req, 'MarketsCandles', generateMarketsCandles());
aliasQuery(req, 'MarketsData', generateMarketsData());
aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
aliasQuery(req, 'DealTicket', generateDealTicket());
aliasQuery(req, 'MarketTags', generateMarketTags());
@@ -34,9 +34,9 @@ describe('Market trade', { tags: '@smoke' }, () => {
});
cy.visit('/markets');
cy.wait('@Markets').then((response) => {
const data: Markets | undefined = response?.response?.body?.data;
if (data.marketsConnection.edges.length) {
markets = data.marketsConnection.edges.map((edge) => edge.node);
const data: MarketsQuery | undefined = response?.response?.body?.data;
if (data?.marketsConnection?.edges.length) {
markets = data?.marketsConnection?.edges.map((edge) => edge.node);
}
});
});
@@ -299,7 +299,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
cy.getByTestId('place-order').click();
cy.getByTestId('dialog-title').should(
'have.text',
'Confirm transaction in wallet'
'Awaiting network confirmation'
);
}
});
@@ -56,7 +56,7 @@ describe('Portfolio page', { tags: '@smoke' }, () => {
});
it('data should be properly rendered', () => {
cy.get('.ag-center-cols-container .ag-row').should('have.length', 5);
cy.get('.ag-center-cols-container .ag-row').should('have.length', 3);
cy.get(
'.ag-center-cols-container [row-id="ACCOUNT_TYPE_GENERAL-asset-id-null"]'
)
@@ -4,7 +4,9 @@ export const connectVegaWallet = () => {
const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE');
cy.getByTestId('connect-vega-wallet').click();
cy.getByTestId('connectors-list').find('button').click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-gui"]')
.click();
cy.getByTestId(form).find('#wallet').click().type(walletName);
cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase);
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
@@ -18,4 +18,14 @@ import 'cypress-real-events/support';
// Import commands.js using ES2015 syntax:
import './commands';
import registerCypressGrep from 'cypress-grep';
import { aliasQuery } from '@vegaprotocol/cypress';
registerCypressGrep();
beforeEach(() => {
// Mock chainId fetch which happens on every page for wallet connection
cy.mockGQL((req) => {
aliasQuery(req, 'ChainId', {
statistics: { __typename: 'Statistics', chainId: 'test-chain-id' },
});
});
});
@@ -77,7 +77,7 @@ export const protoCandles = [
];
export const protoMarket: Market = {
id: 'first-btcusd-id',
id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15',
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
state: MarketState.STATE_ACTIVE,
decimalPlaces: 5,
@@ -1,7 +1,7 @@
export const generateDealTicket = () => {
return {
market: {
id: 'first-btcusd-id',
id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15',
decimalPlaces: 5,
positionDecimalPlaces: 0,
state: 'STATE_ACTIVE',
@@ -21,7 +21,7 @@ export const generateMarketPositions = () => {
},
balance: '265329',
market: {
id: 'first-btcusd-id',
id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15',
__typename: 'Market',
},
},
@@ -43,7 +43,7 @@ export const generateMarketPositions = () => {
node: {
openVolume: '12',
market: {
id: 'first-btcusd-id',
id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15',
__typename: 'Market',
},
__typename: 'Position',
File diff suppressed because it is too large Load Diff
@@ -31,6 +31,8 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
updatedAt: null,
expiresAt: null,
rejectionReason: null,
liquidityProvision: null,
peggedOrder: null,
},
{
__typename: 'Order',
@@ -50,6 +52,8 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
updatedAt: null,
expiresAt: null,
rejectionReason: null,
liquidityProvision: null,
peggedOrder: null,
},
{
__typename: 'Order',
@@ -69,6 +73,8 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
updatedAt: null,
expiresAt: null,
rejectionReason: null,
liquidityProvision: null,
peggedOrder: null,
},
{
__typename: 'Order',
@@ -88,6 +94,8 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
updatedAt: null,
expiresAt: null,
rejectionReason: null,
liquidityProvision: null,
peggedOrder: null,
},
{
__typename: 'Order',
@@ -106,6 +114,8 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
createdAt: new Date(2020, 1, 27).toISOString(),
updatedAt: null,
expiresAt: null,
liquidityProvision: null,
peggedOrder: null,
rejectionReason: null,
},
];
+1
View File
@@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"sourceMap": false,
"allowSyntheticDefaultImports": true,
+1 -1
View File
@@ -20,7 +20,7 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json"
NX_VEGA_ENV = 'TESTNET'
NX_VEGA_URL="https://api.n11.testnet.vega.xyz/graphql"
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_NETWORKS={"MAINNET":"https://alpha.console.vega.xyz"}
@@ -105,9 +105,7 @@ const ConsoleLiteGrid = <T extends { id?: string }>(
);
};
const ConsoleLiteGridForwarder = forwardRef(ConsoleLiteGrid) as <
T extends { id?: string }
>(
const ConsoleLiteGridForwarder = forwardRef(ConsoleLiteGrid) as <T>(
p: Props<T> & { ref?: React.Ref<AgGridReact> }
) => React.ReactElement;
@@ -37,13 +37,13 @@ const PARTY_BALANCE_QUERY = gql`
export const DealTicketContainer = () => {
const { marketId } = useParams<{ marketId: string }>();
const { keypair } = useVegaWallet();
const { pubKey } = useVegaWallet();
const { data: partyData, loading } = useQuery<PartyBalanceQuery>(
PARTY_BALANCE_QUERY,
{
variables: { partyId: keypair?.pub },
skip: !keypair?.pub,
variables: { partyId: pubKey },
skip: !pubKey,
}
);
@@ -63,7 +63,7 @@ export const DealTicketContainer = () => {
data.market.tradableInstrument.instrument.product?.settlementAsset
}
accounts={partyData?.party?.accounts || []}
isWalletConnected={!!keypair?.pub}
isWalletConnected={!!pubKey}
/>
);
@@ -82,7 +82,7 @@ export const DealTicketContainer = () => {
return (
<section className="flex p-4 md:p-6">
<section className="w-full md:w-1/2 md:min-w-[500px]">
{keypair ? container : <ConnectWallet />}
{pubKey ? container : <ConnectWallet />}
</section>
<Baubles />
</section>
@@ -1,12 +1,17 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form';
import { Stepper } from '../stepper';
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
import {
getDefaultOrder,
useOrderValidation,
validateSize,
} from '@vegaprotocol/deal-ticket';
import { InputError } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from 'bignumber.js';
import { MarketSelector } from '@vegaprotocol/deal-ticket';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet, VegaTxStatus } from '@vegaprotocol/wallet';
import {
t,
@@ -15,14 +20,11 @@ import {
removeDecimal,
} from '@vegaprotocol/react-helpers';
import {
getDefaultOrder,
useOrderValidation,
useOrderSubmit,
getOrderDialogTitle,
getOrderDialogIntent,
getOrderDialogIcon,
OrderFeedback,
validateSize,
} from '@vegaprotocol/orders';
import { DealTicketSize } from './deal-ticket-size';
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
@@ -59,7 +61,7 @@ export const DealTicketSteps = ({
watch,
setValue,
formState: { errors },
} = useForm<Order>({
} = useForm<OrderSubmissionBody['orderSubmission']>({
mode: 'onChange',
defaultValues: getDefaultOrder(market),
});
@@ -78,15 +80,15 @@ export const DealTicketSteps = ({
fieldErrors: errors,
});
const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit();
const { keypair } = useVegaWallet();
const { pubKey } = useVegaWallet();
const estMargin = useOrderMargin({
order,
market,
partyId: keypair?.pub || '',
partyId: pubKey || '',
});
const maxTrade = useMaximumPositionSize({
partyId: keypair?.pub || '',
partyId: pubKey || '',
accounts: partyData?.party?.accounts || [],
marketId: market.id,
settlementAssetId:
@@ -203,7 +205,7 @@ export const DealTicketSteps = ({
);
const onSubmit = useCallback(
(order: Order) => {
(order: OrderSubmissionBody['orderSubmission']) => {
if (transactionStatus !== 'pending') {
submit({
...order,
@@ -7,7 +7,7 @@ import {
import * as React from 'react';
import classNames from 'classnames';
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { SIDE_NAMES } from './side-selector';
import SimpleMarketExpires from '../simple-market-list/simple-market-expires';
import { gql, useQuery } from '@apollo/client';
@@ -36,7 +36,7 @@ interface Props {
market: DealTicketMarketFragment;
isDisabled: boolean;
transactionStatus?: string;
order: Order;
order: OrderSubmissionBody['orderSubmission'];
estCloseOut: string;
estMargin: string;
quoteName: string;
@@ -33,11 +33,11 @@ const DEPOSITS_QUERY = gql`
*/
export const DepositContainer = () => {
const { VEGA_ENV } = useEnvironment();
const { keypair } = useVegaWallet();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useQuery<DepositAssets>(DEPOSITS_QUERY, {
variables: { partyId: keypair?.pub },
skip: !keypair?.pub,
variables: { partyId: pubKey },
skip: !pubKey,
});
const assets = getEnabledAssets(data);
@@ -1,16 +1,10 @@
import { useMemo, useRef } from 'react';
import { useMemo, useRef, useCallback } from 'react';
import { useOutletContext } from 'react-router-dom';
import type { AgGridReact } from 'ag-grid-react';
import { PriceCell, useDataProvider } from '@vegaprotocol/react-helpers';
import type {
AccountFieldsFragment,
AccountEventsSubscription,
} from '@vegaprotocol/accounts';
import {
accountsDataProvider,
accountsManagerUpdate,
getId,
} from '@vegaprotocol/accounts';
import type { AccountFields } from '@vegaprotocol/accounts';
import { aggregatedAccountsDataProvider, getId } from '@vegaprotocol/accounts';
import type { IGetRowsParams } from 'ag-grid-community';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import {
AssetDetailsDialog,
@@ -20,20 +14,43 @@ import { NO_DATA_MESSAGE } from '../../../constants';
import { ConsoleLiteGrid } from '../../console-lite-grid';
import { useAccountColumnDefinitions } from '.';
interface AccountObj extends AccountFieldsFragment {
id: string;
}
const AccountsManager = () => {
const { partyId = '' } = useOutletContext<{ partyId: string }>();
const { isOpen, symbol, setOpen } = useAssetDetailsDialogStore();
const gridRef = useRef<AgGridReact | null>(null);
const dataRef = useRef<AccountFields[] | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const update = useMemo(() => accountsManagerUpdate(gridRef), []);
const { data, error, loading } = useDataProvider<
AccountFieldsFragment[],
AccountEventsSubscription['accounts']
>({ dataProvider: accountsDataProvider, update, variables });
const update = useCallback(
({ data }: { data: AccountFields[] | null }) => {
if (!gridRef.current?.api) {
return false;
}
if (dataRef.current?.length) {
dataRef.current = data;
gridRef.current.api.refreshInfiniteCache();
return true;
}
return false;
},
[gridRef]
);
const { data, error, loading } = useDataProvider<AccountFields[], never>({
dataProvider: aggregatedAccountsDataProvider,
update,
variables,
});
dataRef.current = data;
const getRows = async ({
successCallback,
startRow,
endRow,
}: IGetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current?.length ?? -1;
successCallback(rowsThisBlock, lastRow);
};
const { columnDefs, defaultColDef } = useAccountColumnDefinitions();
return (
<>
@@ -43,8 +60,11 @@ const AccountsManager = () => {
data={data}
noDataMessage={NO_DATA_MESSAGE}
>
<ConsoleLiteGrid<AccountObj>
rowData={data as AccountObj[]}
<ConsoleLiteGrid<AccountFields>
rowData={data?.length ? undefined : []}
rowModelType={data?.length ? 'infinite' : 'clientSide'}
ref={gridRef}
datasource={{ getRows }}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
components={{ PriceCell }}
@@ -1,45 +1,9 @@
import React, { useMemo } from 'react';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import type { SummaryRow } from '@vegaprotocol/react-helpers';
import type { AccountFieldsFragment } from '@vegaprotocol/accounts';
import type { AccountFields } from '@vegaprotocol/accounts';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import type {
ColDef,
GroupCellRendererParams,
ValueFormatterParams,
} from 'ag-grid-community';
import type { AccountType } from '@vegaprotocol/types';
import { AccountTypeMapping } from '@vegaprotocol/types';
interface AccountsTableValueFormatterParams extends ValueFormatterParams {
data: AccountFieldsFragment;
}
const comparator = (
valueA: string,
valueB: string,
nodeA: { data: AccountFieldsFragment & SummaryRow },
nodeB: { data: AccountFieldsFragment & SummaryRow },
isInverted: boolean
) => {
if (valueA < valueB) {
return -1;
}
if (valueA > valueB) {
return 1;
}
if (nodeA.data.__summaryRow) {
return isInverted ? -1 : 1;
}
if (nodeB.data.__summaryRow) {
return isInverted ? 1 : -1;
}
return 0;
};
import type { ColDef, GroupCellRendererParams } from 'ag-grid-community';
import type { VegaValueFormatterParams } from '@vegaprotocol/ui-toolkit';
const useAccountColumnDefinitions = () => {
const { open } = useAssetDetailsDialogStore();
@@ -49,7 +13,6 @@ const useAccountColumnDefinitions = () => {
colId: 'account-asset',
headerName: t('Asset'),
field: 'asset.symbol',
comparator,
headerClass: 'uppercase justify-start',
cellClass: 'uppercase flex h-full items-center md:pl-4',
cellRenderer: ({ value }: GroupCellRendererParams) =>
@@ -71,28 +34,34 @@ const useAccountColumnDefinitions = () => {
),
},
{
colId: 'type',
headerName: t('Type'),
field: 'type',
cellClass: 'uppercase !flex h-full items-center',
valueFormatter: ({ value }: ValueFormatterParams) =>
value ? AccountTypeMapping[value as AccountType] : '-',
},
{
colId: 'market',
headerName: t('Market'),
cellClass: 'uppercase !flex h-full items-center',
field: 'market.tradableInstrument.instrument.name',
valueFormatter: "value || '—'",
},
{
colId: 'balance',
headerName: t('Balance'),
field: 'balance',
cellClass: 'uppercase !flex h-full items-center',
colId: 'deposited',
headerName: t('Deposited'),
field: 'deposited',
cellRenderer: 'PriceCell',
valueFormatter: ({ value, data }: AccountsTableValueFormatterParams) =>
addDecimalsFormatNumber(value, data.asset.decimals),
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<AccountFields, 'deposited'>) => {
if (value && data) {
return addDecimalsFormatNumber(value, data.asset.decimals);
}
return '-';
},
},
{
colId: 'used',
headerName: t('Used'),
field: 'used',
cellRenderer: 'PriceCell',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<AccountFields, 'used'>) => {
if (value && data) {
return addDecimalsFormatNumber(value, data.asset.decimals);
}
return '-';
},
},
];
}, [open]);
@@ -1,7 +1,7 @@
import { useRef } from 'react';
import { useOutletContext } from 'react-router-dom';
import type { AgGridReact } from 'ag-grid-react';
import type { TradeWithMarket } from '@vegaprotocol/fills';
import type { Trade } from '@vegaprotocol/fills';
import { useFillsList } from '@vegaprotocol/fills';
import type { BodyScrollEndEvent, BodyScrollEvent } from 'ag-grid-community';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
@@ -37,7 +37,7 @@ const FillsManager = () => {
data={data?.length ? data : null}
noDataMessage={NO_DATA_MESSAGE}
>
<ConsoleLiteGrid<TradeWithMarket>
<ConsoleLiteGrid<Trade>
ref={gridRef}
rowModelType="infinite"
datasource={{ getRows }}
@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import type { ColDef, ValueFormatterParams } from 'ag-grid-community';
import type { TradeWithMarket } from '@vegaprotocol/fills';
import type { Trade } from '@vegaprotocol/fills';
import classNames from 'classnames';
import {
addDecimal,
@@ -34,7 +34,7 @@ const useColumnDefinitions = ({ partyId }: Props) => {
headerClass: 'uppercase',
field: 'size',
width: 100,
cellClass: ({ data }: { data: TradeWithMarket }) => {
cellClass: ({ data }: { data: Trade }) => {
return classNames('!flex h-full items-center justify-center', {
[positiveClassNames]: data?.buyer.id === partyId,
[negativeClassNames]: data?.seller.id,
@@ -2,7 +2,7 @@ import { useRef, useState } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { BodyScrollEndEvent, BodyScrollEvent } from 'ag-grid-community';
import { useOutletContext } from 'react-router-dom';
import type { OrderWithMarket } from '@vegaprotocol/orders';
import type { Order } from '@vegaprotocol/orders';
import {
useOrderCancel,
useOrderListData,
@@ -20,7 +20,7 @@ import useColumnDefinitions from './use-column-definitions';
const OrdersManager = () => {
const { partyId } = useOutletContext<{ partyId: string }>();
const [editOrder, setEditOrder] = useState<OrderWithMarket | null>(null);
const [editOrder, setEditOrder] = useState<Order | null>(null);
const orderCancel = useOrderCancel();
const orderEdit = useOrderEdit(editOrder);
const { columnDefs, defaultColDef } = useColumnDefinitions({
@@ -54,7 +54,7 @@ const OrdersManager = () => {
data={data?.length ? data : null}
noDataMessage={NO_DATA_MESSAGE}
>
<ConsoleLiteGrid<OrderWithMarket>
<ConsoleLiteGrid<Order>
ref={gridRef}
rowModelType={data?.length ? 'infinite' : 'clientSide'}
rowData={data?.length ? undefined : []}
@@ -13,7 +13,7 @@ import {
} from '@vegaprotocol/react-helpers';
import type {
Orders_party_ordersConnection_edges_node,
OrderWithMarket,
Order,
CancelOrderArgs,
} from '@vegaprotocol/orders';
import { isOrderActive } from '@vegaprotocol/orders';
@@ -35,7 +35,7 @@ type StatusKey = keyof typeof OrderStatusMapping;
type RejectReasonKey = keyof typeof OrderRejectionReasonMapping;
type OrderTimeKey = keyof typeof OrderTimeInForceMapping;
interface Props {
setEditOrder: (order: OrderWithMarket) => void;
setEditOrder: (order: Order) => void;
orderCancel: {
cancel: (args: CancelOrderArgs) => void;
[key: string]: unknown;
@@ -6,11 +6,11 @@ import { HorizontalMenu } from '../horizontal-menu';
import * as constants from './constants';
export const Portfolio = () => {
const { keypair } = useVegaWallet();
const { pubKey } = useVegaWallet();
const { pathname } = useLocation();
const module = pathname.split('/portfolio/')?.[1] ?? '';
const outlet = useOutlet({ partyId: keypair?.pub || '' });
if (!keypair) {
const outlet = useOutlet({ partyId: pubKey || '' });
if (!pubKey) {
return (
<section className="xl:w-1/2">
<ConnectWallet />
@@ -11,7 +11,7 @@ import type {
PositionsTableValueFormatterParams,
Position,
} from '@vegaprotocol/positions';
import { AmountCell, ProgressBarCell } from '@vegaprotocol/positions';
import { AmountCell } from '@vegaprotocol/positions';
import type {
CellRendererSelectorResult,
ICellRendererParams,
@@ -20,7 +20,7 @@ import type {
ColDef,
} from 'ag-grid-community';
import { MarketTradingMode } from '@vegaprotocol/types';
import { Intent } from '@vegaprotocol/ui-toolkit';
import { Intent, ProgressBarCell } from '@vegaprotocol/ui-toolkit';
const EmptyCell = () => '';
@@ -5,7 +5,7 @@ import { EXPIRE_DATE_FORMAT } from '../../constants';
const SimpleMarketExpires = ({
tags,
}: {
tags: ReadonlyArray<string> | null;
tags?: ReadonlyArray<string> | null;
}) => {
if (tags) {
const dateFound = tags.reduce<Date | null>((agg, tag) => {
@@ -58,21 +58,17 @@ let marketsMock = [
},
] as unknown as Market[];
const LIB = '@vegaprotocol/market-list';
const useMarketList = () => {
const LIB = '@vegaprotocol/react-helpers';
const useDataProvider = () => {
return {
data: {
markets: marketsMock,
marketsData: [],
marketsCandles: [],
},
data: marketsMock,
loading: false,
error: false,
};
};
jest.mock(LIB, () => ({
...jest.requireActual(LIB),
useMarketList: jest.fn(() => useMarketList()),
useDataProvider: jest.fn(() => useDataProvider()),
}));
const mockIsTradable = jest.fn((_arg) => true);
@@ -1,7 +1,10 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef, useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import type { AgGridReact } from 'ag-grid-react';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import {
useScreenDimensions,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { MarketState } from '@vegaprotocol/types';
import useMarketsFilterData from './use-markets-filter-data';
@@ -9,8 +12,9 @@ import useColumnDefinitions from './use-column-definitions';
import SimpleMarketToolbar from './simple-market-toolbar';
import { IS_MARKET_TRADABLE } from '../../constants';
import { ConsoleLiteGrid } from '../console-lite-grid';
import type { Market, MarketsListData } from '@vegaprotocol/market-list';
import { useMarketList } from '@vegaprotocol/market-list';
import type { Market } from '@vegaprotocol/market-list';
import { Interval } from '@vegaprotocol/types';
import { marketsWithCandlesProvider } from '@vegaprotocol/market-list';
export type MarketWithPercentChange = Market & {
percentChange?: number | '-';
@@ -29,8 +33,19 @@ const SimpleMarketList = () => {
const statusesRef = useRef<Record<string, MarketState | ''>>({});
const gridRef = useRef<AgGridReact | null>(null);
const { data, error, loading } = useMarketList();
const localData = useMarketsFilterData(data as MarketsListData, params);
const variables = useMemo(() => {
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
return {
since: new Date(yesterday * 1000).toISOString(),
interval: Interval.INTERVAL_I1H,
};
}, []);
const { data, error, loading } = useDataProvider({
dataProvider: marketsWithCandlesProvider,
variables,
noUpdate: true,
});
const localData = useMarketsFilterData(data, params);
const handleOnGridReady = useCallback(() => {
gridRef.current?.api?.sizeColumnsToFit();
@@ -38,7 +53,7 @@ const SimpleMarketList = () => {
useEffect(() => {
const statuses: Record<string, MarketState | ''> = {};
data?.markets?.forEach((market) => {
data?.forEach((market) => {
statuses[market.id] = market.state || '';
});
statusesRef.current = statuses;
@@ -62,7 +77,7 @@ const SimpleMarketList = () => {
return (
<div className="h-full p-4 md:p-6 grid grid-rows-[min-content,1fr]">
<SimpleMarketToolbar data={data?.markets || []} />
<SimpleMarketToolbar data={data || []} />
<AsyncRenderer loading={loading} error={error} data={localData}>
<ConsoleLiteGrid<MarketWithPercentChange>
classNamesParam="mb-32 min-h-[300px]"
@@ -70,6 +85,7 @@ const SimpleMarketList = () => {
rowData={localData}
defaultColDef={defaultColDef}
handleRowClicked={handleRowClicked}
getRowId={({ data }) => data.id}
/>
</AsyncRenderer>
</div>
@@ -15,13 +15,13 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { MarketState } from '@vegaprotocol/types';
import useMarketFiltersData from '../../hooks/use-markets-filter';
import type { Markets_marketsConnection_edges_node } from '@vegaprotocol/market-list';
import type { Market } from '@vegaprotocol/market-list';
import { HorizontalMenu } from '../horizontal-menu';
import type { HorizontalMenuItem } from '../horizontal-menu';
import * as constants from './constants';
interface Props {
data: Markets_marketsConnection_edges_node[];
data: Market[];
}
const SimpleMarketToolbar = ({ data }: Props) => {
@@ -7,7 +7,7 @@ import { Icon } from '@vegaprotocol/ui-toolkit';
import type { ValueSetterParams } from 'ag-grid-community';
import { IconNames } from '@blueprintjs/icons';
import { IS_MARKET_TRADABLE, MARKET_STATES_MAP } from '../../constants';
import type { Candle, Market } from '@vegaprotocol/market-list';
import type { MarketWithCandles as Market } from '@vegaprotocol/market-list';
interface Props {
isMobile: boolean;
@@ -69,15 +69,16 @@ const useColumnDefinitions = ({ isMobile }: Props) => {
data,
setValue,
}: {
data: { id: string; candles: Candle[] };
data: Market;
setValue: (arg: unknown) => void;
}) => (
<SimpleMarketPercentChange
candles={data.candles}
marketId={data.id}
setValue={setValue}
/>
),
}) =>
data.candles && (
<SimpleMarketPercentChange
candles={data.candles}
marketId={data.id}
setValue={setValue}
/>
),
comparator: (valueA: number | '-', valueB: number | '-') => {
if (valueA === valueB) return 0;
if (valueA === '-') {
@@ -1,12 +1,15 @@
import { useMemo } from 'react';
import { MarketState } from '@vegaprotocol/types';
import type { MarketsListData } from '@vegaprotocol/market-list';
import type { MarketWithCandles } from '@vegaprotocol/market-list';
import type { RouterParams } from './simple-market-list';
const useMarketsFilterData = (data: MarketsListData, params: RouterParams) => {
const useMarketsFilterData = (
data: MarketWithCandles[] | null,
params: RouterParams
) => {
return useMemo(() => {
const markets =
data?.markets?.filter((item) => {
return (
data?.filter((item) => {
if (
params.product &&
params.product !==
@@ -32,21 +35,9 @@ const useMarketsFilterData = (data: MarketsListData, params: RouterParams) => {
return false;
}
return true;
}) || [];
return markets.map((market) => ({
...market,
candles: (data?.marketsCandles || [])
.filter((c) => c.marketId === market.id)
.map((c) => c.candles),
}));
}, [
data?.marketsCandles,
data?.markets,
params.product,
params.asset,
params.state,
]);
}) || []
);
}, [data, params.product, params.asset, params.state]);
};
export default useMarketsFilterData;
@@ -10,8 +10,8 @@ export const VegaWalletConnectButton = ({
setConnectDialog,
setManageDialog,
}: VegaWalletConnectButtonProps) => {
const { keypair } = useVegaWallet();
const isConnected = keypair !== null;
const { pubKey } = useVegaWallet();
const isConnected = pubKey !== null;
const handleClick = () => {
if (isConnected) {
@@ -31,7 +31,7 @@ export const VegaWalletConnectButton = ({
onClick={handleClick}
className="ml-auto inline-block text-ui-small font-mono hover:underline"
>
{isConnected ? truncateByChars(keypair.pub) : 'Connect Vega wallet'}
{isConnected ? truncateByChars(pubKey) : 'Connect Vega wallet'}
</button>
</span>
);
@@ -1,7 +1,7 @@
import { MockedProvider } from '@apollo/client/testing';
import { renderHook } from '@testing-library/react';
import { Side } from '@vegaprotocol/types';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import useCalculateSlippage from './use-calculate-slippage';
const mockData = {
@@ -84,7 +84,10 @@ describe('useCalculateSlippage Hook', () => {
() =>
useCalculateSlippage({
marketId: 'marketId',
order: { size: '10', side: Side.SIDE_BUY } as Order,
order: {
size: '10',
side: Side.SIDE_BUY,
} as OrderSubmissionBody['orderSubmission'],
}),
{
wrapper: MockedProvider,
@@ -98,7 +101,10 @@ describe('useCalculateSlippage Hook', () => {
() =>
useCalculateSlippage({
marketId: 'marketId',
order: { size: '10', side: Side.SIDE_SELL } as Order,
order: {
size: '10',
side: Side.SIDE_SELL,
} as OrderSubmissionBody['orderSubmission'],
}),
{
wrapper: MockedProvider,
@@ -121,7 +127,10 @@ describe('useCalculateSlippage Hook', () => {
() =>
useCalculateSlippage({
marketId: 'marketId',
order: { size: '10', side: Side.SIDE_SELL } as Order,
order: {
size: '10',
side: Side.SIDE_SELL,
} as OrderSubmissionBody['orderSubmission'],
}),
{
wrapper: MockedProvider,
@@ -3,7 +3,7 @@ import { Side } from '@vegaprotocol/types';
import { useOrderBookData } from '@vegaprotocol/market-depth';
import { marketProvider } from '@vegaprotocol/market-list';
import type { Market } from '@vegaprotocol/market-list';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { BigNumber } from 'bignumber.js';
import {
formatNumber,
@@ -13,7 +13,7 @@ import {
interface Props {
marketId: string;
order: Order;
order: OrderSubmissionBody['orderSubmission'];
}
const useCalculateSlippage = ({ marketId, order }: Props) => {
@@ -14,6 +14,9 @@ const useMarketFilters = (data: Market[]) => {
const product = item.tradableInstrument.instrument.product.__typename;
const asset =
item.tradableInstrument.instrument.product.settlementAsset.symbol;
if (!product) {
return;
}
if (!(product in localAssetPerProduct)) {
localAssetPerProduct[product] = new Set<string>();
}
@@ -1,5 +1,5 @@
import useMarketPositions from './use-market-positions';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
import { useSettlementAccount } from './use-settlement-account';
import { AccountType, Side } from '@vegaprotocol/types';
@@ -11,7 +11,7 @@ interface Props {
marketId: string;
price?: string;
settlementAssetId: string;
order: Order;
order: OrderSubmissionBody['orderSubmission'];
}
const getSize = (balance: string, price: string) =>
@@ -2,7 +2,7 @@ import * as React from 'react';
import { renderHook } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import useOrderCloseOut from './use-order-closeout';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
@@ -48,7 +48,7 @@ describe('useOrderCloseOut Hook', () => {
const { result } = renderHook(
() =>
useOrderCloseOut({
order: order as Order,
order: order as OrderSubmissionBody['orderSubmission'],
market: market as DealTicketMarketFragment,
partyData: partyData as PartyBalanceQuery,
}),
@@ -65,7 +65,10 @@ describe('useOrderCloseOut Hook', () => {
const { result } = renderHook(
() =>
useOrderCloseOut({
order: { ...order, side: 'SIDE_SELL' } as Order,
order: {
...order,
side: 'SIDE_SELL',
} as OrderSubmissionBody['orderSubmission'],
market: market as DealTicketMarketFragment,
partyData: partyData as PartyBalanceQuery,
}),
@@ -82,7 +85,10 @@ describe('useOrderCloseOut Hook', () => {
const { result } = renderHook(
() =>
useOrderCloseOut({
order: { ...order, side: 'SIDE_SELL' } as Order,
order: {
...order,
side: 'SIDE_SELL',
} as OrderSubmissionBody['orderSubmission'],
market: market as DealTicketMarketFragment,
}),
{
@@ -1,5 +1,5 @@
import { BigNumber } from 'bignumber.js';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
import { useSettlementAccount } from './use-settlement-account';
@@ -46,13 +46,13 @@ const CLOSEOUT_PRICE_QUERY = gql`
`;
interface Props {
order: Order;
order: OrderSubmissionBody['orderSubmission'];
market: DealTicketMarketFragment;
partyData?: PartyBalanceQuery;
}
const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
const { keypair } = useVegaWallet();
const { pubKey } = useVegaWallet();
const account = useSettlementAccount(
market.tradableInstrument.instrument.product.settlementAsset.id,
partyData?.party?.accounts || []
@@ -61,15 +61,15 @@ const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
CLOSEOUT_PRICE_QUERY,
{
pollInterval: 5000,
variables: { partyId: keypair?.pub || '' },
skip: !keypair?.pub,
variables: { partyId: pubKey || '' },
skip: !pubKey,
}
);
const markPriceData = useMarketData(market.id);
const marketPositions = useMarketPositions({
marketId: market.id,
partyId: keypair?.pub || '',
partyId: pubKey || '',
});
const marginMaintenanceLevel = new BigNumber(
@@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react';
import { useQuery } from '@apollo/client';
import { BigNumber } from 'bignumber.js';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
import type { PositionMargin } from './use-market-positions';
import useOrderMargin from './use-order-margin';
@@ -53,7 +53,7 @@ describe('useOrderMargin Hook', () => {
it('margin should be properly calculated', () => {
const { result } = renderHook(() =>
useOrderMargin({
order: order as Order,
order: order as OrderSubmissionBody['orderSubmission'],
market: market as DealTicketMarketFragment,
partyId,
})
@@ -71,7 +71,7 @@ describe('useOrderMargin Hook', () => {
it('fees should be properly calculated', () => {
const { result } = renderHook(() =>
useOrderMargin({
order: order as Order,
order: order as OrderSubmissionBody['orderSubmission'],
market: market as DealTicketMarketFragment,
partyId,
})
@@ -83,7 +83,7 @@ describe('useOrderMargin Hook', () => {
mockMarketPositions = null;
const { result } = renderHook(() =>
useOrderMargin({
order: order as Order,
order: order as OrderSubmissionBody['orderSubmission'],
market: market as DealTicketMarketFragment,
partyId,
})
@@ -110,7 +110,7 @@ describe('useOrderMargin Hook', () => {
};
const { result } = renderHook(() =>
useOrderMargin({
order: order as Order,
order: order as OrderSubmissionBody['orderSubmission'],
market: market as DealTicketMarketFragment,
partyId,
})
@@ -1,5 +1,5 @@
import { BigNumber } from 'bignumber.js';
import type { Order } from '@vegaprotocol/orders';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { gql, useQuery } from '@apollo/client';
import type {
EstimateOrder,
@@ -46,7 +46,7 @@ export const ESTIMATE_ORDER_QUERY = gql`
`;
interface Props {
order: Order;
order: OrderSubmissionBody['orderSubmission'];
market: DealTicketMarketFragment;
partyId: string;
}
+5
View File
@@ -27,9 +27,14 @@ module.exports = defineConfig({
networkQueryUrl: 'http://localhost:3028/query',
ethUrl: 'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
commitHash: 'dev',
CYPRESS_TEARDOWN_NETWORK_AFTER_FLOWS: true,
tsConfig: 'tsconfig.json',
grepTags: '@regression @smoke @slow',
grepFilterSpecs: true,
grepOmitFiltered: true,
vegaWalletName: 'capsule_wallet',
vegaWalletLocation: '~/.vegacapsule/testnet/wallet',
vegaWalletPublicKey:
'02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
},
});
@@ -1,4 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io"
}
@@ -0,0 +1 @@
123
@@ -0,0 +1 @@
ozone access unlock valid olympic save include omit supply green clown session
@@ -1,5 +1,3 @@
import '../support/common.functions';
context('Asset page', { tags: '@regression' }, function () {
describe('Verify elements on page', function () {
const assetsNavigation = 'a[href="/assets"]';
@@ -1,5 +1,3 @@
import '../support/common.functions';
context('Blocks page', { tags: '@regression' }, function () {
before('visit token home page', function () {
cy.visit('/');
@@ -1,5 +1,3 @@
import '../support/common.functions';
//Tests set to skip until market bug for capsule checkpoint is fixed
context.skip('Market page', { tags: '@regression' }, function () {
describe('Verify elements on page', function () {
@@ -1,5 +1,3 @@
import '../support/common.functions';
context('Network parameters page', { tags: '@smoke' }, function () {
before('visit token home page', function () {
cy.visit('/');
@@ -0,0 +1,154 @@
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const partiesMenuHeader = 'a[href="/parties"]';
const partiesSearchBox = '[data-testid="party-input"]';
const partiesSearchAction = '[data-testid="go-submit"]';
const partiesJsonSection = '[data-testid="parties-json"]';
const txTimeout = Cypress.env('txTimeout');
let assetData = {
fUSDC: { id: 'fUSDC', name: 'USDC (fake)', amount: '10' },
fBTC: { id: 'fBTC', name: 'BTC (fake)', amount: '6' },
fEURO: { id: 'fEURO', name: 'EURO (fake)', amount: '8' },
fDAI: { id: 'fDAI', name: 'DAI (fake)', amount: '2' },
};
const assetsInTest = Object.keys(assetData);
context('Parties page', { tags: '@regression' }, function () {
before('send-faucet assets to connected vega wallet', function () {
cy.vega_wallet_import();
assetsInTest.forEach((asset) => {
cy.vega_wallet_receive_fauceted_asset(
assetData[asset].name,
assetData[asset].amount,
vegaWalletPublicKey
);
});
cy.visit('/');
});
describe('Verify parties page content', function () {
before('navigate to parties page and search for party', function () {
cy.get(partiesMenuHeader).click();
// Deliberate slow entry of party id/key - enabling transactions to sync
cy.get(partiesSearchBox).type(vegaWalletPublicKey, { delay: 120 });
cy.get(partiesSearchAction).click();
});
it('should see party address id - having searched', function () {
cy.contains('Address')
.siblings()
.contains(vegaWalletPublicKey)
.should('be.visible');
});
it('should see each asset - within asset data section', function () {
assetsInTest.forEach((asset) => {
cy.contains(assetData[asset].name, txTimeout).should('be.visible');
cy.contains(assetData[asset].name)
.siblings()
.contains(assetData[asset].id)
.should('be.visible');
cy.contains(assetData[asset].name, txTimeout)
.parent()
.siblings()
.within(() => {
cy.get_asset_decimals(asset).then((assetDecimals) => {
cy.contains_exactly(
assetData[asset].amount + '.' + assetDecimals
).should('be.visible');
});
});
});
});
it('should be able to copy the party address id', function () {
cy.monitor_clipboard().as('clipboard');
cy.contains('Address').siblings().last().click();
cy.get('@clipboard')
.get_copied_text_from_clipboard()
.should('equal', vegaWalletPublicKey);
});
it('should be able to copy an asset id', function () {
cy.monitor_clipboard().as('clipboard');
cy.contains(assetData.fDAI.name, txTimeout).should('be.visible');
cy.contains(assetData.fDAI.name)
.siblings()
.within(() => {
cy.get('[data-state="closed"]').last().click({ force: true });
});
cy.get('@clipboard')
.get_copied_text_from_clipboard()
.should('equal', assetData.fDAI.id);
});
it('should be able to see JSON of each asset containing correct balance and decimals', function () {
cy.get(partiesJsonSection).should('be.visible');
assetsInTest.forEach((asset) => {
cy.get(partiesJsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.get_party_accounts_data()
.then((accounts) => {
cy.get_asset_decimals(asset).then((assetDecimals) => {
assert.equal(
accounts[asset].balance,
assetData[asset].amount + assetDecimals
);
});
});
});
});
after(
'teardown environment to prevent test data bleeding into other tests',
function () {
if (Cypress.env('CYPRESS_TEARDOWN_NETWORK_AFTER_FLOWS')) {
cy.restart_vegacapsule_network();
}
}
);
Cypress.Commands.add('get_asset_decimals', (assetID) => {
cy.get_asset_information().then((assetsInfo) => {
const assetDecimals = assetsInfo[assetData[assetID].name].decimals;
let decimals = '';
for (let i = 0; i < assetDecimals; i++) decimals += '0';
return decimals;
});
});
Cypress.Commands.add(
'convert_string_json_to_js_object',
{ prevSubject: true },
(jsonBlobString) => {
return JSON.parse(jsonBlobString);
}
);
Cypress.Commands.add(
'get_party_accounts_data',
{ prevSubject: true },
(jsObject) => {
const accounts = jsObject.party.accounts.reduce(function (
account,
entry
) {
account[entry.asset.id] = {
balance: entry.balance,
id: entry.asset.id,
decimals: entry.asset.decimals,
name: entry.asset.name,
};
return account;
},
{});
return accounts;
}
);
});
});
@@ -1,5 +1,3 @@
import '../support/common.functions';
context('Validator page', { tags: '@smoke' }, function () {
describe('Verify elements on page', function () {
const validatorNavigation = 'a[href="/validators"]';
@@ -51,3 +51,19 @@ Cypress.Commands.add('common_verify_json_int_values', function (expectedNum) {
cy.wrap($paramValue).should('not.be.empty');
});
});
Cypress.Commands.add('monitor_clipboard', () => {
cy.window().then((win) => {
return cy.stub(win, 'prompt').returns(win.prompt);
});
});
Cypress.Commands.add(
'get_copied_text_from_clipboard',
{ prevSubject: true },
(clipboard) => {
// Must first setup with cy.monitor_clipboard().as('clipboard')
// This function then chained off a cy.get('@clipboard')
return clipboard.args[0][1];
}
);
+1
View File
@@ -14,5 +14,6 @@
// ***********************************************************
import '@vegaprotocol/cypress';
import './common.functions.js';
import registerCypressGrep from 'cypress-grep';
registerCypressGrep();
+1
View File
@@ -5,6 +5,7 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
# App flags
NX_EXPLORER_ASSETS=1
+1
View File
@@ -7,3 +7,4 @@ NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https:
NX_VEGA_ENV=TESTNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_URL=https://api.n09.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
@@ -14,7 +14,12 @@ jest.mock('../search', () => ({
const renderComponent = () => (
<MemoryRouter>
<Header />
<Header
theme="dark"
toggleTheme={jest.fn()}
menuOpen={false}
setMenuOpen={jest.fn()}
/>
</MemoryRouter>
);
@@ -0,0 +1,231 @@
import {
detectTypeByFetching,
detectTypeFromQuery,
getSearchType,
isBlock,
isHexadecimal,
isNetworkParty,
isNonHex,
SearchTypes,
toHex,
toNonHex,
} from './detect-search';
import { DATA_SOURCES } from '../../config';
global.fetch = jest.fn();
describe('Detect Search', () => {
it("should detect that it's a hexadecimal", () => {
const expected = true;
const testString =
'0x073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
const actual = isHexadecimal(testString);
expect(actual).toBe(expected);
});
it("should detect that it's not hexadecimal", () => {
const expected = true;
const testString =
'073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
const actual = isNonHex(testString);
expect(actual).toBe(expected);
});
it("should detect that it's a network party", () => {
const expected = true;
const testString = 'network';
const actual = isNetworkParty(testString);
expect(actual).toBe(expected);
});
it("should detect that it's a block", () => {
const expected = true;
const testString = '3188';
const actual = isBlock(testString);
expect(actual).toBe(expected);
});
it('should convert from non-hex to hex', () => {
const expected = '0x123';
const testString = '123';
const actual = toHex(testString);
expect(actual).toBe(expected);
});
it('should convert from hex to non-hex', () => {
const expected = '123';
const testString = '0x123';
const actual = toNonHex(testString);
expect(actual).toBe(expected);
});
it("should detect type client side from query if it's a hexadecimal", () => {
const expected = [SearchTypes.Party, SearchTypes.Transaction];
const testString =
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("should detect type client side from query if it's a non hex", () => {
const expected = [SearchTypes.Party, SearchTypes.Transaction];
const testString =
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("should detect type client side from query if it's a network party", () => {
const expected = [SearchTypes.Party];
const testString = 'network';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("should detect type client side from query if it's a block (number)", () => {
const expected = [SearchTypes.Block];
const testString = '23432';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("detectTypeByFetching should call fetch with hex query it's a transaction", async () => {
const query = 'abc';
const type = SearchTypes.Transaction;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
result: {
tx: query,
},
}),
})
)
);
const result = await detectTypeByFetching(query, type);
expect(fetch).toHaveBeenCalledWith(
`${DATA_SOURCES.tendermintUrl}/tx?hash=0x${query}`
);
expect(result).toBe(type);
});
it("detectTypeByFetching should call fetch with non-hex query it's a party", async () => {
const query = 'abc';
const type = SearchTypes.Party;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
result: {
txs: [query],
},
}),
})
)
);
const result = await detectTypeByFetching(query, type);
expect(fetch).toHaveBeenCalledWith(
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${query}'"`
);
expect(result).toBe(type);
});
it('detectTypeByFetching should return undefined if no matches', async () => {
const query = 'abc';
const type = SearchTypes.Party;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: false,
})
)
);
const result = await detectTypeByFetching(query, type);
expect(fetch).toHaveBeenCalledWith(
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${query}'"`
);
expect(result).toBe(undefined);
});
it('getSearchType should return party from fetch response', async () => {
const query =
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const expected = SearchTypes.Party;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
result: {
txs: [query],
},
}),
})
)
);
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return party from transaction response', async () => {
const query =
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const expected = SearchTypes.Transaction;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
result: {
tx: query,
},
}),
})
)
);
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return undefined from transaction response', async () => {
const query =
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const expected = undefined;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: false,
})
)
);
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return block if query is number', async () => {
const query = '123';
const expected = SearchTypes.Block;
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return party if query is network', async () => {
const query = 'network';
const expected = SearchTypes.Party;
const result = await getSearchType(query);
expect(result).toBe(expected);
});
});
@@ -0,0 +1,104 @@
import { DATA_SOURCES } from '../../config';
export enum SearchTypes {
Transaction = 'transaction',
Party = 'party',
Block = 'block',
Order = 'order',
}
export const TX_LENGTH = 64;
export const isHexadecimal = (search: string) =>
search.startsWith('0x') && search.length === 2 + TX_LENGTH;
export const isNonHex = (search: string) =>
!search.startsWith('0x') && search.length === TX_LENGTH;
export const isBlock = (search: string) => !Number.isNaN(Number(search));
export const isNetworkParty = (search: string) => search === 'network';
export const toHex = (query: string) =>
isHexadecimal(query) ? query : `0x${query}`;
export const toNonHex = (query: string) =>
isNonHex(query) ? query : `${query.replace('0x', '')}`;
export const detectTypeFromQuery = (
query: string
): SearchTypes[] | undefined => {
const i = query.toLowerCase();
if (isHexadecimal(i) || isNonHex(i)) {
return [SearchTypes.Party, SearchTypes.Transaction];
} else if (isNetworkParty(i)) {
return [SearchTypes.Party];
} else if (isBlock(i)) {
return [SearchTypes.Block];
}
return undefined;
};
export const detectTypeByFetching = async (
query: string,
type: SearchTypes
): Promise<SearchTypes | undefined> => {
const TYPES = [SearchTypes.Party, SearchTypes.Transaction];
if (!TYPES.includes(type)) {
throw new Error('Search type provided not recognised');
}
if (type === SearchTypes.Transaction) {
const hash = toHex(query);
const request = await fetch(
`${DATA_SOURCES.tendermintUrl}/tx?hash=${hash}`
);
if (request?.ok) {
const body = await request.json();
if (body?.result?.tx) {
return SearchTypes.Transaction;
}
}
} else if (type === SearchTypes.Party) {
const party = toNonHex(query);
const request = await fetch(
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${party}'"`
);
if (request.ok) {
const body = await request.json();
if (body?.result?.txs?.length) {
return SearchTypes.Party;
}
}
}
return undefined;
};
export const getSearchType = async (
query: string
): Promise<SearchTypes | undefined> => {
const searchTypes = detectTypeFromQuery(query);
const hasResults = searchTypes?.length;
if (hasResults) {
if (hasResults > 1) {
const promises = searchTypes.map((type) =>
detectTypeByFetching(query, type)
);
const results = await Promise.all(promises);
return results.find((type) => type !== undefined);
}
return searchTypes[0];
}
return undefined;
};
@@ -2,14 +2,23 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { Search } from './search';
import { MemoryRouter } from 'react-router-dom';
import { Routes } from '../../routes/route-names';
import { SearchTypes, getSearchType } from './detect-search';
const mockedNavigate = jest.fn();
const mockGetSearchType = getSearchType as jest.MockedFunction<
typeof getSearchType
>;
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedNavigate,
}));
jest.mock('./detect-search', () => ({
...jest.requireActual('./detect-search'),
getSearchType: jest.fn(),
}));
beforeEach(() => {
mockedNavigate.mockClear();
});
@@ -39,9 +48,10 @@ describe('Search', () => {
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
"Something doesn't look right"
'Transaction type is not recognised'
);
});
it('should render error if no input is given', async () => {
render(renderComponent());
const { button } = getInputs();
@@ -49,47 +59,14 @@ describe('Search', () => {
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'Search required'
);
});
it('should render error if transaction is not hex', async () => {
render(renderComponent());
const { button, input } = getInputs();
fireEvent.change(input, {
target: {
value:
'0x123456789012345678901234567890123456789012345678901234567890123Q',
},
});
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'Transaction is not hexadecimal'
);
});
it('should render error if transaction is not hex and does not have leading 0x', async () => {
render(renderComponent());
const { button, input } = getInputs();
fireEvent.change(input, {
target: {
value:
'123456789012345678901234567890123456789012345678901234567890123Q',
},
});
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'Transaction is not hexadecimal'
'Search query required'
);
});
it('should redirect to transactions page', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
fireEvent.change(input, {
target: {
value:
@@ -108,6 +85,7 @@ describe('Search', () => {
it('should redirect to transactions page without proceeding 0x', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
fireEvent.change(input, {
target: {
value:
@@ -123,9 +101,48 @@ describe('Search', () => {
});
});
it('should redirect to parties page', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
fireEvent.change(input, {
target: {
value:
'0x1234567890123456789012345678901234567890123456789012345678901234',
},
});
fireEvent.click(button);
await waitFor(() => {
expect(mockedNavigate).toBeCalledWith(
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
);
});
});
it('should redirect to parties page without proceeding 0x', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
fireEvent.change(input, {
target: {
value:
'1234567890123456789012345678901234567890123456789012345678901234',
},
});
fireEvent.click(button);
await waitFor(() => {
expect(mockedNavigate).toBeCalledWith(
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
);
});
});
it('should redirect to blocks page if passed a number', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Block);
fireEvent.change(input, {
target: {
value: '123',
@@ -1,55 +1,57 @@
import React, { useCallback, useState } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { Input, InputError, Button } from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { Button, Input, InputError } from '@vegaprotocol/ui-toolkit';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { getSearchType, SearchTypes, toHex } from './detect-search';
import { Routes } from '../../routes/route-names';
const TX_LENGTH = 64;
interface FormFields {
search: string;
}
const isPrependedTransaction = (search: string) =>
search.startsWith('0x') && search.length === 2 + TX_LENGTH;
const isTransaction = (search: string) =>
!search.startsWith('0x') && search.length === TX_LENGTH;
const isBlock = (search: string) => !Number.isNaN(Number(search));
export const Search = () => {
const { register, handleSubmit } = useForm<FormFields>();
const navigate = useNavigate();
const [error, setError] = React.useState<Error | null>(null);
const onSubmit = React.useCallback(
(fields: FormFields) => {
const [error, setError] = useState<Error | null>(null);
const onSubmit = useCallback(
async (fields: FormFields) => {
setError(null);
const search = fields.search;
if (!search) {
setError(new Error(t('Search required')));
} else if (isPrependedTransaction(search)) {
if (Number.isNaN(Number(search))) {
setError(new Error(t('Transaction is not hexadecimal')));
} else {
navigate(`${Routes.TX}/${search}`);
}
} else if (isTransaction(search)) {
if (Number.isNaN(Number(`0x${search}`))) {
setError(new Error(t('Transaction is not hexadecimal')));
} else {
navigate(`${Routes.TX}/0x${search}`);
}
} else if (isBlock(search)) {
navigate(`${Routes.BLOCKS}/${Number(search)}`);
const query = fields.search;
if (!query) {
setError(new Error(t('Search query required')));
} else {
setError(new Error(t("Something doesn't look right")));
const result = await getSearchType(query);
const urlAsHex = toHex(query);
const unrecognisedError = new Error(
t('Transaction type is not recognised')
);
if (result) {
switch (result) {
case SearchTypes.Party:
navigate(`${Routes.PARTIES}/${urlAsHex}`);
break;
case SearchTypes.Transaction:
navigate(`${Routes.TX}/${urlAsHex}`);
break;
case SearchTypes.Block:
navigate(`${Routes.BLOCKS}/${Number(query)}`);
break;
default:
setError(unrecognisedError);
}
}
setError(unrecognisedError);
}
},
[navigate]
);
return (
<form
onSubmit={handleSubmit(onSubmit)}
@@ -66,7 +68,7 @@ export const Search = () => {
className="text-white"
hasError={Boolean(error?.message)}
type="text"
placeholder={t('Enter block number or transaction hash')}
placeholder={t('Enter block number, party id or transaction hash')}
/>
{error?.message && (
<div className="absolute top-[100%] flex-1 w-full">
@@ -150,7 +150,9 @@ const Party = () => {
<SubHeading>{t('Staking')}</SubHeading>
{staking}
<SubHeading>{t('JSON')}</SubHeading>
<SyntaxHighlighter data={data} />
<section data-testid="parties-json">
<SyntaxHighlighter data={data} />
</section>
</>
) : null}
+1 -1
View File
@@ -20,7 +20,7 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json"
NX_VEGA_ENV = 'TESTNET'
NX_VEGA_URL="https://api.n11.testnet.vega.xyz/graphql"
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_NETWORKS={"MAINNET":"https://alpha.console.vega.xyz"}
@@ -50,7 +50,8 @@
"executor": "./tools/executors/webpack:serve",
"options": {
"buildTarget": "liquidity-provision-dashboard:build",
"hmr": true
"hmr": true,
"port": 4201
},
"configurations": {
"development": {
+11
View File
@@ -0,0 +1,11 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic"
}
]
],
"plugins": []
}
+16
View File
@@ -0,0 +1,16 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.
+4
View File
@@ -0,0 +1,4 @@
NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
+5
View File
@@ -0,0 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=DEVNET
+5
View File
@@ -0,0 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=MAINNET
+5
View File
@@ -0,0 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
+5
View File
@@ -0,0 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://api.n09.testnet.vega.xyz/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=TESTNET
+18
View File
@@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__", "__generated___"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
+46
View File
@@ -0,0 +1,46 @@
## Multisig-signer
## Development
First copy the configuration of the application you are starting:
```bash
cp .env.[environment] .env.local
```
Starting the app:
```bash
yarn nx serve multisig-signer
```
### Configuration
Example configurations are provided here:
- [Mainnet](./.env.mainnet)
- [Devnet](./.env.devnet)
- [Capsule](./.env.capsule)
- [Testnet](./.env.testnet)
- [Stagnet3](./.env.stagnet3)
For convenience, you can boot the app injecting one of the configurations above by running:
```bash
yarn nx run multisig-signer:serve --env={env} # e.g. stagnet3
```
There are a few different configuration options offered for this app:
| **Flag** | **Purpose** |
| -------------------------------- | ---------------------------------------------------------------------------------------------------- | --- | |
| `NX_VEGA_URL` | The GraphQl query endpoint of a [Vega data node](https://github.com/vegaprotocol/networks#data-node) |
| `NX_VEGA_ENV` | The name of the currently connected vega environment |
## Testing
To run the minimal set of unit tests, run the following:
```bash
yarn nx test multisig-signer
```
+5
View File
@@ -0,0 +1,5 @@
/// <reference types="react-scripts" />
interface Window {
_env_?: Record<string, string>;
}
+12
View File
@@ -0,0 +1,12 @@
/* eslint-disable */
export default {
displayName: 'multisig-signer',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/multisig-signer',
setupFilesAfterEnv: ['./src/app/setup-tests.ts'],
};
+4
View File
@@ -0,0 +1,4 @@
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
+10
View File
@@ -0,0 +1,10 @@
const { join } = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};
+80
View File
@@ -0,0 +1,80 @@
{
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/multisig-signer/src",
"projectType": "application",
"targets": {
"build": {
"executor": "./tools/executors/webpack:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/multisig-signer",
"index": "apps/multisig-signer/src/index.html",
"baseHref": "/",
"main": "apps/multisig-signer/src/main.tsx",
"polyfills": "apps/multisig-signer/src/polyfills.ts",
"tsConfig": "apps/multisig-signer/tsconfig.app.json",
"assets": ["apps/multisig-signer/src/assets"],
"styles": ["apps/multisig-signer/src/styles.css"],
"scripts": [],
"webpackConfig": "apps/multisig-signer/webpack.config.js"
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/multisig-signer/src/environments/environment.ts",
"with": "apps/multisig-signer/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false
}
}
},
"serve": {
"executor": "./tools/executors/webpack:serve",
"options": {
"port": 3000,
"buildTarget": "multisig-signer:build:development",
"hmr": true
},
"configurations": {
"production": {
"buildTarget": "multisig-signer:build:production",
"hmr": false
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/multisig-signer/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/apps/multisig-signer"],
"options": {
"jestConfig": "apps/multisig-signer/jest.config.ts",
"passWithNoTests": true
}
},
"build-netlify": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"cp apps/multisig-signer/netlify.toml netlify.toml",
"nx build multisig-signer"
]
}
}
},
"tags": []
}
+108
View File
@@ -0,0 +1,108 @@
import * as Sentry from '@sentry/react';
import classnames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
import { BrowserTracing } from '@sentry/tracing';
import {
EnvironmentProvider,
NetworkLoader,
useEnvironment,
} from '@vegaprotocol/environment';
import { AsyncRenderer, Button, Lozenge } from '@vegaprotocol/ui-toolkit';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { useEthereumConfig, Web3Provider } from '@vegaprotocol/web3';
import { ThemeContext, useThemeSwitcher, t } from '@vegaprotocol/react-helpers';
import { createClient } from './lib/apollo-client';
import { ENV } from './config/env';
import { ContractsProvider } from './config/contracts/contracts-provider';
import {
AddSignerForm,
RemoveSignerForm,
Header,
ContractDetails,
} from './components';
import { createConnectors } from './lib/web3-connectors';
import { Web3Connector } from './components/web3-connector';
import { EthWalletContainer } from './components/eth-wallet-container';
import { useWeb3React } from '@web3-react/core';
const pageWrapperClasses = classnames(
'min-h-screen w-screen',
'grid grid-rows-[auto,1fr]',
'bg-white dark:bg-black',
'text-neutral-900 dark:text-neutral-100'
);
const ConnectedApp = ({ config }: { config: EthereumConfig | null }) => {
const { account, connector } = useWeb3React();
return (
<main className="w-full max-w-3xl px-5 justify-self-center">
<h1>{t('Multisig signer')}</h1>
<div className="mb-8">
<p>
Connected to Eth wallet: <Lozenge>{account}</Lozenge>
</p>
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
</div>
<ContractDetails config={config} />
<h2>{t('Add or remove signer')}</h2>
<AddSignerForm />
<RemoveSignerForm />
</main>
);
};
function App() {
const { VEGA_ENV, ETHEREUM_PROVIDER_URL } = useEnvironment();
const { config, loading, error } = useEthereumConfig();
const [dialogOpen, setDialogOpen] = useState(false);
const [theme, toggleTheme] = useThemeSwitcher();
useEffect(() => {
Sentry.init({
dsn: ENV.dsn,
integrations: [new BrowserTracing()],
tracesSampleRate: 1,
environment: VEGA_ENV,
});
}, [VEGA_ENV]);
const Connectors = useMemo(() => {
if (config?.chain_id) {
return createConnectors(ETHEREUM_PROVIDER_URL, Number(config.chain_id));
}
return [];
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
return (
<ThemeContext.Provider value={theme}>
<Web3Provider connectors={Connectors}>
<Web3Connector dialogOpen={dialogOpen} setDialogOpen={setDialogOpen}>
<div className={pageWrapperClasses}>
<AsyncRenderer loading={loading} data={config} error={error}>
<Header theme={theme} toggleTheme={toggleTheme} />
<EthWalletContainer
dialogOpen={dialogOpen}
setDialogOpen={setDialogOpen}
>
<ConnectedApp config={config} />
</EthWalletContainer>
</AsyncRenderer>
</div>
</Web3Connector>
</Web3Provider>
</ThemeContext.Provider>
);
}
const Wrapper = () => {
return (
<EnvironmentProvider>
<NetworkLoader createClient={createClient}>
<ContractsProvider>
<App />
</ContractsProvider>
</NetworkLoader>
</EnvironmentProvider>
);
};
export default Wrapper;
@@ -0,0 +1,45 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: AddSignerBundle
// ====================================================
export interface AddSignerBundle_erc20MultiSigSignerAddedBundles_edges_node {
__typename: "ERC20MultiSigSignerAddedBundle";
/**
* The ethereum address of the signer to be added
*/
newSigner: string;
/**
* The nonce used in the signing operation
*/
nonce: string;
/**
* The bundle of signatures from current validators to sign in the new signer
*/
signatures: string;
}
export interface AddSignerBundle_erc20MultiSigSignerAddedBundles_edges {
__typename: "ERC20MultiSigSignerAddedBundleEdge";
node: AddSignerBundle_erc20MultiSigSignerAddedBundles_edges_node;
}
export interface AddSignerBundle_erc20MultiSigSignerAddedBundles {
__typename: "ERC20MultiSigSignerAddedConnection";
edges: (AddSignerBundle_erc20MultiSigSignerAddedBundles_edges | null)[] | null;
}
export interface AddSignerBundle {
/**
* Get the signature bundle to add a particular validator to the signer list of the multisig contract
*/
erc20MultiSigSignerAddedBundles: AddSignerBundle_erc20MultiSigSignerAddedBundles;
}
export interface AddSignerBundleVariables {
nodeId: string;
}
@@ -0,0 +1,48 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: RemoveSignerBundle
// ====================================================
export interface RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges_node {
__typename: "ERC20MultiSigSignerRemovedBundle";
/**
* The ethereum address of the signer to be removed
*/
oldSigner: string;
/**
* The nonce used in the signing operation
*/
nonce: string;
/**
* The bundle of signatures from current validators to sign in the new signer
*/
signatures: string;
}
export interface RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges {
__typename: "ERC20MultiSigSignerRemovedBundleEdge";
node: RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges_node;
}
export interface RemoveSignerBundle_erc20MultiSigSignerRemovedBundles {
__typename: "ERC20MultiSigSignerRemovedConnection";
/**
* The list of signer bundles for that validator
*/
edges: (RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges | null)[] | null;
}
export interface RemoveSignerBundle {
/**
* Get the signatures bundle to remove a particular validator from signer list of the multisig contract
*/
erc20MultiSigSignerRemovedBundles: RemoveSignerBundle_erc20MultiSigSignerRemovedBundles;
}
export interface RemoveSignerBundleVariables {
nodeId: string;
}
@@ -0,0 +1,121 @@
import { useState } from 'react';
import { gql, useLazyQuery } from '@apollo/client';
import { captureException } from '@sentry/react';
import { t } from '@vegaprotocol/react-helpers';
import { useEthereumTransaction } from '@vegaprotocol/web3';
import {
FormGroup,
Input,
Button,
InputError,
Loader,
} from '@vegaprotocol/ui-toolkit';
import { prepend0x } from '@vegaprotocol/smart-contracts';
import { useContracts } from '../../config/contracts/contracts-context';
import type { FormEvent } from 'react';
import type {
AddSignerBundle,
AddSignerBundleVariables,
} from '../__generated__/AddSignerBundle';
import type { MultisigControl } from '@vegaprotocol/smart-contracts';
export const ADD_SIGNER_QUERY = gql`
query AddSignerBundle($nodeId: ID!) {
erc20MultiSigSignerAddedBundles(nodeId: $nodeId) {
edges {
node {
newSigner
nonce
signatures
}
}
}
}
`;
export const AddSignerForm = () => {
const { multisig } = useContracts();
const [address, setAddress] = useState('');
const [bundleNotFound, setBundleNotFound] = useState(false);
const [runQuery, { data, error, loading }] = useLazyQuery<
AddSignerBundle,
AddSignerBundleVariables
>(ADD_SIGNER_QUERY);
const { perform, Dialog } = useEthereumTransaction<
MultisigControl,
'add_signer'
>(multisig, 'add_signer');
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setBundleNotFound(false);
try {
if (address === '') {
return;
}
await runQuery({
variables: { nodeId: address },
});
const bundle = data?.erc20MultiSigSignerAddedBundles?.edges?.[0]?.node;
if (!bundle) {
if (!error) {
setBundleNotFound(true);
}
return;
}
await perform(
bundle.newSigner,
bundle.nonce.startsWith('0x') ? bundle.nonce : prepend0x(bundle.nonce),
bundle.signatures.startsWith('0x')
? bundle.signatures
: prepend0x(bundle.signatures)
);
} catch (err: unknown) {
captureException(err);
}
};
return (
<form onSubmit={(e) => handleSubmit(e)}>
<FormGroup
label={t('Add signer')}
labelFor="add-signer-input"
labelDescription={t('Public key of the signer to add')}
className="max-w-xl"
>
<div className="grid grid-cols-[1fr,auto] gap-2">
<Input
id="add-signer-input"
onChange={(e) => setAddress(e.target.value)}
data-testid="add-signer-input-input"
/>
<Button
type="submit"
data-testid="add-signer-submit"
disabled={loading}
>
{loading ? <Loader size="small" /> : t('Add')}
</Button>
</div>
<div>
{error && (
<InputError intent="danger">
{error?.message.includes('InvalidArgument')
? t('Invalid node id')
: error?.message}
</InputError>
)}
{bundleNotFound && !error && (
<InputError intent="danger">
{t(
'Bundle was not found, are you sure this validator needs to be added?'
)}
</InputError>
)}
</div>
</FormGroup>
<Dialog />
</form>
);
};
@@ -0,0 +1 @@
export * from './add-signer-form';
@@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';
import * as Sentry from '@sentry/react';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
import { useContracts } from '../../config/contracts/contracts-context';
import type { EthereumConfig } from '@vegaprotocol/web3';
interface ContractDetailsProps {
config: EthereumConfig | null;
}
export const ContractDetails = ({ config }: ContractDetailsProps) => {
const { multisig } = useContracts();
const [validSignerCount, setValidSignerCount] = useState(undefined);
useEffect(() => {
(async () => {
try {
const res = await multisig.get_valid_signer_count();
setValidSignerCount(res);
} catch (err) {
Sentry.captureException(err);
}
})();
}, [multisig]);
return (
<div className="mb-8">
<p>
Multisig contract address:{' '}
<Lozenge>{config?.multisig_control_contract?.address}</Lozenge>
</p>
<p>Valid signer count: {validSignerCount}</p>
</div>
);
};
@@ -0,0 +1 @@
export * from './contract-details';
@@ -0,0 +1,25 @@
import { Button } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
import type { ReactElement } from 'react';
export const EthWalletContainer = ({
dialogOpen,
setDialogOpen,
children,
}: {
dialogOpen: boolean;
setDialogOpen: (open: boolean) => void;
children: ReactElement;
}) => {
const { account } = useWeb3React();
if (!account) {
return (
<div className="w-full text-center">
<Button onClick={() => setDialogOpen(true)}>
Connect Ethereum Wallet
</Button>
</div>
);
}
return children;
};
@@ -0,0 +1 @@
export * from './eth-wallet-container';
@@ -0,0 +1,24 @@
import {
BackgroundVideo,
ThemeSwitcher,
VegaLogo,
} from '@vegaprotocol/ui-toolkit';
interface HeaderProps {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
export const Header = ({ theme, toggleTheme }: HeaderProps) => {
return (
<header className="relative overflow-hidden py-2 mb-8">
<BackgroundVideo />
<div className="relative flex justify-center px-2 dark:bg-black bg-white">
<div className="w-full max-w-3xl p-5 flex items-center justify-between">
<VegaLogo />
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
</div>
</div>
</header>
);
};
@@ -0,0 +1 @@
export * from './header';
@@ -0,0 +1,4 @@
export * from './add-signer-form';
export * from './remove-signer-form';
export * from './header';
export * from './contract-details';
@@ -0,0 +1 @@
export * from './remove-signer-form';
@@ -0,0 +1,121 @@
import { useState } from 'react';
import { gql, useLazyQuery } from '@apollo/client';
import { captureException } from '@sentry/react';
import { t } from '@vegaprotocol/react-helpers';
import { useEthereumTransaction } from '@vegaprotocol/web3';
import {
FormGroup,
Input,
Button,
InputError,
Loader,
} from '@vegaprotocol/ui-toolkit';
import { prepend0x } from '@vegaprotocol/smart-contracts';
import { useContracts } from '../../config/contracts/contracts-context';
import type { FormEvent } from 'react';
import type {
RemoveSignerBundle,
RemoveSignerBundleVariables,
} from '../__generated__/RemoveSignerBundle';
import type { MultisigControl } from '@vegaprotocol/smart-contracts';
const REMOVE_SIGNER_QUERY = gql`
query RemoveSignerBundle($nodeId: ID!) {
erc20MultiSigSignerRemovedBundles(nodeId: $nodeId) {
edges {
node {
oldSigner
nonce
signatures
}
}
}
}
`;
export const RemoveSignerForm = () => {
const { multisig } = useContracts();
const [address, setAddress] = useState('');
const [bundleNotFound, setBundleNotFound] = useState(false);
const [runQuery, { data, error, loading }] = useLazyQuery<
RemoveSignerBundle,
RemoveSignerBundleVariables
>(REMOVE_SIGNER_QUERY);
const { perform, Dialog } = useEthereumTransaction<
MultisigControl,
'remove_signer'
>(multisig, 'remove_signer');
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setBundleNotFound(false);
try {
if (address === '') {
return;
}
await runQuery({
variables: { nodeId: address },
});
const bundle = data?.erc20MultiSigSignerRemovedBundles?.edges?.[0]?.node;
if (!bundle) {
if (!error) {
setBundleNotFound(true);
}
return;
}
await perform(
bundle.oldSigner,
bundle.nonce.startsWith('0x') ? bundle.nonce : prepend0x(bundle.nonce),
bundle.signatures.startsWith('0x')
? bundle.signatures
: prepend0x(bundle.signatures)
);
} catch (err: unknown) {
captureException(err);
}
};
return (
<form onSubmit={(e) => handleSubmit(e)}>
<FormGroup
label={t('Remove signer')}
labelFor="remove-signer-input"
labelDescription={t('Public key of the signer to remove')}
className="max-w-xl"
>
<div className="grid grid-cols-[1fr,auto] gap-2">
<Input
id="remove-signer-input"
onChange={(e) => setAddress(e.target.value)}
data-testid="remove-signer-input-input"
/>
<Button
type="submit"
data-testid="remove-signer-submit"
disabled={loading}
>
{loading ? <Loader size="small" /> : t('Remove')}
</Button>
</div>
<div>
{error && (
<InputError intent="danger">
{error?.message.includes('InvalidArgument')
? t('Invalid node id')
: error?.message}
</InputError>
)}
{bundleNotFound && !error && (
<InputError intent="danger">
{t(
'Bundle was not found, are you sure this validator needs to be removed?'
)}
</InputError>
)}
</div>
</FormGroup>
<Dialog />
</form>
);
};

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