Compare commits

...

116 Commits

Author SHA1 Message Date
Gala 00bd028d8b Merge branch 'feat/2722-ne-ui' into release/v1.1.5 2023-01-11 16:35:12 +01:00
farbanas 273dc41559 feat: update changelogs 2023-01-10 13:43:09 +01:00
farbanas 61bc74148f Update versions 2023-01-10 13:10:24 +01:00
Pierre Dommerc e181a1cfb1 feat(wallet-buy): pass wallet address as url param (#2780) 2023-01-06 09:38:30 +01:00
Pierre Dommerc fdbe3a1f6a fix(explorer,explorer-api): mixnode location (#2763)
* chore(explorer-api): remove useless route (/terms)

* feat(explorer-api-geoip): add coordinates lat&lon

* fix(explorer): mixnode location

* fix: typo

* fix: clippy
2023-01-05 15:53:15 +01:00
Fouad 5b15ed6f15 merge resolve 2022-12-22 15:48:40 +01:00
Gala 57703af642 Merge pull request #2746 from nymtech/revert-1519-339-net-switch-bttn
Revert "NE: Switch button to change the explorer network"
2022-12-22 14:21:24 +01:00
Gala 9dd4c5d871 Revert "NE: Switch button to change the explorer network" 2022-12-22 14:20:21 +01:00
farbanas 4813cf6c18 feat: release v1.1.5 of nym-wallet 2022-12-22 12:14:01 +01:00
benedetta davico 83b76c6b37 Merge pull request #1519 from nymtech/339-net-switch-bttn
NE: Switch button to change the explorer network
2022-12-22 11:23:00 +01:00
Gala 4120234155 fix build problem 2022-12-22 11:09:55 +01:00
Gala 42b444ddf8 Merge branch '339-net-switch-bttn' of github.com:nymtech/nym into 339-net-switch-bttn 2022-12-22 10:55:03 +01:00
Gala acd832d8e5 cleaning 2022-12-22 10:54:26 +01:00
Gala 75a726ebbe some styles 2022-12-22 10:54:12 +01:00
Gala b9fbab6024 adding button on mov nav and make it smaller 2022-12-22 10:54:12 +01:00
Gala 06ed8716a1 adding a switch between networks 2022-12-22 10:54:12 +01:00
benedetta davico beddd3dcee Merge pull request #2742 from nymtech/feat/2130-tables-update-rebase
Feat/2130 tables update rebase
2022-12-22 10:37:53 +01:00
benedetta davico 4d02dfb899 Merge pull request #2741 from nymtech/fix/explorer-gateway-bond-decimals
fix(explorer): set gateway bond 6 decimals
2022-12-21 18:10:47 +01:00
Gala 80d6cb5c12 last small touch 2022-12-21 17:34:26 +01:00
Gala 7422ab69ba Merge branch 'feat/2130-tables-update-rebase' of github.com:nymtech/nym into feat/2130-tables-update-rebase 2022-12-21 17:30:26 +01:00
Gala 60c8185bea cleaning 2022-12-21 17:26:34 +01:00
Gala 9d3c7c0be8 refactor from PR request 2022-12-21 17:26:34 +01:00
Gala d6048fae52 refactor 2022-12-21 17:26:22 +01:00
Gala ddb7b0e872 delegations layout and tooltip when delegate with vesting 2022-12-21 17:26:22 +01:00
Gala 4995dde705 bond table changes 2022-12-21 17:25:09 +01:00
Gala 63bfe4246f fix parameters settings layout 2022-12-21 17:24:23 +01:00
Gala 4654b360e0 cleaning 2022-12-21 17:23:52 +01:00
Gala 7c5c19986a refactor from PR request 2022-12-21 17:23:52 +01:00
Gala 46edca0bd4 refactor 2022-12-21 17:23:52 +01:00
Gala b03a1f922d refactor 2022-12-21 17:23:52 +01:00
Gala 9616c90433 delegations layout and tooltip when delegate with vesting 2022-12-21 17:23:52 +01:00
Gala ea49f0a265 delegations table changes wip 2022-12-21 17:23:52 +01:00
Gala 2470c8b9b5 tidying up 2022-12-21 17:23:52 +01:00
Gala 7d001965ec change date order to dd/mm/yy 2022-12-21 17:22:26 +01:00
Gala 11b1089d83 removing epoch on settings and fixing divider position 2022-12-21 17:22:26 +01:00
Gala 52699d7598 bond table changes 2022-12-21 17:21:51 +01:00
benedetta davico 87443dd624 Merge pull request #2743 from nymtech/feat/2161-ne-gate-version
Feat/2161 ne gate version
2022-12-21 15:25:07 +01:00
Gala e0f2fa6705 fix indentation 2022-12-21 14:45:14 +01:00
Gala 7949b07213 Merge branch 'feat/2161-ne-gate-version' of github.com:nymtech/nym into feat/2161-ne-gate-version 2022-12-21 14:07:16 +01:00
Gala bd20fd0b1f remove console log 2022-12-21 14:06:32 +01:00
Gala 57d3d6fd0f sdding version number on the gateways list and details 2022-12-21 14:06:32 +01:00
Gala 06eff652dd wip adding gt version 2022-12-21 14:06:32 +01:00
Gala 246decac4a remove console log 2022-12-21 14:01:53 +01:00
Gala a14ae298ae sdding version number on the gateways list and details 2022-12-21 13:59:23 +01:00
Gala 43188051d3 Merge branch 'feat/2130-tables-update-rebase' of github.com:nymtech/nym into feat/2130-tables-update-rebase 2022-12-21 12:48:46 +01:00
Gala 8aa15fa467 fix parameters settings layout 2022-12-21 12:41:12 +01:00
benedetta davico f77b037ef7 Merge pull request #2704 from nymtech/feature/nym-connect-display-info
NymConnect - Display service info in tooltip **1.1.5 Release**
2022-12-21 12:39:44 +01:00
Gala 75dbc5d790 Merge branch 'release/v1.1.5' into feat/2130-tables-update-rebase 2022-12-21 12:20:42 +01:00
Gala e6bcd706ff cleaning 2022-12-21 12:03:46 +01:00
Gala 9a077a0928 refactor from PR request 2022-12-21 12:03:46 +01:00
Gala 11fd42e187 refactor 2022-12-21 12:03:46 +01:00
Gala 6e499e5996 refactor 2022-12-21 12:03:46 +01:00
Gala f98cc73a1f delegations layout and tooltip when delegate with vesting 2022-12-21 12:03:46 +01:00
Gala db9bf4d3fa delegations table changes wip 2022-12-21 12:03:46 +01:00
Gala b30628529d tidying up 2022-12-21 12:03:46 +01:00
Gala 3be615f74f change date order to dd/mm/yy 2022-12-21 12:03:46 +01:00
Gala a8ccd2ec17 removing epoch on settings and fixing divider position 2022-12-21 12:03:46 +01:00
Gala 2d71caf50a bond table changes 2022-12-21 12:03:46 +01:00
Gala 4d08d62fc2 wip adding gt version 2022-12-21 11:54:41 +01:00
pierre 40e5595d65 fix(explorer): set gateway bond 6 decimals 2022-12-21 11:46:12 +01:00
Gala 6780d58a98 cleaning 2022-12-21 11:19:50 +01:00
Gala ccbf06f179 refactor from PR request 2022-12-21 11:19:50 +01:00
Gala e6ecf71cc3 refactor 2022-12-21 11:19:50 +01:00
Gala c66312ee96 refactor 2022-12-21 11:19:50 +01:00
Gala 808802aeb4 delegations layout and tooltip when delegate with vesting 2022-12-21 11:19:50 +01:00
Gala fb38f24e5c delegations table changes wip 2022-12-21 11:19:50 +01:00
Gala 65f148a5ad tidying up 2022-12-21 11:19:50 +01:00
Gala 2bcdc5d11e change date order to dd/mm/yy 2022-12-21 11:19:50 +01:00
Gala 5ecb03ffe9 removing epoch on settings and fixing divider position 2022-12-21 11:19:50 +01:00
Gala 8505989dad bond table changes 2022-12-21 11:19:50 +01:00
benedetta davico ed5e865db7 Merge pull request #2718 from nymtech/feature/wallet-epoch-time-in-unbond-modal
Add epoch info to unbond modal **1.1.5 Release**
2022-12-21 10:37:38 +01:00
benedetta davico ea3194e0c3 Merge pull request #2720 from nymtech/feature/wallet-fix-parameter-input-layout
Fix param input layout **1.1.5 Release**
2022-12-21 10:24:05 +01:00
Gala 190bff4ffe Merge branch 'release/v1.1.5' of github.com:nymtech/nym into release/v1.1.5 2022-12-21 10:21:23 +01:00
fmtabbara c509555d15 fix param input layout 2022-12-21 09:22:01 +01:00
fmtabbara f0d66fdd88 add epoch info to unbond modal 2022-12-21 09:19:39 +01:00
fmtabbara 14847d01b7 remove console.log 2022-12-21 09:17:38 +01:00
fmtabbara f4711902bd display service info in tooltip
trim provider address
2022-12-21 09:17:38 +01:00
Jędrzej Stuczyński 32ad93c57e Feature/multi surb transmission lanes (#2723)
* Preserve information about original transmission lanes when buffering reply packets

* Attempting to send partial data in 'handle_send_reply' if we don't have enough surbs immediately

* Display logging of reply surb request target

* promoted reply_controller to a directory

* moved channels and messages to separate file

* simplifications due to rust 1.66

* Using a shoarthand for obtaining connection_id

* made TransmissionBuffer generic

* Moved transmissaion buffer to a higher level directory + defined wasm helpers

* Using transmission buffer in reply controller

* Using the pending replies size in lane lenghts queries

* fixed an out of bounds use of fragments

* Fixed dropped channel in getting lane queue length

* Fixed an edge case failure for reply retransmissions

* measuring (and logging) time it takes to obtain lane lenghts

* decreased logging level

* Removed non-wasm lock on total_size
2022-12-20 19:38:49 +00:00
farbanas c4a68dbbe6 chore: formatting 2022-12-20 15:28:51 +01:00
fmtabbara 1140503eba merge 1.1.4 to develop 2022-12-20 13:49:10 +00:00
farbanas d92d6877a4 Merge branch 'release/v1.1.4' 2022-12-20 12:33:46 +01:00
farbanas b9fed9f455 updating changelogs 2022-12-20 12:33:20 +01:00
farbanas 5e45f7d3a5 updating changelogs 2022-12-20 12:28:13 +01:00
farbanas 1133acd8bd update nym-connect and nym-wallet version 2022-12-20 12:14:05 +01:00
farbanas 08e6f3c4b7 update binaries version 2022-12-20 12:10:21 +01:00
farbanas 0ed546b739 fix: remove clear all settings button 2022-12-20 12:04:24 +01:00
Drazen Urch b4f2233d2b Outfox and Lion (#2730) 2022-12-20 11:29:28 +01:00
Mark Sinclair d8369eb4c9 Test and earn
- add logging and clear local storage menu items
- bump nym-connect version
- add app version number
- add reset trigger when clearing SP storage
- bump tauri version (nym-connect and nym-wallet)
- fix webpack config for prod builds
- new selector for services, with an advanced section for service providers
2022-12-19 18:18:08 +00:00
fmtabbara db3d379219 update buy NYM text 2022-12-19 15:07:49 +00:00
Gala a746738d48 make fields match button height 2022-12-19 13:40:26 +01:00
Jon Häggblad c67f0fb7f8 Merge remote-tracking branch 'origin/release/v1.1.4' into develop 2022-12-19 12:39:11 +01:00
Fouad 7c12a3422c Feature/node settings apy playground (#1677)
* initial ui for test my node

use svg for node path

add stories for test my node

* add initial rewards calculation

* update validation for rewards playground

* init playground with default values

* get node uptime

* get mixnode reward estimation

* calculate saturation

calculate stake saturation

* Make ComputeRewardEstParam derive Debug

* set active set to be always true

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2022-12-19 11:22:51 +00:00
Gala f047209baf Merge pull request #2691 from nymtech/2129-buy-2-secc
ui and copy changes
2022-12-19 10:43:03 +01:00
Gala cc5a69d4f1 Merge branch 'release/v1.1.4' into 2129-buy-2-secc 2022-12-19 10:40:01 +01:00
Gala 776d27a899 Merge pull request #2694 from nymtech/2687-remove-warning
remove warning error message
2022-12-19 10:35:25 +01:00
Gala 829dbb1695 Merge pull request #2689 from nymtech/2139-buy-secc-1
adding bity icon and wallet address
2022-12-19 10:33:47 +01:00
Jon Häggblad 52156e0c38 validator-client: fix typo in error message 2022-12-19 09:22:30 +01:00
Jon Häggblad 901275fd63 wallet: rewrite some abci errors on the fly (#2716)
* wallet: rewrite some abci errors on the fly

* changelog: update

* Tidy
2022-12-19 09:22:25 +01:00
Jon Häggblad 6c4f0bc2f4 validator-client: fix typo in error message 2022-12-19 09:12:33 +01:00
Jon Häggblad a0a73421d0 fix clippy for new rustc 2022-12-16 11:19:53 +01:00
Gala 80c81fa3d7 using a plain string 2022-12-14 14:52:33 +01:00
Gala 27e6539e98 remove message only for gateways 2022-12-14 14:48:49 +01:00
Gala a2d10d9956 remove warning error message 2022-12-14 14:12:16 +01:00
Gala 142a2bb26b copy 2022-12-14 13:05:54 +01:00
Gala 677d8c7fce text size 2022-12-14 12:30:10 +01:00
Gala 3563ad67b2 ui and copy changes 2022-12-14 12:21:36 +01:00
Gala c11a4c23fa adding bity icon and wallet address 2022-12-14 11:16:08 +01:00
farbanas ce676c2bb5 Merge branch 'release/v1.1.3' 2022-12-13 15:18:49 +01:00
farbanas a096f9d54e Merge branch 'release/v1.1.3' 2022-12-13 13:24:01 +01:00
farbanas d9b6823106 Merge branch 'release/v1.1.2' 2022-12-07 12:37:14 +01:00
farbanas 89bcb5649b changed ubuntu-latest on GH actions to ubuntu-20.04 2022-12-06 17:23:08 +01:00
Gala a74a99d81d cleaning 2022-08-10 16:38:56 +02:00
Gala c09d3af92f Merge branch 'develop' into 340-ne-content 2022-08-10 16:35:38 +02:00
Gala 76773c58d7 some styles 2022-08-10 16:34:30 +02:00
Gala fd90175e87 adding button on mov nav and make it smaller 2022-08-10 15:43:42 +02:00
Gala 0eb859467e adding a switch between networks 2022-08-10 15:09:44 +02:00
225 changed files with 12066 additions and 1352 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ GEOIPUPDATE_LICENSE_KEY=xxx
# List of space-separated database edition IDs. Edition IDs may
# consist of letters, digits, and dashes. For example, GeoIP2-City
# would download the GeoIP2 City database (GeoIP2-City).
GEOIPUPDATE_EDITION_IDS=GeoLite2-Country
GEOIPUPDATE_EDITION_IDS=GeoLite2-City
# The number of hours between geoipupdate runs. If this is not set
# or is set to 0, geoipupdate will run once and exit.
GEOIPUPDATE_FREQUENCY=72
+46 -34
View File
@@ -6,17 +6,35 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added
- socks5: send status message for service ready, and network-requester error response
- socks5: send status message for service ready, and network-requester error response in https://github.com/nymtech/nym/pull/2715
### Changed
- all-binaries: improved error logging ([#2686])
- native client: bring shutdown logic up to the same level as socks5-client
- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement ([#2670])
- all-binaries: improved error logging in https://github.com/nymtech/nym/pull/2686
- native client: bring shutdown logic up to the same level as socks5-client in https://github.com/nymtech/nym/pull/2695
- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement in https://github.com/nymtech/nym/pull/2670
- DKG resharing unit test by @neacsu in https://github.com/nymtech/nym/pull/2668
- Renaming validator-api to nym-api by @futurechimp in https://github.com/nymtech/nym/pull/1863
- Modify wasm specific make targets by @neacsu in https://github.com/nymtech/nym/pull/2693
- client: create websocket handler builder by @octol in https://github.com/nymtech/nym/pull/2700
- Outfox and Lion by @durch in https://github.com/nymtech/nym/pull/2730
- Feature/multi surb transmission lanes by @jstuczyn in https://github.com/nymtech/nym/pull/2723
[#2686]: https://github.com/nymtech/nym/pull/2686
[#2670]: https://github.com/nymtech/nym/pull/2670
## [v1.1.4] (2022-12-20)
This release adds multiple Single Use Reply Blocks (SURBs) to allow arbitrarily-sized anonymized replies.
At the moment this is turned off by default, but available for use by application developers.
We will need to wait for network-requesters to upgrade to this new release, after which multi-SURB anonymization will become the default setting for the SOCKS proxy clients.
The release also include some additional work for distributed key generation in the Coconut signing authority nodes.
### Changed
- Feature/dkg contract threshold by @neacsu in https://github.com/nymtech/nym/pull/1885
- Multi-surbs by @jstuczyn in https://github.com/nymtech/nym/pull/2667
- Fix multi-surb backwards compatibility in pre 1.1.4 client config files by @jstuczyn in https://github.com/nymtech/nym/pull/2703
- fix: ignore corrupted surb storage and instead create fresh one by @jstuczyn in https://github.com/nymtech/nym/pull/2711
- socks5: rework waiting in inbound.rs by @octol in https://github.com/nymtech/nym/pull/1880
## [v1.1.3] (2022-12-13)
@@ -31,7 +49,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- network-requester: fix bug where websocket connection disconnect resulted in success error code
- clients: fix a few panics handling the gateway-client
- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884])
- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884])
- socks5: fixed bug where connections sometimes where closed too early
- clients: improve message logging when received message fails to get reconstructed ([#1803])
@@ -51,7 +69,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- gateway: Renamed flag from `enabled/disabled_credentials_mode` to `only-coconut-credentials`
- "Family" feature for node families + layers
- Initial coconut functionality including credentials and distributed key generation
- Initial coconut functionality including credentials and distributed key generation
## [v1.1.1](https://github.com/nymtech/nym/tree/v1.1.1) (2022-11-29)
@@ -87,7 +105,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1786]: https://github.com/nymtech/nym/pull/1786
[#1805]: https://github.com/nymtech/nym/pull/1805
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
### Added
@@ -114,7 +131,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- moved `Percent` struct to `contracts-common`, change affects explorer-api
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
@@ -142,7 +159,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1724]: https://github.com/nymtech/nym/pull/1724
[#1725]: https://github.com/nymtech/nym/pull/1725
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
### Added
@@ -196,8 +212,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
- network explorer: tweak how active set probability is shown ([#1503])
- validator-api: rewarder set update fails without panicking on possible nymd queries ([#1520])
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
[#1249]: https://github.com/nymtech/nym/pull/1249
[#1256]: https://github.com/nymtech/nym/pull/1256
@@ -291,9 +306,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Bump minimist from 1.2.5 to 1.2.6 in /clients/tauri-client [\#1163](https://github.com/nymtech/nym/pull/1163) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /clients/webassembly/js-example [\#1162](https://github.com/nymtech/nym/pull/1162) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /clients/native/examples/js-examples/websocket [\#1160](https://github.com/nymtech/nym/pull/1160) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript\_client/upload\_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript_client/upload_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/vesting full [\#1158](https://github.com/nymtech/nym/pull/1158) ([fmtabbara](https://github.com/fmtabbara))
- get\_current\_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
- get_current_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
- Cleanup [\#1155](https://github.com/nymtech/nym/pull/1155) ([durch](https://github.com/durch))
- Feature flag reward payments [\#1154](https://github.com/nymtech/nym/pull/1154) ([durch](https://github.com/durch))
- Add Query endpoints for calculating rewards [\#1152](https://github.com/nymtech/nym/pull/1152) ([durch](https://github.com/durch))
@@ -302,7 +317,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- wallet: use Urls rather than Strings for validator urls [\#1148](https://github.com/nymtech/nym/pull/1148) ([octol](https://github.com/octol))
- Change accumulated reward to Option, migrate delegations [\#1147](https://github.com/nymtech/nym/pull/1147) ([durch](https://github.com/durch))
- wallet: fetch validators url remotely if available [\#1146](https://github.com/nymtech/nym/pull/1146) ([octol](https://github.com/octol))
- Fix delegated\_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
- Fix delegated_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
- Update Nym wallet dependencies to use `ts-packages` [\#1144](https://github.com/nymtech/nym/pull/1144) ([mmsinclair](https://github.com/mmsinclair))
- wallet: try validators one by one if available [\#1143](https://github.com/nymtech/nym/pull/1143) ([octol](https://github.com/octol))
- Update Network Explorer Packages and add mix node identity key copy [\#1142](https://github.com/nymtech/nym/pull/1142) ([mmsinclair](https://github.com/mmsinclair))
@@ -342,14 +357,13 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- feature/pedersen-commitments [\#1048](https://github.com/nymtech/nym/pull/1048) ([danielementary](https://github.com/danielementary))
- Feature/reuse init owner [\#970](https://github.com/nymtech/nym/pull/970) ([neacsu](https://github.com/neacsu))
## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.0...v0.12.1)
**Implemented enhancements:**
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
**Fixed bugs:**
@@ -379,7 +393,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Bugfix/remove mixnode bonding overwrite [\#917](https://github.com/nymtech/nym/pull/917) ([jstuczyn](https://github.com/jstuczyn))
- Fixes crash condition in validator API when calculating last day uptime [\#909](https://github.com/nymtech/nym/pull/909) ([jstuczyn](https://github.com/jstuczyn))
- Bugfix/monitor initial values wait [\#907](https://github.com/nymtech/nym/pull/907) ([jstuczyn](https://github.com/jstuczyn))
- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair))
- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair))
- Explorer API: port test now split out address resolution and add units tests [\#755](https://github.com/nymtech/nym/pull/755) ([mmsinclair](https://github.com/mmsinclair))
**Closed issues:**
@@ -394,7 +408,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- help!!! [\#712](https://github.com/nymtech/nym/issues/712)
- UX feature request: show all delegated nodes in wallet [\#711](https://github.com/nymtech/nym/issues/711)
- UX feature request: add current balance on wallet pages [\#710](https://github.com/nymtech/nym/issues/710)
- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709)
- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709)
- As a wallet user, I would like to be able to log out of the wallet [\#706](https://github.com/nymtech/nym/issues/706)
- As a wallet user, I would like to have a "receive" page where I can see my own wallet address [\#705](https://github.com/nymtech/nym/issues/705)
- Update native client/socks client/mixnode/gateway `upgrade` command [\#689](https://github.com/nymtech/nym/issues/689)
@@ -404,7 +418,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- nym-socks5-client crash after opening Keybase team "Browse all channels" [\#494](https://github.com/nymtech/nym/issues/494)
- Mixed Content problem [\#400](https://github.com/nymtech/nym/issues/400)
- Gateway disk quota [\#137](https://github.com/nymtech/nym/issues/137)
- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127)
- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127)
- Create constants for cli argument names [\#115](https://github.com/nymtech/nym/issues/115)
- Using Blake3 as a hash function [\#103](https://github.com/nymtech/nym/issues/103)
- Validator should decide which layer a node is in [\#86](https://github.com/nymtech/nym/issues/86)
@@ -460,10 +474,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Feature/pre cosmrs updates [\#935](https://github.com/nymtech/nym/pull/935) ([jstuczyn](https://github.com/jstuczyn))
- Feature/client on behalf [\#934](https://github.com/nymtech/nym/pull/934) ([neacsu](https://github.com/neacsu))
- Webpack wallet prod configuration [\#933](https://github.com/nymtech/nym/pull/933) ([tommyv1987](https://github.com/tommyv1987))
- Adding tx\_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp))
- Adding tx_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp))
- Release/1.0.0 pre1 [\#931](https://github.com/nymtech/nym/pull/931) ([durch](https://github.com/durch))
- Feature/identity verification [\#930](https://github.com/nymtech/nym/pull/930) ([jstuczyn](https://github.com/jstuczyn))
- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire))
- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire))
- Feature/mixnet contract further adjustments [\#928](https://github.com/nymtech/nym/pull/928) ([jstuczyn](https://github.com/jstuczyn))
- typo copy change for nodemap [\#926](https://github.com/nymtech/nym/pull/926) ([Aid19801](https://github.com/Aid19801))
- Feature/UI enhancements for Desktop Wallet [\#925](https://github.com/nymtech/nym/pull/925) ([fmtabbara](https://github.com/fmtabbara))
@@ -476,7 +490,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Feature/faucet page react [\#911](https://github.com/nymtech/nym/pull/911) ([fmtabbara](https://github.com/fmtabbara))
- Feature/mixnet contract refactor [\#910](https://github.com/nymtech/nym/pull/910) ([futurechimp](https://github.com/futurechimp))
- Update README.md [\#905](https://github.com/nymtech/nym/pull/905) ([tommyv1987](https://github.com/tommyv1987))
- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801))
- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801))
- Explorer UI tests missing data-testid [\#903](https://github.com/nymtech/nym/pull/903) ([tommyv1987](https://github.com/tommyv1987))
- Fix up Nym-Wallet README.md [\#899](https://github.com/nymtech/nym/pull/899) ([tommyv1987](https://github.com/tommyv1987))
- Feature/batch delegator rewarding [\#898](https://github.com/nymtech/nym/pull/898) ([jstuczyn](https://github.com/jstuczyn))
@@ -494,7 +508,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Reverted gateway registration handshake to its 0.11.0 version [\#882](https://github.com/nymtech/nym/pull/882) ([jstuczyn](https://github.com/jstuczyn))
- Network Explorer [\#881](https://github.com/nymtech/nym/pull/881) ([mmsinclair](https://github.com/mmsinclair))
- Feature/rewarding interval updates [\#880](https://github.com/nymtech/nym/pull/880) ([jstuczyn](https://github.com/jstuczyn))
- Put client\_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu))
- Put client_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu))
- remove gateway selection on delegation and undelegation pages [\#873](https://github.com/nymtech/nym/pull/873) ([fmtabbara](https://github.com/fmtabbara))
- Set MSRV on all binaries to 1.56 [\#872](https://github.com/nymtech/nym/pull/872) ([jstuczyn](https://github.com/jstuczyn))
- add native window items \(copy/paste\) via tauri [\#871](https://github.com/nymtech/nym/pull/871) ([fmtabbara](https://github.com/fmtabbara))
@@ -510,7 +524,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Overflow checks in release [\#846](https://github.com/nymtech/nym/pull/846) ([jstuczyn](https://github.com/jstuczyn))
- fix delegate success overflow [\#842](https://github.com/nymtech/nym/pull/842) ([fmtabbara](https://github.com/fmtabbara))
- Feature NYM wallet webdriverio test [\#841](https://github.com/nymtech/nym/pull/841) ([tommyv1987](https://github.com/tommyv1987))
- Update nym\_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987))
- Update nym_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987))
- Feature/vouchers [\#837](https://github.com/nymtech/nym/pull/837) ([aniampio](https://github.com/aniampio))
- Apply readable ids to elements on Nym Wallet [\#836](https://github.com/nymtech/nym/pull/836) ([tommyv1987](https://github.com/tommyv1987))
- Feature/removal of monitor good nodes [\#833](https://github.com/nymtech/nym/pull/833) ([jstuczyn](https://github.com/jstuczyn))
@@ -534,8 +548,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Created getters for AccountData [\#787](https://github.com/nymtech/nym/pull/787) ([jstuczyn](https://github.com/jstuczyn))
- Feature/migrate hidden delegations [\#786](https://github.com/nymtech/nym/pull/786) ([neacsu](https://github.com/neacsu))
- Feature/persistent gateway storage [\#784](https://github.com/nymtech/nym/pull/784) ([jstuczyn](https://github.com/jstuczyn))
- Replaced unwrap\_or\_else with unwrap\_or\_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn))
- Add block\_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch))
- Replaced unwrap_or_else with unwrap_or_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn))
- Add block_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch))
- Make fee helpers public [\#777](https://github.com/nymtech/nym/pull/777) ([durch](https://github.com/durch))
- re-enable bonding [\#776](https://github.com/nymtech/nym/pull/776) ([fmtabbara](https://github.com/fmtabbara))
- Explorer-api: add API resource to show the delegations for each mix node [\#774](https://github.com/nymtech/nym/pull/774) ([mmsinclair](https://github.com/mmsinclair))
@@ -544,14 +558,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Adding deps for building the Tauri wallet under Ubuntu [\#770](https://github.com/nymtech/nym/pull/770) ([futurechimp](https://github.com/futurechimp))
- remove alert [\#767](https://github.com/nymtech/nym/pull/767) ([fmtabbara](https://github.com/fmtabbara))
- Feature/consumable bandwidth [\#766](https://github.com/nymtech/nym/pull/766) ([neacsu](https://github.com/neacsu))
- Update coconut-rs and use hash\_to\_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu))
- Update coconut-rs and use hash_to_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu))
- Feature/active sets [\#764](https://github.com/nymtech/nym/pull/764) ([jstuczyn](https://github.com/jstuczyn))
- add app alert banner [\#762](https://github.com/nymtech/nym/pull/762) ([fmtabbara](https://github.com/fmtabbara))
- Updated cosmos-sdk [\#761](https://github.com/nymtech/nym/pull/761) ([jstuczyn](https://github.com/jstuczyn))
- Feature/bond blockstamp [\#760](https://github.com/nymtech/nym/pull/760) ([neacsu](https://github.com/neacsu))
- Feature/revert migration code [\#759](https://github.com/nymtech/nym/pull/759) ([neacsu](https://github.com/neacsu))
- Bump next from 11.1.0 to 11.1.1 in /wallet-web [\#758](https://github.com/nymtech/nym/pull/758) ([dependabot[bot]](https://github.com/apps/dependabot))
- Add block\_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu))
- Add block_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu))
- Feature/add blockstamp [\#756](https://github.com/nymtech/nym/pull/756) ([neacsu](https://github.com/neacsu))
- NetworkMonitorBuilder - starting the monitor after rocket has launched [\#754](https://github.com/nymtech/nym/pull/754) ([jstuczyn](https://github.com/jstuczyn))
- Enabled validators api argument [\#753](https://github.com/nymtech/nym/pull/753) ([jstuczyn](https://github.com/jstuczyn))
@@ -563,7 +577,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Feature/more reliable uptime calculation [\#747](https://github.com/nymtech/nym/pull/747) ([jstuczyn](https://github.com/jstuczyn))
- Update template toml key [\#746](https://github.com/nymtech/nym/pull/746) ([neacsu](https://github.com/neacsu))
- Feature/cred after handshake [\#745](https://github.com/nymtech/nym/pull/745) ([neacsu](https://github.com/neacsu))
- Reinstate the POST method blind\_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu))
- Reinstate the POST method blind_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu))
- explorer-api: add pending field to port check response [\#742](https://github.com/nymtech/nym/pull/742) ([mmsinclair](https://github.com/mmsinclair))
- Feature/use delegation rates [\#741](https://github.com/nymtech/nym/pull/741) ([neacsu](https://github.com/neacsu))
- Feature/copy to clipboard [\#740](https://github.com/nymtech/nym/pull/740) ([fmtabbara](https://github.com/fmtabbara))
@@ -600,6 +614,4 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Hang coconut issuance off the validator-api [\#679](https://github.com/nymtech/nym/pull/679) ([durch](https://github.com/durch))
- Update hmac and blake3 [\#673](https://github.com/nymtech/nym/pull/673) ([durch](https://github.com/durch))
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
Generated
+170 -22
View File
@@ -23,6 +23,16 @@ dependencies = [
"generic-array 0.14.5",
]
[[package]]
name = "aead"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8"
dependencies = [
"crypto-common",
"generic-array 0.14.5",
]
[[package]]
name = "aes"
version = "0.7.5"
@@ -53,7 +63,7 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [
"aead",
"aead 0.4.3",
"aes 0.7.5",
"cipher 0.3.0",
"ctr 0.8.0",
@@ -81,6 +91,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "ansi_term"
version = "0.12.1"
@@ -443,6 +459,12 @@ dependencies = [
"rustc_version 0.4.0",
]
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.73"
@@ -474,6 +496,30 @@ dependencies = [
"keystream",
]
[[package]]
name = "chacha20"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08"
dependencies = [
"cfg-if 1.0.0",
"cipher 0.4.3",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead 0.5.1",
"chacha20",
"cipher 0.4.3",
"poly1305",
"zeroize",
]
[[package]]
name = "chrono"
version = "0.4.19"
@@ -488,6 +534,33 @@ dependencies = [
"winapi",
]
[[package]]
name = "ciborium"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
[[package]]
name = "ciborium-ll"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "cipher"
version = "0.3.0"
@@ -505,6 +578,7 @@ checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]]
@@ -586,7 +660,7 @@ dependencies = [
[[package]]
name = "client-core"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"async-trait",
"client-connections",
@@ -980,9 +1054,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
dependencies = [
"atty",
"cast",
"cast 0.2.7",
"clap 2.34.0",
"criterion-plot",
"criterion-plot 0.4.4",
"csv",
"itertools",
"lazy_static",
@@ -999,13 +1073,49 @@ dependencies = [
"walkdir",
]
[[package]]
name = "criterion"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
dependencies = [
"anes",
"atty",
"cast 0.3.0",
"ciborium",
"clap 3.2.8",
"criterion-plot 0.5.0",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
dependencies = [
"cast",
"cast 0.2.7",
"itertools",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast 0.3.0",
"itertools",
]
@@ -1134,11 +1244,12 @@ dependencies = [
[[package]]
name = "crypto-common"
version = "0.1.3"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array 0.14.5",
"rand_core 0.6.3",
"typenum",
]
@@ -1466,7 +1577,7 @@ dependencies = [
"bitvec",
"bls12_381 0.6.0",
"bs58",
"criterion",
"criterion 0.3.5",
"ff 0.11.0",
"group 0.11.0",
"lazy_static",
@@ -1698,9 +1809,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fastrand"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
@@ -3123,7 +3234,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"anyhow",
"base64",
@@ -3177,7 +3288,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"clap 3.2.8",
"client-connections",
@@ -3216,7 +3327,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"anyhow",
"async-trait",
@@ -3263,7 +3374,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"anyhow",
"bs58",
@@ -3305,7 +3416,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"async-trait",
"clap 3.2.8",
@@ -3337,7 +3448,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"dirs",
"log",
@@ -3351,9 +3462,25 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-outfox"
version = "0.1.0"
dependencies = [
"blake3",
"chacha20",
"chacha20poly1305",
"criterion 0.4.0",
"curve25519-dalek",
"fastrand",
"getrandom 0.2.6",
"rayon",
"thiserror",
"zeroize",
]
[[package]]
name = "nym-socks5-client"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"clap 3.2.8",
"client-connections",
@@ -3420,7 +3547,7 @@ dependencies = [
[[package]]
name = "nym-validator-api"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"anyhow",
"async-trait",
@@ -3509,7 +3636,7 @@ dependencies = [
"bincode",
"bls12_381 0.6.0",
"bs58",
"criterion",
"criterion 0.3.5",
"digest 0.9.0",
"dkg",
"doc-comment",
@@ -4021,6 +4148,17 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug 0.3.0",
"universal-hash 0.5.0",
]
[[package]]
name = "polyval"
version = "0.5.3"
@@ -4030,7 +4168,7 @@ dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug 0.3.0",
"universal-hash",
"universal-hash 0.4.1",
]
[[package]]
@@ -6388,6 +6526,16 @@ dependencies = [
"subtle 2.4.1",
]
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle 2.4.1",
]
[[package]]
name = "untrusted"
version = "0.7.1"
@@ -6911,9 +7059,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zeroize"
version = "1.4.3"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
dependencies = [
"zeroize_derive",
]
+1
View File
@@ -76,6 +76,7 @@ members = [
"service-providers/network-statistics",
"nym-api",
"nym-api/nym-api-requests",
"nym-outfox",
"tools/nym-cli",
"tools/ts-rs-cli"
]
+2 -1
View File
@@ -1,8 +1,9 @@
[package]
name = "client-core"
version = "1.1.3"
version = "1.1.5"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -371,7 +371,7 @@ where
// channels responsible for dealing with reply-related fun
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::new_control_channels();
reply_controller::requests::new_control_channels();
let self_address = self.as_mix_recipient();
@@ -437,7 +437,7 @@ where
input_receiver,
sphinx_message_sender.clone(),
reply_storage,
reply_controller_sender,
reply_controller_sender.clone(),
reply_controller_receiver,
shared_lane_queue_lengths.clone(),
client_connection_rx,
@@ -471,6 +471,7 @@ where
received_buffer_request_sender,
},
},
reply_controller_sender,
task_manager,
})
}
@@ -480,5 +481,8 @@ pub struct BaseClient {
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
// it feels very wrong to put this channel here, but I can't think of any other way of passing it to the native client
pub reply_controller_sender: ReplyControllerSender,
pub task_manager: TaskManager,
}
@@ -0,0 +1,14 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(not(target_arch = "wasm32"))]
mod non_wasm;
#[cfg(target_arch = "wasm32")]
mod wasm;
#[cfg(not(target_arch = "wasm32"))]
pub use non_wasm::*;
#[cfg(target_arch = "wasm32")]
pub use wasm::*;
@@ -0,0 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use tokio::time::*;
pub type IntervalStream = tokio_stream::wrappers::IntervalStream;
pub(crate) fn get_time_now() -> Instant {
Instant::now()
}
pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream {
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate))
}
@@ -0,0 +1,16 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
use wasm_timer;
pub use wasm_timer::*;
pub type IntervalStream = gloo_timers::future::IntervalStream;
pub(crate) fn get_time_now() -> Instant {
wasm_timer::Instant::now()
}
pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream {
gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32)
}
+2
View File
@@ -3,6 +3,7 @@
pub mod base_client;
pub mod cover_traffic_stream;
pub(crate) mod helpers;
pub mod inbound_messages;
pub mod key_manager;
pub mod mix_traffic;
@@ -10,3 +11,4 @@ pub mod real_messages_control;
pub mod received_buffer;
pub mod replies;
pub mod topology_control;
pub(crate) mod transmission_buffer;
@@ -20,6 +20,7 @@ use nymsphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nymsphinx::preparer::{MessagePreparer, PreparedFragment};
use nymsphinx::Delay;
use rand::{CryptoRng, Rng};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
@@ -278,7 +279,7 @@ where
reply_surb: ReplySurb,
amount: u32,
) -> Result<(), SurbWrappedPreparationError> {
debug!("requesting {amount} reply SURBs from {from:?}");
debug!("requesting {amount} reply SURBs from {from}");
let surbs_request =
ReplyMessage::new_surb_request_message(self.config.sender_address, amount);
@@ -294,19 +295,11 @@ where
)))
}
// the only difference between this method and `try_send_reply_chunks` is that
// here we are not creating acks as acks are already in memory waiting to get cleared.
// we are only updating their existing delays
pub(crate) async fn try_send_retransmission_reply_chunks(
pub(crate) async fn send_retransmission_reply_chunks(
&mut self,
fragments: Vec<Fragment>,
reply_surbs: Vec<ReplySurb>,
prepared_fragments: Vec<PreparedFragment>,
lane: TransmissionLane,
) -> Result<(), SurbWrappedPreparationError> {
let prepared_fragments = self
.prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs)
.await?;
) {
let mut real_messages = Vec::with_capacity(prepared_fragments.len());
for prepared in prepared_fragments {
@@ -315,33 +308,58 @@ where
}
self.forward_messages(real_messages, lane).await;
Ok(())
}
pub(crate) async fn try_send_reply_chunks(
pub(crate) async fn try_send_reply_chunks_on_lane(
&mut self,
target: AnonymousSenderTag,
fragments: Vec<Fragment>,
reply_surbs: Vec<ReplySurb>,
lane: TransmissionLane,
) -> Result<(), SurbWrappedPreparationError> {
// TODO: technically this is performing an unnecessary cloning, but in the grand scheme of things
// is it really that bad?
self.try_send_reply_chunks(
target,
fragments.into_iter().map(|f| (lane, f)).collect(),
reply_surbs,
)
.await
}
pub(crate) async fn try_send_reply_chunks(
&mut self,
target: AnonymousSenderTag,
fragments: Vec<(TransmissionLane, Fragment)>,
reply_surbs: Vec<ReplySurb>,
) -> Result<(), SurbWrappedPreparationError> {
let prepared_fragments = self
.prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs)
.prepare_reply_chunks_for_sending(
fragments.iter().map(|(_, f)| f.clone()).collect(),
reply_surbs,
)
.await?;
let mut pending_acks = Vec::with_capacity(fragments.len());
let mut real_messages = Vec::with_capacity(fragments.len());
let mut to_forward: HashMap<_, Vec<_>> = HashMap::new();
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) {
let lane = raw.0;
let fragment = raw.1;
let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
let delay = prepared.total_delay;
let pending_ack = PendingAcknowledgement::new_anonymous(raw, delay, target, false);
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
real_messages.push(real_message);
let entry = to_forward.entry(lane).or_default();
entry.push(real_message);
pending_acks.push(pending_ack);
}
self.forward_messages(real_messages, lane).await;
for (lane, real_messages) in to_forward {
self.forward_messages(real_messages, lane).await;
}
self.insert_pending_acks(pending_acks);
Ok(())
}
@@ -467,7 +485,7 @@ where
Ok(prepared_fragment)
}
async fn prepare_reply_chunks_for_sending(
pub(crate) async fn prepare_reply_chunks_for_sending(
&mut self,
fragments: Vec<Fragment>,
reply_surbs: Vec<ReplySurb>,
@@ -1,9 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::sending_delay_controller::SendingDelayController;
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor;
use crate::client::transmission_buffer::TransmissionBuffer;
use client_connections::{
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
};
@@ -29,22 +31,7 @@ use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use self::{
sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
};
mod sending_delay_controller;
mod transmission_buffer;
#[cfg(not(target_arch = "wasm32"))]
fn get_time_now() -> time::Instant {
time::Instant::now()
}
#[cfg(target_arch = "wasm32")]
fn get_time_now() -> wasm_timer::Instant {
wasm_timer::Instant::now()
}
/// Configurable parameters of the `OutQueueControl`
pub(crate) struct Config {
@@ -135,7 +122,7 @@ where
/// Buffer containing all incoming real messages keyed by transmission lane, that we will send
/// out to the mixnet.
transmission_buffer: TransmissionBuffer,
transmission_buffer: TransmissionBuffer<RealMessage>,
/// Incoming channel for being notified of closed connections, so that we can close lanes
/// corresponding to connections. To avoid sending traffic unnecessary
@@ -162,6 +149,10 @@ impl From<PreparedFragment> for RealMessage {
}
impl RealMessage {
pub(crate) fn packet_size(&self) -> usize {
self.mix_packet.sphinx_packet().len()
}
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
RealMessage {
mix_packet,
@@ -207,7 +198,7 @@ where
real_receiver,
rng,
topology_access,
transmission_buffer: Default::default(),
transmission_buffer: TransmissionBuffer::new(),
client_connection_rx,
lane_queue_lengths,
}
@@ -328,7 +319,9 @@ where
fn pop_next_message(&mut self) -> Option<RealMessage> {
// Pop the next message from the transmission buffer
let (lane, real_next) = self.transmission_buffer.pop_next_message_at_random()?;
let (lane, real_next) = self
.transmission_buffer
.pop_next_message_at_random(&mut self.rng)?;
// Update the published queue length
let lane_length = self.transmission_buffer.lane_length(&lane);
@@ -1,14 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::get_time_now;
use crate::client::helpers::{get_time_now, Instant};
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
// the available buffer space we want to take somewhat swift action, but we still need to give a
// short time to give the channel a chance reduce pressure.
@@ -39,19 +34,11 @@ pub(crate) struct SendingDelayController {
lower_bound: u32,
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
#[cfg(not(target_arch = "wasm32"))]
time_when_changed: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_changed: wasm_timer::Instant,
time_when_changed: Instant,
/// If we have a long enough time without any backpressure detected we try reducing the sending
/// delay multiplier
#[cfg(not(target_arch = "wasm32"))]
time_when_backpressure_detected: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_backpressure_detected: wasm_timer::Instant,
time_when_backpressure_detected: Instant,
}
impl Default for SendingDelayController {
@@ -4,8 +4,8 @@
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError};
use crate::client::replies::reply_storage::CombinedReplyStorage;
use client_connections::TransmissionLane;
use futures::channel::mpsc;
use client_connections::{ConnectionId, TransmissionLane};
use futures::channel::oneshot;
use futures::StreamExt;
use log::{debug, error, info, trace, warn};
use nymsphinx::addressing::clients::Recipient;
@@ -15,116 +15,16 @@ use nymsphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use rand::{CryptoRng, Rng};
use std::cmp::{max, min};
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Weak};
use std::time::Duration;
use time::OffsetDateTime;
#[cfg(not(target_arch = "wasm32"))]
type IntervalStream = tokio_stream::wrappers::IntervalStream;
use crate::client::helpers::new_interval_stream;
use crate::client::transmission_buffer::TransmissionBuffer;
pub(crate) use requests::{ReplyControllerMessage, ReplyControllerReceiver, ReplyControllerSender};
#[cfg(target_arch = "wasm32")]
type IntervalStream = gloo_timers::future::IntervalStream;
pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) {
let (tx, rx) = mpsc::unbounded();
(tx.into(), rx)
}
#[derive(Debug, Clone)]
pub(crate) struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
impl From<mpsc::UnboundedSender<ReplyControllerMessage>> for ReplyControllerSender {
fn from(inner: mpsc::UnboundedSender<ReplyControllerMessage>) -> Self {
ReplyControllerSender(inner)
}
}
impl ReplyControllerSender {
pub(crate) fn send_retransmission_data(
&self,
recipient: AnonymousSenderTag,
timed_out_ack: Weak<PendingAcknowledgement>,
extra_surb_request: bool,
) {
self.0
.unbounded_send(ReplyControllerMessage::RetransmitReply {
recipient,
timed_out_ack,
extra_surb_request,
})
.expect("ReplyControllerReceiver has died!")
}
pub(crate) fn send_reply(
&self,
recipient: AnonymousSenderTag,
message: Vec<u8>,
lane: TransmissionLane,
) {
self.0
.unbounded_send(ReplyControllerMessage::SendReply {
recipient,
message,
lane,
})
.expect("ReplyControllerReceiver has died!")
}
pub(crate) fn send_additional_surbs(
&self,
sender_tag: AnonymousSenderTag,
reply_surbs: Vec<ReplySurb>,
from_surb_request: bool,
) {
self.0
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
sender_tag,
reply_surbs,
from_surb_request,
})
.expect("ReplyControllerReceiver has died!")
}
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
self.0
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
recipient: Box::new(recipient),
amount,
})
.expect("ReplyControllerReceiver has died!")
}
}
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
#[derive(Debug)]
pub(crate) enum ReplyControllerMessage {
RetransmitReply {
recipient: AnonymousSenderTag,
timed_out_ack: Weak<PendingAcknowledgement>,
extra_surb_request: bool,
},
SendReply {
recipient: AnonymousSenderTag,
message: Vec<u8>,
lane: TransmissionLane,
},
AdditionalSurbs {
sender_tag: AnonymousSenderTag,
reply_surbs: Vec<ReplySurb>,
from_surb_request: bool,
},
// Should this also be handled in here? it's technically a completely different side of the pipe
// let's see how it works when combined, might split it before creating PR
AdditionalSurbsRequest {
recipient: Box<Recipient>,
amount: u32,
},
}
pub mod requests;
pub struct Config {
min_surb_request_size: u32,
@@ -172,7 +72,7 @@ pub struct ReplyController<R> {
// of surbs required to send the message through
// expected_reliability: f32,
request_receiver: ReplyControllerReceiver,
pending_replies: HashMap<AnonymousSenderTag, VecDeque<Fragment>>,
pending_replies: HashMap<AnonymousSenderTag, TransmissionBuffer<Fragment>>,
/// Retransmission packets that have already timed out and are waiting for additional reply SURBs
/// so that they could be sent back to the network. Once we receive more SURBs, we should send them ASAP.
@@ -204,17 +104,28 @@ where
}
}
/// Inserts the pending replies into the BACK of the queue fn insert_pending_replies<V: Into<VecDeque<Fragment>>>(
fn insert_pending_replies<V: Into<VecDeque<Fragment>>>(
fn insert_pending_replies<I: IntoIterator<Item = Fragment>>(
&mut self,
recipient: &AnonymousSenderTag,
fragments: V,
fragments: I,
lane: TransmissionLane,
) {
if let Some(existing) = self.pending_replies.get_mut(recipient) {
existing.append(&mut fragments.into())
} else {
self.pending_replies.insert(*recipient, fragments.into());
}
self.pending_replies
.entry(*recipient)
.or_insert_with(TransmissionBuffer::new)
.store(&lane, fragments)
}
fn re_insert_pending_replies(
&mut self,
recipient: &AnonymousSenderTag,
fragments: Vec<(TransmissionLane, Fragment)>,
) {
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
self.pending_replies
.entry(*recipient)
.or_insert_with(TransmissionBuffer::new)
.store_multiple(fragments)
}
fn re_insert_pending_retransmission(
@@ -244,7 +155,7 @@ where
let pending_queue_size = self
.pending_replies
.get(target)
.map(|pending_queue| pending_queue.len())
.map(|pending_queue| pending_queue.total_size())
.unwrap_or_default();
let retransmission_queue = self
@@ -299,43 +210,62 @@ where
}
trace!("handling reply to {:?}", recipient_tag);
let fragments = self.message_handler.split_reply_message(data);
let mut fragments = self.message_handler.split_reply_message(data);
let total_size = fragments.len();
trace!("This reply requires {:?} SURBs", total_size);
let required_surbs = fragments.len();
trace!("This reply requires {:?} SURBs", required_surbs);
// TODO: edge case:
// we're making a lot of requests and have to request a lot of surbs
// (but at some point we run out of surbs for surb requests)
let (surbs, _surbs_left) = self
let available_surbs = self
.full_reply_storage
.surbs_storage_ref()
.get_reply_surbs(&recipient_tag, required_surbs);
.available_surbs(&recipient_tag);
let min_surbs_threshold = self
.full_reply_storage
.surbs_storage_ref()
.min_surb_threshold();
if let Some(reply_surbs) = surbs {
if let Err(err) = self
.message_handler
.try_send_reply_chunks(recipient_tag, fragments, reply_surbs, lane)
.await
{
let err = err.return_unused_surbs(
self.full_reply_storage.surbs_storage_ref(),
&recipient_tag,
);
warn!("failed to send reply to {:?} - {err}", recipient_tag);
// TODO: should we buffer that data to try again?
}
let max_to_send = if available_surbs > min_surbs_threshold {
min(fragments.len(), available_surbs - min_surbs_threshold)
} else {
// we don't have enough surbs for this reply
self.insert_pending_replies(&recipient_tag, fragments);
0
};
if self.should_request_more_surbs(&recipient_tag) {
self.request_reply_surbs_for_queue_clearing(recipient_tag)
.await;
if max_to_send > 0 {
let (surbs, _surbs_left) = self
.full_reply_storage
.surbs_storage_ref()
.get_reply_surbs(&recipient_tag, max_to_send);
if let Some(reply_surbs) = surbs {
let to_send = fragments.drain(..max_to_send).collect::<Vec<_>>();
if let Err(err) = self
.message_handler
.try_send_reply_chunks_on_lane(
recipient_tag,
to_send.clone(),
reply_surbs,
lane,
)
.await
{
let err = err.return_unused_surbs(
self.full_reply_storage.surbs_storage_ref(),
&recipient_tag,
);
warn!("failed to send reply to {recipient_tag}: {err}");
self.insert_pending_replies(&recipient_tag, to_send, lane);
}
}
}
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
if !fragments.is_empty() {
self.insert_pending_replies(&recipient_tag, fragments, lane);
}
if self.should_request_more_surbs(&recipient_tag) {
self.request_reply_surbs_for_queue_clearing(recipient_tag)
.await;
}
}
async fn request_additional_reply_surbs(
@@ -398,35 +328,18 @@ where
};
let mut to_take = Vec::new();
let mut to_remove = Vec::new();
// TODO: once rust 1.66.0 is stabilised on 15.12.22, just change it to
// `.pop_front()` to directly take ownership
for (k, data) in pending.iter() {
let upgraded = match data.upgrade() {
Some(upgraded) => upgraded,
None => {
// we got the ack while the data was waiting in the queue
to_remove.push(*k);
continue;
while to_take.len() < max_to_clear {
if let Some((_, data)) = pending.pop_first() {
// no need to do anything if we failed to upgrade the reference,
// it means we got the ack while the data was waiting in the queue
if let Some(upgraded) = data.upgrade() {
to_take.push(upgraded)
}
};
to_take.push(upgraded);
// we have taken as many entries as we could have
if to_take.len() >= max_to_clear {
} else {
// our map is empty!
break;
}
// TODO: use if upgraded.is_extra_surb_request() to bypass the limit
}
for ack in &to_take {
pending.remove(&ack.inner_fragment_identifier());
}
for id in to_remove {
pending.remove(&id);
}
if to_take.is_empty() {
@@ -447,46 +360,47 @@ where
let to_send_vec = to_take.iter().map(|ack| ack.fragment_data()).collect();
if let Err(err) = self
let prepared_fragments = match self
.message_handler
.try_send_retransmission_reply_chunks(
to_send_vec,
surbs_for_reply,
TransmissionLane::Retransmission,
)
.prepare_reply_chunks_for_sending(to_send_vec, surbs_for_reply)
.await
{
let err = err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
self.re_insert_pending_retransmission(&target, to_take);
Ok(prepared) => prepared,
Err(err) => {
let err =
err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
self.re_insert_pending_retransmission(&target, to_take);
warn!(
"failed to clear pending retransmission queue for {:?} - {err}",
target
);
}
warn!(
"failed to clear pending retransmission queue for {:?} - {err}",
target
);
return;
}
};
// we can't fail at this point, so drop all references to acks so that timer updates wouldn't blow up
drop(to_take);
self.message_handler
.send_retransmission_reply_chunks(prepared_fragments, TransmissionLane::Retransmission)
.await;
}
fn pop_at_most_pending_replies(
&mut self,
from: &AnonymousSenderTag,
amount: usize,
) -> Option<VecDeque<Fragment>> {
) -> Option<Vec<(TransmissionLane, Fragment)>> {
// if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb
let total = self.pending_replies.get(from)?.len();
let total = self.pending_replies.get(from)?.total_size();
trace!("pending queue has {total} elements");
if total == 0 {
return None;
}
if total < amount {
self.pending_replies.remove(from)
} else {
Some(
self.pending_replies
.get_mut(from)?
.drain(..amount)
.collect(),
)
}
self.pending_replies
.get_mut(from)?
.pop_at_most_n_next_messages_at_random(amount)
}
async fn try_clear_pending_queue(&mut self, target: AnonymousSenderTag) {
@@ -510,9 +424,9 @@ where
// we're guaranteed to not get more entries than we have reply surbs for
if let Some(to_send) = self.pop_at_most_pending_replies(&target, max_to_clear) {
let to_send_vec = to_send.iter().cloned().collect::<Vec<_>>();
let to_send_clone = to_send.clone();
if to_send_vec.is_empty() {
if to_send_clone.is_empty() {
panic!(
"please let the devs know if you ever see this message (reply_controller.rs)"
);
@@ -521,27 +435,22 @@ where
let (surbs_for_reply, _) = self
.full_reply_storage
.surbs_storage_ref()
.get_reply_surbs(&target, to_send_vec.len());
.get_reply_surbs(&target, to_send_clone.len());
let Some(surbs_for_reply) = surbs_for_reply else {
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
self.insert_pending_replies(&target, to_send);
self.re_insert_pending_replies(&target, to_send);
return;
};
if let Err(err) = self
.message_handler
.try_send_reply_chunks(
target,
to_send_vec,
surbs_for_reply,
TransmissionLane::General,
)
.try_send_reply_chunks(target, to_send_clone, surbs_for_reply)
.await
{
let err =
err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
self.insert_pending_replies(&target, to_send);
self.re_insert_pending_replies(&target, to_send);
warn!("failed to clear pending queue for {:?} - {err}", target);
}
} else {
@@ -712,6 +621,30 @@ where
}
}
// to be honest this doesn't make a lot of sense in the context of `connection_id`,
// it should really be asked per tag
fn handle_lane_queue_length(
&self,
connection_id: ConnectionId,
response_channel: oneshot::Sender<usize>,
) {
// TODO: if we ever have duplicate ids for different senders, it means our rng is super weak
// thus I don't think we have to worry about it?
let lane = TransmissionLane::ConnectionId(connection_id);
for buf in self.pending_replies.values() {
if let Some(length) = buf.lane_length(&lane) {
if response_channel.send(length).is_err() {
error!("the requester for lane queue length has dropped the response channel!")
}
return;
}
}
// make sure that if we didn't find that lane, we reply with 0
if response_channel.send(0).is_err() {
error!("the requester for lane queue length has dropped the response channel!")
}
}
async fn handle_request(&mut self, request: ReplyControllerMessage) {
match request {
ReplyControllerMessage::RetransmitReply {
@@ -735,19 +668,26 @@ where
self.handle_received_surbs(sender_tag, reply_surbs, from_surb_request)
.await
}
ReplyControllerMessage::LaneQueueLength {
connection_id,
response_channel,
} => self.handle_lane_queue_length(connection_id, response_channel),
ReplyControllerMessage::AdditionalSurbsRequest { recipient, amount } => {
self.handle_surb_request(*recipient, amount).await
}
}
}
// TODO: modify this method to more accurately determine the amount of surbs it needs to request
// it should take into consideration the average latency, sending rate and queue size.
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
trace!("requesting surbs for queues clearing");
let pending_queue_size = self
.pending_replies
.get(&target)
.map(|pending_queue| pending_queue.len())
.map(|pending_queue| pending_queue.total_size())
.unwrap_or_default();
let retransmission_queue = self
@@ -787,7 +727,7 @@ where
}
let Some(last_received) = self.full_reply_storage.surbs_storage_ref().surbs_last_received_at(pending_reply_target) else {
error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", vals.len());
error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", vals.total_size());
to_remove.push(*pending_reply_target);
continue;
};
@@ -883,23 +823,20 @@ where
}
}
fn create_interval_stream(polling_rate: Duration) -> IntervalStream {
#[cfg(not(target_arch = "wasm32"))]
return tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate));
#[cfg(target_arch = "wasm32")]
return gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32);
}
// #[cfg(not(target_arch = "wasm32"))]
// async fn log_status(&self) {
// todo!()
// }
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
debug!("Started ReplyController with graceful shutdown support");
let polling_rate = Duration::from_secs(5);
let mut stale_inspection = Self::create_interval_stream(polling_rate);
let mut stale_inspection = new_interval_stream(polling_rate);
// this is in the order of hours/days so we don't have to poll it that often
let polling_rate = Duration::from_secs(self.config.max_reply_surb_age.as_secs() / 10);
let mut invalidation_inspection = Self::create_interval_stream(polling_rate);
let mut invalidation_inspection = new_interval_stream(polling_rate);
while !shutdown.is_shutdown() {
tokio::select! {
@@ -0,0 +1,136 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
use client_connections::{ConnectionId, TransmissionLane};
use futures::channel::{mpsc, oneshot};
use log::error;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
use nymsphinx::anonymous_replies::ReplySurb;
use std::sync::Weak;
pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) {
let (tx, rx) = mpsc::unbounded();
(tx.into(), rx)
}
#[derive(Debug, Clone)]
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
impl From<mpsc::UnboundedSender<ReplyControllerMessage>> for ReplyControllerSender {
fn from(inner: mpsc::UnboundedSender<ReplyControllerMessage>) -> Self {
ReplyControllerSender(inner)
}
}
impl ReplyControllerSender {
pub(crate) fn send_retransmission_data(
&self,
recipient: AnonymousSenderTag,
timed_out_ack: Weak<PendingAcknowledgement>,
extra_surb_request: bool,
) {
self.0
.unbounded_send(ReplyControllerMessage::RetransmitReply {
recipient,
timed_out_ack,
extra_surb_request,
})
.expect("ReplyControllerReceiver has died!")
}
pub(crate) fn send_reply(
&self,
recipient: AnonymousSenderTag,
message: Vec<u8>,
lane: TransmissionLane,
) {
self.0
.unbounded_send(ReplyControllerMessage::SendReply {
recipient,
message,
lane,
})
.expect("ReplyControllerReceiver has died!")
}
pub(crate) fn send_additional_surbs(
&self,
sender_tag: AnonymousSenderTag,
reply_surbs: Vec<ReplySurb>,
from_surb_request: bool,
) {
self.0
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
sender_tag,
reply_surbs,
from_surb_request,
})
.expect("ReplyControllerReceiver has died!")
}
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
self.0
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
recipient: Box::new(recipient),
amount,
})
.expect("ReplyControllerReceiver has died!")
}
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
let (response_tx, response_rx) = oneshot::channel();
self.0
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
connection_id,
response_channel: response_tx,
})
.expect("ReplyControllerReceiver has died!");
match response_rx.await {
Ok(length) => length,
Err(_) => {
error!("The reply controller has dropped our response channel!");
// TODO: should we panic here instead? this message implies something weird and unrecoverable has happened
0
}
}
}
}
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
#[derive(Debug)]
pub(crate) enum ReplyControllerMessage {
RetransmitReply {
recipient: AnonymousSenderTag,
timed_out_ack: Weak<PendingAcknowledgement>,
extra_surb_request: bool,
},
SendReply {
recipient: AnonymousSenderTag,
message: Vec<u8>,
lane: TransmissionLane,
},
AdditionalSurbs {
sender_tag: AnonymousSenderTag,
reply_surbs: Vec<ReplySurb>,
from_surb_request: bool,
},
// this one doesn't belong here either...
LaneQueueLength {
connection_id: ConnectionId,
response_channel: oneshot::Sender<usize>,
},
// Should this also be handled in here? it's technically a completely different side of the pipe
// let's see how it works when combined, might split it before creating PR
AdditionalSurbsRequest {
recipient: Box<Recipient>,
amount: u32,
},
}
@@ -1,38 +1,57 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::helpers::{get_time_now, Instant};
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use client_connections::TransmissionLane;
use nymsphinx::chunking::fragment::Fragment;
use rand::{seq::SliceRandom, Rng};
use std::{
collections::{HashMap, HashSet, VecDeque},
time::Duration,
};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use super::{get_time_now, RealMessage};
// The number of lanes included in the oldest set. Used when we need to prioritize traffic.
const OLDEST_LANE_SET_SIZE: usize = 4;
// As a way of prune connections we also check for timeouts.
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
#[derive(Default)]
pub(crate) struct TransmissionBuffer {
buffer: HashMap<TransmissionLane, LaneBufferEntry>,
pub(crate) trait SizedData {
fn data_size(&self) -> usize;
}
impl TransmissionBuffer {
impl SizedData for RealMessage {
fn data_size(&self) -> usize {
self.packet_size()
}
}
impl SizedData for Fragment {
fn data_size(&self) -> usize {
// note that raw `Fragment` is smaller than sphinx packet payload
// as it doesn't include surb-ack or the [shared] key materials
self.payload_size()
}
}
#[derive(Default)]
pub(crate) struct TransmissionBuffer<T> {
buffer: HashMap<TransmissionLane, LaneBufferEntry<T>>,
}
impl<T> TransmissionBuffer<T> {
pub(crate) fn new() -> Self {
TransmissionBuffer {
buffer: HashMap::new(),
}
}
#[allow(unused)]
pub(crate) fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry> {
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry<T>> {
self.buffer.remove(lane)
}
@@ -57,20 +76,22 @@ impl TransmissionBuffer {
.collect()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size(&self) -> usize {
self.buffer.values().map(LaneBufferEntry::len).sum()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size_in_bytes(&self) -> usize {
pub(crate) fn total_size_in_bytes(&self) -> usize
where
T: SizedData,
{
self.buffer
.values()
.map(|lane_buffer_entry| {
lane_buffer_entry
.real_messages
.items
.iter()
.map(|real_message| real_message.mix_packet.sphinx_packet().len())
.map(|item| item.data_size())
.sum::<usize>()
})
.sum()
@@ -92,42 +113,51 @@ impl TransmissionBuffer {
.collect()
}
pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec<RealMessage>) {
pub(crate) fn store<I: IntoIterator<Item = T>>(&mut self, lane: &TransmissionLane, items: I) {
if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) {
lane_buffer_entry.append(real_messages);
lane_buffer_entry.extend(items);
} else {
self.buffer
.insert(*lane, LaneBufferEntry::new(real_messages));
.insert(*lane, LaneBufferEntry::new(items.into_iter().collect()));
}
}
fn pick_random_lane(&self) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
lanes.choose(&mut rand::thread_rng()).copied()
pub(crate) fn store_multiple(&mut self, items: Vec<(TransmissionLane, T)>) {
for (lane, item) in items {
self.buffer
.entry(lane)
.or_insert_with(LaneBufferEntry::new_empty)
.push_item(item)
}
}
fn pick_random_small_lane(&self) -> Option<&TransmissionLane> {
fn pick_random_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
lanes.choose(rng).copied()
}
fn pick_random_small_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self
.buffer
.iter()
.filter(|(_, v)| v.is_small())
.map(|(k, _)| k)
.collect();
lanes.choose(&mut rand::thread_rng()).copied()
lanes.choose(rng).copied()
}
// 2/3 chance to pick from the old lanes
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
fn pick_random_old_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<TransmissionLane> {
let rand = &mut rand::thread_rng();
if rand.gen_ratio(2, 3) {
let lanes = self.get_oldest_set();
lanes.choose(rand).copied()
} else {
self.pick_random_lane().copied()
self.pick_random_lane(rng).copied()
}
}
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<T> {
let real_msgs_queued = self.buffer.get_mut(lane)?;
let real_next = real_msgs_queued.pop_front()?;
real_msgs_queued.messages_transmitted += 1;
@@ -137,19 +167,48 @@ impl TransmissionBuffer {
Some(real_next)
}
pub(crate) fn pop_next_message_at_random(&mut self) -> Option<(TransmissionLane, RealMessage)> {
pub(crate) fn pop_at_most_n_next_messages_at_random(
&mut self,
n: usize,
) -> Option<Vec<(TransmissionLane, T)>> {
// let start = Instant::now();
if self.buffer.is_empty() {
return None;
}
let rng = &mut rand::thread_rng();
let mut items = Vec::with_capacity(n);
while items.len() < n {
let Some(next) = self.pop_next_message_at_random(rng) else {
break
};
items.push(next)
}
// todo!("time time taken");
Some(items)
}
pub(crate) fn pop_next_message_at_random<R: Rng + ?Sized>(
&mut self,
// turns out the caller always have access to some rng, so no point in instantiating new one
rng: &mut R,
) -> Option<(TransmissionLane, T)> {
if self.buffer.is_empty() {
return None;
}
// Very basic heuristic where we prioritize according to small lanes first, the older lanes
// to try to finish lanes when possible, then the rest.
let lane = if let Some(small_lane) = self.pick_random_small_lane() {
let lane = if let Some(small_lane) = self.pick_random_small_lane(rng) {
*small_lane
} else if let Some(old_lane) = self.pick_random_old_lane() {
} else if let Some(old_lane) = self.pick_random_old_lane(rng) {
old_lane
} else {
*self.pick_random_lane()?
*self.pick_random_lane(rng)?
};
let msg = self.pop_front_from_lane(&lane)?;
@@ -171,35 +230,46 @@ impl TransmissionBuffer {
}
}
pub(crate) struct LaneBufferEntry {
pub real_messages: VecDeque<RealMessage>,
pub(crate) struct LaneBufferEntry<T> {
pub items: VecDeque<T>,
pub messages_transmitted: usize,
#[cfg(not(target_arch = "wasm32"))]
pub time_for_last_activity: time::Instant,
#[cfg(target_arch = "wasm32")]
pub time_for_last_activity: wasm_timer::Instant,
pub time_for_last_activity: Instant,
}
impl LaneBufferEntry {
fn new(real_messages: Vec<RealMessage>) -> Self {
impl<T> LaneBufferEntry<T> {
fn new_empty() -> Self {
LaneBufferEntry {
real_messages: real_messages.into(),
items: VecDeque::new(),
messages_transmitted: 0,
time_for_last_activity: get_time_now(),
}
}
fn append(&mut self, real_messages: Vec<RealMessage>) {
self.real_messages.append(&mut real_messages.into());
fn new(items: VecDeque<T>) -> Self {
LaneBufferEntry {
items,
messages_transmitted: 0,
time_for_last_activity: get_time_now(),
}
}
fn push_item(&mut self, item: T) {
self.items.push_back(item);
// I'm not updating time here on purpose. This method is called just after `new_empty`,
// where the time is already set. Furthermore, this method is called there multiple times at once
}
fn extend<I: IntoIterator<Item = T>>(&mut self, items: I) {
self.items.extend(items);
self.time_for_last_activity = get_time_now();
}
fn pop_front(&mut self) -> Option<RealMessage> {
self.real_messages.pop_front()
fn pop_front(&mut self) -> Option<T> {
self.items.pop_front()
}
fn is_small(&self) -> bool {
self.real_messages.len() < 100
self.items.len() < 100
}
fn is_stale(&self) -> bool {
@@ -208,10 +278,10 @@ impl LaneBufferEntry {
}
fn len(&self) -> usize {
self.real_messages.len()
self.items.len()
}
fn is_empty(&self) -> bool {
self.real_messages.is_empty()
self.items.is_empty()
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.3"
version = "1.1.5"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+4
View File
@@ -13,6 +13,7 @@ use client_core::client::base_client::{
use client_core::client::inbound_messages::InputMessage;
use client_core::client::key_manager::KeyManager;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use client_core::client::replies::reply_controller::requests::ReplyControllerSender;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
@@ -87,6 +88,7 @@ impl SocketClient {
client_input: ClientInput,
client_output: ClientOutput,
self_address: &Recipient,
reply_controller_sender: ReplyControllerSender,
shutdown: task::TaskClient,
) {
info!("Starting websocket listener...");
@@ -107,6 +109,7 @@ impl SocketClient {
received_buffer_request_sender,
self_address,
shared_lane_queue_lengths,
reply_controller_sender,
);
websocket::Listener::new(config.get_listening_port()).start(websocket_handler, shutdown);
@@ -154,6 +157,7 @@ impl SocketClient {
client_input,
client_output,
&self_address,
started_client.reply_controller_sender,
started_client.task_manager.subscribe(),
);
+63 -73
View File
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use client_connections::{
ConnectionCommand, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
};
use client_core::client::replies::reply_controller::requests::ReplyControllerSender;
use client_core::client::{
inbound_messages::{InputMessage, InputMessageSender},
received_buffer::{
@@ -16,7 +17,9 @@ use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
use nymsphinx::receiver::ReconstructedMessage;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::Instant;
use tokio_tungstenite::{
accept_async,
tungstenite::{protocol::Message as WsMessage, Error as WsError},
@@ -41,6 +44,7 @@ pub(crate) struct HandlerBuilder {
buffer_requester: ReceivedBufferRequestSender,
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
}
impl HandlerBuilder {
@@ -50,6 +54,7 @@ impl HandlerBuilder {
buffer_requester: ReceivedBufferRequestSender,
self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
) -> Self {
Self {
msg_input,
@@ -57,6 +62,7 @@ impl HandlerBuilder {
buffer_requester,
self_full_address: *self_full_address,
lane_queue_lengths,
reply_controller_sender,
}
}
@@ -70,6 +76,7 @@ impl HandlerBuilder {
socket: None,
received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
reply_controller_sender: self.reply_controller_sender.clone(),
}
}
}
@@ -82,6 +89,7 @@ pub(crate) struct Handler {
socket: Option<WebSocketStream<TcpStream>>,
received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
}
impl Drop for Handler {
@@ -97,6 +105,48 @@ impl Drop for Handler {
}
impl Handler {
async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> Option<ServerResponse> {
let req_start = Instant::now();
// get the base queue length
// Note that this does _NOT_ take into account the packets that have been received but not
// yet reach `OutQueueControl`, so it might be a tad low.
let conn_lane = TransmissionLane::ConnectionId(connection_id);
let Ok(base_length) = self
.lane_queue_lengths
.lock()
.map(|guard| guard.get(&conn_lane).unwrap_or_default()) else {
// I'd argue we should panic here as this error it not recoverable
error!("The lane queue length lock is poisoned!!");
return None
};
// get the number of pending replies waiting for reply surbs
let reply_queue_length = self
.reply_controller_sender
.get_lane_queue_length(connection_id)
.await;
let queue_length = base_length + reply_queue_length;
let time_taken = req_start.elapsed();
let msg =
format!("it took {time_taken:?} to get lane length for connection {connection_id}. The length is: {queue_length} = {base_length} (already queued up) + {reply_queue_length} (waiting for reply SURBs)");
if time_taken > Duration::from_millis(1) {
info!("{msg}");
} else if time_taken > Duration::from_millis(10) {
warn!("{msg}");
} else if time_taken > Duration::from_millis(50) {
error!("{msg}");
}
Some(ServerResponse::LaneQueueLength {
lane: connection_id,
queue_length,
})
}
async fn handle_send(
&mut self,
recipient: Recipient,
@@ -121,27 +171,11 @@ impl Handler {
.expect("InputMessageReceiver has stopped receiving!");
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
let connection_id = match lane {
TransmissionLane::General
| TransmissionLane::ReplySurbRequest
| TransmissionLane::Retransmission
| TransmissionLane::AdditionalReplySurbs => return None,
TransmissionLane::ConnectionId(id) => id,
let TransmissionLane::ConnectionId(connection_id) = lane else {
return None
};
// on receiving a send, we reply back the current lane queue length for that connection id.
// Note that this does _NOT_ take into account the packets that have been received but not
// yet reach `OutQueueControl`, so it might be a tad low.
if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() {
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
return Some(ServerResponse::LaneQueueLength {
lane: connection_id,
queue_length,
});
}
log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length");
None
self.get_lane_queue_length(connection_id).await
}
async fn handle_send_anonymous(
@@ -168,27 +202,11 @@ impl Handler {
.expect("InputMessageReceiver has stopped receiving!");
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
let connection_id = match lane {
TransmissionLane::General
| TransmissionLane::ReplySurbRequest
| TransmissionLane::Retransmission
| TransmissionLane::AdditionalReplySurbs => return None,
TransmissionLane::ConnectionId(id) => id,
let TransmissionLane::ConnectionId(connection_id) = lane else {
return None
};
// on receiving a send, we reply back the current lane queue length for that connection id.
// Note that this does _NOT_ take into account the packets that have been received but not
// yet reach `OutQueueControl`, so it might be a tad low.
if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() {
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
return Some(ServerResponse::LaneQueueLength {
lane: connection_id,
queue_length,
});
}
log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length");
None
self.get_lane_queue_length(connection_id).await
}
async fn handle_reply(
@@ -211,27 +229,11 @@ impl Handler {
.expect("InputMessageReceiver has stopped receiving!");
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
let connection_id = match lane {
TransmissionLane::General
| TransmissionLane::ReplySurbRequest
| TransmissionLane::Retransmission
| TransmissionLane::AdditionalReplySurbs => return None,
TransmissionLane::ConnectionId(id) => id,
let TransmissionLane::ConnectionId(connection_id) = lane else {
return None
};
// on receiving a send, we reply back the current lane queue length for that connection id.
// Note that this does _NOT_ take into account the packets that have been received but not
// yet reach `OutQueueControl`, so it might be a tad low.
if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() {
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
return Some(ServerResponse::LaneQueueLength {
lane: connection_id,
queue_length,
});
}
log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length");
None
self.get_lane_queue_length(connection_id).await
}
fn handle_self_address(&self) -> ServerResponse {
@@ -245,20 +247,8 @@ impl Handler {
None
}
fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else {
log::warn!(
"Failed to get the lane queue length lock, not responding back with the current queue length"
);
return None;
};
let lane = TransmissionLane::ConnectionId(connection_id);
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
Some(ServerResponse::LaneQueueLength {
lane: connection_id,
queue_length,
})
async fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
self.get_lane_queue_length(connection_id).await
}
async fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
@@ -287,7 +277,7 @@ impl Handler {
ClientRequest::SelfAddress => Some(self.handle_self_address()),
ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id),
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id),
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id).await,
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.3"
version = "1.1.5"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+1 -2
View File
@@ -1,9 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use futures::channel::mpsc;
use std::collections::HashMap;
pub type ConnectionId = u64;
@@ -9,10 +9,11 @@ use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse,
GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, StakeSaturationResponse,
UptimeResponse,
};
use reqwest::Response;
use serde::{Deserialize, Serialize};
@@ -361,6 +362,25 @@ impl Client {
.await
}
pub async fn compute_mixnode_reward_estimation(
&self,
mix_id: MixId,
request_body: &ComputeRewardEstParam,
) -> Result<RewardEstimationResponse, NymAPIError> {
self.post_nym_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
routes::COMPUTE_REWARD_ESTIMATION,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn get_mixnode_stake_saturation(
&self,
mix_id: MixId,
@@ -28,6 +28,7 @@ pub const STATUS: &str = "status";
pub const REPORT: &str = "report";
pub const HISTORY: &str = "history";
pub const REWARD_ESTIMATION: &str = "reward-estimation";
pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
@@ -166,7 +166,7 @@ fn try_parse_abci_log(log: &abci::Log) -> Option<String> {
.value()
.contains("Maximum amount of locked coins has already been pledged")
{
Some("Maximum amount of locked tokens has alredy been used. You can only use up tp 10% of your locked tokens for bonding and delegating.".to_string())
Some("Maximum amount of locked tokens has alredy been used. You can only use up to 10% of your locked tokens for bonding and delegating.".to_string())
} else {
None
}
@@ -208,6 +208,11 @@ impl Fragment {
}
}
/// Gets the size of payload contained in this `Fragment`.
pub fn payload_size(&self) -> usize {
self.payload.len()
}
/// Extracts id of this `Fragment`.
pub fn id(&self) -> i32 {
self.header.id
+1 -1
View File
@@ -1,2 +1,2 @@
# The path to the geoip database file
GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb
GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.1"
version = "1.1.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+1 -1
View File
@@ -40,7 +40,7 @@ It should be previously installed thanks to `geoipupdate` service.
For example:
```shell
GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb cargo run
GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run
```
Note: explorer-api binary reads the provided `.env` file.
-26
View File
@@ -1,26 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::geo_ip::location::Location;
use crate::state::ExplorerApiStateContext;
use rocket::response::status;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::settings::OpenApiSettings;
pub fn nym_terms_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: terms]
}
#[openapi(tag = "terms")]
#[get("/")]
pub(crate) async fn terms(
_state: &State<ExplorerApiStateContext>,
location: Location,
) -> Result<Json<String>, status::Forbidden<String>> {
if location.iso_alpha2 == "US" {
return Err(status::Forbidden(Some("US government sucks".to_string())));
}
Ok(Json("Nym Terms & Conditions: Welcome".to_string()))
}
-1
View File
@@ -1 +0,0 @@
pub(crate) mod http;
+11 -6
View File
@@ -3,14 +3,14 @@
use isocountry::CountryCode;
use log::warn;
use maxminddb::{geoip2::Country, MaxMindDBError, Reader};
use maxminddb::{geoip2::City, MaxMindDBError, Reader};
use std::{
net::{IpAddr, ToSocketAddrs},
str::FromStr,
sync::Arc,
};
const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-Country.mmdb";
const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-City.mmdb";
const FAKE_PORT: u16 = 1234;
#[derive(Debug)]
@@ -38,6 +38,8 @@ pub(crate) struct Location {
pub(crate) iso_alpha3: String,
/// English country short name (ISO 3166-1)
pub(crate) name: String,
pub(crate) latitude: Option<f64>,
pub(crate) longitude: Option<f64>,
}
impl GeoIp {
@@ -86,7 +88,7 @@ impl GeoIp {
error!("No registered GeoIP database");
GeoIpError::InternalError
})?
.lookup::<Country>(ip);
.lookup::<City>(ip);
match &result {
Ok(v) => Ok(Some(
Location::try_from(v).map_err(|_| GeoIpError::InternalError)?,
@@ -99,11 +101,11 @@ impl GeoIp {
}
}
impl<'a> TryFrom<&Country<'a>> for Location {
impl<'a> TryFrom<&City<'a>> for Location {
type Error = String;
fn try_from(country: &Country) -> Result<Self, Self::Error> {
let data = country.country.as_ref().ok_or_else(|| {
fn try_from(city: &City) -> Result<Self, Self::Error> {
let data = city.country.as_ref().ok_or_else(|| {
warn!("No Country data found");
"No Country data found"
})?;
@@ -119,10 +121,13 @@ impl<'a> TryFrom<&Country<'a>> for Location {
warn!("{}", &message);
message
})?;
Ok(Location {
iso_alpha2,
iso_alpha3: String::from(iso_codes.alpha3()),
name: String::from(iso_codes.name()),
latitude: city.location.as_ref().and_then(|l| l.latitude),
longitude: city.location.as_ref().and_then(|l| l.longitude),
})
}
}
-2
View File
@@ -5,7 +5,6 @@ use rocket::{Build, Request, Rocket};
use rocket_cors::{AllowedHeaders, AllowedOrigins};
use rocket_okapi::swagger_ui::make_swagger_ui;
use crate::buy_terms::http::nym_terms_make_default_routes;
use crate::country_statistics::http::country_statistics_make_default_routes;
use crate::gateways::http::gateways_make_default_routes;
use crate::http::swagger::get_docs;
@@ -57,7 +56,6 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
"/overview" => overview_make_default_routes(&openapi_settings),
"/ping" => ping_make_default_routes(&openapi_settings),
"/validators" => validators_make_default_routes(&openapi_settings),
"/terms" => nym_terms_make_default_routes(&openapi_settings),
};
building_rocket
-1
View File
@@ -10,7 +10,6 @@ use logging::setup_logging;
use network_defaults::setup_env;
use task::TaskManager;
mod buy_terms;
pub(crate) mod cache;
mod client;
pub(crate) mod commands;
+4
View File
@@ -36,6 +36,8 @@ pub(crate) struct Location {
pub(crate) two_letter_iso_country_code: String,
pub(crate) three_letter_iso_country_code: String,
pub(crate) country_name: String,
pub(crate) latitude: Option<f64>,
pub(crate) longitude: Option<f64>,
}
impl Location {
@@ -44,6 +46,8 @@ impl Location {
country_name: location.name,
two_letter_iso_country_code: location.iso_alpha2,
three_letter_iso_country_code: location.iso_alpha3,
latitude: location.latitude,
longitude: location.longitude,
}
}
}
+8
View File
@@ -0,0 +1,8 @@
## UNRELEASED
## [nym-explorer-v1.0.1](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.1) (2023-01-10)
- Feat/2161 ne gate version by @gala1234 in https://github.com/nymtech/nym/pull/2743
- fix(explorer): set gateway bond 6 decimals by @doums in https://github.com/nymtech/nym/pull/2741
- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742
- fix(explorer,explorer-api): mixnode location by @doums in https://github.com/nymtech/nym-api
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/network-explorer",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"license": "Apache-2.0",
"dependencies": {
+2 -2
View File
@@ -5,7 +5,7 @@ import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
import { Box } from '@mui/system';
import { cellStyles } from './Universal-DataGrid';
import { currencyToString } from '../utils/currency';
import { unymToNym } from '../utils/currency';
import { GatewayEnrichedRowType } from './Gateways';
import { MixnodeRowType } from './MixNodes';
@@ -38,7 +38,7 @@ function formatCellValues(val: string | number, field: string) {
);
}
if (field === 'bond') {
return currencyToString(val.toString());
return unymToNym(val, 6);
}
return val;
}
+3
View File
@@ -7,6 +7,7 @@ export type GatewayRowType = {
bond: number;
host: string;
location: string;
version: string;
};
export type GatewayEnrichedRowType = GatewayRowType & {
@@ -26,6 +27,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
location: gw?.gateway?.location || '',
bond: gw.pledge_amount.amount || 0,
host: gw.gateway.host || '',
version: gw.gateway.version || '',
}));
}
@@ -40,6 +42,7 @@ export function gatewayEnrichedToGridRow(
location: gateway?.gateway?.location || '',
bond: gateway.pledge_amount.amount || 0,
host: gateway.gateway.host || '',
version: gateway.gateway.version || '',
clientsPort: gateway.gateway.clients_port || 0,
mixPort: gateway.gateway.mix_port || 0,
routingScore: `${report.most_recent}%`,
+6 -1
View File
@@ -4,6 +4,8 @@ import Select, { SelectChangeEvent } from '@mui/material/Select';
import { Filters } from './Filters/Filters';
import { useIsMobile } from '../hooks/useIsMobile';
const fieldsHeight = '42.25px';
type TableToolBarProps = {
onChangeSearch: (arg: string) => void;
onChangePageSize: (event: SelectChangeEvent<string>) => void;
@@ -35,7 +37,7 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
}}
>
<Box sx={{ display: 'flex', flexDirection: isMobile ? 'column-reverse' : 'row', alignItems: 'middle' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', height: fieldsHeight }}>
{childrenBefore}
<Select
labelId="simple-select-label"
@@ -65,6 +67,9 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
sx={{
width: isMobile ? '100%' : 200,
marginBottom: isMobile ? 2 : 0,
'& > :not(style)': {
height: fieldsHeight,
},
}}
value={searchTerm}
data-testid="search-box"
@@ -58,6 +58,12 @@ const columns: ColumnsType[] = [
headerAlign: 'left',
flex: 1,
},
{
field: 'version',
title: 'Version',
headerAlign: 'left',
flex: 1,
},
];
/**
+21 -3
View File
@@ -10,7 +10,7 @@ import { TableToolbar } from '../../components/TableToolbar';
import { CustomColumnHeading } from '../../components/CustomColumnHeading';
import { Title } from '../../components/Title';
import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid';
import { currencyToString } from '../../utils/currency';
import { unymToNym } from '../../utils/currency';
import { Tooltip } from '../../components/Tooltip';
import { BIG_DIPPER } from '../../api/constants';
import { splice } from '../../utils';
@@ -77,7 +77,7 @@ export const PageGateways: React.FC = () => {
to={`/network-components/gateway/${params.row.identityKey}`}
data-testid="pledge-amount"
>
{currencyToString(params.value)}
{unymToNym(params.value, 6)}
</MuiLink>
),
},
@@ -128,7 +128,7 @@ export const PageGateways: React.FC = () => {
field: 'owner',
headerName: 'Owner',
renderHeader: () => <CustomColumnHeading headingTitle="Owner" />,
width: 380,
width: 180,
headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => (
@@ -142,6 +142,24 @@ export const PageGateways: React.FC = () => {
</MuiLink>
),
},
{
field: 'version',
headerName: 'Version',
renderHeader: () => <CustomColumnHeading headingTitle="Version" />,
width: 150,
headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => (
<MuiLink
sx={{ ...cellStyles }}
href={`${BIG_DIPPER}/account/${params.value}`}
target="_blank"
data-testid="owner"
>
{params.value}
</MuiLink>
),
},
];
const handlePageSize = (event: SelectChangeEvent<string>) => {
+2 -2
View File
@@ -164,10 +164,10 @@ const PageMixnodeDetailWithState: React.FC = () => {
{mixNode && (
<ContentCard title="Location">
{mixNode?.error && <ComponentError text="There was a problem retrieving this mixnode location" />}
{mixNode.data && mixNode?.data?.location && (
{mixNode?.data?.location?.latitude && mixNode?.data?.location?.longitude && (
<WorldMap
loading={mixNode.isLoading}
userLocation={[mixNode?.data?.location?.lng, mixNode?.data?.location?.lat]}
userLocation={[mixNode.data.location.longitude, mixNode.data.location.latitude]}
/>
)}
</ContentCard>
+2 -2
View File
@@ -77,8 +77,8 @@ export interface MixNodeResponseItem {
status: MixnodeStatus;
location: {
country_name: string;
lat: number;
lng: number;
latitude?: number;
longitude?: number;
three_letter_iso_country_code: string;
two_letter_iso_country_code: string;
};
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.3"
version = "1.1.5"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.3"
version = "1.1.5"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-validator-api"
version = "1.1.3"
version = "1.1.5"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -114,7 +114,7 @@ impl MixNodeBondAnnotated {
}
}
#[derive(Deserialize, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ComputeRewardEstParam {
pub performance: Option<Performance>,
pub active_in_rewarded_set: Option<bool>,
+13
View File
@@ -30,6 +30,19 @@ module.exports = {
use: ['@svgr/webpack'],
});
config.module.rules.unshift({
test: /\.ya?ml$/,
type: 'json',
use: [
{
loader: 'yaml-loader',
options: {
asJSON: true,
},
},
],
});
config.resolve.extensions = ['.tsx', '.ts', '.js'];
config.resolve.plugins = [new TsconfigPathsPlugin()];
@@ -0,0 +1,10 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api/notification), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
isPermissionGranted: () => undefined,
requestPermission: () => undefined,
sendNotification: () => undefined,
};
+16 -1
View File
@@ -1,10 +1,25 @@
## UNRELEASED
## [nym-connect-v1.1.5](https://github.com/nymtech/nym/tree/nym-connect-v1.1.5) (2023-01-10)
- get version number from tauri and display by @fmtabbara in https://github.com/nymtech/nym/pull/2684
- Feature/nym connect experimental software text by @fmtabbara in https://github.com/nymtech/nym/pull/2692
- NymConnect - Display service info in tooltip **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2704
- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742
## [nym-connect-v1.1.4](https://github.com/nymtech/nym/tree/nym-connect-v1.1.4) (2022-12-20)
This release contains the new opt-in Test & Earn program, and it uses a stress-tested directory of network requesters to improve reliability. It also has some bugfixes, performance improvements, and better error handling.
- nym-connect: send status messages from socks5 task to tauri backend by @octol in https://github.com/nymtech/nym/pull/1882
- socks5: rework waiting in inbound.rs by @octol in https://github.com/nymtech/nym/pull/1880
- Test&Earn by @mmsinclair in https://github.com/nymtech/nym/pull/2729
## [nym-connect-v1.1.3](https://github.com/nymtech/nym/tree/nym-connect-v1.1.3) (2022-12-13)
- socks5-client: added support for socks4a.
## [nym-connect-v1.1.2](https://github.com/nymtech/nym/tree/nym-connect-v1.1.2) (2022-11-29)
## [nym-connect-v1.1.2](https://github.com/nymtech/nym/tree/nym-connect-v1.1.2) (2022-12-06)
- socks5-client: fix error with client failing and disconnecting unnecessarily.
+230 -92
View File
@@ -14,12 +14,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aes"
version = "0.7.5"
@@ -469,12 +463,11 @@ dependencies = [
[[package]]
name = "cargo_toml"
version = "0.11.6"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4419e9adae9fd7e231b60d50467481bf8181ddeef6ed54683b23ae925c74c9c"
checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8"
dependencies = [
"serde",
"serde_derive",
"toml",
]
@@ -639,7 +632,7 @@ dependencies = [
[[package]]
name = "client-core"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"async-trait",
"client-connections",
@@ -754,6 +747,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colored"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "colored"
version = "2.0.0"
@@ -1316,13 +1320,14 @@ dependencies = [
]
[[package]]
name = "deflate"
version = "0.7.20"
name = "dbus"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6"
dependencies = [
"adler32",
"byteorder",
"libc",
"libdbus-sys",
"winapi",
]
[[package]]
@@ -1471,6 +1476,12 @@ dependencies = [
"dtoa",
]
[[package]]
name = "dunce"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
[[package]]
name = "dyn-clone"
version = "1.0.9"
@@ -1632,6 +1643,16 @@ dependencies = [
"instant",
]
[[package]]
name = "fern"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
dependencies = [
"colored 1.9.3",
"log",
]
[[package]]
name = "ff"
version = "0.10.1"
@@ -2620,12 +2641,12 @@ dependencies = [
[[package]]
name = "ico"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804"
checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd"
dependencies = [
"byteorder",
"png 0.11.0",
"png",
]
[[package]]
@@ -2702,15 +2723,6 @@ dependencies = [
"cfb",
]
[[package]]
name = "inflate"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4"
dependencies = [
"adler32",
]
[[package]]
name = "inout"
version = "0.1.3"
@@ -2749,9 +2761,9 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
[[package]]
name = "itertools"
version = "0.10.4"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
@@ -2793,9 +2805,9 @@ dependencies = [
[[package]]
name = "jni"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
dependencies = [
"cesu8",
"combine",
@@ -2914,6 +2926,15 @@ version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "libdbus-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b"
dependencies = [
"pkg-config",
]
[[package]]
name = "libgit2-sys"
version = "0.14.0+1.5.0"
@@ -2974,6 +2995,12 @@ dependencies = [
"safemem",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "lioness"
version = "0.1.2"
@@ -3003,6 +3030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
"serde",
]
[[package]]
@@ -3034,6 +3062,19 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "mac-notification-sys"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5"
dependencies = [
"cc",
"dirs-next",
"objc-foundation",
"objc_id",
"time 0.3.17",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -3255,6 +3296,17 @@ dependencies = [
"wasm-timer",
]
[[package]]
name = "notify-rust"
version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368e89ea58df747ce88be669ae44e79783c1d30bfd540ad0fc520b3f41f0b3b0"
dependencies = [
"dbus",
"mac-notification-sys",
"tauri-winrt-notification",
]
[[package]]
name = "num-derive"
version = "0.3.3"
@@ -3276,17 +3328,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg 1.1.0",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
@@ -3357,21 +3398,28 @@ dependencies = [
name = "nym-connect"
version = "1.1.4"
dependencies = [
"anyhow",
"bip39",
"chrono",
"client-core",
"config",
"crypto",
"dirs",
"eyre",
"fern",
"fix-path-env",
"futures",
"itertools",
"log",
"logging",
"nym-socks5-client",
"pretty_env_logger",
"rand 0.8.5",
"reqwest",
"rust-embed",
"serde",
"serde_json",
"serde_repr",
"tap",
"task",
"tauri",
@@ -3385,11 +3433,12 @@ dependencies = [
"topology",
"ts-rs",
"url",
"yaml-rust",
]
[[package]]
name = "nym-socks5-client"
version = "1.1.3"
version = "1.1.4"
dependencies = [
"clap",
"client-connections",
@@ -3612,9 +3661,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.14.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "opaque-debug"
@@ -4065,18 +4114,6 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "png"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925"
dependencies = [
"bitflags",
"deflate",
"inflate",
"num-iter",
]
[[package]]
name = "png"
version = "0.17.6"
@@ -4221,6 +4258,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.21"
@@ -4473,9 +4519,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
@@ -4535,6 +4581,7 @@ dependencies = [
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-socks",
"tower-service",
"url",
"wasm-bindgen",
@@ -4604,6 +4651,41 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "rust-embed"
version = "6.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "6.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "7.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054"
dependencies = [
"globset",
"sha2 0.10.6",
"walkdir",
]
[[package]]
name = "rustc_version"
version = "0.3.3"
@@ -5459,6 +5541,27 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
dependencies = [
"heck 0.3.3",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "subtle"
version = "1.0.0"
@@ -5531,9 +5634,9 @@ dependencies = [
[[package]]
name = "tao"
version = "0.14.0"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43336f5d1793543ba96e2a1e75f3a5c7dcd592743be06a0ab3a190f4fcb4b934"
checksum = "a1fa15735311b4816d030ff54da58560b047daca0970e1031aed5502e84231a8"
dependencies = [
"bitflags",
"cairo-rs",
@@ -5566,12 +5669,12 @@ dependencies = [
"once_cell",
"parking_lot 0.12.1",
"paste",
"png 0.17.6",
"png",
"raw-window-handle",
"scopeguard",
"serde",
"unicode-segmentation",
"uuid 1.1.2",
"uuid 1.2.2",
"windows 0.39.0",
"windows-implement",
"x11-dl",
@@ -5608,9 +5711,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "1.1.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbf22abd61d95ca9b2becd77f9db4c093892f73e8a07d21d8b0b2bf71a7bcea"
checksum = "d8ea1d785ab2164373703817bff144c4610a69ad3f659becaca0e1ea004b98d8"
dependencies = [
"anyhow",
"attohttpc",
@@ -5628,6 +5731,7 @@ dependencies = [
"http",
"ignore",
"minisign-verify",
"notify-rust",
"objc",
"once_cell",
"open",
@@ -5652,7 +5756,7 @@ dependencies = [
"time 0.3.17",
"tokio",
"url",
"uuid 1.1.2",
"uuid 1.2.2",
"webkit2gtk",
"webview2-com",
"windows 0.39.0",
@@ -5661,9 +5765,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "1.1.1"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0991fb306849897439dbd4a72e4cbed2413e2eb26cb4b3ba220b94edba8b4b88"
checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4"
dependencies = [
"anyhow",
"cargo_toml",
@@ -5677,16 +5781,16 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "1.1.1"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356fa253e40ae4d6ff02075011f2f2bb4066f5c9d8c1e16ca6912d7b75903ba6"
checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251"
dependencies = [
"base64",
"brotli",
"ico",
"json-patch",
"plist",
"png 0.17.6",
"png",
"proc-macro2",
"quote",
"regex",
@@ -5697,15 +5801,15 @@ dependencies = [
"tauri-utils",
"thiserror",
"time 0.3.17",
"uuid 1.1.2",
"uuid 1.2.2",
"walkdir",
]
[[package]]
name = "tauri-macros"
version = "1.1.1"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6051fd6940ddb22af452340d03c66a3e2f5d72e0788d4081d91e31528ccdc4d"
checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075"
dependencies = [
"heck 0.4.0",
"proc-macro2",
@@ -5717,30 +5821,29 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "0.11.1"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49439a5ea47f474572b854972f42eda2e02a470be5ca9609cc83bb66945abe2"
checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b"
dependencies = [
"gtk",
"http",
"http-range",
"infer",
"rand 0.8.5",
"raw-window-handle",
"serde",
"serde_json",
"tauri-utils",
"thiserror",
"uuid 1.1.2",
"uuid 1.2.2",
"webview2-com",
"windows 0.39.0",
]
[[package]]
name = "tauri-runtime-wry"
version = "0.11.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dce920995fd49907aa9bea7249ed1771454f11f7611924c920a1f75fb614d4"
checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30"
dependencies = [
"cocoa",
"gtk",
@@ -5749,7 +5852,7 @@ dependencies = [
"raw-window-handle",
"tauri-runtime",
"tauri-utils",
"uuid 1.1.2",
"uuid 1.2.2",
"webkit2gtk",
"webview2-com",
"windows 0.39.0",
@@ -5758,15 +5861,16 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "1.1.1"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e8fdae6f29cef959809a3c3afef510c5b715a446a597ab8b791497585363f39"
checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1"
dependencies = [
"brotli",
"ctor",
"glob",
"heck 0.4.0",
"html5ever",
"infer",
"json-patch",
"kuchiki",
"memchr",
@@ -5783,6 +5887,17 @@ dependencies = [
"windows 0.39.0",
]
[[package]]
name = "tauri-winrt-notification"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b"
dependencies = [
"quick-xml",
"strum",
"windows 0.39.0",
]
[[package]]
name = "tempfile"
version = "3.3.0"
@@ -6055,6 +6170,18 @@ dependencies = [
"webpki 0.22.0",
]
[[package]]
name = "tokio-socks"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
dependencies = [
"either",
"futures-util",
"thiserror",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.10"
@@ -6346,9 +6473,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]]
name = "uuid"
version = "1.1.2"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
dependencies = [
"getrandom 0.2.7",
]
@@ -6363,7 +6490,7 @@ dependencies = [
"coconut-bandwidth-contract-common",
"coconut-dkg-common",
"coconut-interface",
"colored",
"colored 2.0.0",
"config",
"contracts-common",
"cosmrs",
@@ -6634,9 +6761,9 @@ dependencies = [
[[package]]
name = "webkit2gtk"
version = "0.18.0"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29952969fb5e10fe834a52eb29ad0814ccdfd8387159b0933edf1344a1c9cdcc"
checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370"
dependencies = [
"bitflags",
"cairo-rs",
@@ -7011,15 +7138,16 @@ dependencies = [
[[package]]
name = "wry"
version = "0.21.1"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff5c1352b4266fdf92c63479d2f58ab4cd29dc4e78fbc1b62011ed1227926945"
checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98"
dependencies = [
"base64",
"block",
"cocoa",
"core-graphics",
"crossbeam-channel",
"dunce",
"gdk",
"gio",
"glib",
@@ -7035,6 +7163,7 @@ dependencies = [
"serde",
"serde_json",
"sha2 0.10.6",
"soup2",
"tao",
"thiserror",
"url",
@@ -7101,6 +7230,15 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zeroize"
version = "1.5.7"
@@ -7124,9 +7262,9 @@ dependencies = [
[[package]]
name = "zip"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d"
checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080"
dependencies = [
"byteorder",
"crc32fast",
+8 -4
View File
@@ -1,11 +1,12 @@
{
"name": "@nym/nym-connect",
"version": "1.1.3",
"version": "1.1.5",
"main": "index.js",
"license": "MIT",
"scripts": {
"prewebpack:dev": "yarn --cwd .. build",
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
"webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js",
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
"tauri:dev": "RUST_DEBUG=1 yarn tauri dev",
"tauri:build": "yarn tauri build",
@@ -30,7 +31,7 @@
"@mui/material": "^5.2.2",
"@mui/styles": "^5.2.2",
"@nymproject/react": "^1.0.0",
"@tauri-apps/api": "^1.1.0",
"@tauri-apps/api": "^1.2.0",
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
"clsx": "^1.1.1",
"luxon": "^2.3.0",
@@ -39,6 +40,7 @@
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
"react-hook-form": "^7.14.2",
"react-markdown": "^8.0.4",
"react-router-dom": "^5.2.0",
"semver": "^6.3.0",
"yup": "^0.32.9"
@@ -49,11 +51,12 @@
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@mdx-js/loader": "^2.1.5",
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@storybook/react": "^6.5.8",
"@svgr/webpack": "^6.1.1",
"@tauri-apps/cli": "^1.1.0",
"@tauri-apps/cli": "^1.2.2",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/jest": "^27.0.1",
@@ -105,6 +108,7 @@
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
"webpack-merge": "^5.8.0",
"yaml-loader": "^0.8.0"
}
}
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Nym Connect</title>
</head>
<body style="background: rgb(29, 33, 37);">
<div id="root-growth"></div>
</body>
</html>
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>nym-connect</title>
<title>Nym Connect</title>
</head>
<body>
<div id="root"></div>
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Nym Wallet Logs</title>
</head>
<body>
<div id="root-log"></div>
</body>
</html>
+15 -8
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.1.4"
version = "1.1.5"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
@@ -13,33 +13,40 @@ rust-version = "1.58"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "^1.1.1", features = [] }
tauri-build = { version = "^1.2.1", features = [] }
tauri-codegen = "^1.1.1"
tauri-macros = "^1.1.1"
tauri-codegen = "^1.2.1"
tauri-macros = "^1.2.1"
[dependencies]
anyhow = "1.0"
bip39 = "1.0"
chrono = "0.4"
dirs = "4.0"
eyre = "0.6.5"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
futures = "0.3"
log = "0.4"
fern = { version = "0.6.1", features = ["colored"] }
itertools = "0.10.5"
log = { version = "0.4", features = ["serde"] }
pretty_env_logger = "0.4.0"
rand = "0.8"
reqwest = { version = "0.11", features = ["json"] }
reqwest = { version = "0.11", features = ["json", "socks"] }
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_repr = "0.1"
tap = "1.0.1"
tauri = { version = "^1.1.1", features = ["clipboard-write-text", "macos-private-api", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] }
tauri = { version = "^1.2.2", features = ["clipboard-write-text", "macos-private-api", "notification-all", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] }
tendermint-rpc = "0.23.0"
thiserror = "1.0"
tokio = { version = "1.21.2", features = ["sync", "time"] }
url = "2.2"
yaml-rust = "0.4"
client-core = { path = "../../clients/client-core" }
config-common = { path = "../../common/config", package = "config" }
crypto = { path = "../../common/crypto" }
logging = { path = "../../common/logging"}
nym-socks5-client = { path = "../../clients/socks5" }
task = { path = "../../common/task" }
+7
View File
@@ -44,6 +44,13 @@ impl Config {
}
}
#[allow(unused)]
pub fn new_with_port<S: Into<String>>(id: S, provider_mix_address: S, port: u16) -> Self {
Config {
socks5: Socks5Config::new(id, provider_mix_address).with_port(port),
}
}
pub fn get_socks5(&self) -> &Socks5Config {
&self.socks5
}
+12
View File
@@ -27,6 +27,11 @@ pub enum BackendError {
source: tauri::Error,
},
#[error("{source}")]
TauriApiError {
#[from]
source: tauri::api::Error,
},
#[error("{source}")]
SerdeJsonError {
#[from]
source: serde_json::Error,
@@ -36,6 +41,11 @@ pub enum BackendError {
#[from]
source: ClientCoreError<fs_backend::Backend>,
},
#[error("{source}")]
ApiClientError {
#[from]
source: crate::operations::growth::api_client::ApiClientError,
},
#[error("Could not send disconnect signal to the SOCKS5 client")]
CoundNotSendDisconnectSignal,
@@ -57,6 +67,8 @@ pub enum BackendError {
CouldNotGetConfigFilename,
#[error("Could not load existing gateway configuration")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("Unable to open a new window")]
NewWindowError,
}
impl Serialize for BackendError {
+113
View File
@@ -0,0 +1,113 @@
use std::str::FromStr;
use fern::colors::ColoredLevelConfig;
use serde::Serialize;
use serde_repr::{Deserialize_repr, Serialize_repr};
use tauri::Manager;
pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> {
let colors = ColoredLevelConfig::new();
let base_config = fern::Dispatch::new()
.level(global_level())
.filter_lowlevel_external_components()
.show_operations();
let stdout_config = fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
colors.color(record.level()),
message,
))
})
.chain(std::io::stdout());
let tauri_event_config = fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{}[{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
message,
))
})
.chain(fern::Output::call(move |record| {
let msg = LogMessage {
message: record.args().to_string(),
level: record.level().into(),
};
app_handle.emit_all("log://log", msg).unwrap();
}));
base_config
.chain(stdout_config)
.chain(tauri_event_config)
.apply()
}
trait FernExt {
fn show_operations(self) -> Self;
fn filter_lowlevel_external_components(self) -> Self;
}
impl FernExt for fern::Dispatch {
fn show_operations(self) -> Self {
if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() {
self.level_for("nym_connect::operations", log::LevelFilter::Trace)
} else {
self
}
}
fn filter_lowlevel_external_components(self) -> Self {
self.level_for("hyper", log::LevelFilter::Warn)
.level_for("tokio_reactor", log::LevelFilter::Warn)
.level_for("reqwest", log::LevelFilter::Warn)
.level_for("mio", log::LevelFilter::Warn)
.level_for("want", log::LevelFilter::Warn)
.level_for("sled", log::LevelFilter::Warn)
.level_for("tungstenite", log::LevelFilter::Warn)
.level_for("tokio_tungstenite", log::LevelFilter::Warn)
.level_for("rustls", log::LevelFilter::Warn)
.level_for("tokio_util", log::LevelFilter::Warn)
}
}
fn global_level() -> log::LevelFilter {
if let Ok(s) = ::std::env::var("RUST_LOG") {
log::LevelFilter::from_str(&s).unwrap_or(log::LevelFilter::Info)
} else {
log::LevelFilter::Info
}
}
#[derive(Debug, Serialize, Clone)]
struct LogMessage {
message: String,
level: LogLevel,
}
// Serialize to u16 instead of strings.
#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)]
#[repr(u16)]
enum LogLevel {
Trace = 1,
Debug,
Info,
Warn,
Error,
}
impl From<log::Level> for LogLevel {
fn from(level: log::Level) -> Self {
match level {
log::Level::Trace => LogLevel::Trace,
log::Level::Debug => LogLevel::Debug,
log::Level::Info => LogLevel::Info,
log::Level::Warn => LogLevel::Warn,
log::Level::Error => LogLevel::Error,
}
}
}
+27 -5
View File
@@ -6,8 +6,7 @@
use std::sync::Arc;
use config_common::defaults::setup_env;
use logging::setup_logging;
use tauri::Menu;
use tauri::{Manager, Menu};
use tokio::sync::RwLock;
use crate::menu::AddDefaultSubmenus;
@@ -17,6 +16,7 @@ use crate::window::window_toggle;
mod config;
mod error;
mod logging;
mod menu;
mod models;
mod operations;
@@ -25,7 +25,6 @@ mod tasks;
mod window;
fn main() {
setup_logging();
setup_env(None);
println!("Starting up...");
@@ -35,11 +34,13 @@ fn main() {
log::warn!("Failed to fix PATH: {error}");
}
let context = tauri::generate_context!();
tauri::Builder::default()
.manage(Arc::new(RwLock::new(State::new())))
.invoke_handler(tauri::generate_handler![
crate::config::get_config_file_location,
crate::config::get_config_id,
crate::operations::connection::status::get_connection_status,
crate::operations::connection::connect::get_gateway,
crate::operations::connection::connect::get_service_provider,
crate::operations::connection::connect::set_gateway,
@@ -49,10 +50,31 @@ fn main() {
crate::operations::directory::get_services,
crate::operations::export::export_keys,
crate::operations::window::hide_window,
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
crate::operations::growth::test_and_earn::growth_tne_take_part,
crate::operations::growth::test_and_earn::growth_tne_get_draws,
crate::operations::growth::test_and_earn::growth_tne_ping,
crate::operations::growth::test_and_earn::growth_tne_submit_wallet_address,
crate::operations::growth::test_and_earn::growth_tne_enter_draw,
crate::operations::growth::test_and_earn::growth_tne_toggle_window,
crate::operations::help::log::help_log_toggle_window,
])
.menu(Menu::new().add_default_app_submenu_if_macos())
.menu(Menu::os_default(&context.package_info().name).add_default_app_submenus())
.on_menu_event(|event| {
if event.menu_item_id() == menu::SHOW_LOG_WINDOW {
let _r = crate::operations::help::log::help_log_toggle_window(
event.window().app_handle(),
);
}
if event.menu_item_id() == menu::CLEAR_STORAGE {
let _r = crate::operations::help::storage::help_clear_storage(
event.window().app_handle(),
);
}
})
.setup(|app| Ok(crate::logging::setup_logging(app.app_handle())?))
.system_tray(create_tray_menu())
.on_system_tray_event(tray_menu_event_handler)
.run(tauri::generate_context!())
.run(context)
.expect("error while running tauri application");
}
+13 -29
View File
@@ -1,40 +1,24 @@
use crate::window_toggle;
use tauri::{
AppHandle, CustomMenuItem, Menu, SystemTray, SystemTrayEvent, SystemTrayMenu,
AppHandle, CustomMenuItem, Menu, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu,
SystemTrayMenuItem, Wry,
};
#[cfg(target_os = "macos")]
use tauri::{MenuItem, Submenu};
use crate::window_toggle;
pub const SHOW_LOG_WINDOW: &str = "show_log_window";
pub const CLEAR_STORAGE: &str = "clear_storage";
pub trait AddDefaultSubmenus {
fn add_default_app_submenu_if_macos(self) -> Self;
fn add_default_app_submenus(self) -> Self;
}
impl AddDefaultSubmenus for Menu {
fn add_default_app_submenu_if_macos(self) -> Menu {
#[cfg(target_os = "macos")]
return self
.add_submenu(Submenu::new(
"File",
Menu::new().add_native_item(MenuItem::Quit),
))
.add_submenu(Submenu::new(
"Edit",
Menu::new()
.add_native_item(MenuItem::Copy)
.add_native_item(MenuItem::Cut)
.add_native_item(MenuItem::Paste)
.add_native_item(MenuItem::SelectAll),
))
.add_submenu(Submenu::new(
"Window",
Menu::new()
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::HideOthers)
.add_native_item(MenuItem::ShowAll),
));
#[cfg(not(target_os = "macos"))]
return self;
fn add_default_app_submenus(self) -> Self {
let submenu = Submenu::new(
"Help",
Menu::new().add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")),
);
self.add_submenu(submenu)
}
}
+21 -3
View File
@@ -39,21 +39,31 @@ impl fmt::Display for ConnectionStatusKind {
pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-changed";
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Clone, serde::Serialize)]
#[derive(Clone, Serialize)]
pub struct AppEventConnectionStatusChangedPayload {
pub status: ConnectionStatusKind,
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DirectoryService {
pub id: String,
pub description: String,
pub items: Vec<DirectoryServiceProvider>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct HarbourMasterService {
pub service_provider_client_id: String,
pub gateway_identity_key: String,
pub ip_address: String,
pub last_successful_ping_utc: String,
pub last_updated_utc: String,
pub routing_score: f32,
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DirectoryServiceProvider {
pub id: String,
pub description: String,
@@ -63,3 +73,11 @@ pub struct DirectoryServiceProvider {
/// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
pub gateway: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PagedResult<T> {
pub page: u32,
pub size: u32,
pub total: i32,
pub items: Vec<T>,
}
@@ -37,10 +37,10 @@ pub async fn get_service_provider(state: tauri::State<'_, Arc<RwLock<State>>>) -
#[tauri::command]
pub async fn set_service_provider(
service_provider: String,
service_provider: Option<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<()> {
log::trace!("Setting service_provider: {service_provider}");
log::trace!("Setting service_provider: {:?}", &service_provider);
let mut guard = state.write().await;
guard.set_service_provider(service_provider);
Ok(())
@@ -57,10 +57,10 @@ pub async fn get_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<
#[tauri::command]
pub async fn set_gateway(
gateway: String,
gateway: Option<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<()> {
log::trace!("Setting gateway: {gateway}");
log::trace!("Setting gateway: {:?}", &gateway);
let mut guard = state.write().await;
guard.set_gateway(gateway);
Ok(())
@@ -1,2 +1,3 @@
pub mod connect;
pub mod disconnect;
pub mod status;
@@ -0,0 +1,15 @@
use crate::error::Result;
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::models::ConnectionStatusKind;
use crate::state::State;
#[tauri::command]
pub async fn get_connection_status(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ConnectionStatusKind> {
let state = state.read().await;
Ok(state.get_status())
}
@@ -1,9 +1,13 @@
use itertools::Itertools;
use crate::error::Result;
use crate::models::DirectoryService;
use crate::models::{DirectoryService, HarbourMasterService, PagedResult};
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
"https://nymtech.net/.wellknown/connect/service-providers.json";
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
#[tauri::command]
pub async fn get_services() -> Result<Vec<DirectoryService>> {
log::trace!("Fetching services");
@@ -12,5 +16,36 @@ pub async fn get_services() -> Result<Vec<DirectoryService>> {
.json::<Vec<DirectoryService>>()
.await?;
log::trace!("Received: {:#?}", res);
Ok(res)
// TODO: get paged
log::trace!("Fetching active services");
let active_services = reqwest::get(HARBOUR_MASTER_URL)
.await?
.json::<PagedResult<HarbourMasterService>>()
.await?;
log::trace!("Active: {:#?}", active_services);
let mut filtered: Vec<DirectoryService> = vec![];
for service in &res {
let items: _ = service
.items
.clone()
.into_iter()
.filter(|sp| {
active_services
.items
.iter()
.any(|active| active.service_provider_client_id == sp.address)
})
.collect_vec();
log::trace!("service = {} has {} items", service.id, items.len());
filtered.push(DirectoryService {
id: service.id.clone(),
description: service.description.clone(),
items,
})
}
Ok(filtered)
}
@@ -5,6 +5,24 @@ use crate::{
error::{BackendError, Result},
state::State,
};
use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity;
pub async fn get_identity_key(
state: &tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Arc<identity::KeyPair>> {
let config = {
let state = state.read().await;
state.load_socks5_config()?
};
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
let key_manager = KeyManager::load_keys(&pathfinder)?;
let identity_keypair = key_manager.identity_keypair();
Ok(identity_keypair)
}
/// Export the gateway keys as a JSON string blob
#[tauri::command]
@@ -0,0 +1,271 @@
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[allow(unused)]
#[derive(Error, Debug)]
pub enum ApiClientError {
#[error("{source}")]
Reqwest {
#[from]
source: reqwest::Error,
},
#[error("{source}")]
SerdeJson {
#[from]
source: serde_json::Error,
},
#[error("{0}")]
Status(String),
}
const API_BASE_URL: &str = "https://growth-api.nymtech.net";
// For development mode, switch to this
// const API_BASE_URL: &str = "http://localhost:8000";
#[derive(Debug, Clone)]
pub struct GrowthApiClient {
base_url: String,
}
impl GrowthApiClient {
pub fn new(resource_base: &str) -> Self {
let base_url = std::env::var("API_BASE_URL").unwrap_or_else(|_| API_BASE_URL.to_string());
GrowthApiClient {
base_url: format!("{}{}", base_url, resource_base),
}
}
pub fn registrations() -> Registrations {
Registrations::new(GrowthApiClient::new("/v1/tne"))
}
pub fn daily_draws() -> DailyDraws {
DailyDraws::new(GrowthApiClient::new("/v1/tne/daily_draw"))
}
pub(crate) async fn get<T: DeserializeOwned>(&self, url: &str) -> Result<T, ApiClientError> {
log::info!(">>> GET {}", url);
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
let client = reqwest::Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(10))
.build()?;
match client.get(format!("{}{}", self.base_url, url)).send().await {
Ok(res) => {
if res.status().is_client_error() || res.status().is_server_error() {
log::error!("<<< {}", res.status());
return Err(ApiClientError::Status(res.status().to_string()));
}
match res.text().await {
Ok(response_body) => {
log::info!("<<< {}", response_body);
match serde_json::from_str(&response_body) {
Ok(res) => Ok(res),
Err(e) => {
log::error!("<<< JSON parsing error: {}", e);
Err(e.into())
}
}
}
Err(e) => {
log::error!("<<< Request error: {}", e);
Err(e.into())
}
}
}
Err(e) => {
log::error!("<<< Response parsing error: {}", e);
Err(e.into())
}
}
}
pub(crate) async fn post<REQ: Serialize + ?Sized, RESP: DeserializeOwned>(
&self,
url: &str,
body: &REQ,
) -> Result<RESP, ApiClientError> {
log::info!(">>> POST {}", url);
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
let client = reqwest::Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(10))
.build()?;
match client
.post(format!("{}{}", self.base_url, url))
.json(body)
.send()
.await
{
Ok(res) => {
if res.status().is_client_error() || res.status().is_server_error() {
log::error!("<<< {}", res.status());
return Err(ApiClientError::Status(res.status().to_string()));
}
match res.text().await {
Ok(response_body) => {
log::info!("<<< {}", response_body);
match serde_json::from_str(&response_body) {
Ok(res) => Ok(res),
Err(e) => {
log::error!("<<< JSON parsing error: {}", e);
Err(e.into())
}
}
}
Err(e) => {
log::error!("<<< Request error: {}", e);
Err(e.into())
}
}
}
Err(e) => {
log::error!("<<< Response parsing error: {}", e);
Err(e.into())
}
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClientIdPartial {
pub client_id: String,
pub client_id_signature: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Registration {
pub id: String,
pub client_id: String,
pub timestamp: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Ping {
pub client_id: String,
pub timestamp: String,
}
pub struct Registrations {
client: GrowthApiClient,
}
impl Registrations {
pub fn new(client: GrowthApiClient) -> Self {
Registrations { client }
}
pub async fn register(
&self,
registration: &ClientIdPartial,
) -> Result<Registration, ApiClientError> {
self.client.post("/register", &registration).await
}
#[allow(dead_code)]
pub async fn unregister(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
self.client.post("/unregister", &registration).await
}
#[allow(dead_code)]
pub async fn status(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
self.client.post("/status", &registration).await
}
pub async fn ping(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
self.client.post("/ping", &registration).await
}
#[allow(dead_code)]
pub async fn health(&self) -> Result<(), ApiClientError> {
self.client.get("/health").await
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DrawEntryPartial {
pub draw_id: String,
pub client_id: String,
pub client_id_signature: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DrawEntry {
pub id: String,
pub draw_id: String,
pub timestamp: String,
pub status: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DrawWithWordOfTheDay {
pub id: String,
pub start_utc: String,
pub end_utc: String,
pub word_of_the_day: Option<String>,
pub last_modified: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClaimPartial {
pub draw_id: String,
pub registration_id: String,
pub client_id: String,
pub client_id_signature: String,
pub wallet_address: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Winner {
pub id: String,
pub client_id: String,
pub draw_id: String,
pub timestamp: String,
pub winner_reg_id: String,
pub winner_wallet_address: Option<String>,
pub winner_claim_timestamp: Option<String>,
}
pub struct DailyDraws {
client: GrowthApiClient,
}
impl DailyDraws {
pub fn new(client: GrowthApiClient) -> Self {
DailyDraws { client }
}
pub async fn current(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
self.client.get("/current").await
}
pub async fn next(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
self.client.get("/next").await
}
#[allow(dead_code)]
pub async fn status(&self, draw_id: &str) -> Result<DrawWithWordOfTheDay, ApiClientError> {
self.client
.get(format!("/status/{}", draw_id).as_str())
.await
}
pub async fn enter(&self, entry: &DrawEntryPartial) -> Result<DrawEntry, ApiClientError> {
self.client.post("/enter", entry).await
}
pub async fn entries(
&self,
client_id: &ClientIdPartial,
) -> Result<Vec<DrawEntry>, ApiClientError> {
self.client.post("/entries", client_id).await
}
pub async fn claim(&self, claim: &ClaimPartial) -> Result<Winner, ApiClientError> {
self.client.post("/claim", claim).await
}
}
@@ -0,0 +1,57 @@
use rust_embed::RustEmbed;
extern crate yaml_rust;
use yaml_rust::YamlLoader;
#[derive(RustEmbed)]
#[folder = "../src/components/Growth/content/"]
#[include = "*.yaml"]
#[exclude = "*.mdx"]
struct Asset;
#[derive(Debug)]
pub struct NotificationContent {
pub title: String,
pub body: String,
}
#[derive(Debug)]
pub struct Notifications {
pub you_are_in_draw: NotificationContent,
pub take_part: NotificationContent,
}
pub struct Content {}
const RESOURCE_ERROR: &str = "❌ RESOURCE ERROR";
fn get_as_string_or_error_message(value: &yaml_rust::Yaml) -> String {
value.as_str().unwrap_or(RESOURCE_ERROR).to_string()
}
impl Content {
pub fn get_notifications() -> Notifications {
let content = Asset::get("en.yaml").unwrap();
let s = std::str::from_utf8(content.data.as_ref()).unwrap();
let content = YamlLoader::load_from_str(s).unwrap();
let content = &content[0];
Notifications {
you_are_in_draw: NotificationContent {
title: get_as_string_or_error_message(
&content["testAndEarn"]["notifications"]["youAreInDraw"]["title"],
),
body: get_as_string_or_error_message(
&content["testAndEarn"]["notifications"]["youAreInDraw"]["body"],
),
},
take_part: NotificationContent {
title: get_as_string_or_error_message(
&content["testAndEarn"]["notifications"]["takePart"]["title"],
),
body: get_as_string_or_error_message(
&content["testAndEarn"]["notifications"]["takePart"]["body"],
),
},
}
}
}
@@ -0,0 +1,3 @@
pub mod api_client;
pub mod assets;
pub mod test_and_earn;
@@ -0,0 +1,156 @@
use crate::error::BackendError;
use crate::operations::export::get_identity_key;
use crate::operations::growth::api_client::{
ClaimPartial, ClientIdPartial, DrawEntry, DrawEntryPartial, DrawWithWordOfTheDay,
GrowthApiClient, Registration, Winner,
};
use crate::State;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tauri::api::notification::Notification;
use tauri::Manager;
use tokio::sync::RwLock;
async fn get_client_id(
state: &tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ClientIdPartial, BackendError> {
let keypair = get_identity_key(state).await?;
let client_id = keypair.public_key().to_base58_string();
let client_id_signature = keypair
.private_key()
.sign(client_id.as_bytes())
.to_base58_string();
Ok(ClientIdPartial {
client_id,
client_id_signature,
})
}
#[tauri::command]
pub async fn growth_tne_get_client_id(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ClientIdPartial, BackendError> {
get_client_id(&state).await
}
#[tauri::command]
pub async fn growth_tne_take_part(
app_handle: tauri::AppHandle,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Registration, BackendError> {
let notifications = super::assets::Content::get_notifications();
let client_id = get_client_id(&state).await?;
let registration = GrowthApiClient::registrations()
.register(&client_id)
.await?;
log::info!("<<< Test&Earn: registration details: {:?}", registration);
if let Err(e) = Notification::new(&app_handle.config().tauri.bundle.identifier)
.title(notifications.take_part.title)
.body(notifications.take_part.body)
.show()
{
log::error!("Could not show notification. Error = {:?}", e);
}
Ok(registration)
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Draws {
pub current: Option<DrawWithWordOfTheDay>,
pub next: Option<DrawWithWordOfTheDay>,
pub draws: Vec<DrawEntry>,
}
#[tauri::command]
pub async fn growth_tne_get_draws(client_details: ClientIdPartial) -> Result<Draws, BackendError> {
let draws_api = GrowthApiClient::daily_draws();
let current = draws_api.current().await.ok();
let next = draws_api.next().await.ok();
let draws = draws_api.entries(&client_details).await?;
Ok(Draws {
current,
next,
draws,
})
}
#[tauri::command]
pub async fn growth_tne_enter_draw(
client_details: ClientIdPartial,
draw_id: String,
) -> Result<DrawEntry, BackendError> {
Ok(GrowthApiClient::daily_draws()
.enter(&DrawEntryPartial {
draw_id,
client_id: client_details.client_id,
client_id_signature: client_details.client_id_signature,
})
.await?)
}
#[tauri::command]
pub async fn growth_tne_submit_wallet_address(
client_details: ClientIdPartial,
draw_id: String,
wallet_address: String,
registration_id: String,
) -> Result<Winner, BackendError> {
Ok(GrowthApiClient::daily_draws()
.claim(&ClaimPartial {
draw_id,
client_id: client_details.client_id,
client_id_signature: client_details.client_id_signature,
wallet_address,
registration_id,
})
.await?)
}
#[tauri::command]
pub async fn growth_tne_ping(client_details: ClientIdPartial) -> Result<(), BackendError> {
log::info!("Test&Earn is sending a ping...");
Ok(GrowthApiClient::registrations()
.ping(&client_details)
.await?)
}
#[tauri::command]
pub async fn growth_tne_toggle_window(
app_handle: tauri::AppHandle,
window_title: Option<String>,
) -> Result<(), BackendError> {
if let Some(window) = app_handle.windows().get("growth") {
log::info!("Closing growth window...");
if let Err(e) = window.close() {
log::error!("Unable to close growth window: {:?}", e);
}
return Ok(());
}
log::info!("Creating growth window...");
match tauri::WindowBuilder::new(
&app_handle,
"growth",
tauri::WindowUrl::App("growth.html".into()),
)
.title(window_title.unwrap_or_else(|| "NymConnect Test&Earn".to_string()))
.build()
{
Ok(window) => {
if let Err(e) = window.set_focus() {
log::error!("Unable to focus growth window: {:?}", e);
}
Ok(())
}
Err(e) => {
log::error!("Unable to create growth window: {:?}", e);
Err(BackendError::NewWindowError)
}
}
}
@@ -0,0 +1,30 @@
use crate::error::BackendError;
use tauri::Manager;
#[tauri::command]
pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), BackendError> {
if let Some(current_log_window) = app_handle.windows().get("log") {
log::info!("Closing log window...");
if let Err(e) = current_log_window.close() {
log::error!("Unable to close log window: {:?}", e);
}
return Ok(());
}
log::info!("Creating log window...");
match tauri::WindowBuilder::new(&app_handle, "log", tauri::WindowUrl::App("log.html".into()))
.title("Nym Connect Logs")
.build()
{
Ok(window) => {
if let Err(e) = window.set_focus() {
log::error!("Unable to focus log window: {:?}", e);
}
Ok(())
}
Err(e) => {
log::error!("Unable to create log window: {:?}", e);
Err(BackendError::NewWindowError)
}
}
}
@@ -0,0 +1,2 @@
pub mod log;
pub mod storage;
@@ -0,0 +1,20 @@
use crate::error::BackendError;
use serde::Serialize;
use tauri::Manager;
#[derive(Debug, Serialize, Clone)]
struct ClearStorageEvent {
kind: String,
}
#[tauri::command]
pub fn help_clear_storage(app_handle: tauri::AppHandle) -> Result<(), BackendError> {
log::info!("Sending event to clear local storage...");
let event = ClearStorageEvent {
kind: "local_storage".to_string(),
};
app_handle.emit_all("help://clear-storage", event)?;
Ok(())
}
@@ -1,4 +1,6 @@
pub mod connection;
pub mod directory;
pub mod export;
pub mod growth;
pub mod help;
pub mod window;
+4 -4
View File
@@ -64,16 +64,16 @@ impl State {
&self.service_provider
}
pub fn set_service_provider(&mut self, provider: String) {
self.service_provider = Some(provider);
pub fn set_service_provider(&mut self, provider: Option<String>) {
self.service_provider = provider;
}
pub fn get_gateway(&self) -> &Option<String> {
&self.gateway
}
pub fn set_gateway(&mut self, gateway: String) {
self.gateway = Some(gateway);
pub fn set_gateway(&mut self, gateway: Option<String>) {
self.gateway = gateway;
}
/// The effective config id is the static config id appended with the id of the gateway
+5 -2
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.1.4"
"version": "1.1.5"
},
"build": {
"distDir": "../dist",
@@ -59,13 +59,16 @@
"startDragging": true,
"close": true,
"minimize": true
},
"notification": {
"all": true
}
},
"windows": [
{
"title": "NymConnect",
"width": 240,
"height": 575,
"height": 675,
"resizable": false,
"decorations": false,
"transparent": true
+12 -1
View File
@@ -1,15 +1,27 @@
import React, { useEffect } from 'react';
import { DateTime } from 'luxon';
import { forage } from '@tauri-apps/tauri-forage';
import { ConnectionStatusKind } from './types';
import { useClientContext } from './context/main';
import { DefaultLayout } from './layouts/DefaultLayout';
import { ConnectedLayout } from './layouts/ConnectedLayout';
import { HelpGuideLayout } from './layouts/HelpGuideLayout';
import { useTauriEvents } from './utils';
export const App: React.FC = () => {
const context = useClientContext();
const [busy, setBusy] = React.useState<boolean>();
const [showInfoModal, setShowInfoModal] = React.useState(false);
useTauriEvents('help://clear-storage', (_event) => {
console.log('About to clear local storage...');
// clear local storage
try {
forage.clear()();
console.log('Local storage cleared');
} catch (e) {
console.error('Failed to clear local storage', e);
}
});
const handleConnectClick = React.useCallback(async () => {
const currentStatus = context.connectionStatus;
@@ -49,7 +61,6 @@ export const App: React.FC = () => {
busy={busy}
onConnectClick={handleConnectClick}
services={context.services}
onServiceProviderChange={context.setServiceProvider}
/>
);
}
+6 -8
View File
@@ -1,17 +1,15 @@
import { Typography } from '@mui/material';
import React from 'react';
import { Typography, Box } from '@mui/material';
import { useClientContext } from 'src/context/main';
export const AppVersion = () => {
const { appVersion } = useClientContext();
return (
<Typography
fontSize="small"
textAlign="center"
sx={{ color: 'grey.600', position: 'absolute', bottom: 10, width: '100%' }}
>
Version {appVersion}
</Typography>
<Box sx={{ display: 'grid', width: '100%', justifyContent: 'center' }}>
<Box fontSize="small" sx={{ mb: 4, color: 'grey.600' }}>
Version {appVersion}
</Box>
</Box>
);
};
@@ -8,10 +8,9 @@ export const AppWindowFrame: React.FC = ({ children }) => (
sx={{
display: 'grid',
borderRadius: '12px',
// screen height is 540px - These should add up to that
gridTemplateRows: '40px 1fr 30px',
bgcolor: 'nym.background.dark',
height: '100vh',
overflowY: 'hidden',
}}
>
<CustomTitleBar />
+13 -12
View File
@@ -1,8 +1,9 @@
import React from 'react';
import { Box, CircularProgress, Typography } from '@mui/material';
import { Box, CircularProgress, Divider, Stack, Tooltip, Typography } from '@mui/material';
import { DateTime } from 'luxon';
import { ConnectionStatusKind } from '../types';
import { ServiceProvider } from '../types/directory';
import { ServiceProviderInfo } from './ServiceProviderInfo';
const FONT_SIZE = '10px';
const FONT_WEIGHT = '600';
@@ -14,8 +15,8 @@ const ConnectionStatusContent: React.FC<{
switch (status) {
case ConnectionStatusKind.connected:
return (
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} textAlign="center">
Connected
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize="14px">
Connected to
</Typography>
);
case ConnectionStatusKind.disconnecting:
@@ -44,7 +45,7 @@ const ConnectionStatusContent: React.FC<{
ml={1}
textTransform="uppercase"
textAlign="center"
fontSize="10px"
fontSize={FONT_SIZE}
sx={{ wordSpacing: 3, letterSpacing: 2 }}
>
You are not protected
@@ -59,7 +60,7 @@ export const ConnectionStatus: React.FC<{
status: ConnectionStatusKind;
connectedSince?: DateTime;
serviceProvider?: ServiceProvider;
}> = ({ status, connectedSince, serviceProvider }) => {
}> = ({ status, serviceProvider }) => {
const color =
status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting
? '#21D072'
@@ -70,13 +71,13 @@ export const ConnectionStatus: React.FC<{
<Box color={color} fontSize={FONT_SIZE} sx={{ mb: 1 }}>
<ConnectionStatusContent status={status} />
</Box>
<Box>
{serviceProvider && (
<Typography fontSize={12} textAlign="center">
To {serviceProvider.description}
</Typography>
)}
</Box>
{serviceProvider ? (
<Tooltip title={<ServiceProviderInfo serviceProvider={serviceProvider} />}>
<Box sx={{ cursor: 'pointer' }}>
{serviceProvider && <Typography>{serviceProvider.description}</Typography>}
</Box>
</Tooltip>
) : null}
</>
);
};
@@ -14,9 +14,13 @@ export const CopyToClipboard = ({
}) => {
const [copied, setCopied] = useState(false);
const handleCopy = async (text: string) => {
const handleCopy = async (textToCopy: string) => {
try {
await clipboard.writeText(text);
if (clipboard) {
await clipboard.writeText(textToCopy);
} else {
await navigator.clipboard.writeText(textToCopy);
}
setCopied(true);
} catch (e) {
console.log(`failed to copy: ${e}`);
@@ -0,0 +1,17 @@
import React from 'react';
import { FallbackProps } from 'react-error-boundary';
import { Alert, AlertTitle, Button } from '@mui/material';
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (
<div>
<Alert severity="error" data-testid="error-message">
<AlertTitle>{error.name}</AlertTitle>
{error.message}
</Alert>
<Alert severity="error" data-testid="stack-trace">
<AlertTitle>Stack trace</AlertTitle>
{error.stack}
</Alert>
<Button onClick={resetErrorBoundary}>Back to safety</Button>
</div>
);
@@ -0,0 +1,83 @@
import React from 'react';
import { Badge, Box, Button, Tooltip } from '@mui/material';
import MonetizationOnIcon from '@mui/icons-material/MonetizationOn';
import { invoke } from '@tauri-apps/api';
import Content from './content/en.yaml';
import { useClientContext } from '../../context/main';
import { useTestAndEarnContext } from './context/TestAndEarnContext';
import { NymShipyardTheme } from '../../theme';
import { ConnectionStatusKind } from '../../types';
export const Wrapper: React.FC<{ disabled: boolean }> = ({ disabled, children }) => {
if (disabled) {
return (
<Badge badgeContent="!" color="warning">
<Tooltip arrow title={disabled ? Content.testAndEarn.mainWindow.button.popup.disconnected : undefined}>
<div>{children}</div>
</Tooltip>
</Badge>
);
}
return <>{children}</>;
};
export const TestAndEarnButtonArea: React.FC = () => {
const clientContext = useClientContext();
const context = useTestAndEarnContext();
const disabled = clientContext.connectionStatus !== ConnectionStatusKind.connected;
const pinger = React.useRef<NodeJS.Timer | null>();
const doPing = async () => {
if (context.clientDetails) {
try {
await invoke('growth_tne_ping', { clientDetails: context.clientDetails });
} catch (_e) {
// console.error('Failed to ping: ', e);
}
}
};
React.useEffect(() => {
(async () => {
if (!disabled) {
// sleep a little until the SOCKS5 proxy connects
setTimeout(() => {
doPing();
}, 1000 * 10);
// update every 15 mins
pinger.current = setInterval(doPing, 1000 * 60 * 15);
} else if (pinger.current) {
clearInterval(pinger.current);
pinger.current = null;
}
})();
}, [disabled, context.clientDetails]);
const handleClick = async () => {
if (!disabled) {
await context.toggleGrowthWindow(Content.testAndEarn.popupWindow.title);
}
};
return (
<NymShipyardTheme>
<Box justifyContent="center" display="grid">
<Wrapper disabled={disabled}>
<Button
color={disabled ? 'secondary' : undefined}
variant="contained"
size="small"
endIcon={<MonetizationOnIcon />}
sx={{ width: '150px', mb: 4, opacity: disabled ? 0.4 : undefined }}
onClick={handleClick}
>
{context.registration
? Content.testAndEarn.mainWindow.button.text.entered
: Content.testAndEarn.mainWindow.button.text.default}
</Button>
</Wrapper>
</Box>
</NymShipyardTheme>
);
};
@@ -0,0 +1,94 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { DateTime, Duration } from 'luxon';
import {
TestAndEarnCurrentDraw,
TestAndEarnCurrentDrawEntered,
TestAndEarnCurrentDrawFuture,
} from './TestAndEarnCurrentDraw';
import { NymShipyardTheme } from '../../theme';
import { DrawEntryStatus } from './context/types';
import { testMarkdown } from './context/mocks/TestAndEarnContext';
export default {
title: 'Growth/TestAndEarn/Components/Cards/Current Draw',
component: TestAndEarnCurrentDraw,
} as ComponentMeta<typeof TestAndEarnCurrentDraw>;
export const Valid = () => (
<NymShipyardTheme>
<TestAndEarnCurrentDraw
draw={{
id: '1',
start_utc: DateTime.now().toISO(),
end_utc: DateTime.now()
.plus(Duration.fromMillis(1000 * 3600))
.toISO(),
last_modified: DateTime.now().toISO(),
word_of_the_day: 'words words words',
}}
/>
</NymShipyardTheme>
);
export const EnteredMalformedDraw = () => (
<NymShipyardTheme>
<TestAndEarnCurrentDrawEntered
draw={{
id: '1',
start_utc: DateTime.now().toISO(),
end_utc: DateTime.now()
.plus(Duration.fromMillis(1000 * 3600))
.toISO(),
last_modified: DateTime.now().toISO(),
word_of_the_day: undefined,
entry: {
draw_id: '1',
status: DrawEntryStatus.pending,
id: 'aaaa',
timestamp: DateTime.now().toISO(),
},
}}
/>
</NymShipyardTheme>
);
export const EnteredDraw = () => (
<NymShipyardTheme>
<TestAndEarnCurrentDrawEntered
draw={{
id: '1',
start_utc: DateTime.now().toISO(),
end_utc: DateTime.now()
.plus(Duration.fromMillis(1000 * 3600))
.toISO(),
last_modified: DateTime.now().toISO(),
word_of_the_day: testMarkdown,
entry: {
draw_id: '1',
status: DrawEntryStatus.pending,
id: 'aaaa',
timestamp: DateTime.now().toISO(),
},
}}
/>
</NymShipyardTheme>
);
export const Future = () => (
<NymShipyardTheme>
<TestAndEarnCurrentDrawFuture
draw={{
id: '1',
start_utc: DateTime.now()
.plus(Duration.fromMillis(1000 * 3600))
.toISO(),
end_utc: DateTime.now()
.plus(Duration.fromMillis(1000 * 3600 * 2))
.toISO(),
last_modified: DateTime.now().toISO(),
word_of_the_day: 'words words words',
}}
/>
</NymShipyardTheme>
);
@@ -0,0 +1,192 @@
import React from 'react';
import LoadingButton from '@mui/lab/LoadingButton';
import { Alert, AlertTitle, Box, Card, CardContent, CardMedia, Link, Typography } from '@mui/material';
import { SxProps } from '@mui/system';
import { DateTime } from 'luxon';
import ReactMarkdown from 'react-markdown';
import assetAnimation from './content/assets/matrix.webp';
import { CopyToClipboard } from '../CopyToClipboard';
import { useTestAndEarnContext } from './context/TestAndEarnContext';
import { DrawEntryStatus, DrawWithWordOfTheDay } from './context/types';
import Content from './content/en.yaml';
export const TestAndEarnCurrentDrawFuture: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
const startsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.start_utc), [draw?.start_utc]);
const startsIn = React.useMemo(() => {
if (draw && startsUtc) {
return startsUtc.toRelative();
}
return undefined;
}, [draw?.start_utc]);
if (!draw || !startsUtc) {
return null;
}
return (
<Card sx={{ mb: 2 }} elevation={10}>
<CardContent>
<h3>
{Content.testAndEarn.draw.next.header} {startsIn}
</h3>
<p>on {startsUtc.toLocaleString(DateTime.DATETIME_FULL)}</p>
</CardContent>
</Card>
);
};
export const TestAndEarnCurrentDrawEnter: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
const context = useTestAndEarnContext();
const [busy, setBusy] = React.useState(false);
const [error, setError] = React.useState<string>();
const handleClick = async () => {
if (!draw) {
setError('No draw selected');
return;
}
setBusy(true);
try {
await context.enterDraw(draw.id);
} catch (e) {
const message = `${e}`;
console.error('Could not enter draw', message);
setError(message);
}
setBusy(false);
};
return (
<Box display="flex" flexDirection="column" alignItems="center" py={3} px={2} mx={6} my={2}>
<Typography mb={4}>Complete todays task for the chance to earn 1000 NYMs.</Typography>
<LoadingButton variant="contained" size="large" loading={busy} onClick={handleClick}>
Start task
</LoadingButton>
{error && (
<Box mt={2}>
<Alert variant="filled" severity="error">
<AlertTitle>Oh no! Something went wrong.</AlertTitle>
{error}
</Alert>
</Box>
)}
</Box>
);
};
export const TestAndEarnCurrentDrawEntered: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
if (!draw || !draw.entry) {
return null;
}
if (!draw.word_of_the_day) {
return (
<Alert severity="error" variant="filled">
<AlertTitle>Oh no! Something is wrong</AlertTitle>
Someone configured the wrong instructions for the task, you will not be able to see it until this is fixed
</Alert>
);
}
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
sx={{ background: 'rgba(255,255,255,0.1)' }}
py={4}
mx={6}
my={2}
borderRadius={2}
>
<Box py={2} px={4} color="warning.light">
<ReactMarkdown>{draw.word_of_the_day}</ReactMarkdown>
</Box>
<Typography>{Content.testAndEarn.task.afterText}</Typography>
<Typography mt={2} fontFamily="monospace" fontWeight="bold" color="warning.main">
{draw.entry.id} <CopyToClipboard iconButton light text={draw.entry.id} />
</Typography>
<Typography mt={2}>{Content.testAndEarn.task.beforeSocials}</Typography>
<Typography mt={2} mx={1} textAlign="center">
<Typography component="span" color="info.light" fontWeight="bold">
Twitter
</Typography>{' '}
- remember to
<Typography component="span" color="info.light">
@nymproject
</Typography>{' '}
and use the hashtag{' '}
<Typography component="span" color="info.light">
#PrivacyLovesCompany
</Typography>
</Typography>
<Typography mt={2}>or</Typography>
<Typography textAlign="center" fontWeight="bold">
Nym{' '}
<Link target="_blank" href="https://t.me/nymchan" color="info.light">
Telegram channel
</Link>
</Typography>
</Box>
);
};
export const TestAndEarnCurrentDraw: React.FC<{
draw?: DrawWithWordOfTheDay;
sx?: SxProps;
}> = ({ draw, sx }) => {
const [trigger, setTrigger] = React.useState(DateTime.now().toISO());
const endsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.end_utc), [draw?.end_utc]);
const closesIn = React.useMemo(() => {
if (draw && endsUtc) {
return endsUtc.toRelative();
}
return undefined;
}, [trigger, endsUtc]);
React.useEffect(() => {
const timer = setInterval(() => setTrigger(DateTime.now().toISO()), 1000 * 3600 * 15);
return () => clearInterval(timer);
}, []);
if (draw && closesIn && endsUtc) {
return (
<Card sx={{ mb: 2, ...sx }} elevation={10}>
<CardContent>
<h3>
{"Today's task ends "}
{closesIn}
<Typography sx={{ opacity: 0.5 }}>
{endsUtc.weekdayLong} {endsUtc.toLocaleString(DateTime.DATETIME_FULL)}
</Typography>
</h3>
{!draw.entry && <TestAndEarnCurrentDrawEnter draw={draw} />}
{draw.entry && <TestAndEarnCurrentDrawEntered draw={draw} />}
</CardContent>
<CardMedia component="img" height="150" image={assetAnimation} alt="lottery" />
</Card>
);
}
return null;
};
export const TestAndEarnCurrentDrawWithState: React.FC<{
sx?: SxProps;
}> = ({ sx }) => {
const context = useTestAndEarnContext();
if (
context.draws?.current?.entry?.status === DrawEntryStatus.winner ||
context.draws?.current?.entry?.status === DrawEntryStatus.claimed ||
context.draws?.current?.entry?.status === DrawEntryStatus.noWin
) {
return null;
}
if (!context.draws?.current) {
return <TestAndEarnCurrentDrawFuture draw={context.draws?.next} />;
}
return <TestAndEarnCurrentDraw sx={sx} draw={context.draws.current} />;
};
@@ -0,0 +1,18 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { NymShipyardTheme } from 'src/theme';
import { TestAndEarnDraws } from './TestAndEarnDraws';
import { MockTestAndEarnProvider_RegisteredWithAllDraws } from './context/mocks/TestAndEarnContext';
export default {
title: 'Growth/TestAndEarn/Components/Cards/Draws',
component: TestAndEarnDraws,
} as ComponentMeta<typeof TestAndEarnDraws>;
export const Draws = () => (
<NymShipyardTheme>
<MockTestAndEarnProvider_RegisteredWithAllDraws>
<TestAndEarnDraws />
</MockTestAndEarnProvider_RegisteredWithAllDraws>
</NymShipyardTheme>
);
@@ -0,0 +1,195 @@
import React from 'react';
import LoadingButton from '@mui/lab/LoadingButton';
import {
Alert,
AlertTitle,
Button,
Card,
CardContent,
Chip,
Dialog,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
Tooltip,
Typography,
} from '@mui/material';
import { SxProps } from '@mui/system';
import { DateTime } from 'luxon';
import { useTestAndEarnContext } from './context/TestAndEarnContext';
import { DrawEntry, DrawEntryStatus } from './context/types';
import { CopyToClipboard } from '../CopyToClipboard';
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
import Content from './content/en.yaml';
const statusToText = (status: string): string => Content.testAndEarn.status.chip[status] || '-';
const statusToColor = (status: string): 'info' | 'success' | 'warning' | undefined => {
switch (status) {
case DrawEntryStatus.pending:
return 'info';
case DrawEntryStatus.winner:
return 'warning';
case DrawEntryStatus.claimed:
return 'success';
default:
return undefined;
}
};
const StatusText: React.FC<{ entry: DrawEntry }> = ({ entry }) => {
const context = useTestAndEarnContext();
const [busy, setBusy] = React.useState(false);
const [error, setError] = React.useState<string>();
const [showWalletCapture, setShowWalletCapture] = React.useState(false);
const clear = () => {
setShowWalletCapture(false);
setError(undefined);
setBusy(false);
};
const handleStartWalletCapture = async () => {
setBusy(true);
setShowWalletCapture(true);
};
const cancelEndWalletCapture = async () => {
setBusy(false);
setShowWalletCapture(false);
};
const handleEndWalletCapture = async () => {
setBusy(true);
setShowWalletCapture(false);
if (!context.walletAddress) {
setError('Wallet address is not set');
return;
}
if (!entry.draw_id) {
setError('Task id is not set');
return;
}
try {
await context.claim(entry.draw_id, context.walletAddress);
} catch (e) {
const message = `${e}`;
console.error('Failed to submit claim');
setError(message);
}
setBusy(false);
};
if (error) {
return (
<Alert severity="error" variant="filled">
<AlertTitle>Oh no! Failed to submit claim</AlertTitle>
{error}
<Button variant="contained" color="secondary" size="small" onClick={() => clear()} sx={{ mx: 2 }}>
Try again!
</Button>
</Alert>
);
}
if (showWalletCapture) {
return (
<Dialog open fullWidth onBackdropClick={cancelEndWalletCapture}>
<TestAndEarnEnterWalletAddress onSubmit={handleEndWalletCapture} />
</Dialog>
);
}
switch (entry.status) {
case DrawEntryStatus.pending:
return <>{Content.testAndEarn.status.text.Pending}</>;
case DrawEntryStatus.winner:
return (
<>
{Content.testAndEarn.status.text.Winner}
<LoadingButton
loading={busy}
disabled={busy}
variant="contained"
sx={{ ml: 2 }}
size="small"
onClick={handleStartWalletCapture}
>
{Content.testAndEarn.winner.claimButton.text}
</LoadingButton>
</>
);
case DrawEntryStatus.claimed:
return <>{Content.testAndEarn.status.text.Claimed}</>;
case DrawEntryStatus.noWin:
return <>{Content.testAndEarn.status.text.NoWin}</>;
default:
return null;
}
};
export const TestAndEarnDraws: React.FC<{
sx?: SxProps;
}> = ({ sx }) => {
const context = useTestAndEarnContext();
const draws = React.useMemo<DrawEntry[]>(
() =>
(context.draws?.draws || []).map((item) => ({
...item,
timestamp: DateTime.fromISO(item.timestamp).toLocaleString(DateTime.DATETIME_FULL),
})),
[context.draws?.draws],
);
if (!context.draws) {
return null;
}
return (
<Card sx={{ mb: 2 }}>
<CardContent>
<Typography mb={2}>Here is a history of the tasks you have completed:</Typography>
<TableContainer>
<Table>
<TableBody>
{draws.map((entry) => (
<TableRow key={entry.draw_id}>
<TableCell width="150px">{entry.timestamp}</TableCell>
<TableCell width="150px">
<Tooltip arrow title={`Task Id: ${entry.draw_id}`}>
<Chip label={statusToText(entry.status)} color={statusToColor(entry.status)} />
</Tooltip>
</TableCell>
<TableCell>
<StatusText entry={entry} />
</TableCell>
<TableCell>
{entry.id} <CopyToClipboard iconButton light text={entry.id} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
);
};
export const TestAndEarnDrawsWithState: React.FC<{
sx?: SxProps;
}> = ({ sx }) => {
const context = useTestAndEarnContext();
const drawCount = context.draws?.draws?.length || 0;
if (drawCount < 1) {
return null;
}
return <TestAndEarnDraws sx={sx} />;
};
@@ -0,0 +1,41 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Box } from '@mui/material';
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
import { NymShipyardTheme } from '../../theme';
export default {
title: 'Growth/TestAndEarn/Components/Enter wallet address',
component: TestAndEarnEnterWalletAddress,
} as ComponentMeta<typeof TestAndEarnEnterWalletAddress>;
export const Empty = () => (
<NymShipyardTheme>
<TestAndEarnContextProvider>
<Box minWidth="25vw" maxWidth={500}>
<TestAndEarnEnterWalletAddress sx={{ width: '100%' }} />
</Box>
</TestAndEarnContextProvider>
</NymShipyardTheme>
);
export const ErrorValue = () => (
<NymShipyardTheme>
<TestAndEarnContextProvider>
<Box minWidth="25vw" maxWidth={500}>
<TestAndEarnEnterWalletAddress initialValue="this is a bad value" sx={{ width: '100%' }} />
</Box>
</TestAndEarnContextProvider>
</NymShipyardTheme>
);
export const ValidValue = () => (
<NymShipyardTheme>
<TestAndEarnContextProvider>
<Box minWidth="25vw" maxWidth={500}>
<TestAndEarnEnterWalletAddress initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" sx={{ width: '100%' }} />
</Box>
</TestAndEarnContextProvider>
</NymShipyardTheme>
);
@@ -0,0 +1,37 @@
import React from 'react';
import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField';
import { SxProps } from '@mui/system';
import { Paper, Stack, Button, Box } from '@mui/material';
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
import { useTestAndEarnContext } from './context/TestAndEarnContext';
export const TestAndEarnEnterWalletAddress: React.FC<{
initialValue?: string;
placeholder?: string;
onSubmit?: () => Promise<void> | void;
sx?: SxProps;
}> = ({ initialValue, placeholder, onSubmit, sx }) => {
const context = useTestAndEarnContext();
const [isAddressValid, setAddressIsValid] = React.useState(false);
return (
<Paper sx={{ py: 4, px: 2 }}>
<Stack spacing={4}>
<Box>
<WalletAddressFormField
label="Wallet address"
initialValue={initialValue}
placeholder={placeholder || 'Please enter your wallet address'}
onChanged={context.setWalletAddress}
onValidate={setAddressIsValid}
sx={{ width: '80%' }}
/>
</Box>
<Box>
<Button variant="contained" endIcon={<ArrowCircleRightIcon />} disabled={!isAddressValid} onClick={onSubmit}>
Submit
</Button>
</Box>
</Stack>
</Paper>
);
};
@@ -0,0 +1,13 @@
import React from 'react';
import { Box, Button } from '@mui/material';
export const TestAndEarnError: React.FC<{ error?: string }> = ({ error = 'An error has occurred' }) => (
<Box>
<Box mb={4} fontWeight="bold">
{error}
</Box>
<Button variant="outlined" color="secondary">
Send us an error report
</Button>
</Box>
);
@@ -0,0 +1,167 @@
/* eslint-disable react/jsx-pascal-case */
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Alert, Box } from '@mui/material';
import { NymShipyardTheme } from 'src/theme';
import { TestAndEarnPopup, TestAndEarnPopupContent } from './TestAndEarnPopup';
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
import { MockProvider } from '../../context/mocks/main';
import { ConnectionStatusKind } from '../../types';
import { TestAndEarnCurrentDraw } from './TestAndEarnCurrentDraw';
import { TestAndEarnWinner } from './TestAndEarnWinner';
import { TestAndEarnDraws } from './TestAndEarnDraws';
import { TestAndEarnWinnerWalletAddress } from './TestAndEarnWinnerWalletAddress';
import {
MockTestAndEarnProvider_NotRegistered,
MockTestAndEarnProvider_Registered,
MockTestAndEarnProvider_RegisteredAndError,
MockTestAndEarnProvider_RegisteredWithDraws,
MockTestAndEarnProvider_RegisteredWithDrawsAndEntry,
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner,
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner,
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed,
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet,
MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent,
} from './context/mocks/TestAndEarnContext';
export default {
title: 'Growth/TestAndEarn/Content/Popup',
component: TestAndEarnPopupContent,
} as ComponentMeta<typeof TestAndEarnPopupContent>;
const MacOSWindow: React.FC<{ width?: string | number; height?: string | number; title?: string }> = ({
title,
width,
height,
children,
}) => (
<Box sx={{ border: '1px solid #EEEEEE', width, height }}>
<Box sx={{ background: '#EEEEEE', display: 'grid', gridTemplateColumns: 'auto auto', gridTemplateRows: 'auto' }}>
<Box ml={1}>
<svg width="52px" height="12px" viewBox="0 0 52 12" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="Components" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="macOS" transform="translate(-600.000000, -220.000000)">
<g id="Group" transform="translate(600.000000, 220.000000)" strokeWidth="0.5">
<g id="Traffic-Lights">
<circle id="Traffic-Light---Zoom" stroke="#1BAC2C" fill="#2ACB42" cx="46" cy="6" r="5.75" />
<circle id="Traffic-Light---Minimise" stroke="#DFA023" fill="#FFC12F" cx="26" cy="6" r="5.75" />
<circle id="Traffic-Light---Close" stroke="#E24640" fill="#FF6157" cx="6" cy="6" r="5.75" />
</g>
</g>
</g>
</g>
</svg>
</Box>
<Box
sx={{
alignSelf: 'center',
color: '#000000',
opacity: 0.848675272,
fontSize: 13,
}}
>
{title || 'Window title'}
</Box>
</Box>
<Box sx={{ overflowY: 'scroll', height: 'calc(100% - 25px)' }}>{children}</Box>
</Box>
);
const Wrapper: React.FC<{ text: React.ReactNode }> = ({ text }) => (
<NymShipyardTheme>
<Alert severity="info" sx={{ mb: 4 }}>
{text}
</Alert>
<MacOSWindow width={700} height={600} title="Test&Earn">
<TestAndEarnPopup />
</MacOSWindow>
</NymShipyardTheme>
);
export const Stage0 = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_NotRegistered>
<Wrapper text="The user sees this content when they have not joined Test&Earn." />
</MockTestAndEarnProvider_NotRegistered>
</MockProvider>
);
export const Stage1EnterDraw = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_RegisteredWithDraws>
<Wrapper text="The user has signed up and can see the next draw and choose the enter." />
</MockTestAndEarnProvider_RegisteredWithDraws>
</MockProvider>
);
export const Stage2GetTask = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
<Wrapper text="The user has entered a draw and can view the word of the day if they missed the popup notification." />
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
</MockProvider>
);
export const Stage3Winner = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
<Wrapper text="The user has won and can claim their prize." />
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
</MockProvider>
);
export const Stage3NoPrize = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
<Wrapper text="The user has not won. A winner has been announced." />
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
</MockProvider>
);
export const Stage4EnterWalletAddress = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
<Wrapper text="The user is a winner, claims their prize and enters their wallet address." />
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
</MockProvider>
);
export const Stage5ClaimedPrize = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
<Wrapper text="The user is a winner and has claimed their prize." />
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
</MockProvider>
);
export const Stage6DrawsFinished = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
<MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
<Wrapper text="There are no more draws. The user can see their entries and prizes they have claimed." />
</MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
</MockProvider>
);
export const Connecting = () => (
<MockProvider connectionStatus={ConnectionStatusKind.connecting}>
<TestAndEarnContextProvider>
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown while connecting." />
</TestAndEarnContextProvider>
</MockProvider>
);
export const Disconnected = () => (
<MockProvider connectionStatus={ConnectionStatusKind.disconnected}>
<TestAndEarnContextProvider>
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown when not connected." />
</TestAndEarnContextProvider>
</MockProvider>
);
export const Error = () => (
<MockProvider>
<MockTestAndEarnProvider_RegisteredAndError>
<Wrapper text="The user see this with details about errors. They can submit an error report." />
</MockTestAndEarnProvider_RegisteredAndError>
</MockProvider>
);
@@ -0,0 +1,118 @@
import React from 'react';
import { Box, CircularProgress, LinearProgress, Stack, Typography } from '@mui/material';
import { useClientContext } from '../../context/main';
import ErrorContent from './content/TestAndEarn/Error.mdx';
import ContentStep0 from './content/TestAndEarn/Stage0_intro.mdx';
import ContentNotAvailable from './content/TestAndEarnNotAvaialble.mdx';
import { ConnectionStatusKind } from '../../types';
import { useTestAndEarnContext } from './context/TestAndEarnContext';
import { TestAndEarnWinnerWithState } from './TestAndEarnWinner';
import { TestAndEarnCurrentDrawWithState } from './TestAndEarnCurrentDraw';
import { TestAndEarnDrawsWithState } from './TestAndEarnDraws';
enum Stages {
mustRegister = 'mustRegister',
registered = 'registered',
}
export const TestAndEarnPopupContent: React.FC<{
stage?: string;
connectionStatus?: ConnectionStatusKind;
error?: string;
}> = ({ connectionStatus, error, stage = Stages.mustRegister }) => {
if (error) {
return (
<Box p={4}>
<ErrorContent error={error} />
</Box>
);
}
if (!connectionStatus || connectionStatus === ConnectionStatusKind.disconnected) {
return (
<Box p={4}>
<ContentNotAvailable />
</Box>
);
}
if (connectionStatus === ConnectionStatusKind.connecting || connectionStatus === ConnectionStatusKind.disconnecting) {
return (
<Box p={4} justifyContent="center" alignItems="center" display="flex">
<CircularProgress />
<Typography ml={3}>Please wait...</Typography>
</Box>
);
}
switch (stage) {
case Stages.mustRegister:
return (
<Box p={4}>
<ContentStep0 />
</Box>
);
case Stages.registered:
return (
<Box p={4}>
<TestAndEarnWinnerWithState />
<TestAndEarnCurrentDrawWithState />
<TestAndEarnDrawsWithState />
</Box>
);
default:
return (
<Box p={4}>
<Stack direction="row" spacing={2} display="flex" alignItems="center">
<CircularProgress />
<Box>Waiting for task information...</Box>
</Stack>
</Box>
);
}
};
export const TestAndEarnPopup: React.FC = () => {
const clientContext = useClientContext();
const context = useTestAndEarnContext();
React.useEffect(() => {
if (clientContext.connectionStatus === ConnectionStatusKind.connected) {
context.refresh();
}
}, [clientContext.connectionStatus]);
const stage = React.useMemo<Stages>(() => {
if (context.registration) {
return Stages.registered;
}
return Stages.mustRegister;
}, [context.registration?.id]);
React.useEffect(() => {
const interval = setInterval(context.refresh, 1000 * 60 * 5);
return () => clearInterval(interval);
}, []);
if (!context.loadedOnce && clientContext.connectionStatus === ConnectionStatusKind.connected) {
const message = 'Waiting for data to be transferred over the mixnet...';
return (
<Box p={4}>
<Stack direction="row" spacing={2} display="flex" alignItems="center">
<CircularProgress />
<Box>{message}</Box>
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
</Stack>
</Box>
);
}
return (
<>
{context.loading && <LinearProgress />}
{/* <Button onClick={context.refresh}>Refresh</Button> */}
<TestAndEarnPopupContent connectionStatus={clientContext.connectionStatus} stage={stage} error={context.error} />
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
</>
);
};
@@ -0,0 +1,75 @@
import React from 'react';
import { Alert, AlertTitle, Box, Checkbox, Link, Stack } from '@mui/material';
import LoadingButton from '@mui/lab/LoadingButton';
import { SxProps } from '@mui/system';
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
import { invoke } from '@tauri-apps/api';
import { useTestAndEarnContext } from './context/TestAndEarnContext';
import { ClientId, Registration } from './context/types';
export const TestAndEarnTakePart: React.FC<{
websiteLinkUrl: string;
websiteLinkText: string;
content: string;
sx?: SxProps;
}> = ({ content, websiteLinkText, websiteLinkUrl, sx }) => {
const [agree, setAgree] = React.useState(false);
const [busy, setBusy] = React.useState(false);
const [error, setError] = React.useState<string>();
const context = useTestAndEarnContext();
const handleNext = async () => {
try {
setBusy(true);
if (context.clientDetails) {
const registration: Registration = await invoke('growth_tne_take_part');
console.log('Registration: ', { registration });
await context.setAndStoreRegistration(registration);
if (registration) {
console.log('Registered...');
} else {
setError('Failed to get registration details');
}
} else {
setError('Failed to get client details');
}
} catch (e) {
const message = `${e}`;
console.error('An error occurred', message);
setError(message);
setBusy(false); // the busy state only resets on errors, for success stats, the context will navigate the window away
}
};
return (
<>
<Stack direction="row" spacing={6} alignItems="center" sx={sx}>
<Stack alignItems="center" direction="row">
<Checkbox onChange={(_event, checked) => setAgree(checked)} />
<Box color="primary.light" fontWeight="bold">
{content}
</Box>
</Stack>
<Box>
<Link href={websiteLinkUrl} target="_blank" color="secondary" sx={{ opacity: 0.5 }}>
{websiteLinkText}
</Link>
</Box>
<LoadingButton
loading={busy}
disabled={!agree || busy}
variant="contained"
sx={{ justifySelf: 'end' }}
endIcon={<ArrowCircleRightIcon />}
onClick={handleNext}
>
Next
</LoadingButton>
</Stack>
{error && (
<Alert severity="error" variant="filled">
<AlertTitle>Oh no! Something went wrong</AlertTitle>
{error}
</Alert>
)}
</>
);
};
@@ -0,0 +1,18 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { NymShipyardTheme } from 'src/theme';
import { TestAndEarnWinner, TestAndEarnWinnerWithState } from './TestAndEarnWinner';
import { MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner } from './context/mocks/TestAndEarnContext';
export default {
title: 'Growth/TestAndEarn/Components/Cards/Winner',
component: TestAndEarnWinner,
} as ComponentMeta<typeof TestAndEarnWinner>;
export const Winner = () => (
<NymShipyardTheme>
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
<TestAndEarnWinnerWithState />
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
</NymShipyardTheme>
);
@@ -0,0 +1,114 @@
import React from 'react';
import { Alert, AlertTitle, Button, Card, CardContent, CardMedia, Dialog, Typography } from '@mui/material';
import { SxProps } from '@mui/system';
import LoadingButton from '@mui/lab/LoadingButton';
import winner from './content/assets/winner.webp';
import { useTestAndEarnContext } from './context/TestAndEarnContext';
import { DrawEntry, DrawEntryStatus } from './context/types';
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
import Content from './content/en.yaml';
export const TestAndEarnWinner: React.FC<{
sx?: SxProps;
entry?: DrawEntry;
}> = ({ sx, entry }) => {
const context = useTestAndEarnContext();
const [busy, setBusy] = React.useState(false);
const [error, setError] = React.useState<string>();
const [showWalletCapture, setShowWalletCapture] = React.useState(false);
const clear = () => {
setShowWalletCapture(false);
setError(undefined);
setBusy(false);
};
const handleStartWalletCapture = async () => {
setBusy(true);
setShowWalletCapture(true);
};
const cancelEndWalletCapture = async () => {
setBusy(false);
setShowWalletCapture(false);
};
const handleEndWalletCapture = async () => {
setBusy(true);
setShowWalletCapture(false);
if (!context.walletAddress) {
setError('Wallet address is not set');
return;
}
if (!entry?.draw_id) {
setError('Draw id is not set');
return;
}
try {
await context.claim(entry.draw_id, context.walletAddress);
} catch (e) {
const message = `${e}`;
console.error('Failed to submit claim', entry.draw_id, context.walletAddress);
setError(message);
}
setBusy(false);
};
return (
<>
{showWalletCapture && (
<Dialog open fullWidth onBackdropClick={cancelEndWalletCapture}>
<TestAndEarnEnterWalletAddress onSubmit={handleEndWalletCapture} />
</Dialog>
)}
<Card sx={{ mb: 2 }}>
<CardMedia component="img" height="165" image={winner} alt="winner" />
<CardContent>
<Typography color="warning.main" fontSize={20} fontWeight="bold">
{Content.testAndEarn.winner.card.header}
</Typography>
<Typography mt={2}>
{entry && (
<>
{Content.testAndEarn.winner.card.text} {entry.draw_id}.
</>
)}
<LoadingButton
loading={busy}
variant="contained"
sx={{ ml: 2, my: 2 }}
size="small"
onClick={handleStartWalletCapture}
>
{Content.testAndEarn.winner.claimButton.text}
</LoadingButton>
</Typography>
{error && (
<Alert severity="error" variant="filled">
<AlertTitle>Oh no! Failed to submit claim</AlertTitle>
{error}
<Button variant="contained" color="secondary" size="small" onClick={() => clear()} sx={{ mx: 2 }}>
Try again!
</Button>
</Alert>
)}
</CardContent>
</Card>
</>
);
};
export const TestAndEarnWinnerWithState: React.FC<{
sx?: SxProps;
}> = ({ sx }) => {
const context = useTestAndEarnContext();
if (context.draws?.current?.entry?.status === DrawEntryStatus.winner) {
return <TestAndEarnWinner sx={sx} entry={context.draws.current.entry} />;
}
// when the user does not have any unclaimed prizes, don't render anything
return null;
};
@@ -0,0 +1,12 @@
import React from 'react';
import { Box, Button, Card, CardContent, CardMedia, Typography } from '@mui/material';
import { SxProps } from '@mui/system';
import Content from './content/TestAndEarn/WinnerEntersWalletAddress.mdx';
export const TestAndEarnWinnerWalletAddress: React.FC<{
sx?: SxProps;
}> = ({ sx }) => (
<Box>
<Content />
</Box>
);
@@ -0,0 +1,17 @@
import { Alert, AlertTitle, Link } from '@mui/material';
import { TestAndEarnError } from '../../TestAndEarnError';
<Alert severity="error" sx={{ mb: 4 }}>
<AlertTitle>Oh no! Something went wrong</AlertTitle>
Sorry about that. Here is some more information about the error that occurred:
<TestAndEarnError/>
Any error reports that you send us will contain information about your client, the gateway you're using and your IP address.
We need this information to make Nym better for everyone.
</Alert>
If you'd like more information about Test&Earn please look on the <Link href="http://shipyard.nymtech.net/test-and-win">Shipyard website</Link>.
@@ -0,0 +1,25 @@
import { Card, CardContent, Link, Typography } from '@mui/material';
import { TestAndEarnTakePart } from '../../TestAndEarnTakePart';
### Test privacy & Earn tokens!
<Typography color="primary.light" component="span">
Help us stress test the Nym privacy system and have the chance to earn 1000 NYMs per day!
</Typography>
All you need to do is:
1. Make sure you're running NymConnect and it is connected.
2. Note your reference number.
3. NymConnect will ping you a task every day.
Complete the task, post it on Twitter <Typography color="primary.light" component="span">#PrivacyLovesCompany</Typography> or <Link target="_blank" href="https://t.me/nymchan">Telegram</Link> along with your reference number!
Thank you for being part of the Nym community and helping to build a flourishing and free digital society. #PrivacyLovesCompany and we love you!
<Card>
<CardContent sx={{ py: 2 }}>
<TestAndEarnTakePart content={"I want to take part"} websiteLinkText={"Terms and conditions"}
websiteLinkUrl={"https://shipyard.nymtech.net/test-and-win"} sx={{ py: 2 }} />
</CardContent>
</Card>
@@ -0,0 +1,9 @@
import { TestAndEarnEnterWalletAddress } from '../../TestAndEarnEnterWalletAddress';
### 🎉 Congratulations! One more thing...
We need one more thing from you, and that is your wallet address:
<TestAndEarnEnterWalletAddress/>
Once you've submitting your wallet address over the mixnet, we will be in touch to arrange sending your tokens to you.

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