Compare commits

...
Author SHA1 Message Date
Matthew Russell bc6afda830 chore: update test to use waitfor 2023-02-20 16:14:02 -08:00
Matthew Russell 463541c512 chore: remove @testing-library/react-hooks 2023-02-20 15:12:14 -08:00
Matthew Russell 76e266180d fix: broken enum and update tests to use waitFor 2023-02-20 15:08:55 -08:00
Matthew Russell c9a3834302 chore: re-add proposal event sub 2023-02-20 13:13:31 -08:00
Matthew Russell 613d818530 chore: remove busEvent for proposals and use proposal sub, update tests 2023-02-20 12:03:53 -08:00
mattrussell36 a2190fdde0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-20 18:06:33 +00:00
Matthew Russell 849415a78e fix: set size properly for checkbox icon (#2903) 2023-02-20 16:50:27 +00:00
Dexter Edwards d33f5428b6 chore(token): governance missing details (#2938) 2023-02-20 16:18:40 +00:00
m.ray c73b01d549 feat(trading): do not pin row of existing account as it will become duplicated (#2942) 2023-02-20 15:59:01 +00:00
Sam Keen 4286bc37b3 feat(wallet): tighten up the flow for "view as vega user" (#2934) 2023-02-20 15:56:12 +00:00
Art d4ecb88fb4 feat(explorer): new proposals index page (#2925) 2023-02-20 15:53:23 +00:00
Edd 57a8955795 feat(explorer): add feedback link to footer (#2943) 2023-02-20 15:52:12 +00:00
Sam Keen ac53b1f97a feat(governance,ui-toolkit): individual rewards table (#2928) 2023-02-20 14:30:11 +00:00
Art 4b83a10475 chore(explorer): change asset dialog to details page (#2941) 2023-02-20 14:15:18 +00:00
mattrussell36 7e957a2841 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-20 12:09:22 +00:00
Joe Tsang 38c0e70bbc test(governance): proposal flow refactor (#2933) 2023-02-20 09:28:16 +00:00
m.ray 94c40cce7d fix(trading): re-use input errors instead of notification component (#2924) 2023-02-20 07:53:16 +00:00
mattrussell36 0059050440 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-20 06:08:40 +00:00
mattrussell36 f7be890eb9 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-20 00:11:21 +00:00
mattrussell36 59ae971a07 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-19 18:06:29 +00:00
mattrussell36 cfcd5efa2c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-19 12:06:35 +00:00
mattrussell36 fb1e85893c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-19 06:06:22 +00:00
mattrussell36 084972577e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-19 00:11:16 +00:00
mattrussell36 94df5ca048 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-18 18:06:20 +00:00
mattrussell36 6142a7afbb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-18 12:08:53 +00:00
mattrussell36 d2e0c57676 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-18 06:06:37 +00:00
mattrussell36 e48d8d8715 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-18 00:11:33 +00:00
mattrussell36 7de333e1ea chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 18:05:59 +00:00
Sam Keen 2123afc4b0 fix(governance): overstaked penalty shown as NaN (#2932) 2023-02-17 16:42:22 +00:00
Sam Keen ea91fa1723 feat(governance): ranking score for all validator types (#2929) 2023-02-17 16:41:56 +00:00
Sam Keen 4dcdec7a35 fix(governance): navigation token drop down behaviour (#2931) 2023-02-17 16:41:32 +00:00
Joe Tsang 196f1914a3 test(governance): check proposals on homepage (#2908) 2023-02-17 15:17:04 +00:00
daro-maj ea4c7a4a37 test(trading): fixed dropdown issue in markets tests (#2930) 2023-02-17 15:31:14 +01:00
m.ray c22fec97b4 feat(trading): deposit to trade collateral tab pinned row (#2921) 2023-02-17 13:32:20 +00:00
mattrussell36 989a0456c0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 12:09:22 +00:00
daro-maj 8c79a94a8c test(trading): added waiting for market (#2927) 2023-02-17 06:57:19 -05:00
mattrussell36 a7a6a820c0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 06:08:47 +00:00
mattrussell36 1fa09a690e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 00:13:12 +00:00
mattrussell36 b0a84970f2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 18:06:17 +00:00
Maciek b2a115f935 chore(trading): handle timeout and offline errors (#2918) 2023-02-16 17:30:32 +01:00
mattrussell36 9dfed0a211 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 12:09:15 +00:00
mattrussell36 5283676be7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 06:07:52 +00:00
Matthew Russell a82509f0e0 feat(trading): datanode block header for env and node switcher (#2905) 2023-02-15 19:52:54 -08:00
mattrussell36 64c92ce91d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 00:10:37 +00:00
mattrussell36 b7426e73d8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-15 18:07:58 +00:00
Art 969bfd6945 feat(explorer): markets and market details pages (#2914) 2023-02-15 14:20:01 +00:00
mattrussell36 200487a6fc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-15 12:07:55 +00:00
Daniel 3f330e78c5 chore(ci): print logs files in cypress pr workflow (#2906) 2023-02-15 09:11:01 +00:00
mattrussell36 05726c6a5a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-15 06:06:31 +00:00
mattrussell36 fcace22393 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-15 00:12:52 +00:00
mattrussell36 b95462ea38 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-14 18:08:30 +00:00
Joe Tsang 42cd32f376 test(governance): refactor proposal enactment tests (#2897) 2023-02-14 16:44:36 +00:00
m.ray 55d6dd4dce feat(trading): deposit flow deal ticket (#2874) 2023-02-14 15:43:52 +00:00
mattrussell36 7017e24adf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-14 12:06:40 +00:00
mattrussell36 a4d597d426 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-14 06:08:13 +00:00
mattrussell36 b51376ba0d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-14 00:11:34 +00:00
mattrussell36 c72b5d7912 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-13 18:06:53 +00:00
Sam Keen 462a5bc84b chore(governance): update announcement banner (#2894) 2023-02-13 16:48:16 +00:00
Joe Tsang 3edbc71787 test(governance): update home tests (#2900) 2023-02-13 15:56:42 +00:00
Maciek ee67736ac5 chore(trading): positions and collateral tab remove infinitive scroll (#2898) 2023-02-13 15:48:44 +01:00
mattrussell36 8bb6b7a942 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-13 12:06:44 +00:00
daro-maj c1c0d960eb chore(trading): fixed copy prompt alert for chrome (#2899) 2023-02-13 12:41:45 +01:00
mattrussell36 a25e2ad101 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-13 06:08:24 +00:00
mattrussell36 7ee8bd47c8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-13 00:10:44 +00:00
mattrussell36 b72ed66bb3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-12 18:07:34 +00:00
mattrussell36 589bd8d29f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-12 12:08:59 +00:00
mattrussell36 7f7c425e34 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-12 06:08:46 +00:00
mattrussell36 062ea5e968 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-12 00:12:11 +00:00
mattrussell36 87e187c225 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-11 18:08:14 +00:00
mattrussell36 c0f15d8212 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-11 12:06:27 +00:00
mattrussell36 499de6e836 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-11 06:08:23 +00:00
mattrussell36 e72d117a62 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-11 00:12:57 +00:00
mattrussell36 abede79a4c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-10 18:06:41 +00:00
Sam Keen fa415318ff feat(governance): epoch total rewards tables (#2889) 2023-02-10 18:01:30 +00:00
Edd 718959b920 fix(explorer): use position decimal places to format size in order summary for batch orders (#2892) 2023-02-10 12:18:16 +00:00
Edd 6d0ebc3a1b feat(explorer): add node announce tx view (#2888) 2023-02-10 12:13:53 +00:00
Edd 5f5cb965e3 feat(explorer): add vega key and eth key rotate views (#2891) 2023-02-10 12:03:50 +00:00
mattrussell36 86090c295c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-10 06:05:53 +00:00
mattrussell36 6983587f28 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-10 00:10:28 +00:00
mattrussell36 5e87baf174 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 18:07:43 +00:00
ArtandEdd fc93bbd7c5 feat(explorer): asset details (#2882)
Co-authored-by: Edd <edd@vega.xyz>
2023-02-09 18:12:38 +01:00
Edd ce832ad6f4 feat(explorer): state variable proposal tx (#2837) 2023-02-09 15:29:46 +00:00
Bartłomiej Głownia a5d53eee77 chore(trading): live update deal ticket market data (#2570) 2023-02-09 14:20:31 +00:00
Maciek 4dd63da62b chore(trading): handle positions with market data gql errors (#2884) 2023-02-09 14:26:23 +01:00
mattrussell36 2e8dd294de chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 12:06:41 +00:00
Joe Tsang 8917ceb08a test(token): e2e tests for validator tooltips (#2883) 2023-02-09 10:35:05 +00:00
mattrussell36 87f116daee chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 06:07:31 +00:00
mattrussell36 9e91746488 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 00:12:29 +00:00
mattrussell36 5b4ed1a0c3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 18:07:17 +00:00
Bartłomiej Głownia 56731e34cc chore(react-helpers): add generic data provider unit test (#2776) 2023-02-08 17:35:10 +00:00
daro-maj 026e5f5679 chore(trading): key to key transfers e2e tests (#2878) 2023-02-08 18:31:41 +01:00
Daniel e75c160579 feat(ci): archive logs in cypres pr run (#2879) 2023-02-08 16:37:01 +00:00
mattrussell36 5d613396e1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 12:09:14 +00:00
Sam Keen da551f9d3c feat(governance): replace proposal and vote busEvents with subscriptions (#2862) 2023-02-08 09:42:20 +00:00
Edd 47e4861fdb fix(explorer): use position decimal places to format size in order (#2861) 2023-02-08 08:22:27 +00:00
mattrussell36 1a85a89440 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 06:06:13 +00:00
mattrussell36 7ab412a8f7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 00:12:40 +00:00
Edd 5a14174a81 feat(explorer): add issuesignatures tx view (#2867) 2023-02-07 18:17:00 +00:00
mattrussell36 5e00b93783 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 18:06:49 +00:00
Edd 31d9b023e6 feat(explorer): change party to pubkey (#2868) 2023-02-07 18:00:26 +00:00
mattrussell36 103e503a47 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 12:07:30 +00:00
Maciek 1952cb0e78 fix(trading): inform the user his connection to wallet is lost (#2863) 2023-02-07 11:31:15 +01:00
mattrussell36 69340f4ddf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 06:06:15 +00:00
mattrussell36 c52bf2200e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 00:10:59 +00:00
Matthew Russell 8dccee69f2 chore(trading): remove unnecessary sentry capture from asyncrenderer component (#2869) 2023-02-06 15:21:39 -08:00
Art c7a6fdd879 chore(trading): new toasts designs (#2779) 2023-02-06 20:09:56 +00:00
Matthew Russell 8bcdaf4cda feat(trading): key to key transfers (#2784) 2023-02-06 11:35:40 -08:00
mattrussell36 3eb1359504 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 18:08:25 +00:00
Sam Keen 39cf8ad4d6 feat(governance): remove some performance info from validator mouseover (#2865) 2023-02-06 17:08:34 +00:00
Edd faef98f0ae fix(explorer): type generation broken in an oracle refactor (#2859) 2023-02-06 13:29:23 +00:00
macqbat 710e2daa27 chore(trading): notification component redesign (#2845) 2023-02-06 13:31:35 +01:00
daro-maj 06ea3924fa chore(trading): increase withdraw ac test coverage (#2853) 2023-02-06 13:27:12 +01:00
mattrussell36 965e2d8972 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 12:09:01 +00:00
m.ray 73de2fed43 fix(trading): when order is rejected change tx toast intent (#2851) 2023-02-06 11:44:04 +00:00
Sam Keen 536859e067 feat(governance): visual tweaks to main nav (#2839) 2023-02-06 11:18:14 +00:00
Sam Keen 54bfaef473 fix(governance): empty div disrupts other page elements (#2855) 2023-02-06 10:35:26 +00:00
Matthew Russell e2cad707a7 chore(trading): add validator testnet env file (#2852) 2023-02-06 09:04:34 +00:00
mattrussell36 ec8d9798ab chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 06:08:16 +00:00
mattrussell36 e629c1e81a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 00:10:27 +00:00
mattrussell36 25feed793c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 18:08:10 +00:00
mattrussell36 2867367e16 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 12:07:47 +00:00
mattrussell36 50b408e74d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 06:05:57 +00:00
mattrussell36 8f531c7c05 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 00:13:19 +00:00
mattrussell36 350d16fee1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 18:07:27 +00:00
mattrussell36 c052c66266 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 12:06:25 +00:00
mattrussell36 e2032df236 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 06:07:41 +00:00
Matthew Russell 9ece19869d chore(trading): update order subs to not use busevents (#2849) 2023-02-03 16:12:20 -08:00
Joe Tsang 22abc8160c test(token): pending balances (#2843) 2023-02-03 19:26:17 +00:00
Edd b1a7a22bf9 feat(explorer): add protocol upgrade view (#2802) 2023-02-03 19:25:36 +00:00
mattrussell36 c4b38cc0dc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 18:05:46 +00:00
m.ray 44d6d931d5 fix(trading): update async renderer on error (#2842) 2023-02-03 16:52:10 +00:00
m.ray 7f1e47d7fd fix(trading): remove price flash cell from PNL (#2829) 2023-02-03 14:59:24 +00:00
Art 0eddfa4073 fix(ui-toolkit): dark and light shades mismatch (#2841) 2023-02-03 12:33:13 +00:00
mattrussell36 83a034ce91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 12:06:17 +00:00
5ab5aa01a2 feat(trading): 2803 console announcement banner (#2830)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-02-03 10:34:32 +00:00
mattrussell36 b22f9156b5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 06:07:49 +00:00
mattrussell36 bab5e2018b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 00:12:30 +00:00
macqbat 3f7692ce75 chore(trading): add alpha font to trading (#2833) 2023-02-02 19:25:14 +01:00
mattrussell36 b69fb3b0e5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 18:08:33 +00:00
macqbat 83af6e74f6 chore(trading): no data message in charts (#2816) 2023-02-02 18:43:16 +01:00
m.ray b9908003ba fix(trading): fix symbol on order estimates (#2827) 2023-02-02 13:30:47 +00:00
Sam KeenandMatthew Russell 1f9af79215 feat(ui-toolkit): mainnet sims announcement banner (#2806)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-02-02 13:13:55 +00:00
Sam Keen e31e40a7ef feat(ui-toolkit): mainnet sims announcement banner (#2806) 2023-02-02 12:56:22 +00:00
mattrussell36 81f6b3cc15 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 12:08:12 +00:00
mattrussell36 8adb208a91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 06:06:32 +00:00
mattrussell36 545c3a17d2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 00:09:59 +00:00
mattrussell36 ad76988f63 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 18:08:30 +00:00
Sam KeenandDexter d2bbd7e1fc feat(governance): pending balances: add event listeners (#2541)
Co-authored-by: Dexter <dexter.edwards93@gmail.com>
2023-02-01 16:02:50 +00:00
macqbat fb1ebb3bdf chore(trading): remove noise form the test logs (#2801) 2023-02-01 16:58:47 +01:00
m.ray b2489537ef fix(trading): market data and auction time not updating (#2813) 2023-02-01 14:08:49 +00:00
Sam Keen 382a7ea411 feat(governance): body can scroll again after clicking 'back to staking' (#2785) 2023-02-01 12:56:25 +00:00
mattrussell36 1ae2b4c153 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 12:06:46 +00:00
mattrussell36 e8875e817e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 06:06:26 +00:00
mattrussell36 4a01c880f3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 00:10:41 +00:00
mattrussell36 f7f00dc7bb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 18:07:31 +00:00
macqbat d151b6e923 chore(trading): add sub statuses of opening auction trading mode (#2797) 2023-01-31 17:31:55 +01:00
Sam Keen 8f8e9c1061 feat(ui-toolkit): announcement banner to highlight mainnet sims (#2800) 2023-01-31 16:12:44 +00:00
m.ray b40358a723 fix(trading): consolidate view as user mode (#2778) 2023-01-31 16:04:52 +00:00
Art 01f0934da3 chore(ui-toolkit): new colour palette (#2783) 2023-01-31 16:33:16 +01:00
mattrussell36 df7755dbc4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 12:06:33 +00:00
Joe Tsang 9073234040 test(ci): fix tags in cypress workflow (#2788) 2023-01-31 11:35:42 +00:00
Art 053cd0fa27 fix(trading): asset option on mobile got overflown (#2782) 2023-01-31 11:10:55 +01:00
mattrussell36 961dc88dc7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 06:08:30 +00:00
mattrussell36 b16607a844 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 00:13:30 +00:00
Matthew Russell d0d804631c chore(ci): add nx scope linting for pr title (#2781) 2023-01-30 21:06:18 +00:00
daro-maj 3e778a88dc chore: capsule tests refactor and increase deposit ac (#2773) 2023-01-30 20:35:58 +01:00
Edd dbb09c9c79 fix(explorer): correct calculation for whether party txs list has more txs (#2774) 2023-01-30 18:23:09 +00:00
Edd 06f44b67de fix(explorer): tx viewer dropdown did not correctly represent state (#2780) 2023-01-30 18:17:47 +00:00
Joe Tsang 1861852852 test(2704): proposals with market (#2775) 2023-01-30 18:06:04 +00:00
mattrussell36 3ee0eee80d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 12:09:00 +00:00
mattrussell36 69ce870a81 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 06:06:56 +00:00
mattrussell36 d8467c3206 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 00:12:51 +00:00
mattrussell36 de87f40a01 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 18:08:12 +00:00
560 changed files with 21952 additions and 11314 deletions
+5 -2
View File
@@ -64,12 +64,11 @@ jobs:
######
- name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
CYPRESS_grepTags: ${{ inputs.tags }}
######
## Upload logs
@@ -83,6 +82,10 @@ jobs:
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- name: Print logs files
if: ${{ always() }}
run: ls -alsh /home/runner/.vegacapsule/testnet/logs/
- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
+7 -5
View File
@@ -8,14 +8,16 @@ jobs:
lint_pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.14.0
- name: Install commitlint cli and config
run: npm install @commitlint/cli @commitlint/config-conventional
- name: Create config
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
- name: Install root dependencies
run: yarn install
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+39 -141
View File
@@ -1,150 +1,48 @@
context('Asset page', { tags: '@regression' }, function () {
before('gather system asset information', function () {
cy.get_asset_information().as('assetsInfo');
});
describe('Verify elements on page', function () {
const assetsNavigation = 'a[href="/assets"]';
const assetHeader = '[data-testid="asset-header"]';
const jsonSection = '.language-json';
before('Navigate to assets page', function () {
cy.visit('/');
cy.get(assetsNavigation).click();
// Check we have enough enough assets
const assetNames = Object.keys(this.assetsInfo);
assert.isAtLeast(
assetNames.length,
5,
'Ensuring we have at least 5 assets to test'
);
context('Asset page', { tags: '@regression' }, () => {
const columns = ['symbol', 'name', 'id', 'type', 'status', 'actions'];
const hiddenOnMobile = ['id', 'type', 'status'];
describe('Verify elements on page', () => {
before('Navigate to assets page', () => {
cy.visit('/assets');
});
it('should be able to see assets page sections', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
});
});
it('should be able to see all asset details displayed in JSON', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.next()
.within(() => {
cy.get(jsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.then((assetsListedInJson) => {
const assetInfo = this.assetsInfo[assetName];
assert.equal(assetsListedInJson.name, assetInfo.node.name);
assert.equal(assetsListedInJson.id, assetInfo.node.id);
assert.equal(
assetsListedInJson.decimals,
assetInfo.node.decimals
);
assert.equal(assetsListedInJson.symbol, assetInfo.node.symbol);
assert.equal(
assetsListedInJson.source.__typename,
assetInfo.node.source.__typename
);
if (assetInfo.node.source.__typename == 'ERC20') {
assert.equal(
assetsListedInJson.source.contractAddress,
assetInfo.node.source.contractAddress
);
}
if (assetInfo.node.source.__typename == 'BuiltinAsset') {
assert.equal(
assetsListedInJson.source.maxFaucetAmountMint,
assetInfo.node.source.maxFaucetAmountMint
);
}
let knownAssetTypes = ['BuiltinAsset', 'ERC20'];
assert.include(
knownAssetTypes,
assetInfo.node.source.__typename,
`Checking that current asset type of ${assetInfo.node.source.__typename} /
is one of: ${knownAssetTypes}: /
If fail then we need to add extra tests for un-encountered asset types`
);
});
});
});
});
it('should be able to switch assets between light and dark mode', function () {
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
const themeSwitcher = '[data-testid="theme-switcher"]';
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
cy.get(themeSwitcher).click();
it('should be able to see full assets list', () => {
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
});
// Engage white mode
cy.get(themeSwitcher).click();
// White Mode
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', whiteThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', whiteThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', whiteThemeSideMenuBackgroundColor);
// Dark Mode
cy.get(themeSwitcher).click();
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', darkThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', darkThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', darkThemeSideMenuBackgroundColor);
});
columns.forEach((col) => {
cy.get(`[col-id="${col}"]`).should('be.visible');
});
});
it('should be able to see assets page displayed in mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(assetsNavigation).click();
it('should be able to see assets page displayed in mobile', () => {
cy.switchToMobile();
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
hiddenOnMobile.forEach((col) => {
cy.get(`[col-id="${col}"]`).should('have.length', 0);
});
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
});
});
});
it('should open details page when clicked on "View details"', () => {
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
.eq(0)
.should('contain.text', 'View details');
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
.eq(0)
.click();
cy.getByTestId('asset-header').should('have.text', asset.name);
cy.go('back');
});
});
});
});
@@ -247,7 +247,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
.and('include', darkThemeSideMenuBackgroundColor);
});
it('should be able to see network parameters - on mobile', function () {
it.skip('should be able to see network parameters - on mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(networkParametersNavigation).click();
cy.get_network_parameters().then((network_parameters) => {
@@ -123,7 +123,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
.convert_string_json_to_js_object()
.get_party_accounts_data_from_js_object()
.then((accountsListedInJson) => {
cy.get_asset_information().then((assetsInfo) => {
cy.getAssets().then((assetsInfo) => {
const assetInfo =
assetsInfo[accountsListedInJson[assetInTest].asset.name];
@@ -205,7 +205,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
});
Cypress.Commands.add('get_asset_decimals', (assetID) => {
cy.get_asset_information().then((assetsInfo) => {
cy.getAssets().then((assetsInfo) => {
const assetDecimals = assetsInfo[assetData[assetID].name].decimals;
let decimals = '';
for (let i = 0; i < assetDecimals; i++) decimals += '0';
@@ -21,6 +21,10 @@ Cypress.Commands.add(
}
);
Cypress.Commands.add('switchToMobile', () => {
cy.viewport('iphone-x');
});
Cypress.Commands.add('common_switch_to_mobile_and_click_toggle', function () {
cy.viewport('iphone-x');
cy.visit('/');
+5 -3
View File
@@ -1,10 +1,12 @@
# App configuration variables
NX_TENDERMINT_URL=https://n04.d.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.devnet1.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=DEVNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet1-network.json
+2 -2
View File
@@ -6,7 +6,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.sandbox.vega.xyz/websocket
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
+1
View File
@@ -6,3 +6,4 @@ NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
+37 -31
View File
@@ -1,62 +1,68 @@
import classnames from 'classnames';
import { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
import { Nav } from './components/nav';
import { Header } from './components/header';
import { Main } from './components/main';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import type { InMemoryCacheConfig } from '@apollo/client';
import { Footer } from './components/footer/footer';
import { AnnouncementBanner, ExternalLink } from '@vegaprotocol/ui-toolkit';
import {
AssetDetailsDialog,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
const DialogsContainer = () => {
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
return (
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
asJson={asJson}
open={isOpen}
onChange={setOpen}
/>
);
};
function App() {
const [menuOpen, setMenuOpen] = useState(false);
const location = useLocation();
useEffect(() => {
setMenuOpen(false);
}, [location]);
const cacheConfig: InMemoryCacheConfig = {
typePolicies: {
statistics: {
keyFields: false,
},
},
};
const layoutClasses = classnames(
'grid grid-rows-[auto_1fr_auto] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)_auto] md:grid-cols-[300px_1fr]',
'min-h-[100vh] mx-auto my-0',
'border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r',
'bg-white dark:bg-black',
'antialiased text-black dark:text-white',
{
'h-[100vh] min-h-auto overflow-hidden': menuOpen,
}
'overflow-hidden relative'
);
return (
<TendermintWebsocketProvider>
<NetworkLoader cache={cacheConfig}>
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<AnnouncementBanner>
<div className="font-alpha calt uppercase text-center text-lg text-white">
<span className="pr-4">Mainnet sim 2 coming in March!</span>
<ExternalLink href="https://fairground.wtf/">
Learn more
</ExternalLink>
</div>
</AnnouncementBanner>
<div className={layoutClasses}>
<Header menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
<Nav menuOpen={menuOpen} />
<Header />
<Nav />
<Main />
<Footer />
</div>
<DialogsContainer />
</NetworkLoader>
</TendermintWebsocketProvider>
);
}
const Wrapper = () => {
return (
<EnvironmentProvider>
<App />
</EnvironmentProvider>
);
useInitializeEnv();
return <App />;
};
export default Wrapper;
@@ -1,6 +1,6 @@
import { useAssetDataProvider } from '@vegaprotocol/assets';
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
import { AssetLink } from '../links';
import { useExplorerAssetQuery } from '../links/asset-link/__generated__/Asset';
export type AssetBalanceProps = {
assetId: string;
@@ -17,21 +17,17 @@ const AssetBalance = ({
price,
showAssetLink = true,
}: AssetBalanceProps) => {
const { data } = useExplorerAssetQuery({
variables: { id: assetId },
});
const { data: asset } = useAssetDataProvider(assetId);
const label =
data && data.asset?.decimals
? addDecimalsFormatNumber(price, data.asset.decimals)
asset && asset.decimals
? addDecimalsFormatNumber(price, asset.decimals)
: price;
return (
<div className="inline-block">
<span>{label}</span>{' '}
{showAssetLink && data?.asset?.id ? (
<AssetLink id={data.asset.id} />
) : null}
{showAssetLink && asset?.id ? <AssetLink assetId={assetId} /> : null}
</div>
);
};
@@ -0,0 +1,41 @@
import { render, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { assetsList } from '../../mocks/assets';
import { AssetsTable } from './assets-table';
describe('AssetsTable', () => {
it('shows loading message on first render', async () => {
const res = render(
<MemoryRouter>
<AssetsTable data={null} />
</MemoryRouter>
);
expect(await res.findByText('Loading...')).toBeInTheDocument();
});
it('shows no data message if no assets found', async () => {
const res = render(
<MemoryRouter>
<AssetsTable data={[]} />
</MemoryRouter>
);
expect(
await res.findByText('This chain has no assets')
).toBeInTheDocument();
});
it('shows a table/list with all the assets', async () => {
const res = render(
<MemoryRouter>
<AssetsTable data={assetsList} />
</MemoryRouter>
);
await waitFor(() => {
const rowA1 = res.container.querySelector('[row-id="123"]');
expect(rowA1).toBeInTheDocument();
const rowA2 = res.container.querySelector('[row-id="456"]');
expect(rowA2).toBeInTheDocument();
});
});
});
@@ -0,0 +1,105 @@
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/react-helpers';
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
import { useNavigate } from 'react-router-dom';
import type { RowClickedEvent } from 'ag-grid-community';
type AssetsTableProps = {
data: AssetFieldsFragment[] | null;
};
export const AssetsTable = ({ data }: AssetsTableProps) => {
const navigate = useNavigate();
const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => {
ref.current?.columnApi.setColumnsVisible(
['id', 'type', 'status'],
window.innerWidth > BREAKPOINT_MD
);
};
useLayoutEffect(() => {
window.addEventListener('resize', showColumnsOnDesktop);
return () => {
window.removeEventListener('resize', showColumnsOnDesktop);
};
}, []);
return (
<AgGrid
ref={ref}
rowData={data}
getRowId={({ data }: { data: AssetFieldsFragment }) => data.id}
overlayNoRowsTemplate={t('This chain has no assets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
suppressCellFocus={true}
onRowClicked={({ data }: RowClickedEvent) => {
navigate(data.id);
}}
>
<AgGridColumn headerName={t('Symbol')} field="symbol" />
<AgGridColumn headerName={t('Name')} field="name" />
<AgGridColumn
flex="2"
headerName={t('ID')}
field="id"
hide={window.innerWidth < BREAKPOINT_MD}
/>
<AgGridColumn
colId="type"
headerName={t('Type')}
field="source.__typename"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetTypeMapping[value].value
}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetStatusMapping[value].value
}
/>
<AgGridColumn
colId="actions"
headerName=""
sortable={false}
filter={false}
resizable={false}
wrapText={true}
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
navigate(value);
}}
>
{t('View details')}
</ButtonLink>
) : (
''
)
}
/>
</AgGrid>
);
};
@@ -0,0 +1,56 @@
import { t } from '@vegaprotocol/react-helpers';
import {
Button,
Dialog,
Icon,
SyntaxHighlighter,
} from '@vegaprotocol/ui-toolkit';
type JsonViewerDialogProps = {
title: string;
content: unknown;
open: boolean;
onChange: (isOpen: boolean) => void;
trigger?: HTMLElement;
};
export const JsonViewerDialog = ({
title,
content,
open,
onChange,
trigger,
}: JsonViewerDialogProps) => {
return (
<Dialog
size="medium"
title={title}
icon={<Icon name="info-sign"></Icon>}
open={open}
onChange={(isOpen) => onChange(isOpen)}
onCloseAutoFocus={(e) => {
/**
* This mimics radix's default behaviour that focuses the dialog's
* trigger after closing itself
*/
if (trigger) {
e.preventDefault();
trigger.focus();
}
}}
>
<div className="pr-8 mb-8 max-h-[70vh] overflow-y-scroll">
<SyntaxHighlighter size="smaller" data={content} />
</div>
<div className="w-1/4">
<Button
data-testid="close-asset-details-dialog"
fill={true}
size="sm"
onClick={() => onChange(false)}
>
{t('Close')}
</Button>
</div>
</Dialog>
);
};
@@ -1,39 +1,59 @@
import { useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/react-helpers';
import { Link } from '@vegaprotocol/ui-toolkit';
import { NodeSwitcherDialog, useEnvironment } from '@vegaprotocol/environment';
import { t, useScreenDimensions } from '@vegaprotocol/react-helpers';
import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
import { useMemo, useState } from 'react';
import { ENV } from '../../config/env';
export const Footer = () => {
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL, setNodeSwitcherOpen } =
useEnvironment();
return (
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-neutral-700 dark:border-neutral-300">
<div className="flex justify-between gap-2 align-middle">
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
{GIT_COMMIT_HASH && (
<p data-testid="git-commit-hash">
{t('Version')}:{' '}
<Link
href={
GIT_ORIGIN_URL
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
: undefined
}
target={GIT_ORIGIN_URL ? '_blank' : undefined}
>
{GIT_COMMIT_HASH}
</Link>
</p>
)}
</div>
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment();
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo(
() => ['lg', 'xl'].includes(screenSize),
[screenSize]
);
<div className="flex pl-2 content-center">
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<Link className="ml-2" onClick={setNodeSwitcherOpen}>
{t('Change')}
</Link>
return (
<>
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-neutral-700 dark:border-neutral-300">
<div className="flex justify-between gap-2 align-middle">
{GIT_COMMIT_HASH && (
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
<p data-testid="git-commit-hash">
{t('Version')}:{' '}
<Link
href={
GIT_ORIGIN_URL
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
: undefined
}
target={GIT_ORIGIN_URL ? '_blank' : undefined}
>
{GIT_COMMIT_HASH}
</Link>
</p>
</div>
)}
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
{t('Change')}
</Link>
</div>
<div className="flex pl-2 content-center">
<ExternalLink href={ENV.addresses.feedback}>
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
</ExternalLink>
</div>
</div>
</div>
</footer>
</footer>
<NodeSwitcherDialog
open={nodeSwitcherOpen}
setOpen={setNodeSwitcherOpen}
/>
</>
);
};
@@ -14,7 +14,7 @@ jest.mock('../search', () => ({
const renderComponent = () => (
<MemoryRouter>
<Header menuOpen={false} setMenuOpen={jest.fn()} />
<Header />
</MemoryRouter>
);
@@ -4,15 +4,11 @@ import { ThemeSwitcher, Icon } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { Search } from '../search';
import { Routes } from '../../routes/route-names';
import type { Dispatch, SetStateAction } from 'react';
import { NetworkSwitcher } from '@vegaprotocol/environment';
import { useNavStore } from '../nav';
interface ThemeToggleProps {
menuOpen: boolean;
setMenuOpen: Dispatch<SetStateAction<boolean>>;
}
export const Header = ({ menuOpen, setMenuOpen }: ThemeToggleProps) => {
export const Header = () => {
const [open, toggle] = useNavStore((state) => [state.open, state.toggle]);
const headerClasses = classnames(
'md:col-span-2',
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
@@ -36,9 +32,9 @@ export const Header = ({ menuOpen, setMenuOpen }: ThemeToggleProps) => {
<button
data-testid="open-menu"
className="md:hidden text-white"
onClick={() => setMenuOpen(!menuOpen)}
onClick={() => toggle()}
>
<Icon name={menuOpen ? 'cross' : 'menu'} />
<Icon name={open ? 'cross' : 'menu'} />
</button>
<Search />
<ThemeSwitcher className="-my-4" />
@@ -1,8 +0,0 @@
query ExplorerAsset($id: ID!) {
asset(id: $id) {
id
name
status
decimals
}
}
@@ -1,51 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerAssetQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerAssetQuery = { __typename?: 'Query', asset?: { __typename?: 'Asset', id: string, name: string, status: Types.AssetStatus, decimals: number } | null };
export const ExplorerAssetDocument = gql`
query ExplorerAsset($id: ID!) {
asset(id: $id) {
id
name
status
decimals
}
}
`;
/**
* __useExplorerAssetQuery__
*
* To run a query within a React component, call `useExplorerAssetQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerAssetQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerAssetQuery(baseOptions: Apollo.QueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
}
export function useExplorerAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
}
export type ExplorerAssetQueryHookResult = ReturnType<typeof useExplorerAssetQuery>;
export type ExplorerAssetLazyQueryHookResult = ReturnType<typeof useExplorerAssetLazyQuery>;
export type ExplorerAssetQueryResult = Apollo.QueryResult<ExplorerAssetQuery, ExplorerAssetQueryVariables>;
@@ -1,63 +1,37 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import AssetLink from './asset-link';
import { ExplorerAssetDocument } from './__generated__/Asset';
import { render, waitFor } from '@testing-library/react';
import { AssetLink } from './asset-link';
import { mockAssetA1 } from '../../../mocks/assets';
function renderComponent(id: string, mock: MockedResponse[]) {
return (
<MockedProvider mocks={mock}>
<MockedProvider mocks={mock} addTypename={false}>
<MemoryRouter>
<AssetLink id={id} />
<AssetLink assetId={id} />
</MemoryRouter>
</MockedProvider>
);
}
describe('Asset link component', () => {
it('Renders the ID at first', () => {
describe('AssetLink', () => {
it('renders the asset id when not found and makes the button disabled', async () => {
const res = render(renderComponent('123', []));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByTestId('asset-link')).toBeDisabled();
await waitFor(async () => {
expect(await res.queryByText('A ONE')).toBeFalsy();
});
});
it('Renders the asset name when the query returns a result', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
variables: {
id: '123',
},
},
result: {
data: {
asset: {
id: '123',
name: 'test-label',
status: 'irrelevant-test-data',
decimals: 18,
},
},
},
};
const res = render(renderComponent('123', [mock]));
it('renders the asset name when found and make the button enabled', async () => {
const res = render(renderComponent('123', [mockAssetA1]));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByText('test-label')).toBeInTheDocument();
});
it('Leaves the asset id when the asset is not found', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
variables: {
id: '123',
},
},
error: new Error('No such asset'),
};
const res = render(renderComponent('123', [mock]));
expect(await res.findByText('123')).toBeInTheDocument();
await waitFor(async () => {
expect(await res.findByText('A ONE')).toBeInTheDocument();
expect(await res.findByTestId('asset-link')).not.toBeDisabled();
});
});
});
@@ -1,36 +1,43 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { useExplorerAssetQuery } from './__generated__/Asset';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import {
useAssetDataProvider,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
import { useNavigate } from 'react-router-dom';
import { Routes } from '../../../routes/route-names';
export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
assetId: string;
asDialog?: boolean;
};
/**
* Given an asset ID, it will fetch the asset name and show that,
* with a link to the assets list. If the name does not come back
* with a link to the assets modal. If the name does not come back
* it will use the ID instead.
*/
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
const { data } = useExplorerAssetQuery({
variables: { id },
});
let label: string = id;
if (data?.asset?.name) {
label = data.asset.name;
}
export const AssetLink = ({ assetId, asDialog, ...props }: AssetLinkProps) => {
const { data: asset } = useAssetDataProvider(assetId);
const open = useAssetDetailsDialogStore((state) => state.open);
const navigate = useNavigate();
const label = asset?.name ? asset.name : assetId;
return (
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
<ButtonLink
data-testid="asset-link"
disabled={!asset}
onClick={(e) => {
if (asDialog) {
open(assetId, e.target as HTMLElement);
} else {
navigate(`${Routes.ASSETS}/${asset?.id}`);
}
}}
{...props}
>
<Hash text={label} />
</Link>
</ButtonLink>
);
};
export default AssetLink;
@@ -2,4 +2,4 @@ export { default as BlockLink } from './block-link/block-link';
export { default as PartyLink } from './party-link/party-link';
export { default as NodeLink } from './node-link/node-link';
export { default as MarketLink } from './market-link/market-link';
export { default as AssetLink } from './asset-link/asset-link';
export * from './asset-link/asset-link';
@@ -2,6 +2,7 @@ query ExplorerMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -8,7 +8,7 @@ export type ExplorerMarketQueryVariables = Types.Exact<{
}>;
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
export const ExplorerMarketDocument = gql`
@@ -16,6 +16,7 @@ export const ExplorerMarketDocument = gql`
market(id: $id) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -55,6 +55,7 @@ describe('Market link component', () => {
market: {
id: '123',
decimalPlaces: 5,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -2,7 +2,7 @@ import { AppRouter } from '../../routes';
export const Main = () => {
return (
<main className="p-4 overflow-scroll">
<main className="p-4">
<AppRouter />
</main>
);
@@ -0,0 +1,246 @@
import {
addDecimalsFormatNumber,
formatNumberPercentage,
getMarketExpiryDateFormatted,
t,
} from '@vegaprotocol/react-helpers';
import type { MarketInfoNoCandlesQuery } from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info';
import pick from 'lodash/pick';
import {
MarketStateMapping,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { Splash } from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
import { useMemo } from 'react';
import { Link } from 'react-router-dom';
export const MarketDetails = ({
market,
}: {
market: MarketInfoNoCandlesQuery['market'];
}) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol;
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
);
const { data: asset } = useAssetDataProvider(assetId ?? '');
if (!market) return null;
const keyDetails = {
...pick(market, 'decimalPlaces', 'positionDecimalPlaces', 'tradingMode'),
state: MarketStateMapping[market.state],
};
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const panels = [
{
title: t('Key details'),
content: (
<MarketInfoTable
noBorder={false}
data={{
name: market.tradableInstrument.instrument.name,
marketID: market.id,
tradingMode:
keyDetails.tradingMode &&
MarketTradingModeMapping[keyDetails.tradingMode],
marketDecimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
settlementAssetDecimalPlaces: assetDecimals,
}}
/>
),
},
{
title: t('Instrument'),
content: (
<MarketInfoTable
noBorder={false}
data={{
marketName: market.tradableInstrument.instrument.name,
code: market.tradableInstrument.instrument.code,
productType:
market.tradableInstrument.instrument.product.__typename,
...market.tradableInstrument.instrument.product,
}}
/>
),
},
{
title: t('Settlement asset'),
content: asset ? (
<AssetDetailsTable
asset={asset}
inline={true}
noBorder={false}
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
/>
) : (
<Splash>{t('No data')}</Splash>
),
},
{
title: t('Metadata'),
content: (
<MarketInfoTable
noBorder={false}
data={{
expiryDate: getMarketExpiryDateFormatted(
market.tradableInstrument.instrument.metadata.tags
),
...market.tradableInstrument.instrument.metadata.tags
?.map((tag) => {
const [key, value] = tag.split(':');
return { [key]: value };
})
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
}}
/>
),
},
{
title: t('Risk model'),
content: (
<MarketInfoTable
noBorder={false}
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
/>
),
},
{
title: t('Risk parameters'),
content: (
<MarketInfoTable
noBorder={false}
data={market.tradableInstrument.riskModel.params}
unformatted={true}
omits={[]}
/>
),
},
{
title: t('Risk factors'),
content: (
<MarketInfoTable
noBorder={false}
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
/>
),
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => ({
title: t(`Price monitoring trigger ${i + 1}`),
content: <MarketInfoTable noBorder={false} data={trigger} />,
})
),
...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({
title: t(`Price monitoring bound ${i + 1}`),
content: (
<>
<MarketInfoTable
noBorder={false}
data={trigger}
decimalPlaces={market.decimalPlaces}
omits={['referencePrice', '__typename']}
/>
<MarketInfoTable
noBorder={false}
data={{ referencePrice: trigger.referencePrice }}
decimalPlaces={assetDecimals}
/>
</>
),
})),
{
title: t('Liquidity monitoring parameters'),
content: (
<MarketInfoTable
noBorder={false}
data={{
triggeringRatio:
market.liquidityMonitoringParameters.triggeringRatio,
...market.liquidityMonitoringParameters.targetStakeParameters,
}}
/>
),
},
{
title: t('Liquidity price range'),
content: (
<MarketInfoTable
noBorder={false}
data={{
liquidityPriceRange: formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
),
LPVolumeMin:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
LPVolumeMax:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
}}
></MarketInfoTable>
),
},
{
title: t('Oracle'),
content: (
<MarketInfoTable
noBorder={false}
data={
market.tradableInstrument.instrument.product.dataSourceSpecBinding
}
>
<Link
className="text-xs hover:underline"
to={`/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
>
{t('View settlement data oracle specification')}
</Link>
<Link
className="text-xs hover:underline"
to={`/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForTradingTermination.id}`}
>
{t('View termination oracle specification')}
</Link>
</MarketInfoTable>
),
},
];
return (
<>
{panels.map((p) => (
<div className="mb-3">
<h2 className="font-alpha text-xl">{p.title}</h2>
{p.content}
</div>
))}
</>
);
};
@@ -0,0 +1,132 @@
import type { MarketFieldsFragment } from '@vegaprotocol/market-list';
import { t } from '@vegaprotocol/react-helpers';
import type {
VegaICellRendererParams,
VegaValueGetterParams,
} from '@vegaprotocol/ui-toolkit';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
import { MarketStateMapping } from '@vegaprotocol/types';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import type { RowClickedEvent } from 'ag-grid-community';
import { Link, useNavigate } from 'react-router-dom';
type MarketsTableProps = {
data: MarketFieldsFragment[] | null;
};
export const MarketsTable = ({ data }: MarketsTableProps) => {
const openAssetDetailsDialog = useAssetDetailsDialogStore(
(state) => state.open
);
const navigate = useNavigate();
const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => {
const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible(
['id', 'state', 'asset'],
window.innerWidth > BREAKPOINT_MD
);
};
window.addEventListener('resize', showColumnsOnDesktop);
return () => {
window.removeEventListener('resize', showColumnsOnDesktop);
};
}, []);
return (
<AgGrid
ref={gridRef}
rowData={data}
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
overlayNoRowsTemplate={t('This chain has no markets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
navigate(data.id);
}
}}
>
<AgGridColumn
colId="code"
headerName={t('Code')}
field="tradableInstrument.instrument.code"
/>
<AgGridColumn
colId="name"
headerName={t('Name')}
field="tradableInstrument.instrument.name"
/>
<AgGridColumn
headerName={t('Status')}
field="state"
hide={window.innerWidth <= BREAKPOINT_MD}
valueGetter={({
data,
}: VegaValueGetterParams<MarketFieldsFragment, 'state'>) => {
return data?.state ? MarketStateMapping[data?.state] : '-';
}}
/>
<AgGridColumn
colId="asset"
headerName={t('Settlement asset')}
field="tradableInstrument.instrument.product.settlementAsset"
hide={window.innerWidth <= BREAKPOINT_MD}
cellRenderer={({
value,
}: VegaICellRendererParams<
MarketFieldsFragment,
'tradableInstrument.instrument.product.settlementAsset'
>) =>
value ? (
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value.id, e.target as HTMLElement);
}}
>
{value.symbol}
</ButtonLink>
) : (
''
)
}
/>
<AgGridColumn
flex={2}
headerName={t('Market ID')}
field="id"
hide={window.innerWidth <= BREAKPOINT_MD}
/>
<AgGridColumn
colId="actions"
headerName=""
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
value ? (
<Link className="underline" to={value}>
{t('View details')}
</Link>
) : (
''
)
}
/>
</AgGrid>
);
};
+1 -45
View File
@@ -1,45 +1 @@
import { NavLink } from 'react-router-dom';
import routerConfig from '../../routes/router-config';
import classnames from 'classnames';
interface NavProps {
menuOpen: boolean;
}
export const Nav = ({ menuOpen }: NavProps) => {
return (
<nav className="relative">
<div
className={classnames(
'absolute top-0 z-50 md:static',
'w-full p-4 md:border-r border-neutral-700 dark:border-neutral-300',
'bg-white dark:bg-black',
'transition-[right]',
{
'right-0 h-[100vh]': menuOpen,
'right-[200vw] h-full': !menuOpen,
}
)}
>
{routerConfig.map((r) => (
<NavLink
key={r.name}
to={r.path}
className={({ isActive }) =>
classnames(
'block mb-2 px-2',
'text-lg hover:bg-vega-pink dark:hover:bg-vega-yellow hover:text-white dark:hover:text-black',
{
'bg-vega-pink text-white dark:bg-vega-yellow dark:text-black':
isActive,
}
)
}
>
{r.text}
</NavLink>
))}
</div>
</nav>
);
};
export * from './nav';
@@ -0,0 +1,181 @@
import { NavLink, useLocation } from 'react-router-dom';
import type { Navigable } from '../../routes/router-config';
import routerConfig from '../../routes/router-config';
import classnames from 'classnames';
import { create } from 'zustand';
import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
} from 'react';
import { Icon } from '@vegaprotocol/ui-toolkit';
import first from 'lodash/first';
import last from 'lodash/last';
import { BREAKPOINT_MD } from '../../config/breakpoints';
type NavStore = {
open: boolean;
toggle: () => void;
hide: () => void;
};
export const useNavStore = create<NavStore>((set, get) => ({
open: false,
toggle: () => set({ open: !get().open }),
hide: () => set({ open: false }),
}));
const NavLinks = ({ links }: { links: Navigable[] }) => {
const navLinks = links.map((r) => (
<li key={r.name}>
<NavLink
to={r.path}
className={({ isActive }) =>
classnames(
'block mb-2 px-2',
'text-lg hover:bg-vega-pink dark:hover:bg-vega-yellow hover:text-white dark:hover:text-black',
{
'bg-vega-pink text-white dark:bg-vega-yellow dark:text-black':
isActive,
}
)
}
>
{r.text}
</NavLink>
</li>
));
return <ul className="pr-8 md:pr-0">{navLinks}</ul>;
};
export const Nav = () => {
const [open, hide] = useNavStore((state) => [state.open, state.hide]);
const location = useLocation();
const navRef = useRef<HTMLElement>(null);
const btnRef = useRef<HTMLButtonElement>(null);
const focusable = useMemo(
() =>
navRef.current
? [
...(navRef.current.querySelectorAll(
'a, button'
) as NodeListOf<HTMLElement>),
]
: [],
// eslint-disable-next-line react-hooks/exhaustive-deps
[navRef.current] // do not remove `navRef.current` from deps
);
const closeNav = useCallback(() => {
hide();
console.log(focusable);
focusable.forEach((fe) =>
fe.setAttribute(
'tabindex',
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
)
);
}, [focusable, hide]);
// close navigation when location changes
useEffect(() => {
closeNav();
}, [closeNav, location]);
useLayoutEffect(() => {
if (open) {
focusable.forEach((fe) => fe.setAttribute('tabindex', '0'));
}
document.body.style.overflow = open ? 'hidden' : '';
const offset =
document.querySelector('header')?.getBoundingClientRect().top || 0;
if (navRef.current) {
navRef.current.style.height = `calc(100vh - ${offset}px)`;
}
// focus current by default
if (navRef.current && open) {
(navRef.current.querySelector('a[aria-current]') as HTMLElement)?.focus();
}
const closeOnEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closeNav();
}
};
// tabbing loop
const focusLast = (e: FocusEvent) => {
e.preventDefault();
const isNavElement =
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
if (!isNavElement && open) {
last(focusable)?.focus();
}
};
const focusFirst = (e: FocusEvent) => {
e.preventDefault();
const isNavElement =
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
if (!isNavElement && open) {
first(focusable)?.focus();
}
};
const resetOnDesktop = () => {
focusable.forEach((fe) =>
fe.setAttribute(
'tabindex',
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
)
);
};
window.addEventListener('resize', resetOnDesktop);
first(focusable)?.addEventListener('focusout', focusLast);
last(focusable)?.addEventListener('focusout', focusFirst);
document.addEventListener('keydown', closeOnEsc);
return () => {
window.removeEventListener('resize', resetOnDesktop);
document.removeEventListener('keydown', closeOnEsc);
first(focusable)?.removeEventListener('focusout', focusLast);
last(focusable)?.removeEventListener('focusout', focusFirst);
};
}, [closeNav, focusable, open]);
return (
<nav
ref={navRef}
className={classnames(
'absolute top-0 z-20 overflow-y-auto',
'transition-[right]',
{
'right-[-200vw] h-full': !open,
'right-0 h-[100vh]': open,
},
'w-full p-4 border-neutral-700 dark:border-neutral-300',
'bg-white dark:bg-black',
'md:static md:border-r'
)}
>
<NavLinks links={routerConfig} />
<button
ref={btnRef}
className="absolute top-0 right-0 p-4 md:hidden"
onClick={() => {
closeNav();
}}
>
<Icon name="cross" />
</button>
</nav>
);
};
@@ -117,7 +117,7 @@ const NestedDataListItem = ({
)}
</h4>
{!hasChildren && (
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
<code className="text-vega-light-100 mb-2 last:mb-0 dark:text-vega-dark-100 break-all">
{JSON.stringify(value, null, ' ')}
</code>
)}
@@ -19,6 +19,7 @@ fragment ExplorerDeterministicOrderFields on Order {
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID'];
@@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
}>;
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order {
@@ -35,6 +35,7 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -61,6 +61,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
__typename: 'Market',
id: '789',
state: 'STATUS_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: '5',
tradableInstrument: {
instrument: {
@@ -88,6 +89,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
market: {
id: '789',
decimalPlaces: 5,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -4,6 +4,7 @@ import { MarketLink } from '../links';
import PriceInMarket from '../price-in-market/price-in-market';
import { Time } from '../time';
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market';
export interface DeterministicOrderDetailsProps {
id: string;
@@ -90,7 +91,7 @@ const DeterministicOrderDetails = ({
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
{o.size}
<SizeInMarket size={o.size} marketId={o.market.id} />
</h5>
</div>
@@ -38,6 +38,7 @@ const mock = {
__typename: 'Market',
id: '789',
state: 'STATE_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: 2,
tradableInstrument: {
instrument: {
@@ -1,6 +1,7 @@
import { useExplorerDeterministicOrderQuery } from '../order-details/__generated__/Order';
import PriceInMarket from '../price-in-market/price-in-market';
import { sideText } from '../order-details/lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market';
// Note: Edited has no style currently
export type OrderSummaryModifier = 'cancelled' | 'edited';
@@ -41,7 +42,8 @@ const OrderSummary = ({ id, modifier }: OrderSummaryProps) => {
return (
<div data-testid="order-summary" className={getClassName(modifier)}>
<span>{sideText[order.side]}</span>&nbsp;
<span>{order.size}</span>&nbsp;<i>@</i>&nbsp;
<SizeInMarket marketId={order.market.id} size={order.size} />
&nbsp;<i>@</i>&nbsp;
<PriceInMarket marketId={order.market.id} price={order.price} />
</div>
);
@@ -90,6 +90,7 @@ describe('Order TX Summary component', () => {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -111,13 +112,14 @@ describe('Order TX Summary component', () => {
const res = renderComponent(o, [mock]);
expect(res.queryByTestId('order-summary')).toBeInTheDocument();
expect(res.getByText('Buy')).toBeInTheDocument();
expect(res.getByText('10')).toBeInTheDocument();
// Initially renders price alone
// Initially renders price and size unformatted
expect(res.getByText('333')).toBeInTheDocument();
expect(res.getByText('10')).toBeInTheDocument();
// After fetch renders formatted price and asset quotename
expect(await res.findByText('3.33')).toBeInTheDocument();
expect(await res.findByText('TEST')).toBeInTheDocument();
expect(await res.getByText('0.10')).toBeInTheDocument();
});
});
@@ -2,6 +2,7 @@ import type { components } from '../../../types/explorer';
import PriceInMarket from '../price-in-market/price-in-market';
import { sideText } from '../order-details/lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market';
export type OrderSummaryProps = {
order: components['schemas']['v1OrderSubmission'];
@@ -29,7 +30,12 @@ const OrderTxSummary = ({ order }: OrderSummaryProps) => {
return (
<div data-testid="order-summary">
<span>{sideText[order.side]}</span>&nbsp;
<span>{order.size}</span>&nbsp;<i className="text-xs">@</i>&nbsp;
{order.size ? (
<SizeInMarket size={order.size} marketId={order.marketId} />
) : (
'-'
)}
&nbsp;<i className="text-xs">@</i>&nbsp;
<PriceInMarket
marketId={order.marketId}
price={order.price}
@@ -37,6 +37,7 @@ const fullMock = {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -0,0 +1,227 @@
import type { ProposalListFieldsFragment } from '@vegaprotocol/governance';
import { VoteProgress } from '@vegaprotocol/governance';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/ui-toolkit';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { RowClickedEvent } from 'ag-grid-community';
import {
getDateTimeFormat,
NetworkParams,
t,
useNetworkParams,
} from '@vegaprotocol/react-helpers';
import { ProposalStateMapping } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import { BREAKPOINT_MD } from '../../config/breakpoints';
import { JsonViewerDialog } from '../dialogs/json-viewer-dialog';
type ProposalTermsDialog = {
open: boolean;
title: string;
content: unknown;
};
type ProposalsTableProps = {
data: ProposalListFieldsFragment[] | null;
};
export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
]);
const tokenLink = useLinks(DApp.Token);
const requiredMajorityPercentage = useMemo(() => {
const requiredMajority =
params?.governance_proposal_market_requiredMajority ?? 1;
return new BigNumber(requiredMajority).times(100);
}, [params?.governance_proposal_market_requiredMajority]);
const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => {
const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible(
['voting', 'cDate', 'eDate', 'type'],
window.innerWidth > BREAKPOINT_MD
);
gridRef.current?.columnApi.setColumnWidth(
'actions',
window.innerWidth > BREAKPOINT_MD ? 221 : 80
);
};
window.addEventListener('resize', showColumnsOnDesktop);
return () => {
window.removeEventListener('resize', showColumnsOnDesktop);
};
}, []);
const [dialog, setDialog] = useState<ProposalTermsDialog>({
open: false,
title: '',
content: null,
});
return (
<>
<AgGrid
ref={gridRef}
rowData={data}
getRowId={({ data }: { data: ProposalListFieldsFragment }) =>
data.id || data.rationale.title
}
overlayNoRowsTemplate={t('This chain has no markets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if (
(event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON'
) {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data.id)
);
window.open(proposalPage, '_blank');
}
}}
>
<AgGridColumn
colId="title"
headerName={t('Title')}
field="rationale.title"
flex={2}
wrapText={true}
/>
<AgGridColumn
colId="type"
maxWidth={180}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Type')}
field="terms.change.__typename"
/>
<AgGridColumn
maxWidth={100}
headerName={t('State')}
field="state"
valueFormatter={({
value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
return value ? ProposalStateMapping[value] : '-';
}}
/>
<AgGridColumn
colId="voting"
maxWidth={100}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Voting')}
cellRenderer={({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) {
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
const noTokens = new BigNumber(data.votes.no.totalTokens);
const totalTokensVoted = yesTokens.plus(noTokens);
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</div>
);
}
return '-';
}}
/>
<AgGridColumn
colId="cDate"
maxWidth={150}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Closing date')}
field="terms.closingDatetime"
valueFormatter={({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.closingDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
}}
/>
<AgGridColumn
colId="eDate"
maxWidth={150}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Enactment date')}
field="terms.enactmentDatetime"
valueFormatter={({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.enactmentDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
}}
/>
<AgGridColumn
colId="actions"
minWidth={window.innerWidth > BREAKPOINT_MD ? 221 : 80}
maxWidth={221}
sortable={false}
filter={false}
resizable={false}
cellRenderer={({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data?.id || '')
);
const openDialog = () => {
if (!data) return;
setDialog({
open: true,
title: data.rationale.title,
content: data.terms,
});
};
return (
<div className="pb-1">
<button
className="underline max-md:hidden"
onClick={openDialog}
>
{t('View terms')}
</button>{' '}
<ExternalLink className="max-md:hidden" href={proposalPage}>
{t('Open in Governance')}
</ExternalLink>
<ExternalLink className="md:hidden" href={proposalPage}>
{t('Open')}
</ExternalLink>
</div>
);
}}
/>
</AgGrid>
<JsonViewerDialog
open={dialog.open}
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
title={dialog.title}
content={dialog.content}
/>
</>
);
};
@@ -1,5 +1,4 @@
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { StatusMessage } from '../status-message';
interface RenderFetchedProps {
@@ -66,7 +66,9 @@ export const Search = () => {
className="text-white"
hasError={Boolean(error?.message)}
type="text"
placeholder={t('Enter block number, party id or transaction hash')}
placeholder={t(
'Enter block number, public key or transaction hash'
)}
/>
{error?.message && (
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b text-black">
@@ -0,0 +1,74 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import SizeInMarket from './size-in-market';
import type { DecimalSource } from './size-in-market';
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
function renderComponent(
size: string | undefined,
marketId: string,
mocks: MockedResponse[],
decimalSource: DecimalSource = 'MARKET'
) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<SizeInMarket
marketId={marketId}
size={size}
decimalSource={decimalSource}
/>
</MemoryRouter>
</MockedProvider>
);
}
const fullMock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'test dai',
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 18,
},
},
},
},
},
},
},
};
describe('Size in Market component', () => {
it('Renders a dash size when there is no size', () => {
const res = render(renderComponent(undefined, '123', []));
expect(res.getByText('-')).toBeInTheDocument();
});
it('Renders the raw size when there is no market data', () => {
const res = render(renderComponent('100', '123', []));
expect(res.getByText('100')).toBeInTheDocument();
});
it('Renders the formatted size when market data is fetched', async () => {
const res = render(renderComponent('100', '123', [fullMock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
});
});
@@ -0,0 +1,44 @@
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
export type DecimalSource = 'MARKET';
export type PriceInMarketProps = {
marketId: string;
size?: string | number;
decimalSource?: DecimalSource;
};
/**
* Given a market ID and an order size it will fetch the market
* order size, and format the size accordingly
*/
const SizeInMarket = ({
marketId,
size,
decimalSource = 'MARKET',
}: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
});
if (!size) {
return <span>-</span>;
}
let label = size;
if (data) {
if (decimalSource === 'MARKET' && data.market?.positionDecimalPlaces) {
label = addDecimalsFormatNumber(size, data.market.positionDecimalPlaces);
}
}
return (
<label>
<span>{label}</span>
</label>
);
};
export default SizeInMarket;
@@ -3,6 +3,7 @@ import React from 'react';
import classnames from 'classnames';
interface TableProps {
allowWrap?: boolean;
children: React.ReactNode;
className?: string;
}
@@ -25,8 +26,15 @@ interface TableCellProps extends ThHTMLAttributes<HTMLTableCellElement> {
modifier?: 'bordered' | 'background';
}
export const Table = ({ children, className, ...props }: TableProps) => {
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
export const Table = ({
allowWrap,
children,
className,
...props
}: TableProps) => {
const classes = allowWrap
? className
: classnames(className, 'overflow-x-auto whitespace-nowrap');
return (
<div className={classes}>
<table className="w-full" {...props}>
@@ -37,11 +45,14 @@ export const Table = ({ children, className, ...props }: TableProps) => {
};
export const TableWithTbody = ({
allowWrap,
children,
className,
...props
}: TableProps) => {
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
const classes = allowWrap
? className
: classnames(className, 'overflow-x-auto whitespace-nowrap');
return (
<div className={classes}>
<table className="w-full" {...props}>
@@ -76,9 +76,7 @@ describe('Chain Event: Builtin asset deposit', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -34,7 +34,7 @@ export const TxDetailsChainEventBuiltinDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId} /> ({t('built in asset')})
<AssetLink assetId={deposit.vegaAssetId} /> ({t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -82,9 +82,7 @@ describe('Chain Event: Builtin asset withdrawal', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -39,8 +39,8 @@ export const TxDetailsChainEventBuiltinWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId || ''} /> ({t('built in asset')}
)
<AssetLink assetId={withdrawal.vegaAssetId || ''} /> (
{t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -63,9 +63,7 @@ describe('Chain Event: ERC20 Asset Delist', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -29,7 +29,7 @@ export const TxDetailsChainEventErc20AssetDelist = ({
<TableRow modifier="bordered">
<TableCell>{t('Removed Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetDelist.vegaAssetId || ''} />
<AssetLink assetId={assetDelist.vegaAssetId || ''} />
</TableCell>
</TableRow>
</>
@@ -79,10 +79,8 @@ describe('Chain Event: ERC20 Asset limits updated', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
@@ -51,7 +51,7 @@ export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
<TableRow modifier="bordered">
<TableCell>{t('Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetLimitsUpdated.vegaAssetId} />
<AssetLink assetId={assetLimitsUpdated.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -65,10 +65,8 @@ describe('Chain Event: ERC20 Asset List', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.assetSource}`);
@@ -41,7 +41,7 @@ export const TxDetailsChainEventErc20AssetList = ({
<TableRow modifier="bordered">
<TableCell>{t('Added Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetList.vegaAssetId} />
<AssetLink assetId={assetList.vegaAssetId} />
</TableCell>
</TableRow>
</>
@@ -75,10 +75,8 @@ describe('Chain Event: ERC20 asset deposit', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
@@ -51,7 +51,7 @@ export const TxDetailsChainEventDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId} />
<AssetLink assetId={deposit.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -60,10 +60,8 @@ describe('Chain Event: ERC20 asset deposit', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
@@ -45,7 +45,7 @@ export const TxDetailsChainEventWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId} />
<AssetLink assetId={withdrawal.vegaAssetId} />
</TableCell>
</TableRow>
</>
@@ -0,0 +1,108 @@
import { getValues } from './bound-factors';
import type { components } from '../../../../../types/explorer';
type KeyValueBundle = components['schemas']['vegaKeyValueBundle'][];
describe('getValues', () => {
it('handles an empty array by returning a dashed template', () => {
const res = getValues([]);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '-');
expect(res.up).toHaveProperty('value', '-');
});
it('handles undefined', () => {
const res = getValues(undefined as unknown as KeyValueBundle);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '-');
expect(res.up).toHaveProperty('value', '-');
});
it('handles a kvb that only has one side (should not happen)', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
it('handles a kvb that has a matrixVal instead of a scalarval by ignoring it', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { matrixVal: { value: [{ value: ['0.123'] }] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '-');
});
it('ignores unexpected extra values in the kvb', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123', '0.77'] } },
},
{
key: 'down',
tolerance: '0.001',
value: { vectorVal: { value: ['0.321'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '0.001');
expect(res.down).toHaveProperty('value', '0.321');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
it('handles a full kvb', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123'] } },
},
{
key: 'down',
tolerance: '0.001',
value: { vectorVal: { value: ['0.321'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '0.001');
expect(res.down).toHaveProperty('value', '0.321');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
});
@@ -0,0 +1,87 @@
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
interface StateVariableProposalBoundFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* A dumb as rocks function completely tied to what the structure of this variable should be
* @param kvb The key/value bundle
* @returns Object
*/
export function getValues(kvb: StateVariableProposalBoundFactorsProps['kvb']) {
const template = {
up: {
tolerance: '-',
value: '-',
},
down: {
tolerance: '-',
value: '-',
},
};
if (kvb && kvb.length > 0) {
kvb.forEach((v) => {
if (v.key === 'up') {
template.up.tolerance = v.tolerance || '-';
template.up.value = v.value?.vectorVal?.value
? v.value?.vectorVal.value[0]
: '-';
} else if (v.key === 'down') {
template.down.tolerance = v.tolerance || '-';
template.down.value = v.value?.vectorVal?.value
? v.value?.vectorVal.value[0]
: '-';
}
});
}
return template;
}
/**
* State Variable proposals updating Bound Factors. This contains two bundles,
* an up vector and a down vector
*
* This is nearly identical to risk factors.
*/
export const StateVariableProposalBoundFactors = ({
kvb,
}: StateVariableProposalBoundFactorsProps) => {
const v = getValues(kvb);
return (
<Table allowWrap={true} className="w-1/3">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Parameter')}</TableHeader>
<TableHeader align="center">{t('New value')}</TableHeader>
<TableHeader align="right">{t('Tolerance')}</TableHeader>
</TableRow>
</thead>
<tbody>
<TableRow modifier="bordered">
<TableCell>{t('Up')}</TableCell>
<TableCell align="right" className="font-mono">
{v.up.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.up.tolerance}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Down')}</TableCell>
<TableCell align="right" className="font-mono">
{v.down.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.down.tolerance}
</TableCell>
</TableRow>
</tbody>
</Table>
);
};
@@ -0,0 +1,31 @@
import type { components } from '../../../../../types/explorer';
import { StateVariableProposalUnknown } from './unknown';
import { StateVariableProposalBoundFactors } from './bound-factors';
import { StateVariableProposalRiskFactors } from './risk-factors';
interface StateVariableProposalWrapperProps {
stateVariable: string | undefined;
kvb: readonly components['schemas']['vegaKeyValueBundle'][] | undefined;
}
/**
* State Variable proposals
*/
export const StateVariableProposalWrapper = ({
stateVariable,
kvb,
}: StateVariableProposalWrapperProps) => {
if (!stateVariable || !kvb || kvb.length === 0) {
return null;
}
if (stateVariable.indexOf('bound-factors') !== -1) {
return <StateVariableProposalBoundFactors kvb={kvb} />;
} else if (stateVariable.indexOf('risk-factors') !== -1) {
return <StateVariableProposalRiskFactors kvb={kvb} />;
} else if (stateVariable.indexOf('probability_of_trading') !== -1) {
return <StateVariableProposalRiskFactors kvb={kvb} />;
} else {
return <StateVariableProposalUnknown kvb={kvb} />;
}
};
@@ -0,0 +1,124 @@
import { t } from '@vegaprotocol/react-helpers';
import zip from 'lodash/zip';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
import { StateVariableProposalUnknown } from './unknown';
interface StateVariableProposalRiskFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* A dumb as rocks function completely tied to what the structure of this variable should be
*
* @param kvb The key/value bundle
* @returns Object
*/
export function getValues(kvb: StateVariableProposalRiskFactorsProps['kvb']) {
try {
const template = {
bid: {
offsetTolerance: '-',
probabilityTolerance: '-',
offset: [] as Readonly<string[]>,
probability: [] as Readonly<string[]>,
rows: [] as [string | undefined, string | undefined][],
},
ask: {
offsetTolerance: '-',
probabilityTolerance: '-',
offset: [] as Readonly<string[]>,
probability: [] as Readonly<string[]>,
rows: [] as [string | undefined, string | undefined][],
},
};
kvb.forEach((v) => {
if (v.key === 'bidOffset') {
template.bid.offsetTolerance = v.tolerance || '-';
template.bid.offset = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'bidProbability') {
template.bid.probabilityTolerance = v.tolerance || '-';
template.bid.probability = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'askOffset') {
template.ask.offsetTolerance = v.tolerance || '-';
template.ask.offset = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'askProbability') {
template.ask.probabilityTolerance = v.tolerance || '-';
template.ask.probability = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
}
});
// Bundles up offset and probability in to a row
if (template.bid.offset.length > 0 && template.bid.probability.length > 0) {
template.bid.rows = zip(template.bid.offset, template.bid.probability);
}
if (template.ask.offset.length > 0 && template.ask.probability.length > 0) {
template.ask.rows = zip(template.ask.offset, template.ask.probability);
}
return template;
} catch (e) {
// This will result in the table not being rendered
return null;
}
}
/**
* State Variable proposals updating Risk Factors. This contains two bundles,
* a long vector and a short vector
*/
export const StateVariableProposalRiskFactors = ({
kvb,
}: StateVariableProposalRiskFactorsProps) => {
const v = getValues(kvb);
const all = v ? zip(v.bid.rows, v.ask.rows) : [];
if (all.length === 0) {
// Give up, do a JSON view
return <StateVariableProposalUnknown kvb={kvb} />;
}
return (
<Table allowWrap={true} className="text-xs lg:text-base max-w-2xl">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Mid offset')}</TableHeader>
<TableHeader align="right">{t('Bid probability')}</TableHeader>
<TableHeader align="right" className="pl-2">
{t('Ask probability')}
</TableHeader>
</TableRow>
</thead>
<tbody>
{all.map((r) => {
// Simple remapping of the data to protect against undefineds
const row = {
o: r[0] ? r[0][0] : r[1] ? r[1][0] : '-',
b: r[0] ? r[0][1] : '-',
a: r[1] ? r[1][1] : '-',
};
return (
<TableRow key={`${row.o}${row.b}${row.a}`}>
<TableCell align="left">{row.o}</TableCell>
<TableCell align="right" className="font-mono">
{row.b}
</TableCell>
<TableCell align="right" className="pl-2 font-mono">
{row.a}
</TableCell>
</TableRow>
);
})}
</tbody>
</Table>
);
};
@@ -0,0 +1,168 @@
import { getValues, StateVariableProposalRiskFactors } from './risk-factors';
import type { components } from '../../../../../types/explorer';
import { render } from '@testing-library/react';
type kvb = components['schemas']['vegaKeyValueBundle'][];
describe('Risk Factors: getValues', () => {
it('returns null if null is passed in', () => {
const res = getValues(null as unknown as kvb);
expect(res).toBeNull();
});
it('returns a blank template if kvb is empty', () => {
const res = getValues([]);
expect(res).not.toBeNull();
expect(res?.bid.offsetTolerance).toEqual('-');
expect(res?.bid.probabilityTolerance).toEqual('-');
expect(res?.bid.probability).toEqual([]);
expect(res?.bid.offset).toEqual([]);
expect(res?.bid.rows.length).toEqual(0);
expect(res?.ask.offsetTolerance).toEqual('-');
expect(res?.ask.probabilityTolerance).toEqual('-');
expect(res?.ask.probability).toEqual([]);
expect(res?.ask.offset).toEqual([]);
expect(res?.ask.rows.length).toEqual(0);
});
it('parses out a correct bid offset and probability', () => {
const k: kvb = [
{
key: 'bidOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['2'] } },
},
];
const res = getValues(k);
expect(res?.bid.offset).toEqual(['1']);
expect(res?.bid.probability).toEqual(['2']);
expect(res?.bid.rows).toEqual([['1', '2']]);
});
it('parses out a correct ask offset and probability', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2'] } },
},
];
const res = getValues(k);
expect(res?.ask.offset).toEqual(['1']);
expect(res?.ask.probability).toEqual(['2']);
expect(res?.ask.rows).toEqual([['1', '2']]);
});
it('parses out a correct ask/bid offset and probability', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4'] } },
},
];
const res = getValues(k);
expect(res?.ask.rows).toEqual([['1', '2']]);
expect(res?.bid.rows).toEqual([['3', '4']]);
});
});
describe('Risk Factors: component', () => {
it('renders 3 rows correctly', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2.2', '2.3', '2.4'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
},
];
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
expect(screen.getByText('Mid offset')).toBeInTheDocument();
expect(screen.getByText('Bid probability')).toBeInTheDocument();
expect(screen.getByText('Ask probability')).toBeInTheDocument();
// First row
expect(screen.getByText('1.1')).toBeInTheDocument();
expect(screen.getByText('2.2')).toBeInTheDocument();
expect(screen.getByText('4.4')).toBeInTheDocument();
// Second row
expect(screen.getByText('1.2')).toBeInTheDocument();
expect(screen.getByText('2.3')).toBeInTheDocument();
expect(screen.getByText('4.5')).toBeInTheDocument();
// Third row
expect(screen.getByText('1.3')).toBeInTheDocument();
expect(screen.getByText('2.4')).toBeInTheDocument();
expect(screen.getByText('4.6')).toBeInTheDocument();
});
it('renders uneven row counts correctly', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1.1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2.2'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
},
];
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
expect(screen.getByText('Mid offset')).toBeInTheDocument();
expect(screen.getByText('Bid probability')).toBeInTheDocument();
expect(screen.getByText('Ask probability')).toBeInTheDocument();
// First row, as previous test
expect(screen.getByText('1.1')).toBeInTheDocument();
expect(screen.getByText('2.2')).toBeInTheDocument();
expect(screen.getByText('4.4')).toBeInTheDocument();
// Second row - offset comes from bid, not ask
expect(screen.getByText('1.2')).toBeInTheDocument();
expect(screen.getByText('4.5')).toBeInTheDocument();
// Third row
expect(screen.getByText('1.3')).toBeInTheDocument();
expect(screen.getByText('4.6')).toBeInTheDocument();
// The askOffset levels without a probability render -
expect(screen.getAllByText('-')).toHaveLength(2);
});
});
@@ -0,0 +1,52 @@
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
import { getValues } from './bound-factors';
interface StateVariableProposalBoundFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* State Variable proposals updating Bound Factors. This contains two bundles,
* an up vector and a down vector
*
* This is nearly identical to risk factors.
*/
export const StateVariableProposalBoundFactors = ({
kvb,
}: StateVariableProposalBoundFactorsProps) => {
const v = getValues(kvb);
return (
<Table allowWrap={true} className="w-1/3">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Parameter')}</TableHeader>
<TableHeader align="center">{t('New value')}</TableHeader>
<TableHeader align="right">{t('Tolerance')}</TableHeader>
</TableRow>
</thead>
<tbody>
<TableRow modifier="bordered">
<TableCell>{t('Up')}</TableCell>
<TableCell align="right" className="font-mono">
{v.up.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.up.tolerance}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Down')}</TableCell>
<TableCell align="right" className="font-mono">
{v.down.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.down.tolerance}
</TableCell>
</TableRow>
</tbody>
</Table>
);
};
@@ -0,0 +1,16 @@
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import type { components } from '../../../../../types/explorer';
interface StateVariableProposalUnknownProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* State Variable proposals of an unknown type. Let's just dump
* it out.
*/
export const StateVariableProposalUnknown = ({
kvb,
}: StateVariableProposalUnknownProps) => {
return <SyntaxHighlighter data={kvb} />;
};
@@ -53,7 +53,7 @@ export const TxDetailsBatch = ({
let index = 0;
return (
<div key={`tx-${index}`}>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -32,7 +32,7 @@ export const TxDetailsChainEvent = ({
}
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<ChainEvent txData={txData} />
</TableWithTbody>
@@ -38,7 +38,7 @@ export const TxDetailsDataSubmission = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -35,7 +35,7 @@ export const TxDetailsDelegate = ({
txData.command.delegateSubmission;
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{d.nodeId ? (
<TableRow modifier="bordered">
@@ -0,0 +1,62 @@
import { t } from '@vegaprotocol/react-helpers';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { BlockLink } from '../../links';
import { StatusMessage } from '../../status-message';
import { ENV } from '../../../config/env';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { components } from '../../../../types/explorer';
interface TxDetailsProtocolUpgradeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Validator event: Protocol Upgrade proposal
*/
export const TxDetailsProtocolUpgrade = ({
txData,
pubKey,
blockData,
}: TxDetailsProtocolUpgradeProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const upgrade: components['schemas']['v1ProtocolUpgradeProposal'] =
txData.command.protocolUpgradeProposal;
if (!upgrade || !upgrade.upgradeBlockHeight || !upgrade.vegaReleaseTag) {
return (
<StatusMessage>{t('Invalid upgrade proposal format')}</StatusMessage>
);
}
const urlBase = ENV.dataSources.vegaRepoUrl;
const release = upgrade.vegaReleaseTag;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Upgrade at block')}</TableCell>
<TableCell>
<BlockLink height={upgrade.upgradeBlockHeight} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Upgrade to')}</TableCell>
<TableCell>
<ExternalLink href={`${urlBase}${release}`}>
{upgrade.vegaReleaseTag}
</ExternalLink>
</TableCell>
</TableRow>
</TableWithTbody>
);
};
@@ -19,6 +19,10 @@ import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission';
import { TxProposalVote } from './tx-proposal-vote';
import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
import { TxDetailsIssueSignatures } from './tx-issue-signatures';
import { TxDetailsNodeAnnounce } from './tx-node-announce';
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -66,10 +70,16 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
// These come from https://github.com/vegaprotocol/vega/blob/develop/core/txn/command.go#L72-L98
switch (txData.type) {
case 'Register new Node':
return TxDetailsNodeAnnounce;
case 'Issue Signatures':
return TxDetailsIssueSignatures;
case 'Submit Order':
return TxDetailsOrder;
case 'Submit Oracle Data':
return TxDetailsDataSubmission;
case 'Protocol Upgrade':
return TxDetailsProtocolUpgrade;
case 'Cancel Order':
return TxDetailsOrderCancel;
case 'Amend Order':
@@ -96,6 +106,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsDelegate;
case 'Undelegate':
return TxDetailsUndelegate;
case 'State Variable Proposal':
return TxDetailsStateVariable;
default:
return TxDetailsGeneric;
}
@@ -0,0 +1,80 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
import { BlockLink } from '../../links';
type EthKeyRotate = components['schemas']['v1EthereumKeyRotateSubmission'];
interface TxDetailsEthKeyRotateProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* A node is changing ethereum key
*/
export const TxDetailsEthKeyRotate = ({
txData,
pubKey,
blockData,
}: TxDetailsEthKeyRotateProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const k: EthKeyRotate = txData.command;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{k.targetBlock ? (
<TableRow modifier="bordered">
<TableCell>{t('Target block')}</TableCell>
<TableCell>
<BlockLink height={k.targetBlock} />
</TableCell>
</TableRow>
) : null}
{k.currentAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Old Address')}</TableCell>
<TableCell>
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={k.currentAddress}
/>
</TableCell>
</TableRow>
) : null}
{k.newAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('New Address')}</TableCell>
<TableCell>
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={k.newAddress}
/>
</TableCell>
</TableRow>
) : null}
{k.submitterAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Submitter address')}</TableCell>
<TableCell>
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={k.submitterAddress}
/>
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};
@@ -11,8 +11,7 @@ interface TxDetailsGenericProps {
}
/**
* If there is not yet a custom component for a transaction, just display
* the basic details. This allows someone to view the decoded transaction.
* A node is
*/
export const TxDetailsGeneric = ({
txData,
@@ -24,7 +23,7 @@ export const TxDetailsGeneric = ({
}
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
</TableWithTbody>
);
@@ -60,7 +60,7 @@ export const TxDetailsHeartbeat = ({
const blockHeight = txData.command.blockHeight || '';
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Node')}</TableCell>
@@ -0,0 +1,77 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
import { NodeLink } from '../../links';
type Command = components['schemas']['v1IssueSignatures'];
const kind: Record<components['schemas']['v1NodeSignatureKind'], string> = {
NODE_SIGNATURE_KIND_UNSPECIFIED: 'Unspecified',
NODE_SIGNATURE_KIND_ASSET_NEW: 'New asset',
NODE_SIGNATURE_KIND_ASSET_WITHDRAWAL: 'Asset withdrawal',
NODE_SIGNATURE_KIND_ASSET_UPDATE: ' Asset update',
NODE_SIGNATURE_KIND_ERC20_MULTISIG_SIGNER_ADDED: 'Multisig signer added',
NODE_SIGNATURE_KIND_ERC20_MULTISIG_SIGNER_REMOVED: 'Multisig signer removed',
};
interface TxDetailsGenericProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* If there is not yet a custom component for a transaction, just display
* the basic details. This allows someone to view the decoded transaction.
*/
export const TxDetailsIssueSignatures = ({
txData,
pubKey,
blockData,
}: TxDetailsGenericProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd: Command = txData.command;
const k = cmd.kind ? kind[cmd.kind] : null;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{k ? (
<TableRow modifier="bordered">
<TableCell>{t('Kind')}</TableCell>
<TableCell>{k}</TableCell>
</TableRow>
) : null}
{cmd.submitter ? (
<TableRow modifier="bordered">
<TableCell>{t('ETH key')}</TableCell>
<TableCell>
<EthExplorerLink
id={cmd.submitter}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
{cmd.validatorNodeId ? (
<TableRow modifier="bordered">
<TableCell>{t('Validator')}</TableCell>
<TableCell>
<NodeLink id={cmd.validatorNodeId} />
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};
@@ -0,0 +1,67 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { BlockLink, PartyLink } from '../../links';
type KeyRotate = components['schemas']['v1KeyRotateSubmission'];
interface TxDetailsKeyRotateProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* A node is changing Vega key
*/
export const TxDetailsKeyRotate = ({
txData,
pubKey,
blockData,
}: TxDetailsKeyRotateProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const k: KeyRotate = txData.command;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{k.targetBlock ? (
<TableRow modifier="bordered">
<TableCell>{t('Target block')}</TableCell>
<TableCell>
<BlockLink height={k.targetBlock} />
</TableCell>
</TableRow>
) : null}
{k.currentPubKeyHash ? (
<TableRow modifier="bordered">
<TableCell>{t('Old Address')}</TableCell>
<TableCell>
<PartyLink id={k.currentPubKeyHash} />
</TableCell>
</TableRow>
) : null}
{k.currentPubKeyHash ? (
<TableRow modifier="bordered">
<TableCell>{t('New Address')}</TableCell>
<TableCell>
<PartyLink id={k.currentPubKeyHash} />
</TableCell>
</TableRow>
) : null}
{k.newPubKeyIndex ? (
<TableRow modifier="bordered">
<TableCell>{t('Key index')}</TableCell>
<TableCell>
<code>{k.newPubKeyIndex}</code>
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};
@@ -36,7 +36,7 @@ export const TxDetailsLiquidityAmendment = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -1,10 +1,11 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
export type LiquidityCancellation =
components['schemas']['v1LiquidityProvisionCancellation'];
@@ -36,7 +37,7 @@ export const TxDetailsLiquidityCancellation = ({
const marketId: string = cancel.marketId || '-';
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
@@ -35,7 +35,7 @@ export const TxDetailsLiquiditySubmission = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -0,0 +1,112 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
import { PartyLink } from '../../links';
import Hash from '../../links/hash';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
type Command = components['schemas']['v1AnnounceNode'];
interface TxDetailsNodeAnnounceProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* When a new potential validator node comes online, it announces
* itself with this transaction.
*
* Design decisions:
* - Signatures are not rendered. You can still access them via the
* TX details. This is consistent with explorers for other chains
* - The avatar icon is rendered as a link rather than embedding
*/
export const TxDetailsNodeAnnounce = ({
txData,
pubKey,
blockData,
}: TxDetailsNodeAnnounceProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd: Command = txData.command.announceNode;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{cmd.name ? (
<TableRow modifier="bordered">
<TableCell>{t('Name')}</TableCell>
<TableCell>
<span>{cmd.name}</span>
</TableCell>
</TableRow>
) : null}
{cmd.id ? (
<TableRow modifier="bordered">
<TableCell>{t('ID')}</TableCell>
<TableCell>
<Hash text={cmd.id} />
</TableCell>
</TableRow>
) : null}
{cmd.chainPubKey ? (
<TableRow modifier="bordered">
<TableCell>{t('Chain public key')}</TableCell>
<TableCell>
<Hash text={cmd.chainPubKey} />
</TableCell>
</TableRow>
) : null}
{cmd.ethereumAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Ethereum Address')}</TableCell>
<TableCell>
<EthExplorerLink
type={EthExplorerLinkTypes.address}
id={cmd.ethereumAddress}
/>
</TableCell>
</TableRow>
) : null}
{cmd.vegaPubKey ? (
<TableRow modifier="bordered">
<TableCell>{t('Vega public key')}</TableCell>
<TableCell>
<PartyLink id={cmd.vegaPubKey} />
</TableCell>
</TableRow>
) : null}
{cmd.avatarUrl ? (
<TableRow modifier="bordered">
<TableCell>{t('Avatar URL')}</TableCell>
<TableCell>
<ExternalLink href={cmd.avatarUrl} rel="noreferrer noopener">
{cmd.avatarUrl}
</ExternalLink>
</TableCell>
</TableRow>
) : null}
{cmd.infoUrl ? (
<TableRow modifier="bordered">
<TableCell>{t('Info link')}</TableCell>
<TableCell>
<ExternalLink href={cmd.infoUrl} rel="noreferrer noopener">
{cmd.infoUrl}
</ExternalLink>
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};
@@ -42,7 +42,7 @@ export const TxDetailsNodeVote = ({
}
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{data && !!data.deposit
? TxDetailsNodeVoteDeposit({ deposit: data })
@@ -29,7 +29,7 @@ export const TxDetailsOrderAmend = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -29,7 +29,7 @@ export const TxDetailsOrderCancel = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -39,7 +39,7 @@ export const TxDetailsOrder = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -32,7 +32,7 @@ export const TxProposalVote = ({
const vote = txData.command.voteSubmission.value ? '👍' : '👎';
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
@@ -0,0 +1,57 @@
import {
hackyGetMarketFromStateVariable,
hackyGetVariableFromStateVariable,
} from './tx-state-variable-proposal';
describe('Hacky Get market from state variable', () => {
it('Extracts a market id from a known state variable proposal id', () => {
const knownId =
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f_5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
const res = hackyGetMarketFromStateVariable(knownId);
expect(res).toEqual(
'5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba'
);
});
it('Returns null if the string looks a bit like the known one, but with different segments', () => {
const knownId =
'5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
const res = hackyGetMarketFromStateVariable(knownId);
expect(res).toEqual(null);
});
it('Handles empty/weird data', () => {
expect(hackyGetMarketFromStateVariable(null as unknown as string)).toEqual(
null
);
expect(hackyGetMarketFromStateVariable('')).toEqual(null);
expect(
hackyGetMarketFromStateVariable(undefined as unknown as string)
).toEqual(null);
expect(hackyGetMarketFromStateVariable(2 as unknown as string)).toEqual(
null
);
});
});
describe('Hacky Get Variable from state variable proposal id', () => {
it('Extracts an variable name from a known state variable proposal id', () => {
const knownId =
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f_5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
const res = hackyGetVariableFromStateVariable(knownId);
expect(res).toEqual('probability of trading');
});
it('Handles empty/weird data', () => {
expect(
hackyGetVariableFromStateVariable(null as unknown as string)
).toEqual(null);
expect(hackyGetVariableFromStateVariable('')).toEqual(null);
expect(
hackyGetVariableFromStateVariable(undefined as unknown as string)
).toEqual(null);
expect(hackyGetVariableFromStateVariable(2 as unknown as string)).toEqual(
null
);
});
});
@@ -0,0 +1,113 @@
import { t } from '@vegaprotocol/react-helpers';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { MarketLink } from '../../links';
import type { components } from '../../../../types/explorer';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { StateVariableProposalWrapper } from './state-variable/data-wrapper';
interface TxDetailsStateVariableProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* There is no market ID in the event, but it appears to be encoded in to the variable
* ID so let's pull it out. MarketLink component will handle if it isn't a real market.
*
* Given how liable to break this is, it's wrapped in a try catch
*
* @param stateVarId The full state variable proposal variable name
* @returns null or a string market id
*/
export function hackyGetMarketFromStateVariable(
stateVarId?: string
): string | null {
try {
const res = stateVarId ? stateVarId.split('_')[1] : null;
return res && res.length === 64 ? res : null;
} catch (e) {
return null;
}
}
/**
* There is no event name in the event, but it appears to be encoded in to the variable
* ID so let's pull it out. Will display nothing if it doesn't parse as expected
*
* Given how liable to break this is, it's wrapped in a try catch
*
* @param stateVarId The full state variable proposal variable name
* @returns null or a string variable name
*/
export function hackyGetVariableFromStateVariable(
stateVarId?: string
): string | null {
try {
if (!stateVarId) {
return null;
}
return stateVarId.split('_').slice(2).join(' ').replace('-', ' ');
} catch (e) {
return null;
}
}
/**
* State Variable proposals
*/
export const TxDetailsStateVariable = ({
txData,
pubKey,
blockData,
}: TxDetailsStateVariableProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const command: components['schemas']['v1StateVariableProposal'] =
txData.command.stateVariableProposal;
const variable = hackyGetVariableFromStateVariable(
command.proposal?.stateVarId
);
const marketId = hackyGetMarketFromStateVariable(
command.proposal?.stateVarId
);
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
{marketId ? (
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Variable')}</TableCell>
<TableCell className="capitalize">
<span>{variable}</span>
</TableCell>
</TableRow>
</TableWithTbody>
<section>
<StateVariableProposalWrapper
stateVariable={command.proposal?.stateVarId}
kvb={command.proposal?.kvb}
/>
</section>
</>
);
};
@@ -46,7 +46,7 @@ export const TxDetailsUndelegate = ({
txData.command.undelegateSubmission;
return (
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{u.nodeId ? (
<TableRow modifier="bordered">
@@ -41,7 +41,7 @@ export const TxDetailsWithdrawSubmission = ({
return (
<>
<TableWithTbody className="mb-8">
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -63,7 +63,7 @@ export const TxDataView = ({ txData, blockData }: TxDataViewProps) => {
<Select
placeholder="View as..."
onChange={(v) => setShowTxData(v.target.value as ShowTxDataType)}
value={'JSON'}
value={showTxData}
>
<option value={'JSON'}>JSON</option>
<option value={'base64'}>Base64</option>
@@ -0,0 +1,4 @@
/**
* Equivalent of tailwind's `md` modifier
*/
export const BREAKPOINT_MD = 768;
+5
View File
@@ -10,6 +10,7 @@ const truthy = ['1', 'true'];
export const ENV = {
// Data sources
// Environment
env: windowOrDefault('NX_VEGA_ENV'),
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
dataSources: {
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
@@ -17,6 +18,7 @@ export const ENV = {
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
},
flags: {
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),
@@ -31,4 +33,7 @@ export const ENV = {
parties: truthy.includes(windowOrDefault('NX_EXPLORER_PARTIES')),
validators: truthy.includes(windowOrDefault('NX_EXPLORER_VALIDATORS')),
},
addresses: {
feedback: windowOrDefault('NX_GITHUB_FEEDBACK_URL'),
},
};
+5 -4
View File
@@ -5,6 +5,7 @@ import type {
BlockExplorerTransactions,
} from '../routes/types/block-explorer-response';
import { DATA_SOURCES } from '../config';
import isNumber from 'lodash/isNumber';
export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[];
@@ -54,15 +55,15 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => {
} = useFetch<BlockExplorerTransactions>(url, {}, false);
useEffect(() => {
if (data?.transactions?.length) {
if (data && isNumber(data?.transactions?.length)) {
setTxsState((prev) => ({
txsData: [...prev.txsData, ...data.transactions],
hasMoreTxs: true,
hasMoreTxs: data.transactions.length > 0,
lastCursor:
data.transactions[data.transactions.length - 1].cursor || '',
data.transactions[data.transactions.length - 1]?.cursor || '',
}));
}
}, [setTxsState, data?.transactions]);
}, [setTxsState, data]);
const loadTxs = useCallback(() => {
return refetch({
+134
View File
@@ -0,0 +1,134 @@
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetDocument } from '@vegaprotocol/assets';
import { AssetsDocument } from '@vegaprotocol/assets';
import { AssetStatus } from '@vegaprotocol/types';
const A1: AssetFieldsFragment = {
__typename: 'Asset',
id: '123',
name: 'A ONE',
symbol: 'A1',
decimals: 0,
quantum: '',
status: AssetStatus.STATUS_ENABLED,
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '',
},
infrastructureFeeAccount: {
__typename: 'AccountBalance',
balance: '',
},
globalRewardPoolAccount: {
__typename: 'AccountBalance',
balance: '',
},
lpFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
makerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
marketProposerRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
takerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
};
const A2: AssetFieldsFragment = {
__typename: 'Asset',
id: '456',
name: 'A TWO',
symbol: 'A2',
decimals: 0,
quantum: '',
status: AssetStatus.STATUS_ENABLED,
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '',
},
infrastructureFeeAccount: {
__typename: 'AccountBalance',
balance: '',
},
globalRewardPoolAccount: {
__typename: 'AccountBalance',
balance: '',
},
lpFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
makerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
marketProposerRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
takerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
};
export const assetsList = [A1, A2];
export const mockAssetsList = {
request: {
query: AssetsDocument,
},
result: {
data: {
assetsConnection: {
__typename: 'AssetsConnection',
edges: [
{
__typename: 'AssetEdge',
node: A1,
},
{
__typename: 'AssetEdge',
node: A2,
},
],
},
},
},
};
export const mockEmptyAssetsList = {
request: {
query: AssetsDocument,
},
result: { data: null },
};
export const mockAssetA1 = {
request: {
query: AssetDocument,
variables: {
assetId: '123',
},
},
result: {
data: {
assetsConnection: {
__typename: 'AssetsConnection',
edges: [
{
__typename: 'AssetEdge',
node: A1,
},
],
},
},
},
};
@@ -1,32 +0,0 @@
fragment AssetsFields on Asset {
id
name
symbol
decimals
source {
... on ERC20 {
contractAddress
}
... on BuiltinAsset {
maxFaucetAmountMint
}
}
infrastructureFeeAccount {
type
balance
market {
id
}
}
}
query ExplorerAssets {
assetsConnection {
edges {
node {
...AssetsFields
}
}
}
}
@@ -1,73 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type AssetsFieldsFragment = { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string }, infrastructureFeeAccount?: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string } | null } | null };
export type ExplorerAssetsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerAssetsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string }, infrastructureFeeAccount?: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string } | null } | null } } | null> | null } | null };
export const AssetsFieldsFragmentDoc = gql`
fragment AssetsFields on Asset {
id
name
symbol
decimals
source {
... on ERC20 {
contractAddress
}
... on BuiltinAsset {
maxFaucetAmountMint
}
}
infrastructureFeeAccount {
type
balance
market {
id
}
}
}
`;
export const ExplorerAssetsDocument = gql`
query ExplorerAssets {
assetsConnection {
edges {
node {
...AssetsFields
}
}
}
}
${AssetsFieldsFragmentDoc}`;
/**
* __useExplorerAssetsQuery__
*
* To run a query within a React component, call `useExplorerAssetsQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerAssetsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerAssetsQuery({
* variables: {
* },
* });
*/
export function useExplorerAssetsQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>(ExplorerAssetsDocument, options);
}
export function useExplorerAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>(ExplorerAssetsDocument, options);
}
export type ExplorerAssetsQueryHookResult = ReturnType<typeof useExplorerAssetsQuery>;
export type ExplorerAssetsLazyQueryHookResult = ReturnType<typeof useExplorerAssetsLazyQuery>;
export type ExplorerAssetsQueryResult = Apollo.QueryResult<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>;
@@ -0,0 +1,50 @@
import { t } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { useParams } from 'react-router-dom';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { useState } from 'react';
export const AssetPage = () => {
useDocumentTitle(['Assets']);
useScrollToLocation();
const { assetId } = useParams<{ assetId: string }>();
const { data, loading, error } = useAssetDataProvider(assetId || '');
const title = data ? data.name : error ? t('Asset not found') : '';
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
return (
<>
<section className="relative">
<RouteTitle data-testid="asset-header">{title}</RouteTitle>
<AsyncRenderer
noDataMessage={t('Asset not found')}
data={data}
loading={loading}
error={error}
>
<div className="absolute top-0 right-0">
<Button size="xs" onClick={() => setDialogOpen(true)}>
{t('View JSON')}
</Button>
</div>
<div className="h-full relative">
<AssetDetailsTable asset={data as AssetFieldsFragment} />
</div>
</AsyncRenderer>
</section>
<JsonViewerDialog
open={dialogOpen}
onChange={(isOpen) => setDialogOpen(isOpen)}
title={data?.name || ''}
content={data}
/>
</>
);
};
@@ -0,0 +1,30 @@
import { t } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { AssetsTable } from '../../components/assets/assets-table';
export const AssetsPage = () => {
useDocumentTitle(['Assets']);
useScrollToLocation();
const { data, loading, error } = useAssetsDataProvider();
return (
<section>
<RouteTitle data-testid="assets-header">{t('Assets')}</RouteTitle>
<AsyncRenderer
noDataMessage={t('This chain has no assets')}
data={data}
loading={loading}
error={error}
>
<div className="h-full relative">
<AssetsTable data={data} />
</div>
</AsyncRenderer>
</section>
);
};

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