Compare commits

..
Author SHA1 Message Date
Edd 8a3612a18c chore: add genesis validation script 2023-11-07 11:25:25 +00:00
m.ray 7a91f48bcb fix(trading): update LP table on Updating next epoch (#5204) 2023-11-06 15:47:08 +01:00
Edd b43ea0f60b feat(explorer): update liquidity submission view (#5187) 2023-11-03 15:52:27 +00:00
Matthew Russell 6e31fb03ae fix(accounts): fix key selection and validation, and add use max to transfers (#5184) 2023-11-03 15:12:40 +00:00
Matthew Russell d76fa13c5d chore(trading): fees page use window length in label (#5186) 2023-11-03 15:11:51 +00:00
Edd 89aeed6305 feat(explorer): new labels for proposals (#5190) 2023-11-03 14:58:56 +00:00
Edd 6e677084a3 feat(explorer): explorer oracle section rework (#5135) 2023-11-03 14:01:33 +00:00
m.ray 8069aa5ee7 fix(trading): hide funding tabs if market is not perp (#5181) 2023-11-03 10:49:31 +00:00
Matthew Russell b0169a0d39 fix(accounts): dont add vesting account balances to used total (#5182) 2023-11-02 16:56:56 -07:00
Edd 06cfd79415 feat(explorer): add timestamp to tx list (#5174) 2023-11-02 13:22:37 +00:00
Edd 443220283c fix(explorer): format new network params correctly (#5172) 2023-11-02 13:17:11 +00:00
Matthew Russell a642bf8ce4 chore(trading): misc changes to trades and transfer (#5175) 2023-11-01 15:57:38 -07:00
Matthew Russell 20233db706 fix(deal-ticket): fees breakdown rendering issues (#5173) 2023-11-01 13:32:51 -07:00
m.ray d4f50eb70c fix(trading): pagination limit for candles (#5169) 2023-11-01 12:21:36 -07:00
Matthew Russell e5d3f90d45 feat(trading): transfer to and from accounts on same key (#5161) 2023-11-01 11:11:17 +00:00
Bartłomiej Głownia 673c896e2f feat(deal-ticket): get discounts for estimation from previous epoch (#5163) 2023-11-01 10:09:56 +00:00
Matthew Russell deea63fa5e fix(trading): empty items loaded text (#5158) 2023-11-01 10:08:16 +00:00
Matthew Russell d31333538b chore(trading): ignore .next from lint (#5162) 2023-11-01 10:06:53 +00:00
Matthew Russell 4af9979a21 chore(trading): referrals stats tweaks (#5160) 2023-11-01 10:06:36 +00:00
m.ray 6421cf87c6 feat(trading): liquidity provision current and pending on update (#5152) 2023-10-31 11:24:18 +00:00
Matthew Russell 0ebfab64ff fix(ui-toolkit): sparkline appearing to go up when the price moved down (#5137) 2023-10-31 11:20:48 +00:00
Edd 142f08343b feat(governance): add update benefit tiers table (#5146) 2023-10-31 10:20:38 +00:00
m.ray 61aa45a9ed feat(trading): show fees paid across whole market (#5154) 2023-10-30 18:16:49 -07:00
Bartłomiej GłowniaandMatthew Russell 4c95db5fb3 feat(deal-ticket): calculate fees and discount base on volumeDiscountStats and referralSetStats (#5147)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-10-30 18:13:26 -07:00
ArtandMatthew Russell 3072b7824f fix(trading): referrer stats tiles (#5143)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-10-30 18:12:59 -07:00
Bartłomiej Głownia 0580e90171 feat(funding-payments): add load more button to the funding payments table (#5150) 2023-10-30 15:57:43 -07:00
Bartłomiej Głownia c440abc77d feat(trading): show full history of orders, fills and trades (#5105) 2023-10-27 14:21:26 +01:00
Matthew Russell fbafc726a4 feat(candles-chart): combine overlays and studies into indicators dropdown (#5129) 2023-10-27 14:21:14 +01:00
Joe Tsang dd1890d8c6 chore(cypress): fix failing assertions (#5132) 2023-10-27 14:21:02 +01:00
Matthew Russell c8e624eaba chore(trading): update fee factor with discount calculation (#5138) 2023-10-27 14:20:45 +01:00
Art cc6629ad27 fix(trading): referrals next tier tiles (#5139) 2023-10-27 13:48:43 +02:00
Art 9838efa00e feat(proposals): generic proposal toasts (#5134) 2023-10-27 13:17:51 +02:00
m.ray dac7142a98 feat(trading): add earmarked fees (#5123) 2023-10-27 13:09:01 +02:00
Matthew Russell 1397aafd25 fix(ui-toolkit): sparkline red shaded area color 2023-10-26 10:19:05 -07:00
Matthew Russell b192603e57 feat(ui-toolkit): sparkline shaded area (#5131) 2023-10-26 10:06:05 -07:00
Matthew Russell 8c2c8d987c feat(trading): add row click for fees table (#5130) 2023-10-26 08:38:52 -07:00
Matthew Russell 52c96794f7 feat(trading): fees page (#5055) 2023-10-25 14:59:30 -07:00
Ben 58ce016d4d chore(trading): suspend market test to console test (#5128) 2023-10-25 16:21:11 +01:00
Ben ac16eb06f5 chore(trading): delete fills cypress tests (#5122) 2023-10-25 14:02:43 +01:00
Ben 3319871617 chore(trading): delete dupe transfer tests (#5121) 2023-10-25 12:29:21 +00:00
Bartłomiej Głownia 37e850304a feat(trading): upgrade pennant@1.14.0 (#5120) 2023-10-25 12:44:23 +01:00
daro-maj 655131c744 test(trading): fix capsule test (#5116) 2023-10-25 12:39:18 +01:00
m.ray f1cb1b9408 fix(trading): quote base unit for size (#5124) 2023-10-25 12:36:57 +01:00
m.ray 891ac527a2 feat(trading): add tooltips for iceberg and post or reduce only (#5119) 2023-10-25 12:18:52 +01:00
Art 546087fb9f chore(trading): referrals with sidebar (#5117) 2023-10-25 11:14:00 +02:00
BenandMatthew Russell f7a82d33ba chore(trading): withdraw tests to jest (#5109)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-10-25 08:45:58 +01:00
Matthew Russell d85f413e41 chore(trading,positions): update estimatePosition query to use market decimals for liquidation price (#5005) 2023-10-24 17:30:17 -07:00
Edd c7d0025b4f feat(explorer): add determinsitic id to transfer tx (#5112) 2023-10-24 16:48:17 +01:00
Art 6c9272cd53 chore(trading): i18n-friendly referrals (#5113) 2023-10-24 17:43:33 +02:00
m.rayandasiaznik 82fb29d541 feat(trading): add liquidity provision new SLA parameters to liquidity tables (#5032)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
2023-10-24 10:32:07 +01:00
Art 43cd170c77 feat(trading): referrals (Mk2), referrals stats, apply preview (#5021) 2023-10-23 16:57:18 +02:00
Edd 1d39f81dfc fix(governance): remove restricted route from navigation (#5106) 2023-10-23 14:16:07 +00:00
Bartłomiej Głownia f5868b6f1b feat(trading): show when a market may be closed through governance (#5057) 2023-10-23 13:42:37 +02:00
m.ray 20e1522088 chore(trading): use quantum for liquidity supplied (#5099) 2023-10-23 10:24:26 +01:00
Radosław Szpiech ae02b153f8 chore(trading): fix cancel all button and move trading orders tests (#5100) 2023-10-23 08:34:50 +02:00
Maciek fee5f862a7 chore(trading): remove account history tab (#5098) 2023-10-20 15:08:19 +02:00
Edd c25362b084 feat(governance): add restricted page (#5097) 2023-10-20 11:27:14 +00:00
Edd 7c105d2c6b feat(explorer): add restricted route (#5094) 2023-10-19 18:28:33 +00:00
Maciek 2dfcb02d1e chore(orders): 4970 add no rows and error info (#5076) 2023-10-19 14:45:55 +01:00
Sam Keen a98ab775c3 feat(governance): transfers destination field can fallback to type (#5092) 2023-10-19 11:16:00 +01:00
Maciek 097bf7c08c chore(trading): add button for copying asset id (#5075) 2023-10-18 17:02:34 +02:00
Bartłomiej Głowniaandbwallacee 236e35a92b feat(trading): show successor banner if market is settled or successor market exists (#5056)
Co-authored-by: bwallacee <ben@vega.xyz>
2023-10-18 14:48:52 +02:00
Maciek abf84b9d45 chore(ui-toolkit): 4970 async renderer - improve error handling (#5063) 2023-10-18 11:11:05 +02:00
m.ray 6d2a2ea0a0 chore(trading): update snap version to 0.3.1 (#5059) 2023-10-18 09:56:50 +01:00
Matthew Russell 6ac79e9f6d chore(trading,explorer,governance): update codeowners so dev team only is required for reviews 2023-10-17 20:14:48 -07:00
Matthew Russell 835ee64243 fix(trading): hydration error due to loader (#5043) 2023-10-17 20:05:53 -07:00
Matthew Russell e7c3b5054c fix(trading): market selector dropdown not updating (#5027) 2023-10-17 09:56:48 -07:00
Maciek 9dc9588a14 chore(ledger): 4895 refactor ledger csv extract (#5053) 2023-10-17 17:15:38 +02:00
Ben a0844d41bf chore(trading): move transfer test to jest (#5058) 2023-10-17 10:56:21 +01:00
m.ray 3d1aa74128 fix(trading): liquidation price tooltip link (#5050) 2023-10-17 10:44:49 +01:00
Ben 2376bfe58f chore(trading): deposit test to jest (#5036) 2023-10-16 11:44:37 -07:00
Matthew Russell 72ccf96523 fix(trading): remove additional page for closed markets (#5042) 2023-10-16 11:18:26 -07:00
Matthew Russell f416fa4a25 chore(trading,explorer,governance): adjust codeowners to require reviews from both teams (#5054) 2023-10-16 16:11:19 +01:00
Matthew Russell a24e3646cc fix(trading): glitchy waterfall loading (#5039) 2023-10-16 07:49:23 -07:00
Art 55b092b216 fix(trading): update referral program proposal changes (#5030) 2023-10-16 16:45:00 +02:00
Matthew Russell ef4cf6a87e chore(assets): add testnet deny list (#5044) 2023-10-16 07:41:31 -07:00
Sam Keen c9860a3412 fix(governance): governance initiated transfers fixes (#5025) 2023-10-16 12:02:06 +01:00
m.ray 127bd3faf3 chore(trading): code owners fix for two reviewers (#5046) 2023-10-16 12:00:20 +01:00
Bartłomiej Głownia 1791b3ca6a feat(trading): adjust layout for funding payments (#5034) 2023-10-16 11:19:08 +02:00
Matthew Russell 6044b86ff4 chore(trading): add background to sidebar and minor tweaks (#5037) 2023-10-13 11:30:04 -07:00
Matthew Russell 341d78e761 chore(trading): fix hydration errors and improve loading experience (#5038) 2023-10-13 16:02:33 +02:00
Joe Tsanganddaro-maj 58cc074538 test(governance): governance transfer proposal tests (#5011)
Co-authored-by: daro-maj <119658839+daro-maj@users.noreply.github.com>
2023-10-12 18:48:39 +02:00
Bartłomiej Głownia 852c9bb8f4 feat(trading): display fees discounts (#4800) 2023-10-12 16:58:57 +02:00
Maciek efe9b7a0e7 chore(trading): upgrade pennant version (#5023) 2023-10-12 13:18:17 +00:00
daro-maj 695a059dde test(trading): add funding spec (#5028) 2023-10-12 11:58:32 +01:00
Maciek 5d7e4d273a chore(trading): add timezone note to the ledger entries form (#5018) 2023-10-12 09:12:54 +00:00
Sam Keen b137e024c6 feat(governance): volume discount proposals (#5022) 2023-10-11 18:51:52 +00:00
Ben 6651ee52b4 chore(trading): oracle banner test (#5020) 2023-10-11 15:54:26 +01:00
m.rayandRado 0d14722c5d feat(trading): add risk factors leverage and update accordion panel (#5000)
Co-authored-by: Rado <szpiechrados@gmail.com>
2023-10-11 10:19:43 +01:00
Sam Keen 4968b537b8 feat(governance): referral proposals (#5013) 2023-10-11 09:11:35 +00:00
Bartłomiej Głownia 5f5c45f3b9 feat(trading): display empty values for funding and countdown if perp market is in auction (#4987) 2023-10-10 16:59:31 +01:00
MaciekandMatthew Russell 6b10df015d feat(trading): 4387 market terminate warning banner (#4912)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-10-10 17:50:49 +02:00
Ben 6d7e24732b chore(trading): transfer form test to jest (#5010) 2023-10-10 14:37:06 +01:00
Matthew Russell b33cf04e16 fix(trading): flickering of market loading (#5006) 2023-10-10 14:20:27 +01:00
Ben 66ebb26b0f chore(trading): deal ticket tif tests (#5009) 2023-10-10 13:05:02 +01:00
Ben 51c88d2fce chore(trading): move trades tests to jest (#5001) 2023-10-10 11:13:23 +01:00
daro-maj 2786a984b0 test(trading): remove portfolio test (#5007) 2023-10-10 10:02:09 +01:00
Maciek 39c53a7326 chore(trading): 4858 redesign market header (#4976) 2023-10-10 10:45:09 +02:00
Radosław Szpiech 3371e8e2cc Merge pull request #5004 from vegaprotocol/chore/fix-multiline-condition
chore(ci): merge main fixes to develop
2023-10-10 10:17:49 +02:00
Rado 5843a36851 Merge remote-tracking branch 'origin/main' into chore/fix-multiline-condition 2023-10-09 17:29:36 +02:00
Radosław Szpiech 68b4909dff chore(ci): fix badly written condition in workflow (#5002) 2023-10-09 17:24:18 +02:00
Bartłomiej Głownia da8e382454 feat(trading): add funding payment history (#4989) 2023-10-09 16:59:00 +02:00
81ebefc329 chore(trading): accounts test to jest (#4984)
Co-authored-by: daro-maj <119658839+daro-maj@users.noreply.github.com>
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: Mikołaj Młodzikowski <mikolaj.mlodzikowski@gmail.com>
2023-10-09 10:09:10 +01:00
Bartłomiej Głownia 1ed7d6fda2 feat(positions): add closed markets filter to positions in portfolio (#4982) 2023-10-09 08:28:21 +02:00
Radosław Szpiech a2e4f34b0d Merge pull request #4996 from vegaprotocol/chore/update-develop
chore(ci): merge main changes to develop
2023-10-06 16:43:05 +02:00
Rado 3a1bb855aa Merge remote-tracking branch 'origin/main' into chore/update-develop 2023-10-06 16:14:09 +02:00
Radosław Szpiech b4ca0a8390 chore(ci): run console-test on PR again (#4995) 2023-10-06 16:12:13 +02:00
Radosław Szpiech 349c790ecc Merge pull request #4993 from vegaprotocol/chore/update-develop-with-main
chore(ci,governance): update develop with latest main changes
2023-10-06 13:52:19 +02:00
Rado 4c8a15cc01 chore(governance): merge main 2023-10-06 12:57:32 +02:00
Radosław Szpiech f433a030ff chore(ci): separate export docker image job in console-test workflow (#4980) 2023-10-06 10:47:38 +00:00
Joe Tsang 63a6e4ecbd chore(governance): fix failing link test (#4990) 2023-10-06 12:00:21 +02:00
Joe Tsang 555f6e2a6d chore(explorer): fix failing market test (#4992) 2023-10-06 11:20:12 +02:00
Matthew Russell c116762865 fix(trading,wallet): set version for vega snap (#4991) 2023-10-05 14:18:40 -04:00
Joe Tsang b47bd49e83 test(governance): skip staking test (#4974) 2023-10-05 14:22:08 +00:00
Ben 4b942cfb7d chore(ci): add release label on release (#4985) 2023-10-05 13:28:24 +01:00
Mikołaj Młodzikowski c27443fc13 feat(ci): add workflow to clean up caches on closed PRs (#4988) 2023-10-05 12:46:22 +02:00
Bartłomiej Głownia 6dbde76c84 chore(wallet): remove dependency on apollo from wallet lib (#4814) 2023-10-05 11:25:54 +02:00
daro-maj f4abd7cef5 test(trading): remove account test (#4983) 2023-10-05 10:05:02 +02:00
Bartłomiej Głownia 3b978118dc feat(deal-ticket): fix margin and fees alignment (#4956) 2023-10-05 07:40:25 +02:00
Ben bb4cc1e51d chore(trading): set default sort order (#4978) 2023-10-05 00:12:47 +01:00
Bartłomiej Głownia 797db6ee93 feat(ui-toolkit): increase checkbox and radio clickable area (#4906) 2023-10-04 12:50:02 +02:00
Radosław Szpiech 6e7b87d9ef Merge pull request #4972 from vegaprotocol/chore/merge-main-pull-requests
chore(trading): merge main to develop
2023-10-04 12:02:11 +02:00
Madalina Raicu 6965bdf0ca chore: remove closed markets 2023-10-03 18:40:36 +01:00
Madalina Raicu f4d46efff5 Merge branch 'main' of github.com:vegaprotocol/frontend-monorepo into chore/merge-main-pull-requests 2023-10-03 18:21:10 +01:00
Art d510330c6b feat(governance): governance transfers (#4411) 2023-10-03 15:35:17 +00:00
Ben 5b5765ef63 chore(trading): add accounts jest test (#4962) 2023-10-03 15:16:24 +00:00
Bartłomiej Głownia 283f654a4c chore(trading): upgrade pennant (#4967) 2023-10-03 13:00:35 +01:00
Radosław Szpiech 6945514b49 chore(ci): quotes and double brackets change (#4965) 2023-10-03 12:57:17 +01:00
Maciek f74687d30c chore(trading): 4947 cant view settled market main (#4964) 2023-10-03 12:31:05 +01:00
m.ray 2a7574bd8e fix(governance): add wallet connect project ID (#4966) 2023-10-03 12:30:24 +01:00
Matthew RussellandMadalina Raicu 872f1d300f fix(wallet): dont show error state if user rejects connection (#4901)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2023-10-03 12:21:19 +01:00
Mikołaj Młodzikowski 600ea0eeab Revert "Update Jenkinsfile"
This reverts commit 7c3f7a7ab1.
2023-10-03 12:30:39 +02:00
Mikołaj Młodzikowski 7c3f7a7ab1 Update Jenkinsfile
feat(ci): check jenkins execution
2023-10-03 12:15:03 +02:00
Maciek 539abce8af chore(trading): cant view settled market (#4958) 2023-10-03 11:08:30 +01:00
Bartłomiej Głownia 60ca6c2eb6 feat(trading): show fills for all markets (#4951) 2023-10-03 11:00:26 +01:00
Art eeff4ffcd4 fix(trading): missing wallet connect button (#4960) 2023-10-03 10:53:32 +01:00
Radosław Szpiech 73ae00f12c chore(ci): add missing quote marks that may have caused some workflow fails (#4963) 2023-10-03 10:44:12 +01:00
Art ee73e4d5e2 fix(trading): missing wallet connect button (#4959) 2023-10-03 10:38:37 +01:00
Joe Tsang 14928d318d chore(governance): add e2e tests for market update proposals (#4945) 2023-10-02 20:50:47 +01:00
m.ray a19ea1c939 fix(trading): update filter for market selector to include suspended via governance (#4957) 2023-10-02 18:00:49 +01:00
m.rayandMatthew Russell c65c296db2 feat(trading): ethereum oracle spec in oracle panels (#4914)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-10-02 17:48:12 +01:00
Maciek 6c5cd85d96 fix(deal-ticket): prevent empty call estimate position (#4950) 2023-10-02 18:26:10 +02:00
m.ray 8efadda98c fix(trading): remove market value proxy and formatting (#4916) 2023-10-02 16:00:55 +01:00
Maciek 0872a14f44 feat(positions): close position with max possible size (#4940) 2023-10-02 16:38:22 +02:00
Radosław Szpiech 8b249e1917 chore(ci): fix some minor issues in workflows (#4953) 2023-10-02 16:12:24 +02:00
Ben 13817e4d57 chore(trading): delete duplicate asset details test (#4954) 2023-10-02 13:57:54 +00:00
Radosław Szpiech abb771e2f9 chore(ci): add conditions to run e2e tests (#4946) 2023-10-02 14:29:59 +02:00
Edd acf1d50d0f chore(explorer): ignore errors on oracle page (#4944) 2023-09-29 17:29:16 +01:00
Matthew Russell 9da704c117 Merge pull request #4943 from vegaprotocol/chore/merge-main
chore(ci): merge main to develop
2023-09-29 12:11:25 -04:00
Rado d262650258 chore: merge main to develop 2023-09-29 17:46:20 +02:00
Radosław Szpiech 30da1663eb chore(ci): add main develop merge condition to runner (#4941) 2023-09-29 17:36:18 +02:00
Radosław Szpiech 79cbe62774 chore(ci): add mainnet compatible runner to release mainnet branch push actions (#4939) 2023-09-29 11:00:20 -04:00
Matthew Russell df11401b69 Merge pull request #4932 from vegaprotocol/main
chore(ci,trading): update develop with mainnet fixes
2023-09-29 10:33:42 -04:00
Maciek 2f0be0bf34 chore(trading): 4774 put connect dialog inside onboarding dialog - main branch (#4920) 2023-09-29 15:07:18 +02:00
Radosław Szpiech 5b5802104e chore(ci): trigger lint-pr on repoened and sync (#4937) 2023-09-29 15:03:58 +02:00
Radosław Szpiech 3a149ddfb2 chore(ci): add mainnet compatible runner to release mainnet branch push actions (#4936) 2023-09-29 14:01:04 +02:00
Sam Keen 0e519f4d0e feat(governance): market state update proposals (#4935) 2023-09-29 10:11:02 +01:00
Maciek ff2e2574f6 chore(trading): add handle of autosizing selected columns - main branch (#4888) 2023-09-28 15:14:32 -04:00
Joe Tsang 9b52cecf21 chore(governance): remove explorer market diff test (#4928) 2023-09-28 17:24:05 +01:00
Radosław Szpiech 478cc9e753 chore(ci): publish main image in dockerhub (#4930) 2023-09-28 17:38:00 +02:00
Ben 18f1fbd56d chore(trading): deal ticket order tests move (#4927) 2023-09-28 17:16:00 +02:00
Matthew Russell ccb22f02c3 chore(ci): fix conflict in vegacapsule genesis file 2023-09-28 10:44:22 -04:00
Radosław Szpiech 015d8f51c0 chore(ci): sync main to develop (#4924) 2023-09-28 14:43:02 +02:00
Radosław Szpiech e2a72cb395 chore(ci): separate lint pr (main) (#4917) 2023-09-28 14:01:36 +02:00
Radosław Szpiech 0669696b6f chore(ci): fix node version (develop) (#4910) 2023-09-28 13:30:50 +02:00
Joe Tsang 56c662e5ba test(governance): perpetual market e2e test (#4892) 2023-09-28 11:35:46 +02:00
Radosław Szpiech 7fe269fad6 chore(ci): fix node version (main) (#4911) 2023-09-28 11:26:15 +02:00
Radosław Szpiech b761023069 chore(ci): add logic for choosing correct runner for cypress tests depending on base branch (#4918) 2023-09-28 11:25:44 +02:00
Radosław Szpiech 7453acd632 chore(trading): separate workflow for lint-pr (#4904) 2023-09-27 14:44:57 -04:00
Sam Keen 1f507facba feat(governance): show perp or future pills (#4863) 2023-09-27 14:55:43 +01:00
Radosław Szpiech 9d6b34963a chore(trading): add possibility to manualy trigger console-test (#4899) (#4908) 2023-09-27 15:46:49 +02:00
Radosław Szpiech 7ac3a68ac9 chore(trading): add possibility to manualy trigger console-test (#4899) 2023-09-27 15:18:32 +02:00
Radosław Szpiech 2640ccb20a chore(trading): change runner for mainnet compatible vega version (#4907) 2023-09-27 14:42:12 +02:00
Matthew Russell 77c0894501 Merge pull request #4905 from vegaprotocol/chore/update-develop-with-mainnet-fixes
chore(trading,deal-ticket,market-depth,ci): update develop with mainnet fixes
2023-09-26 16:30:10 -04:00
Matthew Russell 60d7cf3029 Merge branch 'main' into chore/update-develop-with-mainnet-fixes 2023-09-26 15:06:07 -04:00
Matthew Russell 2b8abde938 Merge pull request #4880 from vegaprotocol/chore/update-develop-with-mainnet-fixes
chore(trading,governance): update develop with mainnet fixes
2023-09-26 14:06:20 -04:00
Matthew Russell 2005f19d50 fix(trading): prevent deposit sidebar showing on corresponding standalone page (#4840) 2023-09-26 13:47:47 -04:00
Maciek d78de10855 chore(trading): remove underlines on tooltips (#4884) 2023-09-26 13:45:33 -04:00
Matthew Russell bb402c02f6 chore(trading): make volume bars color match candle color (#4881) 2023-09-26 12:08:12 -04:00
Bartłomiej Głownia 8a9b1c7874 feat(deal-ticket): amend fee estimates for postOnly orders and when market is in auction (#4843) 2023-09-26 11:57:47 -04:00
Matthew Russell a7e8b0eb01 fix(market-depth): ensure scroll bar doesn't show on orderbook (#4882) 2023-09-26 11:54:42 -04:00
Bartłomiej Głownia 89b3c06107 feat(deal-ticket): show worst case instead of ranges in estimated values (#4870) 2023-09-26 11:53:45 -04:00
Radosław Szpiech cd5c73d3fd chore(trading): temporary disable console-test to unblock CI queue (#4885) 2023-09-26 10:45:27 +01:00
Joe Tsang b3a5ab022d chore(ci): update genesis file (#4887) 2023-09-26 10:44:55 +01:00
Matthew Russell ba438549bc Merge branch 'main' into chore/update-develop-with-mainnet-fixes 2023-09-25 21:30:40 -04:00
Mikołaj Młodzikowski 496d1f5c68 feat(ci): parse if there are any changes 2023-09-25 18:10:01 -04:00
Mikołaj Młodzikowski e914e7bb70 fix(ci): provide proper ipfs process 2023-09-25 18:09:31 -04:00
Matthew Russell 71a36c2382 fix(trading): remove upgrade banner [main] (#4877) 2023-09-25 17:27:01 -04:00
Matthew Russell 1c6a307bcd fix(wallet): reduce time to check for wallet running [main] (#4876) 2023-09-25 17:26:40 -04:00
Matthew Russell ef4a740b91 fix(trading): allotment breaking [main] (#4875) 2023-09-25 17:26:06 -04:00
a6303456c5 feat(trading): liquidity SLA in market info (#4649)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: Joe Tsang <30622993+jtsang586@users.noreply.github.com>
Co-authored-by: Joe <joe@vega.xyz>
2023-09-25 14:44:55 +01:00
Sam Keen 2d4be5fcb3 fix(governance): mainnet rewards query looping (#4865) 2023-09-25 09:59:38 +00:00
Sam Keen 9cbd6baccd fix(governance): removed loop caused by rewards epoch refresh (#4864) 2023-09-25 09:34:47 +00:00
Maciek 525773773a chore(trading): 4811 make market table columns resizable (#4856) 2023-09-25 10:14:18 +02:00
Ben 45d6f43e74 chore(trading): deal ticket test to jest (#4842) 2023-09-22 15:25:50 +01:00
Art 41760f2956 chore(trading): disable apply code when applied (#4855) 2023-09-22 12:06:02 +01:00
m.rayandMatthew Russell 835f2b793f chore(trading): remove static assets console coming from vega.xyz (#4825)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-09-19 23:54:07 -07:00
Matthew Russell d50b988b4a Merge branch 'release/mainnet-trading' of github.com:vegaprotocol/frontend-monorepo into release/mainnet-trading 2023-09-18 09:03:44 -07:00
Madalina Raicu a6aec899c8 chore: update tag metamask snaps to true 2023-09-18 15:08:46 +01:00
610 changed files with 19132 additions and 9660 deletions
@@ -82,3 +82,38 @@ jobs:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/ https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/ https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
ipfs://${{ env.IPFS_V0 }}/ ipfs://${{ env.IPFS_V0 }}/
- name: Ensure 'Released' label exists
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPO="${{ github.repository }}"
LABEL_EXIST=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$REPO/labels/Released")
if [[ "$LABEL_EXIST" == *"Not Found"* ]]; then
curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-X POST "https://api.github.com/repos/$REPO/labels" \
-d '{"name": "Released", "color": "FFFFFF"}'
fi
- name: Extract issues from release notes
id: extract-issues
run: |
ISSUES=$(echo "${{ github.event.release.body }}" | grep -o -E '#[0-9]+' | tr -d '#' | jq -R . | jq -cs .)
echo "Issues to label: $ISSUES"
echo "::set-output name=issue_numbers::$ISSUES"
- name: Add 'Released' label to issues
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ISSUE_NUMBERS="${{ steps.extract-issues.outputs.issue_numbers }}"
REPO="${{ github.repository }}"
for ISSUE in $(echo "$ISSUE_NUMBERS" | jq -r '.[]'); do
curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-X POST "https://api.github.com/repos/$REPO/issues/$ISSUE/labels" \
-d '{"labels": ["Released"]}'
done
+34 -18
View File
@@ -5,12 +5,12 @@ on:
branches: branches:
- release/* - release/*
- develop - develop
- main
pull_request: pull_request:
types: types:
- opened - opened
- ready_for_review - ready_for_review
- reopened - reopened
- edited
- synchronize - synchronize
jobs: jobs:
node-modules: node-modules:
@@ -44,13 +44,6 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true' if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile run: yarn install --pure-lockfile
lint-pr-title:
needs: node-modules
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
lint-format: lint-format:
timeout-minutes: 20 timeout-minutes: 20
needs: node-modules needs: node-modules
@@ -177,25 +170,48 @@ jobs:
preview_explorer: ${{ env.PREVIEW_EXPLORER }} preview_explorer: ${{ env.PREVIEW_EXPLORER }}
preview_tools: ${{ env.PREVIEW_TOOLS }} preview_tools: ${{ env.PREVIEW_TOOLS }}
# console-e2e: check-e2e-needed:
# needs: build-sources runs-on: ubuntu-latest
# name: '(CI) console python' needs: build-sources
# uses: ./.github/workflows/console-test-run.yml name: '(CI) check if e2e needed'
# secrets: inherit outputs:
# if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }} run-tests: ${{ steps.check-test.outputs.e2e-needed }}
# with: steps:
# github-sha: ${{ github.event.pull_request.head.sha || github.sha }} - name: Check branch
id: check-test
run: |
if [[ "${{ github.base_ref }}" == "develop" ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "main" ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/"* ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
else
echo "e2e-needed=false" >> $GITHUB_OUTPUT
fi
- name: Print result
run: |
echo "e2e-needed: ${{ steps.check-test.outputs.e2e-needed }}"
cypress: cypress:
needs: build-sources needs: [build-sources, check-e2e-needed]
name: '(CI) cypress' name: '(CI) cypress'
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }} if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
uses: ./.github/workflows/cypress-run.yml uses: ./.github/workflows/cypress-run.yml
secrets: inherit secrets: inherit
with: with:
projects: ${{ needs.build-sources.outputs.projects-e2e }} projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke' tags: '@smoke'
console-e2e:
needs: [build-sources, check-e2e-needed]
name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml
secrets: inherit
if: needs.check-e2e-needed.outputs.run-tests == 'true' && contains(needs.build-sources.outputs.projects, 'trading')
with:
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
publish-dist: publish-dist:
needs: build-sources needs: build-sources
name: '(CD) publish dist' name: '(CD) publish dist'
+29
View File
@@ -0,0 +1,29 @@
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#managing-caches
name: cleanup caches by a branch
on:
pull_request:
types:
- closed
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
+156 -50
View File
@@ -1,31 +1,42 @@
name: (CI) Console tests name: (CI) Console tests
env:
VEGA_VERSION: v0.72.14
on: on:
workflow_call: workflow_call:
inputs: inputs:
github-sha: github-sha:
required: true required: true
type: string type: string
workflow_dispatch:
inputs:
console-test-branch:
type: choice
description: 'main: v0.72.14, develop: v0.73.0-preview7'
options:
- main
- develop
jobs: jobs:
run-tests: create-docker-image:
name: run-tests name: Create docker image for console-test
runs-on: console-test runs-on: ubuntu-22.04
timeout-minutes: 40 timeout-minutes: 20
steps: steps:
#---------------------------------------------- #----------------------------------------------
# check-out frontend-monorepo # check-out frontend-monorepo
#---------------------------------------------- #----------------------------------------------
- name: Checkout console test repo - name: Checkout frontend-monorepo
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
ref: ${{ inputs.github-sha }} ref: ${{ inputs.github-sha || github.sha }}
#---------------------------------------------- #----------------------------------------------
# cache node modules # cache node modules
#---------------------------------------------- #----------------------------------------------
- name: setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: yarn
- name: Cache node modules - name: Cache node modules
id: cache id: cache
uses: actions/cache@v3 uses: actions/cache@v3
@@ -44,23 +55,105 @@ jobs:
#---------------------------------------------- #----------------------------------------------
# build trading # build trading
#---------------------------------------------- #----------------------------------------------
- name: Build affected spec - name: Build trading app
run: | run: |
yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading
DIST_LOCATION=dist/apps/trading/exported
mv $DIST_LOCATION dist-result
tree dist-result
#---------------------------------------------- #----------------------------------------------
# run trading server # export trading app docker image
#---------------------------------------------- #----------------------------------------------
- name: Run trading server
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and export to local Docker
id: docker_build
uses: docker/build-push-action@v5
with:
context: .
file: docker/node-outside-docker.Dockerfile
load: true
build-args: |
APP=trading
ENV_NAME=stagnet1
tags: ci/trading:local
outputs: type=docker,dest=/tmp/console-image.tar
- name: Verify docker image created
run: | run: |
docker run -d -p 80:4200 -v $PWD/docker/nginx.conf:/etc/nginx/conf.d/default.conf -v $PWD/dist/apps/trading/exported:/usr/share/nginx/html nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629 echo ${{ steps.docker_build.outputs.digest }}
sleep 5 echo ${{ steps.docker_build.outputs.imageid }}
docker ps
#---------------------------------------------- - name: Upload docker image for console-test usage
# check if container persists between runs uses: actions/upload-artifact@v3
#---------------------------------------------- with:
- name: Check server name: console-image
path: /tmp/console-image.tar
console-test-branch:
name: Choose console-test branch to run on
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
console-branch: ${{ steps.output-step.outputs.branch }}
steps:
- name: Workflow dispatch input
id: dispatch-step
if: github.event_name == 'workflow_dispatch'
run: echo "branch=${{ inputs.console-test-branch }}" >> $GITHUB_OUTPUT
- name: Print Workflow dispatch input
if: github.event_name == 'workflow_dispatch'
run: echo ${{ steps.dispatch-step.outputs.branch }}
- name: Workflow_call input
id: workflow_call-step
if: github.event_name != 'workflow_dispatch'
run: | run: |
docker ps if [[ "${{ github.base_ref }}" == "main" ]]; then
echo "branch=main" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "develop" && "${{ github.ref_name }}" == "main" ]]; then
echo "branch=main" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/mainnet"* ]]; then
echo "branch=main" >> $GITHUB_OUTPUT
else
echo "branch=develop" >> $GITHUB_OUTPUT
fi
- name: Print Workflow_call input
if: github.event_name != 'workflow_dispatch'
run: echo ${{ steps.workflow_call-step.outputs.branch }}
- name: Set output
id: output-step
run: echo "branch=${{ steps.dispatch-step.outputs.branch || steps.workflow_call-step.outputs.branch }}" >> $GITHUB_OUTPUT
- name: Print final output
run: echo ${{ steps.output-step.outputs.branch }}
run-tests:
name: run-tests
runs-on: 8-cores
needs: [create-docker-image, console-test-branch]
timeout-minutes: 20
steps:
#----------------------------------------------
# load docker image
#----------------------------------------------
- name: Download docker image from previous job
uses: actions/download-artifact@v3
with:
name: console-image
path: /tmp
- name: Load Docker image
run: |
docker load --input /tmp/console-image.tar
docker image ls -a
#---------------------------------------------- #----------------------------------------------
# check-out tests repo # check-out tests repo
#---------------------------------------------- #----------------------------------------------
@@ -68,52 +161,55 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: vegaprotocol/console-test repository: vegaprotocol/console-test
path: './console-test' ref: ${{ needs.console-test-branch.outputs.console-branch }}
- name: Load console test envs
id: console-test-env
uses: falti/dotenv-action@v1.0.4
with:
path: '.env.${{ needs.console-test-branch.outputs.console-branch }}'
export-variables: true
keys-case: upper
log-variables: true
#---------------------------------------------- #----------------------------------------------
# install dependencies # ----- Setup python -----
#----------------------------------------------
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
#----------------------------------------------
# ----- install & configure poetry -----
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
#----------------------------------------------
# install python dependencies
#---------------------------------------------- #----------------------------------------------
- name: Install dependencies - name: Install dependencies
working-directory: ./console-test
run: poetry install --no-interaction --no-root run: poetry install --no-interaction --no-root
#---------------------------------------------- #----------------------------------------------
# find vega binaries path # install vega binaries
#----------------------------------------------
- name: Find vega binaries path
id: vega_bin_path
working-directory: ./console-test
run: echo path=$(poetry run python -c "import vega_sim; print(vega_sim.vega_bin_path)") >> $GITHUB_OUTPUT
#----------------------------------------------
# vega binaries cache
#----------------------------------------------
- name: Vega binaries cache
uses: actions/cache@v3
id: vega_binaries_cache
with:
path: ${{ steps.vega_bin_path.outputs.path }}
key: ${{ runner.os }}-vega-binaries-${{ env.VEGA_VERSION }}
#----------------------------------------------
# install vega binaries
#---------------------------------------------- #----------------------------------------------
- name: Install vega binaries - name: Install vega binaries
working-directory: ./console-test run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }}
if: steps.vega_binaries_cache.outputs.cache-hit != 'true'
run: poetry run python -m vega_sim.tools.load_binaries --force --version $VEGA_VERSION
#---------------------------------------------- #----------------------------------------------
# install playwright # install playwright
#---------------------------------------------- #----------------------------------------------
- name: install playwright - name: install playwright
run: poetry run playwright install --with-deps chromium run: poetry run playwright install --with-deps chromium
working-directory: ./console-test
#---------------------------------------------- #----------------------------------------------
# run tests # run tests
#---------------------------------------------- #----------------------------------------------
- name: Run tests - name: Run tests
working-directory: ./console-test run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
ls -al console-test
#---------------------------------------------- #----------------------------------------------
# upload traces # upload traces
#---------------------------------------------- #----------------------------------------------
@@ -124,3 +220,13 @@ jobs:
name: playwright-trace name: playwright-trace
path: ./traces/ path: ./traces/
retention-days: 15 retention-days: 15
#----------------------------------------------
# ----- upload logs -----
#----------------------------------------------
- name: Upload worker logs
uses: actions/upload-artifact@v3
if: always()
with:
name: worker-logs
path: ./logs/
retention-days: 15
+24 -1
View File
@@ -13,13 +13,35 @@ on:
type: string type: string
jobs: jobs:
runner-choice:
runs-on: ubuntu-latest
outputs:
runner: ${{ steps.step.outputs.runner }}
steps:
- name: Check branch
id: step
run: |
if [[ "${{ github.base_ref }}" == "main" ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "develop" && "${{ github.ref_name }}" == "main" ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/mainnet"* ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
else
echo "runner=self-hosted-runner" >> $GITHUB_OUTPUT
fi
- name: Print runner
run: echo ${{ steps.step.outputs.runner }}
e2e: e2e:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
project: ${{ fromJSON(inputs.projects) }} project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }} name: ${{ matrix.project }}
runs-on: self-hosted-runner needs: runner-choice
runs-on: ${{ needs.runner-choice.outputs.runner }}
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
# Checks if skip cache was requested # Checks if skip cache was requested
@@ -63,6 +85,7 @@ jobs:
- name: Run Vegacapsule network and Vega wallet - name: Run Vegacapsule network and Vega wallet
id: setup-vega id: setup-vega
uses: ./frontend-monorepo/.github/actions/run-vegacapsule uses: ./frontend-monorepo/.github/actions/run-vegacapsule
timeout-minutes: 10
###### ######
## Run some tests ## Run some tests
+11 -11
View File
@@ -2,7 +2,12 @@
name: Verify PR title name: Verify PR title
on: on:
workflow_call: pull_request:
types:
- opened
- edited
- reopened
- synchronize
jobs: jobs:
lint_pr: lint_pr:
@@ -11,21 +16,16 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node - name: Setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version-file: '.nvmrc' node-version: 16
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: Cache node modules - name: Install dependencies
uses: actions/cache@v3 run: |
with: rm package.json
path: node_modules npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Check PR title - name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+12 -6
View File
@@ -30,12 +30,18 @@ jobs:
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELEASE=false >> $GITHUB_ENV echo IS_S3_RELEASE=false >> $GITHUB_ENV
echo IS_DEV_IMAGE=false >> $GITHUB_ENV echo IS_DEV_IMAGE=false >> $GITHUB_ENV
echo IS_MAIN_IMAGE=false >> $GITHUB_ENV
- name: Is dev image - name: Is dev image
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }} if: ${{ github.ref_name == 'develop' && github.event_name == 'push' && matrix.app == 'trading' }}
run: | run: |
echo IS_DEV_IMAGE=true >> $GITHUB_ENV echo IS_DEV_IMAGE=true >> $GITHUB_ENV
- name: Is main image
if: ${{ github.ref_name == 'main' && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
echo IS_MAIN_IMAGE=true >> $GITHUB_ENV
- name: Is PR - name: Is PR
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
run: | run: |
@@ -57,7 +63,7 @@ jobs:
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release - name: Is S3 Release
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' }} if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' && github.ref_name != 'main'}}
run: | run: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV echo IS_S3_RELEASE=true >> $GITHUB_ENV
@@ -81,7 +87,7 @@ jobs:
- name: Log in to the Container registry (docker hub) - name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2 uses: docker/login-action@v2
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }} if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -179,7 +185,7 @@ jobs:
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
continue-on-error: true continue-on-error: true
id: dockerhub-push id: dockerhub-push
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }} if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
with: with:
context: . context: .
file: docker/node-outside-docker.Dockerfile file: docker/node-outside-docker.Dockerfile
@@ -189,7 +195,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }} vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }} vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
- name: Publish dist as docker image (ghcr - retry) - name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
@@ -216,7 +222,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }} vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }} vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
# bucket creation in github.com/vegaprotocol/terraform//frontend # bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3 - name: Publish dist to s3
-1
View File
@@ -1,3 +1,2 @@
* @vegaprotocol/frontend * @vegaprotocol/frontend
* @vegaprotocol/frontend-qa
*.graphql @vegaprotocol/core *.graphql @vegaprotocol/core
@@ -36,10 +36,9 @@ context('Market page', { tags: '@regression' }, function () {
it('Able to go to market details page', function () { it('Able to go to market details page', function () {
cy.navigate_to('markets'); cy.navigate_to('markets');
cy.get_element_by_col_id('actions').eq(1).click(); cy.contains('Test market 1').click();
cy.getByTestId(marketHeaders).should('have.text', 'Test market 1'); cy.getByTestId(marketHeaders).should('have.text', 'Test market 1');
cy.validate_element_from_table('Name', 'Test market 1'); cy.validate_element_from_table('Name', 'Test market 1');
cy.validate_element_from_table('Market ID', this.createdMarketId);
cy.validate_element_from_table('Trading Mode', 'Opening auction'); cy.validate_element_from_table('Trading Mode', 'Opening auction');
cy.validate_element_from_table('Market Decimal Places', '5'); cy.validate_element_from_table('Market Decimal Places', '5');
cy.validate_element_from_table('Position Decimal Places', '5'); cy.validate_element_from_table('Position Decimal Places', '5');
@@ -87,15 +86,13 @@ context('Market page', { tags: '@regression' }, function () {
// Liquidity // Liquidity
cy.validate_element_from_table('Target Stake', '0.00 fUSDC'); cy.validate_element_from_table('Target Stake', '0.00 fUSDC');
cy.validate_element_from_table('Supplied Stake', '0.00 fUSDC'); cy.validate_element_from_table('Supplied Stake', '0.00 fUSDC');
cy.validate_element_from_table('Market Value Proxy', '0.00 fUSDC');
// Liquidity price range // Liquidity price range
cy.validate_element_from_table( cy.validate_element_from_table(
'Liquidity Price Range', 'Liquidity Price Range',
'1,000.00% of mid price' '95.00% of mid price'
); );
cy.validate_element_from_table('Lowest Price', '0.00 fUSDC'); cy.validate_element_from_table('Lowest Price', '0.00 fUSDC');
cy.validate_element_from_table('Highest Price', '0.00 fUSDC'); cy.validate_element_from_table('Highest Price', '0.00 fUSDC');
cy.getByTestId('oracle-spec-links') cy.getByTestId('oracle-spec-links')
.should('have.attr', 'href') .should('have.attr', 'href')
.and( .and(
@@ -144,11 +141,8 @@ context('Market page', { tags: '@regression' }, function () {
.as('successorMarketId'); .as('successorMarketId');
cy.contains('Token test market').click(); cy.contains('Token test market').click();
cy.getByTestId(marketHeaders).should('have.text', 'Token test market'); cy.getByTestId(marketHeaders).should('have.text', 'Token test market');
cy.validate_proposal_change_type('Triggering Ratio', 'Added');
cy.validate_element_from_table('Triggering Ratio', '0.7'); cy.validate_element_from_table('Triggering Ratio', '0.7');
cy.validate_proposal_change_type('Time Window', 'Added');
cy.validate_element_from_table('Time Window', '3,600'); cy.validate_element_from_table('Time Window', '3,600');
cy.validate_proposal_change_type('Scaling Factor', 'Added');
cy.validate_element_from_table('Scaling Factor', '10'); cy.validate_element_from_table('Scaling Factor', '10');
cy.getByTestId(successionLineItem) cy.getByTestId(successionLineItem)
@@ -40,7 +40,7 @@ context.skip('Node switcher', { tags: '@regression' }, function () {
const errorTypeTxt = 'Error: invalid url'; const errorTypeTxt = 'Error: invalid url';
const nodeErrorTxt = 'fakeUrl is not a valid url.'; const nodeErrorTxt = 'fakeUrl is not a valid url.';
cy.getByTestId('node-url-custom').click(); cy.getByTestId('node-url-custom').click({ force: true });
cy.getByTestId(customNodeBtn).within(() => { cy.getByTestId(customNodeBtn).within(() => {
cy.get('input').clear().type('fakeUrl'); cy.get('input').clear().type('fakeUrl');
@@ -1,68 +0,0 @@
context('Oracle page', { tags: '@smoke' }, () => {
describe('Verify elements on page', () => {
before('create market and navigate to oracle page', () => {
cy.createMarket();
cy.visit('/oracles');
});
it('should see oracle data', () => {
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
cy.getByTestId('oracle-details')
.should('exist')
.eq(0)
.within(() => {
cy.get('tr')
.eq(0)
.within(() => {
cy.get('th').should('have.text', 'ID');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/oracles/');
});
cy.get('tr')
.eq(1)
.within(() => {
cy.get('th').should('have.text', 'Type');
cy.get('td').should('have.text', 'External data');
});
cy.get('tr')
.eq(2)
.within(() => {
cy.get('th').should('have.text', 'Signer');
cy.getByTestId('keytype').should('have.text', 'Vega');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/parties/');
});
cy.get('tr')
.eq(3)
.within(() => {
cy.get('th').should('have.text', 'Settlement for');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/markets/');
});
cy.get('tr')
.eq(4)
.within(() => {
cy.get('th').should('have.text', 'Matched data');
cy.get('td').should('have.text', '❌');
});
cy.get('details')
.eq(0)
.within(() => {
cy.contains('Filter').click();
cy.get('.language-json').should('exist');
});
cy.get('details')
.eq(1)
.within(() => {
cy.contains('JSON').click();
cy.get('.language-json').should('exist');
});
});
});
});
});
@@ -129,6 +129,12 @@ function getSuccessorTxBody(parentMarketId) {
parentMarketId: parentMarketId, parentMarketId: parentMarketId,
insurancePoolFraction: '0.75', insurancePoolFraction: '0.75',
}, },
liquiditySlaParameters: {
priceRange: '0.95',
commitmentMinTimeFraction: '0.5',
performanceHysteresisEpochs: 2,
slaCompetitionFactor: '0.75',
},
}, },
}, },
closingTimestamp, closingTimestamp,
+1 -1
View File
@@ -77,7 +77,7 @@
"executor": "nx:run-commands", "executor": "nx:run-commands",
"options": { "options": {
"commands": [ "commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.72.3/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.73.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
] ]
} }
}, },
+2 -2
View File
@@ -1,6 +1,6 @@
import { import {
AppFailure,
NetworkLoader, NetworkLoader,
NodeFailure,
NodeGuard, NodeGuard,
NodeSwitcherDialog, NodeSwitcherDialog,
useEnvironment, useEnvironment,
@@ -31,7 +31,7 @@ function App() {
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}> <NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<NodeGuard <NodeGuard
skeleton={<div>{t('Loading')}</div>} skeleton={<div>{t('Loading')}</div>}
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />} failure={<NodeFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
> >
<Suspense fallback={splashLoading}> <Suspense fallback={splashLoading}>
<RouterProvider router={router} fallbackElement={splashLoading} /> <RouterProvider router={router} fallbackElement={splashLoading} />
@@ -4,7 +4,7 @@ import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid'; import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react'; import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints'; import { BREAKPOINT_MD } from '../../config/breakpoints';
@@ -1,5 +1,6 @@
export type HashProps = { export type HashProps = {
text: string; text: string;
truncate?: boolean;
}; };
/** /**
@@ -7,10 +8,16 @@ export type HashProps = {
* are broken when they need to wrap. This will remove the need * are broken when they need to wrap. This will remove the need
* for a lot of the overflow scrolling that currently exists. * for a lot of the overflow scrolling that currently exists.
*/ */
const Hash = ({ text }: HashProps) => { const Hash = ({ text, truncate = false }: HashProps) => {
const h = truncate ? text.slice(0, 6) : text;
return ( return (
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}> <code
{text} title={text}
className="break-all font-mono"
style={{ wordWrap: 'break-word' }}
>
{h}
</code> </code>
); );
}; };
@@ -3,6 +3,7 @@ query ExplorerMarket($id: ID!) {
id id
decimalPlaces decimalPlaces
positionDecimalPlaces positionDecimalPlaces
state
tradableInstrument { tradableInstrument {
instrument { instrument {
name name
@@ -22,6 +23,5 @@ query ExplorerMarket($id: ID!) {
} }
} }
} }
state
} }
} }
@@ -17,6 +17,7 @@ export const ExplorerMarketDocument = gql`
id id
decimalPlaces decimalPlaces
positionDecimalPlaces positionDecimalPlaces
state
tradableInstrument { tradableInstrument {
instrument { instrument {
name name
@@ -36,7 +37,6 @@ export const ExplorerMarketDocument = gql`
} }
} }
} }
state
} }
} }
`; `;
@@ -63,6 +63,9 @@ describe('Market link component', () => {
product: { product: {
__typename: 'Future', __typename: 'Future',
quoteName: 'dai', quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -23,6 +23,7 @@ const MarketLink = ({
}: MarketLinkProps) => { }: MarketLinkProps) => {
const { data, error, loading } = useExplorerMarketQuery({ const { data, error, loading } = useExplorerMarketQuery({
variables: { id }, variables: { id },
fetchPolicy: 'cache-first',
}); });
let label = <span>{id}</span>; let label = <span>{id}</span>;
@@ -0,0 +1,57 @@
import OracleLink, { getStatusString } from './oracle-link';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getStatusString', () => {
it("returns 'Unknown' for undefined status", () => {
expect(getStatusString(undefined)).toBe('Unknown');
});
it('returns the correct string for a known status', () => {
expect(getStatusString('STATUS_ACTIVE')).toBe('Active');
expect(getStatusString('STATUS_DEACTIVATED')).toBe('Deactivated');
});
});
describe('OracleLink', () => {
it('renders the truncated Oracle ID', () => {
const id = '123456789';
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} />
</MemoryRouter>
</MockedProvider>
);
const idElement = screen.getByText(id.slice(0, 6));
expect(idElement).toBeInTheDocument();
expect(idElement).toHaveAttribute('title', id);
});
it('renders the Oracle status', () => {
const id = '123';
const status = 'STATUS_ACTIVE';
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} status={status} data-testid="link" />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('link')).toHaveAttribute('data-status', 'Active');
});
it('renders the Oracle data indicator', () => {
const id = '123';
const hasSeenOracleReports = true;
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} hasSeenOracleReports={hasSeenOracleReports} />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('oracle-data-indicator')).toBeInTheDocument();
});
});
@@ -3,20 +3,93 @@ import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react'; import type { ComponentProps } from 'react';
import Hash from '../hash'; import Hash from '../hash';
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import {
DataSourceSpecStatus,
DataSourceSpecStatusMapping,
} from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
/**
* Returns a human-readable string for the given status, or a meaningful
* default if the status is unrecognised
* @param status string status
*/
export function getStatusString(status: string | undefined): string {
if (status && status in DataSourceSpecStatus) {
return DataSourceSpecStatusMapping[status as DataSourceSpecStatus];
}
return t('Unknown');
}
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & { export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
// The Oracle ID
id: string; id: string;
// If available, the oracle status
status?: string;
// If the oracle has corresponding data in the OracleDataConnection
hasSeenOracleReports?: boolean;
}; };
const OracleLink = ({ id, ...props }: OracleLinkProps) => { /**
* Given an Oracle ID, renders a data-dense link to the Oracle page. Data density is achieved by:
* - Colour coding the link based on the Oracle's status
* - Showing a small indicator if the Oracle has matched data
* - Showing a tooltip with the Oracle's status and whether it has matched data
*
* @returns
*/
export const OracleLink = ({
id,
status,
hasSeenOracleReports = false,
...props
}: OracleLinkProps) => {
const bgColour =
status === 'STATUS_ACTIVE'
? 'bg-yellow-100 hover:bg-yellow-200 border-yellow-200 dark:bg-yellow-200 dark:border-yellow-200 dark:text-gray-900 dark:border-yellow-300'
: 'bg-gray-100 hover:bg-gray-200 border-gray-200';
const indicatorColour =
status === 'STATUS_ACTIVE'
? 'bg-yellow-300 hover:bg-yellow-500 dark:bg-yellow-500'
: 'bg-gray-300 hover:bg-gray-500';
const description = (
<div>
<p>
<strong>{`Status: `}</strong>
{getStatusString(status)}
</p>
<p>
<strong>{`Matched data: `}</strong>
{hasSeenOracleReports ? (
<VegaIcon name={VegaIconNames.TICK} />
) : (
<VegaIcon name={VegaIconNames.CROSS} />
)}
</p>
</div>
);
return ( return (
<Link <Tooltip description={description}>
className="underline font-mono" <Link
{...props} className={`pl-2 pr-2 font-mono dark:text-black ${bgColour} rounded-sm border-solid border-2 relative`}
to={`/${Routes.ORACLES}/${id}`} {...props}
> to={`/${Routes.ORACLES}/${id}`}
<Hash text={id} /> data-status={getStatusString(status)}
</Link> >
<Hash text={id} truncate={true} />
{hasSeenOracleReports ? (
<strong
data-testid="oracle-data-indicator"
className={`absolute top-0 right-0 w-1 h-full font-thin ${indicatorColour}`}
title="Oracle has matched data"
></strong>
) : null}
</Link>
</Tooltip>
); );
}; };
@@ -1,6 +1,9 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets'; import type { MarketInfoWithData } from '@vegaprotocol/markets';
import { import {
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
PriceMonitoringBoundsInfoPanel, PriceMonitoringBoundsInfoPanel,
SuccessionLineInfoPanel, SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData, getDataSourceSpecForSettlementData,
@@ -15,7 +18,6 @@ import {
OracleInfoPanel, OracleInfoPanel,
RiskFactorsInfoPanel, RiskFactorsInfoPanel,
RiskModelInfoPanel, RiskModelInfoPanel,
RiskParametersInfoPanel,
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
} from '@vegaprotocol/markets'; } from '@vegaprotocol/markets';
import { MarketInfoTable } from '@vegaprotocol/markets'; import { MarketInfoTable } from '@vegaprotocol/markets';
@@ -67,8 +69,8 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<MetadataInfoPanel market={market} /> <MetadataInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk model')}</h2> <h2 className={headerClassName}>{t('Risk model')}</h2>
<RiskModelInfoPanel market={market} /> <RiskModelInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk parameters')}</h2> <h2 className={headerClassName}>{t('Margin scaling factors')}</h2>
<RiskParametersInfoPanel market={market} /> <MarginScalingFactorsPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2> <h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} /> <RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => ( {(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
@@ -94,6 +96,10 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
)} )}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2> <h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} /> <LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity SLA protocol')}</h2>
<LiquiditySLAParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity')}</h2> <h2 className={headerClassName}>{t('Liquidity')}</h2>
<LiquidityInfoPanel market={market} /> <LiquidityInfoPanel market={market} />
{showTwoOracles ? ( {showTwoOracles ? (
@@ -4,7 +4,7 @@ import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import type { ColDef } from 'ag-grid-community'; import type { ColDef } from 'ag-grid-community';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
VegaValueGetterParams, VegaValueGetterParams,
@@ -74,6 +74,9 @@ function renderExistingAmend(
product: { product: {
__typename: 'Future', __typename: 'Future',
quoteName: '123', quoteName: '123',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -124,6 +127,9 @@ function renderExistingAmend(
product: { product: {
__typename: 'Future', __typename: 'Future',
quoteName: '123', quoteName: '123',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -152,6 +158,9 @@ function renderExistingAmend(
product: { product: {
__typename: 'Future', __typename: 'Future',
quoteName: 'dai', quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -2,7 +2,7 @@ import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals'; import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
VegaValueFormatterParams, VegaValueFormatterParams,
@@ -105,7 +105,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
? new BigNumber(0) ? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted); : yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return ( return (
<div className="uppercase flex h-full items-center justify-center pt-2"> <div className="flex items-center justify-center h-full pt-2 uppercase">
<VoteProgress <VoteProgress
threshold={requiredMajorityPercentage} threshold={requiredMajorityPercentage}
progress={yesPercentage} progress={yesPercentage}
@@ -22,5 +22,9 @@ export const TimeAgo = ({ date, ...props }: TimeAgoProps) => {
return <>{t('Date unknown')}</>; return <>{t('Date unknown')}</>;
} }
return <span {...props}>{t(`${distanceToNow} ago`)}</span>; return (
<span {...props} title={date} className="underline decoration-dotted">
{t(`${distanceToNow} ago`)}
</span>
);
}; };
@@ -1,6 +0,0 @@
query ExplorerSettlementAssetForMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
}
}
@@ -1,49 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerSettlementAssetForMarketQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerSettlementAssetForMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number } | null };
export const ExplorerSettlementAssetForMarketDocument = gql`
query ExplorerSettlementAssetForMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
}
}
`;
/**
* __useExplorerSettlementAssetForMarketQuery__
*
* To run a query within a React component, call `useExplorerSettlementAssetForMarketQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerSettlementAssetForMarketQuery` 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 } = useExplorerSettlementAssetForMarketQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerSettlementAssetForMarketQuery(baseOptions: Apollo.QueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
}
export function useExplorerSettlementAssetForMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
}
export type ExplorerSettlementAssetForMarketQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketQuery>;
export type ExplorerSettlementAssetForMarketLazyQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketLazyQuery>;
export type ExplorerSettlementAssetForMarketQueryResult = Apollo.QueryResult<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>;
@@ -1,131 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { Side } from '@vegaprotocol/types';
import type { LiquidityOrder } from '@vegaprotocol/types';
import { PeggedReference } from '@vegaprotocol/types';
import { LiquidityProvisionDetailsRow } from './liquidity-provision-details-row';
import type { VegaSide } from './liquidity-provision-details-row';
describe('LiquidityProvisionDetails component', () => {
function renderComponent(
order: LiquidityOrder,
side: VegaSide,
normaliseProportionsTo: number,
marketId: string
) {
return render(
<MockedProvider>
<table>
<tbody data-testid="container">
<LiquidityProvisionDetailsRow
order={order}
marketId={marketId}
normaliseProportionsTo={normaliseProportionsTo}
side={side}
/>
</tbody>
</table>
</MockedProvider>
);
}
it('renders null for an order with no proportion', () => {
const mockOrder = {
offset: '1',
reference: PeggedReference.PEGGED_REFERENCE_MID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
expect(res.getByTestId('container')).toBeEmptyDOMElement();
});
it('renders null for a null order', () => {
const res = renderComponent(
null as unknown as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
expect(res.getByTestId('container')).toBeEmptyDOMElement();
});
it('renders a row when the order is as expected', () => {
const mockOrder = {
offset: '1',
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_MID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
// Row test ids and keys are based on the side, reference and proportion
expect(res.getByTestId('SIDE_BUY-20-1')).toBeInTheDocument();
expect(res.getByText('+1')).toBeInTheDocument();
expect(res.getByText('Mid')).toBeInTheDocument();
expect(res.getByText('20%')).toBeInTheDocument();
});
it('normalises offsets when normaliseToProportion is not 100', () => {
const mockOrder = {
offset: '1',
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('-1')).toBeInTheDocument();
expect(res.getByText('Best Bid')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
});
it('handles a missing offset gracefully (should not happen)', () => {
const mockOrder = {
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-')).toBeInTheDocument();
expect(res.getByText('-')).toBeInTheDocument();
});
it('handles a missing reference gracefully (should not happen)', () => {
const mockOrder = {
offset: '1',
proportion: 20,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
expect(res.getByText('-')).toBeInTheDocument();
});
});
@@ -1,73 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../../types/explorer';
import { TableRow } from '../../../../table';
import { LiquidityProvisionOffset } from './liquidity-provision-offset';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export type VegaSide = components['schemas']['vegaSide'];
export type LiquidityProvisionOrder =
components['schemas']['vegaLiquidityOrder'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
export type LiquidityProvisionDetailsRowProps = {
order?: LiquidityProvisionOrder;
marketId?: string;
side: VegaSide;
// If this is
normaliseProportionsTo: number;
};
/**
*
* Note: offset is formatted by settlement asset on the market, assuming that is available
* Note: Due to the mix of references (MID vs BEST_X), it's not possible to correctly order
* the orders by their actual distance from a midpoint. This would require us knowing
* the best bid (now or at placement) and the mid. Getting the data for *now* would be
* misleading for LP submissions in the past. There is no API for getting <mid />
* at the time of a transaction.
*/
export function LiquidityProvisionDetailsRow({
normaliseProportionsTo,
order,
side,
marketId,
}: LiquidityProvisionDetailsRowProps) {
if (!order || !order.proportion) {
return null;
}
const proportion =
normaliseProportionsTo === 100
? order.proportion
: Math.round((order.proportion / normaliseProportionsTo) * 100);
const key = `${side}-${proportion}-${order.offset ? order.offset : ''}`;
return (
<TableRow modifier="bordered" key={key} data-testid={key}>
<td className="text-right px-2">
{order.offset && marketId ? (
<LiquidityProvisionOffset
offset={order.offset}
side={side}
marketId={marketId}
/>
) : (
'-'
)}
</td>
<td className="text-center">
{order.reference ? LiquidityReferenceLabel[order.reference] : '-'}
</td>
<td className="text-center">{proportion}%</td>
</TableRow>
);
}
@@ -1,22 +0,0 @@
import { render } from '@testing-library/react';
import { LiquidityProvisionMid } from './liquidity-provision-mid';
describe('LiquidityProvisionMid component', () => {
function renderComponent() {
return render(
<table>
<tbody data-testid="container">
<LiquidityProvisionMid />
</tbody>
</table>
);
}
it('renders a basic row that spans the whole table', () => {
const res = renderComponent();
const display = res.getByTestId('mid-display');
expect(res.getByTestId('mid')).toBeInTheDocument();
expect(display).toBeInTheDocument();
expect(display).toHaveAttribute('colspan', '3');
});
});
@@ -1,17 +0,0 @@
import { TableRow } from '../../../../table';
/**
* In a LiquidityProvision table, this row is the midpoint. Above our LP orders on the
* buy side, below are LP orders on the sell side. This component simply divides them.
*
* There is no API that can give us the mid price when the order was created, and even
* if there was it isn't clear that would be appropriate for this centre row. So instead
* it's a simple divider.
*/
export function LiquidityProvisionMid() {
return (
<TableRow modifier="bordered" data-testid="mid">
<td data-testid="mid-display" colSpan={3} className="bg-white"></td>
</TableRow>
);
}
@@ -1,67 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { ExplorerSettlementAssetForMarketDocument } from '../__generated__/Explorer-settlement-asset';
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import type { VegaSide } from './liquidity-provision-details-row';
import {
getFormattedOffset,
LiquidityProvisionOffset,
} from './liquidity-provision-offset';
const decimalsMock: ExplorerSettlementAssetForMarketQuery = {
market: {
id: '123',
__typename: 'Market',
decimalPlaces: 5,
},
};
describe('LiquidityProvisionOffset component', () => {
function renderComponent(
offset: string,
side: VegaSide,
marketId: string,
mocks: MockedResponse[]
) {
return render(
<MockedProvider mocks={mocks}>
<LiquidityProvisionOffset
offset={offset}
side={side}
marketId={marketId}
/>
</MockedProvider>
);
}
it('renders a simple row before market data comes in', () => {
const res = renderComponent('1', 'SIDE_BUY', '123', []);
expect(res.getByText('+1')).toBeInTheDocument();
});
it('replaces unformatted with formatted if the market data comes in', () => {
const mock = {
request: {
query: ExplorerSettlementAssetForMarketDocument,
variables: {
id: '123',
},
result: {
data: decimalsMock,
},
},
};
const res = renderComponent('1', 'SIDE_BUY', '123', [mock]);
expect(res.getByText('+1')).toBeInTheDocument();
});
it('getFormattedOffset returns the unformatted offset if there is not enough data', () => {
const res = getFormattedOffset('1', {});
expect(res).toEqual('1');
});
it('getFormattedOffset decimal formats a number if it comes in with market data', () => {
const res = getFormattedOffset('1', decimalsMock);
expect(res).toEqual('0.00001');
});
});
@@ -1,59 +0,0 @@
import { useExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import type { VegaSide } from './liquidity-provision-details-row';
export type LiquidityProvisionOffsetProps = {
side: VegaSide;
offset: string;
marketId: string;
};
/**
* Correctly formats an LP's offset according to the market settlement decimal places.
* Initially this will appear unformatted, then when the query loads in the proper formatted
* value will be displayed
*
* @see getFormattedOffset
*/
export function LiquidityProvisionOffset({
side,
offset,
marketId,
}: LiquidityProvisionOffsetProps) {
const { data } = useExplorerSettlementAssetForMarketQuery({
variables: {
id: marketId,
},
});
// getFormattedOffset handles missing results/loading states
const formattedOffset = getFormattedOffset(offset, data);
const label = side === 'SIDE_BUY' ? '+' : '-';
const className = side === 'SIDE_BUY' ? 'text-vega-green' : 'text-vega-pink';
return <span className={className}>{`${label}${formattedOffset}`}</span>;
}
/**
* Does the work of formatting the number now we have the market decimal places.
* If no market data is assigned (i.e. during loading, or if the market doesn't exist)
* this function will return the unformatted number
*
* @see LiquidityProvisionOffset
* @param data the result of a ExplorerSettlementAssetForMarketQuery
* @param offset the unformatted offset
* @returns string the offset of this lp order formatted with the settlement decimal places
*/
export function getFormattedOffset(
offset: string,
data?: ExplorerSettlementAssetForMarketQuery
) {
const decimals = data?.market?.decimalPlaces;
if (!decimals) {
return offset;
}
return addDecimalsFormatNumber(offset, decimals);
}
@@ -1,209 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import type { LiquidityOrder } from '@vegaprotocol/types';
import { PeggedReference } from '@vegaprotocol/types';
import type { LiquiditySubmission } from '../tx-liquidity-submission';
import {
LiquidityProvisionDetails,
sumProportions,
} from './liquidity-provision-details';
function mockProportion(proportion: number): LiquidityOrder {
return {
proportion,
reference: PeggedReference.PEGGED_REFERENCE_MID,
offset: '1',
};
}
describe('sumProportions function', () => {
it('returns 0 if the side is undefined', () => {
const side: LiquidityOrder[] = undefined as unknown as LiquidityOrder[];
const res = sumProportions(side);
expect(res).toEqual(0);
});
it('returns 0 if the side is empty', () => {
const side: LiquidityOrder[] = [];
const res = sumProportions(side);
expect(res).toEqual(0);
});
it('sums 1 item correctly (under 100%)', () => {
const side: LiquidityOrder[] = [mockProportion(10)];
const res = sumProportions(side);
expect(res).toEqual(10);
});
it('sums 2 item correctly (exactly 100%)', () => {
const side: LiquidityOrder[] = [mockProportion(50), mockProportion(50)];
const res = sumProportions(side);
expect(res).toEqual(100);
});
it('sums 3 item correctly to over 100%', () => {
const side: LiquidityOrder[] = [
mockProportion(20),
mockProportion(40),
mockProportion(50),
];
const res = sumProportions(side);
expect(res).toEqual(110);
});
});
describe('LiquidityProvisionDetails component', () => {
function renderComponent(provision: LiquiditySubmission) {
return render(
<MockedProvider>
<LiquidityProvisionDetails provision={provision} />
</MockedProvider>
);
}
it('handles an LP with no buys or sells by returning empty (should never happen)', () => {
const mock: LiquiditySubmission = {};
const res = renderComponent(mock);
expect(res.container).toBeEmptyDOMElement();
});
it('handles an LP with no sells by just rendering buys', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
});
it('handles an LP with no buys by just rendering sells', () => {
const mock: LiquiditySubmission = {
marketId: '123',
sells: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
});
it('handles an LP with sells by just rendering buys', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
});
it('handles an LP with both sides', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
sells: [
{
offset: '4',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-4')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
});
it('normalises proportions when they do not total 100%', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 25,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 30,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('45%')).toBeInTheDocument();
expect(res.getByText('55%')).toBeInTheDocument();
});
});
@@ -1,94 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import type { LiquiditySubmission } from '../tx-liquidity-submission';
import { TableRow } from '../../../table';
import { LiquidityProvisionMid } from './components/liquidity-provision-mid';
import { LiquidityProvisionDetailsRow } from './components/liquidity-provision-details-row';
import { Side } from '@vegaprotocol/types';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export type LiquidityProvisionOrder =
components['schemas']['vegaLiquidityOrder'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
/**
* Given a side of a liquidity provision order, returns the total
* It should be 100%, but it isn't always and if it isn't the proportion
* reported for each order should be scaled
*
* @returns number
*/
export function sumProportions(
side: LiquiditySubmission['buys'] | LiquiditySubmission['sells']
): number {
if (!side || side.length === 0) {
return 0;
}
return side.reduce((total, o) => total + (o.proportion || 0), 0);
}
export type LiquidityProvisionDetailsProps = {
provision: LiquiditySubmission;
};
/**
* Renders a table displaying all buys and sells in this LP. It is valid for there
* to be no buys or sells.
*
* It might seem logical to turn proportions in to values based on the total commitment
* but based on the current API structure it is awkward, and given that non-LP orders
* will change the amount that is actually deployed vs assigned to a level, we decided
* not to bother going down that route.
*/
export function LiquidityProvisionDetails({
provision,
}: LiquidityProvisionDetailsProps) {
if (!provision.buys?.length && !provision.sells?.length) {
return null;
}
// We need to do some additional calcs if these aren't both 100
const buyTotal = sumProportions(provision.buys);
const sellTotal = sumProportions(provision.sells);
return (
<table>
<thead>
<TableRow modifier="bordered">
<th className="px-2 pb-1">{t('Price offset')}</th>
<th className="px-2 pb-1">{t('Price reference')}</th>
<th className="px-2 pb-1">{t('Proportion')}</th>
</TableRow>
</thead>
<tbody>
{provision.buys?.map((b, i) => (
<LiquidityProvisionDetailsRow
order={b}
marketId={provision.marketId}
side={Side.SIDE_BUY}
key={`SIDE_BUY-${i}`}
normaliseProportionsTo={buyTotal}
/>
))}
<LiquidityProvisionMid />
{provision.sells?.map((s, i) => (
<LiquidityProvisionDetailsRow
order={s}
marketId={provision.marketId}
side={Side.SIDE_SELL}
key={`SIDE_SELL-${i}`}
normaliseProportionsTo={sellTotal}
/>
))}
</tbody>
</table>
);
}
@@ -1,11 +1,20 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import { TableCell, TableRow } from '../../../table'; import { TableCell, TableRow } from '../../../table';
import type { VegaPeggedReference } from '../liquidity-provision/liquidity-provision-details';
import { Side, PeggedReferenceMapping } from '@vegaprotocol/types'; import { Side, PeggedReferenceMapping } from '@vegaprotocol/types';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market'; import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market'; import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
export interface TxDetailsOrderProps { export interface TxDetailsOrderProps {
offset: string; offset: string;
reference: VegaPeggedReference; reference: VegaPeggedReference;
@@ -33,6 +33,14 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_HOLDING: 'Holding', ACCOUNT_TYPE_HOLDING: 'Holding',
ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution', ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution',
ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees', ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees',
ACCOUNT_TYPE_NETWORK_TREASURY: 'Network Treasury',
ACCOUNT_TYPE_VESTING_REWARDS: 'Vesting Rewards',
ACCOUNT_TYPE_VESTED_REWARDS: 'Vested Rewards',
ACCOUNT_TYPE_REWARD_AVERAGE_POSITION: 'Reward Average Position',
ACCOUNT_TYPE_REWARD_RELATIVE_RETURN: 'Reward Relative Return',
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility',
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking',
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending Fee Referral Reward',
}; };
interface TransferParticipantsProps { interface TransferParticipantsProps {
@@ -1,10 +1,50 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableWithTbody } from '../../table'; import { TableWithTbody } from '../../table';
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
import { ChainEvent } from './chain-events';
import { BigNumber } from 'ethers';
import type { AbiType } from '../../../lib/encoders/abis/abi-types';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { ChainEvent } from './chain-events';
interface AbiOutput {
type: AbiType;
internalType: AbiType;
name: string;
}
/**
* Decodes the b64/ABIcoded result from an eth cal
* @param data
* @returns
*/
export function decodeEthCallResult(
data: BlockExplorerTransactionResult
): string {
const ethResult = data.command.chainEvent?.contractCall.result;
try {
// Decode the result string: base64 => uint8array
const data = base64.decode(ethResult);
// Parse the escaped ABI in to an object
const abi = JSON.parse(
'[{"inputs":[],"name":"latestAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"}]'
);
// Pull the expected types out of the Oracles ABI
const types: AbiType[] = abi[0].outputs.map((o: AbiOutput) => o.type);
const rawResult = defaultAbiCoder.decode(types, data);
// Finally, convert the resulting BigNumber in to a string
const res = BigNumber.from(rawResult[0]).toString();
return res;
} catch (e) {
return '-';
}
}
interface TxDetailsChainEventProps { interface TxDetailsChainEventProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -16,7 +16,6 @@ import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission'; import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation'; import { TxDetailsDelegate } from './tx-delegation';
import { TxDetailsUndelegate } from './tx-undelegation'; import { TxDetailsUndelegate } from './tx-undelegation';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend'; import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel'; import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission'; import { TxDetailsDataSubmission } from './tx-data-submission';
@@ -28,6 +27,7 @@ import { TxDetailsStateVariable } from './tx-state-variable-proposal';
import { TxProposal } from './tx-proposal'; import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer'; import { TxDetailsTransfer } from './tx-transfer';
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission'; import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
interface TxDetailsWrapperProps { interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -107,7 +107,7 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsWithdrawSubmission; return TxDetailsWithdrawSubmission;
case 'Liquidity Provision Order': case 'Liquidity Provision Order':
return TxDetailsLiquiditySubmission; return TxDetailsLiquiditySubmission;
case 'Amend LiquidityProvision Order': case 'Amend Liquidity Provision Order':
return TxDetailsLiquidityAmendment; return TxDetailsLiquidityAmendment;
case 'Cancel LiquidityProvision Order': case 'Cancel LiquidityProvision Order':
return TxDetailsLiquidityCancellation; return TxDetailsLiquidityCancellation;
@@ -5,7 +5,6 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market'; import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
@@ -40,40 +39,32 @@ export const TxDetailsLiquidityAmendment = ({
: '-'; : '-';
return ( return (
<> <TableWithTbody className="mb-8" allowWrap={true}>
<TableWithTbody className="mb-8" allowWrap={true}> <TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TxDetailsShared <TableRow modifier="bordered">
txData={txData} <TableCell>{t('Market')}</TableCell>
pubKey={pubKey} <TableCell>
blockData={blockData} <MarketLink id={marketId} />
/> </TableCell>
</TableRow>
{amendment.commitmentAmount ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell> <TableCell>{t('Commitment amount')}</TableCell>
<TableCell> <TableCell>
<MarketLink id={marketId} /> <PriceInMarket
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
{amendment.commitmentAmount ? ( ) : null}
<TableRow modifier="bordered"> {amendment.fee ? (
<TableCell>{t('Commitment amount')}</TableCell> <TableRow modifier="bordered">
<TableCell> <TableCell>{t('Fee')}</TableCell>
<PriceInMarket <TableCell>{fee}%</TableCell>
price={amendment.commitmentAmount} </TableRow>
marketId={marketId} ) : null}
decimalSource="SETTLEMENT_ASSET" </TableWithTbody>
/>
</TableCell>
</TableRow>
) : null}
{amendment.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={amendment} />
</>
); );
}; };
@@ -1,86 +0,0 @@
import { render } from '@testing-library/react';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
describe('TxDetailsLiquiditySubmission', () => {
const mockTxData = {
hash: 'test',
command: {
liquidityProvisionSubmission: {
marketId: 'BTC-USD',
commitmentAmount: 100,
fee: '0.01',
},
},
};
const mockPubKey = '123';
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
it('should render the component with correct data', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(getByText('Market')).toBeInTheDocument();
expect(getByText('BTC-USD')).toBeInTheDocument();
expect(getByText('Commitment amount')).toBeInTheDocument();
expect(getByText('100')).toBeInTheDocument();
expect(getByText('Fee')).toBeInTheDocument();
expect(getByText('1%')).toBeInTheDocument();
});
it('should display awaiting message when tx data is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={undefined}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
it('should display awaiting message when liquidityProvisionSubmission is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={{ command: {} } as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
@@ -1,31 +1,30 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links/'; import { MarketLink } from '../../links';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market'; import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
export type LiquiditySubmission = export type LiquiditySubmission =
components['schemas']['v1LiquidityProvisionSubmission']; components['schemas']['v1LiquidityProvisionSubmission'];
interface TxDetailsLiquiditySubmissionProps { interface TxDetailsLiquidityAmendmentProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined; pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined; blockData: TendermintBlocksResponse | undefined;
} }
/** /**
* Someone cancelled an order * An existing liquidity order is being created.
*/ */
export const TxDetailsLiquiditySubmission = ({ export const TxDetailsLiquiditySubmission = ({
txData, txData,
pubKey, pubKey,
blockData, blockData,
}: TxDetailsLiquiditySubmissionProps) => { }: TxDetailsLiquidityAmendmentProps) => {
if (!txData || !txData.command.liquidityProvisionSubmission) { if (!txData || !txData.command.liquidityProvisionSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
@@ -39,40 +38,38 @@ export const TxDetailsLiquiditySubmission = ({
: '-'; : '-';
return ( return (
<> <TableWithTbody className="mb-8" allowWrap={true}>
<TableWithTbody className="mb-8" allowWrap={true}> <TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TxDetailsShared <TableRow modifier="bordered">
txData={txData} <TableCell>{t('Market')}</TableCell>
pubKey={pubKey} <TableCell>
blockData={blockData} <MarketLink id={marketId} />
/> </TableCell>
</TableRow>
{submission.commitmentAmount ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell> <TableCell>{t('Commitment amount')}</TableCell>
<TableCell> <TableCell>
<MarketLink id={marketId} /> <PriceInMarket
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
{submission.commitmentAmount ? ( ) : null}
<TableRow modifier="bordered"> {submission.fee ? (
<TableCell>{t('Commitment amount')}</TableCell> <TableRow modifier="bordered">
<TableCell> <TableCell>{t('Fee')}</TableCell>
<PriceInMarket <TableCell>{fee}%</TableCell>
price={submission.commitmentAmount} </TableRow>
marketId={marketId} ) : null}
decimalSource="SETTLEMENT_ASSET" {submission.reference ? (
/> <TableRow modifier="bordered">
</TableCell> <TableCell>{t('Reference')}</TableCell>
</TableRow> <TableCell>{submission.reference}</TableCell>
) : null} </TableRow>
{submission.fee ? ( ) : null}
<TableRow modifier="bordered"> </TableWithTbody>
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={submission} />
</>
); );
}; };
@@ -12,6 +12,7 @@ import {
SPECIAL_CASE_NETWORK, SPECIAL_CASE_NETWORK,
SPECIAL_CASE_NETWORK_ID, SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link'; } from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
type Transfer = components['schemas']['commandsv1Transfer']; type Transfer = components['schemas']['commandsv1Transfer'];
@@ -63,10 +64,16 @@ export const TxDetailsTransfer = ({
return ( return (
<> <>
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered"> <TableRow modifier="bordered" data-testid="type">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell> <TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{getTypeLabelForTransfer(transfer)}</TableCell> <TableCell>{getTypeLabelForTransfer(transfer)}</TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared <TxDetailsShared
txData={txData} txData={txData}
pubKey={pubKey} pubKey={pubKey}
@@ -74,7 +81,7 @@ export const TxDetailsTransfer = ({
hideTypeRow={true} hideTypeRow={true}
/> />
{from ? ( {from ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered" data-testid="from">
<TableCell>{t('From')}</TableCell> <TableCell>{t('From')}</TableCell>
<TableCell> <TableCell>
<PartyLink id={from} /> <PartyLink id={from} />
@@ -82,7 +89,7 @@ export const TxDetailsTransfer = ({
</TableRow> </TableRow>
) : null} ) : null}
{transfer.to ? ( {transfer.to ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered" data-testid="to">
<TableCell>{t('To')}</TableCell> <TableCell>{t('To')}</TableCell>
<TableCell> <TableCell>
<PartyLink id={transfer.to} /> <PartyLink id={transfer.to} />
@@ -90,7 +97,7 @@ export const TxDetailsTransfer = ({
</TableRow> </TableRow>
) : null} ) : null}
{transfer.asset && transfer.amount ? ( {transfer.asset && transfer.amount ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered" data-testid="amount">
<TableCell>{t('Amount')}</TableCell> <TableCell>{t('Amount')}</TableCell>
<TableCell> <TableCell>
<SizeInAsset assetId={transfer.asset} size={transfer.amount} /> <SizeInAsset assetId={transfer.asset} size={transfer.amount} />
@@ -18,11 +18,13 @@ import { FilterLabel } from './tx-filter-label';
export type FilterOption = export type FilterOption =
| 'Amend LiquidityProvision Order' | 'Amend LiquidityProvision Order'
| 'Amend Order' | 'Amend Order'
| 'Apply Referral Code'
| 'Batch Market Instructions' | 'Batch Market Instructions'
| 'Cancel LiquidityProvision Order' | 'Cancel LiquidityProvision Order'
| 'Cancel Order' | 'Cancel Order'
| 'Cancel Transfer Funds' | 'Cancel Transfer Funds'
| 'Chain Event' | 'Chain Event'
| 'Create Referral Set'
| 'Delegate' | 'Delegate'
| 'Ethereum Key Rotate Submission' | 'Ethereum Key Rotate Submission'
| 'Issue Signatures' | 'Issue Signatures'
@@ -40,6 +42,7 @@ export type FilterOption =
| 'Submit Order' | 'Submit Order'
| 'Transfer Funds' | 'Transfer Funds'
| 'Undelegate' | 'Undelegate'
| 'Update Referral Set'
| 'Validator Heartbeat' | 'Validator Heartbeat'
| 'Vote on Proposal' | 'Vote on Proposal'
| 'Withdraw'; | 'Withdraw';
@@ -104,10 +104,30 @@ export function getLabelForProposal(
} }
} else if (proposal.terms?.updateMarket) { } else if (proposal.terms?.updateMarket) {
return t('Proposal: Update market'); return t('Proposal: Update market');
} else if (proposal.terms?.updateSpotMarket) {
return t('Proposal: Update spot');
} else if (proposal.terms?.updateMarketState) {
const type = proposal.terms.updateMarketState.changes?.updateType;
if (type === 'MARKET_STATE_UPDATE_TYPE_TERMINATE') {
return t('Proposal: Market terminate');
} else if (type === 'MARKET_STATE_UPDATE_TYPE_SUSPEND') {
return t('Proposal: Market suspend');
} else if (type === 'MARKET_STATE_UPDATE_TYPE_RESUME') {
return t('Proposal: Market resume');
}
return t('Proposal: Market state');
} else if (proposal.terms?.updateNetworkParameter) { } else if (proposal.terms?.updateNetworkParameter) {
return t('Proposal: Network parameter'); return t('Proposal: Network parameter');
} else if (proposal.terms?.updateReferralProgram) {
return t('Proposal: Referral program');
} else if (proposal.terms?.updateVolumeDiscountProgram) {
return t('Proposal: Discount program');
} else if (proposal.terms?.newFreeform) { } else if (proposal.terms?.newFreeform) {
return t('Proposal: Freeform'); return t('Proposal: Freeform');
} else if (proposal.terms?.newTransfer) {
return t('Proposal: Transfer');
} else if (proposal.terms?.cancelTransfer) {
return t('Proposal: Transfer cancel');
} else { } else {
return t('Proposal'); return t('Proposal');
} }
@@ -1,6 +1,15 @@
import { getTypeLabelForTransfer } from './details/tx-transfer'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
import type { components } from '../../../types/explorer'; import type { components } from '../../../types/explorer';
import {
TxDetailsTransfer,
getTypeLabelForTransfer,
} from './details/tx-transfer';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
type Transfer = components['schemas']['commandsv1Transfer']; type Transfer = components['schemas']['commandsv1Transfer'];
describe('TX: Transfer: getLabelForTransfer', () => { describe('TX: Transfer: getLabelForTransfer', () => {
@@ -56,3 +65,70 @@ describe('TX: Transfer: getLabelForTransfer', () => {
expect(getTypeLabelForTransfer(mock)).toEqual('Transfer'); expect(getTypeLabelForTransfer(mock)).toEqual('Transfer');
}); });
}); });
describe('TxDetailsTransfer', () => {
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
const mockTxData: Partial<BlockExplorerTransactionResult> = {
hash: 'test',
submitter:
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
command: {
nonce: '5188810881378065222',
blockHeight: '14951513',
transfer: {
fromAccountType: 'ACCOUNT_TYPE_GENERAL',
to: '78432a2808f20b18a46ccc6a917bdc4d63c2b9e7007f777bdcab5a9f462c5ba6',
toAccountType: 'ACCOUNT_TYPE_GENERAL',
asset:
'dd20590509d30d20bdbbe64dc1090c1140c7690121a9b9940bc66f62dfa2e599',
amount: '4800000000',
reference: '',
oneOff: {
deliverOn: '0',
},
},
},
signature: {
value:
'610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700',
},
};
it('renders basic transfer details', () => {
const { getByTestId } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsTransfer
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockTxData.command.submitter}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
const id = getByTestId('id');
expect(id.children[0].textContent).toEqual('Transfer ID');
expect(id.children[1].textContent).toEqual(
'51f3bab5eb2637651012507a64d497790a734248792c16e5cf36df8984074fbd'
);
const type = getByTestId('type');
expect(type.children[1].textContent).toEqual('Transfer');
const from = getByTestId('from');
expect(from.children[1].textContent).toEqual(mockTxData.submitter);
const to = getByTestId('to');
expect(to.children[1].textContent).toEqual(mockTxData.command.transfer.to);
});
});
@@ -82,7 +82,7 @@ describe('Txs infinite list item', () => {
expect(screen.getByText('Missing vital data')).toBeInTheDocument(); expect(screen.getByText('Missing vital data')).toBeInTheDocument();
}); });
it('renders data correctly', () => { it('renders data even with missing time', () => {
render( render(
<MockedProvider> <MockedProvider>
<MemoryRouter> <MemoryRouter>
@@ -105,5 +105,33 @@ describe('Txs infinite list item', () => {
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey'); expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType'); expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1'); expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-time')).toHaveTextContent('-');
});
it('renders data correctly', () => {
render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxsInfiniteListItem
type="testType"
submitter="testPubKey"
hash="testTxHash"
block="1"
code={0}
command={{}}
createdAt="1970-11-01T18:07:15Z"
/>
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-time').textContent).toMatch(/years ago/);
}); });
}); });
@@ -9,6 +9,7 @@ import { PartyLink } from '../links';
import { useScreenDimensions } from '@vegaprotocol/react-helpers'; import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import type { Screen } from '@vegaprotocol/react-helpers'; import type { Screen } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { TimeAgo } from '../time-ago';
const DEFAULT_TRUNCATE_LENGTH = 7; const DEFAULT_TRUNCATE_LENGTH = 7;
@@ -32,6 +33,7 @@ export const TxsInfiniteListItem = ({
type, type,
block, block,
command, command,
createdAt,
}: Partial<BlockExplorerTransactionResult>) => { }: Partial<BlockExplorerTransactionResult>) => {
const { screenSize } = useScreenDimensions(); const { screenSize } = useScreenDimensions();
const idTruncateLength = useMemo( const idTruncateLength = useMemo(
@@ -85,6 +87,11 @@ export const TxsInfiniteListItem = ({
endChars={5} endChars={5}
/> />
</td> </td>
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
<td className="text-sm items-center font-mono" data-testid="tx-time">
{createdAt ? <TimeAgo date={createdAt} /> : '-'}
</td>
)}
</tr> </tr>
); );
}; };
@@ -3,6 +3,7 @@ import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import EmptyList from '../empty-list/empty-list'; import EmptyList from '../empty-list/empty-list';
import { Loader } from '@vegaprotocol/ui-toolkit'; import { Loader } from '@vegaprotocol/ui-toolkit';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
interface TxsInfiniteListProps { interface TxsInfiniteListProps {
hasMoreTxs: boolean; hasMoreTxs: boolean;
@@ -19,7 +20,16 @@ interface ItemProps {
} }
const Item = ({ tx }: ItemProps) => { const Item = ({ tx }: ItemProps) => {
const { hash, submitter, type, command, block, code, index: blockIndex } = tx; const {
hash,
submitter,
type,
command,
block,
code,
createdAt,
index: blockIndex,
} = tx;
return ( return (
<TxsInfiniteListItem <TxsInfiniteListItem
type={type} type={type}
@@ -29,6 +39,7 @@ const Item = ({ tx }: ItemProps) => {
hash={hash} hash={hash}
block={block} block={block}
index={blockIndex} index={blockIndex}
createdAt={createdAt}
/> />
); );
}; };
@@ -39,6 +50,7 @@ export const TxsInfiniteList = ({
className, className,
hasFilters = false, hasFilters = false,
}: TxsInfiniteListProps) => { }: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
if (!txs || txs.length === 0) { if (!txs || txs.length === 0) {
if (!areTxsLoading) { if (!areTxsLoading) {
return ( return (
@@ -66,6 +78,9 @@ export const TxsInfiniteList = ({
<th>{t('Type')}</th> <th>{t('Type')}</th>
<th className="text-left">{t('From')}</th> <th className="text-left">{t('From')}</th>
<th>{t('Block')}</th> <th>{t('Block')}</th>
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
<th>{t('Time')}</th>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -43,7 +43,7 @@ export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
url.searchParams.append('first', count); url.searchParams.append('first', count);
url.searchParams.append('after', params.after); url.searchParams.append('after', params.after);
} else { } else {
url.searchParams.append('first', count); url.searchParams.append('last', count);
} }
// Hacky fix for param as array // Hacky fix for param as array
@@ -6,7 +6,7 @@ describe('getTxsDataUrl', () => {
count: 10, count: 10,
baseUrl: 'https://example.com/transactions', baseUrl: 'https://example.com/transactions',
}; };
const expectedUrl = 'https://example.com/transactions?first=10'; const expectedUrl = 'https://example.com/transactions?last=10';
expect(getTxsDataUrl(params)).toEqual(expectedUrl); expect(getTxsDataUrl(params)).toEqual(expectedUrl);
}); });
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
baseUrl: 'https://example.com/transactions', baseUrl: 'https://example.com/transactions',
}; };
const expectedUrl = const expectedUrl =
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234'; 'https://example.com/transactions?last=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
expect(getTxsDataUrl(params)).toEqual(expectedUrl); expect(getTxsDataUrl(params)).toEqual(expectedUrl);
}); });
+1 -1
View File
@@ -133,7 +133,7 @@ export const ErrorBoundary = () => {
); );
}; };
const GHOST = ( export const GHOST = (
<svg <svg
width="56" width="56"
height="85" height="85"
@@ -36,30 +36,50 @@ const PERCENTAGE_PARAMS = [
'governance.proposal.updateNetParam.requiredMajority', 'governance.proposal.updateNetParam.requiredMajority',
'governance.proposal.updateNetParam.requiredParticipation', 'governance.proposal.updateNetParam.requiredParticipation',
'governance.proposal.updateMarket.minProposerEquityLikeShare', 'governance.proposal.updateMarket.minProposerEquityLikeShare',
'governance.proposal.VolumeDiscountProgram.requiredMajority',
'governance.proposal.VolumeDiscountProgram.requiredParticipation',
'governance.proposal.referralProgram.requiredMajority',
'governance.proposal.transfer.requiredMajority',
'governance.proposal.updateAsset.requiredMajority',
'governance.proposal.updateAsset.requiredParticipation',
'governance.proposal.transfer.requiredParticipation',
'governance.proposal.referralProgram.requiredParticipation',
'network.validators.ersatz.rewardFactor',
'network.validators.ersatz.multipleOfTendermintValidators',
'validators.vote.required', 'validators.vote.required',
]; 'referralProgram.maxReferralRewardFactor',
'referralProgram.maxReferralDiscountFactor',
'referralProgram.maxReferralRewardProportion',
].map((p) => p.toLowerCase());
const BIG_NUMBER_PARAMS = [ const BIG_NUMBER_PARAMS = [
'spam.protection.delegation.min.tokens', 'spam.protection.delegation.min.tokens',
'validators.delegation.minAmount', 'validators.delegation.minAmount',
'governance.proposal.transfer.maxAmount',
'reward.staking.delegation.minimumValidatorStake', 'reward.staking.delegation.minimumValidatorStake',
'reward.staking.delegation.maxPayoutPerParticipant', 'reward.staking.delegation.maxPayoutPerParticipant',
'reward.staking.delegation.maxPayoutPerEpoch', 'reward.staking.delegation.maxPayoutPerEpoch',
'spam.protection.voting.min.tokens', 'spam.protection.voting.min.tokens',
'spam.protection.proposal.min.tokens',
'governance.proposal.transfer.minVoterBalance',
'governance.proposal.freeform.minProposerBalance', 'governance.proposal.freeform.minProposerBalance',
'governance.proposal.updateNetParam.minVoterBalance', 'governance.proposal.updateNetParam.minVoterBalance',
'governance.proposal.updateMarket.minVoterBalance', 'governance.proposal.updateMarket.minVoterBalance',
'governance.proposal.asset.minVoterBalance', 'governance.proposal.asset.minVoterBalance',
'governance.proposal.updateNetParam.minProposerBalance', 'governance.proposal.updateNetParam.minProposerBalance',
'governance.proposal.freeform.minVoterBalance', 'governance.proposal.freeform.minVoterBalance',
'spam.protection.proposal.min.tokens',
'governance.proposal.updateMarket.minProposerBalance', 'governance.proposal.updateMarket.minProposerBalance',
'governance.proposal.asset.minProposerBalance', 'governance.proposal.asset.minProposerBalance',
'governance.proposal.transfer.minProposerBalance',
'governance.proposal.market.minProposerBalance', 'governance.proposal.market.minProposerBalance',
'governance.proposal.market.minVoterBalance', 'governance.proposal.market.minVoterBalance',
'governance.proposal.updateAsset.minProposerBalance', 'governance.proposal.updateAsset.minProposerBalance',
'governance.proposal.updateAsset.minVoterBalance', 'governance.proposal.updateAsset.minVoterBalance',
]; 'governance.proposal.referralProgram.minProposerBalance',
'governance.proposal.referralProgram.minVoterBalance',
'governance.proposal.VolumeDiscountProgram.minProposerBalance',
'governance.proposal.VolumeDiscountProgram.minVoterBalance',
].map((p) => p.toLowerCase());
export const renderGroupedParams = ( export const renderGroupedParams = (
group: GroupedParams, group: GroupedParams,
@@ -131,12 +151,12 @@ export const NetworkParameterRow = ({
<div className="pb-2"> <div className="pb-2">
<SyntaxHighlighter data={JSON.parse(value)} /> <SyntaxHighlighter data={JSON.parse(value)} />
</div> </div>
) : BIG_NUMBER_PARAMS.includes(key.toLowerCase()) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key.toLowerCase()) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : isNaN(Number(value)) ? ( ) : isNaN(Number(value)) ? (
value value
) : BIG_NUMBER_PARAMS.includes(key) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : ( ) : (
formatNumber(Number(value), 4) formatNumber(Number(value), 4)
)} )}
@@ -1,5 +1,5 @@
fragment ExplorerOracleDataConnection on OracleSpec { fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection { dataConnection(pagination: { first: 30 }) {
edges { edges {
node { node {
externalData { externalData {
@@ -45,10 +45,51 @@ fragment ExplorerOracleDataSource on OracleSpec {
operator operator
} }
} }
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
} }
... on DataSourceDefinitionExternal { ... on DataSourceDefinitionExternal {
sourceType { sourceType {
... on EthCallSpec {
abi
args
method
requiredConfirmations
address
normalisers {
name
expression
}
trigger {
trigger {
... on EthTimeTrigger {
initial
every
until
}
}
}
filters {
key {
name
type
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
... on DataSourceSpecConfiguration { ... on DataSourceSpecConfiguration {
signers { signers {
signer { signer {
@@ -72,6 +113,23 @@ fragment ExplorerOracleDataSource on OracleSpec {
} }
} }
} }
... on EthCallSpec {
abi
address
requiredConfirmations
method
filters {
key {
type
name
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
} }
} }
} }
@@ -81,7 +139,7 @@ fragment ExplorerOracleDataSource on OracleSpec {
} }
query ExplorerOracleSpecs { query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: { first: 50 }) { oracleSpecsConnection(pagination: { first: 30 }) {
pageInfo { pageInfo {
hasNextPage hasNextPage
} }
@@ -1,22 +1,85 @@
fragment ExplorerOraclePerpetual on Perpetual {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForSettlementSchedule {
id
status
}
}
fragment ExplorerOracleFuture on Future {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForTradingTermination {
id
status
}
}
fragment ExplorerOracleForMarketsMarket on Market { fragment ExplorerOracleForMarketsMarket on Market {
id id
state
tradableInstrument { tradableInstrument {
instrument { instrument {
product { product {
... on Future { ... on Future {
dataSourceSpecForSettlementData { ...ExplorerOracleFuture
id
}
dataSourceSpecForTradingTermination {
id
}
} }
... on Perpetual { ... on Perpetual {
dataSourceSpecForSettlementData { ...ExplorerOraclePerpetual
id }
}
}
}
}
fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
spec {
id
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
dataSourceSpecForSettlementSchedule { }
id ... on DataSourceDefinitionExternal {
sourceType {
... on EthCallSpec {
address
}
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
}
} }
} }
} }
@@ -32,4 +95,27 @@ query ExplorerOracleFormMarkets {
} }
} }
} }
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: { last: 1 }) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
} }
+63 -5
View File
@@ -5,23 +5,23 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
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', 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 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, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, 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 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, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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 } };
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>; export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, 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, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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 ExplorerOracleSpecByIdQueryVariables = Types.Exact<{ export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID']; 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, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, 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 }; 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, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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 };
export const ExplorerOracleDataConnectionFragmentDoc = gql` export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec { fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection { dataConnection(pagination: {first: 30}) {
edges { edges {
node { node {
externalData { externalData {
@@ -68,10 +68,51 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
operator operator
} }
} }
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
} }
... on DataSourceDefinitionExternal { ... on DataSourceDefinitionExternal {
sourceType { sourceType {
... on EthCallSpec {
abi
args
method
requiredConfirmations
address
normalisers {
name
expression
}
trigger {
trigger {
... on EthTimeTrigger {
initial
every
until
}
}
}
filters {
key {
name
type
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
... on DataSourceSpecConfiguration { ... on DataSourceSpecConfiguration {
signers { signers {
signer { signer {
@@ -95,6 +136,23 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
} }
} }
} }
... on EthCallSpec {
abi
address
requiredConfirmations
method
filters {
key {
type
name
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
} }
} }
} }
@@ -105,7 +163,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
${ExplorerOracleDataConnectionFragmentDoc}`; ${ExplorerOracleDataConnectionFragmentDoc}`;
export const ExplorerOracleSpecsDocument = gql` export const ExplorerOracleSpecsDocument = gql`
query ExplorerOracleSpecs { query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: {first: 50}) { oracleSpecsConnection(pagination: {first: 30}) {
pageInfo { pageInfo {
hasNextPage hasNextPage
} }
@@ -3,33 +3,106 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Spot' } } } }; export type ExplorerOraclePerpetualFragment = { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } };
export type ExplorerOracleFutureFragment = { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } };
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } };
export type ExplorerOracleDataSourceSpecFragment = { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } };
export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>; export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Spot' } } } } }> } | null }; export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export const ExplorerOracleFutureFragmentDoc = gql`
fragment ExplorerOracleFuture on Future {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForTradingTermination {
id
status
}
}
`;
export const ExplorerOraclePerpetualFragmentDoc = gql`
fragment ExplorerOraclePerpetual on Perpetual {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForSettlementSchedule {
id
status
}
}
`;
export const ExplorerOracleForMarketsMarketFragmentDoc = gql` export const ExplorerOracleForMarketsMarketFragmentDoc = gql`
fragment ExplorerOracleForMarketsMarket on Market { fragment ExplorerOracleForMarketsMarket on Market {
id id
state
tradableInstrument { tradableInstrument {
instrument { instrument {
product { product {
... on Future { ... on Future {
dataSourceSpecForSettlementData { ...ExplorerOracleFuture
id
}
dataSourceSpecForTradingTermination {
id
}
} }
... on Perpetual { ... on Perpetual {
dataSourceSpecForSettlementData { ...ExplorerOraclePerpetual
id }
}
}
}
}
${ExplorerOracleFutureFragmentDoc}
${ExplorerOraclePerpetualFragmentDoc}`;
export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
spec {
id
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
dataSourceSpecForSettlementSchedule { }
id ... on DataSourceDefinitionExternal {
sourceType {
... on EthCallSpec {
address
}
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
}
} }
} }
} }
@@ -46,8 +119,32 @@ export const ExplorerOracleFormMarketsDocument = gql`
} }
} }
} }
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: {last: 1}) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
} }
${ExplorerOracleForMarketsMarketFragmentDoc}`; ${ExplorerOracleForMarketsMarketFragmentDoc}
${ExplorerOracleDataSourceSpecFragmentDoc}`;
/** /**
* __useExplorerOracleFormMarketsQuery__ * __useExplorerOracleFormMarketsQuery__
@@ -51,15 +51,26 @@ describe('Oracle Data view', () => {
{ {
node: { node: {
externalData: { externalData: {
__typename: 'ExternalData',
data: { data: {
broadcastAt: '2022-01-01', __typename: 'Data',
matchedSpecIds: ['123'],
broadcastAt: '2023-01-01T00:00:00Z',
data: [
{
__typename: 'Property',
name: 'Test-name',
value: 'Test-data',
},
],
}, },
}, },
}, },
}, },
], ],
} as DataConnection) } as ExplorerOracleDataConnectionFragment['dataConnection'])
); );
expect(res.getByText('Broadcast data')).toBeInTheDocument(); expect(res.getByText('Test-name')).toBeInTheDocument();
expect(res.getByText('Test-data')).toBeInTheDocument();
}); });
}); });
@@ -1,39 +1,55 @@
import { t } from '@vegaprotocol/i18n';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles'; import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
import { TimeAgo } from '../../../components/time-ago';
import { t } from '@vegaprotocol/i18n';
const cellSpacing = 'px-3';
interface OracleDataTypeProps { interface OracleDataTypeProps {
data: ExplorerOracleDataConnectionFragment['dataConnection']; data: ExplorerOracleDataConnectionFragment['dataConnection'];
} }
/**
* If there is data that has matched this oracle, this view will
* render the data inside a collapsed element so that it can be viewed.
* Currently the data is just rendered as a JSON view, because
* that Does The Job, rather than because it's good.
*/
export function OracleData({ data }: OracleDataTypeProps) { export function OracleData({ data }: OracleDataTypeProps) {
if (!data || !data.edges?.length || data.edges.length > 1) { if (!data || !data.edges?.length) {
return null; return null;
} }
return ( return (
<details data-testid="oracle-data"> <>
<summary>{t('Broadcast data')}</summary> <h2 className="text-3xl font-bold mb-4 display-5 mt-5">
<ul> {t('Recent data')}
{data.edges.map((d) => { </h2>
if (!d) { <table>
return null; <thead>
} <tr className="text-left">
<th className={cellSpacing}>Value</th>
<th className={cellSpacing}>Key</th>
<th className={cellSpacing}>Date</th>
</tr>
</thead>
<tbody>
{data.edges.map((d) => {
if (!d) {
return null;
}
return ( const broadcastAt = d.node.externalData.data.broadcastAt;
<li key={d.node.externalData.data.broadcastAt}> const node = d.node.externalData.data.data?.at(0);
<SyntaxHighlighter data={filter(d, ['__typename'])} /> if (!node || !node.value || !node.name || !node || !broadcastAt) {
</li> return null;
); }
})}
</ul> return (
</details> <tr key={d.node.externalData.data.broadcastAt}>
<td className={`${cellSpacing} font-mono`}>{node.value}</td>
<td className={`${cellSpacing} font-mono`}>{node.name}</td>
<td className={cellSpacing}>
<TimeAgo date={broadcastAt} />
</td>
</tr>
);
})}
</tbody>
</table>
</>
); );
} }
@@ -55,13 +55,13 @@ describe('Oracle type view', () => {
const s = mock('prices.external.whatever'); const s = mock('prices.external.whatever');
expect(isInternalSourceType(s)).toEqual(false); expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s)); const res = render(renderWrappedComponent(s));
expect(res.getByText('External data')).toBeInTheDocument(); expect(res.getByText('External Data')).toBeInTheDocument();
}); });
it('Renders External data otherwise', () => { it('Renders External data otherwise', () => {
const s = mock('prices.external.vegaprotocol.builtin.'); const s = mock('prices.external.vegaprotocol.builtin.');
expect(isInternalSourceType(s)).toEqual(false); expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s)); const res = render(renderWrappedComponent(s));
expect(res.getByText('External data')).toBeInTheDocument(); expect(res.getByText('External Data')).toBeInTheDocument();
}); });
}); });
@@ -26,6 +26,19 @@ export function isInternalSourceType(s: SourceType) {
return false; return false;
} }
export function getExternalType(s: SourceType) {
if (s.sourceType.__typename === 'EthCallSpec') {
return 'Ethereum Contract Call';
} else {
return 'External Data';
}
}
export function getTypeString(s: SourceType) {
const isInternal = isInternalSourceType(s);
return isInternal ? 'Internal data' : getExternalType(s);
}
interface OracleDetailsTypeProps { interface OracleDetailsTypeProps {
sourceType: SourceType; sourceType: SourceType;
} }
@@ -39,14 +52,10 @@ export function OracleDetailsType({ sourceType }: OracleDetailsTypeProps) {
return null; return null;
} }
const isInternal = isInternalSourceType(sourceType);
return ( return (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableHeader scope="row">Type</TableHeader> <TableHeader scope="row">Type</TableHeader>
<TableCell modifier="bordered"> <TableCell modifier="bordered">{getTypeString(sourceType)}</TableCell>
{isInternal ? 'Internal data' : 'External data'}
</TableCell>
</TableRow> </TableRow>
); );
} }
@@ -0,0 +1,39 @@
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
interface OracleDetailsEthSourceProps {
sourceType: SourceType;
}
/**
* Given an Oracle that sources data from Ethereum, this component will render
* a link to the smart contract and some basic details
*/
export function OracleEthSource({ sourceType }: OracleDetailsEthSourceProps) {
if (
sourceType.__typename !== 'DataSourceDefinitionExternal' ||
sourceType.sourceType.__typename !== 'EthCallSpec'
) {
return null;
}
const address = sourceType.sourceType.address;
if (!address) {
return null;
}
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Ethereum Contract</TableHeader>
<TableCell modifier="bordered">
<EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />
<span className="mx-3">&rArr;</span>
<code>{sourceType.sourceType.method}</code>
</TableCell>
</TableRow>
);
}
@@ -1,14 +1,17 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { getConditionsOrFilters, OracleFilter } from './oracle-filter'; import { OracleFilter } from './oracle-filter';
import type { Filter } from './oracle-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles'; import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import { import {
ConditionOperator, ConditionOperator,
DataSourceSpecStatus, DataSourceSpecStatus,
PropertyKeyType, PropertyKeyType,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import type { Condition } from '@vegaprotocol/types';
const mockExternalSpec = { type Spec =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
const mockExternalSpec: Spec = {
sourceType: { sourceType: {
__typename: 'DataSourceSpecConfiguration', __typename: 'DataSourceSpecConfiguration',
filters: [ filters: [
@@ -16,12 +19,12 @@ const mockExternalSpec = {
__typename: 'Filter', __typename: 'Filter',
key: { key: {
type: PropertyKeyType.TYPE_INTEGER, type: PropertyKeyType.TYPE_INTEGER,
name: 'test', name: 'testKey',
}, },
conditions: [ conditions: [
{ {
__typename: 'Condition', __typename: 'Condition',
value: 'test', value: 'testValue',
operator: ConditionOperator.OPERATOR_EQUALS, operator: ConditionOperator.OPERATOR_EQUALS,
}, },
], ],
@@ -30,16 +33,6 @@ const mockExternalSpec = {
}, },
}; };
const mockTimeSpec = {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
value: '123',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
};
function renderComponent(data: ExplorerOracleDataSourceFragment) { function renderComponent(data: ExplorerOracleDataSourceFragment) {
return <OracleFilter data={data} />; return <OracleFilter data={data} />;
} }
@@ -70,11 +63,16 @@ describe('Oracle Filter view', () => {
}, },
}, },
}, },
} as ExplorerOracleDataSourceFragment) dataConnection: {
edges: [],
},
})
); );
expect(res.getByText('Filter')).toBeInTheDocument(); // Renders a comprehensible summary of key = value
// Avoids asserting on how the data is presented because it is very rudimentary expect(res.getByText('testKey')).toBeInTheDocument();
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByText('testValue')).toBeInTheDocument();
}); });
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => { it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
@@ -88,75 +86,59 @@ describe('Oracle Filter view', () => {
data: { data: {
sourceType: { sourceType: {
__typename: 'DataSourceDefinitionInternal', __typename: 'DataSourceDefinitionInternal',
sourceType: mockTimeSpec, sourceType: {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
value: '1',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
},
}, },
}, },
}, },
}, },
} as ExplorerOracleDataSourceFragment) dataConnection: {
edges: [],
},
})
); );
expect(res.getByText('Filter')).toBeInTheDocument(); expect(res.getByText('Time')).toBeInTheDocument();
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByTitle('1').textContent).toMatch(/1970/);
// Avoids asserting on how the data is presented because it is very rudimentary // Avoids asserting on how the data is presented because it is very rudimentary
}); });
});
describe('getConditionsOrFilter', () => { it('DataSourceSpecConfigurationTime handles empty conditions', () => {
it('Returns null if the type is undetermined (not DataSourceSpecConfiguration or DataSourceSpecConfigurationTime', () => { const res = render(
expect(getConditionsOrFilters({})).toBeNull(); renderComponent({
}); dataSourceSpec: {
spec: {
it('Returns the conditions object for time specs', () => { id: 'irrelevant-test-data',
const mock: Filter = { createdAt: 'irrelevant-test-data',
__typename: 'DataSourceSpecConfigurationTime', status: DataSourceSpecStatus.STATUS_ACTIVE,
conditions: [ data: {
{ sourceType: {
__typename: 'Condition', __typename: 'DataSourceDefinitionInternal',
value: '100', sourceType: {
operator: ConditionOperator.OPERATOR_GREATER_THAN, __typename: 'DataSourceSpecConfigurationTime',
}, conditions: [undefined as unknown as Condition],
], },
}; },
const res = getConditionsOrFilters(mock);
// This ugly construction is due to lazy typing on getConditionsOrFilter
if (!res || res.length !== 1 || !res[0] || 'key' in res[0]) {
throw new Error(
'getConditionsOrFilter did not return conditions on a time spec'
);
}
expect(res[0].__typename).toEqual('Condition');
});
it('Returns the filters object for external specs', () => {
const mock: Filter = {
__typename: 'DataSourceSpecConfiguration',
filters: [
{
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'test',
},
conditions: [
{
__typename: 'Condition',
value: 'test',
operator: ConditionOperator.OPERATOR_EQUALS,
}, },
], },
}, },
], dataConnection: {
}; edges: [],
},
})
);
const res = getConditionsOrFilters(mock); // This should never happen, but for coverage sake we test that it does this
// This ugly construction is due to lazy typing on getConditionsOrFilter const ul = res.getByRole('list');
if (!res || res.length !== 1 || !res[0] || 'value' in res[0]) { expect(ul).toBeInTheDocument();
throw new Error( expect(ul).toBeEmptyDOMElement();
'getConditionsOrFilter did not return filters on a external spec'
);
}
expect(res[0].__typename).toEqual('Filter');
}); });
}); });
@@ -1,36 +1,15 @@
import { t } from '@vegaprotocol/i18n';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles'; import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
import { OracleSpecCondition } from './oracle-spec/condition';
import { getCharacterForOperator } from './oracle-spec/operator';
interface OracleFilterProps { interface OracleFilterProps {
data: ExplorerOracleDataSourceFragment; data: ExplorerOracleDataSourceFragment;
} }
export type Filter =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType']['sourceType'];
/**
* Given the main Filter view just uses a JSON dump view, this function
* selects the correct filter to dump in to that view. Internal oracles
* (i.e. the Time oracle) have conditions while external data sources
* have filters
*
* @param s A data source
* @returns Object an object containing conditions or filters
*/
export function getConditionsOrFilters(s: Filter) {
if (s.__typename === 'DataSourceSpecConfiguration') {
return s.filters;
} else if (s.__typename === 'DataSourceSpecConfigurationTime') {
return s.conditions;
}
return null;
}
/** /**
* Shows the conditions that this oracle is using to filter * Shows the conditions that this oracle is using to filter
* data sources. * data sources, as a list.
* *
* Renders nothing if there is no data (which will frequently) * Renders nothing if there is no data (which will frequently)
* be the case) and if there is data, currently renders a simple * be the case) and if there is data, currently renders a simple
@@ -42,16 +21,53 @@ export function OracleFilter({ data }: OracleFilterProps) {
} }
const s = data.dataSourceSpec.spec.data.sourceType.sourceType; const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
const f = getConditionsOrFilters(s); if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) {
return (
<ul>
{s.conditions
.filter((c) => !!c)
.map((c) => {
if (!c) {
return null;
}
return (
<OracleSpecCondition key={c.value} data={c} type={s.__typename} />
);
})}
</ul>
);
} else if (
s.__typename === 'DataSourceSpecConfigurationTimeTrigger' &&
s.triggers
) {
return <OracleSpecInternalTimeTrigger data={s} />;
} else if (
s.__typename === 'EthCallSpec' ||
s.__typename === 'DataSourceSpecConfiguration'
) {
if (s.filters !== null && s.filters && 'filters' in s) {
return (
<ul>
{s.filters.map((f) => {
const prop = <code title={f.key.type}>{f.key.name}</code>;
if (!f) { if (!f.conditions || f.conditions.length === 0) {
return null; return prop;
} else {
return f.conditions.map((c) => {
return (
<li key={`${prop}${c.value}`}>
{prop} {getCharacterForOperator(c.operator)}{' '}
<code>{c.value ? c.value : '-'}</code>
</li>
);
});
}
})}
</ul>
);
}
} }
return ( return null;
<details>
<summary>{t('Filter')}</summary>
<SyntaxHighlighter data={filter(f, ['__typename'])} />
</details>
);
} }
@@ -11,7 +11,7 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
<MemoryRouter> <MemoryRouter>
<MockedProvider mocks={mocks}> <MockedProvider mocks={mocks}>
<Table> <Table>
<tbody> <tbody data-testid="wrapper">
<OracleMarkets id={id} /> <OracleMarkets id={id} />
</tbody> </tbody>
</Table> </Table>
@@ -23,8 +23,7 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
describe('Oracle Markets component', () => { describe('Oracle Markets component', () => {
it('Renders a row with the market ID initially', () => { it('Renders a row with the market ID initially', () => {
const res = render(renderComponent('123', [])); const res = render(renderComponent('123', []));
expect(res.getByText('Market')).toBeInTheDocument(); expect(res.getByTestId('wrapper')).toBeEmptyDOMElement();
expect(res.getByText('123')).toBeInTheDocument();
}); });
it('Renders that this is a termination source for the right market', async () => { it('Renders that this is a termination source for the right market', async () => {
@@ -34,21 +33,58 @@ describe('Oracle Markets component', () => {
}, },
result: { result: {
data: { data: {
oracleSpecsConnection: {
edges: [
{
node: {
dataConnection: {
edges: [
{
node: {
externalData: {
data: {
data: {
name: '123',
value: '456',
},
},
},
},
},
],
},
dataSourceSpec: {
spec: {
id: '789',
state: 'Active',
status: 'Active',
data: {
sourceType: {},
},
},
},
},
},
],
},
marketsConnection: { marketsConnection: {
edges: [ edges: [
{ {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: '123', id: '123',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: '456', id: '456',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: '789', id: '789',
status: 'Active',
}, },
}, },
}, },
@@ -59,15 +95,18 @@ describe('Oracle Markets component', () => {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: 'abc', id: 'abc',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: 'def', id: 'def',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: 'ghi', id: 'ghi',
status: 'Active',
}, },
}, },
}, },
@@ -91,21 +130,58 @@ describe('Oracle Markets component', () => {
}, },
result: { result: {
data: { data: {
oracleSpecsConnection: {
edges: [
{
node: {
dataConnection: {
edges: [
{
node: {
externalData: {
data: {
data: {
name: '123',
value: '456',
},
},
},
},
},
],
},
dataSourceSpec: {
spec: {
id: '789',
state: 'Active',
status: 'Active',
data: {
sourceType: {},
},
},
},
},
},
],
},
marketsConnection: { marketsConnection: {
edges: [ edges: [
{ {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: '123', id: '123',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: '789', id: '789',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: '123', id: '123',
status: 'Active',
}, },
}, },
}, },
@@ -116,15 +192,18 @@ describe('Oracle Markets component', () => {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: 'abc', id: 'abc',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: 'def', id: 'def',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: 'ghi', id: 'ghi',
status: 'Active',
}, },
}, },
}, },
@@ -1,5 +1,4 @@
import { getNodes } from '@vegaprotocol/utils'; import { getNodes } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { MarketLink } from '../../../components/links'; import { MarketLink } from '../../../components/links';
import { TableRow, TableCell, TableHeader } from '../../../components/table'; import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets'; import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets';
@@ -24,38 +23,37 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
); );
if (markets) { if (markets) {
const m = markets.find((m) => { const m = markets.filter((market) => {
const p = m.tradableInstrument.instrument.product; const p = market.tradableInstrument.instrument.product;
if ( if (
((p.__typename === 'Future' || p.__typename === 'Perpetual') && ((p.__typename === 'Future' || p.__typename === 'Perpetual') &&
p.dataSourceSpecForSettlementData.id === id) || p.dataSourceSpecForSettlementData.id === id) ||
('dataSourceSpecForTradingTermination' in p && ('dataSourceSpecForTradingTermination' in p &&
p.dataSourceSpecForTradingTermination.id === id) p.dataSourceSpecForTradingTermination.id === id) ||
(p.__typename === 'Perpetual' &&
p.dataSourceSpecForSettlementSchedule.id === id)
) { ) {
return true; return true;
} }
return false; return false;
}); });
if (m && m.id) { if (m && m.length > 0) {
return ( return (
<TableRow modifier="bordered"> <>
<TableHeader scope="row">{getLabel(id, m)}</TableHeader> {m.map((market) => (
<TableCell modifier="bordered" data-testid={`m-${m.id}`}> <TableRow modifier="bordered" key={`m-${market.id}`}>
<MarketLink id={m.id} /> <TableHeader scope="row">{getLabel(id, market)}</TableHeader>
</TableCell> <TableCell modifier="bordered" data-testid={`m-${market.id}`}>
</TableRow> <MarketLink id={market.id} />
</TableCell>
</TableRow>
))}
</>
); );
} }
} }
return ( return null;
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Market')}</TableHeader>
<TableCell modifier="bordered">
<span>{id}</span>
</TableCell>
</TableRow>
);
} }
export function getLabel( export function getLabel(
@@ -0,0 +1,37 @@
import { t } from '@vegaprotocol/i18n';
import fromUnixTime from 'date-fns/fromUnixTime';
import { getCharacterForOperator } from './operator';
import type { Condition } from '@vegaprotocol/types';
export interface OracleSpecCondition {
data: Condition;
type?: string;
}
export function OracleSpecCondition({ data, type }: OracleSpecCondition) {
const c = getCharacterForOperator(data.operator);
const value =
type === 'DataSourceSpecConfigurationTime' && data.value
? fromUnixTime(parseInt(data.value)).toLocaleString()
: data.value;
const typeLabel =
type === 'DataSourceSpecConfigurationTime' ? (
<span>{t('Time')}</span>
) : (
type
);
return (
<li key={`${typeLabel}${c}${value}`}>
{typeLabel} {c}{' '}
{value && (
<span
title={data.value || value}
className="underline decoration-dotted"
>
{value}
</span>
)}
</li>
);
}
@@ -0,0 +1,44 @@
import { t } from '@vegaprotocol/i18n';
import type { DataSourceSpecConfigurationTimeTrigger } from '@vegaprotocol/types';
import secondsToMinutes from 'date-fns/secondsToMinutes';
import fromUnixTime from 'date-fns/fromUnixTime';
export interface OracleSpecInternalTimeTriggerProps {
data: DataSourceSpecConfigurationTimeTrigger;
}
export function OracleSpecInternalTimeTrigger({
data,
}: OracleSpecInternalTimeTriggerProps) {
return (
<div>
<span>{t('Time')}</span>,&nbsp;
{data.triggers.map((tr) => {
return (
<span>
{tr?.initial ? (
<span title={`${tr.initial}`}>
<strong>{t('starting at')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{fromUnixTime(tr.initial).toLocaleString()}
</em>
</span>
) : (
''
)}
{tr?.every ? (
<span title={`${tr.every} ${t('seconds')}`}>
, <strong>{t('every')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{secondsToMinutes(tr.every)} {t('minutes')}
</em>{' '}
</span>
) : (
''
)}
</span>
);
})}
</div>
);
}
@@ -0,0 +1,21 @@
import { t } from '@vegaprotocol/i18n';
import type { ConditionOperator } from '@vegaprotocol/types';
export function getCharacterForOperator(
operator: ConditionOperator
): React.ReactElement {
switch (operator) {
case 'OPERATOR_EQUALS':
return <span title={t('equals')}>=</span>;
case 'OPERATOR_GREATER_THAN':
return <span title={t('greater than')}>&gt;</span>;
case 'OPERATOR_GREATER_THAN_OR_EQUAL':
return <span title={t('greater than or equal')}>&ge;</span>;
case 'OPERATOR_LESS_THAN':
return <span title={t('less than')}>&lt;</span>;
case 'OPERATOR_LESS_THAN_OR_EQUAL':
return <span title={t('less than or equal')}>&le;</span>;
}
return <span>{operator}</span>;
}
@@ -14,7 +14,9 @@ import { OracleFilter } from './oracle-filter';
import { OracleDetailsType } from './oracle-details-type'; import { OracleDetailsType } from './oracle-details-type';
import { OracleMarkets } from './oracle-markets'; import { OracleMarkets } from './oracle-markets';
import { OracleSigners } from './oracle-signers'; import { OracleSigners } from './oracle-signers';
import OracleLink from '../../../components/links/oracle-link/oracle-link'; import { OracleEthSource } from './oracle-eth-source';
import Hash from '../../../components/links/hash';
import { getStatusString } from '../../../components/links/oracle-link/oracle-link';
export type SourceType = export type SourceType =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType']; ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
@@ -38,10 +40,8 @@ export const OracleDetails = ({
id, id,
dataSource, dataSource,
dataConnection, dataConnection,
showBroadcasts = false,
}: OracleDetailsProps) => { }: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType; const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.edges?.length || 0;
return ( return (
<div> <div>
@@ -49,23 +49,27 @@ export const OracleDetails = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableHeader scope="row">{t('ID')}</TableHeader> <TableHeader scope="row">{t('ID')}</TableHeader>
<TableCell modifier="bordered"> <TableCell modifier="bordered">
<OracleLink id={id} /> <Hash text={id} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<OracleDetailsType sourceType={sourceType} /> <OracleDetailsType sourceType={sourceType} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Status')}</TableHeader>
<TableCell modifier="bordered">
{getStatusString(dataSource.dataSourceSpec.spec.status)}
</TableCell>
</TableRow>
<OracleSigners sourceType={sourceType} /> <OracleSigners sourceType={sourceType} />
<OracleEthSource sourceType={sourceType} />
<OracleMarkets id={id} /> <OracleMarkets id={id} />
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableHeader scope="row">{t('Matched data')}</TableHeader> <TableHeader scope="row">{t('Filter')}</TableHeader>
<TableCell modifier="bordered"> <TableCell modifier="bordered">
{showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'} <OracleFilter data={dataSource} />
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableWithTbody> </TableWithTbody>
<OracleFilter data={dataSource} /> {dataConnection ? <OracleData data={dataConnection} /> : null}
{showBroadcasts && dataConnection ? (
<OracleData data={dataConnection} />
) : null}
</div> </div>
); );
}; };
@@ -1,18 +1,28 @@
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import compact from 'lodash/compact';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../../components/route-title'; import { RouteTitle } from '../../../components/route-title';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
import { useDocumentTitle } from '../../../hooks/use-document-title'; import { useDocumentTitle } from '../../../hooks/use-document-title';
import { OracleDetails } from '../components/oracle';
import { useScrollToLocation } from '../../../hooks/scroll-to-location'; import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter'; import { useExplorerOracleFormMarketsQuery } from '../__generated__/OraclesForMarkets';
import { MarketLink } from '../../../components/links';
import { OracleLink } from '../../../components/links/oracle-link/oracle-link';
import { useState } from 'react';
import { MarketStateMapping } from '@vegaprotocol/types';
import type { MarketState } from '@vegaprotocol/types';
const cellSpacing = 'px-3';
const Oracles = () => { const Oracles = () => {
const { data, loading, error } = useExplorerOracleSpecsQuery(); const { data, loading, error } = useExplorerOracleFormMarketsQuery({
errorPolicy: 'ignore',
});
useDocumentTitle(['Oracles']); useDocumentTitle(['Oracles']);
useScrollToLocation(); useScrollToLocation();
const [hoveredOracle, setHoveredOracle] = useState('');
return ( return (
<section> <section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle> <RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
@@ -28,36 +38,148 @@ const Oracles = () => {
data.oracleSpecsConnection.edges?.length === 0 data.oracleSpecsConnection.edges?.length === 0
} }
> >
{data?.oracleSpecsConnection?.edges <table className="text-left">
? data.oracleSpecsConnection.edges.map((o) => { <thead>
const id = o?.node.dataSourceSpec.spec.id; <tr>
if (!id) { <th className={cellSpacing}>Market</th>
return null; <th className={cellSpacing}>Type</th>
} <th className={cellSpacing}>State</th>
<th className={cellSpacing}>Settlement</th>
<th className={cellSpacing}>Termination</th>
</tr>
</thead>
<tbody>
{data?.marketsConnection?.edges
? data.marketsConnection.edges.map((o) => {
let hasSeenOracleReports = false;
let settlementOracle = '-';
let settlementOracleStatus = '-';
let terminationOracle = '-';
let terminationOracleStatus = '-';
const dataConnection = o?.node.dataConnection; const id = o?.node.id;
if (!id) {
return null;
}
return ( if (
<div o.node.tradableInstrument.instrument.product.__typename ===
id={id} 'Future'
key={id} ) {
className="mb-10" settlementOracle =
data-testid="oracle-details" o.node.tradableInstrument.instrument.product
> .dataSourceSpecForSettlementData.id;
<OracleDetails terminationOracle =
id={id} o.node.tradableInstrument.instrument.product
dataSource={o?.node} .dataSourceSpecForTradingTermination.id;
dataConnection={dataConnection} settlementOracleStatus =
showBroadcasts={false} o.node.tradableInstrument.instrument.product
/> .dataSourceSpecForSettlementData.status;
<details> terminationOracleStatus =
<summary className="pointer">JSON</summary> o.node.tradableInstrument.instrument.product
<SyntaxHighlighter data={filter(o, ['__typename'])} /> .dataSourceSpecForTradingTermination.status;
</details> } else if (
</div> o.node.tradableInstrument.instrument.product.__typename ===
); 'Perpetual'
}) ) {
: null} settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.status;
}
const oracleInformationUnfiltered =
data?.oracleSpecsConnection?.edges?.map((e) =>
e && e.node ? e.node : undefined
) || [];
const oracleInformation = compact(oracleInformationUnfiltered)
.filter(
(o) =>
o.dataConnection.edges &&
o.dataConnection.edges.length > 0 &&
(o.dataSourceSpec.spec.id === settlementOracle ||
o.dataSourceSpec.spec.id === terminationOracle)
)
.at(0);
if (oracleInformation) {
hasSeenOracleReports = true;
}
const oracleList = `${settlementOracle} ${terminationOracle}`;
return (
<tr
id={id}
key={id}
className={
hoveredOracle.length > 0 &&
oracleList.indexOf(hoveredOracle) > -1
? 'bg-gray-100 dark:bg-gray-800'
: ''
}
data-testid="oracle-details"
data-oracles={oracleList}
>
<td className={cellSpacing}>
<MarketLink id={id} />
</td>
<td className={cellSpacing}>
{
o.node.tradableInstrument.instrument.product
.__typename
}
</td>
<td className={cellSpacing}>
{MarketStateMapping[o.node.state as MarketState]}
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === settlementOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={settlementOracle}
status={settlementOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() => setHoveredOracle(settlementOracle)}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === terminationOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={terminationOracle}
status={terminationOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() =>
setHoveredOracle(terminationOracle)
}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
</tr>
);
})
: null}
</tbody>
</table>
</AsyncRenderer> </AsyncRenderer>
</section> </section>
); );
@@ -40,10 +40,9 @@ export const Oracle = () => {
id={id || ''} id={id || ''}
dataSource={data?.oracleSpec} dataSource={data?.oracleSpec}
dataConnection={data?.oracleSpec.dataConnection} dataConnection={data?.oracleSpec.dataConnection}
showBroadcasts={true}
/> />
<details> <details className="mt-5 cursor-pointer">
<summary className="pointer">JSON</summary> <summary>JSON</summary>
<SyntaxHighlighter data={filter(data, ['__typename'])} /> <SyntaxHighlighter data={filter(data, ['__typename'])} />
</details> </details>
</div> </div>
@@ -0,0 +1,108 @@
import { BackgroundVideo } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
const RestrictedPage = () => {
const errorTitle = '451 Unavailable';
const errorMessage =
'Due to uncertainty about the legal and regulatory status of the content hosted on this site, it is not available to visitors in your jurisdiction.';
return (
<div>
<BackgroundVideo className="brightness-50" />
<div
className={classNames(
'max-w-[620px] p-2 mt-[10vh]',
'mx-auto my-0',
'antialiased text-white',
'overflow-hidden relative',
'flex flex-col gap-2'
)}
>
<div className="flex gap-4">
<div>{GHOST}</div>
<h1 className="text-[2.7rem] font-alpha calt break-words uppercase">
{errorTitle}
</h1>
</div>
<div className="text-sm mt-10 overflow-auto font-mono">
{errorMessage}
</div>
</div>
</div>
);
};
const GHOST = (
<svg
width="56"
height="85"
viewBox="0 0 150 234"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.43 93.6821L0 104.097V172.435H5.13793V106.2L14.0779 97.3247L10.43 93.6821Z"
fill="black"
/>
<path d="M105.328 18.521H7.70703V172.435H105.328V18.521Z" fill="white" />
<path d="M38.5364 64.6953H33.3984V69.8258H38.5364V64.6953Z" fill="black" />
<path d="M43.6731 69.8257H38.5352V74.9561H43.6731V69.8257Z" fill="black" />
<path d="M48.8098 74.9561H43.6719V80.0865H48.8098V74.9561Z" fill="black" />
<path d="M38.5364 74.9561H33.3984V80.0865H38.5364V74.9561Z" fill="black" />
<path d="M48.8098 64.6953H43.6719V69.8258H48.8098V64.6953Z" fill="black" />
<path d="M69.3606 64.6953H64.2227V69.8258H69.3606V64.6953Z" fill="black" />
<path d="M74.5012 69.8257H69.3633V74.9561H74.5012V69.8257Z" fill="black" />
<path d="M79.6379 74.9561H74.5V80.0865H79.6379V74.9561Z" fill="black" />
<path d="M69.3606 74.9561H64.2227V80.0865H69.3606V74.9561Z" fill="black" />
<path d="M79.6379 64.6953H74.5V69.8258H79.6379V64.6953Z" fill="black" />
<path d="M79.6398 90.3477H33.3984V95.4781H79.6398V90.3477Z" fill="black" />
<path d="M48.8098 172.435H43.6719V234H48.8098V172.435Z" fill="black" />
<path d="M69.3606 172.435H64.2227V234H69.3606V172.435Z" fill="black" />
<path
d="M129.87 0.00408026L92.0273 14.3682L111.195 64.7203L149.038 50.3562L129.87 0.00408026Z"
fill="#FF0081"
/>
<path
d="M108.565 21.7836L103.762 23.6064L105.587 28.4024L110.39 26.5795L108.565 21.7836Z"
fill="white"
/>
<path
d="M115.189 24.7596L110.387 26.583L112.213 31.3785L117.015 29.5551L115.189 24.7596Z"
fill="white"
/>
<path
d="M121.826 27.7348L117.023 29.5576L118.849 34.3536L123.652 32.5307L121.826 27.7348Z"
fill="white"
/>
<path
d="M128.447 30.7059L123.645 32.5293L125.471 37.3247L130.273 35.5014L128.447 30.7059Z"
fill="white"
/>
<path
d="M135.084 33.6854L130.281 35.5083L132.107 40.3043L136.91 38.4814L135.084 33.6854Z"
fill="white"
/>
<path
d="M124.799 21.1156L119.996 22.939L121.822 27.7344L126.625 25.911L124.799 21.1156Z"
fill="white"
/>
<path
d="M127.775 14.492L122.973 16.3154L124.799 21.1109L129.601 19.2875L127.775 14.492Z"
fill="white"
/>
<path
d="M118.838 34.3499L114.035 36.1733L115.861 40.9688L120.664 39.1454L118.838 34.3499Z"
fill="white"
/>
<path
d="M115.857 40.9691L111.055 42.7925L112.881 47.5879L117.683 45.7645L115.857 40.9691Z"
fill="white"
/>
<path
d="M129.938 52.3822L139.906 91.5276L100.703 101.429L101.988 106.406L146.174 95.2215L134.922 51.0996L129.938 52.3822Z"
fill="black"
/>
</svg>
);
export default RestrictedPage;
@@ -1,5 +1,6 @@
export const Routes = { export const Routes = {
HOME: '/', HOME: '/',
RESTRICTED: '/restricted',
TX: 'txs', TX: 'txs',
BLOCKS: 'blocks', BLOCKS: 'blocks',
PARTIES: 'parties', PARTIES: 'parties',
@@ -30,6 +30,7 @@ import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts'; import { PartyAccountsByAsset } from './parties/id/accounts';
import { Disclaimer } from './pages/disclaimer'; import { Disclaimer } from './pages/disclaimer';
import { FLAGS } from '@vegaprotocol/environment'; import { FLAGS } from '@vegaprotocol/environment';
import RestrictedPage from './restricted';
export type Navigable = { export type Navigable = {
path: string; path: string;
@@ -356,6 +357,14 @@ export const routerConfig: Route[] = [
...validators, ...validators,
], ],
}, },
{
path: Routes.RESTRICTED,
element: <RestrictedPage />,
handle: {
name: t('Restricted'),
text: t('Restricted'),
},
},
]; ];
export const router = createBrowserRouter(routerConfig); export const router = createBrowserRouter(routerConfig);
@@ -14,6 +14,10 @@ export interface BlockExplorerTransactionResult {
value: string; value: string;
}; };
error?: string; error?: string;
// These aren't strictly optional but are new in 0.73.0 so we need to make them optional
createdAt?: string;
version?: string;
pow?: string;
} }
export interface BlockExplorerTransactions { export interface BlockExplorerTransactions {
+670 -247
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -21,6 +21,12 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72 NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_REST_URL=http://localhost:3008/api/v2/ NX_VEGA_REST_URL=http://localhost:3008/api/v2/
NX_SUCCESSOR_MARKETS=true NX_SUCCESSOR_MARKETS=true
NX_PRODUCT_PERPETUALS=true
NX_REFERRALS=true
NX_UPDATE_MARKET_STATE=true
NX_GOVERNANCE_TRANSFERS=true
NX_VOLUME_DISCOUNTS=true
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
+4
View File
@@ -4,3 +4,7 @@ NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
NX_REFERRALS=true
NX_VOLUME_DISCOUNTS=true
+4
View File
@@ -4,3 +4,7 @@ NX_VEGA_URL=https://api.vega.community/graphql
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_PRODUCT_PERPETUALS=false
NX_UPDATE_MARKET_STATE=false
NX_REFERRALS=false
NX_VOLUME_DISCOUNTS=false
+4
View File
@@ -4,3 +4,7 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
NX_REFERRALS=true
NX_VOLUME_DISCOUNTS=true
@@ -1,21 +0,0 @@
{
"rationale": {
"title": "Add USDT Coin (USDT)",
"description": "Proposal to add USDT Coin (USDT) as an asset"
},
"terms": {
"newAsset": {
"changes": {
"name": "USDT Coin",
"symbol": "USDT",
"decimals": "18",
"quantum": "1",
"erc20": {
"contractAddress": "0xb404c51bbc10dcbe948077f18a4b8e553d160084"
}
}
},
"closingTimestamp": 1662374250,
"enactmentTimestamp": 1662460650
}
}
@@ -0,0 +1,15 @@
{
"rationale": {
"title": "Governance cancel transfer proposal",
"description": "Rejected cancel transfer proposal"
},
"terms": {
"cancelTransfer": {
"changes": {
"transferId": "invalid transfer id"
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -0,0 +1,138 @@
{
"rationale": {
"description": "## Summary\n\nThis proposal requests to list BTC PERPS Incentive as a market with USD-P as a settlement asset on the Vega Network as discussed in: https://community.vega.xyz/.\n\n## Rationale\n\n- BTC is the largest Crypto asset with the highest volume and Marketcap.\n- Given the price, 1 decimal places will be used for price due to the number of valid digits in asset price. \n- Position decimal places will be set to 4 considering the value per contract\n- USDT is chosen as settlement asset due to its stability.",
"title": "perpetual market proposal"
},
"terms": {
"closingTimestamp": 0,
"enactmentTimestamp": 0,
"newMarket": {
"changes": {
"instrument": {
"name": "Token test market",
"code": "TEST.24h",
"perpetual": {
"clampLowerBound": "0",
"clampUpperBound": "0",
"interestRate": "0",
"marginFundingFactor": "0.1",
"settlementAsset": "73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0",
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"external": {
"ethOracle": {
"address": "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43",
"abi": "[{\"inputs\":[],\"name\":\"latestAnswer\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
"method": "latestAnswer",
"normalisers": [
{
"name": "btc.price",
"expression": "$[0]"
}
],
"requiredConfirmations": 3,
"trigger": {
"timeTrigger": {
"every": 30
}
},
"filters": [
{
"key": {
"name": "btc.price",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": 8
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "0"
}
]
}
]
}
}
},
"dataSourceSpecForSettlementSchedule": {
"internal": {
"timeTrigger": {
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "0"
}
],
"triggers": [
{
"every": 1800
}
]
}
}
},
"dataSourceSpecBinding": {
"settlementDataProperty": "btc.price",
"settlementScheduleProperty": "vegaprotocol.builtin.timetrigger"
}
}
},
"metadata": [
"base:BTC",
"quote:USD-P",
"class:fx/crypto",
"quarterly",
"sector:defi",
"enactment:2023-06-15T14:00:00Z",
"settlement:2023-09-30T08:00:00Z"
],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "3600",
"probability": "0.9999",
"auctionExtension": "120"
},
{
"horizon": "14400",
"probability": "0.9999",
"auctionExtension": "180"
},
{
"horizon": "43200",
"probability": "0.9999",
"auctionExtension": "300"
}
]
},
"liquidityMonitoringParameters": {
"targetStakeParameters": {
"timeWindow": "3600",
"scalingFactor": 1
},
"triggeringRatio": "0.7",
"auctionExtension": "1"
},
"liquiditySlaParameters": {
"priceRange": "0.05",
"commitmentMinTimeFraction": "0.95",
"performanceHysteresisEpochs": 1,
"slaCompetitionFactor": "0.95"
},
"logNormal": {
"riskAversionParameter": 0.000001,
"tau": 0.0001140771161,
"params": {
"sigma": 1.5
}
},
"decimalPlaces": "1",
"positionDecimalPlaces": "4",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0.0"
}
}
}
}
@@ -0,0 +1,16 @@
{
"rationale": {
"title": "Market resume test",
"description": "E2E test for market resume proposal"
},
"terms": {
"updateMarketState": {
"changes": {
"marketId": "b33bb4157e12355db22e41f277ddd0c10104dec29a4d6960bbcb96d186c40cbd",
"updateType": "MARKET_STATE_UPDATE_TYPE_RESUME"
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -0,0 +1,16 @@
{
"rationale": {
"title": "Market suspended test",
"description": "E2E test for market suspended proposal"
},
"terms": {
"updateMarketState": {
"changes": {
"marketId": "",
"updateType": "MARKET_STATE_UPDATE_TYPE_SUSPEND"
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -0,0 +1,17 @@
{
"rationale": {
"title": "Market terminate test",
"description": "E2E test for market terminate proposal"
},
"terms": {
"updateMarketState": {
"changes": {
"marketId": "",
"updateType": "MARKET_STATE_UPDATE_TYPE_TERMINATE",
"price": "100"
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -1,85 +0,0 @@
{
"instrument": {
"code": "Token.24h",
"future": {
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
}
},
"dataSourceSpecForTradingTermination": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
}
]
}
]
}
}
},
"dataSourceSpecBinding": {
"settlementDataProperty": "prices.BTC.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.3
}
}
}
@@ -9,6 +9,7 @@ import {
createTenDigitUnixTimeStampForSpecifiedDays, createTenDigitUnixTimeStampForSpecifiedDays,
generateFreeFormProposalTitle, generateFreeFormProposalTitle,
getDateFormatForSpecifiedDays, getDateFormatForSpecifiedDays,
getProposalDetailsValue,
getProposalFromTitle, getProposalFromTitle,
getProposalInformationFromTable, getProposalInformationFromTable,
goToMakeNewProposal, goToMakeNewProposal,
@@ -26,12 +27,16 @@ import {
} from '../../../../governance-e2e/src/support/staking.functions'; } from '../../../../governance-e2e/src/support/staking.functions';
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
import { import {
depositAsset,
switchVegaWalletPubKey, switchVegaWalletPubKey,
vegaWalletSetSpecifiedApprovalAmount, vegaWalletSetSpecifiedApprovalAmount,
} from '../../support/wallet-functions'; } from '../../support/wallet-functions';
import type { testFreeformProposal } from '../../support/common-interfaces'; import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils'; import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions'; import {
createGovernanceTransferProposalTxBody,
createSuccessorMarketProposalTxBody,
} from '../../support/proposal.functions';
const proposalListItem = '[data-testid="proposals-list-item"]'; const proposalListItem = '[data-testid="proposals-list-item"]';
const participationNotMet = 'token-participation-not-met'; const participationNotMet = 'token-participation-not-met';
@@ -50,6 +55,8 @@ const openProposals = 'open-proposals';
const viewProposalButton = 'view-proposal-btn'; const viewProposalButton = 'view-proposal-btn';
const proposalTermsToggle = 'proposal-json-toggle'; const proposalTermsToggle = 'proposal-json-toggle';
const marketDataToggle = 'proposal-market-data-toggle'; const marketDataToggle = 'proposal-market-data-toggle';
const governanceTransferToggle = 'proposal-transfer-details';
const marketProposalType = 'proposal-type';
describe( describe(
'Governance flow for proposal details', 'Governance flow for proposal details',
@@ -58,6 +65,17 @@ describe(
before('connect wallets and set approval limit', function () { before('connect wallets and set approval limit', function () {
cy.visit('/'); cy.visit('/');
ethereumWalletConnect(); ethereumWalletConnect();
cy.createMarket();
navigateTo(navigation.proposals);
cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(viewProposalButton).click();
});
});
getProposalInformationFromTable('ID').invoke('text').as('parentMarketId');
}); });
beforeEach('visit proposals tab', function () { beforeEach('visit proposals tab', function () {
@@ -296,9 +314,6 @@ describe(
}); });
it('Able to see successor market details with new and updated values', function () { it('Able to see successor market details with new and updated values', function () {
cy.createMarket();
cy.reload();
waitForSpinner();
cy.getByTestId('closed-proposals').within(() => { cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market') cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
@@ -307,14 +322,9 @@ describe(
cy.getByTestId(viewProposalButton).click(); cy.getByTestId(viewProposalButton).click();
}); });
}); });
getProposalInformationFromTable('ID') cy.VegaWalletSubmitProposal(
.invoke('text') createSuccessorMarketProposalTxBody(this.parentMarketId)
.as('parentMarketId') );
.then(() => {
cy.VegaWalletSubmitProposal(
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
});
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.reload(); cy.reload();
getProposalFromTitle('Test successor market proposal details').within( getProposalFromTitle('Test successor market proposal details').within(
@@ -372,19 +382,221 @@ describe(
}); });
// 3003-PMAN-011 // 3003-PMAN-011
cy.get('.underline').contains('Parent Market ID').realHover(); cy.contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should( cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text', 'contain.text',
'The ID of the market this market succeeds.' 'The ID of the market this market succeeds.'
); );
cy.get('.underline') cy.contains('Insurance Pool Fraction').realMouseUp().realHover();
.contains('Insurance Pool Fraction')
.realMouseUp()
.realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should( cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text', 'contain.text',
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.' 'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
); );
}); });
it('Able to see perpetual market', function () {
const proposalPath =
'src/fixtures/proposals/new-market-perpetual-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('perpetual market proposal').within(() => {
cy.getByTestId(marketProposalType).should(
'have.text',
'New market - perpetual'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketDataToggle).click();
getProposalDetailsValue('Product Type').should(
'contain.text',
'Perpetual'
);
cy.getByTestId(marketProposalType).should(
'have.text',
'New market - perpetual'
);
// Liquidity SLA protocols
getProposalDetailsValue('Performance Hysteresis Epochs').should(
'contain.text',
'1'
);
getProposalDetailsValue('SLA Competition Factor').should(
'contain.text',
'95.00%'
);
getProposalDetailsValue('Epoch Length').should('contain.text', '5s');
getProposalDetailsValue('Non Performance Bond Penalty Max').should(
'contain.text',
'0.05'
);
getProposalDetailsValue('Stake To CCY Volume').should(
'contain.text',
'0.3'
);
getProposalDetailsValue('Min Probability Of Trading LP Orders').should(
'contain.text',
'1e-8'
);
});
it('Able to see suspended market proposal', function () {
const proposalPath = 'src/fixtures/proposals/suspend-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('Market suspended test').within(() => {
cy.getByTestId(marketProposalType).should(
'have.text',
'Suspend market'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'Suspend market');
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
.and('eq', this.parentMarketId);
});
});
it('Able to see resume market proposal', function () {
const proposalPath = 'src/fixtures/proposals/resume-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('Market resume test').within(() => {
cy.getByTestId(marketProposalType).should('have.text', 'Resume market');
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'Resume market');
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
.and('eq', this.parentMarketId);
});
});
it('Able to see terminate market proposal', function () {
const proposalPath = 'src/fixtures/proposals/terminate-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('Market terminate test').within(() => {
cy.getByTestId(marketProposalType).should(
'have.text',
'Terminate market'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should(
'have.text',
'Terminate market'
);
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
.and('eq', this.parentMarketId);
getProposalDetailsValue('Termination Price').should(
'contain.text',
'0.001 fUSDC'
);
});
});
it('Able to see governance transfer proposal', function () {
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
depositAsset(vegaAssetAddress, '1000', 18);
cy.getByTestId('currency-title', Cypress.env('txTimeout')).should(
'contain.text',
'Collateral'
);
cy.VegaWalletTopUpNetworkAccount('100');
cy.VegaWalletSubmitProposal(createGovernanceTransferProposalTxBody());
cy.reload();
getProposalFromTitle('Governance transfer proposal').within(() => {
cy.getByTestId(marketProposalType).should('have.text', 'New transfer');
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'New transfer');
cy.getByTestId(governanceTransferToggle).click();
cy.getByTestId('proposal-transfer-details-table').within(() => {
getProposalInformationFromTable('Source Type')
.invoke('text')
.and('eq', 'Network Treasury');
getProposalInformationFromTable('Destination')
.invoke('text')
.and('eq', Cypress.env('vegaWalletPublicKey'));
getProposalInformationFromTable('Asset')
.invoke('text')
.and('eq', 'VEGA');
getProposalInformationFromTable('Fraction Of Balance')
.invoke('text')
.and('eq', '50%');
getProposalInformationFromTable('Amount')
.invoke('text')
.and('eq', '100.00');
getProposalInformationFromTable('Transfer Type')
.invoke('text')
.and('eq', 'All or nothing');
getProposalInformationFromTable('Kind')
.invoke('text')
.and('eq', 'One off');
});
});
it(' Able to see cancel transfer proposal - rejected', function () {
const proposalPath = 'src/fixtures/proposals/cancel-transfer-raw.json';
const enactmentTimestamp =
createTenDigitUnixTimeStampForSpecifiedDays(11);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(10);
submitUniqueRawProposal({
proposalBody: proposalPath,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
submit: false,
});
cy.getByTestId('proposal-submit').should('be.visible').click();
cy.getByTestId('dialog-title').should('have.text', 'Proposal rejected');
cy.getByTestId('icon-cross').last().click();
navigateTo(navigation.proposals);
cy.get('[href="/proposals/rejected"]').click();
getProposalFromTitle('Governance cancel transfer proposal').within(() => {
cy.getByTestId(marketProposalType).should(
'have.text',
'CancelTransfer'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'Cancel transfer');
getProposalInformationFromTable('Error details')
.invoke('text')
.and('eq', 'Governance transfer invalid transfer id not found');
getProposalInformationFromTable('transferId')
.invoke('text')
.and('eq', 'invalid transfer id');
});
} }
); );
@@ -65,7 +65,10 @@ context(
cy.getByTestId(viewProposalButton).click(); cy.getByTestId(viewProposalButton).click();
}); });
}); });
cy.getByTestId('proposal-type').should('have.text', 'New market'); cy.getByTestId('proposal-type').should(
'have.text',
'New market - future'
);
cy.getByTestId(proposalStatus).should('have.text', 'Enacted'); cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
cy.getByTestId(votesTable).within(() => { cy.getByTestId(votesTable).within(() => {
cy.contains('Voting has ended.').should('be.visible'); cy.contains('Voting has ended.').should('be.visible');
@@ -89,11 +89,12 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
it('Newly created proposals list - shows title and portion of summary', function () { it('Newly created proposals list - shows title and portion of summary', function () {
const proposalPath = 'src/fixtures/proposals/new-market-raw.json'; const proposalPath = 'src/fixtures/proposals/new-market-raw.json';
const proposalTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3); const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({ submitUniqueRawProposal({
proposalBody: proposalPath, proposalBody: proposalPath,
enactmentTimestamp: proposalTimestamp, enactmentTimestamp: enactmentTimestamp,
closingTimestamp: proposalTimestamp, closingTimestamp: closingTimestamp,
}); // 3001-VOTE-052 }); // 3001-VOTE-052
// 3001-VOTE-008 // 3001-VOTE-008
// 3001-VOTE-034 // 3001-VOTE-034
@@ -310,7 +310,9 @@ context(
closeStakingDialog(); closeStakingDialog();
navigateTo(navigation.validators); navigateTo(navigation.validators);
clickOnValidatorFromList(0); clickOnValidatorFromList(0);
cy.getByTestId(stakeRemoveStakeRadioButton, txTimeout).click(); cy.getByTestId(stakeRemoveStakeRadioButton, txTimeout).click({
force: true,
});
cy.getByTestId(stakeTokenAmountInputBox).type('-0.1'); cy.getByTestId(stakeTokenAmountInputBox).type('-0.1');
cy.contains('Waiting for next epoch to start', epochTimeout); cy.contains('Waiting for next epoch to start', epochTimeout);
cy.getByTestId(stakeTokenSubmitButton) cy.getByTestId(stakeTokenSubmitButton)
@@ -331,7 +333,7 @@ context(
closeStakingDialog(); closeStakingDialog();
navigateTo(navigation.validators); navigateTo(navigation.validators);
clickOnValidatorFromList(0); clickOnValidatorFromList(0);
cy.getByTestId(stakeRemoveStakeRadioButton).click(); cy.getByTestId(stakeRemoveStakeRadioButton).click({ force: true });
cy.getByTestId(stakeTokenAmountInputBox).type('4'); cy.getByTestId(stakeTokenAmountInputBox).type('4');
cy.contains('Waiting for next epoch to start', epochTimeout); cy.contains('Waiting for next epoch to start', epochTimeout);
cy.getByTestId(stakeTokenSubmitButton) cy.getByTestId(stakeTokenSubmitButton)
@@ -422,7 +424,7 @@ context(
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%'); validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
}); });
it('Associating wallet tokens - when some already staked - auto stakes tokens to staked validator', function () { it.skip('Associating wallet tokens - when some already staked - auto stakes tokens to staked validator', function () {
// 1002-STKE-004 // 1002-STKE-004
stakingPageAssociateTokens('3'); stakingPageAssociateTokens('3');
verifyUnstakedBalance(3.0); verifyUnstakedBalance(3.0);
@@ -436,7 +438,7 @@ context(
verifyStakedBalance(7.0); verifyStakedBalance(7.0);
}); });
it('Associating vesting contract tokens - when some already staked - auto stakes tokens to staked validator', function () { it.skip('Associating vesting contract tokens - when some already staked - auto stakes tokens to staked validator', function () {
// 1002-STKE-004 // 1002-STKE-004
stakingPageAssociateTokens('3', { type: 'contract' }); stakingPageAssociateTokens('3', { type: 'contract' });
verifyUnstakedBalance(3.0); verifyUnstakedBalance(3.0);
@@ -450,7 +452,7 @@ context(
verifyStakedBalance(7.0); verifyStakedBalance(7.0);
}); });
it('Associating vesting contract tokens - when wallet tokens already staked - auto stakes tokens to staked validator', function () { it.skip('Associating vesting contract tokens - when wallet tokens already staked - auto stakes tokens to staked validator', function () {
// 1002-STKE-004 // 1002-STKE-004
stakingPageAssociateTokens('3', { type: 'wallet' }); stakingPageAssociateTokens('3', { type: 'wallet' });
verifyUnstakedBalance(3.0); verifyUnstakedBalance(3.0);
@@ -464,7 +466,7 @@ context(
verifyStakedBalance(7.0); verifyStakedBalance(7.0);
}); });
it('Associating tokens - with multiple validators already staked - auto stakes to staked validators - abiding by existing stake ratio', function () { it.skip('Associating tokens - with multiple validators already staked - auto stakes to staked validators - abiding by existing stake ratio', function () {
// 1002-STKE-004 // 1002-STKE-004
stakingPageAssociateTokens('6'); stakingPageAssociateTokens('6');
verifyUnstakedBalance(6.0); verifyUnstakedBalance(6.0);
@@ -156,7 +156,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.getByTestId('subscription-cell').should('have.text', 'Yes'); cy.getByTestId('subscription-cell').should('have.text', 'Yes');
}); });
cy.getByTestId('connect').should('be.disabled'); cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('node-url-custom').click(); cy.getByTestId('node-url-custom').click({ force: true });
cy.get('input').should('exist'); cy.get('input').should('exist');
cy.getByTestId('connect').should('be.disabled'); cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('icon-cross').click(); cy.getByTestId('icon-cross').click();
@@ -74,25 +74,12 @@ context(
}); });
it('should be able to see a working link for - find out more about Vega governance', function () { it('should be able to see a working link for - find out more about Vega governance', function () {
// 3001-VOTE-001 // 3001-VOTE-001 // 3002-PROP-001
cy.getByTestId(proposalDocumentationLink) cy.getByTestId(proposalDocumentationLink)
.should('be.visible') .should('be.visible')
.and('have.text', 'Find out more about Vega governance') .and('have.text', 'Find out more about Vega governance')
.and('have.attr', 'href') .and('have.attr', 'href')
.and('equal', governanceDocsUrl); .and('equal', governanceDocsUrl);
// 3002-PROP-001
cy.request(governanceDocsUrl)
.its('body')
.then((body) => {
if (!body.includes('Govern the network')) {
assert.include(
body,
'Govern the network',
`Checking that governance link destination includes 'Govern the network' text`
);
}
});
}); });
// 3007-PNE-021 // 3007-PNE-021
@@ -11,7 +11,6 @@ import {
waitForBeginningOfEpoch, waitForBeginningOfEpoch,
} from '../../support/staking.functions'; } from '../../support/staking.functions';
import { previousEpochData } from '../../fixtures/mocks/previous-epoch'; import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const guideLink = 'staking-guide-link'; const guideLink = 'staking-guide-link';
const validatorTitle = 'validator-node-title'; const validatorTitle = 'validator-node-title';
@@ -38,17 +37,11 @@ const txTimeout = Cypress.env('txTimeout');
context('Validators Page - verify elements on page', function () { context('Validators Page - verify elements on page', function () {
before('navigate to validators page', () => { before('navigate to validators page', () => {
cy.mockGQL((req) => { cy.mockChainId();
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/validators'); cy.visit('/validators');
}); });
beforeEach(() => { beforeEach(() => {
cy.mockGQL((req) => { cy.mockChainId();
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
}); });
describe('with wallets disconnected', { tags: '@smoke' }, function () { describe('with wallets disconnected', { tags: '@smoke' }, function () {
@@ -189,10 +182,7 @@ context('Validators Page - verify elements on page', function () {
{ tags: '@smoke' }, { tags: '@smoke' },
function () { function () {
before('connect wallets and click on validator', function () { before('connect wallets and click on validator', function () {
cy.mockGQL((req) => { cy.mockChainId();
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/validators'); cy.visit('/validators');
cy.connectVegaWallet(); cy.connectVegaWallet();
clickOnValidatorFromList(0); clickOnValidatorFromList(0);
@@ -4,8 +4,6 @@ import {
vegaWalletFaucetAssetsWithoutCheck, vegaWalletFaucetAssetsWithoutCheck,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-functions'; } from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const walletContainer = 'aside [data-testid="vega-wallet"]'; const walletContainer = 'aside [data-testid="vega-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1'; const walletHeader = '[data-testid="wallet-header"] h1';
@@ -88,10 +86,7 @@ context(
describe('when vega wallet connected', function () { describe('when vega wallet connected', function () {
before('connect vega wallet', function () { before('connect vega wallet', function () {
cy.mockGQL((req) => { cy.mockChainId();
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/'); cy.visit('/');
cy.wait('@ChainId'); cy.wait('@ChainId');
cy.connectVegaWallet(); cy.connectVegaWallet();
@@ -276,10 +271,7 @@ context(
]; ];
before('faucet assets to connected vega wallet', function () { before('faucet assets to connected vega wallet', function () {
cy.mockGQL((req) => { cy.mockChainId();
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
for (const { id, amount } of assets) { for (const { id, amount } of assets) {
vegaWalletFaucetAssetsWithoutCheck(id, amount, vegaWalletPublicKey); vegaWalletFaucetAssetsWithoutCheck(id, amount, vegaWalletPublicKey);
} }
@@ -54,6 +54,7 @@ export function submitUniqueRawProposal(proposalFields: {
proposalBody?: string; proposalBody?: string;
proposalTitle?: string; proposalTitle?: string;
proposalDescription?: string; proposalDescription?: string;
updateMarketId?: string;
closingTimestamp?: number; closingTimestamp?: number;
enactmentTimestamp?: number; enactmentTimestamp?: number;
submit?: boolean; submit?: boolean;
@@ -71,6 +72,10 @@ export function submitUniqueRawProposal(proposalFields: {
if (proposalFields.proposalDescription) { if (proposalFields.proposalDescription) {
rawProposal.rationale.description = proposalFields.proposalDescription; rawProposal.rationale.description = proposalFields.proposalDescription;
} }
if (proposalFields.updateMarketId) {
rawProposal.terms.updateMarketState.changes.marketId =
proposalFields.updateMarketId;
}
if (proposalFields.closingTimestamp) { if (proposalFields.closingTimestamp) {
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp; rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
} else if ( } else if (
@@ -232,21 +237,25 @@ export function getDownloadedProposalJsonPath(proposalType: string) {
return filepath; return filepath;
} }
export function getProposalDetailsValue(RowName: string) {
return cy
.contains(RowName)
.parentsUntil(proposalInformationTableRows)
.parent()
.first();
}
export function validateProposalDetailsDiff( export function validateProposalDetailsDiff(
RowName: string, RowName: string,
changeType: proposalChangeType, changeType: proposalChangeType,
newValue: string, newValue: string,
oldValue?: string oldValue?: string
) { ) {
cy.contains(RowName) getProposalDetailsValue(RowName).within(() => {
.parentsUntil(proposalInformationTableRows) cy.contains(changeType).should('be.visible');
.parent() cy.contains(newValue).should('be.visible');
.first() if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
.within(() => { });
cy.contains(changeType).should('be.visible');
cy.contains(newValue).should('be.visible');
if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
});
} }
function getFormattedTime() { function getFormattedTime() {

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