Compare commits

...
Author SHA1 Message Date
asiaznik cdbef93ab5 fix(environment): missing block height in node switcher 2023-11-07 17:18:14 +01:00
ArtandMatthew Russell 9233778e85 feat(governance,accounts): vesting and vested accound in wallet card, fix transfer form to account (#5206)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-11-06 12:42:42 -08: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
612 changed files with 19539 additions and 9795 deletions
@@ -82,3 +82,38 @@ jobs:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
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:
- release/*
- develop
- main
pull_request:
types:
- opened
- ready_for_review
- reopened
- edited
- synchronize
jobs:
node-modules:
@@ -44,13 +44,6 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
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:
timeout-minutes: 20
needs: node-modules
@@ -177,25 +170,48 @@ jobs:
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
preview_tools: ${{ env.PREVIEW_TOOLS }}
# console-e2e:
# needs: build-sources
# name: '(CI) console python'
# uses: ./.github/workflows/console-test-run.yml
# secrets: inherit
# if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
# with:
# github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
check-e2e-needed:
runs-on: ubuntu-latest
needs: build-sources
name: '(CI) check if e2e needed'
outputs:
run-tests: ${{ steps.check-test.outputs.e2e-needed }}
steps:
- 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:
needs: build-sources
needs: [build-sources, check-e2e-needed]
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
secrets: inherit
with:
projects: ${{ needs.build-sources.outputs.projects-e2e }}
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:
needs: build-sources
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
env:
VEGA_VERSION: v0.72.14
on:
workflow_call:
inputs:
github-sha:
required: true
type: string
workflow_dispatch:
inputs:
console-test-branch:
type: choice
description: 'main: v0.72.14, develop: v0.73.0-preview7'
options:
- main
- develop
jobs:
run-tests:
name: run-tests
runs-on: console-test
timeout-minutes: 40
create-docker-image:
name: Create docker image for console-test
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
#----------------------------------------------
# check-out frontend-monorepo
#----------------------------------------------
- name: Checkout console test repo
- name: Checkout frontend-monorepo
uses: actions/checkout@v3
with:
ref: ${{ inputs.github-sha }}
ref: ${{ inputs.github-sha || github.sha }}
#----------------------------------------------
# cache node modules
#----------------------------------------------
- name: setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: yarn
- name: Cache node modules
id: cache
uses: actions/cache@v3
@@ -44,23 +55,105 @@ jobs:
#----------------------------------------------
# build trading
#----------------------------------------------
- name: Build affected spec
- name: Build trading app
run: |
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: |
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
sleep 5
docker ps
#----------------------------------------------
# check if container persists between runs
#----------------------------------------------
- name: Check server
echo ${{ steps.docker_build.outputs.digest }}
echo ${{ steps.docker_build.outputs.imageid }}
- name: Upload docker image for console-test usage
uses: actions/upload-artifact@v3
with:
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: |
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
#----------------------------------------------
@@ -68,52 +161,55 @@ jobs:
uses: actions/checkout@v3
with:
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
working-directory: ./console-test
run: poetry install --no-interaction --no-root
#----------------------------------------------
# find vega binaries path
#----------------------------------------------
- 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
# install vega binaries
#----------------------------------------------
- name: Install vega binaries
working-directory: ./console-test
if: steps.vega_binaries_cache.outputs.cache-hit != 'true'
run: poetry run python -m vega_sim.tools.load_binaries --force --version $VEGA_VERSION
run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }}
#----------------------------------------------
# install playwright
#----------------------------------------------
- name: install playwright
run: poetry run playwright install --with-deps chromium
working-directory: ./console-test
#----------------------------------------------
# run tests
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
ls -al console-test
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
#----------------------------------------------
# upload traces
#----------------------------------------------
@@ -124,3 +220,13 @@ jobs:
name: playwright-trace
path: ./traces/
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
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:
strategy:
fail-fast: false
matrix:
project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }}
runs-on: self-hosted-runner
needs: runner-choice
runs-on: ${{ needs.runner-choice.outputs.runner }}
timeout-minutes: 120
steps:
# Checks if skip cache was requested
@@ -63,6 +85,7 @@ jobs:
- name: Run Vegacapsule network and Vega wallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
timeout-minutes: 10
######
## Run some tests
+11 -11
View File
@@ -2,7 +2,12 @@
name: Verify PR title
on:
workflow_call:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
jobs:
lint_pr:
@@ -11,21 +16,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
node-version: 16
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: |
rm package.json
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
- name: Check PR title
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_S3_RELEASE=false >> $GITHUB_ENV
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
echo IS_MAIN_IMAGE=false >> $GITHUB_ENV
- 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: |
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
if: ${{ github.event_name == 'pull_request' }}
run: |
@@ -57,7 +63,7 @@ jobs:
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- 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: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV
@@ -81,7 +87,7 @@ jobs:
- name: Log in to the Container registry (docker hub)
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:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -179,7 +185,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
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:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -189,7 +195,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
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)
uses: docker/build-push-action@v3
@@ -216,7 +222,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
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
- name: Publish dist to s3
-1
View File
@@ -1,3 +1,2 @@
* @vegaprotocol/frontend
* @vegaprotocol/frontend-qa
*.graphql @vegaprotocol/core
@@ -36,10 +36,9 @@ context('Market page', { tags: '@regression' }, function () {
it('Able to go to market details page', function () {
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.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('Market Decimal Places', '5');
cy.validate_element_from_table('Position Decimal Places', '5');
@@ -87,15 +86,13 @@ context('Market page', { tags: '@regression' }, function () {
// Liquidity
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('Market Value Proxy', '0.00 fUSDC');
// Liquidity price range
cy.validate_element_from_table(
'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('Highest Price', '0.00 fUSDC');
cy.getByTestId('oracle-spec-links')
.should('have.attr', 'href')
.and(
@@ -144,11 +141,8 @@ context('Market page', { tags: '@regression' }, function () {
.as('successorMarketId');
cy.contains('Token test market').click();
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_proposal_change_type('Time Window', 'Added');
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.getByTestId(successionLineItem)
@@ -40,7 +40,7 @@ context.skip('Node switcher', { tags: '@regression' }, function () {
const errorTypeTxt = 'Error: invalid 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.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,
insurancePoolFraction: '0.75',
},
liquiditySlaParameters: {
priceRange: '0.95',
commitmentMinTimeFraction: '0.5',
performanceHysteresisEpochs: 2,
slaCompetitionFactor: '0.75',
},
},
},
closingTimestamp,
+1 -1
View File
@@ -77,7 +77,7 @@
"executor": "nx:run-commands",
"options": {
"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 {
AppFailure,
NetworkLoader,
NodeFailure,
NodeGuard,
NodeSwitcherDialog,
useEnvironment,
@@ -31,7 +31,7 @@ function App() {
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<NodeGuard
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}>
<RouterProvider router={router} fallbackElement={splashLoading} />
@@ -4,7 +4,7 @@ import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
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 { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
@@ -1,5 +1,6 @@
export type HashProps = {
text: string;
truncate?: boolean;
};
/**
@@ -7,10 +8,16 @@ export type HashProps = {
* are broken when they need to wrap. This will remove the need
* for a lot of the overflow scrolling that currently exists.
*/
const Hash = ({ text }: HashProps) => {
const Hash = ({ text, truncate = false }: HashProps) => {
const h = truncate ? text.slice(0, 6) : text;
return (
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
{text}
<code
title={text}
className="break-all font-mono"
style={{ wordWrap: 'break-word' }}
>
{h}
</code>
);
};
@@ -3,6 +3,7 @@ query ExplorerMarket($id: ID!) {
id
decimalPlaces
positionDecimalPlaces
state
tradableInstrument {
instrument {
name
@@ -22,6 +23,5 @@ query ExplorerMarket($id: ID!) {
}
}
}
state
}
}
@@ -17,6 +17,7 @@ export const ExplorerMarketDocument = gql`
id
decimalPlaces
positionDecimalPlaces
state
tradableInstrument {
instrument {
name
@@ -36,7 +37,6 @@ export const ExplorerMarketDocument = gql`
}
}
}
state
}
}
`;
@@ -63,6 +63,9 @@ describe('Market link component', () => {
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
},
},
},
@@ -23,6 +23,7 @@ const MarketLink = ({
}: MarketLinkProps) => {
const { data, error, loading } = useExplorerMarketQuery({
variables: { id },
fetchPolicy: 'cache-first',
});
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 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>> & {
// The Oracle ID
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 (
<Link
className="underline font-mono"
{...props}
to={`/${Routes.ORACLES}/${id}`}
>
<Hash text={id} />
</Link>
<Tooltip description={description}>
<Link
className={`pl-2 pr-2 font-mono dark:text-black ${bgColour} rounded-sm border-solid border-2 relative`}
{...props}
to={`/${Routes.ORACLES}/${id}`}
data-status={getStatusString(status)}
>
<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 type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
PriceMonitoringBoundsInfoPanel,
SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData,
@@ -15,7 +18,6 @@ import {
OracleInfoPanel,
RiskFactorsInfoPanel,
RiskModelInfoPanel,
RiskParametersInfoPanel,
SettlementAssetInfoPanel,
} from '@vegaprotocol/markets';
import { MarketInfoTable } from '@vegaprotocol/markets';
@@ -67,8 +69,8 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<MetadataInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk model')}</h2>
<RiskModelInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk parameters')}</h2>
<RiskParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Margin scaling factors')}</h2>
<MarginScalingFactorsPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
@@ -94,6 +96,10 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<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>
<LiquidityInfoPanel market={market} />
{showTwoOracles ? (
@@ -4,7 +4,7 @@ import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { ColDef } from 'ag-grid-community';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueGetterParams,
@@ -74,6 +74,9 @@ function renderExistingAmend(
product: {
__typename: 'Future',
quoteName: '123',
settlementAsset: {
decimals: 8,
},
},
},
},
@@ -124,6 +127,9 @@ function renderExistingAmend(
product: {
__typename: 'Future',
quoteName: '123',
settlementAsset: {
decimals: 8,
},
},
},
},
@@ -152,6 +158,9 @@ function renderExistingAmend(
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
},
},
},
@@ -2,7 +2,7 @@ import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
@@ -105,7 +105,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
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
threshold={requiredMajorityPercentage}
progress={yesPercentage}
@@ -22,5 +22,9 @@ export const TimeAgo = ({ date, ...props }: TimeAgoProps) => {
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 type { components } from '../../../../../types/explorer';
import { TableCell, TableRow } from '../../../table';
import type { VegaPeggedReference } from '../liquidity-provision/liquidity-provision-details';
import { Side, PeggedReferenceMapping } from '@vegaprotocol/types';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
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 {
offset: string;
reference: VegaPeggedReference;
@@ -33,6 +33,14 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_HOLDING: 'Holding',
ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution',
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 {
@@ -1,10 +1,50 @@
import { t } from '@vegaprotocol/i18n';
import { TxDetailsShared } from './shared/tx-details-shared';
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 { 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 {
txData: BlockExplorerTransactionResult | undefined;
@@ -16,7 +16,6 @@ import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation';
import { TxDetailsUndelegate } from './tx-undelegation';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission';
@@ -28,6 +27,7 @@ import { TxDetailsStateVariable } from './tx-state-variable-proposal';
import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer';
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -107,7 +107,7 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsWithdrawSubmission;
case 'Liquidity Provision Order':
return TxDetailsLiquiditySubmission;
case 'Amend LiquidityProvision Order':
case 'Amend Liquidity Provision Order':
return TxDetailsLiquidityAmendment;
case 'Cancel LiquidityProvision Order':
return TxDetailsLiquidityCancellation;
@@ -5,7 +5,6 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
@@ -40,40 +39,32 @@ export const TxDetailsLiquidityAmendment = ({
: '-';
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
{amendment.commitmentAmount ? (
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
<PriceInMarket
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
{amendment.commitmentAmount ? (
<TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>
<PriceInMarket
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
) : null}
{amendment.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={amendment} />
</>
) : null}
{amendment.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};
@@ -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 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 { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
export type LiquiditySubmission =
components['schemas']['v1LiquidityProvisionSubmission'];
interface TxDetailsLiquiditySubmissionProps {
interface TxDetailsLiquidityAmendmentProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Someone cancelled an order
* An existing liquidity order is being created.
*/
export const TxDetailsLiquiditySubmission = ({
txData,
pubKey,
blockData,
}: TxDetailsLiquiditySubmissionProps) => {
}: TxDetailsLiquidityAmendmentProps) => {
if (!txData || !txData.command.liquidityProvisionSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
@@ -39,40 +38,38 @@ export const TxDetailsLiquiditySubmission = ({
: '-';
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
{submission.commitmentAmount ? (
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
<PriceInMarket
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
{submission.commitmentAmount ? (
<TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>
<PriceInMarket
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
) : null}
{submission.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={submission} />
</>
) : null}
{submission.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
{submission.reference ? (
<TableRow modifier="bordered">
<TableCell>{t('Reference')}</TableCell>
<TableCell>{submission.reference}</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};
@@ -12,6 +12,7 @@ import {
SPECIAL_CASE_NETWORK,
SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
type Transfer = components['schemas']['commandsv1Transfer'];
@@ -63,10 +64,16 @@ export const TxDetailsTransfer = ({
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered">
<TableRow modifier="bordered" data-testid="type">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{getTypeLabelForTransfer(transfer)}</TableCell>
</TableRow>
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -74,7 +81,7 @@ export const TxDetailsTransfer = ({
hideTypeRow={true}
/>
{from ? (
<TableRow modifier="bordered">
<TableRow modifier="bordered" data-testid="from">
<TableCell>{t('From')}</TableCell>
<TableCell>
<PartyLink id={from} />
@@ -82,7 +89,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
{transfer.to ? (
<TableRow modifier="bordered">
<TableRow modifier="bordered" data-testid="to">
<TableCell>{t('To')}</TableCell>
<TableCell>
<PartyLink id={transfer.to} />
@@ -90,7 +97,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
{transfer.asset && transfer.amount ? (
<TableRow modifier="bordered">
<TableRow modifier="bordered" data-testid="amount">
<TableCell>{t('Amount')}</TableCell>
<TableCell>
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
@@ -18,11 +18,13 @@ import { FilterLabel } from './tx-filter-label';
export type FilterOption =
| 'Amend LiquidityProvision Order'
| 'Amend Order'
| 'Apply Referral Code'
| 'Batch Market Instructions'
| 'Cancel LiquidityProvision Order'
| 'Cancel Order'
| 'Cancel Transfer Funds'
| 'Chain Event'
| 'Create Referral Set'
| 'Delegate'
| 'Ethereum Key Rotate Submission'
| 'Issue Signatures'
@@ -40,6 +42,7 @@ export type FilterOption =
| 'Submit Order'
| 'Transfer Funds'
| 'Undelegate'
| 'Update Referral Set'
| 'Validator Heartbeat'
| 'Vote on Proposal'
| 'Withdraw';
@@ -104,10 +104,30 @@ export function getLabelForProposal(
}
} else if (proposal.terms?.updateMarket) {
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) {
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) {
return t('Proposal: Freeform');
} else if (proposal.terms?.newTransfer) {
return t('Proposal: Transfer');
} else if (proposal.terms?.cancelTransfer) {
return t('Proposal: Transfer cancel');
} else {
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 {
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'];
describe('TX: Transfer: getLabelForTransfer', () => {
@@ -56,3 +65,70 @@ describe('TX: Transfer: getLabelForTransfer', () => {
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();
});
it('renders data correctly', () => {
it('renders data even with missing time', () => {
render(
<MockedProvider>
<MemoryRouter>
@@ -105,5 +105,33 @@ describe('Txs infinite list item', () => {
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')).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 type { Screen } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
import { TimeAgo } from '../time-ago';
const DEFAULT_TRUNCATE_LENGTH = 7;
@@ -32,6 +33,7 @@ export const TxsInfiniteListItem = ({
type,
block,
command,
createdAt,
}: Partial<BlockExplorerTransactionResult>) => {
const { screenSize } = useScreenDimensions();
const idTruncateLength = useMemo(
@@ -85,6 +87,11 @@ export const TxsInfiniteListItem = ({
endChars={5}
/>
</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>
);
};
@@ -3,6 +3,7 @@ import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import EmptyList from '../empty-list/empty-list';
import { Loader } from '@vegaprotocol/ui-toolkit';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
interface TxsInfiniteListProps {
hasMoreTxs: boolean;
@@ -19,7 +20,16 @@ interface 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 (
<TxsInfiniteListItem
type={type}
@@ -29,6 +39,7 @@ const Item = ({ tx }: ItemProps) => {
hash={hash}
block={block}
index={blockIndex}
createdAt={createdAt}
/>
);
};
@@ -39,6 +50,7 @@ export const TxsInfiniteList = ({
className,
hasFilters = false,
}: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
if (!txs || txs.length === 0) {
if (!areTxsLoading) {
return (
@@ -66,6 +78,9 @@ export const TxsInfiniteList = ({
<th>{t('Type')}</th>
<th className="text-left">{t('From')}</th>
<th>{t('Block')}</th>
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
<th>{t('Time')}</th>
)}
</tr>
</thead>
<tbody>
@@ -43,7 +43,7 @@ export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
url.searchParams.append('first', count);
url.searchParams.append('after', params.after);
} else {
url.searchParams.append('first', count);
url.searchParams.append('last', count);
}
// Hacky fix for param as array
@@ -6,7 +6,7 @@ describe('getTxsDataUrl', () => {
count: 10,
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);
});
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
baseUrl: 'https://example.com/transactions',
};
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);
});
+1 -1
View File
@@ -133,7 +133,7 @@ export const ErrorBoundary = () => {
);
};
const GHOST = (
export const GHOST = (
<svg
width="56"
height="85"
@@ -36,30 +36,50 @@ const PERCENTAGE_PARAMS = [
'governance.proposal.updateNetParam.requiredMajority',
'governance.proposal.updateNetParam.requiredParticipation',
'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',
];
'referralProgram.maxReferralRewardFactor',
'referralProgram.maxReferralDiscountFactor',
'referralProgram.maxReferralRewardProportion',
].map((p) => p.toLowerCase());
const BIG_NUMBER_PARAMS = [
'spam.protection.delegation.min.tokens',
'validators.delegation.minAmount',
'governance.proposal.transfer.maxAmount',
'reward.staking.delegation.minimumValidatorStake',
'reward.staking.delegation.maxPayoutPerParticipant',
'reward.staking.delegation.maxPayoutPerEpoch',
'spam.protection.voting.min.tokens',
'spam.protection.proposal.min.tokens',
'governance.proposal.transfer.minVoterBalance',
'governance.proposal.freeform.minProposerBalance',
'governance.proposal.updateNetParam.minVoterBalance',
'governance.proposal.updateMarket.minVoterBalance',
'governance.proposal.asset.minVoterBalance',
'governance.proposal.updateNetParam.minProposerBalance',
'governance.proposal.freeform.minVoterBalance',
'spam.protection.proposal.min.tokens',
'governance.proposal.updateMarket.minProposerBalance',
'governance.proposal.asset.minProposerBalance',
'governance.proposal.transfer.minProposerBalance',
'governance.proposal.market.minProposerBalance',
'governance.proposal.market.minVoterBalance',
'governance.proposal.updateAsset.minProposerBalance',
'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 = (
group: GroupedParams,
@@ -131,12 +151,12 @@ export const NetworkParameterRow = ({
<div className="pb-2">
<SyntaxHighlighter data={JSON.parse(value)} />
</div>
) : BIG_NUMBER_PARAMS.includes(key.toLowerCase()) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key.toLowerCase()) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : isNaN(Number(value)) ? (
value
) : BIG_NUMBER_PARAMS.includes(key) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : (
formatNumber(Number(value), 4)
)}
@@ -1,5 +1,5 @@
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection {
dataConnection(pagination: { first: 30 }) {
edges {
node {
externalData {
@@ -45,10 +45,51 @@ fragment ExplorerOracleDataSource on OracleSpec {
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
}
}
... on DataSourceDefinitionExternal {
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 {
signers {
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 {
oracleSpecsConnection(pagination: { first: 50 }) {
oracleSpecsConnection(pagination: { first: 30 }) {
pageInfo {
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 {
id
state
tradableInstrument {
instrument {
product {
... on Future {
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForTradingTermination {
id
}
...ExplorerOracleFuture
}
... on Perpetual {
dataSourceSpecForSettlementData {
id
...ExplorerOraclePerpetual
}
}
}
}
}
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;
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 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<{
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`
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection {
dataConnection(pagination: {first: 30}) {
edges {
node {
externalData {
@@ -68,10 +68,51 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
}
}
... on DataSourceDefinitionExternal {
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 {
signers {
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}`;
export const ExplorerOracleSpecsDocument = gql`
query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: {first: 50}) {
oracleSpecsConnection(pagination: {first: 30}) {
pageInfo {
hasNextPage
}
@@ -3,33 +3,106 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
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 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`
fragment ExplorerOracleForMarketsMarket on Market {
id
state
tradableInstrument {
instrument {
product {
... on Future {
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForTradingTermination {
id
}
...ExplorerOracleFuture
}
... on Perpetual {
dataSourceSpecForSettlementData {
id
...ExplorerOraclePerpetual
}
}
}
}
}
${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__
@@ -51,15 +51,26 @@ describe('Oracle Data view', () => {
{
node: {
externalData: {
__typename: 'ExternalData',
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 { TimeAgo } from '../../../components/time-ago';
import { t } from '@vegaprotocol/i18n';
const cellSpacing = 'px-3';
interface OracleDataTypeProps {
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) {
if (!data || !data.edges?.length || data.edges.length > 1) {
if (!data || !data.edges?.length) {
return null;
}
return (
<details data-testid="oracle-data">
<summary>{t('Broadcast data')}</summary>
<ul>
{data.edges.map((d) => {
if (!d) {
return null;
}
<>
<h2 className="text-3xl font-bold mb-4 display-5 mt-5">
{t('Recent data')}
</h2>
<table>
<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 (
<li key={d.node.externalData.data.broadcastAt}>
<SyntaxHighlighter data={filter(d, ['__typename'])} />
</li>
);
})}
</ul>
</details>
const broadcastAt = d.node.externalData.data.broadcastAt;
const node = d.node.externalData.data.data?.at(0);
if (!node || !node.value || !node.name || !node || !broadcastAt) {
return null;
}
return (
<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');
expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s));
expect(res.getByText('External data')).toBeInTheDocument();
expect(res.getByText('External Data')).toBeInTheDocument();
});
it('Renders External data otherwise', () => {
const s = mock('prices.external.vegaprotocol.builtin.');
expect(isInternalSourceType(s)).toEqual(false);
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;
}
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 {
sourceType: SourceType;
}
@@ -39,14 +52,10 @@ export function OracleDetailsType({ sourceType }: OracleDetailsTypeProps) {
return null;
}
const isInternal = isInternalSourceType(sourceType);
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Type</TableHeader>
<TableCell modifier="bordered">
{isInternal ? 'Internal data' : 'External data'}
</TableCell>
<TableCell modifier="bordered">{getTypeString(sourceType)}</TableCell>
</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 { getConditionsOrFilters, OracleFilter } from './oracle-filter';
import type { Filter } from './oracle-filter';
import { OracleFilter } from './oracle-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import {
ConditionOperator,
DataSourceSpecStatus,
PropertyKeyType,
} from '@vegaprotocol/types';
import type { Condition } from '@vegaprotocol/types';
const mockExternalSpec = {
type Spec =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
const mockExternalSpec: Spec = {
sourceType: {
__typename: 'DataSourceSpecConfiguration',
filters: [
@@ -16,12 +19,12 @@ const mockExternalSpec = {
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'test',
name: 'testKey',
},
conditions: [
{
__typename: 'Condition',
value: 'test',
value: 'testValue',
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) {
return <OracleFilter data={data} />;
}
@@ -70,11 +63,16 @@ describe('Oracle Filter view', () => {
},
},
},
} as ExplorerOracleDataSourceFragment)
dataConnection: {
edges: [],
},
})
);
expect(res.getByText('Filter')).toBeInTheDocument();
// Avoids asserting on how the data is presented because it is very rudimentary
// Renders a comprehensible summary of key = value
expect(res.getByText('testKey')).toBeInTheDocument();
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByText('testValue')).toBeInTheDocument();
});
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
@@ -88,75 +86,59 @@ describe('Oracle Filter view', () => {
data: {
sourceType: {
__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
});
});
describe('getConditionsOrFilter', () => {
it('Returns null if the type is undetermined (not DataSourceSpecConfiguration or DataSourceSpecConfigurationTime', () => {
expect(getConditionsOrFilters({})).toBeNull();
});
it('Returns the conditions object for time specs', () => {
const mock: Filter = {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
__typename: 'Condition',
value: '100',
operator: ConditionOperator.OPERATOR_GREATER_THAN,
},
],
};
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,
it('DataSourceSpecConfigurationTime handles empty conditions', () => {
const res = render(
renderComponent({
dataSourceSpec: {
spec: {
id: 'irrelevant-test-data',
createdAt: 'irrelevant-test-data',
status: DataSourceSpecStatus.STATUS_ACTIVE,
data: {
sourceType: {
__typename: 'DataSourceDefinitionInternal',
sourceType: {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [undefined as unknown as Condition],
},
},
},
],
},
},
],
};
dataConnection: {
edges: [],
},
})
);
const res = getConditionsOrFilters(mock);
// This ugly construction is due to lazy typing on getConditionsOrFilter
if (!res || res.length !== 1 || !res[0] || 'value' in res[0]) {
throw new Error(
'getConditionsOrFilter did not return filters on a external spec'
);
}
expect(res[0].__typename).toEqual('Filter');
// This should never happen, but for coverage sake we test that it does this
const ul = res.getByRole('list');
expect(ul).toBeInTheDocument();
expect(ul).toBeEmptyDOMElement();
});
});
@@ -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 { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
import { OracleSpecCondition } from './oracle-spec/condition';
import { getCharacterForOperator } from './oracle-spec/operator';
interface OracleFilterProps {
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
* data sources.
* data sources, as a list.
*
* Renders nothing if there is no data (which will frequently)
* 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 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) {
return null;
if (!f.conditions || f.conditions.length === 0) {
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 (
<details>
<summary>{t('Filter')}</summary>
<SyntaxHighlighter data={filter(f, ['__typename'])} />
</details>
);
return null;
}
@@ -11,7 +11,7 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
<MemoryRouter>
<MockedProvider mocks={mocks}>
<Table>
<tbody>
<tbody data-testid="wrapper">
<OracleMarkets id={id} />
</tbody>
</Table>
@@ -23,8 +23,7 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
describe('Oracle Markets component', () => {
it('Renders a row with the market ID initially', () => {
const res = render(renderComponent('123', []));
expect(res.getByText('Market')).toBeInTheDocument();
expect(res.getByText('123')).toBeInTheDocument();
expect(res.getByTestId('wrapper')).toBeEmptyDOMElement();
});
it('Renders that this is a termination source for the right market', async () => {
@@ -34,21 +33,58 @@ describe('Oracle Markets component', () => {
},
result: {
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: {
edges: [
{
node: {
__typename: 'Market',
id: '123',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: '456',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: '789',
status: 'Active',
},
},
},
@@ -59,15 +95,18 @@ describe('Oracle Markets component', () => {
node: {
__typename: 'Market',
id: 'abc',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: 'def',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: 'ghi',
status: 'Active',
},
},
},
@@ -91,21 +130,58 @@ describe('Oracle Markets component', () => {
},
result: {
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: {
edges: [
{
node: {
__typename: 'Market',
id: '123',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: '789',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: '123',
status: 'Active',
},
},
},
@@ -116,15 +192,18 @@ describe('Oracle Markets component', () => {
node: {
__typename: 'Market',
id: 'abc',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: 'def',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: 'ghi',
status: 'Active',
},
},
},
@@ -1,5 +1,4 @@
import { getNodes } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { MarketLink } from '../../../components/links';
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets';
@@ -24,38 +23,37 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
);
if (markets) {
const m = markets.find((m) => {
const p = m.tradableInstrument.instrument.product;
const m = markets.filter((market) => {
const p = market.tradableInstrument.instrument.product;
if (
((p.__typename === 'Future' || p.__typename === 'Perpetual') &&
p.dataSourceSpecForSettlementData.id === id) ||
('dataSourceSpecForTradingTermination' in p &&
p.dataSourceSpecForTradingTermination.id === id)
p.dataSourceSpecForTradingTermination.id === id) ||
(p.__typename === 'Perpetual' &&
p.dataSourceSpecForSettlementSchedule.id === id)
) {
return true;
}
return false;
});
if (m && m.id) {
if (m && m.length > 0) {
return (
<TableRow modifier="bordered">
<TableHeader scope="row">{getLabel(id, m)}</TableHeader>
<TableCell modifier="bordered" data-testid={`m-${m.id}`}>
<MarketLink id={m.id} />
</TableCell>
</TableRow>
<>
{m.map((market) => (
<TableRow modifier="bordered" key={`m-${market.id}`}>
<TableHeader scope="row">{getLabel(id, market)}</TableHeader>
<TableCell modifier="bordered" data-testid={`m-${market.id}`}>
<MarketLink id={market.id} />
</TableCell>
</TableRow>
))}
</>
);
}
}
return (
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Market')}</TableHeader>
<TableCell modifier="bordered">
<span>{id}</span>
</TableCell>
</TableRow>
);
return null;
}
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 { OracleMarkets } from './oracle-markets';
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 =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
@@ -38,10 +40,8 @@ export const OracleDetails = ({
id,
dataSource,
dataConnection,
showBroadcasts = false,
}: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.edges?.length || 0;
return (
<div>
@@ -49,23 +49,27 @@ export const OracleDetails = ({
<TableRow modifier="bordered">
<TableHeader scope="row">{t('ID')}</TableHeader>
<TableCell modifier="bordered">
<OracleLink id={id} />
<Hash text={id} />
</TableCell>
</TableRow>
<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} />
<OracleEthSource sourceType={sourceType} />
<OracleMarkets id={id} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Matched data')}</TableHeader>
<TableHeader scope="row">{t('Filter')}</TableHeader>
<TableCell modifier="bordered">
{showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'}
<OracleFilter data={dataSource} />
</TableCell>
</TableRow>
</TableWithTbody>
<OracleFilter data={dataSource} />
{showBroadcasts && dataConnection ? (
<OracleData data={dataConnection} />
) : null}
{dataConnection ? <OracleData data={dataConnection} /> : null}
</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 { t } from '@vegaprotocol/i18n';
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { OracleDetails } from '../components/oracle';
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
import { 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 { data, loading, error } = useExplorerOracleSpecsQuery();
const { data, loading, error } = useExplorerOracleFormMarketsQuery({
errorPolicy: 'ignore',
});
useDocumentTitle(['Oracles']);
useScrollToLocation();
const [hoveredOracle, setHoveredOracle] = useState('');
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
@@ -28,36 +38,148 @@ const Oracles = () => {
data.oracleSpecsConnection.edges?.length === 0
}
>
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
<table className="text-left">
<thead>
<tr>
<th className={cellSpacing}>Market</th>
<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 (
<div
id={id}
key={id}
className="mb-10"
data-testid="oracle-details"
>
<OracleDetails
id={id}
dataSource={o?.node}
dataConnection={dataConnection}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
if (
o.node.tradableInstrument.instrument.product.__typename ===
'Future'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.status;
} else if (
o.node.tradableInstrument.instrument.product.__typename ===
'Perpetual'
) {
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>
</section>
);
@@ -40,10 +40,9 @@ export const Oracle = () => {
id={id || ''}
dataSource={data?.oracleSpec}
dataConnection={data?.oracleSpec.dataConnection}
showBroadcasts={true}
/>
<details>
<summary className="pointer">JSON</summary>
<details className="mt-5 cursor-pointer">
<summary>JSON</summary>
<SyntaxHighlighter data={filter(data, ['__typename'])} />
</details>
</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 = {
HOME: '/',
RESTRICTED: '/restricted',
TX: 'txs',
BLOCKS: 'blocks',
PARTIES: 'parties',
@@ -30,6 +30,7 @@ import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
import { Disclaimer } from './pages/disclaimer';
import { FLAGS } from '@vegaprotocol/environment';
import RestrictedPage from './restricted';
export type Navigable = {
path: string;
@@ -356,6 +357,14 @@ export const routerConfig: Route[] = [
...validators,
],
},
{
path: Routes.RESTRICTED,
element: <RestrictedPage />,
handle: {
name: t('Restricted'),
text: t('Restricted'),
},
},
];
export const router = createBrowserRouter(routerConfig);
@@ -14,6 +14,10 @@ export interface BlockExplorerTransactionResult {
value: 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 {
+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_VEGA_REST_URL=http://localhost:3008/api/v2/
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_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_ETHERSCAN_URL=https://sepolia.etherscan.io
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_ETHERSCAN_URL=https://etherscan.io
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_ETHERSCAN_URL=https://sepolia.etherscan.io
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,
generateFreeFormProposalTitle,
getDateFormatForSpecifiedDays,
getProposalDetailsValue,
getProposalFromTitle,
getProposalInformationFromTable,
goToMakeNewProposal,
@@ -26,12 +27,16 @@ import {
} from '../../../../governance-e2e/src/support/staking.functions';
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
import {
depositAsset,
switchVegaWalletPubKey,
vegaWalletSetSpecifiedApprovalAmount,
} from '../../support/wallet-functions';
import type { testFreeformProposal } from '../../support/common-interfaces';
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 participationNotMet = 'token-participation-not-met';
@@ -50,6 +55,8 @@ const openProposals = 'open-proposals';
const viewProposalButton = 'view-proposal-btn';
const proposalTermsToggle = 'proposal-json-toggle';
const marketDataToggle = 'proposal-market-data-toggle';
const governanceTransferToggle = 'proposal-transfer-details';
const marketProposalType = 'proposal-type';
describe(
'Governance flow for proposal details',
@@ -58,6 +65,17 @@ describe(
before('connect wallets and set approval limit', function () {
cy.visit('/');
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 () {
@@ -296,9 +314,6 @@ describe(
});
it('Able to see successor market details with new and updated values', function () {
cy.createMarket();
cy.reload();
waitForSpinner();
cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem)
@@ -307,14 +322,9 @@ describe(
cy.getByTestId(viewProposalButton).click();
});
});
getProposalInformationFromTable('ID')
.invoke('text')
.as('parentMarketId')
.then(() => {
cy.VegaWalletSubmitProposal(
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
});
cy.VegaWalletSubmitProposal(
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
navigateTo(navigation.proposals);
cy.reload();
getProposalFromTitle('Test successor market proposal details').within(
@@ -372,19 +382,221 @@ describe(
});
// 3003-PMAN-011
cy.get('.underline').contains('Parent Market ID').realHover();
cy.contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'The ID of the market this market succeeds.'
);
cy.get('.underline')
.contains('Insurance Pool Fraction')
.realMouseUp()
.realHover();
cy.contains('Insurance Pool Fraction').realMouseUp().realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'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('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(votesTable).within(() => {
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 () {
const proposalPath = 'src/fixtures/proposals/new-market-raw.json';
const proposalTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
enactmentTimestamp: proposalTimestamp,
closingTimestamp: proposalTimestamp,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
}); // 3001-VOTE-052
// 3001-VOTE-008
// 3001-VOTE-034
@@ -310,7 +310,9 @@ context(
closeStakingDialog();
navigateTo(navigation.validators);
clickOnValidatorFromList(0);
cy.getByTestId(stakeRemoveStakeRadioButton, txTimeout).click();
cy.getByTestId(stakeRemoveStakeRadioButton, txTimeout).click({
force: true,
});
cy.getByTestId(stakeTokenAmountInputBox).type('-0.1');
cy.contains('Waiting for next epoch to start', epochTimeout);
cy.getByTestId(stakeTokenSubmitButton)
@@ -331,7 +333,7 @@ context(
closeStakingDialog();
navigateTo(navigation.validators);
clickOnValidatorFromList(0);
cy.getByTestId(stakeRemoveStakeRadioButton).click();
cy.getByTestId(stakeRemoveStakeRadioButton).click({ force: true });
cy.getByTestId(stakeTokenAmountInputBox).type('4');
cy.contains('Waiting for next epoch to start', epochTimeout);
cy.getByTestId(stakeTokenSubmitButton)
@@ -422,7 +424,7 @@ context(
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
stakingPageAssociateTokens('3');
verifyUnstakedBalance(3.0);
@@ -436,7 +438,7 @@ context(
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
stakingPageAssociateTokens('3', { type: 'contract' });
verifyUnstakedBalance(3.0);
@@ -450,7 +452,7 @@ context(
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
stakingPageAssociateTokens('3', { type: 'wallet' });
verifyUnstakedBalance(3.0);
@@ -464,7 +466,7 @@ context(
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
stakingPageAssociateTokens('6');
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('connect').should('be.disabled');
cy.getByTestId('node-url-custom').click();
cy.getByTestId('node-url-custom').click({ force: true });
cy.get('input').should('exist');
cy.getByTestId('connect').should('be.disabled');
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 () {
// 3001-VOTE-001
// 3001-VOTE-001 // 3002-PROP-001
cy.getByTestId(proposalDocumentationLink)
.should('be.visible')
.and('have.text', 'Find out more about Vega governance')
.and('have.attr', 'href')
.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
@@ -11,7 +11,6 @@ import {
waitForBeginningOfEpoch,
} from '../../support/staking.functions';
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const guideLink = 'staking-guide-link';
const validatorTitle = 'validator-node-title';
@@ -38,17 +37,11 @@ const txTimeout = Cypress.env('txTimeout');
context('Validators Page - verify elements on page', function () {
before('navigate to validators page', () => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.mockChainId();
cy.visit('/validators');
});
beforeEach(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.mockChainId();
});
describe('with wallets disconnected', { tags: '@smoke' }, function () {
@@ -189,10 +182,7 @@ context('Validators Page - verify elements on page', function () {
{ tags: '@smoke' },
function () {
before('connect wallets and click on validator', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.mockChainId();
cy.visit('/validators');
cy.connectVegaWallet();
clickOnValidatorFromList(0);
@@ -4,8 +4,6 @@ import {
vegaWalletFaucetAssetsWithoutCheck,
vegaWalletTeardown,
} from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const walletContainer = 'aside [data-testid="vega-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1';
@@ -88,10 +86,7 @@ context(
describe('when vega wallet connected', function () {
before('connect vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.mockChainId();
cy.visit('/');
cy.wait('@ChainId');
cy.connectVegaWallet();
@@ -276,10 +271,7 @@ context(
];
before('faucet assets to connected vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.mockChainId();
for (const { id, amount } of assets) {
vegaWalletFaucetAssetsWithoutCheck(id, amount, vegaWalletPublicKey);
}
@@ -54,6 +54,7 @@ export function submitUniqueRawProposal(proposalFields: {
proposalBody?: string;
proposalTitle?: string;
proposalDescription?: string;
updateMarketId?: string;
closingTimestamp?: number;
enactmentTimestamp?: number;
submit?: boolean;
@@ -71,6 +72,10 @@ export function submitUniqueRawProposal(proposalFields: {
if (proposalFields.proposalDescription) {
rawProposal.rationale.description = proposalFields.proposalDescription;
}
if (proposalFields.updateMarketId) {
rawProposal.terms.updateMarketState.changes.marketId =
proposalFields.updateMarketId;
}
if (proposalFields.closingTimestamp) {
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
} else if (
@@ -232,21 +237,25 @@ export function getDownloadedProposalJsonPath(proposalType: string) {
return filepath;
}
export function getProposalDetailsValue(RowName: string) {
return cy
.contains(RowName)
.parentsUntil(proposalInformationTableRows)
.parent()
.first();
}
export function validateProposalDetailsDiff(
RowName: string,
changeType: proposalChangeType,
newValue: string,
oldValue?: string
) {
cy.contains(RowName)
.parentsUntil(proposalInformationTableRows)
.parent()
.first()
.within(() => {
cy.contains(changeType).should('be.visible');
cy.contains(newValue).should('be.visible');
if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
});
getProposalDetailsValue(RowName).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() {

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