Compare commits

...
Author SHA1 Message Date
Alex 73049215dd Add path prefix for header links (manifest, theme-setter, etc.) 2024-03-29 21:48:11 -07:00
Alex cc782b5eae Remove '.git' from .dockerignore; the Vega build process uses git commands ('rev-parse') to navigate 2024-03-29 19:59:16 -07:00
Alessio 703792fbda Add a simple path prefix for static assets 2024-03-29 19:59:16 -07:00
daro-maj 029357b5ab fix(trading): fix market sim tests (#6132) 2024-03-29 10:21:23 +00:00
m.ray 954b4b2755 fix(trading): recurring governance transfers after 0.75.1 (#6122) 2024-03-28 15:50:07 +00:00
Matthew Russell d238662c9d fix(trading,governance): align validator testnet name, add chain config (#6133) 2024-03-28 15:00:01 +00:00
Matthew Russell 88251fae4d fix(trading): check for mark price configuration 2024-03-28 14:24:08 +00:00
m.rayandMatthew Russell 17946ae149 feat(trading): show oracle spec in index price stats (#6113)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-03-28 13:09:53 +00:00
Matthew Russell ebb0db3b37 fix(governance): fix market info not showing in proposals (#6104) 2024-03-28 11:09:40 +00:00
Bartłomiej GłowniaandDariusz Majcherczyk df21996707 chore(markets): remove trading mode and type from market query (#6117)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-28 11:09:18 +00:00
Matthew Russell bb2464ddc2 fix(markets): use position decimals in notional calculation (#6125) 2024-03-28 11:07:06 +00:00
Bartłomiej Głownia a77bb06b2f feat(deal-ticket): improve stop order rejection handling (#6084) 2024-03-27 16:11:53 +00:00
Art 1e15032c06 fix(trading): remove the apploader from bootstrap (#6121) 2024-03-27 15:51:55 +00:00
Art e28cdb2463 fix(trading): prevent whole app rerender on price bounds and withdrawals (#6118) 2024-03-27 15:50:53 +00:00
Matthew Russell 840ca63464 feat(trading): use notional for candle calcs (#6119) 2024-03-27 13:32:58 +00:00
m.ray 64fc075f7a fix(trading): do not show alias edit on read only (#6111) 2024-03-27 10:11:37 +00:00
m.ray bfa6be4c1c feat(trading): show rank table in dispatch strategy tooltip (#6078) 2024-03-27 10:02:46 +00:00
Ben 5f8c725c5b chore(trading): back merge run market sim on main (#6103) 2024-03-26 13:21:05 +00:00
Matthew Russell 6d4d0d548e fix(trading): add bg color to depth chart labels to clarify numbers (#6096) 2024-03-26 13:19:49 +00:00
Matthew RussellandArt cd4b0e508e chore(trading): back merge hotfixes (#6099)
Co-authored-by: Art <artur@vegaprotocol.io>
2024-03-26 09:59:13 +00:00
Matthew Russell ed9cd3e0dd fix(positions): getColId error (#6100) 2024-03-26 09:57:41 +00:00
Art 91d00a0520 fix(trading): game results (#6083) 2024-03-25 16:25:50 +00:00
Matthew Russell 0ada3a2f8d fix(markets): use quote asset for settlement in spot markets (#6090) 2024-03-25 12:09:18 +00:00
m.ray 2efe0a7aa1 fix(trading): align checkboxes tp/sl (#6088) 2024-03-22 17:21:39 +00:00
Matthew Russell 678ba9c4f7 fix(deal-ticket): broken test 2024-03-22 17:09:58 +00:00
Matthew Russell 4d566f5a5f fix(types): duplicate deposit status mapping 2024-03-22 17:07:39 +00:00
m.ray f13307aa77 feat(trading): align deal ticket checkboxes (#6082) 2024-03-22 16:54:45 +00:00
Art aed45e419d chore(trading): date format on team profile page (#6071) 2024-03-22 13:36:25 +00:00
Artandbwallacee 01e87443ee fix(positions): rework of the liquidation tooltip (#6070)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-22 13:36:05 +00:00
Matthew Russell 891e0d3d2f fix(trading): wrong team image showing (#6081) 2024-03-22 13:35:29 +00:00
m.ray dc81334aec feat(trading): games container search bar (#6077) 2024-03-22 13:34:40 +00:00
Bartłomiej Głownia 0a70c0ea49 feat(deal-ticket): use order price if markPrice is not available in useMaxSize (#6057) 2024-03-22 12:21:00 +01:00
Ben 05debbd777 chore(trading): tests for market to auction (#6073) 2024-03-21 15:18:48 +00:00
Matthew Russell 8c383d8756 fix(markets): make trading app spot compatible (#6067) 2024-03-21 14:32:22 +00:00
Ben 3e61018d89 chore(trading): update teams tests (#6069) 2024-03-21 13:04:13 +00:00
Ben 77b1306f25 chore(trading): capped rewards e2e test (#6066) 2024-03-21 09:53:27 +00:00
daro-maj ff3cddc21b chore(trading): update the vega version for sim tests 75.1 (#6065) 2024-03-21 10:52:42 +01:00
daro-maj 1d2ca52a25 chore(trading): test filtered future cards (#6029) 2024-03-21 07:42:09 +00:00
ArtandDariusz Majcherczyk 62ecaaa9ce feat(trading): team profile improvements (#6054)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-20 18:17:05 +01:00
Bartłomiej Głownia 204871b81c feat(deal-ticket): add useMaxSize unit tests (#6053) 2024-03-20 15:25:30 +01:00
m.ray ecdd917977 fix(trading): do not show tick or cross for rewards that will start in the future (#6052) 2024-03-20 13:10:35 +00:00
Matthew Russell c0396db2b1 fix(governance): withdrawals dissapearing when the withdraw dialog is opened (#6045) 2024-03-19 15:33:08 +00:00
m.ray 1fd1013353 feat(trading): rewards container tweaks (#6037) 2024-03-19 12:53:03 +00:00
Bartłomiej Głownia e389579537 feat(deal-ticket): add size slider (#6006) 2024-03-19 12:49:31 +01:00
m.ray 74f0f7bb3d feat(trading): show inactive rewards (#6031) 2024-03-19 11:29:48 +00:00
Edd 3e78d55c0e feat(emblem): create example emblem component (#6013) 2024-03-19 11:10:21 +00:00
Matthew Russell 1df8ba0972 chore(trading): move meta tag to remove nextjs warning (#6030) 2024-03-19 11:04:56 +00:00
daro-maj e515928d87 chore(trading): update sim-tests v0.75.0-preview.8 (#6025) 2024-03-18 14:25:39 +00:00
Matthew Russell 2928181884 fix(governance): hard coded default chain in vega wallet (#6026) 2024-03-18 11:45:46 +00:00
Edd f6f3fb43eb fix(explorer): fix party filter persistence (#6016) 2024-03-15 16:59:45 +00:00
Edd 1511772849 fix(explorer): fix lp tx filters (#6015) 2024-03-15 16:59:36 +00:00
Edd ab6c67fd37 fix(markets): fix link to explorer oracle (#6020) 2024-03-15 16:59:01 +00:00
Edd f4212724d0 feat(explorer): add full details to oracle page (#6005) 2024-03-15 16:43:57 +00:00
m.rayandDariusz Majcherczyk 7b162104b6 feat(trading): eligibility active rewards (#6010)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-15 16:41:48 +00:00
ArtandDariusz Majcherczyk 88f99031d1 feat(markets): new price monitoring bounds panel (#5996)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-15 16:22:58 +00:00
Matthew Russell 72df08851c chore(trading): fix ssr loader server mismatch, remove preload (#6018) 2024-03-15 16:00:04 +00:00
Matthew Russell d9292b9be2 fix(markets,explorer): fallback to ethereum mainnet chain Id if sourceChain is not provided (#6014) 2024-03-15 11:10:54 +00:00
m.ray be8de04686 feat(trading): add cap reward fee multiple on the reward cards (#5999) 2024-03-14 16:42:23 +00:00
Matthew Russell bd14c2a39a chore(trading,governance,explorer): remove theme-lite and tailwind warning (#6011) 2024-03-14 16:27:10 +00:00
Matthew Russell 8e2ec8ab67 chore(trading): add missing mock for node health (#6008) 2024-03-14 16:26:37 +00:00
Bartłomiej Głownia 0636d19b0d feat(deal-ticket): change min and max leverage (#5998) 2024-03-14 16:09:32 +00:00
Matthew Russell 0dd58b68cf fix(deal-ticket): dont trigger deal ticket submit when clicking margin (#6004) 2024-03-14 13:43:22 +00:00
Matthew Russell 49fdd0f68a fix(trading,wallet): improve errors from browser wallet (#5995) 2024-03-14 10:36:39 +00:00
Bartłomiej Głownia 3e2e3c3970 feat(trading): Changes to market info for mark price, funding, and settlement (#5967) 2024-03-13 18:52:09 +01:00
m.ray b89523efb2 feat(trading): update validation and rules on take profit and stop loss (#5987) 2024-03-13 17:43:18 +00:00
Edd be9a5a3437 feat(explorer): add market specific oracle page (#5986) 2024-03-13 17:37:58 +00:00
Bartłomiej Głownia 475b52bcaf feat(positions): do not show liquidation price if position openVolume is 0 (#5990) 2024-03-13 16:31:38 +01:00
Ben 7620f0b67d chore(trading): refactor order status test (#5991) 2024-03-13 15:10:33 +01:00
Matthew Russell 0f5b712034 fix(trading): remove invalid candles from price change calc (#5983) (#5984) 2024-03-13 13:34:22 +00:00
Matthew Russell 424061d64c fix(trading): tick size backwards compatibility (#5988) 2024-03-13 11:31:01 +00:00
Edd a49f5f0dd1 feat(explorer): fees on transfer component (#5907) 2024-03-13 11:28:04 +00:00
Edd bf094288fd feat(explorer): add epoch to block pages (#5817) 2024-03-13 11:23:54 +00:00
Ben bc8a427788 chore(trading): fix poetry lock file (#5989) 2024-03-13 09:05:12 +00:00
Ben 447dc254d7 chore(trading): point to main market-sim branch (#5982) 2024-03-12 15:12:16 +00:00
Matthew Russell cebbdb4ee7 Merge branch 'main' into develop 2024-03-12 14:08:50 +00:00
Matthew Russell fd69f6078c fix(trading): incorrect top gaining and top losing order (#5974) 2024-03-12 14:58:13 +01:00
Matthew Russell 917bdde0bb fix(trading): external explorer link (#5973) 2024-03-12 13:56:55 +00:00
Matthew Russell 2153367258 fix(trading): key details panel throwing error on batch created market (#5980) 2024-03-12 13:56:18 +00:00
Art c0942d56a1 fix(trading): crashing tooltip, enactment date from batch proposal (#5979) 2024-03-12 13:48:44 +00:00
Ben e57a6de765 chore(trading): fix volume e2e test (#5978) 2024-03-12 12:52:48 +00:00
Ben 9eee76e5a6 chore(trading): refactor test fixtures (#5969) 2024-03-12 12:52:12 +00:00
Matthew Russell aa71e608f0 fix(trading): proposed markets table (#5958) 2024-03-12 11:59:48 +00:00
Matthew Russell 20ad18c8a0 chore(governance): remove greenfield from logo map (#5960) 2024-03-12 11:12:03 +00:00
Art 457422b5ac chore(governance): lp votes for batch proposal (#5965) 2024-03-12 11:11:51 +00:00
Ben 990c894e2f chore(trading): update vega version to 75 preview 4 (#5968) 2024-03-11 14:15:14 +00:00
Matthew Russell 2e28a175ce chore(wallet): reduce bundle size by removing unnecessary deps (#5955) 2024-03-11 14:07:45 +00:00
Ben a77858c1c0 chore(trading): e2e test for tp and sl (#5962) 2024-03-11 13:52:50 +00:00
ArtandDariusz Majcherczyk 054c0377b4 chore(governance): lp votes for batch proposal (#5914)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-11 13:10:34 +01:00
Matthew Russell 29bcbd06fb fix(trading,types): fix default build and type generation commands (#5959) 2024-03-11 10:06:59 +00:00
Matthew RussellandDariusz Majcherczyk 1d721dc748 fix(trading): party alias snags (#5947)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-08 17:29:41 +00:00
Matthew Russell 1d71a839b3 fix(trading): manifest.json and woff preloading (#5950) 2024-03-08 17:27:36 +00:00
ArtandDariusz Majcherczyk 4b917926c5 chore(trading): market's tick size (#5929)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-08 17:04:33 +00:00
Art 3a56678403 fix(trading): pick the best node to connect, slow notification notification, other node picking improvements (#5909) 2024-03-08 16:12:11 +00:00
Matthew Russell bcb5351dfc chore(types,utils,wallet): prep for publish, remove lp dashboard (#5948) 2024-03-08 13:20:03 +00:00
Ben 177e72dd16 chore(trading): Refactor test fixtures (#5913) 2024-03-07 23:08:31 +01:00
daro-maj 8221882346 chore(trading): fix CI for trading e2e tests (#5945) 2024-03-07 20:23:50 +00:00
Matthew Russell b1a8473131 fix(wallet-react): fix typo in refresh text 2024-03-07 18:00:34 +00:00
Matthew Russell f18216f70f chore(trading,governance,explorer): back merge hotfixes (#5946) 2024-03-07 17:41:58 +00:00
Matthew Russell 05b39e2c08 fix(trading): usdt approvals (#5939) 2024-03-07 14:21:31 +00:00
Ben de2b79416e chore(trading): staking reward card e2e (#5915) 2024-03-07 14:11:14 +00:00
6504912284 fix(wallet): wallet connection and error fixes (#5927)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-07 14:01:46 +00:00
m.rayandDariusz Majcherczyk 93643f1737 feat(trading): take profit and stop loss (#5902)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-07 10:53:16 +00:00
Bartłomiej Głownia 464d5af6be feat(trading): add additional intervals (#5916) 2024-03-07 10:40:33 +00:00
Ben b2a62f16bc chore(trading): fix banners test (#5933) 2024-03-07 09:39:25 +00:00
BenandDariusz Majcherczyk 9ec03a04ea chore(trading): skip market info volume test (#5935)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-06 15:03:03 +00:00
Artandbwallacee 2a0727ec4b chore(trading): scoped to teams games (#5920)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-06 11:44:33 +00:00
Art 0d39c2354c fix(trading): add team id to games query (#5921) 2024-03-06 10:47:47 +00:00
Matthew Russell ad6f0c5798 feat(trading): add and show key aliases (#5819) 2024-03-06 10:47:16 +00:00
Matthew Russell 654dd1e7b0 fix(trading): stored state causing wrong chart data (#5928) 2024-03-05 18:17:18 +00:00
m.ray 38d13085fb chore(trading): remove view as pubkey banner (#5926) 2024-03-05 16:15:51 +00:00
daro-maj c0f4278b81 fix(trading): fix ci cypress issue (#5922) 2024-03-05 16:09:14 +00:00
Ben b2777043b4 chore(trading): update vega and market-sim (#5919) 2024-03-05 15:49:50 +00:00
Matthew Russell 4d19b55096 Merge pull request #5912 from vegaprotocol/main
chore(trading,governance): back merge hotfixes
2024-03-05 04:14:14 -05:00
Matthew Russellandbwallacee 7ea7362a7d fix(trading): market into tab 24hr vol and price fix (#5910)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-04 19:08:42 +00:00
Art bbfe42ddb1 fix(governance): rewards moved to console notification (#5904) 2024-03-04 18:09:52 +00:00
Matthew Russell b163af3e8a fix(trading): 24hr price and volume in header (#5908) 2024-03-04 18:00:54 +00:00
daro-maj e6b3ff456d fix(trading): fix tests due to new wallet connection method (#5903) 2024-03-04 15:58:33 +01:00
Matthew Russell 00dbb7dd60 chore(trading): delete trading-e2e cypress tests (#5901) 2024-03-03 14:25:04 +00:00
Matthew Russell 82df401611 fix(governance): incorrect penalty information in validators table and page (#5896) 2024-03-01 22:22:17 +00:00
Matthew Russell 89e2033556 chore(ci): remove unused release script (#5874) 2024-03-01 14:26:43 +00:00
Matthew Russell 2d821700bd fix(trading): check for batch proposal in trading mode tooltip (#5895) 2024-03-01 14:26:29 +00:00
Matthew Russellandbwallacee 28b4593a1d refactor(trading,governance,wallet): wallet rewrite (#5815)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-01 14:25:56 +00:00
Matthew Russell 358c734e31 Merge pull request #5893 from vegaprotocol/chore/enable-isolated-margin-testnet
chore(trading): enable isolated margin on testnet
2024-03-01 04:35:00 -05:00
Matthew Russell 3dd7496a9a chore: enable isolated margin on testnet 2024-03-01 09:12:13 +00:00
m.ray 80d576d484 chore(trading): show staking reward cards (#5875) 2024-03-01 06:30:12 +00:00
Matthew Russell 4733bc169c fix(governance): rewards pagination and columns (#5879) 2024-02-29 14:57:07 +00:00
m.ray 2b91aebc2a fix(trading): fees by market fix order (#5877) 2024-02-29 10:25:56 +00:00
m.rayandbwallacee 375f4da541 fix(trading): fix reward card asssets (#5888)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-02-29 09:21:35 +00:00
Matthew Russell bc413ed314 chore(trading): revert reuse vega work (#5883) 2024-02-28 16:57:42 +00:00
BenandMatthew Russell 2fab3daebd chore(trading): update vega binaries (#5873)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-02-27 23:24:28 +00:00
Art 2e07ada966 fix(governance): network paramater change in batch proposals (#5869) 2024-02-27 11:47:10 +01:00
ArtandDariusz Majcherczyk b81c4bc948 fix(governance): add multisig score to the validator page (#5845)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-02-27 11:46:48 +01:00
4f8d6bd876 fix(trading): banner for resume trading (#5849)
Co-authored-by: Art <artur@vegaprotocol.io>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-02-26 16:17:08 +00:00
Ben 52e5a37da3 chore(trading): fix skipped tests (#5868) 2024-02-26 15:52:22 +00:00
Ben 22599673ec chore(trading): remove fixture usage (#5865) 2024-02-26 14:24:56 +00:00
Ben 3c6a806ad3 chore(trading): reuse vega (#5850) 2024-02-26 12:57:16 +00:00
Dexter Edwards 7101d49d1d chore(governance, trading): update links for new beta extension (#5858) 2024-02-26 12:57:09 +00:00
Art 72e0cb76aa feat(governance): batch proposal indicators, vote breakdown ui changes (#5857) 2024-02-26 09:48:29 +00:00
m.ray ca64516a52 fix(trading): lp data provider reload - updating book (#5846) 2024-02-23 17:08:22 +00:00
daro-majanddaniel1302 55d692ea6f test(governance): revert tests (#5808)
Co-authored-by: daniel1302 <daniel.1302@gmail.com>
2024-02-23 10:27:35 +01:00
Ben f235c03abe chore(trading): update e2e test readme (#5844) 2024-02-22 21:50:55 +01:00
Matthew Russell 546deb0e1c fix(trading): use same epochs for all games/stats (#5834) 2024-02-22 13:27:13 +00:00
Art 042919eca9 fix(governance): update market proposal diff (#5842) 2024-02-22 13:37:48 +01:00
Ben c4a56e0de3 chore(trading): fix-order-status-test (#5840) 2024-02-21 15:36:55 +00:00
Ben 3c3bfb7dac chore(trading): update e2e dependencies (#5831) 2024-02-21 13:51:30 +00:00
Ben 196ba78806 chore(trading): refactor tests (#5835) 2024-02-21 13:22:48 +00:00
Bartłomiej GłowniaandMatthew Russell 53ac2dadee feat(trading): support modified funding payment calc (#5780)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-02-20 19:08:29 +00:00
Matthew Russell e532f88daa fix(governance): market name component not showing correct instrument code (#5830) 2024-02-20 18:00:51 +00:00
Ben 7b06c05853 chore(trading): update to vega 74.3 (#5829) 2024-02-20 17:56:54 +00:00
Edd a92fe92778 fix(governance): fix willpassbylpvotecalculation (#5824) 2024-02-20 16:10:44 +00:00
Art b8725a7fa8 fix(trading): competitions font tweaks (#5828) 2024-02-20 16:17:02 +01:00
Art f556247e1a chore(trading): competitions texts (#5826) 2024-02-20 14:39:41 +00:00
Art 48d6be0adf fix(governance): batch proposal diff and proposal headers (#5825) 2024-02-20 15:04:11 +01:00
Art be6f395ce4 feat(trading): colourful rewards (#5812) 2024-02-20 13:20:35 +01:00
Edd 9a37572f51 fix(explorer): sizedelta in ammends displayed incorrectly' (#5816) 2024-02-20 09:24:25 +00:00
Edd 19fb406d49 feat(explorer): add treasury view (#5798) 2024-02-19 10:24:51 +00:00
Matthew Russell a2a04c57d2 fix(trading): add depth chart on mobile view (#5811) 2024-02-16 21:34:01 +00:00
Matthew Russell 1f08e8225a chore(trading): disable isolated margin tests 2024-02-15 19:55:40 -08:00
Matthew Russell 5fff6ba3f7 chore: disable isolated margin in testnet 2024-02-15 19:52:20 -08:00
Matthew RussellandDariusz Majcherczyk 6cacc46a74 feat(governance): batch proposals (#5735)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-02-15 20:14:25 +00:00
Artandbwallacee 98ff4f3c04 feat(trading): game results table (#5801)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-02-15 20:14:04 +00:00
Matthew RussellandBen 8268acac6d fix(trading): add missing funding panel, fix 24 hr price/volume change (#5788)
Co-authored-by: Ben <ben@vega.xyz>
2024-02-15 15:41:04 +00:00
daro-majandBen 4ad1a47ded chore(trading): add cross isolated margin info test (#5786)
Co-authored-by: Ben <ben@vega.xyz>
2024-02-15 08:17:38 +00:00
daro-majandbwallacee fea97aac19 chore(trading): update vega version for sim tests - 0.74.0 (#5784)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-02-14 09:31:28 +00:00
Ben f652292e02 chore(trading): add missed vega cleanup (#5794) 2024-02-14 09:19:52 +00:00
JonRay15 9195bf8c91 Merge pull request #5789 from vegaprotocol/fix/5677-gov-rewards-table
fix(governance): dont show all reward types in gov rewards table
2024-02-13 16:18:02 +00:00
Jeremy Letang cd481512f3 Merge pull request #5771 from vegaprotocol/feat/4306-transfer-status
feat(explorer): transfer status and games overview
2024-02-13 16:17:14 +00:00
Matthew Russell 8fe6f3f5f2 chore(trading): misc fixes (#5785) 2024-02-13 16:17:00 +00:00
Art 1c2389dee5 fix(trading): game cards colour, missing market info, other tweaks (#5791) 2024-02-13 13:38:01 +00:00
Jeremy Letang 1e6a2debfc Merge pull request #5783 from vegaprotocol/5781-update-EstimatePosition-argument
feat(deal-ticket): rename EstimatePosition argument
2024-02-13 10:26:02 +00:00
Matthew Russell fc2773d748 fix: make sure asset is not added to list if it doesn't container correct reward types 2024-02-12 13:42:49 -08:00
Matthew Russell b2860121e5 fix: dont show unrelated rewards in gov rewards table 2024-02-12 13:32:45 -08:00
Matthew Russell db5e5ee782 chore(trading, governance, explorer): update snap to 1.0.1 (#5768) 2024-02-12 18:38:31 +00:00
Art 0d3bcf05a1 chore(trading): team page refactor, games with specified epoch from (#5772) 2024-02-12 15:03:21 +01:00
Matthew RussellandMadalina Raicu c7dd5e846a fix(trading): validate whitespace team names, show empty in list (#5727)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2024-02-12 13:23:12 +00:00
Dariusz Majcherczyk 61e5941751 fix(deal-ticket): margin test 2024-02-09 21:14:36 +01:00
m.rayandcandida-d 0d850bd8b9 feat(trading): add LP fee settings (#5773)
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
2024-02-09 19:44:15 +00:00
Bartłomiej Głownia 8db286fa93 feat(deal-ticket): rename EstimatePosition argument 2024-02-09 15:02:09 +01:00
Bartłomiej Głownia 44189591fc feat(accounts): get trasfer fee from estimateTransferFee API (#5721) 2024-02-09 12:55:17 +00:00
Art 3ed2ec88d7 chore(explorer): remove voting column from proposal table (#5776) 2024-02-09 10:32:54 +00:00
Bartłomiej Głownia a21feea699 chore(utils): improve formatNumber to keep precision (#5761) 2024-02-09 10:31:15 +00:00
m.rayandMatthew Russell 41fd14dd00 feat(trading): mobile layout and buttons (#5751)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-02-09 10:30:24 +00:00
Edd c5a27dc6a2 chore(trading): switch eth provider URL (#5779) 2024-02-09 10:26:08 +00:00
Art 0a3b1cadba fix(trading): keep leaderboard rank when filtering (#5775) 2024-02-09 10:00:28 +01:00
Edd b953de953a feat(explorer): update party profile tx (#5719) 2024-02-08 19:06:38 +00:00
Art 5ddcb613e2 chore(trading): create and update team form traversing (#5764) 2024-02-08 14:07:48 +00:00
m.rayandMatthew Russell 76c07992d3 feat(trading): update mobile layout (#5718)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-02-08 13:24:48 +00:00
Edd e5635cae61 test(explorer): add tests for games display 2024-02-08 12:26:32 +00:00
Ben 46e2965fa2 chore(trading): fix test teardown (#5769) 2024-02-08 12:25:16 +00:00
Edd b01c67ced5 chore(explorer): update transfer status query to match latest preview 2024-02-08 10:54:00 +00:00
Edd 636b1f98db chore(explorer): relayout transfers 2024-02-08 10:51:10 +00:00
Edd c31a927526 chore(explorer): progress on rank table 2024-02-08 10:51:10 +00:00
Edd 5803d6e890 feat(explorer): complete transfer status 2024-02-08 10:51:10 +00:00
Edd cb0dd17839 chore(explorer): more fields for rewards 2024-02-08 10:51:10 +00:00
Edd 496b0b5a90 feat(explorer): extend transfers view 2024-02-08 10:51:10 +00:00
m.rayandbwallacee 532ad3a4b9 feat(trading): show value of the volume traded in the last 24hrs (#5766)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-02-07 11:07:24 +00:00
Madalina Raicu 3a8b40d7a5 Revert "feat(trading): show total value of the valume traded in last 24hrs"
This reverts commit be38813a33.
2024-02-06 22:12:33 +00:00
Madalina Raicu be38813a33 feat(trading): show total value of the valume traded in last 24hrs 2024-02-06 21:52:32 +00:00
bf70dc33ec chore(trading): back merge main to develop (#5749)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: Art <artur@vegaprotocol.io>
2024-02-06 14:41:48 +00:00
Art d6084e75a0 fix(environment): pick best node (#5752) 2024-02-06 14:40:14 +01:00
Art 75e7cea32a fix(trading): trading view on multiple tabs (#5748) 2024-02-06 12:31:12 +01:00
Bartłomiej Głownia b4e98e285e feat(deal-ticket): update fields in margin details (#5722) 2024-02-06 08:42:11 +01:00
Edd f62e29c67f feat(explorer): batch proposal support (#5711) 2024-02-05 17:35:12 +00:00
94e7ad489f feat(trading): main to develop back merge (#5739)
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-02-05 16:06:33 +01:00
Art 657942d995 feat(trading): team card (#5724) 2024-02-02 20:07:06 +02:00
Matthew Russell a529b9b031 chore(trading): make team stats aggregation epochs 192 (#5723) 2024-02-02 18:01:28 +00:00
Ben 0f311611ba chore(trading): team e2e tests (#5716) 2024-02-01 19:33:25 +00:00
Art 02bd031bee fix(trading): mark as updated when tx confirmed (#5715) 2024-02-01 13:27:21 +00:00
Art bd97651dd3 fix(trading): game cards are not showing (#5713) 2024-02-01 12:04:36 +00:00
Bartłomiej Głownia 516b3e5b93 feat(deal-ticket): fix margin calculations in DealTicketMarginDetails for isolated margin mode (#5709) 2024-02-01 12:52:45 +01:00
Matthew Russellandbwallacee 42a98b6a35 fix(trading): teams snags (#5707)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-02-01 10:38:57 +00:00
Art 2002731c52 feat(trading): gas fee estimation for withdraw transaction (#5668) 2024-02-01 09:07:13 +01:00
a49139f127 fix(trading): closed markets filter (#5691) (#5697)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-31 14:34:43 +00:00
Edd e216b23472 feat(explorer): updatemarginmode tx view (#5690) 2024-01-31 14:26:35 +00:00
e52ae97233 feat(trading): competitions (#5621)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
Co-authored-by: Ben <ben@vega.xyz>
2024-01-31 14:21:29 +00:00
1780f6fa7f feat(trading): margin estimate update (#5664)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: daro-maj <119658839+daro-maj@users.noreply.github.com>
2024-01-31 14:05:30 +00:00
Ben 8b91592b93 chore(trading): update to preview 7 binaries (#5689) 2024-01-31 12:42:29 +01:00
m.ray 508274268d fix(trading): show user inactive lose streak in remaining epochs (#5667) 2024-01-31 10:54:16 +00:00
Edd 2a40d9ec4d feat(explorer): support for more external chains (#5687) 2024-01-31 10:36:58 +00:00
Art efb746f373 fix(candles-chart): ignore candles from before market open date (#5678) 2024-01-30 14:32:55 +00:00
Ben 6d2f367987 chore(trading): update vega binaries to v0.74.0-preview.6 (#5686) 2024-01-30 13:31:06 +00:00
m.ray 6b7bbc9c94 fix(trading): market id use text-xs in key details (#5684) 2024-01-29 15:02:03 +00:00
Art 9153677a33 fix(markets): key details panel crashing (#5683) 2024-01-29 13:22:17 +00:00
m.ray cff1818940 chore(trading, governance, explorer): add liquidation strategy to market info (#5642) 2024-01-29 13:13:19 +01:00
Zohar Etzioni d05dd6e4cb Merge pull request #5669 from vegaprotocol/feat/use-camelCase-marketId-in-updateMarginMode-tx
feat(trading): use camel case marketId in updateMarginMode tx
2024-01-25 14:46:54 +00:00
Bartłomiej Głownia 4ef789e00a feat(trading): use camel case marketId in updateMarginMode tx 2024-01-25 15:09:09 +01:00
m.ray 0bd13a5f7b fix(trading): adjust full screen for mobile dialogs (#5666) 2024-01-25 13:32:14 +00:00
67d38ff03e fix(trading): error guards not working when poll interval is supplied (#5661)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-25 07:59:40 +00:00
Matthew Russell 6aea10c27b chore(trading, governance, explorer): 0.74.0 type regen (#5619) 2024-01-24 22:34:11 +00:00
Edd 27a9d5f247 fix(explorer): update tx navigation for 0.74.0 (#5662) 2024-01-24 16:31:56 +00:00
261f32aa5b feat(trading): margin mode selector (#5660)
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-24 13:17:22 +00:00
Edd 053775bef6 fix(governance): handle an expected error (#5653) 2024-01-24 11:41:40 +00:00
Matthew Russellandbwallacee 0660eda334 chore(trading): store all chart state (#5627)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-01-24 10:21:33 +00:00
Matthew Russell f22a3bc2d2 Revert "feat(trading): margin mode selector (#5575)"
This reverts commit fde77ebccb.
2024-01-23 08:46:16 -08:00
Bartłomiej GłowniaandDariusz Majcherczyk fde77ebccb feat(trading): margin mode selector (#5575)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-23 16:19:49 +00:00
e309669736 fix(trading): typo translation full featured link (#5659)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-23 16:05:51 +00:00
b1621d1191 chore(trading): fix monitoring bounds (#5655) (#5657)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-23 15:45:05 +00:00
ArtandDexter Edwards 39907f07db feat(wallet): new browser wallet connection model (#5572)
Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
2024-01-23 15:32:23 +00:00
3dab5f3d9b chore(trading): merge main back in develop (#5654)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-23 14:57:20 +00:00
Edd 3cf9ae7582 fix(governance): handle null validator scores properly (#5459) 2024-01-23 14:46:19 +00:00
Ben 557894e2ef chore(trading): turn off parallel (#5650) 2024-01-23 14:43:51 +00:00
m.rayandbwallacee baf9875c69 feat(trading): filter out suspended transfers (#5640)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-01-23 09:38:09 +00:00
Art 51199b02ce feat(governance): update market proposal header with copy and open buttons (#5648) 2024-01-23 10:13:56 +01:00
m.ray bb826c88f0 fix(trading): set trading view as default (#5632) 2024-01-22 16:09:18 +00:00
Edd 0da20b750f feat(explorer): referral and team transaction support (#5623) 2024-01-22 10:53:53 +00:00
m.rayandDariusz Majcherczyk e16c447564 chore(trading): remove quadratic slippage factor (#5641)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-22 10:17:16 +00:00
Bartłomiej GłowniaandDariusz Majcherczyk a2170d27d6 feat(ui-toolkit): update slider design (#5645)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-22 08:30:57 +01:00
Ben 00d840b7b3 chore(trading): delete and move some tests to jest (#5633) 2024-01-19 14:15:53 +00:00
Ben 2a4a05630f chore(trading): fix skipped tests (#5638) 2024-01-19 08:20:52 +00:00
Ben 97f1f40f2c chore(trading): remove waits and tests (#5637) 2024-01-18 13:20:58 +00:00
Bartłomiej GłowniaandMatthew Russell a8e6963521 feat(deposits): if wrong network dont show the form and prompt switching to the correct network (#5571)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-01-18 10:41:19 +01:00
m.ray a2bffa1dfd fix(trading): alphabetically order translations in trading.json (#5635) 2024-01-17 21:28:20 +00:00
Ben 9253e8067a chore(trading): market-sim branch parralel fix (#5630) 2024-01-17 14:44:11 +00:00
Bartłomiej Głownia 58972f0a11 feat(ui-toolkit): add leverage slider component (#5594) 2024-01-17 13:46:11 +00:00
Ben a3c55fd7c3 chore(trading): update vega-market-sim to use to 0.74.0-preview.2 (#5603) 2024-01-17 12:19:52 +00:00
Matthew Russell c63cba1071 chore(trading): enable trading view on mainnet (#5628) 2024-01-16 18:16:58 +00:00
m.ray bc9d87fe30 chore(trading): clean up triggering ratio (#5604) 2024-01-16 18:10:32 +01:00
m.ray 6a21862378 fix(trading): use No trading instead of Trading terminated (#5624) 2024-01-16 18:30:42 +02:00
Ben 0fb4348e9c chore(trading): upload market-sim logs (#5622) 2024-01-15 20:56:52 +00:00
m.ray b751bcf17d feat(trading): mobile responsiveness tweaks (#5613) 2024-01-15 15:26:13 +00:00
m.ray d32f27fcb1 feat(trading): mobile responsiveness - market selector (#5582) 2024-01-11 11:23:48 +00:00
m.ray d05cd6a2ed fix(trading): tiny scroll for rewards (#5586) 2024-01-10 15:49:58 +00:00
m.ray c003e5fa30 fix(trading): liquidity table improve readability and remove grouping (#5598) 2024-01-10 15:49:35 +00:00
Ben f62d3289ab chore(trading): retry on http error (#5601) 2024-01-10 15:44:01 +00:00
Ben a3d3d18c5c chore(trading): fix price monitoring test (#5600) 2024-01-10 14:56:17 +00:00
77ca101781 chore(trading): merge main back in develop (#5597)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-10 10:31:15 +00:00
Ben d238c37d0c chore(trading): vesting test (#5595) 2024-01-10 10:07:59 +00:00
Edd 79feb485f6 fix(governance): improve error message for configuration error (#5589) 2024-01-09 17:21:56 +00:00
Ben 6aa5c3b6e3 chore(trading): rewards page e2e (#5578) 2024-01-09 15:54:40 +00:00
Ben 82abc13fda chore(trading): skip test due to issue (#5583) 2024-01-08 16:16:03 +00:00
Edd 933f07cddf fix(explorer): minor tidyup of deterministic order view (#5573) 2024-01-08 13:25:03 +00:00
Matthew Russell c8067669c2 fix(trading): ensure fees queries always fetch on mount and poll (#5566) 2024-01-05 12:43:30 +00:00
78f5a9c520 feat(trading): activity streaks, reward hoarder bonus and active rewards (#5491)
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: bwallacee <ben@vega.xyz>
2024-01-05 11:16:59 +00:00
Ben 462066959d chore(trading): update vega (#5563) 2024-01-03 12:00:00 +00:00
daro-maj f12f5ab961 chore(trading): fix volume issue for market selector tests (#5564) 2024-01-03 12:04:49 +01:00
98b2460d8b fix(trading): mobile friendly referral and fees views (#5530)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-01-02 15:50:04 +00:00
Matthew Russell 67c182f3de chore(trading-view): minor tv chart tweaks (#5545) 2024-01-02 13:20:01 +00:00
68e0009ab0 feat(trading,markets,proposals): market banners (#5494)
Co-authored-by: bwallacee <ben@vega.xyz>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-02 11:04:43 +00:00
Bartłomiej Głownia ad74b12908 feat(trading): do not show error message if there is no faucet tx error (#5539) 2024-01-02 10:19:10 +01:00
Matthew Russell 7fff33e493 feat(trading): competition feature flag and team page (#5549) 2023-12-29 15:50:51 +00:00
daro-maj e7cb2c8276 chore(trading): fix fees tests (#5548) 2023-12-29 15:25:33 +00:00
4adaeea40a feat(trading): pane settings view (#5509)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-12-28 12:33:18 +00:00
Matthew Russell d47d894147 fix(deal-ticket): stray curly in stop order deal ticket (#5547) 2023-12-28 12:27:56 +00:00
Bartłomiej Głownia 515932d401 fix(trading): use correct interpolation prefix in market info label (#5537) 2023-12-27 18:47:19 +00:00
daro-maj 723ff2805d chore(trading): check e2e tests issue (#5544) 2023-12-27 14:07:27 +01:00
Ben 424cced4be chore(trading): vegaservice import (#5536) 2023-12-27 08:17:20 +00:00
Daniel b2c7ee82c5 feat(ci): validators testnet deployment (#5541) 2023-12-22 18:05:31 +00:00
m.rayandDaniel 4cc63be4f9 chore(trading): update validator testnet pipelines (#5543)
Co-authored-by: Daniel <daniel.1302@gmail.com>
2023-12-22 18:03:45 +00:00
1223 changed files with 61286 additions and 25745 deletions
-1
View File
@@ -4,6 +4,5 @@ tmp/*
.dockerignore .dockerignore
dockerfiles dockerfiles
node_modules node_modules
.git
.github .github
.vscode .vscode
+2 -1
View File
@@ -196,9 +196,9 @@ jobs:
cypress: cypress:
needs: [build-sources, check-e2e-needed] needs: [build-sources, check-e2e-needed]
name: '(CI) cypress' name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
uses: ./.github/workflows/cypress-run.yml uses: ./.github/workflows/cypress-run.yml
secrets: inherit secrets: inherit
if: needs.check-e2e-needed.outputs.run-tests == 'true' && (contains(needs.build-sources.outputs.projects, 'governance') || contains(needs.build-sources.outputs.projects, 'explorer'))
with: with:
projects: ${{ needs.build-sources.outputs.projects-e2e }} projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke' tags: '@smoke'
@@ -287,6 +287,7 @@ jobs:
steps: steps:
- run: | - run: |
result="${{ needs.cypress.result }}" result="${{ needs.cypress.result }}"
echo "Result: $result"
if [[ $result == "success" || $result == "skipped" ]]; then if [[ $result == "success" || $result == "skipped" ]]; then
exit 0 exit 0
else else
+62 -10
View File
@@ -10,7 +10,7 @@ on:
inputs: inputs:
console-test-branch: console-test-branch:
type: choice type: choice
description: 'main: v0.73.5, develop: v0.73.5' description: 'main: v0.74.10, develop: v0.75.5'
options: options:
- main - main
- develop - develop
@@ -19,7 +19,7 @@ jobs:
create-docker-image: create-docker-image:
name: Create docker image for console-test name: Create docker image for console-test
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
timeout-minutes: 20 timeout-minutes: 45
steps: steps:
#---------------------------------------------- #----------------------------------------------
# check-out frontend-monorepo # check-out frontend-monorepo
@@ -57,15 +57,14 @@ jobs:
#---------------------------------------------- #----------------------------------------------
- name: Build trading app - name: Build trading app
run: | run: |
yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading ENV_NAME="${{ needs.console-test-branch.outputs.console-branch == 'main' && 'mainnet' || 'stagnet1' }}"
yarn env-cmd -f ./apps/trading/.env.$ENV_NAME yarn nx export trading
DIST_LOCATION=dist/apps/trading/exported DIST_LOCATION=dist/apps/trading/exported
mv $DIST_LOCATION dist-result mv $DIST_LOCATION dist-result
tree dist-result tree dist-result
#---------------------------------------------- #----------------------------------------------
# export trading app docker image # export trading app docker image
#---------------------------------------------- #----------------------------------------------
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -78,7 +77,7 @@ jobs:
load: true load: true
build-args: | build-args: |
APP=trading APP=trading
ENV_NAME=stagnet1 ENV_NAME=${{ needs.console-test-branch.outputs.console-branch == 'main' && 'mainnet' || 'stagnet1' }}
tags: ci/trading:local tags: ci/trading:local
outputs: type=docker,dest=/tmp/console-image.tar outputs: type=docker,dest=/tmp/console-image.tar
@@ -138,7 +137,7 @@ jobs:
name: run-tests name: run-tests
runs-on: 8-cores runs-on: 8-cores
needs: [create-docker-image, console-test-branch] needs: [create-docker-image, console-test-branch]
timeout-minutes: 20 timeout-minutes: 45
steps: steps:
#---------------------------------------------- #----------------------------------------------
# load docker image # load docker image
@@ -182,12 +181,22 @@ jobs:
virtualenvs-create: true virtualenvs-create: true
virtualenvs-in-project: true virtualenvs-in-project: true
virtualenvs-path: .venv virtualenvs-path: .venv
#----------------------------------------------
# Set up pyproject.toml based on branch
#----------------------------------------------
- name: Create pyproject.toml based on branch
run: |
if [ "${{ needs.console-test-branch.outputs.console-branch }}" = "main" ]; then
mv pyproject.main.toml pyproject.toml
elif [ "${{ needs.console-test-branch.outputs.console-branch }}" = "develop" ]; then
mv pyproject.develop.toml pyproject.toml
fi
working-directory: apps/trading/e2e
#---------------------------------------------- #----------------------------------------------
# install python dependencies # install python dependencies
#---------------------------------------------- #----------------------------------------------
- name: Install dependencies - name: Install dependencies
run: poetry install --no-interaction --no-root run: poetry lock && poetry install --no-interaction --no-root
working-directory: apps/trading/e2e working-directory: apps/trading/e2e
#---------------------------------------------- #----------------------------------------------
# install vega binaries # install vega binaries
@@ -205,7 +214,7 @@ jobs:
# run tests # run tests
#---------------------------------------------- #----------------------------------------------
- name: Run tests - name: Run tests
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15 run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 4 --dist loadfile --durations=45
working-directory: apps/trading/e2e working-directory: apps/trading/e2e
#---------------------------------------------- #----------------------------------------------
# upload traces # upload traces
@@ -227,3 +236,46 @@ jobs:
name: worker-logs name: worker-logs
path: ./logs/ path: ./logs/
retention-days: 15 retention-days: 15
#----------------------------------------------
# ----- upload market-sim logs -----
#----------------------------------------------
- name: Prepare and Zip market-sim-logs
if: always()
run: |
parent_dir="/tmp/market-sim-logs"
echo "Creating parent directory at $parent_dir"
mkdir -p "$parent_dir"
echo "Waiting for vega-sim-* folders to be created..."
sleep 10 # Waits 10 seconds to ensure all folders are created
echo "Before searching for vega-sim-* folders in /tmp..."
folders=$(find /tmp -mindepth 1 -type d -name 'vega-sim-*' -print) || echo "Find command failed with exit code $?"
echo "After searching for vega-sim-* folders in /tmp..."
if [ -z "$folders" ]; then
echo "No vega-sim-* folders found."
exit 0
fi
echo "Moving vega-sim-* folders to $parent_dir"
echo "$folders" | xargs -I {} mv {} "$parent_dir/"
echo "Checking if $parent_dir is not empty..."
if [ "$(ls -A $parent_dir)" ]; then
echo "Zipping the parent directory..."
zip -r market-sim-logs.zip "$parent_dir" && echo "Zip file created successfully."
else
echo "$parent_dir is empty. No zip file created."
exit 0
fi
shell: /usr/bin/bash -e {0}
- name: Upload market-sim-logs
uses: actions/upload-artifact@v3
if: always()
with:
name: market-sim-logs
path: market-sim-logs.zip
retention-days: 15
-36
View File
@@ -1,36 +0,0 @@
name: Cypress Console tests -- live environment
# This workflow runs using provided url
on:
workflow_dispatch:
inputs:
url:
description: 'Url'
required: true
type: string
jobs:
cypress-run:
name: Run Cypress Trading tests -- live environment
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 20
id: Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Run Cypress tests
uses: cypress-io/github-action@v4
with:
browser: chrome
record: true
project: ./apps/trading-e2e
config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -12,7 +12,6 @@ on:
options: options:
- explorer-e2e - explorer-e2e
- governance-e2e - governance-e2e
- trading-e2e
tags: tags:
description: 'Test tags to run' description: 'Test tags to run'
required: true required: true
+1 -1
View File
@@ -10,5 +10,5 @@ jobs:
uses: ./.github/workflows/cypress-run.yml uses: ./.github/workflows/cypress-run.yml
secrets: inherit secrets: inherit
with: with:
projects: '["explorer-e2e","governance-e2e","trading-e2e"]' projects: '["explorer-e2e","governance-e2e"]'
tags: '@smoke @regression @slow' tags: '@smoke @regression @slow'
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
rm package.json rm package.json
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx npm install --no-save @commitlint/cli@16.3.0 @commitlint/config-conventional@18.6.1 @commitlint/config-nx-scopes@18.6.1 nx@17.1.2
- name: Check PR title - name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js run: echo "${{ github.event.pull_request.title }}" | npx @commitlint/cli@16.3.0 --config ./commitlint.config-ci.js
+1
View File
@@ -58,5 +58,6 @@ __pycache__/
apps/trading/e2e/logs/ apps/trading/e2e/logs/
apps/trading/e2e/.pytest_cache/ apps/trading/e2e/.pytest_cache/
apps/trading/e2e/traces/ apps/trading/e2e/traces/
apps/trading/e2e/pyproject.toml
.nx/ .nx/
+28
View File
@@ -0,0 +1,28 @@
# path to a directory with all packages
storage: ../tmp/local-registry/storage
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.yarnpkg.com
maxage: 60m
packages:
'**':
# give all users (including non-authenticated users) full access
# because it is a local registry
access: $all
publish: $all
unpublish: $all
# if package is not available locally, proxy requests to npm registry
proxy: npmjs
# log settings
logs:
type: stdout
format: pretty
level: warn
publish:
allow_offline: true # set offline to true to allow publish offline
+5 -5
View File
@@ -4,7 +4,7 @@ The front-end monorepo provides a toolkit for building apps that interact with V
This repository is managed using [Nx](https://nx.dev). This repository is managed using [Nx](https://nx.dev).
# 🔎 Applications in this repo ## 🔎 Applications in this repo
### [Block explorer](./apps/explorer) ### [Block explorer](./apps/explorer)
@@ -30,7 +30,7 @@ Hosting for static content being shared across apps, for example fonts.
The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract. The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract.
# 🧱 Libraries in this repo ## 🧱 Libraries in this repo
### [UI toolkit](./libs/ui-toolkit) ### [UI toolkit](./libs/ui-toolkit)
@@ -53,7 +53,7 @@ A utility library for connecting to the Ethereum network and interacting with Ve
Generic react helpers that can be used across multiple applications, along with other utilities. Generic react helpers that can be used across multiple applications, along with other utilities.
# 💻 Develop ## 💻 Develop
### Set up ### Set up
@@ -103,7 +103,7 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more. Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
# 🐋 Hosting a console ## 🐋 Hosting a console
To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions). To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions).
@@ -226,6 +226,6 @@ Note: The script is only needed if capsule was built for first time or fresh. To
vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet
``` ```
# 📑 License ## 📑 License
[MIT](./LICENSE) [MIT](./LICENSE)
@@ -1,7 +1,7 @@
import { getNewAssetTxBody } from '../support/governance.functions'; import { getNewAssetTxBody } from '../support/governance.functions';
context('Proposal page', { tags: '@smoke' }, function () { context('Proposal page', { tags: '@smoke' }, function () {
describe('Verify elements on page', function () { describe.skip('Verify elements on page', function () {
const proposalHeading = 'proposals-heading'; const proposalHeading = 'proposals-heading';
const dateTimeRegex = const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm; /(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
@@ -24,10 +24,6 @@ context('Proposal page', { tags: '@smoke' }, function () {
cy.get_element_by_col_id('title').should('have.text', proposalTitle); cy.get_element_by_col_id('title').should('have.text', proposalTitle);
cy.get_element_by_col_id('type').should('have.text', 'NewMarket'); cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
cy.get_element_by_col_id('state').should('have.text', 'Enacted'); cy.get_element_by_col_id('state').should('have.text', 'Enacted');
cy.getByTestId('vote-progress').should('be.visible');
cy.getByTestId('vote-progress-bar-for')
.invoke('attr', 'style')
.should('eq', 'width: 100%;');
cy.get('[col-id="cDate"]') cy.get('[col-id="cDate"]')
.invoke('text') .invoke('text')
.should('match', dateTimeRegex); .should('match', dateTimeRegex);
@@ -44,7 +40,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
cy.getByTestId('icon-cross').click(); cy.getByTestId('icon-cross').click();
}); });
it.skip('Proposal page displayed on mobile', function () { it('Proposal page displayed on mobile', function () {
const proposalTitle = 'Add Lorem Ipsum market'; const proposalTitle = 'Add Lorem Ipsum market';
cy.common_switch_to_mobile_and_click_toggle(); cy.common_switch_to_mobile_and_click_toggle();
@@ -55,7 +51,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
}); });
}); });
it('Able to view new asset proposal', function () { it.skip('Able to view new asset proposal', function () {
const proposalTitle = 'Test new asset proposal'; const proposalTitle = 'Test new asset proposal';
const newAssetProposalBody = getNewAssetTxBody(); const newAssetProposalBody = getNewAssetTxBody();
cy.VegaWalletSubmitProposal(newAssetProposalBody); cy.VegaWalletSubmitProposal(newAssetProposalBody);
@@ -73,10 +69,6 @@ context('Proposal page', { tags: '@smoke' }, function () {
'have.text', 'have.text',
'Waiting for Node Vote' 'Waiting for Node Vote'
); );
cy.getByTestId('vote-progress').should('be.visible');
cy.getByTestId('vote-progress-bar-against')
.invoke('attr', 'style')
.should('eq', 'width: 100%;');
cy.get('[col-id="cDate"]') cy.get('[col-id="cDate"]')
.invoke('text') .invoke('text')
.should('match', dateTimeRegex); .should('match', dateTimeRegex);
-2
View File
@@ -1,5 +1,4 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.rocks NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.rocks
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1 NX_VEGA_ENV=STAGNET1
@@ -9,7 +8,6 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.rocks/rest NX_BLOCK_EXPLORER=https://be.stagnet1.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
-1
View File
@@ -1,6 +1,5 @@
# App configuration variables # App configuration variables
NX_VEGA_ENV=CUSTOM NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/ NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
-1
View File
@@ -2,7 +2,6 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET NX_VEGA_ENV=DEVNET
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.governance.vega.xyz NX_VEGA_GOVERNANCE_URL=https://dev.governance.vega.xyz
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
-1
View File
@@ -4,7 +4,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest NX_BLOCK_EXPLORER=https://be.vega.community/rest
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/ NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
-1
View File
@@ -4,7 +4,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-inter
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
NX_VEGA_ENV=MAINNET_MIRROR NX_VEGA_ENV=MAINNET_MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.rocks/rest NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/ NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/
-1
View File
@@ -4,7 +4,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-inter
NX_VEGA_ENV=TESTNET NX_VEGA_ENV=TESTNET
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
+1 -2
View File
@@ -1,10 +1,9 @@
# App configuration variables # App configuration variables
NX_VEGA_ENV=VALIDATOR_TESTNET NX_VEGA_ENV=VALIDATORS_TESTNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/ NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
-1
View File
@@ -3,7 +3,6 @@ NX_TENDERMINT_URL=http://localhost:26607/
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
NX_VEGA_ENV=CUSTOM NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER= NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/ NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
@@ -7,6 +7,7 @@ export type AssetBalanceProps = {
price: string; price: string;
showAssetLink?: boolean; showAssetLink?: boolean;
showAssetSymbol?: boolean; showAssetSymbol?: boolean;
rounded?: boolean;
}; };
/** /**
@@ -18,12 +19,17 @@ const AssetBalance = ({
price, price,
showAssetLink = true, showAssetLink = true,
showAssetSymbol = false, showAssetSymbol = false,
rounded = false,
}: AssetBalanceProps) => { }: AssetBalanceProps) => {
const { data: asset, loading } = useAssetDataProvider(assetId); const { data: asset, loading } = useAssetDataProvider(assetId);
const label = const label =
!loading && asset && asset.decimals !loading && asset && asset.decimals
? addDecimalsFixedFormatNumber(price, asset.decimals) ? addDecimalsFixedFormatNumber(
price,
asset.decimals,
rounded ? 0 : undefined
)
: price; : price;
return ( return (
@@ -24,7 +24,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const ref = useRef<AgGridReact>(null); const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => { const showColumnsOnDesktop = () => {
ref.current?.columnApi.setColumnsVisible( ref.current?.api.setColumnsVisible(
['id', 'type', 'status'], ['id', 'type', 'status'],
window.innerWidth > BREAKPOINT_MD window.innerWidth > BREAKPOINT_MD
); );
@@ -8,12 +8,14 @@ import EpochMissingOverview from './epoch-missing';
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit'; import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit'; import type { IconProps } from '@vegaprotocol/ui-toolkit';
import isPast from 'date-fns/isPast'; import isPast from 'date-fns/isPast';
import { EpochSymbol } from '../links/block-link/block-link';
const borderClass = const borderClass =
'border-solid border-2 border-vega-dark-200 border-collapse'; 'border-solid border-2 border-vega-dark-200 border-collapse';
export type EpochOverviewProps = { export type EpochOverviewProps = {
id?: string; id?: string;
icon?: boolean;
}; };
/** /**
@@ -24,7 +26,7 @@ export type EpochOverviewProps = {
* *
* The details are hidden in a tooltip, behind the epoch number * The details are hidden in a tooltip, behind the epoch number
*/ */
const EpochOverview = ({ id }: EpochOverviewProps) => { const EpochOverview = ({ id, icon = true }: EpochOverviewProps) => {
const { data, error, loading } = useExplorerEpochQuery({ const { data, error, loading } = useExplorerEpochQuery({
variables: { id: id || '' }, variables: { id: id || '' },
}); });
@@ -38,7 +40,12 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
} }
if (!ti || loading || error) { if (!ti || loading || error) {
return <span>{id}</span>; return (
<span>
<EpochSymbol />
{id}
</span>
);
} }
const description = ( const description = (
@@ -90,7 +97,11 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
return ( return (
<Tooltip description={description}> <Tooltip description={description}>
<p> <p>
<IconForEpoch start={ti.start} end={ti.end} /> {icon ? (
<IconForEpoch start={ti.start} end={ti.end} />
) : (
<EpochSymbol />
)}
{id} {id}
</p> </p>
</Tooltip> </Tooltip>
@@ -41,6 +41,7 @@ export const Header = () => {
Routes.ASSETS, Routes.ASSETS,
Routes.MARKETS, Routes.MARKETS,
Routes.GOVERNANCE, Routes.GOVERNANCE,
Routes.TREASURY,
Routes.NETWORK_PARAMETERS, Routes.NETWORK_PARAMETERS,
Routes.GENESIS, Routes.GENESIS,
].map((n) => pages.find((r) => r.path === n)) ].map((n) => pages.find((r) => r.path === n))
@@ -0,0 +1,10 @@
query ExplorerEpochForBlock($block: String!) {
epoch(block: $block) {
id
timestamps {
start
end
lastBlock
}
}
}
@@ -0,0 +1,53 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerEpochForBlockQueryVariables = Types.Exact<{
block: Types.Scalars['String'];
}>;
export type ExplorerEpochForBlockQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, lastBlock?: string | null } } };
export const ExplorerEpochForBlockDocument = gql`
query ExplorerEpochForBlock($block: String!) {
epoch(block: $block) {
id
timestamps {
start
end
lastBlock
}
}
}
`;
/**
* __useExplorerEpochForBlockQuery__
*
* To run a query within a React component, call `useExplorerEpochForBlockQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerEpochForBlockQuery` 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 } = useExplorerEpochForBlockQuery({
* variables: {
* block: // value for 'block'
* },
* });
*/
export function useExplorerEpochForBlockQuery(baseOptions: Apollo.QueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
}
export function useExplorerEpochForBlockLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
}
export type ExplorerEpochForBlockQueryHookResult = ReturnType<typeof useExplorerEpochForBlockQuery>;
export type ExplorerEpochForBlockLazyQueryHookResult = ReturnType<typeof useExplorerEpochForBlockLazyQuery>;
export type ExplorerEpochForBlockQueryResult = Apollo.QueryResult<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>;
@@ -4,17 +4,56 @@ import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react'; import type { ComponentProps } from 'react';
import Hash from '../hash'; import Hash from '../hash';
import { useExplorerEpochForBlockQuery } from './__generated__/EpochByBlock';
import { t } from '@vegaprotocol/i18n';
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & { export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
height: string; height: string;
showEpoch?: boolean;
}; };
const BlockLink = ({ height, ...props }: BlockLinkProps) => { const BlockLink = ({ height, showEpoch = false, ...props }: BlockLinkProps) => {
return ( return (
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}> <>
<Hash text={height} /> <Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
</Link> <Hash text={height} />
</Link>
{showEpoch && <EpochForBlock block={height} />}
</>
); );
}; };
export function EpochForBlock(props: { block: string }) {
const { error, data, loading } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: props.block },
});
// NOTE: 0.73.x & <0.74.2 can error showing epoch, so for now we hide loading
// or error states and only display if we get usable data
if (error || loading || !data) {
return null;
}
return (
<span className="ml-2" title={t('Epoch')}>
<EpochSymbol />
{data.epoch.id}
</span>
);
}
export const EPOCH_SYMBOL = 'ⓔ';
export function EpochSymbol() {
return (
<em
title={t('Epoch')}
className="mr-1 cursor-default text-xl leading-none align-text-bottom not-italic"
>
{EPOCH_SYMBOL}
</em>
);
}
export default BlockLink; export default BlockLink;
@@ -1,34 +0,0 @@
import React from 'react';
import { DATA_SOURCES } from '../../../config';
import Hash from '../hash';
export enum EthExplorerLinkTypes {
block = 'block',
address = 'address',
tx = 'tx',
}
export type EthExplorerLinkProps = Partial<typeof HTMLAnchorElement> & {
id: string;
type: EthExplorerLinkTypes;
};
export const EthExplorerLink = ({
id,
type,
...props
}: EthExplorerLinkProps) => {
const link = `${DATA_SOURCES.ethExplorerUrl}/${type}/${id}`;
return (
<a
className="underline external font-mono"
target="_blank"
rel="noopener noreferrer"
{...props}
href={link}
>
<Hash text={id} />
</a>
);
};
@@ -0,0 +1,37 @@
import type { ChainIdMapping } from '@vegaprotocol/environment';
import {
SUPPORTED_CHAIN_IDS,
SUPPORTED_CHAIN_LABELS,
} from '@vegaprotocol/environment';
export const SUPPORTED_CHAIN_ICON_URLS: ChainIdMapping = {
'1': '/assets/chain-eth-logo.svg',
'100': '/assets/chain-gno-logo.svg',
'42161': '/assets/chain-arb-logo.svg',
'11155111': '/assets/chain-eth-logo.svg',
};
export type ExternalChainIconProps = {
chainId?: string;
};
export const ExternalChainIcon = ({
// If chainID is not provided, default to a non-existent chain
chainId = '-1',
}: ExternalChainIconProps) => {
if (SUPPORTED_CHAIN_IDS.includes(chainId)) {
const url = SUPPORTED_CHAIN_ICON_URLS[chainId];
const alt = SUPPORTED_CHAIN_LABELS[chainId];
return (
<img
src={url}
className="inline-block w-4 h-4 mr-1 dark:invert"
alt={alt}
title={alt}
/>
);
} else {
return null;
}
};
@@ -0,0 +1,41 @@
import Hash from '../hash';
import { getExternalExplorerLink } from '@vegaprotocol/environment';
import { ExternalChainIcon } from './external-chain-icon';
export enum EthExplorerLinkTypes {
block = 'block',
address = 'address',
tx = 'tx',
}
export type ExternalExplorerLinkProps = Partial<typeof HTMLAnchorElement> & {
id: string;
type: EthExplorerLinkTypes;
// Defaults to Ethereum Mainnet, as chain support was added late
chain?: string;
code?: boolean;
};
export const ExternalExplorerLink = ({
id,
type,
chain = '1',
code = false,
...props
}: ExternalExplorerLinkProps) => {
const link = `${getExternalExplorerLink(chain)}/${type}/${id}${
code ? '#code' : ''
}`;
return (
<a
className="underline external font-mono"
target="_blank"
rel="noopener noreferrer"
{...props}
href={link}
>
<ExternalChainIcon chainId={chain} />
<Hash text={id} />
</a>
);
};
@@ -0,0 +1,16 @@
import { render, screen } from '@testing-library/react';
import GovernanceLink from './governance-link';
describe('GovernanceLink', () => {
it('renders the link with the correct text', () => {
render(<GovernanceLink text="Governance internet website" />);
const linkElement = screen.getByText('Governance internet website');
expect(linkElement).toBeInTheDocument();
});
it('renders the link with the correct href and sensible default text', () => {
render(<GovernanceLink />);
const linkElement = screen.getByText('Governance');
expect(linkElement).toBeInTheDocument();
});
});
@@ -0,0 +1,18 @@
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import { t } from '@vegaprotocol/i18n';
export type GovernanceLinkProps = {
text?: string;
};
/**
* Just a link to the governance page, with optional text
*/
const GovernanceLink = ({ text = t('Governance') }: GovernanceLinkProps) => {
const base = ENV.dataSources.governanceUrl;
return <ExternalLink href={base}>{text}</ExternalLink>;
};
export default GovernanceLink;
@@ -1,4 +1,4 @@
export type HashProps = { export type HashProps = React.HTMLProps<HTMLSpanElement> & {
text: string; text: string;
truncate?: boolean; truncate?: boolean;
}; };
@@ -2,4 +2,5 @@ export { default as BlockLink } from './block-link/block-link';
export { default as PartyLink } from './party-link/party-link'; export { default as PartyLink } from './party-link/party-link';
export { default as NodeLink } from './node-link/node-link'; export { default as NodeLink } from './node-link/node-link';
export { default as MarketLink } from './market-link/market-link'; export { default as MarketLink } from './market-link/market-link';
export { default as NetworkParameterLink } from './network-parameter-link/network-parameter-link';
export * from './asset-link/asset-link'; export * from './asset-link/asset-link';
@@ -0,0 +1,30 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type NetworkParameterLinkProps = Partial<ComponentProps<typeof Link>> & {
parameter: string;
};
/**
* Links a given network parameter to the relevant page and anchor on the page
*/
const NetworkParameterLink = ({
parameter,
...props
}: NetworkParameterLinkProps) => {
return (
<Link
className="underline"
{...props}
to={`/${Routes.NETWORK_PARAMETERS}#${parameter}`}
>
<Hash text={parameter} />
</Link>
);
};
export default NetworkParameterLink;
@@ -32,9 +32,17 @@ export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & { export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string; id: string;
truncate?: boolean; truncate?: boolean;
networkLabel?: string;
truncateLength?: number;
}; };
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => { const PartyLink = ({
id,
truncate = false,
truncateLength = 4,
networkLabel = t('Network'),
...props
}: PartyLinkProps) => {
const { data } = useExplorerNodeNamesQuery(); const { data } = useExplorerNodeNamesQuery();
const name = useMemo(() => getNameForParty(id, data), [data, id]); const name = useMemo(() => getNameForParty(id, data), [data, id]);
const useName = name !== id; const useName = name !== id;
@@ -44,7 +52,7 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) { if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
return ( return (
<span className="font-mono" data-testid="network"> <span className="font-mono" data-testid="network">
{t('Network')} {networkLabel}
</span> </span>
); );
} }
@@ -60,7 +68,7 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
} }
return ( return (
<span className="whitespace-nowrap"> <span>
{useName && <Icon size={4} name="cube" className="mr-2" />} {useName && <Icon size={4} name="cube" className="mr-2" />}
<Link <Link
className="underline font-mono" className="underline font-mono"
@@ -70,7 +78,11 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
{useName ? ( {useName ? (
name name
) : ( ) : (
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} /> <Hash
text={
truncate ? truncateMiddle(id, truncateLength, truncateLength) : id
}
/>
)} )}
</Link> </Link>
</span> </span>
@@ -1,9 +1,11 @@
query ExplorerProposal($id: ID!) { query ExplorerProposal($id: ID!) {
proposal(id: $id) { proposal(id: $id) {
id ... on Proposal {
rationale { id
title rationale {
description title
description
}
} }
} }
} }
@@ -8,16 +8,18 @@ export type ExplorerProposalQueryVariables = Types.Exact<{
}>; }>;
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null }; export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export const ExplorerProposalDocument = gql` export const ExplorerProposalDocument = gql`
query ExplorerProposal($id: ID!) { query ExplorerProposal($id: ID!) {
proposal(id: $id) { proposal(id: $id) {
id ... on Proposal {
rationale { id
title rationale {
description title
description
}
} }
} }
} }
@@ -3,7 +3,11 @@ import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import ProposalLink from './proposal-link'; import ProposalLink from './proposal-link';
import { ExplorerProposalDocument } from './__generated__/Proposal'; import {
ExplorerProposalDocument,
type ExplorerProposalQuery,
type ExplorerProposalQueryVariables,
} from './__generated__/Proposal';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
function renderComponent(id: string, mocks: MockedResponse[]) { function renderComponent(id: string, mocks: MockedResponse[]) {
@@ -23,7 +27,10 @@ describe('Proposal link component', () => {
}); });
it('Renders the ID on error', async () => { it('Renders the ID on error', async () => {
const mock = { const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
request: { request: {
query: ExplorerProposalDocument, query: ExplorerProposalDocument,
variables: { variables: {
@@ -40,17 +47,22 @@ describe('Proposal link component', () => {
}); });
it('Renders the proposal title when the query returns a result', async () => { it('Renders the proposal title when the query returns a result', async () => {
const mock = { const proposalId = '123';
const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
request: { request: {
query: ExplorerProposalDocument, query: ExplorerProposalDocument,
variables: { variables: {
id: '123', id: proposalId,
}, },
}, },
result: { result: {
data: { data: {
proposal: { proposal: {
id: '123', __typename: 'Proposal',
id: proposalId,
rationale: { rationale: {
title: 'test-title', title: 'test-title',
description: 'test description', description: 'test description',
@@ -60,13 +72,16 @@ describe('Proposal link component', () => {
}, },
}; };
const res = render(renderComponent('123', [mock])); const res = render(renderComponent(proposalId, [mock]));
expect(res.getByText('123')).toBeInTheDocument(); expect(res.getByText(proposalId)).toBeInTheDocument();
expect(await res.findByText('test-title')).toBeInTheDocument(); expect(await res.findByText('test-title')).toBeInTheDocument();
}); });
it('Leaves the proposal id when the market is not found', async () => { it('Leaves the proposal id when the market is not found', async () => {
const mock = { const mock: MockedResponse<
ExplorerProposalQuery,
ExplorerProposalQueryVariables
> = {
request: { request: {
query: ExplorerProposalDocument, query: ExplorerProposalDocument,
variables: { variables: {
@@ -1,7 +1,11 @@
import { useExplorerProposalQuery } from './__generated__/Proposal'; import {
useExplorerProposalQuery,
type ExplorerProposalQuery,
} from './__generated__/Proposal';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env'; import { ENV } from '../../../config/env';
import Hash from '../hash'; import Hash from '../hash';
export type ProposalLinkProps = { export type ProposalLinkProps = {
id: string; id: string;
text?: string; text?: string;
@@ -16,8 +20,13 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
variables: { id }, variables: { id },
}); });
const proposal = data?.proposal as Extract<
ExplorerProposalQuery['proposal'],
{ __typename?: 'Proposal' }
>;
const base = ENV.dataSources.governanceUrl; const base = ENV.dataSources.governanceUrl;
const label = data?.proposal?.rationale.title || id; const label = proposal?.rationale?.title || id;
return ( return (
<ExternalLink href={`${base}/proposals/${id}`}> <ExternalLink href={`${base}/proposals/${id}`}>
@@ -1,10 +1,12 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets'; import type { MarketInfoWithData } from '@vegaprotocol/markets';
import { import {
LiquidationStrategyInfoPanel,
LiquidityPriceRangeInfoPanel, LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel, LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel, MarginScalingFactorsPanel,
PriceMonitoringBoundsInfoPanel, PriceMonitoringBoundsInfoPanel,
PriceMonitoringSettingsInfoPanel,
SuccessionLineInfoPanel, SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData, getDataSourceSpecForSettlementData,
getDataSourceSpecForTradingTermination, getDataSourceSpecForTradingTermination,
@@ -20,7 +22,6 @@ import {
RiskModelInfoPanel, RiskModelInfoPanel,
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
} from '@vegaprotocol/markets'; } from '@vegaprotocol/markets';
import { MarketInfoTable } from '@vegaprotocol/markets';
import type { DataSourceFragment } from '@vegaprotocol/markets'; import type { DataSourceFragment } from '@vegaprotocol/markets';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
@@ -73,27 +74,16 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<MarginScalingFactorsPanel market={market} /> <MarginScalingFactorsPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2> <h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} /> <RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => ( <h2 className={headerClassName}>{t('Price monitoring bounds')}</h2>
<> <div className="mt-3">
<h2 className={headerClassName}> <PriceMonitoringBoundsInfoPanel market={market} />
{t('Price monitoring bounds %s', [(i + 1).toString()])} </div>
</h2> <h2 className={headerClassName}>{t('Price monitoring settings')}</h2>
<PriceMonitoringBoundsInfoPanel <div className="mt-3">
market={market} <PriceMonitoringSettingsInfoPanel market={market} />
triggerIndex={i + 1} </div>
/> <h2 className={headerClassName}>{t('Liquidation strategy')}</h2>
</> <LiquidationStrategyInfoPanel market={market} />
))}
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring settings %s', [(i + 1).toString()])}
</h2>
<MarketInfoTable data={trigger} key={i} />
</>
)
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2> <h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} /> <LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2> <h2 className={headerClassName}>{t('Liquidity price range')}</h2>
@@ -1,5 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { getAsset, type MarketFieldsFragment } from '@vegaprotocol/markets'; import { getAsset, type MarketMaybeWithData } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { type AgGridReact } from 'ag-grid-react'; import { type AgGridReact } from 'ag-grid-react';
@@ -17,7 +17,7 @@ import { type RowClickedEvent } from 'ag-grid-community';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
type MarketsTableProps = { type MarketsTableProps = {
data: MarketFieldsFragment[] | null; data: MarketMaybeWithData[] | null;
}; };
export const MarketsTable = ({ data }: MarketsTableProps) => { export const MarketsTable = ({ data }: MarketsTableProps) => {
const openAssetDetailsDialog = useAssetDetailsDialogStore( const openAssetDetailsDialog = useAssetDetailsDialogStore(
@@ -29,7 +29,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
const gridRef = useRef<AgGridReact>(null); const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
const showColumnsOnDesktop = () => { const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible( gridRef.current?.api.setColumnsVisible(
['id', 'state', 'asset'], ['id', 'state', 'asset'],
window.innerWidth > BREAKPOINT_MD window.innerWidth > BREAKPOINT_MD
); );
@@ -56,10 +56,10 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
headerName: t('Status'), headerName: t('Status'),
field: 'state', field: 'state',
hide: window.innerWidth <= BREAKPOINT_MD, hide: window.innerWidth <= BREAKPOINT_MD,
valueGetter: ({ valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
data, return data?.data?.marketState
}: VegaValueGetterParams<MarketFieldsFragment>) => { ? MarketStateMapping[data?.data.marketState]
return data?.state ? MarketStateMapping[data?.state] : '-'; : '-';
}, },
}, },
{ {
@@ -70,7 +70,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
cellRenderer: ({ cellRenderer: ({
data, data,
}: VegaICellRendererParams< }: VegaICellRendererParams<
MarketFieldsFragment, MarketMaybeWithData,
'tradableInstrument.instrument.product.settlementAsset.symbol' 'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => { >) => {
const value = data && getAsset(data); const value = data && getAsset(data);
@@ -99,7 +99,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
field: 'id', field: 'id',
cellRenderer: ({ cellRenderer: ({
value, value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) => }: VegaICellRendererParams<MarketMaybeWithData, 'id'>) =>
value ? ( value ? (
<Link className="underline" to={value}> <Link className="underline" to={value}>
{t('View details')} {t('View details')}
@@ -116,7 +116,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
<AgGrid <AgGrid
ref={gridRef} ref={gridRef}
rowData={data} rowData={data}
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id} getRowId={({ data }: { data: MarketMaybeWithData }) => data.id}
overlayNoRowsTemplate={t('This chain has no markets')} overlayNoRowsTemplate={t('This chain has no markets')}
domLayout="autoHeight" domLayout="autoHeight"
defaultColDef={{ defaultColDef={{
@@ -1,9 +1,5 @@
import { render, waitFor } from '@testing-library/react'; import { render, waitFor } from '@testing-library/react';
import { import { NestedDataList, sortNestedDataByChildren } from './nested-data-list';
BORDER_COLOURS,
NestedDataList,
sortNestedDataByChildren,
} from './nested-data-list';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
const mockData = { const mockData = {
@@ -61,38 +57,6 @@ describe('NestedDataList', () => {
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2'); expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
}); });
it('should repeat the border colours in the correct order', () => {
const colourMockData = {
t0: {
t1: {
t2: {
t3: {
t4: {
t5: {
t6: {
t7: {
t8: {
hello: 'world',
},
},
},
},
},
},
},
},
},
};
const tree = render(<NestedDataList data={colourMockData} />);
const { getByTestId } = tree;
for (let i = 0; i < 8; i++) {
const item = getByTestId(`T${i}`);
const expected = BORDER_COLOURS.light[i % 5];
expect(item.style.borderColor.toUpperCase()).toBe(expected);
}
});
it('should sort the data by values with children', () => { it('should sort the data by values with children', () => {
const mockData = { const mockData = {
nonce: '5980890939790185837', nonce: '5980890939790185837',
@@ -0,0 +1,159 @@
import compact from 'lodash/compact';
import { MarketLink } from '../links';
import { type MarketState, MarketStateMapping } from '@vegaprotocol/types';
import OracleLink from '../links/oracle-link/oracle-link';
import type {
ExplorerOracleForMarketQuery,
ExplorerOracleFormMarketsQuery,
} from '../../routes/oracles/__generated__/OraclesForMarkets';
import { useState } from 'react';
export type OraclesTableProps = {
data?: ExplorerOracleFormMarketsQuery | ExplorerOracleForMarketQuery;
};
const cellSpacing = 'px-3';
export function OraclesTable({ data }: OraclesTableProps) {
const [hoveredOracle, setHoveredOracle] = useState('');
return (
<table className="text-left">
<thead>
<tr>
<th className={cellSpacing}>Market</th>
<th className={cellSpacing}>Type</th>
<th className={cellSpacing}>State</th>
<th className={cellSpacing}>Settlement</th>
<th className={cellSpacing}>Termination</th>
</tr>
</thead>
<tbody>
{data?.marketsConnection?.edges
? data.marketsConnection.edges.map((o) => {
let hasSeenOracleReports = false;
let settlementOracle = '-';
let settlementOracleStatus = '-';
let terminationOracle = '-';
let terminationOracleStatus = '-';
const id = o?.node.id;
if (!id) {
return null;
}
if (
o.node.tradableInstrument.instrument.product.__typename ===
'Future'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.status;
} else if (
o.node.tradableInstrument.instrument.product.__typename ===
'Perpetual'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.status;
}
const oracleInformationUnfiltered =
data?.oracleSpecsConnection?.edges?.map((e) =>
e && e.node ? e.node : undefined
) || [];
const oracleInformation = compact(oracleInformationUnfiltered)
.filter(
(o) =>
o.dataConnection.edges &&
o.dataConnection.edges.length > 0 &&
(o.dataSourceSpec.spec.id === settlementOracle ||
o.dataSourceSpec.spec.id === terminationOracle)
)
.at(0);
if (oracleInformation) {
hasSeenOracleReports = true;
}
const oracleList = `${settlementOracle} ${terminationOracle}`;
return (
<tr
id={id}
key={id}
className={
hoveredOracle.length > 0 &&
oracleList.indexOf(hoveredOracle) > -1
? 'bg-gray-100 dark:bg-gray-800'
: ''
}
data-testid="oracle-details"
data-oracles={oracleList}
>
<td className={cellSpacing}>
<MarketLink id={id} />
</td>
<td className={cellSpacing}>
{o.node.tradableInstrument.instrument.product.__typename}
</td>
<td className={cellSpacing}>
{MarketStateMapping[o.node.state as MarketState]}
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === settlementOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={settlementOracle}
status={settlementOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() => setHoveredOracle(settlementOracle)}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === terminationOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={terminationOracle}
status={terminationOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() => setHoveredOracle(terminationOracle)}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
</tr>
);
})
: null}
</tbody>
</table>
);
}
@@ -175,6 +175,7 @@ describe('Amend order details', () => {
const res = renderExistingAmend('123', 1, amend); const res = renderExistingAmend('123', 1, amend);
expect(await res.findByText('New size')).toBeInTheDocument(); expect(await res.findByText('New size')).toBeInTheDocument();
expect(await res.findByText('Size ±')).toBeInTheDocument();
}); });
it('Renders Reference if provided', async () => { it('Renders Reference if provided', async () => {
@@ -82,7 +82,7 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
{amend.sizeDelta && amend.sizeDelta !== '0' ? ( {amend.sizeDelta && amend.sizeDelta !== '0' ? (
<div className="mb-12 md:mb-0"> <div className="mb-12 md:mb-0">
<h2 className="text-dark mb-4 text-2xl font-bold"> <h2 className="text-dark mb-4 text-2xl font-bold">
{t('New size')} {t('Size ±')}
</h2> </h2>
<h5 <h5
className={`mb-0 text-lg font-medium capitalize text-gray-500 ${getSideDeltaColour( className={`mb-0 text-lg font-medium capitalize text-gray-500 ${getSideDeltaColour(
@@ -93,6 +93,16 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
</h5> </h5>
</div> </div>
) : null} ) : null}
{o && (
<div className="">
<h2 className="text-dark mb-4 text-2xl font-bold">
{t('New size')}
</h2>
<h5 className="mb-0 text-lg font-medium text-gray-500">
{o ? o.size : null}
</h5>
</div>
)}
{amend.price && amend.price !== '0' ? ( {amend.price && amend.price !== '0' ? (
<div className=""> <div className="">
@@ -60,7 +60,7 @@ const DeterministicOrderDetails = ({
const o = data.orderByID; const o = data.orderByID;
return ( return (
<div className={wrapperClasses}> <div className={wrapperClasses}>
<div className="mb-12 lg:mb-0"> <div className="mb-0">
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7"> <div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
<h2 className="text-3xl font-bold mb-4 display-5"> <h2 className="text-3xl font-bold mb-4 display-5">
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2"> <abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
@@ -89,9 +89,9 @@ const DeterministicOrderDetails = ({
<span>{t('Reference')}</span>: {o.reference} <span>{t('Reference')}</span>: {o.reference}
</p> </p>
) : null} ) : null}
<div className="grid md:grid-cols-5 gap-x-6 mt-4"> <div className="grid grid-cols-2 md:grid-cols-5 gap-x-6 mt-4">
<div className="mb-12 md:mb-0"> <div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4"> <h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Status')} {t('Status')}
</h2> </h2>
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize"> <h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
@@ -99,15 +99,17 @@ const DeterministicOrderDetails = ({
</h5> </h5>
</div> </div>
<div className="mb-12 md:mb-0"> <div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2> <h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Size')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0"> <h5 className="text-lg font-medium text-gray-500 mb-0">
<SizeInMarket size={o.size} marketId={o.market.id} /> <SizeInMarket size={o.size} marketId={o.market.id} />
</h5> </h5>
</div> </div>
<div className=""> <div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4"> <h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Version')} {t('Version')}
</h2> </h2>
<h5 className="text-lg font-medium text-gray-500 mb-0"> <h5 className="text-lg font-medium text-gray-500 mb-0">
@@ -115,8 +117,8 @@ const DeterministicOrderDetails = ({
</h5> </h5>
</div> </div>
{o.type ? ( {o.type ? (
<div className=""> <div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4"> <h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Type')} {t('Type')}
</h2> </h2>
<h5 className="text-lg font-medium text-gray-500 mb-0"> <h5 className="text-lg font-medium text-gray-500 mb-0">
@@ -1,5 +1,4 @@
import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals'; import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals';
import { type AgGridReact } from 'ag-grid-react'; import { type AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid'; import { AgGrid } from '@vegaprotocol/datagrid';
@@ -12,12 +11,7 @@ import { type ColDef } from 'ag-grid-community';
import type { RowClickedEvent } from 'ag-grid-community'; import type { RowClickedEvent } from 'ag-grid-community';
import { getDateTimeFormat } from '@vegaprotocol/utils'; import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { ProposalStateMapping } from '@vegaprotocol/types'; import { ProposalStateMapping } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment'; import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import { BREAKPOINT_MD } from '../../config/breakpoints'; import { BREAKPOINT_MD } from '../../config/breakpoints';
import { JsonViewerDialog } from '../dialogs/json-viewer-dialog'; import { JsonViewerDialog } from '../dialogs/json-viewer-dialog';
@@ -31,24 +25,16 @@ type ProposalsTableProps = {
data: ProposalListFieldsFragment[] | null; data: ProposalListFieldsFragment[] | null;
}; };
export const ProposalsTable = ({ data }: ProposalsTableProps) => { export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
]);
const tokenLink = useLinks(DApp.Governance); const tokenLink = useLinks(DApp.Governance);
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); const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
const showColumnsOnDesktop = () => { const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible( gridRef.current?.api.setColumnsVisible(
['voting', 'cDate', 'eDate', 'type'], ['voting', 'cDate', 'eDate', 'type'],
window.innerWidth > BREAKPOINT_MD window.innerWidth > BREAKPOINT_MD
); );
gridRef.current?.columnApi.setColumnWidth( gridRef.current?.api.setColumnWidth(
'actions', 'actions',
window.innerWidth > BREAKPOINT_MD ? 221 : 80 window.innerWidth > BREAKPOINT_MD ? 221 : 80
); );
@@ -90,33 +76,6 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
return value ? ProposalStateMapping[value] : '-'; return value ? ProposalStateMapping[value] : '-';
}, },
}, },
{
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="flex h-full items-center justify-center pt-2 uppercase">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</div>
);
}
return '-';
},
},
{ {
colId: 'cDate', colId: 'cDate',
maxWidth: 150, maxWidth: 150,
@@ -184,7 +143,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
}, },
}, },
], ],
[requiredMajorityPercentage, tokenLink] [tokenLink]
); );
return ( return (
<> <>
@@ -30,12 +30,12 @@ export const Signature = ({ signature }: SignatureProps) => {
return ( return (
<div className="inline-flex border rounded signature-component relative pr-[20px]"> <div className="inline-flex border rounded signature-component relative pr-[20px]">
<span <div
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default" className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
title={`Version ${signature.version}`} title={`${signature.algo}`}
> >
{signature.algo} <span>v{signature.version}</span>
</span> </div>
<div <div
className={ className={
isOpen isOpen
@@ -33,10 +33,10 @@ const SizeInAsset = ({
} }
return ( return (
<p> <span>
<span>{label}</span>&nbsp; <span>{label}</span>&nbsp;
<AssetLink assetId={assetId} showAssetSymbol={true} asDialog={true} /> <AssetLink assetId={assetId} showAssetSymbol={true} asDialog={true} />
</p> </span>
); );
}; };
@@ -1,9 +1,10 @@
import { TableCell, TableRow } from '../../../table'; import { TableCell, TableRow } from '../../../table';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
import { getExternalChainLabel } from '@vegaprotocol/environment';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { defaultAbiCoder, base64 } from 'ethers/lib/utils'; import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
import { BigNumber } from 'ethers'; import { BigNumber } from 'ethers';
@@ -44,10 +45,10 @@ export const TxDetailsContractCall = ({
}, },
}); });
if (!contractCall || !contractCall.result) { if (!contractCall) {
return null; return null;
} }
const chainLabel = getExternalChainLabel(contractCall.sourceChainId);
return ( return (
<> <>
{contractCall.specId && ( {contractCall.specId && (
@@ -64,9 +65,12 @@ export const TxDetailsContractCall = ({
)} )}
{contractCall.blockHeight && ( {contractCall.blockHeight && (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('ETH block')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink {chainLabel} {t('block')}
</TableCell>
<TableCell>
<ExternalExplorerLink
chain={contractCall.sourceChainId}
id={contractCall.blockHeight} id={contractCall.blockHeight}
type={EthExplorerLinkTypes.block} type={EthExplorerLinkTypes.block}
/> />
@@ -75,14 +79,24 @@ export const TxDetailsContractCall = ({
)} )}
{data?.oracleSpec?.dataSourceSpec && ( {data?.oracleSpec?.dataSourceSpec && (
<OracleEthSource <OracleEthSource
chain={contractCall.sourceChainId}
sourceType={data.oracleSpec.dataSourceSpec.spec.data.sourceType} sourceType={data.oracleSpec.dataSourceSpec.spec.data.sourceType}
/> />
)} )}
<TableRow modifier="bordered"> {contractCall.error && (
<TableCell>{t('Result')}</TableCell> <TableRow modifier="bordered">
<TableCell>{decodeEthCallResult(contractCall.result)}</TableCell> <TableCell>{t('Call error')}</TableCell>
</TableRow> <TableCell>{contractCall.error}</TableCell>
</TableRow>
)}
{contractCall.result && (
<TableRow modifier="bordered">
<TableCell>{t('Result')}</TableCell>
<TableCell>{decodeEthCallResult(contractCall.result)}</TableCell>
</TableRow>
)}
</> </>
); );
}; };
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links'; import { AssetLink } from '../../../links';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventErc20AssetLimitsUpdatedProps { interface TxDetailsChainEventErc20AssetLimitsUpdatedProps {
assetLimitsUpdated: components['schemas']['vegaERC20AssetLimitsUpdated']; assetLimitsUpdated: components['schemas']['vegaERC20AssetLimitsUpdated'];
@@ -42,7 +42,7 @@ export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('ERC20 asset')}</TableCell> <TableCell>{t('ERC20 asset')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={assetLimitsUpdated.sourceEthereumAddress} id={assetLimitsUpdated.sourceEthereumAddress}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links'; import { AssetLink } from '../../../links';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventErc20AssetListProps { interface TxDetailsChainEventErc20AssetListProps {
assetList: components['schemas']['vegaERC20AssetList']; assetList: components['schemas']['vegaERC20AssetList'];
@@ -32,7 +32,7 @@ export const TxDetailsChainEventErc20AssetList = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell> <TableCell>{t('Source')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={assetList.assetSource} id={assetList.assetSource}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { AssetLink, PartyLink } from '../../../links'; import { AssetLink, PartyLink } from '../../../links';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventProps { interface TxDetailsChainEventProps {
deposit: components['schemas']['vegaERC20Deposit']; deposit: components['schemas']['vegaERC20Deposit'];
@@ -36,7 +36,7 @@ export const TxDetailsChainEventDeposit = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell> <TableCell>{t('Source')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={deposit.sourceEthereumAddress} id={deposit.sourceEthereumAddress}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links'; import { AssetLink } from '../../../links';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventWithdrawalProps { interface TxDetailsChainEventWithdrawalProps {
withdrawal: components['schemas']['vegaERC20Withdrawal']; withdrawal: components['schemas']['vegaERC20Withdrawal'];
@@ -35,7 +35,7 @@ export const TxDetailsChainEventWithdrawal = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell> <TableCell>{t('Recipient')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={withdrawal.targetEthereumAddress} id={withdrawal.targetEthereumAddress}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links'; import { PartyLink } from '../../../links';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventStakeDepositProps { interface TxDetailsChainEventStakeDepositProps {
deposit: components['schemas']['vegaStakeDeposited']; deposit: components['schemas']['vegaStakeDeposited'];
@@ -39,7 +39,7 @@ export const TxDetailsChainEventStakeDeposit = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell> <TableCell>{t('Source')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={deposit.ethereumAddress} id={deposit.ethereumAddress}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links'; import { PartyLink } from '../../../links';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventStakeRemoveProps { interface TxDetailsChainEventStakeRemoveProps {
remove: components['schemas']['vegaStakeRemoved']; remove: components['schemas']['vegaStakeRemoved'];
@@ -39,7 +39,7 @@ export const TxDetailsChainEventStakeRemove = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell> <TableCell>{t('Source')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={remove.ethereumAddress} id={remove.ethereumAddress}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -3,9 +3,9 @@ import { t } from '@vegaprotocol/i18n';
import { TableRow, TableCell } from '../../../table'; import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer'; import type { components } from '../../../../../types/explorer';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link'; } from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventStakeTotalSupplyProps { interface TxDetailsChainEventStakeTotalSupplyProps {
update: components['schemas']['vegaStakeTotalSupply']; update: components['schemas']['vegaStakeTotalSupply'];
@@ -38,7 +38,7 @@ export const TxDetailsChainEventStakeTotalSupply = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell> <TableCell>{t('Source')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={update.tokenAddress} id={update.tokenAddress}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -1,7 +1,14 @@
query ExplorerProposalStatus($id: ID!) { query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) { proposal(id: $id) {
id ... on Proposal {
state id
rejectionReason state
rejectionReason
}
... on BatchProposal {
id
state
rejectionReason
}
} }
} }
@@ -8,15 +8,22 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
}>; }>;
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null }; export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
export const ExplorerProposalStatusDocument = gql` export const ExplorerProposalStatusDocument = gql`
query ExplorerProposalStatus($id: ID!) { query ExplorerProposalStatus($id: ID!) {
proposal(id: $id) { proposal(id: $id) {
id ... on Proposal {
state id
rejectionReason state
rejectionReason
}
... on BatchProposal {
id
state
rejectionReason
}
} }
} }
`; `;
@@ -0,0 +1,257 @@
import { render, screen } from '@testing-library/react';
import { BatchItem } from './batch-item';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { components } from '../../../../../types/explorer';
type Item = components['schemas']['vegaBatchProposalTermsChange'];
describe('BatchItem', () => {
it('Renders "Unknown proposal type" by default', () => {
const item = {};
render(<BatchItem item={item} />);
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
});
it('Renders "Unknown proposal type" for unknown items', () => {
const item = {
newLochNessMonster: {
location: 'Loch Ness',
},
} as unknown as Item;
render(<BatchItem item={item} />);
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
});
it('Renders "New spot market"', () => {
const item = {
newSpotMarket: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New spot market')).toBeInTheDocument();
});
it('Renders "Cancel transfer"', () => {
const item = {
cancelTransfer: {
changes: {
transferId: 'transfer123',
},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
expect(screen.getByText('transf')).toBeInTheDocument();
});
it('Renders "Cancel transfer" without an id', () => {
const item = {
cancelTransfer: {
changes: {},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
});
it('Renders "New freeform"', () => {
const item = {
newFreeform: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New freeform proposal')).toBeInTheDocument();
});
it('Renders "New market"', () => {
const item = {
newMarket: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New market')).toBeInTheDocument();
});
it('Renders "New transfer"', () => {
const item = {
newTransfer: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('New transfer')).toBeInTheDocument();
});
it('Renders "Update asset" with assetId', () => {
const item = {
updateAsset: {
assetId: 'asset123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update asset')).toBeInTheDocument();
expect(screen.getByText('asset123')).toBeInTheDocument();
});
it('Renders "Update asset" even if assetId is not set', () => {
const item = {
updateAsset: {
assetId: undefined,
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update asset')).toBeInTheDocument();
});
it('Renders "Update market state" with marketId', () => {
const item = {
updateMarketState: {
changes: {
marketId: 'market123',
},
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market state')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update market state" even if marketId is not set', () => {
const item = {
updateMarketState: {
changes: {
marketId: undefined,
},
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market state')).toBeInTheDocument();
});
it('Renders "Update network parameter" with parameter', () => {
const item = {
updateNetworkParameter: {
changes: {
key: 'parameter123',
},
},
};
render(
<MockedProvider>
<MemoryRouter>
<BatchItem item={item} />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
expect(screen.getByText('parameter123')).toBeInTheDocument();
});
it('Renders "Update network parameter" even if parameter is not set', () => {
const item = {
updateNetworkParameter: {
changes: {
key: undefined,
},
},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
});
it('Renders "Update referral program"', () => {
const item = {
updateReferralProgram: {},
};
render(<BatchItem item={item} />);
expect(screen.getByText('Update referral program')).toBeInTheDocument();
});
it('Renders "Update spot market" with marketId', () => {
const item = {
updateSpotMarket: {
marketId: 'market123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update spot market')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update spot market" even if marketId is not set', () => {
const item = {
updateSpotMarket: {
marketId: undefined,
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update spot market')).toBeInTheDocument();
});
it('Renders "Update market" with marketId', () => {
const item = {
updateMarket: {
marketId: 'market123',
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market')).toBeInTheDocument();
expect(screen.getByText('market123')).toBeInTheDocument();
});
it('Renders "Update market" even if marketId is not set', () => {
const item = {
updateMarket: {
marketId: undefined,
},
};
render(
<MemoryRouter>
<MockedProvider>
<BatchItem item={item} />
</MockedProvider>
</MemoryRouter>
);
expect(screen.getByText('Update market')).toBeInTheDocument();
});
it('Renders "Update volume discount program"', () => {
const item = {
updateVolumeDiscountProgram: {},
};
render(<BatchItem item={item} />);
expect(
screen.getByText('Update volume discount program')
).toBeInTheDocument();
});
});
@@ -0,0 +1,87 @@
import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink, NetworkParameterLink } from '../../../links';
import type { components } from '../../../../../types/explorer';
import Hash from '../../../links/hash';
type Item = components['schemas']['vegaBatchProposalTermsChange'];
export interface BatchItemProps {
item: Item;
}
/**
* Produces a one line summary for an item in a batch proposal. Could
* easily be adapted to summarise individual proposals, but there is no
* place for that yet.
*
* Details (like IDs) should be shown and linked if available, but handled
* if not available. This is adequate as the ProposalSummary component contains
* a JSON viewer for the full proposal.
*/
export const BatchItem = ({ item }: BatchItemProps) => {
if (item.cancelTransfer) {
const transferId = item?.cancelTransfer?.changes?.transferId || false;
return (
<span>
{t('Cancel transfer')}&nbsp;
{transferId && (
<Hash className="ml-1" truncate={true} text={transferId} />
)}
</span>
);
} else if (item.newFreeform) {
return <span>{t('New freeform proposal')}</span>;
} else if (item.newMarket) {
return <span>{t('New market')}</span>;
} else if (item.newSpotMarket) {
return <span>{t('New spot market')}</span>;
} else if (item.newTransfer) {
return <span>{t('New transfer')}</span>;
} else if (item.updateAsset) {
const assetId = item?.updateAsset?.assetId || false;
return (
<span>
{t('Update asset')}
{assetId && <AssetLink className="ml-1" assetId={assetId} />}
</span>
);
} else if (item.updateMarket) {
const marketId = item?.updateMarket?.marketId || false;
return (
<span>
{t('Update market')}{' '}
{marketId && <MarketLink className="ml-1" id={marketId} />}
</span>
);
} else if (item.updateMarketState) {
const marketId = item?.updateMarketState?.changes?.marketId || false;
return (
<span>
{t('Update market state')}
{marketId && <MarketLink className="ml-1" id={marketId} />}
</span>
);
} else if (item.updateNetworkParameter) {
const param = item?.updateNetworkParameter?.changes?.key || false;
return (
<span>
{t('Update network parameter')}
{param && <NetworkParameterLink className="ml-1" parameter={param} />}
</span>
);
} else if (item.updateReferralProgram) {
return <span>{t('Update referral program')}</span>;
} else if (item.updateSpotMarket) {
const marketId = item?.updateSpotMarket?.marketId || '';
return (
<span>
{t('Update spot market')}
<MarketLink className="ml-1" id={marketId} />
</span>
);
} else if (item.updateVolumeDiscountProgram) {
return <span>{t('Update volume discount program')}</span>;
}
return <span>{t('Unknown proposal type')}</span>;
};
@@ -14,16 +14,18 @@ export function format(date: string | undefined, def: string) {
return new Date().toLocaleDateString() || def; return new Date().toLocaleDateString() || def;
} }
export function getDate( type Proposal = Extract<
data: ExplorerProposalStatusQuery | undefined, ExplorerProposalStatusQuery['proposal'],
terms: Terms { __typename?: 'Proposal' }
): string { >;
export function getDate(proposal: Proposal | undefined, terms: Terms): string {
const DEFAULT = t('Unknown'); const DEFAULT = t('Unknown');
if (!data?.proposal?.state) { if (!proposal?.state) {
return DEFAULT; return DEFAULT;
} }
switch (data.proposal.state) { switch (proposal.state) {
case 'STATE_DECLINED': case 'STATE_DECLINED':
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`; return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
case 'STATE_ENACTED': case 'STATE_ENACTED':
@@ -62,9 +64,11 @@ export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
}, },
}); });
const proposal = data?.proposal as Proposal;
return ( return (
<Lozenge className="font-sans text-xs float-right"> <Lozenge className="font-sans text-xs float-right">
{getDate(data, terms)} {getDate(proposal, terms)}
</Lozenge> </Lozenge>
); );
}; };
@@ -2,17 +2,8 @@ import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit'; import type { IconProps } from '@vegaprotocol/ui-toolkit';
import { useExplorerProposalStatusQuery } from './__generated__/Proposal'; import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal'; import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
import type * as Apollo from '@apollo/client';
import type * as Types from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
type ProposalQueryResult = Apollo.QueryResult<
ExplorerProposalStatusQuery,
Types.Exact<{
id: string;
}>
>;
interface ProposalStatusIconProps { interface ProposalStatusIconProps {
id: string; id: string;
} }
@@ -29,29 +20,38 @@ type IconAndLabel = {
* @param data a data result from useExplorerProposalStatusQuery * @param data a data result from useExplorerProposalStatusQuery
* @returns Icon name * @returns Icon name
*/ */
export function getIconAndLabelForStatus( export function useIconAndLabelForStatus(id: string): IconAndLabel {
res: ProposalQueryResult const { data, loading, error } = useExplorerProposalStatusQuery({
): IconAndLabel { variables: {
id,
},
});
const proposal = data?.proposal as Extract<
ExplorerProposalStatusQuery['proposal'],
{ __typename?: 'Proposal' }
>;
const DEFAULT: IconAndLabel = { const DEFAULT: IconAndLabel = {
icon: 'error', icon: 'error',
label: t('Proposal state unknown'), label: t('Proposal state unknown'),
}; };
if (res.loading) { if (loading) {
return { return {
icon: 'more', icon: 'more',
label: t('Loading data'), label: t('Loading data'),
}; };
} }
if (!res?.data?.proposal || res.error) { if (!data?.proposal || error) {
return { return {
icon: 'error', icon: 'error',
label: res.error?.message || DEFAULT.label, label: error?.message || DEFAULT.label,
}; };
} }
switch (res.data.proposal.state) { switch (proposal.state) {
case 'STATE_DECLINED': case 'STATE_DECLINED':
return { return {
icon: 'stop', icon: 'stop',
@@ -99,13 +99,7 @@ export function getIconAndLabelForStatus(
/** /**
*/ */
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => { export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
const { icon, label } = getIconAndLabelForStatus( const { icon, label } = useIconAndLabelForStatus(id);
useExplorerProposalStatusQuery({
variables: {
id,
},
})
);
return ( return (
<div className="float-left mr-3"> <div className="float-left mr-3">
@@ -1,6 +1,4 @@
import type { ProposalTerms } from '../tx-proposal';
import { useState } from 'react'; import { useState } from 'react';
import type { components } from '../../../../../types/explorer';
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog'; import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
import ProposalLink from '../../../links/proposal-link/proposal-link'; import ProposalLink from '../../../links/proposal-link/proposal-link';
import truncate from 'lodash/truncate'; import truncate from 'lodash/truncate';
@@ -9,7 +7,12 @@ import ReactMarkdown from 'react-markdown';
import { ProposalDate } from './proposal-date'; import { ProposalDate } from './proposal-date';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { ProposalTerms } from '../tx-proposal';
import type { components } from '../../../../../types/explorer';
import { BatchItem } from './batch-item';
type Rationale = components['schemas']['vegaProposalRationale']; type Rationale = components['schemas']['vegaProposalRationale'];
type Batch = components['schemas']['v1BatchProposalSubmissionTerms']['changes'];
type ProposalTermsDialog = { type ProposalTermsDialog = {
open: boolean; open: boolean;
@@ -21,6 +24,7 @@ interface ProposalSummaryProps {
id: string; id: string;
rationale?: Rationale; rationale?: Rationale;
terms?: ProposalTerms; terms?: ProposalTerms;
batch?: Batch;
} }
/** /**
@@ -31,6 +35,7 @@ export const ProposalSummary = ({
id, id,
rationale, rationale,
terms, terms,
batch,
}: ProposalSummaryProps) => { }: ProposalSummaryProps) => {
const [dialog, setDialog] = useState<ProposalTermsDialog>({ const [dialog, setDialog] = useState<ProposalTermsDialog>({
open: false, open: false,
@@ -59,7 +64,9 @@ export const ProposalSummary = ({
return ( return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5"> <div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
{id && <ProposalStatusIcon id={id} />} {id && <ProposalStatusIcon id={id} />}
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>} {rationale?.title && (
<h1 className="text-xl pb-1 break-all">{rationale.title}</h1>
)}
{rationale?.description && ( {rationale?.description && (
<div className="pt-2 text-sm leading-tight"> <div className="pt-2 text-sm leading-tight">
<ReactMarkdown <ReactMarkdown
@@ -72,6 +79,18 @@ export const ProposalSummary = ({
</ReactMarkdown> </ReactMarkdown>
</div> </div>
)} )}
{batch && (
<section className="pt-2 text-sm leading-tight my-3">
<h2 className="text-lg pb-1">{t('Changes')}</h2>
<ol>
{batch.map((change, index) => (
<li className="ml-4 list-decimal" key={`batch-${index}`}>
<BatchItem item={change} />
</li>
))}
</ol>
</section>
)}
<div className="pt-5"> <div className="pt-5">
<button className="underline max-md:hidden mr-5" onClick={openDialog}> <button className="underline max-md:hidden mr-5" onClick={openDialog}>
{t('View terms')} {t('View terms')}
@@ -0,0 +1,46 @@
import { t } from '@vegaprotocol/i18n';
import { TxDetailsShared } from '../shared/tx-details-shared';
import { TableWithTbody } from '../../../table';
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 { TableCell, TableRow } from '../../../table';
type Update = components['schemas']['v1UpdatePartyProfile'];
interface TxDetailsUpdatePartyProfileProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Party profiles can be an alias and arbitrary key/values pairs.
* This component displays the alias, if any, but not the metadata. When there is
* some wider usage, we can decide how to render it. For now, it's available in the
* full TX details.
*/
export const TxDetailsUpdatePartyProfile = ({
txData,
pubKey,
blockData,
}: TxDetailsUpdatePartyProfileProps) => {
if (!txData?.command.updatePartyProfile) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const update: Update = txData.command.updatePartyProfile;
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{update.alias && (
<TableRow modifier="bordered">
<TableCell>{t('New alias')}</TableCell>
<TableCell>{update.alias}</TableCell>
</TableRow>
)}
</TableWithTbody>
);
};
@@ -0,0 +1,54 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerReferralCodeOwnerQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerReferralCodeOwnerQuery = { __typename?: 'Query', referralSets: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', createdAt: any, updatedAt: any, referrer: string } } | null> } };
export const ExplorerReferralCodeOwnerDocument = gql`
query ExplorerReferralCodeOwner($id: ID!) {
referralSets(id: $id) {
edges {
node {
createdAt
updatedAt
referrer
}
}
}
}
`;
/**
* __useExplorerReferralCodeOwnerQuery__
*
* To run a query within a React component, call `useExplorerReferralCodeOwnerQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerReferralCodeOwnerQuery` 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 } = useExplorerReferralCodeOwnerQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerReferralCodeOwnerQuery(baseOptions: Apollo.QueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
}
export function useExplorerReferralCodeOwnerLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>(ExplorerReferralCodeOwnerDocument, options);
}
export type ExplorerReferralCodeOwnerQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerQuery>;
export type ExplorerReferralCodeOwnerLazyQueryHookResult = ReturnType<typeof useExplorerReferralCodeOwnerLazyQuery>;
export type ExplorerReferralCodeOwnerQueryResult = Apollo.QueryResult<ExplorerReferralCodeOwnerQuery, ExplorerReferralCodeOwnerQueryVariables>;
@@ -0,0 +1,11 @@
query ExplorerReferralCodeOwner($id: ID!) {
referralSets(id: $id) {
edges {
node {
createdAt
updatedAt
referrer
}
}
}
}
@@ -0,0 +1,95 @@
import { render, waitFor } from '@testing-library/react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { ReferralCodeOwner } from './referral-code-owner';
import type { ReferralCodeOwnerProps } from './referral-code-owner';
import { ExplorerReferralCodeOwnerDocument } from './__generated__/code-owner';
const renderComponent = (
props: ReferralCodeOwnerProps,
mocks: MockedResponse[]
) => {
return render(
<MockedProvider mocks={mocks}>
<MemoryRouter>
<ReferralCodeOwner {...props} />
</MemoryRouter>
</MockedProvider>
);
};
describe('ReferralCodeOwner', () => {
it('should render loading state', () => {
const mocks = [
{
request: {
query: ExplorerReferralCodeOwnerDocument,
variables: {
id: 'ABC123',
},
},
},
];
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
expect(getByText('Loading...')).toBeInTheDocument();
});
it('should render error state', async () => {
const errorMessage = 'Error fetching referrer: ABC123';
const mocks = [
{
request: {
query: ExplorerReferralCodeOwnerDocument,
variables: {
id: 'ABC123',
},
},
error: new Error('nope'),
},
];
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
await waitFor(() => {
expect(getByText(errorMessage)).toBeInTheDocument();
});
});
it('should render link to referring party', async () => {
const referrerId = 'DEF789';
const mocks = [
{
request: {
query: ExplorerReferralCodeOwnerDocument,
variables: {
id: 'ABC123',
},
},
result: {
data: {
referralSets: {
edges: [
{
node: {
__typename: 'ReferralSet',
referrer: referrerId,
createdAt: '123',
updatedAt: '456',
},
},
],
},
},
},
},
];
const { getByText } = renderComponent({ code: 'ABC123' }, mocks);
await waitFor(() => {
expect(getByText(referrerId)).toBeInTheDocument();
});
});
});
@@ -0,0 +1,26 @@
import { TableCell } from '../../../table';
import { useExplorerReferralCodeOwnerQuery } from './__generated__/code-owner';
import { PartyLink } from '../../../links';
export interface ReferralCodeOwnerProps {
code: string;
}
/**
* Render the owner of a referral code
*/
export const ReferralCodeOwner = ({ code }: ReferralCodeOwnerProps) => {
const { data, error, loading } = useExplorerReferralCodeOwnerQuery({
variables: {
id: code,
},
});
const referrer = data?.referralSets.edges[0]?.node.referrer || '';
return (
<TableCell>
{loading && 'Loading...'}
{error && `Error fetching referrer: ${code}`}
{referrer.length > 0 && <PartyLink id={referrer} />}
</TableCell>
);
};
@@ -0,0 +1,93 @@
import { render } from '@testing-library/react';
import { ReferralTeam } from './team';
import type { CreateReferralSet } from './team';
import { MockedProvider } from '@apollo/client/testing';
describe('ReferralTeam', () => {
const team = {
name: 'Test Team',
teamUrl: 'https://example.com/team',
avatarUrl: 'https://example.com/avatar',
closed: false,
};
const mockTx: CreateReferralSet = {
team,
};
const mockId = '123456';
const mockCreator = 'JohnDoe';
it('should render the team name', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Test Team')).toBeInTheDocument();
});
it('should render the team ID', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Id')).toBeInTheDocument();
expect(getByText(mockId)).toBeInTheDocument();
});
it('should render the creator', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Creator')).toBeInTheDocument();
expect(getByText(mockCreator)).toBeInTheDocument();
});
it('should render the team URL', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Team URL')).toBeInTheDocument();
expect(getByText(team.teamUrl)).toBeInTheDocument();
});
it('should render the avatar URL', () => {
const { getByText } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByText('Avatar')).toBeInTheDocument();
expect(getByText(team.avatarUrl)).toBeInTheDocument();
});
it('should render the open status as a tick if closed is falsy', () => {
const { getByTestId } = render(
<MockedProvider>
<ReferralTeam tx={mockTx} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByTestId('open-yes')).toBeInTheDocument();
});
it('should render the open status as a cross if it is truthy', () => {
const m = {
team: {
closed: true,
},
};
const { getByTestId } = render(
<MockedProvider>
<ReferralTeam tx={m} id={mockId} creator={mockCreator} />
</MockedProvider>
);
expect(getByTestId('open-no')).toBeInTheDocument();
});
});
@@ -0,0 +1,73 @@
import {
VegaIcon,
Icon,
KeyValueTable,
KeyValueTableRow,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import type { components } from '../../../../../types/explorer';
import Hash from '../../../links/hash';
import { t } from 'i18next';
import { PartyLink } from '../../../links';
export type CreateReferralSet = components['schemas']['v1CreateReferralSet'];
export type ReferralTeam = CreateReferralSet['team'];
export interface ReferralTeamProps {
tx: CreateReferralSet;
id: string;
creator: string;
}
/**
* Renders the details for a team in a CreateReferralSet or UpdateReferralSet transaction.
*
* Intentionally does not render the avatar image or link to the team url.
*/
export const ReferralTeam = ({ tx, id, creator }: ReferralTeamProps) => {
if (!tx.team) {
return null;
}
return (
<section>
<div className="inline-block mr-2 leading-none">
<VegaIcon name={VegaIconNames.TEAM} />
</div>
{tx.team.name && (
<h3 className="inline-block leading-loose">{tx.team.name}</h3>
)}
<div className="min-w-fit max-w-2xl block">
<KeyValueTable>
<KeyValueTableRow>
{t('Id')}
<Hash text={id} truncate={false} />
</KeyValueTableRow>
<KeyValueTableRow>
{t('Creator')}
<PartyLink id={creator} truncate={false} />
</KeyValueTableRow>
{tx.team.teamUrl && (
<KeyValueTableRow>
{t('Team URL')}
<Hash text={tx.team.teamUrl} truncate={false} />
</KeyValueTableRow>
)}
{tx.team.avatarUrl && (
<KeyValueTableRow>
{t('Avatar')}
<Hash text={tx.team.avatarUrl} truncate={false} />
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('Open')}
<span data-testid={!tx.team.closed ? 'open-yes' : 'open-no'}>
{!tx.team.closed ? <Icon name="tick" /> : <Icon name="cross" />}
</span>
</KeyValueTableRow>
</KeyValueTable>
</div>
</section>
);
};
@@ -10,6 +10,8 @@ import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
import { TxDataView } from '../../tx-data-view'; import { TxDataView } from '../../tx-data-view';
import Hash from '../../../links/hash'; import Hash from '../../../links/hash';
import { Signature } from '../../../signature/signature'; import { Signature } from '../../../signature/signature';
import { useExplorerEpochForBlockQuery } from '../../../links/block-link/__generated__/EpochByBlock';
import EpochOverview from '../../../epoch-overview/epoch';
interface TxDetailsSharedProps { interface TxDetailsSharedProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -27,7 +29,8 @@ export const sharedHeaderProps = {
className: 'align-top', className: 'align-top',
}; };
const Labels: Record<BlockExplorerTransactionResult['type'], string> = { // The incoming type field is usually the right thing to show. Exceptions are listed here
const LabelOverrides: Record<BlockExplorerTransactionResult['type'], string> = {
'Stop Orders Submission': 'Stop Order', 'Stop Orders Submission': 'Stop Order',
'Stop Orders Cancellation': 'Cancel Stop Order', 'Stop Orders Cancellation': 'Cancel Stop Order',
}; };
@@ -43,6 +46,11 @@ export const TxDetailsShared = ({
blockData, blockData,
hideTypeRow = false, hideTypeRow = false,
}: TxDetailsSharedProps) => { }: TxDetailsSharedProps) => {
const { data } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: txData?.block?.toString() || '' },
});
if (!txData) { if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
@@ -50,7 +58,7 @@ export const TxDetailsShared = ({
const time: string = blockData?.result.block.header.time || ''; const time: string = blockData?.result.block.header.time || '';
const height: string = blockData?.result.block.header.height || txData.block; const height: string = blockData?.result.block.header.height || txData.block;
const type = Labels[txData.type] || txData.type; const type = LabelOverrides[txData.type] || txData.type;
return ( return (
<> <>
@@ -73,7 +81,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell> <TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
<TableCell> <TableCell>
<BlockLink height={height} /> <BlockLink height={height} showEpoch={false} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
@@ -82,6 +90,7 @@ export const TxDetailsShared = ({
<Signature signature={txData.signature} /> <Signature signature={txData.signature} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell> <TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
<TableCell> <TableCell>
@@ -99,6 +108,14 @@ export const TxDetailsShared = ({
)} )}
</TableCell> </TableCell>
</TableRow> </TableRow>
{data && data.epoch && (
<TableRow modifier="bordered">
<TableCell scope="row">{t('Epoch')}</TableCell>
<TableCell modifier="bordered">
<EpochOverview id={data.epoch.id} icon={false} />
</TableCell>
</TableRow>
)}
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell> <TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
<TableCell> <TableCell>
@@ -0,0 +1,22 @@
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
fees {
amount
epoch
}
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
@@ -0,0 +1,65 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerTransferStatusQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', fees?: Array<{ __typename?: 'TransferFee', amount: string, epoch: number } | null> | null, transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
export const ExplorerTransferStatusDocument = gql`
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
fees {
amount
epoch
}
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
`;
/**
* __useExplorerTransferStatusQuery__
*
* To run a query within a React component, call `useExplorerTransferStatusQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerTransferStatusQuery` 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 } = useExplorerTransferStatusQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerTransferStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export function useExplorerTransferStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export type ExplorerTransferStatusQueryHookResult = ReturnType<typeof useExplorerTransferStatusQuery>;
export type ExplorerTransferStatusLazyQueryHookResult = ReturnType<typeof useExplorerTransferStatusLazyQuery>;
export type ExplorerTransferStatusQueryResult = Apollo.QueryResult<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>;
@@ -41,10 +41,17 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility', ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility',
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking', ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking',
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending Fee Referral Reward', ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending Fee Referral Reward',
ACCOUNT_TYPE_ORDER_MARGIN: 'Order Margin',
};
export type TransferFee = {
amount?: string;
epoch?: number;
}; };
interface TransferParticipantsProps { interface TransferParticipantsProps {
transfer: Transfer; transfer: Transfer;
fees?: TransferFee[] | null;
from: string; from: string;
} }
@@ -59,6 +66,7 @@ interface TransferParticipantsProps {
export function TransferParticipants({ export function TransferParticipants({
transfer, transfer,
from, from,
fees,
}: TransferParticipantsProps) { }: TransferParticipantsProps) {
// This mapping is required as the global account types require a type to be set, while // This mapping is required as the global account types require a type to be set, while
// the underlying protobufs allow for every field to be undefined. // the underlying protobufs allow for every field to be undefined.
@@ -103,6 +111,9 @@ export function TransferParticipants({
{transfer.asset ? ( {transfer.asset ? (
<SizeInAsset assetId={transfer.asset} size={transfer.amount} /> <SizeInAsset assetId={transfer.asset} size={transfer.amount} />
) : null} ) : null}
{transfer.asset && fees && (
<TransferFees assetId={transfer.asset} fees={fees} />
)}
</div> </div>
{/* Empty divs for the top arrow and the bottom arrow of the transfer inset */} {/* Empty divs for the top arrow and the bottom arrow of the transfer inset */}
@@ -110,7 +121,7 @@ export function TransferParticipants({
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9" viewBox="0 0 16 9"
className="fill-vega-light-100 dark:fill-black" className="fill-white dark:fill-black"
> >
<path d="M0,0L8,9l8,-9Z" /> <path d="M0,0L8,9l8,-9Z" />
</svg> </svg>
@@ -119,15 +130,11 @@ export function TransferParticipants({
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9" viewBox="0 0 16 9"
className="fill-vega-light-100 dark:fill-vega-dark-200" className="fill-vega-light-200 dark:fill-vega-dark-200"
> >
<path d="M0,0L8,9l8,-9Z" /> <path d="M0,0L8,9l8,-9Z" />
</svg> </svg>
</div> </div>
{/*
<div className="z-10 absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 rotate-45 w-4 h-4 dark:border-vega-dark-200 border-vega-light-200 bg-white dark:bg-black border-r border-b"></div>
<div className="z-10 absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-1/2 rotate-45 w-4 h-4 border-vega-light-200 dark:border-vega-dark-200 bg-vega-light-200 dark:bg-vega-dark-200 border-r border-b"></div>
*/}
</div> </div>
</div> </div>
@@ -168,3 +175,38 @@ export function TransferRecurringRecipient({
// Fallback should not happen // Fallback should not happen
return null; return null;
} }
export function TransferFees({
assetId,
fees,
}: {
assetId: string;
fees: TransferFee[];
}) {
// A recurring transfer that is rejected or cancelled will have an array of fees of 0 length
if (assetId && fees && fees.length > 0) {
if (fees.length === 1) {
return (
<p className="mt-2">
Fee: <SizeInAsset assetId={assetId} size={fees[0].amount} />
</p>
);
} else {
return (
<details className="cursor-pointer mt-2">
<summary>{t('Fees')}</summary>
<ul>
{fees.map((fee) => (
<li className="text-nowrap leading-normal">
<SizeInAsset assetId={assetId} size={fee.amount} />{' '}
{t('in epoch')} {fee.epoch}
</li>
))}
</ul>
</details>
);
}
}
return null;
}
@@ -1,97 +1,223 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink } from '../../../../links'; import { AssetLink, MarketLink } from '../../../../links';
import { headerClasses, wrapperClasses } from '../transfer-details';
import type { components } from '../../../../../../types/explorer'; import type { components } from '../../../../../../types/explorer';
import type { Recurring } from '../transfer-details'; import type { Recurring } from '../transfer-details';
import { DispatchMetricLabels } from '@vegaprotocol/types'; import {
DispatchMetricLabels,
DistributionStrategy,
} from '@vegaprotocol/types';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '@vegaprotocol/utils';
export type Metric = components['schemas']['vegaDispatchMetric']; export type Metric = components['schemas']['vegaDispatchMetric'];
export type Strategy = components['schemas']['vegaDispatchStrategy']; export type Strategy = components['schemas']['vegaDispatchStrategy'];
export const wrapperClasses = 'border pv-2 w-full flex-auto basis-full';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 text-center text-xl py-2 font-alpha calt';
const metricLabels: Record<Metric, string> = { const metricLabels: Record<Metric, string> = {
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric', DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
...DispatchMetricLabels, ...DispatchMetricLabels,
}; };
// Maps the two (non-null) values of entityScope to the icon that represents it
const entityScopeIcons: Record<
string,
typeof VegaIconNames[keyof typeof VegaIconNames]
> = {
ENTITY_SCOPE_INDIVIDUALS: VegaIconNames.MAN,
ENTITY_SCOPE_TEAMS: VegaIconNames.TEAM,
};
const distributionStrategyLabel: Record<DistributionStrategy, string> = {
[DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA]: 'Pro Rata',
[DistributionStrategy.DISTRIBUTION_STRATEGY_RANK]: 'Ranked',
};
interface TransferRewardsProps { interface TransferRewardsProps {
recurring: Recurring; recurring: Recurring;
} }
/** /**
* Renderer for a transfer. These can vary quite * Renders recurring transfers/game details in a way that is, perhaps, easy to understand
* widely, essentially every field can be null.
* *
* @param transfer A recurring transfer object * @param transfer A recurring transfer object
*/ */
export function TransferRewards({ recurring }: TransferRewardsProps) { export function TransferRewards({ recurring }: TransferRewardsProps) {
const metric =
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
if (!recurring || !recurring.dispatchStrategy) { if (!recurring || !recurring.dispatchStrategy) {
return null; return null;
} }
// Destructure to make things a bit more readable
const {
entityScope,
individualScope,
teamScope,
distributionStrategy,
lockPeriod,
markets,
stakingRequirement,
windowLength,
notionalTimeWeightedAveragePositionRequirement,
rankTable,
nTopPerformers,
} = recurring.dispatchStrategy;
return ( return (
<div className={wrapperClasses}> <div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Reward metrics')}</h2> <h2 className={headerClasses}>{getRewardTitle(entityScope)}</h2>
<ul className="relative block rounded-lg py-6 text-center p-6"> <ul className="relative block rounded-lg py-6 text-left p-6">
{recurring.dispatchStrategy.assetForMetric ? ( {entityScope && entityScopeIcons[entityScope] ? (
<li> <li>
<strong>{t('Asset')}</strong>:{' '} <strong>{t('Scope')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} /> <VegaIcon name={entityScopeIcons[entityScope]} />
&nbsp;
{individualScope ? individualScopeLabels[individualScope] : null}
{getScopeLabel(entityScope, teamScope)}
</li> </li>
) : null} ) : null}
<li> {recurring.dispatchStrategy &&
<strong>{t('Metric')}</strong>: {metricLabels[metric]} recurring.dispatchStrategy.assetForMetric && (
</li> <li>
{recurring.dispatchStrategy.markets && <strong>{t('Asset for metric')}</strong>:{' '}
recurring.dispatchStrategy.markets.length > 0 ? ( <AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
)}
{recurring.dispatchStrategy.metric &&
metricLabels[recurring.dispatchStrategy.metric] && (
<li>
<strong>{t('Metric')}</strong>:{' '}
{metricLabels[recurring.dispatchStrategy.metric]}
</li>
)}
{lockPeriod && (
<li>
<strong>{t('Reward lock')}</strong>:&nbsp;
{recurring.dispatchStrategy.lockPeriod}{' '}
{recurring.dispatchStrategy.lockPeriod === '1'
? t('epoch')
: t('epochs')}
</li>
)}
{markets && markets.length > 0 ? (
<li> <li>
<strong>{t('Markets in scope')}</strong>: <strong>{t('Markets in scope')}</strong>:
<ul> <ul className="inline-block ml-1">
{recurring.dispatchStrategy.markets.map((m) => ( {markets.map((m) => (
<li key={m}> <li key={m} className="inline-block mr-2">
<MarketLink id={m} /> <MarketLink id={m} />
</li> </li>
))} ))}
</ul> </ul>
</li> </li>
) : null} ) : null}
<li>
<strong>{t('Factor')}</strong>: {recurring.factor} {stakingRequirement && stakingRequirement !== '0' ? (
</li> <li>
<strong>{t('Staking requirement')}</strong>: {stakingRequirement}
</li>
) : null}
{windowLength && windowLength !== '0' ? (
<li>
<strong>{t('Window length')}</strong>:{' '}
{recurring.dispatchStrategy.windowLength}{' '}
{recurring.dispatchStrategy.windowLength === '1'
? t('epoch')
: t('epochs')}
</li>
) : null}
{notionalTimeWeightedAveragePositionRequirement &&
notionalTimeWeightedAveragePositionRequirement !== '' ? (
<li>
<strong>{t('Notional TWAP')}</strong>:{' '}
{notionalTimeWeightedAveragePositionRequirement}
</li>
) : null}
{nTopPerformers && (
<li>
<strong>{t('Elligible team members:')}</strong> top{' '}
{`${formatNumber(Number(nTopPerformers) * 100, 0)}%`}
</li>
)}
{distributionStrategy &&
distributionStrategy !== 'DISTRIBUTION_STRATEGY_UNSPECIFIED' && (
<li>
<strong>{t('Distribution strategy')}</strong>:{' '}
{distributionStrategyLabel[distributionStrategy]}
</li>
)}
</ul> </ul>
<div className="px-6 pt-1 pb-5">
{rankTable && rankTable.length > 0 ? (
<table className="border-collapse border border-gray-400 ">
<thead>
<tr>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Start rank')}</strong>
</th>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Share of reward pool')}</strong>
</th>
</tr>
</thead>
<tbody>
{rankTable.map((row, i) => {
return (
<tr key={`rank-${i}`}>
<td className="border border-slate-300 text-center">
{row.startRank}
</td>
<td className="border border-slate-300 text-center">
{row.shareRatio}
</td>
</tr>
);
})}
</tbody>
</table>
) : null}
</div>
</div> </div>
); );
} }
interface TransferRecurringStrategyProps { export function getScopeLabel(
strategy: Strategy; scope: components['schemas']['vegaEntityScope'] | undefined,
} teamScope: readonly string[] | undefined
): string {
/** if (scope === 'ENTITY_SCOPE_TEAMS') {
* Simple renderer for a dispatch strategy in a recurring transfer if (teamScope && teamScope.length !== 0) {
* return ` ${teamScope.length} teams`;
* @param strategy Dispatch strategy object } else {
*/ return t('All teams');
export function TransferRecurringStrategy({ }
strategy, } else if (scope === 'ENTITY_SCOPE_INDIVIDUALS') {
}: TransferRecurringStrategyProps) { return t('Individuals');
if (!strategy) { } else {
return null; return '';
} }
return (
<>
{strategy.assetForMetric ? (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={strategy.assetForMetric} />
</li>
) : null}
<li>
<strong>{t('Metric')}</strong>: {strategy.metric}
</li>
</>
);
} }
export function getRewardTitle(
scope?: components['schemas']['vegaEntityScope']
) {
if (scope === 'ENTITY_SCOPE_TEAMS') {
return t('Game');
}
return t('Reward metrics');
}
const individualScopeLabels: Record<
components['schemas']['vegaIndividualScope'],
string
> = {
// Unspecified and All are not rendered
INDIVIDUAL_SCOPE_UNSPECIFIED: '',
INDIVIDUAL_SCOPE_ALL: '',
INDIVIDUAL_SCOPE_IN_TEAM: '(in team)',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: '(not in team)',
};
@@ -0,0 +1,100 @@
import { t } from '@vegaprotocol/i18n';
import { headerClasses, wrapperClasses } from '../transfer-details';
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
import type { ApolloError } from '@apollo/client';
import { TransferStatus, TransferStatusMapping } from '@vegaprotocol/types';
import { IconNames } from '@blueprintjs/icons';
interface TransferStatusProps {
status: TransferStatus | undefined;
error: ApolloError | undefined;
loading: boolean;
}
/**
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
*
* @param transfer A recurring transfer object
*/
export function TransferStatusView({ status, loading }: TransferStatusProps) {
if (!status) {
status = TransferStatus.STATUS_PENDING;
}
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Status')}</h2>
<div className="relative block rounded-lg py-6 text-center p-6">
{loading ? (
<div className="leading-10 mt-12">
<Loader size={'small'} />
</div>
) : (
<>
<p className="leading-10 my-2">
<TransferStatusIcon status={status} />
</p>
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
</>
)}
</div>
</div>
);
}
interface TransferStatusIconProps {
status: TransferStatus;
}
export function TransferStatusIcon({ status }: TransferStatusIconProps) {
return (
<span title={TransferStatusMapping[status]}>
<Icon
name={getIconForStatus(status)}
className={getColourForStatus(status)}
/>
</span>
);
}
/**
* Simple mapping from status to icon name
* @param status TransferStatus
* @returns IconName
*/
export function getIconForStatus(status: TransferStatus): IconName {
switch (status) {
case TransferStatus.STATUS_PENDING:
return IconNames.TIME;
case TransferStatus.STATUS_DONE:
return IconNames.TICK;
case TransferStatus.STATUS_REJECTED:
return IconNames.CROSS;
case TransferStatus.STATUS_CANCELLED:
return IconNames.CROSS;
default:
return IconNames.TIME;
}
}
/**
* Simple mapping from status to colour
* @param status TransferStatus
* @returns string Tailwind classname
*/
export function getColourForStatus(status: TransferStatus): string {
switch (status) {
case TransferStatus.STATUS_PENDING:
return 'text-yellow-500';
case TransferStatus.STATUS_DONE:
return 'text-green-500';
case TransferStatus.STATUS_REJECTED:
return 'text-red-500';
case TransferStatus.STATUS_CANCELLED:
return 'text-red-600';
default:
return 'text-yellow-500';
}
}
@@ -2,12 +2,15 @@ import type { components } from '../../../../../types/explorer';
import { TransferRepeat } from './blocks/transfer-repeat'; import { TransferRepeat } from './blocks/transfer-repeat';
import { TransferRewards } from './blocks/transfer-rewards'; import { TransferRewards } from './blocks/transfer-rewards';
import { TransferParticipants } from './blocks/transfer-participants'; import { TransferParticipants } from './blocks/transfer-participants';
import { useExplorerTransferStatusQuery } from './__generated__/Transfer';
import { TransferStatusView } from './blocks/transfer-status';
import { TransferStatus } from '@vegaprotocol/types';
export type Recurring = components['schemas']['commandsv1RecurringTransfer']; export type Recurring = components['schemas']['commandsv1RecurringTransfer'];
export type Metric = components['schemas']['vegaDispatchMetric']; export type Metric = components['schemas']['vegaDispatchMetric'];
export const wrapperClasses = export const wrapperClasses =
'border border-vega-light-150 dark:border-vega-dark-200 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] '; 'border border-vega-light-150 dark:border-vega-dark-200 pv-2 w-full sm:w-1/3 basis-1/3';
export const headerClasses = export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt'; 'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt';
@@ -16,6 +19,7 @@ export type Transfer = components['schemas']['commandsv1Transfer'];
interface TransferDetailsProps { interface TransferDetailsProps {
transfer: Transfer; transfer: Transfer;
from: string; from: string;
id: string;
} }
/** /**
@@ -24,13 +28,31 @@ interface TransferDetailsProps {
* *
* @param transfer A recurring transfer object * @param transfer A recurring transfer object
*/ */
export function TransferDetails({ transfer, from }: TransferDetailsProps) { export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
const recurring = transfer.recurring; const recurring = transfer.recurring;
// Currently all this is passed in to TransferStatus, but the extra details
// may be useful in the future.
const { data, error, loading } = useExplorerTransferStatusQuery({
variables: { id },
});
const status = error
? TransferStatus.STATUS_REJECTED
: data?.transfer?.transfer.status;
const fees = data?.transfer?.fees?.map((fee) => {
return {
amount: fee?.amount ? fee.amount : '0',
epoch: fee?.epoch ? fee.epoch : 0,
};
});
return ( return (
<div className="flex gap-5 flex-wrap"> <div className="flex flex-wrap">
<TransferParticipants from={from} transfer={transfer} /> <TransferParticipants from={from} transfer={transfer} fees={fees} />
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null} {recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
<TransferStatusView status={status} error={error} loading={loading} />
{recurring && recurring.dispatchStrategy ? ( {recurring && recurring.dispatchStrategy ? (
<TransferRewards recurring={transfer.recurring} /> <TransferRewards recurring={transfer.recurring} />
) : null} ) : null}
@@ -0,0 +1,172 @@
import {
getScopeLabel,
getRewardTitle,
TransferRewards,
} from './blocks/transfer-rewards';
import { render } from '@testing-library/react';
import type { components } from '../../../../../types/explorer';
import type { Recurring } from './transfer-details';
import {
DispatchMetric,
DistributionStrategy,
EntityScope,
IndividualScope,
} from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getScopeLabel', () => {
it('should return the correct label for ENTITY_SCOPE_TEAMS with teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = ['team1', 'team2', 'team3'];
const expectedLabel = ' 3 teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_TEAMS without teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = undefined;
const expectedLabel = 'All teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_INDIVIDUALS', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const teamScope = undefined;
const expectedLabel = 'Individuals';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return an empty string for unknown scope', () => {
const scope = 'UNKNOWN_SCOPE';
const teamScope = undefined;
const expectedLabel = '';
const result = getScopeLabel(
scope as unknown as components['schemas']['vegaEntityScope'],
teamScope
);
expect(result).toEqual(expectedLabel);
});
});
describe('getRewardTitle', () => {
it('should return the correct title for ENTITY_SCOPE_TEAMS', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const expectedTitle = 'Game';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
it('should return the correct title for other scopes', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const expectedTitle = 'Reward metrics';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
});
describe('TransferRewards', () => {
it('should render nothing if recurring dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={null as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render nothing if recurring.dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={{} as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render the reward details correctly', () => {
const recurring = {
dispatchStrategy: {
metric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
assetForMetric: '123',
entityScope: EntityScope.ENTITY_SCOPE_TEAMS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM,
teamScope: [],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: '1',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
rankTable: [
{ startRank: 1, shareRatio: 0.2 },
{ startRank: 2, shareRatio: 0.3 },
],
nTopPerformers: 'nTopPerformers',
},
};
const { getByText } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(getByText('Game')).toBeInTheDocument();
expect(getByText('Scope')).toBeInTheDocument();
expect(getByText('Asset for metric')).toBeInTheDocument();
expect(getByText('Metric')).toBeInTheDocument();
expect(getByText('Reward lock')).toBeInTheDocument();
expect(getByText('Markets in scope')).toBeInTheDocument();
expect(getByText('Staking requirement')).toBeInTheDocument();
expect(getByText('Window length')).toBeInTheDocument();
expect(getByText('Notional TWAP')).toBeInTheDocument();
expect(getByText('Elligible team members:')).toBeInTheDocument();
expect(getByText('Distribution strategy')).toBeInTheDocument();
expect(getByText('Start rank')).toBeInTheDocument();
expect(getByText('Share of reward pool')).toBeInTheDocument();
});
it('should not render a rank table if recurring.dispatchStrategy.rankTable is not provided', () => {
const recurring = {
dispatchStrategy: {
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
teamScope: ['team1', 'team2', 'team3'],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: 'stakingRequirement',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
nTopPerformers: 'nTopPerformers',
},
};
const { container } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(container.querySelector('table')).toBeNull();
});
});
@@ -0,0 +1,47 @@
import { t } from '@vegaprotocol/i18n';
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 { ReferralCodeOwner } from './referrals/referral-code-owner';
interface TxDetailsApplyReferralCodeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* The signature can be turned in to an id with txSignatureToDeterministicId but
*/
export const TxDetailsApplyReferralCode = ({
txData,
pubKey,
blockData,
}: TxDetailsApplyReferralCodeProps) => {
if (!txData || !txData.command.applyReferralCode || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const referralCode = txData.command.applyReferralCode.id;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Applied Code')}</TableCell>
<TableCell>{referralCode}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Referrer')}</TableCell>
<ReferralCodeOwner code={referralCode} />
</TableRow>
</TableWithTbody>
</div>
);
};
@@ -0,0 +1,75 @@
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import { ProposalSummary } from './proposal/summary';
import Hash from '../../links/hash';
import { t } from '@vegaprotocol/i18n';
export type Proposal = components['schemas']['v1BatchProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
interface TxBatchProposalProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
*
*/
export const TxBatchProposal = ({
txData,
pubKey,
blockData,
}: TxBatchProposalProps) => {
if (!txData || !txData.command.batchProposalSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
let deterministicId = '';
const proposal: Proposal = txData.command.batchProposalSubmission;
const sig = txData?.signature?.value;
if (sig) {
deterministicId = txSignatureToDeterministicId(sig);
}
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{t('Batch proposal')}</TableCell>
</TableRow>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
hideTypeRow={true}
/>
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
{proposal.terms?.changes?.length || t('No changes')}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>
<Hash text={deterministicId} />
</TableCell>
</TableRow>
</TableWithTbody>
{proposal && (
<ProposalSummary
id={deterministicId}
rationale={proposal?.rationale}
terms={proposal.terms}
batch={proposal.terms?.changes}
/>
)}
</>
);
};
@@ -0,0 +1,52 @@
import { t } from '@vegaprotocol/i18n';
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 { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import { ReferralTeam } from './referrals/team';
interface TxDetailsCreateReferralProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* The signature can be turned in to an id with txSignatureToDeterministicId but
*/
export const TxDetailsCreateReferralSet = ({
txData,
pubKey,
blockData,
}: TxDetailsCreateReferralProps) => {
if (!txData || !txData.command.createReferralSet || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const id = txSignatureToDeterministicId(txData.signature.value);
const isTeam = txData.command.createReferralSet.isTeam || false;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
<TableCell>{id}</TableCell>
</TableRow>
</TableWithTbody>
<ReferralTeam
tx={txData.command.createReferralSet}
id={id}
creator={txData.submitter}
/>
</div>
);
};
@@ -28,6 +28,13 @@ import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer'; import { TxDetailsTransfer } from './tx-transfer';
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission'; import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission'; import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import { TxDetailsCreateReferralSet } from './tx-create-referral-set';
import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
import { TxDetailsJoinTeam } from './tx-join-team';
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
import { TxBatchProposal } from './tx-batch-proposal';
import { TxDetailsUpdatePartyProfile } from './proposal/tx-update-party-profile';
interface TxDetailsWrapperProps { interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -121,6 +128,20 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsStopOrderSubmission; return TxDetailsStopOrderSubmission;
case 'Transfer Funds': case 'Transfer Funds':
return TxDetailsTransfer; return TxDetailsTransfer;
case 'Create Referral Set':
return TxDetailsCreateReferralSet;
case 'Update Referral Set':
return TxDetailsUpdateReferralSet;
case 'Apply Referral Code':
return TxDetailsApplyReferralCode;
case 'Join Team':
return TxDetailsJoinTeam;
case 'Update Margin Mode':
return TxDetailsUpdateMarginMode;
case 'Batch Proposal':
return TxBatchProposal;
case 'Update Party Profile':
return TxDetailsUpdatePartyProfile;
default: default:
return TxDetailsGeneric; return TxDetailsGeneric;
} }
@@ -5,9 +5,9 @@ import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link'; } from '../../links/external-explorer-link/external-explorer-link';
import { BlockLink } from '../../links'; import { BlockLink } from '../../links';
type EthKeyRotate = components['schemas']['v1EthereumKeyRotateSubmission']; type EthKeyRotate = components['schemas']['v1EthereumKeyRotateSubmission'];
@@ -46,7 +46,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Old Address')}</TableCell> <TableCell>{t('Old Address')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
id={k.currentAddress} id={k.currentAddress}
/> />
@@ -57,7 +57,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('New Address')}</TableCell> <TableCell>{t('New Address')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
id={k.newAddress} id={k.newAddress}
/> />
@@ -68,7 +68,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Submitter address')}</TableCell> <TableCell>{t('Submitter address')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
id={k.submitterAddress} id={k.submitterAddress}
/> />
@@ -6,9 +6,9 @@ import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link'; } from '../../links/external-explorer-link/external-explorer-link';
import { NodeLink } from '../../links'; import { NodeLink } from '../../links';
type Command = components['schemas']['v1IssueSignatures']; type Command = components['schemas']['v1IssueSignatures'];
@@ -57,7 +57,7 @@ export const TxDetailsIssueSignatures = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('ETH key')}</TableCell> <TableCell>{t('ETH key')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={cmd.submitter} id={cmd.submitter}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -0,0 +1,47 @@
import { t } from '@vegaprotocol/i18n';
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 { ReferralCodeOwner } from './referrals/referral-code-owner';
interface TxDetailsJoinTeamProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* The signature can be turned in to an id with txSignatureToDeterministicId but
*/
export const TxDetailsJoinTeam = ({
txData,
pubKey,
blockData,
}: TxDetailsJoinTeamProps) => {
if (!txData || !txData.command.joinTeam || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const team = txData.command.joinTeam.id;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Team')}</TableCell>
<TableCell>{team}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Referrer')}</TableCell>
<ReferralCodeOwner code={team} />
</TableRow>
</TableWithTbody>
</div>
);
};
@@ -6,9 +6,9 @@ import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link'; } from '../../links/external-explorer-link/external-explorer-link';
import { PartyLink } from '../../links'; import { PartyLink } from '../../links';
import Hash from '../../links/hash'; import Hash from '../../links/hash';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
@@ -72,7 +72,7 @@ export const TxDetailsNodeAnnounce = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Ethereum Address')}</TableCell> <TableCell>{t('Ethereum Address')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
id={cmd.ethereumAddress} id={cmd.ethereumAddress}
/> />
@@ -8,9 +8,9 @@ import { useExplorerNodeVoteQuery } from './__generated__/Node-vote';
import { PartyLink } from '../../links'; import { PartyLink } from '../../links';
import { Time } from '../../time'; import { Time } from '../../time';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link'; } from '../../links/external-explorer-link/external-explorer-link';
interface TxDetailsNodeVoteProps { interface TxDetailsNodeVoteProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -143,7 +143,7 @@ export function TxHash({ hash }: TxDetailsEthTxHashProps) {
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>Ethereum TX:</TableCell> <TableCell>Ethereum TX:</TableCell>
<TableCell> <TableCell>
<EthExplorerLink id={hash} type={EthExplorerLinkTypes.tx} /> <ExternalExplorerLink id={hash} type={EthExplorerLinkTypes.tx} />
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
@@ -12,6 +12,8 @@ import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update'; import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
import { MarketLink } from '../../links'; import { MarketLink } from '../../links';
import { formatNumber } from '@vegaprotocol/utils'; import { formatNumber } from '@vegaprotocol/utils';
import { TransferDetails } from './transfer/transfer-details';
import { proposalToTransfer } from '../lib/proposal-to-transfer';
export type Proposal = components['schemas']['v1ProposalSubmission']; export type Proposal = components['schemas']['v1ProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms']; export type ProposalTerms = components['schemas']['vegaProposalTerms'];
@@ -104,6 +106,12 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
? ProposalSignatureBundleNewAsset ? ProposalSignatureBundleNewAsset
: ProposalSignatureBundleUpdateAsset; : ProposalSignatureBundleUpdateAsset;
let transfer, from;
if (proposal.terms?.newTransfer?.changes) {
transfer = proposalToTransfer(proposal.terms?.newTransfer.changes);
from = proposal.terms.newTransfer.changes.source;
}
return ( return (
<> <>
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
@@ -149,14 +157,26 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
</> </>
) : null} ) : null}
</TableWithTbody> </TableWithTbody>
<ProposalSummary <ProposalSummary
id={deterministicId} id={deterministicId}
rationale={proposal.rationale} rationale={proposal.rationale}
terms={proposal?.terms} terms={proposal?.terms}
/> />
{proposalRequiresSignatureBundle(proposal) && ( {proposalRequiresSignatureBundle(proposal) && (
<SignatureBundleComponent id={deterministicId} tx={tx} /> <SignatureBundleComponent id={deterministicId} tx={tx} />
)} )}
{transfer && (
<div className="mt-8">
<TransferDetails
transfer={transfer}
from={from || ''}
id={deterministicId}
/>
</div>
)}
</> </>
); );
}; };
@@ -13,6 +13,7 @@ import {
SPECIAL_CASE_NETWORK_ID, SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link'; } from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids'; import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import Hash from '../../links/hash';
type Transfer = components['schemas']['commandsv1Transfer']; type Transfer = components['schemas']['commandsv1Transfer'];
@@ -60,7 +61,7 @@ export const TxDetailsTransfer = ({
} }
const from = txData.submitter; const from = txData.submitter;
const id = txSignatureToDeterministicId(txData.signature.value);
return ( return (
<> <>
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
@@ -71,7 +72,7 @@ export const TxDetailsTransfer = ({
<TableRow modifier="bordered" data-testid="id"> <TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell> <TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell> <TableCell>
{txSignatureToDeterministicId(txData.signature.value)} <Hash text={id} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<TxDetailsShared <TxDetailsShared
@@ -105,20 +106,23 @@ export const TxDetailsTransfer = ({
</TableRow> </TableRow>
) : null} ) : null}
</TableWithTbody> </TableWithTbody>
<TransferDetails from={from} transfer={transfer} /> <TransferDetails from={from} transfer={transfer} id={id} />
</> </>
); );
}; };
/** /**
* Gets a string description of this transfer * Gets a string description of this transfer
* @param txData A full transfer * @param tx A full transfer
* @returns string Transfer label * @returns string Transfer label
*/ */
export function getTypeLabelForTransfer(tx: Transfer) { export function getTypeLabelForTransfer(tx: Transfer) {
if (tx.to === SPECIAL_CASE_NETWORK || tx.to === SPECIAL_CASE_NETWORK_ID) { if (tx.to === SPECIAL_CASE_NETWORK || tx.to === SPECIAL_CASE_NETWORK_ID) {
if (tx.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
return 'Treasury transfer';
}
if (tx.recurring && tx.recurring.dispatchStrategy) { if (tx.recurring && tx.recurring.dispatchStrategy) {
return 'Reward top up transfer'; return 'Reward transfer';
} }
// Else: we don't know that it's a reward transfer, so let's not guess // Else: we don't know that it's a reward transfer, so let's not guess
} else if (tx.recurring) { } else if (tx.recurring) {
@@ -8,7 +8,7 @@ import GovernanceAssetBalance from '../../asset-balance/governance-asset-balance
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
export const methodText: Record< export const methodText: Record<
components['schemas']['UndelegateSubmissionMethod'], components['schemas']['v1UndelegateSubmissionMethod'],
string string
> = { > = {
METHOD_NOW: 'Immediate', METHOD_NOW: 'Immediate',
@@ -0,0 +1,60 @@
import { t } from '@vegaprotocol/i18n';
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 { MarketLink } from '../../links';
interface TxDetailsUpdateMarginModeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
type Mode = components['schemas']['UpdateMarginModeMode'];
const MarginModeLabels: Record<Mode, string> = {
MODE_CROSS_MARGIN: t('Cross margin'),
MODE_ISOLATED_MARGIN: t('Isolated margin'),
MODE_UNSPECIFIED: t('Unspecified'),
};
export const TxDetailsUpdateMarginMode = ({
txData,
pubKey,
blockData,
}: TxDetailsUpdateMarginModeProps) => {
if (!txData || !txData.command.updateMarginMode) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const u: components['schemas']['v1UpdateMarginMode'] =
txData.command.updateMarginMode;
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{u.marketId && (
<TableRow modifier="bordered">
<TableCell>{t('Market ID')}</TableCell>
<TableCell>
<MarketLink id={u.marketId} />
</TableCell>
</TableRow>
)}
{u.mode && (
<TableRow modifier="bordered">
<TableCell>{t('New margin mode')}</TableCell>
<TableCell>{MarginModeLabels[u.mode]}</TableCell>
</TableRow>
)}
{u.marginFactor && (
<TableRow modifier="bordered">
<TableCell>{t('Margin factor')}</TableCell>
<TableCell>{u.marginFactor}</TableCell>
</TableRow>
)}
</TableWithTbody>
);
};
@@ -0,0 +1,54 @@
import { t } from '@vegaprotocol/i18n';
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 { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import { ReferralTeam } from './referrals/team';
interface TxDetailsUpdateReferralProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* A copy of create referral set, effectively.
* Updating a referral set without a team doesn't make sense,
* but is valid, so this component ignores sense.
*/
export const TxDetailsUpdateReferralSet = ({
txData,
pubKey,
blockData,
}: TxDetailsUpdateReferralProps) => {
if (!txData || !txData.command.updateReferralSet || !txData.signature) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const id = txSignatureToDeterministicId(txData.signature.value);
const isTeam = txData.command.updateReferralSet.isTeam || false;
return (
<div>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{isTeam ? t('Team ID') : t('Referral code')}</TableCell>
<TableCell>{id}</TableCell>
</TableRow>
</TableWithTbody>
<ReferralTeam
tx={txData.command.updateReferralSet}
id={id}
creator={txData.submitter}
/>
</div>
);
};
@@ -4,9 +4,9 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import { import {
EthExplorerLink, ExternalExplorerLink,
EthExplorerLinkTypes, EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link'; } from '../../links/external-explorer-link/external-explorer-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids'; import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import AssetBalance from '../../asset-balance/asset-balance'; import AssetBalance from '../../asset-balance/asset-balance';
import { useScrollToLocation } from '../../../hooks/scroll-to-location'; import { useScrollToLocation } from '../../../hooks/scroll-to-location';
@@ -57,7 +57,7 @@ export const TxDetailsWithdrawSubmission = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell> <TableCell>{t('Recipient')}</TableCell>
<TableCell> <TableCell>
<EthExplorerLink <ExternalExplorerLink
id={w.ext.erc20.receiverAddress} id={w.ext.erc20.receiverAddress}
type={EthExplorerLinkTypes.address} type={EthExplorerLinkTypes.address}
/> />
@@ -0,0 +1,29 @@
import type { components } from '../../../../types/explorer';
type TransferProposal = components['schemas']['vegaNewTransferConfiguration'];
type ActualTransfer = components['schemas']['commandsv1Transfer'];
/**
* Converts a governance proposal for a transfer in to a transfer command that the
* TransferDetails component can then render. The types are very similar, but do not
* map precisely to each other due to some missing fields and some different field
* names.
*
* @param proposal Governance proposal for a transfer
* @returns transfer a Transfer object as if it had been submitted
*/
export function proposalToTransfer(proposal: TransferProposal): ActualTransfer {
return {
amount: proposal.amount,
asset: proposal.asset,
// On a transfer, 'from' is determined by the submitter, so there is no 'from' field
// fromAccountType does exist and is just named differently on the proposal
fromAccountType: proposal.sourceType,
oneOff: proposal.oneOff,
recurring: proposal.recurring,
// There is no reference applied on governance initiated transfers
reference: '',
to: proposal.destination,
toAccountType: proposal.destinationType,
};
}
@@ -16,11 +16,12 @@ import { FilterLabel } from './tx-filter-label';
// All possible transaction types. Should be generated. // All possible transaction types. Should be generated.
export type FilterOption = export type FilterOption =
| 'Amend LiquidityProvision Order' | 'Amend Liquidity Provision Order'
| 'Amend Order' | 'Amend Order'
| 'Apply Referral Code' | 'Apply Referral Code'
| 'Batch Market Instructions' | 'Batch Market Instructions'
| 'Cancel LiquidityProvision Order' | 'Batch Proposal'
| 'Cancel Liquidity Provision Order'
| 'Cancel Order' | 'Cancel Order'
| 'Cancel Transfer Funds' | 'Cancel Transfer Funds'
| 'Chain Event' | 'Chain Event'
@@ -28,6 +29,7 @@ export type FilterOption =
| 'Delegate' | 'Delegate'
| 'Ethereum Key Rotate Submission' | 'Ethereum Key Rotate Submission'
| 'Issue Signatures' | 'Issue Signatures'
| 'Join Team'
| 'Key Rotate Submission' | 'Key Rotate Submission'
| 'Liquidity Provision Order' | 'Liquidity Provision Order'
| 'Node Signature' | 'Node Signature'
@@ -42,50 +44,61 @@ export type FilterOption =
| 'Submit Order' | 'Submit Order'
| 'Transfer Funds' | 'Transfer Funds'
| 'Undelegate' | 'Undelegate'
| 'Update Party Profile'
| 'Update Referral Set' | 'Update Referral Set'
| 'Update Margin Mode'
| 'Validator Heartbeat' | 'Validator Heartbeat'
| 'Vote on Proposal' | 'Vote on Proposal'
| 'Withdraw'; | 'Withdraw';
// Alphabetised list of transaction types to appear at the top level export const filterOptions: Record<string, FilterOption[]> = {
export const PrimaryFilterOptions: FilterOption[] = [ 'Market Instructions': [
'Amend LiquidityProvision Order', 'Amend Liquidity Provision Order',
'Amend Order', 'Amend Order',
'Batch Market Instructions', 'Batch Market Instructions',
'Cancel LiquidityProvision Order', 'Cancel Liquidity Provision Order',
'Cancel Order', 'Cancel Order',
'Cancel Transfer Funds', 'Liquidity Provision Order',
'Delegate', 'Stop Orders Submission',
'Liquidity Provision Order', 'Stop Orders Cancellation',
'Proposal', 'Submit Order',
'Stop Orders Submission', 'Update Margin Mode',
'Stop Orders Cancellation', ],
'Submit Oracle Data', 'Transfers and Withdrawals': [
'Submit Order', 'Transfer Funds',
'Transfer Funds', 'Cancel Transfer Funds',
'Undelegate', 'Withdraw',
'Vote on Proposal', ],
'Withdraw', Governance: [
]; 'Batch Proposal',
'Delegate',
'Undelegate',
'Vote on Proposal',
'Proposal',
],
Referrals: [
'Apply Referral Code',
'Create Referral Set',
'Join Team',
'Update Party Profile',
'Update Referral Set',
],
'External Data': ['Chain Event', 'Submit Oracle Data'],
Validators: [
'Ethereum Key Rotate Submission',
'Issue Signatures',
'Key Rotate Submission',
'Node Signature',
'Node Vote',
'Protocol Upgrade',
'Register new Node',
'State Variable Proposal',
'Validator Heartbeat',
],
};
// Alphabetised list of transaction types to nest under a 'More...' submenu export const AllFilterOptions: FilterOption[] =
export const SecondaryFilterOptions: FilterOption[] = [ Object.values(filterOptions).flat();
'Chain Event',
'Ethereum Key Rotate Submission',
'Issue Signatures',
'Key Rotate Submission',
'Node Signature',
'Node Vote',
'Protocol Upgrade',
'Register new Node',
'State Variable Proposal',
'Validator Heartbeat',
];
export const AllFilterOptions: FilterOption[] = [
...PrimaryFilterOptions,
...SecondaryFilterOptions,
];
export interface TxFilterProps { export interface TxFilterProps {
filters: Set<FilterOption>; filters: Set<FilterOption>;
@@ -122,46 +135,33 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>
)} )}
{PrimaryFilterOptions.map((f) => (
<DropdownMenuCheckboxItem {Object.entries(filterOptions).map(([key, value]) => (
key={f} <DropdownMenuSub key={key}>
checked={filters.has(f)} <DropdownMenuSubTrigger>
onCheckedChange={() => { {t(key)}
// NOTE: These act like radio buttons until the API supports multiple filters <Icon name="chevron-right" />
setFilters(new Set([f])); </DropdownMenuSubTrigger>
}} <DropdownMenuSubContent>
id={`radio-${f}`} {value.map((f) => (
> <DropdownMenuCheckboxItem
{f} key={f}
<DropdownMenuItemIndicator> checked={filters.has(f)}
<Icon name="tick-circle" /> onCheckedChange={(checked) => {
</DropdownMenuItemIndicator> // NOTE: These act like radio buttons until the API supports multiple filters
</DropdownMenuCheckboxItem> setFilters(new Set([f]));
}}
id={`radio-${f}`}
>
{f}
<DropdownMenuItemIndicator>
<Icon name="tick-circle" className="inline" />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
))} ))}
<DropdownMenuSub>
<DropdownMenuSubTrigger>
{t('More Types')}
<Icon name="chevron-right" />
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{SecondaryFilterOptions.map((f) => (
<DropdownMenuCheckboxItem
key={f}
checked={filters.has(f)}
onCheckedChange={(checked) => {
// NOTE: These act like radio buttons until the API supports multiple filters
setFilters(new Set([f]));
}}
id={`radio-${f}`}
>
{f}
<DropdownMenuItemIndicator>
<Icon name="tick-circle" className="inline" />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );
@@ -61,53 +61,4 @@ describe('TxsListNavigation', () => {
expect(nextPageMock).toHaveBeenCalledTimes(1); expect(nextPageMock).toHaveBeenCalledTimes(1);
}); });
it('disables "Older" button if hasMoreTxs is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Older')).toBeDisabled();
});
it('disables "Newer" button if hasPreviousPage is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={true}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
});
it('disables both buttons when more and previous are false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
expect(screen.getByText('Older')).toBeDisabled();
});
}); });
@@ -10,7 +10,8 @@ export interface TxListNavigationProps {
loading?: boolean; loading?: boolean;
hasPreviousPage: boolean; hasPreviousPage: boolean;
hasMoreTxs: boolean; hasMoreTxs: boolean;
children: React.ReactNode; children?: React.ReactNode;
isEmpty?: boolean;
} }
/** /**
* Displays a list of transactions with filters and controls to navigate through the list. * Displays a list of transactions with filters and controls to navigate through the list.
@@ -21,9 +22,8 @@ export const TxsListNavigation = ({
refreshTxs, refreshTxs,
nextPage, nextPage,
previousPage, previousPage,
hasMoreTxs,
hasPreviousPage,
children, children,
isEmpty,
loading = false, loading = false,
}: TxListNavigationProps) => { }: TxListNavigationProps) => {
return ( return (
@@ -35,7 +35,6 @@ export const TxsListNavigation = ({
<Button <Button
className="mr-2" className="mr-2"
size="xs" size="xs"
disabled={!hasPreviousPage || loading}
onClick={() => { onClick={() => {
previousPage(); previousPage();
}} }}
@@ -44,7 +43,7 @@ export const TxsListNavigation = ({
</Button> </Button>
<Button <Button
size="xs" size="xs"
disabled={!hasMoreTxs} disabled={isEmpty}
onClick={() => { onClick={() => {
nextPage(); nextPage();
}} }}
@@ -1,6 +1,8 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer'; import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon'; import { VoteIcon } from '../vote-icon/vote-icon';
import { ExternalChainIcon } from '../links/external-explorer-link/external-chain-icon';
import { getTypeLabelForTransfer } from './details/tx-transfer';
interface TxOrderTypeProps { interface TxOrderTypeProps {
orderType: string; orderType: string;
@@ -48,6 +50,8 @@ const displayString: StringMap = {
StopOrdersSubmission: 'Stop', StopOrdersSubmission: 'Stop',
StopOrdersCancellation: 'Cancel stop', StopOrdersCancellation: 'Cancel stop',
'Stop Orders Cancellation': 'Cancel stop', 'Stop Orders Cancellation': 'Cancel stop',
'Apply Referral Code': 'Referral',
'Create Referral Set': 'Create referral',
}; };
export function getLabelForStopOrderType( export function getLabelForStopOrderType(
@@ -92,7 +96,7 @@ export function getLabelForOrderType(
/** /**
* Given a proposal, will return a specific label * Given a proposal, will return a specific label
* @param chainEvent * @param proposal
* @returns * @returns
*/ */
export function getLabelForProposal( export function getLabelForProposal(
@@ -139,6 +143,36 @@ export function getLabelForProposal(
} }
} }
type label = {
type: string;
colours: string;
};
export function getLabelForTransfer(
transfer: components['schemas']['commandsv1Transfer']
): label {
const type = getTypeLabelForTransfer(transfer);
if (transfer.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
return {
type,
colours:
'text-vega-green dark:text-green bg-vega-dark-150 dark:bg-vega-dark-250',
};
} else if (transfer.recurring) {
return {
type,
colours:
'text-vega-yellow dark:text-yellow bg-vega-dark-150 dark:bg-vega-dark-250',
};
}
return {
type,
colours:
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250',
};
}
/** /**
* Given a chain event, will try to provide a more useful label * Given a chain event, will try to provide a more useful label
* @param chainEvent * @param chainEvent
@@ -186,6 +220,9 @@ export function getLabelForChainEvent(
} }
return t('Multisig update'); return t('Multisig update');
} else if (chainEvent.contractCall) { } else if (chainEvent.contractCall) {
if (chainEvent.contractCall.error) {
return t('Call error');
}
return t('Contract call'); return t('Contract call');
} }
return t('Chain Event'); return t('Chain Event');
@@ -219,9 +256,10 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
if (type === 'Chain Event' && !!command?.chainEvent) { if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent); type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink'; colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
} else if (type === 'Validator Heartbeat') { } else if (type === 'Transfer Funds' && command?.transfer) {
colours = const res = getLabelForTransfer(command.transfer);
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100'; type = res.type;
colours = res.colours;
} else if (type === 'Proposal' || type === 'Governance Proposal') { } else if (type === 'Proposal' || type === 'Governance Proposal') {
if (command && !!command.proposalSubmission) { if (command && !!command.proposalSubmission) {
type = getLabelForProposal(command.proposalSubmission); type = getLabelForProposal(command.proposalSubmission);
@@ -255,6 +293,11 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
data-testid="tx-type" data-testid="tx-type"
className={`text-sm rounded-md leading-tight px-2 inline-block whitespace-nowrap ${colours}`} className={`text-sm rounded-md leading-tight px-2 inline-block whitespace-nowrap ${colours}`}
> >
{command?.chainEvent && (
<ExternalChainIcon
chainId={command?.chainEvent?.contractCall?.sourceChainId}
/>
)}
{type} {type}
</div> </div>
); );
@@ -21,7 +21,7 @@ describe('TX: Transfer: getLabelForTransfer', () => {
}, },
}; };
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer'); expect(getTypeLabelForTransfer(mock)).toEqual('Reward transfer');
}); });
it('renders reward top up label if the TO party is network', () => { it('renders reward top up label if the TO party is network', () => {
@@ -32,7 +32,7 @@ describe('TX: Transfer: getLabelForTransfer', () => {
}, },
}; };
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer'); expect(getTypeLabelForTransfer(mock)).toEqual('Reward transfer');
}); });
it('renders recurring label if the tx has a recurring property', () => { it('renders recurring label if the tx has a recurring property', () => {
@@ -81,6 +81,7 @@ describe('TxDetailsTransfer', () => {
hash: 'test', hash: 'test',
submitter: submitter:
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed', 'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
block: '100',
command: { command: {
nonce: '5188810881378065222', nonce: '5188810881378065222',
blockHeight: '14951513', blockHeight: '14951513',

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