Compare commits

...
Author SHA1 Message Date
Edd 86d234b6eb test(explorer): fix capitalisation of test value 2023-02-08 10:34:58 +00:00
Edd 587065beb4 chore(explorer): remove accidental changes to shared lib 2023-02-08 10:34:58 +00:00
Edd 3a1522bbf5 test(explorer): finish tests for risk factors 2023-02-08 10:34:57 +00:00
Edd fc0ae5478e test(explorer): add tests for state variable views 2023-02-08 10:34:57 +00:00
Edd cf89c4f902 feat(explorer): monospace state variable numbers 2023-02-08 10:34:57 +00:00
Edd 5e9009eaf1 feat(explorer): all known state variables render meaningfully 2023-02-08 10:34:57 +00:00
Edd 7f77fceeab feat(explorer): basic state variable proposal view 2023-02-08 10:34:55 +00:00
Sam Keen da551f9d3c feat(governance): replace proposal and vote busEvents with subscriptions (#2862) 2023-02-08 09:42:20 +00:00
Edd 47e4861fdb fix(explorer): use position decimal places to format size in order (#2861) 2023-02-08 08:22:27 +00:00
mattrussell36 1a85a89440 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 06:06:13 +00:00
mattrussell36 7ab412a8f7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 00:12:40 +00:00
Edd 5a14174a81 feat(explorer): add issuesignatures tx view (#2867) 2023-02-07 18:17:00 +00:00
mattrussell36 5e00b93783 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 18:06:49 +00:00
Edd 31d9b023e6 feat(explorer): change party to pubkey (#2868) 2023-02-07 18:00:26 +00:00
mattrussell36 103e503a47 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 12:07:30 +00:00
Maciek 1952cb0e78 fix(trading): inform the user his connection to wallet is lost (#2863) 2023-02-07 11:31:15 +01:00
mattrussell36 69340f4ddf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 06:06:15 +00:00
mattrussell36 c52bf2200e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 00:10:59 +00:00
Matthew Russell 8dccee69f2 chore(trading): remove unnecessary sentry capture from asyncrenderer component (#2869) 2023-02-06 15:21:39 -08:00
Art c7a6fdd879 chore(trading): new toasts designs (#2779) 2023-02-06 20:09:56 +00:00
Matthew Russell 8bcdaf4cda feat(trading): key to key transfers (#2784) 2023-02-06 11:35:40 -08:00
mattrussell36 3eb1359504 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 18:08:25 +00:00
Sam Keen 39cf8ad4d6 feat(governance): remove some performance info from validator mouseover (#2865) 2023-02-06 17:08:34 +00:00
Edd faef98f0ae fix(explorer): type generation broken in an oracle refactor (#2859) 2023-02-06 13:29:23 +00:00
macqbat 710e2daa27 chore(trading): notification component redesign (#2845) 2023-02-06 13:31:35 +01:00
daro-maj 06ea3924fa chore(trading): increase withdraw ac test coverage (#2853) 2023-02-06 13:27:12 +01:00
mattrussell36 965e2d8972 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 12:09:01 +00:00
m.ray 73de2fed43 fix(trading): when order is rejected change tx toast intent (#2851) 2023-02-06 11:44:04 +00:00
Sam Keen 536859e067 feat(governance): visual tweaks to main nav (#2839) 2023-02-06 11:18:14 +00:00
Sam Keen 54bfaef473 fix(governance): empty div disrupts other page elements (#2855) 2023-02-06 10:35:26 +00:00
Matthew Russell e2cad707a7 chore(trading): add validator testnet env file (#2852) 2023-02-06 09:04:34 +00:00
mattrussell36 ec8d9798ab chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 06:08:16 +00:00
mattrussell36 e629c1e81a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 00:10:27 +00:00
mattrussell36 25feed793c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 18:08:10 +00:00
mattrussell36 2867367e16 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 12:07:47 +00:00
mattrussell36 50b408e74d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 06:05:57 +00:00
mattrussell36 8f531c7c05 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 00:13:19 +00:00
mattrussell36 350d16fee1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 18:07:27 +00:00
mattrussell36 c052c66266 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 12:06:25 +00:00
mattrussell36 e2032df236 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 06:07:41 +00:00
Matthew Russell 9ece19869d chore(trading): update order subs to not use busevents (#2849) 2023-02-03 16:12:20 -08:00
Joe Tsang 22abc8160c test(token): pending balances (#2843) 2023-02-03 19:26:17 +00:00
Edd b1a7a22bf9 feat(explorer): add protocol upgrade view (#2802) 2023-02-03 19:25:36 +00:00
mattrussell36 c4b38cc0dc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 18:05:46 +00:00
m.ray 44d6d931d5 fix(trading): update async renderer on error (#2842) 2023-02-03 16:52:10 +00:00
m.ray 7f1e47d7fd fix(trading): remove price flash cell from PNL (#2829) 2023-02-03 14:59:24 +00:00
Art 0eddfa4073 fix(ui-toolkit): dark and light shades mismatch (#2841) 2023-02-03 12:33:13 +00:00
mattrussell36 83a034ce91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 12:06:17 +00:00
5ab5aa01a2 feat(trading): 2803 console announcement banner (#2830)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-02-03 10:34:32 +00:00
mattrussell36 b22f9156b5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 06:07:49 +00:00
mattrussell36 bab5e2018b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 00:12:30 +00:00
macqbat 3f7692ce75 chore(trading): add alpha font to trading (#2833) 2023-02-02 19:25:14 +01:00
mattrussell36 b69fb3b0e5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 18:08:33 +00:00
macqbat 83af6e74f6 chore(trading): no data message in charts (#2816) 2023-02-02 18:43:16 +01:00
m.ray b9908003ba fix(trading): fix symbol on order estimates (#2827) 2023-02-02 13:30:47 +00:00
Sam KeenandMatthew Russell 1f9af79215 feat(ui-toolkit): mainnet sims announcement banner (#2806)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-02-02 13:13:55 +00:00
Sam Keen e31e40a7ef feat(ui-toolkit): mainnet sims announcement banner (#2806) 2023-02-02 12:56:22 +00:00
mattrussell36 81f6b3cc15 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 12:08:12 +00:00
mattrussell36 8adb208a91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 06:06:32 +00:00
mattrussell36 545c3a17d2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 00:09:59 +00:00
mattrussell36 ad76988f63 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 18:08:30 +00:00
Sam KeenandDexter d2bbd7e1fc feat(governance): pending balances: add event listeners (#2541)
Co-authored-by: Dexter <dexter.edwards93@gmail.com>
2023-02-01 16:02:50 +00:00
macqbat fb1ebb3bdf chore(trading): remove noise form the test logs (#2801) 2023-02-01 16:58:47 +01:00
m.ray b2489537ef fix(trading): market data and auction time not updating (#2813) 2023-02-01 14:08:49 +00:00
Sam Keen 382a7ea411 feat(governance): body can scroll again after clicking 'back to staking' (#2785) 2023-02-01 12:56:25 +00:00
mattrussell36 1ae2b4c153 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 12:06:46 +00:00
mattrussell36 e8875e817e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 06:06:26 +00:00
mattrussell36 4a01c880f3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 00:10:41 +00:00
mattrussell36 f7f00dc7bb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 18:07:31 +00:00
macqbat d151b6e923 chore(trading): add sub statuses of opening auction trading mode (#2797) 2023-01-31 17:31:55 +01:00
Sam Keen 8f8e9c1061 feat(ui-toolkit): announcement banner to highlight mainnet sims (#2800) 2023-01-31 16:12:44 +00:00
m.ray b40358a723 fix(trading): consolidate view as user mode (#2778) 2023-01-31 16:04:52 +00:00
Art 01f0934da3 chore(ui-toolkit): new colour palette (#2783) 2023-01-31 16:33:16 +01:00
mattrussell36 df7755dbc4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 12:06:33 +00:00
Joe Tsang 9073234040 test(ci): fix tags in cypress workflow (#2788) 2023-01-31 11:35:42 +00:00
Art 053cd0fa27 fix(trading): asset option on mobile got overflown (#2782) 2023-01-31 11:10:55 +01:00
mattrussell36 961dc88dc7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 06:08:30 +00:00
mattrussell36 b16607a844 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 00:13:30 +00:00
Matthew Russell d0d804631c chore(ci): add nx scope linting for pr title (#2781) 2023-01-30 21:06:18 +00:00
daro-maj 3e778a88dc chore: capsule tests refactor and increase deposit ac (#2773) 2023-01-30 20:35:58 +01:00
Edd dbb09c9c79 fix(explorer): correct calculation for whether party txs list has more txs (#2774) 2023-01-30 18:23:09 +00:00
Edd 06f44b67de fix(explorer): tx viewer dropdown did not correctly represent state (#2780) 2023-01-30 18:17:47 +00:00
Joe Tsang 1861852852 test(2704): proposals with market (#2775) 2023-01-30 18:06:04 +00:00
mattrussell36 3ee0eee80d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 12:09:00 +00:00
mattrussell36 69ce870a81 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 06:06:56 +00:00
mattrussell36 d8467c3206 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 00:12:51 +00:00
mattrussell36 de87f40a01 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 18:08:12 +00:00
mattrussell36 f22fb56afd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 12:06:12 +00:00
mattrussell36 f2627e6ebf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 06:07:03 +00:00
mattrussell36 24e6a13d93 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 00:13:02 +00:00
mattrussell36 b56050ad1c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 18:07:37 +00:00
mattrussell36 cff83ed062 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 12:06:05 +00:00
mattrussell36 8ecce874b0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 06:08:30 +00:00
Bartłomiej Głownia 00e319b3c6 chore: handle not found errors as correct response (#2759) 2023-01-27 18:35:45 -08:00
mattrussell36 05559f3ea0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 00:11:14 +00:00
m.ray 4b3b5c322a fix: trades grid colors (#2766) 2023-01-27 18:55:20 +00:00
Bartłomiej Głownia b1280c8285 fix: hide close button if openVolume is zero (#2768) 2023-01-27 18:44:20 +00:00
Edd 1e0e1c7859 feat(explorer): add long text component for hashes and tx viewer (#2765) 2023-01-27 18:37:59 +00:00
mattrussell36 ec2bb81ec8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 18:08:36 +00:00
m.ray fe95c6fcbc fix: show markets link & deal ticket validation (#2763) 2023-01-27 18:10:20 +01:00
macqbat 853ec8f69c chore(2351): improve handling wallet errors (#2729) 2023-01-27 15:45:29 +01:00
mattrussell36 b68c090ee5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 12:09:03 +00:00
Matthew Russellandasiaznik cb9b811730 feat(#1791): block height check (#2701)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
2023-01-27 11:31:23 +01:00
Bartłomiej Głownia 98b5260d93 feat: fix positions and margins providers update (#2753) 2023-01-27 09:39:39 +01:00
mattrussell36 613262f7a5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 06:05:57 +00:00
mattrussell36 9ae6f5201c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 00:10:54 +00:00
m.ray cfb75fcf8f fix: volume charts position dp (#2756) 2023-01-26 19:36:29 +00:00
mattrussell36 ae6ccdfb52 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-26 18:08:18 +00:00
Radosław Szpiech 9e73c6a462 chore: 2593 trading-e2e refactor - split global.cy (#2754) 2023-01-26 17:50:33 +01:00
Edd 24c017bc2b feat(explorer): add basic votesubmission tx view (#2694) 2023-01-26 14:53:34 +00:00
Edd 3188f23439 fix(explorer): place sensible limits on oracles page queries (#2699) 2023-01-26 14:50:52 +00:00
Edd 33e528c569 fix(explorer): switch decimals used in lp commitment views (#2747) 2023-01-26 14:47:31 +00:00
Radosław Szpiech e10945eeda chore: correct ethereum releated envs in trading-e2e (#2744) 2023-01-26 15:40:49 +01:00
m.ray 47b2a2fdcb fix: volume chart values fixing - position dp (#2749) 2023-01-26 14:21:43 +00:00
m.ray 860938f26c fix: market selected no longer available - add link to all markets (#2748) 2023-01-26 13:46:44 +00:00
Art 5766d84804 fix: select position (#2745) 2023-01-26 12:36:47 +00:00
392 changed files with 9454 additions and 3833 deletions
+1 -3
View File
@@ -64,13 +64,11 @@ jobs:
######
- name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
CYPRESS_grepTags: ${{ inputs.tags }}
######
## Upload logs
+7 -5
View File
@@ -8,14 +8,16 @@ jobs:
lint_pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.14.0
- name: Install commitlint cli and config
run: npm install @commitlint/cli @commitlint/config-conventional
- name: Create config
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
- name: Install root dependencies
run: yarn install
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+2 -1
View File
@@ -6,4 +6,5 @@ NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https:
NX_VEGA_ENV=DEVNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
+1
View File
@@ -7,3 +7,4 @@ NX_VEGA_ENV=MAINNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
+1
View File
@@ -7,6 +7,7 @@ NX_VEGA_ENV=MIRROR
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
# App flags
NX_EXPLORER_ASSETS=1
+3 -2
View File
@@ -6,6 +6,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.sandbox.vega.xyz/websocket
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
+1
View File
@@ -11,3 +11,4 @@ NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz
+3 -1
View File
@@ -4,4 +4,6 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
+1
View File
@@ -10,3 +10,4 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_NETWORKS={}
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
+1 -1
View File
@@ -70,7 +70,7 @@
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.66.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.67.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
]
}
},
+10
View File
@@ -8,6 +8,7 @@ import { Main } from './components/main';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import type { InMemoryCacheConfig } from '@apollo/client';
import { Footer } from './components/footer/footer';
import { AnnouncementBanner, ExternalLink } from '@vegaprotocol/ui-toolkit';
function App() {
const [menuOpen, setMenuOpen] = useState(false);
@@ -40,6 +41,15 @@ function App() {
return (
<TendermintWebsocketProvider>
<NetworkLoader cache={cacheConfig}>
<AnnouncementBanner>
<div className="font-alpha calt uppercase text-center text-lg text-white">
<span className="pr-4">The Mainnet sims are live!</span>
<ExternalLink href="https://fairground.wtf/">
Come help stress test the network
</ExternalLink>
</div>
</AnnouncementBanner>
<div className={layoutClasses}>
<Header menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
<Nav menuOpen={menuOpen} />
@@ -4,6 +4,7 @@ import { useExplorerAssetQuery } from './__generated__/Asset';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -27,7 +28,7 @@ const AssetLink = ({ id, ...props }: AssetLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
{label}
<Hash text={label} />
</Link>
);
};
@@ -3,6 +3,7 @@ import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
height: string;
@@ -11,7 +12,7 @@ export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
{height}
<Hash text={height} />
</Link>
);
};
@@ -1,6 +1,7 @@
import React from 'react';
import { DATA_SOURCES } from '../../../config';
import Hash from '../hash';
export enum EthExplorerLinkTypes {
block = 'block',
@@ -27,7 +28,7 @@ export const EthExplorerLink = ({
{...props}
href={link}
>
{id}
<Hash text={id} />
</a>
);
};
@@ -0,0 +1,18 @@
export type HashProps = {
text: string;
};
/**
* A simple component that ensures long text things like hashes
* are broken when they need to wrap. This will remove the need
* for a lot of the overflow scrolling that currently exists.
*/
const Hash = ({ text }: HashProps) => {
return (
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
{text}
</code>
);
};
export default Hash;
@@ -2,12 +2,16 @@ query ExplorerMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
settlementAsset {
decimals
}
}
}
}
@@ -8,7 +8,7 @@ export type ExplorerMarketQueryVariables = Types.Exact<{
}>;
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null };
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
export const ExplorerMarketDocument = gql`
@@ -16,12 +16,16 @@ export const ExplorerMarketDocument = gql`
market(id: $id) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
settlementAsset {
decimals
}
}
}
}
@@ -55,6 +55,7 @@ describe('Market link component', () => {
market: {
id: '123',
decimalPlaces: 5,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -1,13 +1,14 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { useExplorerMarketQuery } from './__generated__/Market';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import Hash from '../hash';
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
showMarketName?: boolean;
};
/**
@@ -15,7 +16,11 @@ export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
* with a link to the markets list. If the name does not come back
* it will use the ID instead
*/
const MarketLink = ({ id, ...props }: MarketLinkProps) => {
const MarketLink = ({
id,
showMarketName = true,
...props
}: MarketLinkProps) => {
const { data, error, loading } = useExplorerMarketQuery({
variables: { id },
});
@@ -31,17 +36,31 @@ const MarketLink = ({ id, ...props }: MarketLinkProps) => {
<span role="img" aria-label="Unknown market" className="img">
&nbsp;{t('Invalid market')}
</span>
&nbsp;{id}
&nbsp;
<Hash text={id} />
</div>
);
}
}
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
{label}
</Link>
);
if (showMarketName) {
return (
<Link
className="underline"
{...props}
to={`/${Routes.MARKETS}#${id}`}
title={id}
>
{label}
</Link>
);
} else {
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
<Hash text={id} />
</Link>
);
}
};
export default MarketLink;
@@ -4,6 +4,7 @@ import { useExplorerNodeQuery } from './__generated__/Node';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type NodeLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -22,7 +23,7 @@ const NodeLink = ({ id, ...props }: NodeLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.VALIDATORS}#${id}`}>
<code>{label}</code>
<Hash text={label} />
</Link>
);
};
@@ -0,0 +1,23 @@
import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
};
const OracleLink = ({ id, ...props }: OracleLinkProps) => {
return (
<Link
className="underline font-mono"
{...props}
to={`/${Routes.ORACLES}/${id}`}
>
<Hash text={id} />
</Link>
);
};
export default OracleLink;
@@ -2,6 +2,7 @@ import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -14,7 +15,7 @@ const PartyLink = ({ id, ...props }: PartyLinkProps) => {
{...props}
to={`/${Routes.PARTIES}/${id}`}
>
{id}
<Hash text={id} />
</Link>
);
};
@@ -0,0 +1,9 @@
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
id
rationale {
title
description
}
}
}
@@ -0,0 +1,52 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerProposalQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export const ExplorerProposalDocument = gql`
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
id
rationale {
title
description
}
}
}
`;
/**
* __useExplorerProposalQuery__
*
* To run a query within a React component, call `useExplorerProposalQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerProposalQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerProposalQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerProposalQuery(baseOptions: Apollo.QueryHookOptions<ExplorerProposalQuery, ExplorerProposalQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerProposalQuery, ExplorerProposalQueryVariables>(ExplorerProposalDocument, options);
}
export function useExplorerProposalLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerProposalQuery, ExplorerProposalQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerProposalQuery, ExplorerProposalQueryVariables>(ExplorerProposalDocument, options);
}
export type ExplorerProposalQueryHookResult = ReturnType<typeof useExplorerProposalQuery>;
export type ExplorerProposalLazyQueryHookResult = ReturnType<typeof useExplorerProposalLazyQuery>;
export type ExplorerProposalQueryResult = Apollo.QueryResult<ExplorerProposalQuery, ExplorerProposalQueryVariables>;
@@ -0,0 +1,82 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import ProposalLink from './proposal-link';
import { ExplorerProposalDocument } from './__generated__/Proposal';
import { GraphQLError } from 'graphql';
function renderComponent(id: string, mocks: MockedResponse[]) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<ProposalLink id={id} />
</MemoryRouter>
</MockedProvider>
);
}
describe('Proposal link component', () => {
it('Renders the ID at first', () => {
const res = render(renderComponent('123', []));
expect(res.getByText('123')).toBeInTheDocument();
});
it('Renders the ID on error', async () => {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: '456',
},
},
result: {
errors: [new GraphQLError('No such proposal')],
},
};
const res = render(renderComponent('456', [mock]));
// The ID
expect(res.getByText('456')).toBeInTheDocument();
});
it('Renders the proposal title when the query returns a result', async () => {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: '123',
},
},
result: {
data: {
proposal: {
id: '123',
rationale: {
title: 'test-title',
description: 'test description',
},
},
},
},
};
const res = render(renderComponent('123', [mock]));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByText('test-title')).toBeInTheDocument();
});
it('Leaves the proposal id when the market is not found', async () => {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: '123',
},
},
error: new Error('No such asset'),
};
const res = render(renderComponent('123', [mock]));
expect(await res.findByText('123')).toBeInTheDocument();
});
});
@@ -0,0 +1,28 @@
import { useExplorerProposalQuery } from './__generated__/Proposal';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import Hash from '../hash';
export type ProposalLinkProps = {
id: string;
};
/**
* Given a proposal ID, generates an external link over to
* the Governance page for more information
*/
const ProposalLink = ({ id }: ProposalLinkProps) => {
const { data } = useExplorerProposalQuery({
variables: { id },
});
const base = ENV.dataSources.governanceUrl;
const label = data?.proposal?.rationale.title || id;
return (
<ExternalLink href={`${base}/proposals/${id}`}>
<Hash text={label} />
</ExternalLink>
);
};
export default ProposalLink;
@@ -117,7 +117,7 @@ const NestedDataListItem = ({
)}
</h4>
{!hasChildren && (
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
<code className="text-vega-light-100 mb-2 last:mb-0 dark:text-vega-dark-100 break-all">
{JSON.stringify(value, null, ' ')}
</code>
)}
@@ -19,6 +19,7 @@ fragment ExplorerDeterministicOrderFields on Order {
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID'];
@@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
}>;
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order {
@@ -35,6 +35,7 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -61,6 +61,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
__typename: 'Market',
id: '789',
state: 'STATUS_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: '5',
tradableInstrument: {
instrument: {
@@ -88,6 +89,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
market: {
id: '789',
decimalPlaces: 5,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -4,6 +4,7 @@ import { MarketLink } from '../links';
import PriceInMarket from '../price-in-market/price-in-market';
import { Time } from '../time';
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market';
export interface DeterministicOrderDetailsProps {
id: string;
@@ -90,7 +91,7 @@ const DeterministicOrderDetails = ({
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
{o.size}
<SizeInMarket size={o.size} marketId={o.market.id} />
</h5>
</div>
@@ -38,6 +38,7 @@ const mock = {
__typename: 'Market',
id: '789',
state: 'STATE_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: 2,
tradableInstrument: {
instrument: {
@@ -90,6 +90,7 @@ describe('Order TX Summary component', () => {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -97,6 +98,10 @@ describe('Order TX Summary component', () => {
product: {
__typename: 'Future',
quoteName: 'TEST',
settlementAsset: {
__typeName: 'SettlementAsset',
decimals: 18,
},
},
},
},
@@ -3,61 +3,79 @@ import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import PriceInMarket from './price-in-market';
import type { DecimalSource } from './price-in-market';
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
function renderComponent(
price: string,
marketId: string,
mocks: MockedResponse[]
mocks: MockedResponse[],
decimalSource: DecimalSource = 'MARKET'
) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<PriceInMarket marketId={marketId} price={price} />
<PriceInMarket
marketId={marketId}
price={price}
decimalSource={decimalSource}
/>
</MemoryRouter>
</MockedProvider>
);
}
const fullMock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'test dai',
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 18,
},
},
},
},
},
},
},
};
describe('Price in Market component', () => {
it('Renders the raw price when there is no market data', () => {
const res = render(renderComponent('100', '123', []));
expect(res.getByText('100')).toBeInTheDocument();
});
it('Renders the formatted price when market data is fetched', async () => {
const mock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'test dai',
product: {
__typename: 'Future',
quoteName: 'dai',
},
},
},
},
},
},
};
const res = render(renderComponent('100', '123', [mock]));
it('Renders the formatted price when market data is fetched, using market decimals by default', async () => {
const res = render(renderComponent('100', '123', [fullMock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
expect(await res.findByText('dai')).toBeInTheDocument();
});
it('Renders the formatted price when market data is fetched, using settlement decimals', async () => {
const res = render(
renderComponent('100', '123', [fullMock], 'SETTLEMENT_ASSET')
);
expect(await res.findByText('0.0000000000000001')).toBeInTheDocument();
expect(await res.findByText('dai')).toBeInTheDocument();
});
it('Leaves the market id when the market is not found', async () => {
const mock = {
request: {
@@ -3,16 +3,23 @@ import isUndefined from 'lodash/isUndefined';
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
import get from 'lodash/get';
export type DecimalSource = 'MARKET' | 'SETTLEMENT_ASSET';
export type PriceInMarketProps = {
marketId: string;
price: string;
decimalSource?: DecimalSource;
};
/**
* Given a market ID and a price it will fetch the market
* and format the price in that market's decimal places.
*/
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
const PriceInMarket = ({
marketId,
price,
decimalSource = 'MARKET',
}: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
@@ -20,8 +27,19 @@ const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
let label = price;
if (data && data.market?.decimalPlaces) {
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
if (data) {
if (decimalSource === 'MARKET' && data.market?.decimalPlaces) {
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
} else if (
decimalSource === 'SETTLEMENT_ASSET' &&
data.market?.tradableInstrument.instrument.product.settlementAsset
) {
label = addDecimalsFormatNumber(
price,
data.market?.tradableInstrument.instrument.product.settlementAsset
.decimals
);
}
}
const suffix = get(
@@ -1,5 +1,4 @@
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { StatusMessage } from '../status-message';
interface RenderFetchedProps {
@@ -66,7 +66,9 @@ export const Search = () => {
className="text-white"
hasError={Boolean(error?.message)}
type="text"
placeholder={t('Enter block number, party id or transaction hash')}
placeholder={t(
'Enter block number, public key or transaction hash'
)}
/>
{error?.message && (
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b text-black">
@@ -0,0 +1,69 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import SizeInMarket from './size-in-market';
import type { DecimalSource } from './size-in-market';
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
function renderComponent(
price: string,
marketId: string,
mocks: MockedResponse[],
decimalSource: DecimalSource = 'MARKET'
) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<SizeInMarket
marketId={marketId}
size={price}
decimalSource={decimalSource}
/>
</MemoryRouter>
</MockedProvider>
);
}
const fullMock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'test dai',
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 18,
},
},
},
},
},
},
},
};
describe('Size in Market component', () => {
it('Renders the raw size when there is no market data', () => {
const res = render(renderComponent('100', '123', []));
expect(res.getByText('100')).toBeInTheDocument();
});
it('Renders the formatted size when market data is fetched', async () => {
const res = render(renderComponent('100', '123', [fullMock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
});
});
@@ -0,0 +1,41 @@
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
export type DecimalSource = 'MARKET';
export type PriceInMarketProps = {
marketId: string;
size: string | number;
decimalSource?: DecimalSource;
};
/**
* Given a market ID and an order size it will fetch the market
* order size, and format the size accordingly
*/
const SizeInMarket = ({
marketId,
size,
decimalSource = 'MARKET',
}: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
});
let label = size;
if (data) {
if (decimalSource === 'MARKET' && data.market?.positionDecimalPlaces) {
label = addDecimalsFormatNumber(size, data.market.positionDecimalPlaces);
}
}
return (
<label>
<span>{label}</span>
</label>
);
};
export default SizeInMarket;
@@ -3,6 +3,7 @@ import React from 'react';
import classnames from 'classnames';
interface TableProps {
allowWrap?: boolean;
children: React.ReactNode;
className?: string;
}
@@ -25,8 +26,15 @@ interface TableCellProps extends ThHTMLAttributes<HTMLTableCellElement> {
modifier?: 'bordered' | 'background';
}
export const Table = ({ children, className, ...props }: TableProps) => {
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
export const Table = ({
allowWrap,
children,
className,
...props
}: TableProps) => {
const classes = allowWrap
? className
: classnames(className, 'overflow-x-auto whitespace-nowrap');
return (
<div className={classes}>
<table className="w-full" {...props}>
@@ -37,11 +45,14 @@ export const Table = ({ children, className, ...props }: TableProps) => {
};
export const TableWithTbody = ({
allowWrap,
children,
className,
...props
}: TableProps) => {
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
const classes = allowWrap
? className
: classnames(className, 'overflow-x-auto whitespace-nowrap');
return (
<div className={classes}>
<table className="w-full" {...props}>
@@ -63,15 +63,21 @@ describe('Chain Event: Builtin asset deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
@@ -69,15 +69,21 @@ describe('Chain Event: Builtin asset withdrawal', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
@@ -60,8 +60,11 @@ describe('Chain Event: ERC20 Asset Delist', () => {
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
@@ -76,14 +76,20 @@ describe('Chain Event: ERC20 Asset limits updated', () => {
expect(screen.getByText(t('Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
@@ -62,14 +62,20 @@ describe('Chain Event: ERC20 Asset List', () => {
expect(screen.getByText(t('Added Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.assetSource}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.assetSource}`
);
});
@@ -62,21 +62,30 @@ describe('Chain Event: ERC20 asset deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.targetPartyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.targetPartyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
@@ -57,14 +57,20 @@ describe('Chain Event: ERC20 asset deposit', () => {
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.targetEthereumAddress}`
);
});
@@ -64,14 +64,20 @@ describe('Chain Event: Stake deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
@@ -64,14 +64,20 @@ describe('Chain Event: Stake remove', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
@@ -66,7 +66,10 @@ describe('Chain Event: Stake total supply change', () => {
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.tokenAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.tokenAddress}`
);
});
@@ -7,6 +7,8 @@ import type { BlockExplorerTransactionResult } from '../../../../routes/types/bl
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
import { Time } from '../../../time';
import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
import { TxDataView } from '../../tx-data-view';
import Hash from '../../../links/hash';
interface TxDetailsSharedProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -46,7 +48,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
<TableCell>
<code>{txData.hash}</code>
<Hash text={txData.hash} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -82,6 +84,12 @@ export const TxDetailsShared = ({
<ChainResponseCode code={txData.code} error={txData.error} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Transaction')}</TableCell>
<TableCell>
<TxDataView blockData={blockData} txData={txData} />
</TableCell>
</TableRow>
</>
);
};
@@ -0,0 +1,108 @@
import { getValues } from './bound-factors';
import type { components } from '../../../../../types/explorer';
type KeyValueBundle = components['schemas']['vegaKeyValueBundle'][];
describe('getValues', () => {
it('handles an empty array by returning a dashed template', () => {
const res = getValues([]);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '-');
expect(res.up).toHaveProperty('value', '-');
});
it('handles undefined', () => {
const res = getValues(undefined as unknown as KeyValueBundle);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '-');
expect(res.up).toHaveProperty('value', '-');
});
it('handles a kvb that only has one side (should not happen)', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
it('handles a kvb that has a matrixVal instead of a scalarval by ignoring it', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { matrixVal: { value: [{ value: ['0.123'] }] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '-');
});
it('ignores unexpected extra values in the kvb', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123', '0.77'] } },
},
{
key: 'down',
tolerance: '0.001',
value: { vectorVal: { value: ['0.321'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '0.001');
expect(res.down).toHaveProperty('value', '0.321');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
it('handles a full kvb', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123'] } },
},
{
key: 'down',
tolerance: '0.001',
value: { vectorVal: { value: ['0.321'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '0.001');
expect(res.down).toHaveProperty('value', '0.321');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
});
@@ -0,0 +1,87 @@
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
interface StateVariableProposalBoundFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* A dumb as rocks function completely tied to what the structure of this variable should be
* @param kvb The key/value bundle
* @returns Object
*/
export function getValues(kvb: StateVariableProposalBoundFactorsProps['kvb']) {
const template = {
up: {
tolerance: '-',
value: '-',
},
down: {
tolerance: '-',
value: '-',
},
};
if (kvb && kvb.length > 0) {
kvb.forEach((v) => {
if (v.key === 'up') {
template.up.tolerance = v.tolerance || '-';
template.up.value = v.value?.vectorVal?.value
? v.value?.vectorVal.value[0]
: '-';
} else if (v.key === 'down') {
template.down.tolerance = v.tolerance || '-';
template.down.value = v.value?.vectorVal?.value
? v.value?.vectorVal.value[0]
: '-';
}
});
}
return template;
}
/**
* State Variable proposals updating Bound Factors. This contains two bundles,
* an up vector and a down vector
*
* This is nearly identical to risk factors.
*/
export const StateVariableProposalBoundFactors = ({
kvb,
}: StateVariableProposalBoundFactorsProps) => {
const v = getValues(kvb);
return (
<Table allowWrap={true} className="w-1/3">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Parameter')}</TableHeader>
<TableHeader align="center">{t('New value')}</TableHeader>
<TableHeader align="right">{t('Tolerance')}</TableHeader>
</TableRow>
</thead>
<tbody>
<TableRow modifier="bordered">
<TableCell>{t('Up')}</TableCell>
<TableCell align="right" className="font-mono">
{v.up.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.up.tolerance}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Down')}</TableCell>
<TableCell align="right" className="font-mono">
{v.down.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.down.tolerance}
</TableCell>
</TableRow>
</tbody>
</Table>
);
};
@@ -0,0 +1,31 @@
import type { components } from '../../../../../types/explorer';
import { StateVariableProposalUnknown } from './unknown';
import { StateVariableProposalBoundFactors } from './bound-factors';
import { StateVariableProposalRiskFactors } from './risk-factors';
interface StateVariableProposalWrapperProps {
stateVariable: string | undefined;
kvb: readonly components['schemas']['vegaKeyValueBundle'][] | undefined;
}
/**
* State Variable proposals
*/
export const StateVariableProposalWrapper = ({
stateVariable,
kvb,
}: StateVariableProposalWrapperProps) => {
if (!stateVariable || !kvb || kvb.length === 0) {
return null;
}
if (stateVariable.indexOf('bound-factors') !== -1) {
return <StateVariableProposalBoundFactors kvb={kvb} />;
} else if (stateVariable.indexOf('risk-factors') !== -1) {
return <StateVariableProposalRiskFactors kvb={kvb} />;
} else if (stateVariable.indexOf('probability_of_trading') !== -1) {
return <StateVariableProposalRiskFactors kvb={kvb} />;
} else {
return <StateVariableProposalUnknown kvb={kvb} />;
}
};
@@ -0,0 +1,124 @@
import { t } from '@vegaprotocol/react-helpers';
import zip from 'lodash/zip';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
import { StateVariableProposalUnknown } from './unknown';
interface StateVariableProposalRiskFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* A dumb as rocks function completely tied to what the structure of this variable should be
*
* @param kvb The key/value bundle
* @returns Object
*/
export function getValues(kvb: StateVariableProposalRiskFactorsProps['kvb']) {
try {
const template = {
bid: {
offsetTolerance: '-',
probabilityTolerance: '-',
offset: [] as Readonly<string[]>,
probability: [] as Readonly<string[]>,
rows: [] as [string | undefined, string | undefined][],
},
ask: {
offsetTolerance: '-',
probabilityTolerance: '-',
offset: [] as Readonly<string[]>,
probability: [] as Readonly<string[]>,
rows: [] as [string | undefined, string | undefined][],
},
};
kvb.forEach((v) => {
if (v.key === 'bidOffset') {
template.bid.offsetTolerance = v.tolerance || '-';
template.bid.offset = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'bidProbability') {
template.bid.probabilityTolerance = v.tolerance || '-';
template.bid.probability = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'askOffset') {
template.ask.offsetTolerance = v.tolerance || '-';
template.ask.offset = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'askProbability') {
template.ask.probabilityTolerance = v.tolerance || '-';
template.ask.probability = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
}
});
// Bundles up offset and probability in to a row
if (template.bid.offset.length > 0 && template.bid.probability.length > 0) {
template.bid.rows = zip(template.bid.offset, template.bid.probability);
}
if (template.ask.offset.length > 0 && template.ask.probability.length > 0) {
template.ask.rows = zip(template.ask.offset, template.ask.probability);
}
return template;
} catch (e) {
// This will result in the table not being rendered
return null;
}
}
/**
* State Variable proposals updating Risk Factors. This contains two bundles,
* a long vector and a short vector
*/
export const StateVariableProposalRiskFactors = ({
kvb,
}: StateVariableProposalRiskFactorsProps) => {
const v = getValues(kvb);
const all = v ? zip(v.bid.rows, v.ask.rows) : [];
if (all.length === 0) {
// Give up, do a JSON view
return <StateVariableProposalUnknown kvb={kvb} />;
}
return (
<Table allowWrap={true} className="text-xs lg:text-base max-w-2xl">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Mid offset')}</TableHeader>
<TableHeader align="right">{t('Bid probability')}</TableHeader>
<TableHeader align="right" className="pl-2">
{t('Ask probability')}
</TableHeader>
</TableRow>
</thead>
<tbody>
{all.map((r) => {
// Simple remapping of the data to protect against undefineds
const row = {
o: r[0] ? r[0][0] : r[1] ? r[1][0] : '-',
b: r[0] ? r[0][1] : '-',
a: r[1] ? r[1][1] : '-',
};
return (
<TableRow key={`${row.o}${row.b}${row.a}`}>
<TableCell align="left">{row.o}</TableCell>
<TableCell align="right" className="font-mono">
{row.b}
</TableCell>
<TableCell align="right" className="pl-2 font-mono">
{row.a}
</TableCell>
</TableRow>
);
})}
</tbody>
</Table>
);
};
@@ -0,0 +1,168 @@
import { getValues, StateVariableProposalRiskFactors } from './risk-factors';
import type { components } from '../../../../../types/explorer';
import { render } from '@testing-library/react';
type kvb = components['schemas']['vegaKeyValueBundle'][];
describe('Risk Factors: getValues', () => {
it('returns null if null is passed in', () => {
const res = getValues(null as unknown as kvb);
expect(res).toBeNull();
});
it('returns a blank template if kvb is empty', () => {
const res = getValues([]);
expect(res).not.toBeNull();
expect(res?.bid.offsetTolerance).toEqual('-');
expect(res?.bid.probabilityTolerance).toEqual('-');
expect(res?.bid.probability).toEqual([]);
expect(res?.bid.offset).toEqual([]);
expect(res?.bid.rows.length).toEqual(0);
expect(res?.ask.offsetTolerance).toEqual('-');
expect(res?.ask.probabilityTolerance).toEqual('-');
expect(res?.ask.probability).toEqual([]);
expect(res?.ask.offset).toEqual([]);
expect(res?.ask.rows.length).toEqual(0);
});
it('parses out a correct bid offset and probability', () => {
const k: kvb = [
{
key: 'bidOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['2'] } },
},
];
const res = getValues(k);
expect(res?.bid.offset).toEqual(['1']);
expect(res?.bid.probability).toEqual(['2']);
expect(res?.bid.rows).toEqual([['1', '2']]);
});
it('parses out a correct ask offset and probability', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2'] } },
},
];
const res = getValues(k);
expect(res?.ask.offset).toEqual(['1']);
expect(res?.ask.probability).toEqual(['2']);
expect(res?.ask.rows).toEqual([['1', '2']]);
});
it('parses out a correct ask/bid offset and probability', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4'] } },
},
];
const res = getValues(k);
expect(res?.ask.rows).toEqual([['1', '2']]);
expect(res?.bid.rows).toEqual([['3', '4']]);
});
});
describe('Risk Factors: component', () => {
it('renders 3 rows correctly', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2.2', '2.3', '2.4'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
},
];
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
expect(screen.getByText('Mid offset')).toBeInTheDocument();
expect(screen.getByText('Bid probability')).toBeInTheDocument();
expect(screen.getByText('Ask probability')).toBeInTheDocument();
// First row
expect(screen.getByText('1.1')).toBeInTheDocument();
expect(screen.getByText('2.2')).toBeInTheDocument();
expect(screen.getByText('4.4')).toBeInTheDocument();
// Second row
expect(screen.getByText('1.2')).toBeInTheDocument();
expect(screen.getByText('2.3')).toBeInTheDocument();
expect(screen.getByText('4.5')).toBeInTheDocument();
// Third row
expect(screen.getByText('1.3')).toBeInTheDocument();
expect(screen.getByText('2.4')).toBeInTheDocument();
expect(screen.getByText('4.6')).toBeInTheDocument();
});
it('renders uneven row counts correctly', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1.1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2.2'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
},
];
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
expect(screen.getByText('Mid offset')).toBeInTheDocument();
expect(screen.getByText('Bid probability')).toBeInTheDocument();
expect(screen.getByText('Ask probability')).toBeInTheDocument();
// First row, as previous test
expect(screen.getByText('1.1')).toBeInTheDocument();
expect(screen.getByText('2.2')).toBeInTheDocument();
expect(screen.getByText('4.4')).toBeInTheDocument();
// Second row - offset comes from bid, not ask
expect(screen.getByText('1.2')).toBeInTheDocument();
expect(screen.getByText('4.5')).toBeInTheDocument();
// Third row
expect(screen.getByText('1.3')).toBeInTheDocument();
expect(screen.getByText('4.6')).toBeInTheDocument();
// The askOffset levels without a probability render -
expect(screen.getAllByText('-')).toHaveLength(2);
});
});
@@ -0,0 +1,52 @@
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
import { getValues } from './bound-factors';
interface StateVariableProposalBoundFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* State Variable proposals updating Bound Factors. This contains two bundles,
* an up vector and a down vector
*
* This is nearly identical to risk factors.
*/
export const StateVariableProposalBoundFactors = ({
kvb,
}: StateVariableProposalBoundFactorsProps) => {
const v = getValues(kvb);
return (
<Table allowWrap={true} className="w-1/3">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Parameter')}</TableHeader>
<TableHeader align="center">{t('New value')}</TableHeader>
<TableHeader align="right">{t('Tolerance')}</TableHeader>
</TableRow>
</thead>
<tbody>
<TableRow modifier="bordered">
<TableCell>{t('Up')}</TableCell>
<TableCell align="right" className="font-mono">
{v.up.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.up.tolerance}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Down')}</TableCell>
<TableCell align="right" className="font-mono">
{v.down.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.down.tolerance}
</TableCell>
</TableRow>
</tbody>
</Table>
);
};
@@ -0,0 +1,16 @@
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import type { components } from '../../../../../types/explorer';
interface StateVariableProposalUnknownProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* State Variable proposals of an unknown type. Let's just dump
* it out.
*/
export const StateVariableProposalUnknown = ({
kvb,
}: StateVariableProposalUnknownProps) => {
return <SyntaxHighlighter data={kvb} />;
};
@@ -53,7 +53,7 @@ export const TxDetailsBatch = ({
let index = 0;
return (
<div key={`tx-${index}`}>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -32,7 +32,7 @@ export const TxDetailsChainEvent = ({
}
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<ChainEvent txData={txData} />
</TableWithTbody>
@@ -38,7 +38,7 @@ export const TxDetailsDataSubmission = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -35,7 +35,7 @@ export const TxDetailsDelegate = ({
txData.command.delegateSubmission;
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{d.nodeId ? (
<TableRow modifier="bordered">
@@ -0,0 +1,62 @@
import { t } from '@vegaprotocol/react-helpers';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { BlockLink } from '../../links';
import { StatusMessage } from '../../status-message';
import { ENV } from '../../../config/env';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { components } from '../../../../types/explorer';
interface TxDetailsProtocolUpgradeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Validator event: Protocol Upgrade proposal
*/
export const TxDetailsProtocolUpgrade = ({
txData,
pubKey,
blockData,
}: TxDetailsProtocolUpgradeProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const upgrade: components['schemas']['v1ProtocolUpgradeProposal'] =
txData.command.protocolUpgradeProposal;
if (!upgrade || !upgrade.upgradeBlockHeight || !upgrade.vegaReleaseTag) {
return (
<StatusMessage>{t('Invalid upgrade proposal format')}</StatusMessage>
);
}
const urlBase = ENV.dataSources.vegaRepoUrl;
const release = upgrade.vegaReleaseTag;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Upgrade at block')}</TableCell>
<TableCell>
<BlockLink height={upgrade.upgradeBlockHeight} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Upgrade to')}</TableCell>
<TableCell>
<ExternalLink href={`${urlBase}${release}`}>
{upgrade.vegaReleaseTag}
</ExternalLink>
</TableCell>
</TableRow>
</TableWithTbody>
);
};
@@ -8,10 +8,8 @@ import { TxDetailsHeartbeat } from './tx-hearbeat';
import { TxDetailsGeneric } from './tx-generic';
import { TxDetailsBatch } from './tx-batch';
import { TxDetailsChainEvent } from './tx-chain-event';
import { TxContent } from '../../../routes/txs/id/tx-content';
import { TxDetailsNodeVote } from './tx-node-vote';
import { TxDetailsOrderCancel } from './tx-order-cancel';
import get from 'lodash/get';
import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation';
@@ -20,6 +18,10 @@ import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission';
import { TxProposalVote } from './tx-proposal-vote';
import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
import { TxDetailsIssueSignatures } from './tx-issue-signatures';
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -45,23 +47,9 @@ export const TxDetailsWrapper = ({
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
return (
<div key={`txd-${txData.hash}`}>
<section>{child({ txData, pubKey, blockData })}</section>
<details title={t('Decoded transaction')} className="mt-3">
<summary className="cursor-pointer">{t('Decoded transaction')}</summary>
<TxContent data={txData} />
</details>
{raw ? (
<details title={t('Raw transaction')} className="mt-3">
<summary className="cursor-pointer">{t('Raw transaction')}</summary>
<code className="break-all font-mono text-xs">{raw}</code>
</details>
) : null}
</div>
);
};
@@ -81,16 +69,22 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
// These come from https://github.com/vegaprotocol/vega/blob/develop/core/txn/command.go#L72-L98
switch (txData.type) {
case 'Issue Signatures':
return TxDetailsIssueSignatures;
case 'Submit Order':
return TxDetailsOrder;
case 'Submit Oracle Data':
return TxDetailsDataSubmission;
case 'Protocol Upgrade':
return TxDetailsProtocolUpgrade;
case 'Cancel Order':
return TxDetailsOrderCancel;
case 'Amend Order':
return TxDetailsOrderAmend;
case 'Validator Heartbeat':
return TxDetailsHeartbeat;
case 'Vote on Proposal':
return TxProposalVote;
case 'Batch Market Instructions':
return TxDetailsBatch;
case 'Chain Event':
@@ -109,6 +103,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsDelegate;
case 'Undelegate':
return TxDetailsUndelegate;
case 'State Variable Proposal':
return TxDetailsStateVariable;
default:
return TxDetailsGeneric;
}
@@ -11,8 +11,7 @@ interface TxDetailsGenericProps {
}
/**
* If there is not yet a custom component for a transaction, just display
* the basic details. This allows someone to view the decoded transaction.
* A node is
*/
export const TxDetailsGeneric = ({
txData,
@@ -24,7 +23,7 @@ export const TxDetailsGeneric = ({
}
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
</TableWithTbody>
);
@@ -60,7 +60,7 @@ export const TxDetailsHeartbeat = ({
const blockHeight = txData.command.blockHeight || '';
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Node')}</TableCell>
@@ -0,0 +1,77 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
import { NodeLink } from '../../links';
type Command = components['schemas']['v1IssueSignatures'];
const kind: Record<components['schemas']['v1NodeSignatureKind'], string> = {
NODE_SIGNATURE_KIND_UNSPECIFIED: 'Unspecified',
NODE_SIGNATURE_KIND_ASSET_NEW: 'New asset',
NODE_SIGNATURE_KIND_ASSET_WITHDRAWAL: 'Asset withdrawal',
NODE_SIGNATURE_KIND_ASSET_UPDATE: ' Asset update',
NODE_SIGNATURE_KIND_ERC20_MULTISIG_SIGNER_ADDED: 'Multisig signer added',
NODE_SIGNATURE_KIND_ERC20_MULTISIG_SIGNER_REMOVED: 'Multisig signer removed',
};
interface TxDetailsGenericProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* If there is not yet a custom component for a transaction, just display
* the basic details. This allows someone to view the decoded transaction.
*/
export const TxDetailsIssueSignatures = ({
txData,
pubKey,
blockData,
}: TxDetailsGenericProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd: Command = txData.command;
const k = cmd.kind ? kind[cmd.kind] : null;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{k ? (
<TableRow modifier="bordered">
<TableCell>{t('Kind')}</TableCell>
<TableCell>{k}</TableCell>
</TableRow>
) : null}
{cmd.submitter ? (
<TableRow modifier="bordered">
<TableCell>{t('ETH key')}</TableCell>
<TableCell>
<EthExplorerLink
id={cmd.submitter}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
{cmd.validatorNodeId ? (
<TableRow modifier="bordered">
<TableCell>{t('Validator')}</TableCell>
<TableCell>
<NodeLink id={cmd.validatorNodeId} />
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};
@@ -36,7 +36,7 @@ export const TxDetailsLiquidityAmendment = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -55,6 +55,7 @@ export const TxDetailsLiquidityAmendment = ({
<PriceInMarket
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
@@ -1,10 +1,11 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
export type LiquidityCancellation =
components['schemas']['v1LiquidityProvisionCancellation'];
@@ -36,7 +37,7 @@ export const TxDetailsLiquidityCancellation = ({
const marketId: string = cancel.marketId || '-';
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
@@ -35,7 +35,7 @@ export const TxDetailsLiquiditySubmission = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -54,6 +54,7 @@ export const TxDetailsLiquiditySubmission = ({
<PriceInMarket
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
@@ -42,7 +42,7 @@ export const TxDetailsNodeVote = ({
}
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{data && !!data.deposit
? TxDetailsNodeVoteDeposit({ deposit: data })
@@ -29,7 +29,7 @@ export const TxDetailsOrderAmend = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -29,7 +29,7 @@ export const TxDetailsOrderCancel = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -6,6 +6,7 @@ import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
import Hash from '../../links/hash';
interface TxDetailsOrderProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -38,7 +39,7 @@ export const TxDetailsOrder = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -47,7 +48,13 @@ export const TxDetailsOrder = ({
<TableRow modifier="bordered">
<TableCell>{t('Order')}</TableCell>
<TableCell>
<code>{deterministicId}</code>
<Hash text={deterministicId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Market ID')}</TableCell>
<TableCell>
<MarketLink id={marketId} showMarketName={false} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -0,0 +1,57 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import ProposalLink from '../../links/proposal-link/proposal-link';
interface TxProposalVoteProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* A vote on a proposal.
*
* One inconsistency here that feels right but should be standardised is that there are two rows
* for the proposal ID, one that creates a link with the text of the proposal title that takes
* a user out to the governance site, and the other that just shows the ID. Both are useful, but
* doesn't feel quite right. This could be fixed with a separate component to display a preview
* of the proposal and link off to the governance site, removing the title from the header. Or
* something else. For now, this is more useful than the default view
*/
export const TxProposalVote = ({
txData,
pubKey,
blockData,
}: TxProposalVoteProps) => {
if (!txData || !txData.command.voteSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const vote = txData.command.voteSubmission.value ? '👍' : '👎';
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal details')}</TableCell>
<TableCell>
<ProposalLink id={txData.command.voteSubmission.proposalId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Vote')}</TableCell>
<TableCell>{vote}</TableCell>
</TableRow>
</TableWithTbody>
);
};
@@ -0,0 +1,57 @@
import {
hackyGetMarketFromStateVariable,
hackyGetVariableFromStateVariable,
} from './tx-state-variable-proposal';
describe('Hacky Get market from state variable', () => {
it('Extracts a market id from a known state variable proposal id', () => {
const knownId =
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f_5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
const res = hackyGetMarketFromStateVariable(knownId);
expect(res).toEqual(
'5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba'
);
});
it('Returns null if the string looks a bit like the known one, but with different segments', () => {
const knownId =
'5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
const res = hackyGetMarketFromStateVariable(knownId);
expect(res).toEqual(null);
});
it('Handles empty/weird data', () => {
expect(hackyGetMarketFromStateVariable(null as unknown as string)).toEqual(
null
);
expect(hackyGetMarketFromStateVariable('')).toEqual(null);
expect(
hackyGetMarketFromStateVariable(undefined as unknown as string)
).toEqual(null);
expect(hackyGetMarketFromStateVariable(2 as unknown as string)).toEqual(
null
);
});
});
describe('Hacky Get Variable from state variable proposal id', () => {
it('Extracts an variable name from a known state variable proposal id', () => {
const knownId =
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f_5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
const res = hackyGetVariableFromStateVariable(knownId);
expect(res).toEqual('probability of trading');
});
it('Handles empty/weird data', () => {
expect(
hackyGetVariableFromStateVariable(null as unknown as string)
).toEqual(null);
expect(hackyGetVariableFromStateVariable('')).toEqual(null);
expect(
hackyGetVariableFromStateVariable(undefined as unknown as string)
).toEqual(null);
expect(hackyGetVariableFromStateVariable(2 as unknown as string)).toEqual(
null
);
});
});
@@ -0,0 +1,113 @@
import { t } from '@vegaprotocol/react-helpers';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { MarketLink } from '../../links';
import type { components } from '../../../../types/explorer';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { StateVariableProposalWrapper } from './state-variable/data-wrapper';
interface TxDetailsStateVariableProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* There is no market ID in the event, but it appears to be encoded in to the variable
* ID so let's pull it out. MarketLink component will handle if it isn't a real market.
*
* Given how liable to break this is, it's wrapped in a try catch
*
* @param stateVarId The full state variable proposal variable name
* @returns null or a string market id
*/
export function hackyGetMarketFromStateVariable(
stateVarId?: string
): string | null {
try {
const res = stateVarId ? stateVarId.split('_')[1] : null;
return res && res.length === 64 ? res : null;
} catch (e) {
return null;
}
}
/**
* There is no event name in the event, but it appears to be encoded in to the variable
* ID so let's pull it out. Will display nothing if it doesn't parse as expected
*
* Given how liable to break this is, it's wrapped in a try catch
*
* @param stateVarId The full state variable proposal variable name
* @returns null or a string variable name
*/
export function hackyGetVariableFromStateVariable(
stateVarId?: string
): string | null {
try {
if (!stateVarId) {
return null;
}
return stateVarId.split('_').slice(2).join(' ').replace('-', ' ');
} catch (e) {
return null;
}
}
/**
* State Variable proposals
*/
export const TxDetailsStateVariable = ({
txData,
pubKey,
blockData,
}: TxDetailsStateVariableProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const command: components['schemas']['v1StateVariableProposal'] =
txData.command.stateVariableProposal;
const variable = hackyGetVariableFromStateVariable(
command.proposal?.stateVarId
);
const marketId = hackyGetMarketFromStateVariable(
command.proposal?.stateVarId
);
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
{marketId ? (
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Variable')}</TableCell>
<TableCell className="capitalize">
<span>{variable}</span>
</TableCell>
</TableRow>
</TableWithTbody>
<section>
<StateVariableProposalWrapper
stateVariable={command.proposal?.stateVarId}
kvb={command.proposal?.kvb}
/>
</section>
</>
);
};
@@ -46,7 +46,7 @@ export const TxDetailsUndelegate = ({
txData.command.undelegateSubmission;
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{u.nodeId ? (
<TableRow modifier="bordered">
@@ -41,7 +41,7 @@ export const TxDetailsWithdrawSubmission = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -0,0 +1,75 @@
import { useState } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import get from 'lodash/get';
import { Select } from '@vegaprotocol/ui-toolkit';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
export function getClassName(showTxData: ShowTxDataType) {
const baseClasses =
'font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]';
if (showTxData === 'JSON') {
return `${baseClasses} whitespace-pre overflow-x-scroll`;
} else {
return baseClasses;
}
}
export function getContents(
showTxData: ShowTxDataType,
txData: BlockExplorerTransactionResult | null,
blockData: TendermintBlocksResponse | null | undefined
) {
if (showTxData === 'JSON') {
if (txData) {
return JSON.stringify(txData.command, undefined, 1);
}
} else {
if (txData && blockData) {
return get(blockData, `result.block.data.txs[${txData.index}]`);
}
}
return '-';
}
type ShowTxDataType = 'JSON' | 'base64';
interface TxDataViewProps {
txData: BlockExplorerTransactionResult | undefined;
blockData: TendermintBlocksResponse | undefined;
}
export const TxDataView = ({ txData, blockData }: TxDataViewProps) => {
const [showTxData, setShowTxData] = useState<ShowTxDataType>('JSON');
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<details title={t('Show raw transaction')}>
<summary className="cursor-pointer">{t('Show raw transaction')}</summary>
<div className="py-4">
<textarea
readOnly={true}
className={getClassName(showTxData)}
rows={12}
cols={120}
value={getContents(showTxData, txData, blockData)}
/>
<div className="w-40">
<Select
placeholder="View as..."
onChange={(v) => setShowTxData(v.target.value as ShowTxDataType)}
value={showTxData}
>
<option value={'JSON'}>JSON</option>
<option value={'base64'}>Base64</option>
</Select>
</div>
</div>
</details>
);
};
@@ -115,7 +115,7 @@ export const TxsInfiniteList = ({
className="List"
height={995}
itemCount={itemCount}
itemSize={isStacked ? 134 : 72}
itemSize={isStacked ? 134 : 50}
onItemsRendered={onItemsRendered}
ref={ref}
width={'100%'}
+3
View File
@@ -10,12 +10,15 @@ const truthy = ['1', 'true'];
export const ENV = {
// Data sources
// Environment
env: windowOrDefault('NX_VEGA_ENV'),
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
dataSources: {
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
},
flags: {
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),
+5 -4
View File
@@ -5,6 +5,7 @@ import type {
BlockExplorerTransactions,
} from '../routes/types/block-explorer-response';
import { DATA_SOURCES } from '../config';
import isNumber from 'lodash/isNumber';
export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[];
@@ -54,15 +55,15 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => {
} = useFetch<BlockExplorerTransactions>(url, {}, false);
useEffect(() => {
if (data?.transactions?.length) {
if (data && isNumber(data?.transactions?.length)) {
setTxsState((prev) => ({
txsData: [...prev.txsData, ...data.transactions],
hasMoreTxs: true,
hasMoreTxs: data.transactions.length > 0,
lastCursor:
data.transactions[data.transactions.length - 1].cursor || '',
data.transactions[data.transactions.length - 1]?.cursor || '',
}));
}
}, [setTxsState, data?.transactions]);
}, [setTxsState, data]);
const loadTxs = useCallback(() => {
return refetch({
@@ -51,6 +51,9 @@ fragment ExplorerOracleDataSource on OracleSpec {
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection {
pageInfo {
hasNextPage
}
edges {
node {
externalData {
@@ -79,12 +82,21 @@ fragment ExplorerOracleDataConnection on OracleSpec {
}
query ExplorerOracleSpecs {
oracleSpecsConnection {
oracleSpecsConnection(pagination: { first: 50 }) {
pageInfo {
hasNextPage
}
edges {
node {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
}
}
query ExplorerOracleSpecById($id: ID!) {
oracleSpec(oracleSpecId: $id) {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
+55 -7
View File
@@ -5,12 +5,19 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } };
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } } } | null> | null } | null };
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
export const ExplorerOracleDataSourceFragmentDoc = gql`
fragment ExplorerOracleDataSource on OracleSpec {
@@ -67,6 +74,9 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection {
pageInfo {
hasNextPage
}
edges {
node {
externalData {
@@ -96,17 +106,18 @@ export const ExplorerOracleDataConnectionFragmentDoc = gql`
`;
export const ExplorerOracleSpecsDocument = gql`
query ExplorerOracleSpecs {
oracleSpecsConnection {
oracleSpecsConnection(pagination: {first: 50}) {
pageInfo {
hasNextPage
}
edges {
node {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
}
}
${ExplorerOracleDataSourceFragmentDoc}
${ExplorerOracleDataConnectionFragmentDoc}`;
${ExplorerOracleDataSourceFragmentDoc}`;
/**
* __useExplorerOracleSpecsQuery__
@@ -133,4 +144,41 @@ export function useExplorerOracleSpecsLazyQuery(baseOptions?: Apollo.LazyQueryHo
}
export type ExplorerOracleSpecsQueryHookResult = ReturnType<typeof useExplorerOracleSpecsQuery>;
export type ExplorerOracleSpecsLazyQueryHookResult = ReturnType<typeof useExplorerOracleSpecsLazyQuery>;
export type ExplorerOracleSpecsQueryResult = Apollo.QueryResult<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>;
export type ExplorerOracleSpecsQueryResult = Apollo.QueryResult<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>;
export const ExplorerOracleSpecByIdDocument = gql`
query ExplorerOracleSpecById($id: ID!) {
oracleSpec(oracleSpecId: $id) {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
${ExplorerOracleDataSourceFragmentDoc}
${ExplorerOracleDataConnectionFragmentDoc}`;
/**
* __useExplorerOracleSpecByIdQuery__
*
* To run a query within a React component, call `useExplorerOracleSpecByIdQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerOracleSpecByIdQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerOracleSpecByIdQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerOracleSpecByIdQuery(baseOptions: Apollo.QueryHookOptions<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>(ExplorerOracleSpecByIdDocument, options);
}
export function useExplorerOracleSpecByIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>(ExplorerOracleSpecByIdDocument, options);
}
export type ExplorerOracleSpecByIdQueryHookResult = ReturnType<typeof useExplorerOracleSpecByIdQuery>;
export type ExplorerOracleSpecByIdLazyQueryHookResult = ReturnType<typeof useExplorerOracleSpecByIdLazyQuery>;
export type ExplorerOracleSpecByIdQueryResult = Apollo.QueryResult<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>;
@@ -37,7 +37,7 @@ describe('Oracle Data view', () => {
dataConnection: {
edges: [],
},
} as ExplorerOracleDataConnectionFragment)
} as unknown as ExplorerOracleDataConnectionFragment)
);
expect(res.container).toBeEmptyDOMElement();
});
@@ -67,7 +67,7 @@ describe('Oracle Signers component', () => {
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '123',
key: '1234567891234567789123456789123456778912345678912345677891234567',
},
},
],
@@ -4,6 +4,7 @@ import {
EthExplorerLinkTypes,
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import { remove0x } from '@vegaprotocol/react-helpers';
import type { SourceType } from './oracle';
@@ -14,7 +15,15 @@ export type Signer = {
};
export function getAddressTypeLabel(signer: Signer) {
return signer.__typename === 'ETHAddress' ? 'ETH' : 'Vega';
const res = signer.__typename === 'ETHAddress' ? 'ETH' : 'Vega';
// This is a hack: some older oracles were submitted before proper checks stopped
// ETH addresses being returned as Vega addresses
if (res === 'Vega' && signer?.key?.length !== 64) {
return 'ETH';
} else {
return res;
}
}
export function getAddress(signer: Signer) {
@@ -29,6 +38,16 @@ export function getAddressLink(signer: Signer) {
if (signer.__typename === 'ETHAddress') {
return <EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />;
} else if (signer.__typename === 'PubKey' && address.length !== 64) {
// This is a hack: some older oracles were submitted before proper checks stopped
// ETH addresses being returned as Vega addresses
// Hacky 0x prefixing as a bonus
return (
<EthExplorerLink
id={`0x${remove0x(address)}`}
type={EthExplorerLinkTypes.address}
/>
);
} else if (signer.__typename === 'PubKey') {
return <PartyLink id={address} />;
}
@@ -13,6 +13,8 @@ import { OracleData } from './oracle-data';
import { OracleFilter } from './oracle-filter';
import { OracleDetailsType } from './oracle-details-type';
import { OracleMarkets } from './oracle-markets';
import { OracleSigners } from './oracle-signers';
import OracleLink from '../../../components/links/oracle-link/oracle-link';
export type SourceType =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
@@ -20,37 +22,51 @@ export type SourceType =
interface OracleDetailsProps {
id: string;
dataSource: ExplorerOracleDataSourceFragment;
dataConnection: ExplorerOracleDataConnectionFragment;
dataConnection?: ExplorerOracleDataConnectionFragment;
// Defaults to false. Hides the count of 'broadcasts' this oracle has seen
showBroadcasts?: boolean;
}
/**
* Notes:
* - Matched data is really 'Data that matched this oracle' and given oracles are unique
* to each market, and each serves either as trading termination or settlement, really
* they will only ever see 1 match (most likely). So it should be more like 'Has seen
* data' vs 'Has not yet seen data'
*/
export const OracleDetails = ({
id,
dataSource,
dataConnection,
showBroadcasts = false,
}: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.dataConnection.edges?.length || 0;
const reportsCount: number =
dataConnection?.dataConnection.edges?.length || 0;
return (
<div>
<TableWithTbody>
<TableWithTbody className="mb-2">
<TableRow modifier="bordered">
<TableHeader scope="row">{t('ID')}</TableHeader>
<TableCell modifier="bordered">{id}</TableCell>
<TableCell modifier="bordered">
<OracleLink id={id} />
</TableCell>
</TableRow>
<OracleDetailsType type={sourceType.__typename} />
{
// Disabled until https://github.com/vegaprotocol/vega/issues/7286 is released
/*<OracleSigners sourceType={sourceType} />*/
}
<OracleSigners sourceType={sourceType} />
<OracleMarkets id={id} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Broadcasts')}</TableHeader>
<TableCell modifier="bordered">{reportsCount}</TableCell>
<TableHeader scope="row">{t('Matched data')}</TableHeader>
<TableCell modifier="bordered">
{showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'}
</TableCell>
</TableRow>
</TableWithTbody>
<OracleFilter data={dataSource} />
<OracleData data={dataConnection} />
{showBroadcasts && dataConnection ? (
<OracleData data={dataConnection} />
) : null}
</div>
);
};
@@ -0,0 +1,45 @@
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../../components/route-title';
import { t } from '@vegaprotocol/react-helpers';
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { OracleDetails } from '../components/oracle';
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
const Oracles = () => {
const { data, loading } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']);
useScrollToLocation();
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
{loading ? <Loader /> : null}
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
return (
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id}
dataSource={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</section>
);
};
export default Oracles;
@@ -0,0 +1,49 @@
import { RouteTitle } from '../../../components/route-title';
import { RenderFetched } from '../../../components/render-fetched';
import { t, truncateByChars } from '@vegaprotocol/react-helpers';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { useParams } from 'react-router-dom';
import { useExplorerOracleSpecByIdQuery } from '../__generated__/Oracles';
import { OracleDetails } from '../components/oracle';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import { TruncateInline } from '../../../components/truncate/truncate';
export const Oracle = () => {
const { id } = useParams<{ id: string }>();
useDocumentTitle(['Oracle', `Oracle #${truncateByChars(id || '1', 5, 5)}`]);
const { data, error, loading } = useExplorerOracleSpecByIdQuery({
variables: {
id: id || '1',
},
});
return (
<section>
<RouteTitle data-testid="block-header">
{t(`Oracle `)}
<TruncateInline startChars={5} endChars={5} text={id || '1'} />
</RouteTitle>
<RenderFetched error={error} loading={loading}>
{data?.oracleSpec ? (
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id || ''}
dataSource={data?.oracleSpec}
dataConnection={data?.oracleSpec}
showBroadcasts={true}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(data, ['__typename'])} />
</details>
</div>
) : (
<span></span>
)}
</RenderFetched>
</section>
);
};
+4 -42
View File
@@ -1,45 +1,7 @@
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../components/route-title';
import { t } from '@vegaprotocol/react-helpers';
import { useExplorerOracleSpecsQuery } from './__generated__/Oracles';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { OracleDetails } from './components/oracle';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
import { Outlet } from 'react-router-dom';
const Oracles = () => {
const { data, loading } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']);
useScrollToLocation();
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
{loading ? <Loader /> : null}
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
return (
<div id={id} key={id} className="mb-10 cursor-pointer">
<OracleDetails
id={id}
dataSource={o?.node}
dataConnection={o?.node}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</section>
);
const OraclePage = () => {
return <Outlet />;
};
export default Oracles;
export default OraclePage;
@@ -10,7 +10,7 @@ import { useDocumentTitle } from '../../../hooks/use-document-title';
export const JumpToParty = () => {
const navigate = useNavigate();
useDocumentTitle(['Parties']);
useDocumentTitle(['Public keys']);
const handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
@@ -27,8 +27,8 @@ export const JumpToParty = () => {
};
return (
<JumpTo
label={t('Go to party')}
placeholder={t('Party id')}
label={t('Go to public key')}
placeholder={t('Public key')}
inputId="party-input"
inputType="text"
inputName="partyId"
@@ -40,7 +40,7 @@ export const JumpToParty = () => {
const Parties = () => {
return (
<section>
<RouteTitle data-testid="parties-header">{t('Parties')}</RouteTitle>
<RouteTitle data-testid="parties-header">{t('Public keys')}</RouteTitle>
<JumpToParty />
</section>
);
@@ -14,7 +14,7 @@ type PartyIdErrorProps = {
const PartyIdError = ({ id, error }: PartyIdErrorProps) => {
const end = isValidPartyId(id)
? t('No accounts or transactions found for: ')
: 'Invalid party id: ';
: 'Invalid public key: ';
return (
<section>
<p>
@@ -16,7 +16,7 @@ import { PartyAccounts } from './components/party-accounts';
const Party = () => {
const { party } = useParams<{ party: string }>();
useDocumentTitle(['Parties', party || '-']);
useDocumentTitle(['Public keys', party || '-']);
const partyId = toNonHex(party ? party : '');
const { isMobile } = useScreenDimensions();
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
@@ -44,7 +44,7 @@ const Party = () => {
/>
) : (
<Panel>
<p>No party found for key {party}</p>
<p>No data found for public key {party}</p>
</Panel>
);
@@ -71,7 +71,7 @@ const Party = () => {
className="font-alpha uppercase font-xl mb-4 text-zinc-800 dark:text-zinc-200"
data-testid="parties-header"
>
{t('Party')}
{t('Public key')}
</h1>
{partyRes.data ? (
<>
+19 -13
View File
@@ -3,7 +3,9 @@ import BlockPage from './blocks';
import Governance from './governance';
import Home from './home';
import Markets from './markets';
import Oracles from './oracles';
import OraclePage from './oracles';
import Oracles from './oracles/home';
import { Oracle } from './oracles/id';
import Party from './parties';
import { Parties } from './parties/home';
import { Party as PartySingle } from './parties/id';
@@ -85,17 +87,6 @@ const marketsRoutes = flags.markets
]
: [];
const oraclesRoutes = flags.oracles
? [
{
path: Routes.ORACLES,
name: 'Oracles',
text: t('Oracles'),
element: <Oracles />,
},
]
: [];
const networkParametersRoutes = flags.networkParameters
? [
{
@@ -161,12 +152,27 @@ const routerConfig = [
},
],
},
{
path: Routes.ORACLES,
name: 'Oracles',
text: t('Oracles'),
element: <OraclePage />,
children: [
{
index: true,
element: <Oracles />,
},
{
path: ':id',
element: <Oracle />,
},
],
},
...partiesRoutes,
...assetsRoutes,
...genesisRoutes,
...governanceRoutes,
...marketsRoutes,
...oraclesRoutes,
...networkParametersRoutes,
...validators,
];
@@ -38,7 +38,7 @@ const Tx = () => {
to={`/${Routes.TX}`}
>
<Icon
className="text-vega-light-300 dark:text-vega-light-300"
className="text-vega-light-150 dark:text-vega-light-150"
name={IconNames.CHEVRON_LEFT}
/>
All Transactions
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

File diff suppressed because it is too large Load Diff
+12
View File
@@ -16,4 +16,16 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
#Test configuration variables
CYPRESS_FAIRGROUND=false
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
CYPRESS_VEGA_ENV=CUSTOM
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_WALLET_API_TOKEN=

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