Compare commits

...

149 Commits

Author SHA1 Message Date
fmtabbara 5cc1060693 add app notifications 2021-10-04 20:49:04 +01:00
fmtabbara 2417e0565f start purchase page 2021-09-29 21:32:24 +01:00
fmtabbara ea3f70a2ac initial ui 2021-09-27 15:41:07 +01:00
Fouad 80664b911f Merge pull request #783 from nymtech/tauri-wallet-frontend
tauri wallet front-end
2021-09-23 20:59:27 +01:00
fmtabbara 46149012bd PR updates 2021-09-23 17:42:34 +01:00
fmtabbara de601c319a PR updates 2021-09-23 17:10:22 +01:00
max 7318de23f2 added info on nym-wallet (tauri) in readme 2021-09-22 18:07:29 +02:00
max 56e07753ea added minimal readme for wallet 2021-09-22 14:44:22 +02:00
fmtabbara 21b008fae9 use wrapper functions for send and delegate
form updates

update working

finish bonding and unbonding setup

funds allocation check when bonding/sending/delegating

update title
2021-09-21 13:09:57 +01:00
fmtabbara 27a202cbe8 integrate admin functions
updates

integrate admin form update function
2021-09-17 15:37:57 +01:00
Drazen Urch 085538582b Admin functions, reorganize code 2021-09-17 15:37:46 +01:00
Drazen Urch f66ea05929 Admin functions, reorganize code 2021-09-17 10:43:26 +02:00
fmtabbara 052c7188ec merge develop 2021-09-17 09:38:19 +01:00
Drazen Urch f6c316eea9 fix typo 2021-09-16 18:02:15 +02:00
Drazen Urch f33defc645 Squashed commit of the following:
commit 976dd7aae2
Author: Drazen Urch <drazen@urch.eu>
Date:   Wed Sep 15 17:28:49 2021 +0200

    Add block_height method to Delegation (#778)

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 0d21f4e937
Merge: e84af4f6 1403449a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Wed Sep 15 12:41:29 2021 +0100

    Merge pull request #776 from nymtech/update/re-enable-bonding

    re-enable bonding

commit 1403449ad5
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 14 16:00:21 2021 +0100

    enable bonding

commit e84af4f601
Author: Drazen Urch <drazen@urch.eu>
Date:   Tue Sep 14 15:15:26 2021 +0200

    Migrate legacy delegation data (#771)

    * Skip ReadOnlyBucket deserialization errors

    * empty migration

    * clippy

    * cargo schema

    * Drop invalid delegation data

    * Dont drop old data

    * Add todo

    * Unify on type param

    * gateways are different

    * cargo fmt

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 26b032c15c
Merge: e1ddaff0 cba36253
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Sep 14 10:09:14 2021 +0100

    Merge pull request #774 from nymtech/feature/explorer-api-delegations

    Explorer-api: add API resource to show the delegations for each mix node

commit cba3625394
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Mon Sep 13 10:33:41 2021 +0100

    explorer-api: add API resource to show the delegations for each mix node

commit e1ddaff04d
Merge: 0b9c03ca 66ab5de4
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 17:17:14 2021 +0100

    Merge pull request #772 from nymtech/update/disable-bonding

    add app alert

commit 66ab5de442
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 16:16:58 2021 +0100

    add app alert

commit 0b9c03ca90
Author: Dave Hrycyszyn <futurechimp@users.noreply.github.com>
Date:   Fri Sep 10 11:23:21 2021 +0300

    Adding deps for building the Tauri wallet under Ubuntu (#770)

commit c9dce0c1da
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 9 11:21:45 2021 +0200

    Feature/consumable bandwidth (#766)

    * Set actual value for bandwidth

    Also put it as a public attribute, such that it can be actively used
    by the credential consumer

    * Switch from sending Attribute structs to sending the actual attribute bytes over the wire

    * Add atomic bandwidth value to gateway

    * Consume bandwidth based on the mix packet size

    * Use Bandwidth struct for specific functionality

    * Move bandwidth code outside the dependency path of wasm client

    * Use u64 instead of AtomicU64, as the handling is not parallel

commit e00e77db15
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Wed Sep 8 15:07:24 2021 +0200

    Feature/bond blockstamp (#760)

    * Add block_height to MixNode/GatewayBond

    * Reward based on blockstamp of bonded node or of delegation

    * Add specific tests

    * Add migration code

    * Apply doc nit

commit 1074449f91
Merge: 08276e6e 9a3d824a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 7 23:45:10 2021 +0100

    Merge pull request #767 from nymtech/update/remove-app-alert

    remove alert

commit 08276e6e42
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Sep 7 16:33:30 2021 +0200

    Remove migration code (#759)

commit 9a3d824a4a
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Mon Sep 6 20:39:24 2021 +0100

    remove alert

commit 2789ee8f18
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Sep 3 15:30:45 2021 +0300

    Update coconut-rs and use hash_to_scalar from there (#765)

    Failed tests are due to some nightly issue, not related to this PR

commit a7ba643c35
Merge: 28be53ee c42f3c68
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 3 09:14:50 2021 +0100

    Merge pull request #762 from nymtech/feature/app-alert

    add app alert banner

commit 28be53eefb
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 2 18:26:40 2021 +0300

    Add block_height in the Delegation structure as well (#757)

commit 219c45a352
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Sep 2 15:48:29 2021 +0100

    Updated cosmos-sdk (#761)

    * Updated cosmos-sdk

    * Re-exposing more things

commit 1a3b83752e
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Sep 2 15:45:56 2021 +0100

    Bump next from 11.1.0 to 11.1.1 in /wallet-web (#758)

    Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1.
    - [Release notes](https://github.com/vercel/next.js/releases)
    - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
    - [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1)

    ---
    updated-dependencies:
    - dependency-name: next
      dependency-type: direct:production
    ...

    Signed-off-by: dependabot[bot] <support@github.com>

    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit c42f3c6844
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Thu Sep 2 12:29:47 2021 +0100

    add app alert banner

commit a5d3ba3900
Merge: 92e13a5d cdf0d443
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 15:59:11 2021 +0100

    Merge pull request #755 from nymtech/bugfix/explorer-api-ping

    Explorer API: port test now split out address resolution and add units tests

commit cdf0d44341
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 14:37:28 2021 +0100

    explorer-api: turned down logging from `error` to `warn`

commit 92f976a45d
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 11:34:21 2021 +0100

    explorer-api: sanitize hostname before running checks to avoid leading or trailing spaces that are known to exist in the current test net

commit 2bc858cde3
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 09:32:26 2021 +0100

    explorer-api: port test: split out address resolution and add units tests

commit 92e13a5d00
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 31 14:51:15 2021 +0300

    Feature/add blockstamp (#756)

    * Add RawDelegationData

    * Fix current tests for the new stored data

    * Added migration commit. Will be reverted after doing the migration

    * New tests for block height

    * Use current blockstamp instead of 24h old one

    * Put _alot_ of migration stuff in the migrate function scope

commit 122f5d9f2e
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Mon Aug 30 10:27:20 2021 +0300

    Feature/cred after handshake (#745)

    * Call perform_initial_authentication instead of register in clients

    * Refactor the register/authenticate functions a bit

    * Introduce Bandwidth request type

    * Add encryption layer to cred

    * Remove cred pass and check from handshake

    * Replaced unreachable!  with error

    * Changed decrypt_tagged signature to not take mutable ownership of data

    * Put handle_bandwidth work inside a function

    * Add check before unwrap

    * Remove unnecessary async

    * Decouple bandwidth credential from authentication

    * Use new_error for ServerResponse:Error

    * Send a fresh IV each time the BandwidthCredential request is sent

    * Remove unwrap of bincode::serialize

    * Add comment regarding Bandwidth response

    * Remove _mut from naming

    * Leave Debug trait alone, as the initial error doesn't reproduce anymore

    * Pass iv as Vec<u8> instead of base58 string

    * Renamed AuthenticationIV to IV, as it is now used for more the just authentication

    * Did some IV refactorization

commit 982ee0266c
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Aug 27 16:02:34 2021 +0300

    Feature/get own delegations (#748)

    * Introduce reverse delegation bucket

    * Add client command

    * Fix clippy error

    * Added tests in queries

    * Add tests in transactions

    * Migration code. Will be reverted after it's called on testnet

    * Replace unwrap with expect

    * Move some test code in the right file...

    ... to remove unnecessary auxiliary function.

    * Reduce the scope to migration auxiliary functions

    * Rename everything from [node]reverse to reverse[node]

    * Fix fmt

commit 5f42a9bd05
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:18 2021 +0100

    NetworkMonitorBuilder - starting the monitor after rocket has launched (#754)

    * NetworkMonitorBuilder - starting the monitor after rocket has launched

    * Removed unused import

commit 1811df9ddb
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:10 2021 +0100

    Enabled validators api argument (#753)

commit 6bdfe7f895
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Aug 26 11:21:01 2021 +0100

    Correctly bounding nominator of uptime calculation (#752)

commit c6b286a1db
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Wed Aug 25 14:50:57 2021 +0100

    Fixed argument parsing for ipv6 'good' topology (#751)

commit b3568a26f5
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 24 11:25:05 2021 +0300

    Revert "Migration commit, will be reverted after the testnet contract is updated" (#749)

    This reverts commit 38d868bcce.

commit 15ae0f521e
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Mon Aug 23 10:26:51 2021 +0100

    Feature/more reliable uptime calculation (#747)

    * New database table holding monitor run info

    * SQL interface for new table

    * Updated uptime calculation to instead rely on number of monitor test runs

commit 2923d4b872
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Aug 19 22:03:07 2021 +0300

    Update template toml key (#746)
2021-09-16 17:36:24 +02:00
Drazen Urch 424230c3bb Squashed commit of the following:
commit cddd9e8e4c
Merge: 40fbdff0 976dd7aa
Author: Drazen Urch <durch@users.noreply.guthub.com>
Date:   Thu Sep 16 17:27:27 2021 +0200

    Merge branch 'develop' into tauri-wallet

commit 976dd7aae2
Author: Drazen Urch <drazen@urch.eu>
Date:   Wed Sep 15 17:28:49 2021 +0200

    Add block_height method to Delegation (#778)

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 0d21f4e937
Merge: e84af4f6 1403449a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Wed Sep 15 12:41:29 2021 +0100

    Merge pull request #776 from nymtech/update/re-enable-bonding

    re-enable bonding

commit 1403449ad5
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 14 16:00:21 2021 +0100

    enable bonding

commit e84af4f601
Author: Drazen Urch <drazen@urch.eu>
Date:   Tue Sep 14 15:15:26 2021 +0200

    Migrate legacy delegation data (#771)

    * Skip ReadOnlyBucket deserialization errors

    * empty migration

    * clippy

    * cargo schema

    * Drop invalid delegation data

    * Dont drop old data

    * Add todo

    * Unify on type param

    * gateways are different

    * cargo fmt

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 26b032c15c
Merge: e1ddaff0 cba36253
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Sep 14 10:09:14 2021 +0100

    Merge pull request #774 from nymtech/feature/explorer-api-delegations

    Explorer-api: add API resource to show the delegations for each mix node

commit cba3625394
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Mon Sep 13 10:33:41 2021 +0100

    explorer-api: add API resource to show the delegations for each mix node

commit e1ddaff04d
Merge: 0b9c03ca 66ab5de4
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 17:17:14 2021 +0100

    Merge pull request #772 from nymtech/update/disable-bonding

    add app alert

commit 66ab5de442
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 16:16:58 2021 +0100

    add app alert

commit 0b9c03ca90
Author: Dave Hrycyszyn <futurechimp@users.noreply.github.com>
Date:   Fri Sep 10 11:23:21 2021 +0300

    Adding deps for building the Tauri wallet under Ubuntu (#770)

commit c9dce0c1da
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 9 11:21:45 2021 +0200

    Feature/consumable bandwidth (#766)

    * Set actual value for bandwidth

    Also put it as a public attribute, such that it can be actively used
    by the credential consumer

    * Switch from sending Attribute structs to sending the actual attribute bytes over the wire

    * Add atomic bandwidth value to gateway

    * Consume bandwidth based on the mix packet size

    * Use Bandwidth struct for specific functionality

    * Move bandwidth code outside the dependency path of wasm client

    * Use u64 instead of AtomicU64, as the handling is not parallel

commit e00e77db15
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Wed Sep 8 15:07:24 2021 +0200

    Feature/bond blockstamp (#760)

    * Add block_height to MixNode/GatewayBond

    * Reward based on blockstamp of bonded node or of delegation

    * Add specific tests

    * Add migration code

    * Apply doc nit

commit 1074449f91
Merge: 08276e6e 9a3d824a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 7 23:45:10 2021 +0100

    Merge pull request #767 from nymtech/update/remove-app-alert

    remove alert

commit 08276e6e42
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Sep 7 16:33:30 2021 +0200

    Remove migration code (#759)

commit 9a3d824a4a
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Mon Sep 6 20:39:24 2021 +0100

    remove alert

commit 2789ee8f18
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Sep 3 15:30:45 2021 +0300

    Update coconut-rs and use hash_to_scalar from there (#765)

    Failed tests are due to some nightly issue, not related to this PR

commit a7ba643c35
Merge: 28be53ee c42f3c68
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 3 09:14:50 2021 +0100

    Merge pull request #762 from nymtech/feature/app-alert

    add app alert banner

commit 28be53eefb
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 2 18:26:40 2021 +0300

    Add block_height in the Delegation structure as well (#757)

commit 219c45a352
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Sep 2 15:48:29 2021 +0100

    Updated cosmos-sdk (#761)

    * Updated cosmos-sdk

    * Re-exposing more things

commit 1a3b83752e
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Sep 2 15:45:56 2021 +0100

    Bump next from 11.1.0 to 11.1.1 in /wallet-web (#758)

    Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1.
    - [Release notes](https://github.com/vercel/next.js/releases)
    - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
    - [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1)

    ---
    updated-dependencies:
    - dependency-name: next
      dependency-type: direct:production
    ...

    Signed-off-by: dependabot[bot] <support@github.com>

    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit c42f3c6844
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Thu Sep 2 12:29:47 2021 +0100

    add app alert banner

commit a5d3ba3900
Merge: 92e13a5d cdf0d443
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 15:59:11 2021 +0100

    Merge pull request #755 from nymtech/bugfix/explorer-api-ping

    Explorer API: port test now split out address resolution and add units tests

commit cdf0d44341
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 14:37:28 2021 +0100

    explorer-api: turned down logging from `error` to `warn`

commit 92f976a45d
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 11:34:21 2021 +0100

    explorer-api: sanitize hostname before running checks to avoid leading or trailing spaces that are known to exist in the current test net

commit 2bc858cde3
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 09:32:26 2021 +0100

    explorer-api: port test: split out address resolution and add units tests

commit 92e13a5d00
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 31 14:51:15 2021 +0300

    Feature/add blockstamp (#756)

    * Add RawDelegationData

    * Fix current tests for the new stored data

    * Added migration commit. Will be reverted after doing the migration

    * New tests for block height

    * Use current blockstamp instead of 24h old one

    * Put _alot_ of migration stuff in the migrate function scope

commit 122f5d9f2e
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Mon Aug 30 10:27:20 2021 +0300

    Feature/cred after handshake (#745)

    * Call perform_initial_authentication instead of register in clients

    * Refactor the register/authenticate functions a bit

    * Introduce Bandwidth request type

    * Add encryption layer to cred

    * Remove cred pass and check from handshake

    * Replaced unreachable!  with error

    * Changed decrypt_tagged signature to not take mutable ownership of data

    * Put handle_bandwidth work inside a function

    * Add check before unwrap

    * Remove unnecessary async

    * Decouple bandwidth credential from authentication

    * Use new_error for ServerResponse:Error

    * Send a fresh IV each time the BandwidthCredential request is sent

    * Remove unwrap of bincode::serialize

    * Add comment regarding Bandwidth response

    * Remove _mut from naming

    * Leave Debug trait alone, as the initial error doesn't reproduce anymore

    * Pass iv as Vec<u8> instead of base58 string

    * Renamed AuthenticationIV to IV, as it is now used for more the just authentication

    * Did some IV refactorization

commit 982ee0266c
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Aug 27 16:02:34 2021 +0300

    Feature/get own delegations (#748)

    * Introduce reverse delegation bucket

    * Add client command

    * Fix clippy error

    * Added tests in queries

    * Add tests in transactions

    * Migration code. Will be reverted after it's called on testnet

    * Replace unwrap with expect

    * Move some test code in the right file...

    ... to remove unnecessary auxiliary function.

    * Reduce the scope to migration auxiliary functions

    * Rename everything from [node]reverse to reverse[node]

    * Fix fmt

commit 5f42a9bd05
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:18 2021 +0100

    NetworkMonitorBuilder - starting the monitor after rocket has launched (#754)

    * NetworkMonitorBuilder - starting the monitor after rocket has launched

    * Removed unused import

commit 1811df9ddb
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:10 2021 +0100

    Enabled validators api argument (#753)

commit 6bdfe7f895
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Aug 26 11:21:01 2021 +0100

    Correctly bounding nominator of uptime calculation (#752)

commit c6b286a1db
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Wed Aug 25 14:50:57 2021 +0100

    Fixed argument parsing for ipv6 'good' topology (#751)

commit b3568a26f5
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 24 11:25:05 2021 +0300

    Revert "Migration commit, will be reverted after the testnet contract is updated" (#749)

    This reverts commit 38d868bcce.

commit 15ae0f521e
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Mon Aug 23 10:26:51 2021 +0100

    Feature/more reliable uptime calculation (#747)

    * New database table holding monitor run info

    * SQL interface for new table

    * Updated uptime calculation to instead rely on number of monitor test runs

commit 2923d4b872
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Aug 19 22:03:07 2021 +0300

    Update template toml key (#746)
2021-09-16 17:33:20 +02:00
Drazen Urch cddd9e8e4c Merge branch 'develop' into tauri-wallet 2021-09-16 17:27:27 +02:00
fmtabbara 63bc42cd5f update title 2021-09-16 10:01:53 +01:00
fmtabbara 9dc8ba7b77 funds allocation check when bonding/sending/delegating 2021-09-15 21:39:18 +01:00
fmtabbara e130131f16 finish bonding and unbonding setup 2021-09-15 20:37:24 +01:00
fmtabbara 64a5b4b593 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-14 12:08:38 +01:00
Drazen Urch 40fbdff05a Coin and denom tests 2021-09-14 10:29:31 +02:00
fmtabbara 9934b9bc8a update working 2021-09-13 15:20:56 +01:00
fmtabbara 6954b383a7 form updates 2021-09-13 14:26:21 +01:00
fmtabbara 4b5276e816 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-13 13:32:24 +01:00
Drazen Urch 0278bd2c26 Fix coin to cosmwasm coin 2021-09-13 14:31:33 +02:00
fmtabbara 56a9527497 use wrapper functions for send and delegate 2021-09-13 13:30:05 +01:00
Drazen Urch a601c28a20 Return useful info from bond/unbond 2021-09-13 13:07:57 +02:00
Drazen Urch 005dd7513b Merge branch 'tauri-wallet-frontend' into tauri-wallet 2021-09-13 12:58:01 +02:00
Drazen Urch 3ebdc55847 extract Coin and Denom 2021-09-13 12:50:26 +02:00
fmtabbara aaf5d18692 update admin component 2021-09-13 11:19:28 +01:00
fmtabbara bcbec1f3e6 admin form style updates 2021-09-12 21:49:39 +01:00
fmtabbara c95005265d button updates 2021-09-12 01:26:57 +01:00
fmtabbara b299c9e4b5 add qrcode to receive page 2021-09-12 01:03:21 +01:00
fmtabbara bacbd3dfce fix bug 2021-09-12 00:40:53 +01:00
fmtabbara 55561fe1f7 add admin page 2021-09-12 00:26:15 +01:00
fmtabbara 0d01500b87 remove login deets 2021-09-11 00:35:14 +01:00
fmtabbara 74e34567b4 form updates 2021-09-11 00:32:54 +01:00
fmtabbara b77025bfd5 form updates 2021-09-11 00:10:03 +01:00
fmtabbara 4d831efcd6 more fees work 2021-09-10 22:33:00 +01:00
fmtabbara d227d20385 add gas fees to bond form 2021-09-10 17:31:04 +01:00
fmtabbara fb2d3bae3c use new getFee api 2021-09-10 14:17:24 +01:00
fmtabbara 967d74eb19 add nvmrc file 2021-09-10 11:15:52 +01:00
fmtabbara 228df278d9 update tsconfig 2021-09-10 11:13:18 +01:00
fmtabbara 37da23ab1c set up global error handling 2021-09-10 09:56:04 +01:00
fmtabbara 0c9cf7b5d9 finish create account 2021-09-10 09:16:45 +01:00
fmtabbara 262149078c rust updates 2021-09-09 19:21:38 +01:00
fmtabbara 08fd1c1b47 make getBalance global 2021-09-09 19:00:30 +01:00
Drazen Urch 3bcbb90127 fix docs 2021-09-09 17:47:11 +02:00
Drazen Urch 8156ed0029 get_fee, create_new_account 2021-09-09 17:43:56 +02:00
Drazen Urch 265f7a7c2e Dedicated workspace, random_mnemonic, gas_limits 2021-09-09 13:21:27 +02:00
fmtabbara 5de8c9d1ed style updates 2021-09-09 11:35:49 +01:00
fmtabbara 7658eec9b9 create account page 2021-09-09 09:16:38 +01:00
fmtabbara 99c49581df Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-08 20:49:27 +01:00
Drazen Urch 926689da1d Merge branch 'tauri-wallet' of https://github.com/nymtech/nym into tauri-wallet 2021-09-08 11:54:49 +02:00
Drazen Urch 714171f4e5 redundant into 2021-09-08 11:54:36 +02:00
fmtabbara 7a8ad1387d add eslint file 2021-09-08 10:30:51 +01:00
fmtabbara bd72426280 start adding gas fees 2021-09-07 23:44:02 +01:00
fmtabbara 48d0f31d7e send updates 2021-09-07 23:13:54 +01:00
fmtabbara 4522c18a55 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-07 21:53:38 +01:00
fmtabbara b9389f1235 send updates 2021-09-07 21:53:05 +01:00
durch b40be179ae [ci skip] Generate TS types 2021-09-07 12:37:24 +00:00
Drazen Urch 32ef9e019e More verbose send response 2021-09-07 14:27:49 +02:00
fmtabbara 45a56a7088 update logo 2021-09-06 20:27:15 +01:00
Drazen Urch d50afd6113 Add coconut creds 2021-09-06 20:00:47 +02:00
fmtabbara 3205b1e0e6 start work on send form 2021-09-06 16:42:56 +01:00
Drazen Urch 53ea8486f8 get_gas_price, get_gas_limits 2021-09-06 17:36:46 +02:00
Drazen Urch 43ababf8d4 Rework client errors 2021-09-06 17:31:08 +02:00
fmtabbara 5461574023 small refactors 2021-09-06 13:23:20 +01:00
fmtabbara 01d2df7bb7 set up form validation for undelegation 2021-09-06 11:12:39 +01:00
fmtabbara 7cff72757b set up delgate request 2021-09-05 00:07:46 +01:00
fmtabbara 5bcbf45d16 onerror onsuccess added to bond form 2021-09-04 22:52:02 +01:00
fmtabbara 4295d75e0f format bonding data pre request 2021-09-03 16:53:32 +01:00
fmtabbara 018666a614 layout updates 2021-09-03 16:37:51 +01:00
fmtabbara 15048524a7 layout updates 2021-09-03 16:25:15 +01:00
fmtabbara 2b792945cc update type roots 2021-09-03 15:07:38 +01:00
fmtabbara b6193270a6 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-03 15:02:56 +01:00
fmtabbara 0b4a8fe657 update type roots 2021-09-03 15:02:09 +01:00
Drazen Urch 2997337948 printable balance should be major 2021-09-03 15:29:20 +02:00
Drazen Urch e1bea43ff4 Autogenerate types (#763)
* Generate TS types on push

* run shell

* fix typo

* pango

* libpango-dev

* hopefully all deps are in now :)

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-09-03 15:22:48 +02:00
fmtabbara 27ef28da8d Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-03 12:13:28 +01:00
fmtabbara 42bf139ebb update args collection function 2021-09-03 12:12:52 +01:00
fmtabbara 352862e4d0 delegation form validation 2021-09-03 11:42:31 +01:00
fmtabbara 9f9ab010d8 integrate rust generated types 2021-09-03 11:39:17 +01:00
Drazen Urch 87a0b05d1a Send 2021-09-03 07:43:29 +02:00
Drazen Urch dbf82da9b6 Delegation docs 2021-09-03 07:36:02 +02:00
Drazen Urch 01a4305883 Delegation 2021-09-03 07:33:11 +02:00
Drazen Urch 704b3241ee Gateway bonding 2021-09-03 06:58:57 +02:00
fmtabbara 4513edae46 rust updates 2021-09-02 21:38:19 +01:00
Drazen Urch f020b21106 node unbond 2021-09-02 21:08:19 +02:00
Drazen Urch 2587906473 TS exports, rework internals 2021-09-02 20:57:10 +02:00
fmtabbara 5d3f1b86e8 delegate form validation 2021-09-02 17:29:03 +01:00
fmtabbara 5a17e48581 finish bond form validation 2021-09-02 16:11:54 +01:00
fmtabbara 41356f2181 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust updates
2021-09-02 10:10:55 +01:00
fmtabbara bd7d788741 more validation work 2021-09-02 10:10:18 +01:00
Drazen Urch 63107c2bca WIP node bonding 2021-09-02 10:27:49 +02:00
fmtabbara f043639ad2 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust update
2021-09-01 19:48:22 +01:00
fmtabbara 1d2a1b2635 start form validation 2021-09-01 19:47:41 +01:00
Drazen Urch 6fb15fff8b printable_balance 2021-09-01 16:33:29 +02:00
fmtabbara c69d7fa46f remove unused imports 2021-09-01 15:12:50 +01:00
fmtabbara c2ee02a2cf use printable balance 2021-09-01 15:12:25 +01:00
fmtabbara 6f5f0c00b5 use printable balance 2021-09-01 15:11:24 +01:00
fmtabbara 12707c5f1e merge rust updates 2021-09-01 14:59:27 +01:00
Drazen Urch 0daf89eeb4 Utility native <> printable functions 2021-09-01 15:14:50 +02:00
fmtabbara 2aa7fa0426 move balance function call to global state 2021-09-01 13:41:42 +01:00
fmtabbara f5aa6e2db2 style updates 2021-09-01 09:39:22 +01:00
fmtabbara ee5d1c3419 fix nav styling 2021-08-31 20:57:21 +01:00
fmtabbara e68c261162 fix ts error 2021-08-31 20:48:30 +01:00
fmtabbara c7fe4cd24e update imports 2021-08-31 15:28:46 +01:00
fmtabbara 1c690fc3d0 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust updates
2021-08-31 15:24:35 +01:00
Drazen Urch f56edb825a Fix client address 2021-08-31 15:22:10 +02:00
fmtabbara d8e6a5fb2e make typescript happy 2021-08-31 12:55:13 +01:00
fmtabbara b95893bb02 update balance page 2021-08-31 11:20:12 +01:00
fmtabbara 6bdff701b4 merge backend updates 2021-08-31 11:05:51 +01:00
fmtabbara ccdb5911c6 update routing and use new sign in function 2021-08-31 09:35:24 +01:00
Drazen Urch eff38c8466 some cleanup, get blockchain stuff working 2021-08-31 07:38:25 +02:00
Drazen Urch 5f4fabc0b8 Add internal documentation scaffolding 2021-08-27 15:17:15 +02:00
fmtabbara e2dd1cc9ae Merge branch 'tauri-wallet' into tauri-wallet-frontend
connect with mnemonic update
2021-08-25 14:51:29 +01:00
Drazen Urch 42f75028bc Resolve state deadlock 2021-08-25 15:44:08 +02:00
fmtabbara c7e622f284 begin sign in rust integration 2021-08-25 14:06:37 +01:00
fmtabbara 248da351c6 use color palette object 2021-08-25 14:05:00 +01:00
fmtabbara fe1c8a3b08 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust api updates
2021-08-25 10:36:11 +01:00
fmtabbara 84af923389 update receive to use state value 2021-08-25 10:35:42 +01:00
Drazen Urch 1bc17abbaa Add connect_with_mnemonic and get_balance tauri functions 2021-08-25 11:18:16 +02:00
fmtabbara b8c2735520 cargo lock update 2021-08-24 16:12:08 +01:00
fmtabbara 67fd4367ef global theme update 2021-08-24 16:08:55 +01:00
fmtabbara 4540d2c447 Merge branch 'feature/tauri-wallet-ui' into tauri-wallet
merge with rust updates
2021-08-24 12:56:59 +01:00
fmtabbara 4ad25114c3 update nav cards 2021-08-24 12:46:34 +01:00
fmtabbara 3ce7888c07 address and balance cards 2021-08-24 11:59:10 +01:00
Drazen Urch ce75769703 Bootstrap nymd client with config 2021-08-24 12:54:49 +02:00
fmtabbara 66210658cb add favicon 2021-08-24 10:35:54 +01:00
fmtabbara 728da763b3 fix padding issue 2021-08-23 22:54:30 +01:00
fmtabbara 3da08ee33c style updates 2021-08-23 22:49:39 +01:00
fmtabbara 55977185fd add unbond and undelegte pages 2021-08-23 22:04:26 +01:00
fmtabbara 1c8c0a47bc receive page adjust margin 2021-08-23 21:34:14 +01:00
fmtabbara c161b2fe71 send wizard updates 2021-08-23 21:32:29 +01:00
fmtabbara eb91c1180d update balance page 2021-08-23 17:23:25 +01:00
fmtabbara 13e357637b style updates 2021-08-23 17:18:07 +01:00
fmtabbara 54c4bdb7d2 send wizard update 2021-08-23 17:12:40 +01:00
fmtabbara 9b5f50913f add send wizard 2021-08-23 14:56:52 +01:00
fmtabbara 7cfa35b542 style updates 2021-08-23 11:18:13 +01:00
fmtabbara 581a6b0a6f update nav bar 2021-08-23 10:38:56 +01:00
fmtabbara 85f23eb3d1 create delegation page 2021-08-23 10:38:42 +01:00
fmtabbara 17100fa7da create node type selector component 2021-08-23 10:38:17 +01:00
fmtabbara 24af0c94bf add bond form 2021-08-20 22:26:14 +01:00
fmtabbara 8ec9e3121f side bar updates 2021-08-20 17:35:59 +01:00
fmtabbara 498dbb8ba3 add balance page 2021-08-20 12:24:04 +01:00
fmtabbara 7345bd8148 update navigation style 2021-08-20 11:51:27 +01:00
fmtabbara b405d2e4af set up tauri app with React frontend 2021-08-20 09:03:21 +01:00
135 changed files with 19490 additions and 509 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ name: Continuous integration
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
ci: build:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' }} continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' }}
strategy: strategy:
+1 -1
View File
@@ -3,7 +3,7 @@ name: Mixnet Contract
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
ci: mixnet-contract:
# since it's going to be compiled into wasm, there's absolutely # since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es # no point in running CI on different OS-es
runs-on: ubuntu-latest runs-on: ubuntu-latest
+22
View File
@@ -0,0 +1,22 @@
name: Generate TS types
on: push
jobs:
tauri-wallet-types:
runs-on: ubuntu-latest
steps:
- name: Prepare
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Generate TS
run: cd tauri-wallet/src-tauri && cargo test
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
with:
add: '["tauri-wallet"]'
message: '[ci skip] Generate TS types'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -3,7 +3,7 @@ name: Wasm Client
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
ci: wasm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
Generated
+917 -472
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -15,6 +15,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices. * nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing"). * nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer. * nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) [![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
@@ -31,6 +31,7 @@ flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true } sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true } itertools = { version = "0.10", optional = true }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true } cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
ts-rs = "3.0"
[features] [features]
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"] nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
@@ -442,6 +442,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
} }
} }
#[derive(Debug)]
pub struct Client { pub struct Client {
rpc_client: HttpClient, rpc_client: HttpClient,
signer: DirectSecp256k1HdWallet, signer: DirectSecp256k1HdWallet,
@@ -5,8 +5,11 @@ use crate::nymd::GasPrice;
use cosmrs::tx::{Fee, Gas}; use cosmrs::tx::{Fee, Gas};
use cosmrs::Coin; use cosmrs::Coin;
use cosmwasm_std::Uint128; use cosmwasm_std::Uint128;
use serde::{Deserialize, Serialize};
use std::fmt;
use ts_rs::TS;
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, TS)]
pub enum Operation { pub enum Operation {
Upload, Upload,
Init, Init,
@@ -37,9 +40,30 @@ pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
} }
} }
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Operation::Upload => f.write_str("Upload"),
Operation::Init => f.write_str("Init"),
Operation::Migrate => f.write_str("Migrate"),
Operation::ChangeAdmin => f.write_str("ChangeAdmin"),
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::DelegateToGateway => f.write_str("DelegateToGateway"),
Operation::UndelegateFromGateway => f.write_str("UndelegateFromGateway"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
}
}
}
impl Operation { impl Operation {
// TODO: some value tweaking // TODO: some value tweaking
pub(crate) fn default_gas_limit(&self) -> Gas { pub fn default_gas_limit(&self) -> Gas {
match self { match self {
Operation::Upload => 2_500_000u64.into(), Operation::Upload => 2_500_000u64.into(),
Operation::Init => 500_000u64.into(), Operation::Init => 500_000u64.into(),
@@ -35,10 +35,11 @@ pub use signing_client::Client as SigningNymdClient;
pub mod cosmwasm_client; pub mod cosmwasm_client;
pub mod error; pub mod error;
pub(crate) mod fee_helpers; pub mod fee_helpers;
pub mod gas_price; pub mod gas_price;
pub mod wallet; pub mod wallet;
#[derive(Debug)]
pub struct NymdClient<C> { pub struct NymdClient<C> {
client: C, client: C,
contract_address: Option<AccountId>, contract_address: Option<AccountId>,
@@ -124,6 +125,14 @@ impl<C> NymdClient<C> {
self.custom_gas_limits.insert(operation, limit); self.custom_gas_limits.insert(operation, limit);
} }
pub fn get_gas_price(&self) -> GasPrice {
self.gas_price.clone()
}
pub fn get_custom_gas_limits(&self) -> HashMap<Operation, Gas> {
self.custom_gas_limits.clone()
}
pub fn contract_address(&self) -> Result<&AccountId, NymdError> { pub fn contract_address(&self) -> Result<&AccountId, NymdError> {
self.contract_address self.contract_address
.as_ref() .as_ref()
@@ -145,7 +154,7 @@ impl<C> NymdClient<C> {
&self.client_address.as_ref().unwrap()[0] &self.client_address.as_ref().unwrap()[0]
} }
fn get_fee(&self, operation: Operation) -> Fee { pub fn get_fee(&self, operation: Operation) -> Fee {
let gas_limit = self.custom_gas_limits.get(&operation).cloned(); let gas_limit = self.custom_gas_limits.get(&operation).cloned();
operation.determine_fee(&self.gas_price, gas_limit) operation.determine_fee(&self.gas_price, gas_limit)
} }
@@ -517,15 +526,17 @@ impl<C> NymdClient<C> {
/// Delegates specified amount of stake to particular mixnode. /// Delegates specified amount of stake to particular mixnode.
pub async fn delegate_to_mixnode( pub async fn delegate_to_mixnode(
&self, &self,
mix_identity: IdentityKey, mix_identity: &str,
amount: Coin, amount: &Coin,
) -> Result<ExecuteResult, NymdError> ) -> Result<ExecuteResult, NymdError>
where where
C: SigningCosmWasmClient + Sync, C: SigningCosmWasmClient + Sync,
{ {
let fee = self.get_fee(Operation::DelegateToMixnode); let fee = self.get_fee(Operation::DelegateToMixnode);
let req = ExecuteMsg::DelegateToMixnode { mix_identity }; let req = ExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.to_string(),
};
self.client self.client
.execute( .execute(
self.address(), self.address(),
@@ -533,7 +544,7 @@ impl<C> NymdClient<C> {
&req, &req,
fee, fee,
"Delegating to mixnode from rust!", "Delegating to mixnode from rust!",
vec![cosmwasm_coin_to_cosmos_coin(amount)], vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
) )
.await .await
} }
@@ -541,14 +552,16 @@ impl<C> NymdClient<C> {
/// Removes stake delegation from a particular mixnode. /// Removes stake delegation from a particular mixnode.
pub async fn remove_mixnode_delegation( pub async fn remove_mixnode_delegation(
&self, &self,
mix_identity: IdentityKey, mix_identity: &str,
) -> Result<ExecuteResult, NymdError> ) -> Result<ExecuteResult, NymdError>
where where
C: SigningCosmWasmClient + Sync, C: SigningCosmWasmClient + Sync,
{ {
let fee = self.get_fee(Operation::UndelegateFromMixnode); let fee = self.get_fee(Operation::UndelegateFromMixnode);
let req = ExecuteMsg::UndelegateFromMixnode { mix_identity }; let req = ExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.to_string(),
};
self.client self.client
.execute( .execute(
self.address(), self.address(),
@@ -608,15 +621,17 @@ impl<C> NymdClient<C> {
/// Delegates specified amount of stake to particular gateway. /// Delegates specified amount of stake to particular gateway.
pub async fn delegate_to_gateway( pub async fn delegate_to_gateway(
&self, &self,
gateway_identity: IdentityKey, gateway_identity: &str,
amount: Coin, amount: &Coin,
) -> Result<ExecuteResult, NymdError> ) -> Result<ExecuteResult, NymdError>
where where
C: SigningCosmWasmClient + Sync, C: SigningCosmWasmClient + Sync,
{ {
let fee = self.get_fee(Operation::DelegateToGateway); let fee = self.get_fee(Operation::DelegateToGateway);
let req = ExecuteMsg::DelegateToGateway { gateway_identity }; let req = ExecuteMsg::DelegateToGateway {
gateway_identity: gateway_identity.to_string(),
};
self.client self.client
.execute( .execute(
self.address(), self.address(),
@@ -624,7 +639,7 @@ impl<C> NymdClient<C> {
&req, &req,
fee, fee,
"Delegating to gateway from rust!", "Delegating to gateway from rust!",
vec![cosmwasm_coin_to_cosmos_coin(amount)], vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
) )
.await .await
} }
@@ -632,14 +647,16 @@ impl<C> NymdClient<C> {
/// Removes stake delegation from a particular gateway. /// Removes stake delegation from a particular gateway.
pub async fn remove_gateway_delegation( pub async fn remove_gateway_delegation(
&self, &self,
gateway_identity: IdentityKey, gateway_identity: &str,
) -> Result<ExecuteResult, NymdError> ) -> Result<ExecuteResult, NymdError>
where where
C: SigningCosmWasmClient + Sync, C: SigningCosmWasmClient + Sync,
{ {
let fee = self.get_fee(Operation::UndelegateFromGateway); let fee = self.get_fee(Operation::UndelegateFromGateway);
let req = ExecuteMsg::UndelegateFromGateway { gateway_identity }; let req = ExecuteMsg::UndelegateFromGateway {
gateway_identity: gateway_identity.to_string(),
};
self.client self.client
.execute( .execute(
self.address(), self.address(),
@@ -682,3 +699,11 @@ fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
amount: (coin.amount.u128() as u64).into(), amount: (coin.amount.u128() as u64).into(),
} }
} }
fn cosmwasm_coin_ptr_to_cosmos_coin(coin: &Coin) -> CosmosCoin {
CosmosCoin {
denom: coin.denom.parse().unwrap(),
// this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64
amount: (coin.amount.u128() as u64).into(),
}
}
@@ -10,6 +10,7 @@ use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId}; use cosmrs::{tx, AccountId};
/// Derivation information required to derive a keypair and an address from a mnemonic. /// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug)]
struct Secp256k1Derivation { struct Secp256k1Derivation {
hd_path: DerivationPath, hd_path: DerivationPath,
prefix: String, prefix: String,
@@ -25,6 +26,7 @@ pub struct AccountData {
type Secp256k1Keypair = (SigningKey, PublicKey); type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug)]
pub struct DirectSecp256k1HdWallet { pub struct DirectSecp256k1HdWallet {
/// Base secret /// Base secret
secret: bip39::Mnemonic, secret: bip39::Mnemonic,
+1
View File
@@ -14,3 +14,4 @@ cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-up
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1" serde_repr = "0.1"
schemars = "0.8" schemars = "0.8"
ts-rs = "3.0"
+2 -1
View File
@@ -6,10 +6,11 @@ use cosmwasm_std::{coin, Addr, Coin};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use ts_rs::TS;
use crate::current_block_height; use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
pub struct Gateway { pub struct Gateway {
pub host: String, pub host: String,
pub mix_port: u16, pub mix_port: u16,
+2 -1
View File
@@ -7,10 +7,11 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use std::fmt::Display; use std::fmt::Display;
use ts_rs::TS;
use crate::current_block_height; use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
pub struct MixNode { pub struct MixNode {
pub host: String, pub host: String,
pub mix_port: u16, pub mix_port: u16,
+1
View File
@@ -7,4 +7,5 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
serde = {version = "1.0", features = ["derive"]}
url = "2.2" url = "2.2"
+19 -11
View File
@@ -1,19 +1,24 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
pub struct ValidatorDetails<'a> { #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ValidatorDetails {
// it is assumed those values are always valid since they're being provided in our defaults file // it is assumed those values are always valid since they're being provided in our defaults file
pub nymd_url: &'a str, pub nymd_url: String,
// Right now api_url is optional as we are not running the api reliably on all validators // Right now api_url is optional as we are not running the api reliably on all validators
// however, later on it should be a mandatory field // however, later on it should be a mandatory field
pub api_url: Option<&'a str>, pub api_url: Option<String>,
} }
impl<'a> ValidatorDetails<'a> { impl ValidatorDetails {
pub const fn new(nymd_url: &'a str, api_url: Option<&'a str>) -> Self { pub fn new(nymd_url: &str, api_url: Option<&str>) -> Self {
ValidatorDetails { nymd_url, api_url } let api_url = api_url.map(|api_url_str| api_url_str.to_string());
ValidatorDetails {
nymd_url: nymd_url.to_string(),
api_url,
}
} }
pub fn nymd_url(&self) -> Url { pub fn nymd_url(&self) -> Url {
@@ -24,27 +29,30 @@ impl<'a> ValidatorDetails<'a> {
pub fn api_url(&self) -> Option<Url> { pub fn api_url(&self) -> Option<Url> {
self.api_url self.api_url
.as_ref()
.map(|url| url.parse().expect("the provided api url is invalid!")) .map(|url| url.parse().expect("the provided api url is invalid!"))
} }
} }
pub const DEFAULT_VALIDATORS: &[ValidatorDetails] = &[ pub fn default_validators() -> Vec<ValidatorDetails> {
vec![
ValidatorDetails::new( ValidatorDetails::new(
"https://testnet-milhon-validator1.nymtech.net", "https://testnet-milhon-validator1.nymtech.net",
Some("https://testnet-milhon-validator1.nymtech.net/api"), Some("https://testnet-milhon-validator1.nymtech.net/api"),
), ),
ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None), ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None),
]; ]
}
pub fn default_nymd_endpoints() -> Vec<Url> { pub fn default_nymd_endpoints() -> Vec<Url> {
DEFAULT_VALIDATORS default_validators()
.iter() .iter()
.map(|validator| validator.nymd_url()) .map(|validator| validator.nymd_url())
.collect() .collect()
} }
pub fn default_api_endpoints() -> Vec<Url> { pub fn default_api_endpoints() -> Vec<Url> {
DEFAULT_VALIDATORS default_validators()
.iter() .iter()
.filter_map(|validator| validator.api_url()) .filter_map(|validator| validator.api_url())
.collect() .collect()
+15
View File
@@ -0,0 +1,15 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"esmodules": true
}
}
],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": ["@babel/plugin-transform-async-to-generator"]
}
+109
View File
@@ -0,0 +1,109 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"jest": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["react", "react-hooks", "jsx-a11y", "prettier", "jest"],
"extends": [
"plugin:react/recommended",
"airbnb",
"prettier",
"plugin:jest/recommended",
"plugin:jest/style"
],
"rules": {
"jest/prefer-strict-equal": "error",
"jest/prefer-to-have-length": "warn",
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"react/prop-types": "off",
"react/jsx-filename-extension": "off",
"react/jsx-props-no-spreading": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s",
"**/*.test.[jt]sx",
"**/*.spec.[jt]sx"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"tsx": "never",
"js": "never",
"jsx": "never"
}
]
},
"overrides": [
{
"files": "**/*.+(ts|tsx)",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
],
"settings": {
"import/resolver": {
"root-import": {
"rootPathPrefix": "@",
"rootPathSuffix": "src",
"extensions": [".js", ".ts", ".tsx", ".jsx", ".mdx"]
}
}
}
}
+1
View File
@@ -0,0 +1 @@
14
+5207
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
[workspace]
members = ["src-tauri"]
+19
View File
@@ -0,0 +1,19 @@
<!--
Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
SPDX-License-Identifier: Apache-2.0
-->
# Nym Tauri Wallet
A Rust and Tauri desktop wallet implementation.
## Installation prerequisites
* `Yarn`
* `NodeJS >= v16.8.0`
* `Rust & cargo >= v1.51`
## Installation & usage
* `yarn install`
* `yarn dev`
View File
+53
View File
@@ -0,0 +1,53 @@
{
"name": "tauri-app",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"webpack:dev": "yarn webpack serve",
"tauri:dev": "yarn tauri dev",
"dev": "yarn run webpack:dev & yarn run tauri:dev "
},
"dependencies": {
"@babel/preset-typescript": "^7.15.0",
"@hookform/resolvers": "^2.8.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@material-ui/styles": "^4.11.4",
"@types/react-dom": "^17.0.9",
"bs58": "^4.0.1",
"clsx": "^1.1.1",
"notistack": "^1.0.10",
"qrcode.react": "^1.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
"react-hook-form": "^7.14.2",
"react-router-dom": "^5.2.0",
"semver": "^6.3.0",
"yup": "^0.32.9"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@tauri-apps/api": "^1.0.0-beta.6",
"@tauri-apps/cli": "^1.0.0-beta.9",
"@types/bs58": "^4.0.1",
"@types/qrcode.react": "^1.0.2",
"@types/react-router-dom": "^5.1.8",
"@types/semver": "^7.3.8",
"babel-loader": "^8.2.2",
"css-loader": "^6.2.0",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.2",
"style-loader": "^3.2.1",
"url-loader": "^4.1.1",
"webpack": "^5.50.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.1.0"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

+9
View File
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>Nym Wallet</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
+4
View File
@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools
+44
View File
@@ -0,0 +1,44 @@
[package]
name = "nym_wallet"
version = "0.1.0"
description = "Nym Native Wallet"
authors = ["you"]
license = ""
repository = ""
default-run = "nym_wallet"
edition = "2018"
build = "src/build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.0.0-beta.4" }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0.0-beta.8", features = [] }
tokio = { version = "1.10", features = ["sync"] }
dirs = "3.0"
# url = "2.2"
bip39 = "1.0"
thiserror = "1.0"
tendermint-rpc = "0.21.0"
ts-rs = "3.0"
url = "2.0"
rand = "0.6.5"
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"] }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
"nymd-client",
] }
mixnet-contract = { path = "../../common/mixnet-contract" }
config = { path = "../../common/config" }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+14
View File
@@ -0,0 +1,14 @@
max_width = 100
hard_tabs = false
tab_spaces = 2
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2018"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
imports_granularity = "Crate"
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
+349
View File
@@ -0,0 +1,349 @@
// This should be moved out of the wallet, and used as a primary coin type throughout the codebase
use ::config::defaults::DENOM;
use cosmrs::Decimal;
use cosmrs::Denom as CosmosDenom;
use cosmwasm_std::Coin as CosmWasmCoin;
use cosmwasm_std::Uint128;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
use std::ops::{Add, Sub};
use std::str::FromStr;
use ts_rs::TS;
use validator_client::nymd::{CosmosCoin, GasPrice};
use crate::format_err;
#[derive(TS, Serialize, Deserialize, Clone, PartialEq, Debug)]
pub enum Denom {
Major,
Minor,
}
const MINOR_IN_MAJOR: f64 = 1_000_000.;
impl fmt::Display for Denom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Denom::Major => f.write_str(&DENOM[1..]),
Denom::Minor => f.write_str(DENOM),
}
}
}
impl FromStr for Denom {
type Err = String;
fn from_str(s: &str) -> Result<Denom, String> {
let s = s.to_lowercase();
if s == DENOM.to_lowercase() || s == "minor" {
Ok(Denom::Minor)
} else if s == DENOM[1..].to_lowercase() || s == "major" {
Ok(Denom::Major)
} else {
Err(format_err!(format!(
"{} is not a valid denomination string",
s
)))
}
}
}
#[derive(TS, Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Coin {
amount: String,
denom: Denom,
}
// TODO convert to TryFrom
impl From<GasPrice> for Coin {
fn from(g: GasPrice) -> Coin {
Coin {
amount: g.amount.to_string(),
denom: Denom::from_str(&g.denom.to_string()).unwrap(),
}
}
}
impl fmt::Display for Coin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format!("{} {}", self.amount, self.denom))
}
}
// Allows adding minor and major denominations, output will have the LHS denom.
impl Add for Coin {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let denom = self.denom.clone();
let lhs = self.to_minor();
let rhs = rhs.to_minor();
let lhs_amount = lhs.amount.parse::<u64>().unwrap();
let rhs_amount = rhs.amount.parse::<u64>().unwrap();
let amount = lhs_amount + rhs_amount;
let coin = Coin {
amount: amount.to_string(),
denom: Denom::Minor,
};
match denom {
Denom::Major => coin.to_major(),
Denom::Minor => coin,
}
}
}
// Allows adding minor and major denominations, output will have the LHS denom.
impl Sub for Coin {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
let denom = self.denom.clone();
let lhs = self.to_minor();
let rhs = rhs.to_minor();
let lhs_amount = lhs.amount.parse::<i64>().unwrap();
let rhs_amount = rhs.amount.parse::<i64>().unwrap();
let amount = lhs_amount - rhs_amount;
let coin = Coin {
amount: amount.to_string(),
denom: Denom::Minor,
};
match denom {
Denom::Major => coin.to_major(),
Denom::Minor => coin,
}
}
}
impl Coin {
pub fn major<T: ToString>(amount: T) -> Coin {
Coin {
amount: amount.to_string(),
denom: Denom::Major,
}
}
pub fn minor<T: ToString>(amount: T) -> Coin {
Coin {
amount: amount.to_string(),
denom: Denom::Minor,
}
}
pub fn new<T: ToString>(amount: T, denom: &Denom) -> Coin {
Coin {
amount: amount.to_string(),
denom: denom.clone(),
}
}
pub fn to_major(&self) -> Coin {
match self.denom {
Denom::Major => self.clone(),
Denom::Minor => Coin {
amount: (self.amount.parse::<f64>().unwrap() / MINOR_IN_MAJOR).to_string(),
denom: Denom::Major,
},
}
}
pub fn to_minor(&self) -> Coin {
match self.denom {
Denom::Minor => self.clone(),
Denom::Major => Coin {
amount: (self.amount.parse::<f64>().unwrap() * MINOR_IN_MAJOR).to_string(),
denom: Denom::Minor,
},
}
}
pub fn amount(&self) -> String {
self.amount.clone()
}
pub fn denom(&self) -> Denom {
self.denom.clone()
}
}
impl TryFrom<Coin> for CosmWasmCoin {
type Error = String;
fn try_from(coin: Coin) -> Result<CosmWasmCoin, String> {
Ok(CosmWasmCoin::new(
Uint128::try_from(coin.amount.as_str()).unwrap().u128(),
coin.denom.to_string(),
))
}
}
impl TryFrom<Coin> for CosmosCoin {
type Error = String;
fn try_from(coin: Coin) -> Result<CosmosCoin, String> {
match Decimal::from_str(&coin.amount) {
Ok(d) => Ok(CosmosCoin {
amount: d,
denom: CosmosDenom::from_str(&coin.denom.to_string()).unwrap(),
}),
Err(e) => Err(format_err!(e)),
}
}
}
impl From<CosmosCoin> for Coin {
fn from(c: CosmosCoin) -> Coin {
Coin {
amount: c.amount.to_string(),
denom: Denom::from_str(&c.denom.to_string()).unwrap(),
}
}
}
impl From<CosmWasmCoin> for Coin {
fn from(c: CosmWasmCoin) -> Coin {
Coin {
amount: c.amount.to_string(),
denom: Denom::from_str(&c.denom).unwrap(),
}
}
}
#[cfg(test)]
mod test {
use crate::coin::{Coin, Denom};
use cosmrs::Coin as CosmosCoin;
use cosmrs::Decimal;
use cosmrs::Denom as CosmosDenom;
use cosmwasm_std::Coin as CosmWasmCoin;
use serde_json::json;
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;
#[test]
fn json_to_coin() {
let minor = json!({
"amount": "1",
"denom": "Minor"
});
let major = json!({
"amount": "1",
"denom": "Major"
});
let test_minor_coin = Coin::minor("1");
let test_major_coin = Coin::major("1");
let minor_coin = serde_json::from_value::<Coin>(minor).unwrap();
let major_coin = serde_json::from_value::<Coin>(major).unwrap();
assert_eq!(minor_coin, test_minor_coin);
assert_eq!(major_coin, test_major_coin);
}
#[test]
fn denom_conversions() {
let minor = Coin::minor("1");
let major = minor.to_major();
assert_eq!(major, Coin::major("0.000001"));
let minor = major.to_minor();
assert_eq!(minor, Coin::minor("1"));
}
fn amounts() -> Vec<&'static str> {
vec![
"1",
"10",
"100",
"1000",
"10000",
"100000",
"10000000",
"100000000",
"1000000000",
"10000000000",
"100000000000",
"1000000000000",
"10000000000000",
"100000000000000",
"1000000000000000",
"10000000000000000",
"100000000000000000",
"1000000000000000000",
]
}
#[test]
fn coin_to_cosmoswasm() {
for amount in amounts() {
let coin: Coin = Coin::minor(amount).into();
let cosmoswasm_coin: CosmWasmCoin = coin.try_into().unwrap();
assert_eq!(
cosmoswasm_coin,
CosmWasmCoin::new(amount.parse::<u128>().unwrap(), Denom::Minor.to_string())
);
assert_eq!(
Coin::try_from(cosmoswasm_coin).unwrap(),
Coin::minor(amount)
);
let coin: Coin = Coin::major(amount).into();
let cosmoswasm_coin: CosmWasmCoin = coin.try_into().unwrap();
assert_eq!(
cosmoswasm_coin,
CosmWasmCoin::new(amount.parse::<u128>().unwrap(), Denom::Major.to_string())
);
assert_eq!(
Coin::try_from(cosmoswasm_coin).unwrap(),
Coin::major(amount)
);
}
}
#[test]
fn coin_to_cosmos() {
for amount in amounts() {
let coin: Coin = Coin::minor(amount).into();
let cosmos_coin: CosmosCoin = coin.try_into().unwrap();
assert_eq!(
cosmos_coin,
CosmosCoin {
amount: Decimal::from_str(amount).unwrap(),
denom: CosmosDenom::from_str(&Denom::Minor.to_string()).unwrap()
}
);
assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::minor(amount));
let coin: Coin = Coin::major(amount).into();
let cosmos_coin: CosmosCoin = coin.try_into().unwrap();
assert_eq!(
cosmos_coin,
CosmosCoin {
amount: Decimal::from_str(amount).unwrap(),
denom: CosmosDenom::from_str(&Denom::Major.to_string()).unwrap()
}
);
assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::major(amount));
}
}
#[test]
fn test_add() {
assert_eq!(Coin::minor("1") + Coin::minor("1"), Coin::minor("2"));
assert_eq!(Coin::major("1") + Coin::major("1"), Coin::major("2"));
assert_eq!(Coin::minor("1") + Coin::major("1"), Coin::minor("1000001"));
assert_eq!(Coin::major("1") + Coin::minor("1"), Coin::major("1.000001"));
}
#[test]
fn test_sub() {
assert_eq!(Coin::minor("1") - Coin::minor("1"), Coin::minor("0"));
assert_eq!(Coin::major("1") - Coin::major("1"), Coin::major("0"));
assert_eq!(Coin::minor("1") - Coin::major("1"), Coin::minor("-999999"));
assert_eq!(Coin::major("1") - Coin::minor("1"), Coin::major("0.999999"));
}
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::{default_validators, ValidatorDetails, DEFAULT_MIXNET_CONTRACT_ADDRESS};
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::str::FromStr;
use tendermint_rpc::Url;
mod template;
use template::config_template;
use crate::error::BackendError;
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Config {
base: Base,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Base {
validators: Vec<ValidatorDetails>,
/// Address of the validator contract managing the network
mixnet_contract_address: String,
/// Mnemonic (currently of the network monitor) used for rewarding
mnemonic: String,
}
impl Default for Base {
fn default() -> Self {
Base {
validators: default_validators(),
mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(),
mnemonic: String::default(),
}
}
}
impl NymConfig for Config {
fn template() -> &'static str {
config_template()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("wallet")
}
fn root_directory(&self) -> PathBuf {
Self::default_root_directory()
}
fn config_directory(&self) -> PathBuf {
self.root_directory().join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory().join("data")
}
}
impl Config {
pub fn get_nymd_validator_url(&self) -> Result<Url, BackendError> {
// TODO make this a random choice
if let Some(validator_details) = self.base.validators.first() {
match tendermint_rpc::Url::from_str(&validator_details.nymd_url().to_string()) {
Ok(url) => Ok(url),
Err(e) => Err(e.into()),
}
} else {
panic!("No validators found in config")
}
}
pub fn get_mixnet_contract_address(&self) -> String {
self.base.mixnet_contract_address.clone()
}
// pub fn get_mnemonic(&self) -> String {
// self.base.mnemonic.clone()
// }
}
@@ -0,0 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) fn config_template() -> &'static str {
r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base tauri-wallet config options #####
[base]
# Validator server to which the API will be getting information about the network.
validator_url = '{{ base.validator_url }}'
"#
}
+21
View File
@@ -0,0 +1,21 @@
use thiserror::Error;
use validator_client::nymd::error::NymdError;
#[derive(Error, Debug)]
pub enum BackendError {
#[error("Error parsing bip39 mnemonic")]
Bip39Error {
#[from]
source: bip39::Error,
},
#[error("Error parsing into tendermint Url")]
TendermintError {
#[from]
source: tendermint_rpc::Error,
},
#[error("Error getting balances")]
NymdError {
#[from]
source: NymdError,
},
}
+78
View File
@@ -0,0 +1,78 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use mixnet_contract::{Gateway, MixNode};
use std::sync::Arc;
use tokio::sync::RwLock;
use ts_rs::export;
use validator_client::nymd::fee_helpers::Operation;
mod coin;
mod config;
mod error;
mod operations;
mod state;
mod utils;
use crate::operations::account::*;
use crate::operations::admin::*;
use crate::operations::bond::*;
use crate::operations::delegate::*;
use crate::operations::send::*;
use crate::utils::*;
use crate::state::State;
#[cfg(test)]
use crate::coin::{Coin, Denom};
#[macro_export]
macro_rules! format_err {
($e:expr) => {
format!("line {}: {}", line!(), $e)
};
}
fn main() {
tauri::Builder::default()
.manage(Arc::new(RwLock::new(State::default())))
.invoke_handler(tauri::generate_handler![
connect_with_mnemonic,
get_balance,
minor_to_major,
major_to_minor,
owns_gateway,
owns_mixnode,
bond_mixnode,
unbond_mixnode,
bond_gateway,
unbond_gateway,
delegate_to_mixnode,
undelegate_from_mixnode,
delegate_to_gateway,
undelegate_from_gateway,
send,
create_new_account,
get_fee,
get_state_params,
update_state_params
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
export! {
MixNode => "../src/types/rust/mixnode.ts",
Coin => "../src/types/rust/coin.ts",
Balance => "../src/types/rust/balance.ts",
Gateway => "../src/types/rust/gateway.ts",
TauriTxResult => "../src/types/rust/tauritxresult.ts",
TransactionDetails => "../src/types/rust/transactiondetails.ts",
Operation => "../src/types/rust/operation.ts",
Denom => "../src/types/rust/denom.ts",
DelegationResult => "../src/types/rust/delegationresult.ts",
Account => "../src/types/rust/account.ts",
TauriStateParams => "../src/types/rust/stateparams.ts"
}
@@ -0,0 +1,113 @@
use crate::coin::{Coin, Denom};
use crate::config::Config;
use crate::error::BackendError;
use crate::format_err;
use crate::state::State;
use bip39::{Language, Mnemonic};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
use ts_rs::TS;
use validator_client::nymd::{AccountId, NymdClient, SigningNymdClient};
#[derive(TS, Serialize, Deserialize)]
pub struct Account {
contract_address: String,
client_address: String,
denom: Denom,
mnemonmic: Option<String>,
}
#[derive(TS, Serialize, Deserialize)]
pub struct Balance {
coin: Coin,
printable_balance: String,
}
#[tauri::command]
pub async fn connect_with_mnemonic(
mnemonic: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, String> {
let mnemonic = match Mnemonic::from_str(&mnemonic) {
Ok(mnemonic) => mnemonic,
Err(e) => return Err(BackendError::from(e).to_string()),
};
let client;
{
let r_state = state.read().await;
client = _connect_with_mnemonic(mnemonic, &r_state.config());
}
let contract_address = match client.contract_address() {
Ok(address) => address.to_string(),
Err(e) => return Err(format_err!(e)),
};
let client_address = client.address().to_string();
let denom = match client.denom() {
Ok(denom) => denom,
Err(e) => return Err(format_err!(e)),
};
let account = Account {
contract_address,
client_address,
denom: Denom::from_str(&denom.to_string())?,
mnemonmic: None,
};
let mut w_state = state.write().await;
w_state.set_client(client);
Ok(account)
}
#[tauri::command]
pub async fn get_balance(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<Balance, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.get_balance(client.address()).await {
Ok(Some(coin)) => {
let coin = Coin::new(
&coin.amount.to_string(),
&Denom::from_str(&coin.denom.to_string())?,
);
Ok(Balance {
coin: coin.clone(),
printable_balance: coin.to_major().to_string(),
})
}
Ok(None) => Err(format!(
"No balance available for address {}",
client.address()
)),
Err(e) => Err(BackendError::from(e).to_string()),
}
}
#[tauri::command]
pub async fn create_new_account(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, String> {
let mnemonic = random_mnemonic();
let mut client = connect_with_mnemonic(mnemonic.to_string(), state).await?;
client.mnemonmic = Some(mnemonic.to_string());
Ok(client)
}
fn random_mnemonic() -> Mnemonic {
let mut rng = rand::thread_rng();
Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap()
}
fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> NymdClient<SigningNymdClient> {
match NymdClient::connect_with_mnemonic(
config.get_nymd_validator_url().unwrap(),
Some(AccountId::from_str(&config.get_mixnet_contract_address()).unwrap()),
mnemonic,
) {
Ok(client) => client,
Err(e) => panic!("{}", e),
}
}
@@ -0,0 +1,84 @@
use crate::format_err;
use crate::state::State;
use cosmwasm_std::Decimal;
use cosmwasm_std::Uint128;
use mixnet_contract::StateParams;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
use ts_rs::TS;
#[derive(Serialize, Deserialize, TS)]
pub struct TauriStateParams {
epoch_length: u32,
minimum_mixnode_bond: String,
minimum_gateway_bond: String,
mixnode_bond_reward_rate: String,
gateway_bond_reward_rate: String,
mixnode_delegation_reward_rate: String,
gateway_delegation_reward_rate: String,
mixnode_active_set_size: u32,
}
impl From<StateParams> for TauriStateParams {
fn from(p: StateParams) -> TauriStateParams {
TauriStateParams {
epoch_length: p.epoch_length,
minimum_mixnode_bond: p.minimum_mixnode_bond.to_string(),
minimum_gateway_bond: p.minimum_gateway_bond.to_string(),
mixnode_bond_reward_rate: p.mixnode_bond_reward_rate.to_string(),
gateway_bond_reward_rate: p.gateway_bond_reward_rate.to_string(),
mixnode_delegation_reward_rate: p.mixnode_delegation_reward_rate.to_string(),
gateway_delegation_reward_rate: p.gateway_delegation_reward_rate.to_string(),
mixnode_active_set_size: p.mixnode_active_set_size,
}
}
}
impl TryFrom<TauriStateParams> for StateParams {
type Error = Box<dyn std::error::Error>;
fn try_from(p: TauriStateParams) -> Result<StateParams, Self::Error> {
Ok(StateParams {
epoch_length: p.epoch_length,
minimum_mixnode_bond: Uint128::try_from(p.minimum_mixnode_bond.as_str())?,
minimum_gateway_bond: Uint128::try_from(p.minimum_gateway_bond.as_str())?,
mixnode_bond_reward_rate: Decimal::from_str(p.mixnode_bond_reward_rate.as_str())?,
gateway_bond_reward_rate: Decimal::from_str(p.gateway_bond_reward_rate.as_str())?,
mixnode_delegation_reward_rate: Decimal::from_str(p.mixnode_delegation_reward_rate.as_str())?,
gateway_delegation_reward_rate: Decimal::from_str(p.gateway_delegation_reward_rate.as_str())?,
mixnode_active_set_size: p.mixnode_active_set_size,
})
}
}
#[tauri::command]
pub async fn get_state_params(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriStateParams, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.get_state_params().await {
Ok(params) => Ok(params.into()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn update_state_params(
params: TauriStateParams,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriStateParams, String> {
let r_state = state.read().await;
let client = r_state.client()?;
let state_params: StateParams = match params.try_into() {
Ok(state_params) => state_params,
Err(e) => return Err(format_err!(e)),
};
match client.update_state_params(state_params.clone()).await {
Ok(_) => Ok(state_params.into()),
Err(e) => Err(format_err!(e)),
}
}
@@ -0,0 +1,64 @@
use crate::coin::Coin;
use crate::format_err;
use crate::state::State;
use crate::{Gateway, MixNode};
use cosmwasm_std::Coin as CosmWasmCoin;
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::RwLock;
#[tauri::command]
pub async fn bond_gateway(
gateway: Gateway,
bond: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), String> {
let r_state = state.read().await;
let bond: CosmWasmCoin = match bond.try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let client = r_state.client()?;
match client.bond_gateway(gateway, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn unbond_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<(), String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.unbond_gateway().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn unbond_mixnode(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<(), String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.unbond_mixnode().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn bond_mixnode(
mixnode: MixNode,
bond: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), String> {
let r_state = state.read().await;
let bond: CosmWasmCoin = match bond.try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let client = r_state.client()?;
match client.bond_mixnode(mixnode, bond).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -0,0 +1,94 @@
use crate::coin::Coin;
use crate::format_err;
use crate::state::State;
use cosmwasm_std::Coin as CosmWasmCoin;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::RwLock;
use ts_rs::TS;
#[derive(TS, Serialize, Deserialize)]
pub struct DelegationResult {
source_address: String,
target_address: String,
amount: Option<Coin>,
}
#[tauri::command]
pub async fn delegate_to_mixnode(
identity: &str,
amount: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, String> {
let r_state = state.read().await;
let bond: CosmWasmCoin = match amount.try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let client = r_state.client()?;
match client.delegate_to_mixnode(identity, &bond).await {
Ok(_result) => Ok(DelegationResult {
source_address: client.address().to_string(),
target_address: identity.to_string(),
amount: Some(bond.into()),
}),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn undelegate_from_mixnode(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.remove_mixnode_delegation(identity).await {
Ok(_result) => Ok(DelegationResult {
source_address: client.address().to_string(),
target_address: identity.to_string(),
amount: None,
}),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn delegate_to_gateway(
identity: &str,
amount: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, String> {
let r_state = state.read().await;
let bond: CosmWasmCoin = match amount.try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let client = r_state.client()?;
match client.delegate_to_gateway(identity, &bond).await {
Ok(_result) => Ok(DelegationResult {
source_address: client.address().to_string(),
target_address: identity.to_string(),
amount: Some(bond.into()),
}),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn undelegate_from_gateway(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.remove_gateway_delegation(identity).await {
Ok(_result) => Ok(DelegationResult {
source_address: client.address().to_string(),
target_address: identity.to_string(),
amount: None,
}),
Err(e) => Err(format_err!(e)),
}
}
@@ -0,0 +1,5 @@
pub mod account;
pub mod admin;
pub mod bond;
pub mod delegate;
pub mod send;
@@ -0,0 +1,69 @@
use crate::coin::Coin;
use crate::format_err;
use crate::state::State;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::str::FromStr;
use std::sync::Arc;
use tendermint_rpc::endpoint::broadcast::tx_commit::Response;
use tokio::sync::RwLock;
use ts_rs::TS;
use validator_client::nymd::{AccountId, CosmosCoin};
#[derive(Deserialize, Serialize, TS)]
pub struct TauriTxResult {
code: u32,
gas_wanted: u64,
gas_used: u64,
block_height: u64,
details: TransactionDetails,
}
#[derive(Deserialize, Serialize, TS)]
pub struct TransactionDetails {
from_address: String,
to_address: String,
amount: Coin,
}
impl TauriTxResult {
fn new(t: Response, details: TransactionDetails) -> TauriTxResult {
TauriTxResult {
code: t.check_tx.code.value(),
gas_wanted: t.check_tx.gas_wanted.value(),
gas_used: t.check_tx.gas_used.value(),
block_height: t.height.value(),
details,
}
}
}
#[tauri::command]
pub async fn send(
address: &str,
amount: Coin,
memo: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriTxResult, String> {
let address = match AccountId::from_str(address) {
Ok(addy) => addy,
Err(e) => return Err(format_err!(e)),
};
let cosmos_amount: CosmosCoin = match amount.clone().try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let r_state = state.read().await;
let client = r_state.client()?;
match client.send(&address, vec![cosmos_amount], memo).await {
Ok(result) => Ok(TauriTxResult::new(
result,
TransactionDetails {
from_address: client.address().to_string(),
to_address: address.to_string(),
amount,
},
)),
Err(e) => Err(format_err!(e)),
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::config::Config;
use validator_client::nymd::{NymdClient, SigningNymdClient};
#[derive(Default)]
pub struct State {
config: Config,
signing_client: Option<NymdClient<SigningNymdClient>>,
}
impl State {
pub fn client(&self) -> Result<&NymdClient<SigningNymdClient>, String> {
self.signing_client.as_ref().ok_or_else(|| {
"Client has not been initialized yet, connect with mnemonic to initialize".to_string()
})
}
pub fn config(&self) -> Config {
self.config.clone()
}
pub fn set_client(&mut self, signing_client: NymdClient<SigningNymdClient>) {
self.signing_client = Some(signing_client)
}
}
+54
View File
@@ -0,0 +1,54 @@
use crate::coin::{Coin, Denom};
use crate::format_err;
use crate::state::State;
use crate::Operation;
use std::sync::Arc;
use tokio::sync::RwLock;
#[tauri::command]
pub fn major_to_minor(amount: &str) -> Result<Coin, String> {
let coin = Coin::new(amount, &Denom::Major);
Ok(coin.to_minor())
}
#[tauri::command]
pub fn minor_to_major(amount: &str) -> Result<Coin, String> {
let coin = Coin::new(amount, &Denom::Minor);
Ok(coin.to_major())
}
#[tauri::command]
pub async fn owns_mixnode(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<bool, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.owns_mixnode(client.address()).await {
Ok(o) => Ok(o),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn owns_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<bool, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.owns_gateway(client.address()).await {
Ok(o) => Ok(o),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn get_fee(
operation: Operation,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, String> {
let r_state = state.read().await;
let client = r_state.client()?;
let fee = client.get_fee(operation);
let mut coin = Coin::new("0", &Denom::Major);
for f in fee.amount {
coin = coin + f.into();
}
Ok(coin)
}
+65
View File
@@ -0,0 +1,65 @@
{
"package": {
"productName": "tauri-wallet",
"version": "0.1.0"
},
"build": {
"distDir": "../dist",
"devPath": "http://localhost:9000",
"beforeDevCommand": "",
"beforeBuildCommand": ""
},
"tauri": {
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [],
"externalBin": [],
"copyright": "",
"category": "DeveloperTool",
"shortDescription": "",
"longDescription": "",
"deb": {
"depends": [],
"useBootstrapper": false
},
"macOS": {
"frameworks": [],
"minimumSystemVersion": "",
"useBootstrapper": false,
"exceptionDomain": "",
"signingIdentity": null,
"entitlements": null
},
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"updater": {
"active": false
},
"allowlist": {},
"windows": [
{
"title": "nym-wallet",
"width": 1268,
"height": 768,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
}
}
}
+223
View File
@@ -0,0 +1,223 @@
import React, { useContext, useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import {
Backdrop,
Box,
Button,
CircularProgress,
FormControl,
Grid,
Paper,
Slide,
TextField,
Theme,
} from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
import { ClientContext } from '../context/main'
import { NymCard } from '.'
import { getContractParams, setContractParams } from '../requests'
import { TauriStateParams } from '../types'
export const Admin: React.FC = () => {
const { showAdmin, handleShowAdmin } = useContext(ClientContext)
const [isLoading, setIsLoading] = useState(false)
const [params, setParams] = useState<TauriStateParams>()
const onCancel = () => {
setParams(undefined)
setIsLoading(false)
handleShowAdmin()
}
useEffect(() => {
const requestContractParams = async () => {
if (showAdmin) {
setIsLoading(true)
const params = await getContractParams()
setParams(params)
setIsLoading(false)
}
}
requestContractParams()
}, [showAdmin])
return (
<Backdrop open={showAdmin} style={{ zIndex: 2, overflow: 'auto' }}>
<Slide in={showAdmin}>
<Paper style={{ margin: 'auto' }}>
<NymCard title="Admin" subheader="Contract administration" noPadding>
{isLoading && (
<Box style={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={48} />
</Box>
)}
{!isLoading && params && (
<AdminForm onCancel={onCancel} params={params} />
)}
</NymCard>
</Paper>
</Slide>
</Backdrop>
)
}
const AdminForm: React.FC<{
params: TauriStateParams
onCancel: () => void
}> = ({ params, onCancel }) => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm({ defaultValues: { ...params } })
const onSubmit = async (data: TauriStateParams) => {
await setContractParams(data)
console.log(data)
onCancel()
}
const theme: Theme = useTheme()
return (
<FormControl fullWidth>
<div
style={{ padding: theme.spacing(3, 5), maxWidth: 700, minWidth: 400 }}
>
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
{...register('minimum_mixnode_bond')}
required
variant="outlined"
id="minimum_mixnode_bond"
name="minimum_mixnode_bond"
label="Minumum mixnode bond"
fullWidth
error={!!errors.minimum_mixnode_bond}
helperText={errors?.minimum_mixnode_bond?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('minimum_gateway_bond')}
required
variant="outlined"
id="minimum_gateway_bond"
name="minimum_gateway_bond"
label="Minumum gateway bond"
fullWidth
error={!!errors.minimum_gateway_bond}
helperText={errors?.minimum_gateway_bond?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('mixnode_bond_reward_rate')}
required
variant="outlined"
id="mixnode_bond_reward_rate"
name="mixnode_bond_reward_rate"
label="Mixnode bond reward rate"
fullWidth
error={!!errors.mixnode_bond_reward_rate}
helperText={errors?.mixnode_bond_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('gateway_bond_reward_rate')}
required
variant="outlined"
id="gateway_bond_reward_rate"
name="gateway_bond_reward_rate"
label="Gateway bond reward rate"
fullWidth
error={!!errors.gateway_bond_reward_rate}
helperText={errors?.gateway_bond_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('mixnode_delegation_reward_rate')}
required
variant="outlined"
id="mixnode_delegation_reward_rate"
name="mixnode_delegation_reward_rate"
label="Mixnode Delegation Reward Rate"
fullWidth
error={!!errors.mixnode_delegation_reward_rate}
helperText={errors?.mixnode_delegation_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('gateway_delegation_reward_rate')}
required
variant="outlined"
id="gateway_delegation_reward_rate"
name="gateway_delegation_reward_rate"
label="Gateway Delegation Reward Rate"
fullWidth
error={!!errors.gateway_delegation_reward_rate}
helperText={errors?.gateway_delegation_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('epoch_length')}
required
variant="outlined"
id="epochLength"
name="epochLength"
label="Epoch length (hours)"
fullWidth
error={!!errors.epoch_length}
helperText={errors?.epoch_length?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('mixnode_active_set_size', { valueAsNumber: true })}
required
variant="outlined"
id="mixnode_active_set_size"
name="mixnode_active_set_size"
label="Mixnode Active Set Size"
fullWidth
error={!!errors.mixnode_active_set_size}
helperText={errors?.mixnode_active_set_size?.message}
/>
</Grid>
</Grid>
</div>
<Grid
container
spacing={1}
justifyContent="flex-end"
style={{
borderTop: `1px solid ${theme.palette.grey[200]}`,
background: theme.palette.grey[100],
padding: theme.spacing(2),
}}
>
<Grid item>
<Button onClick={onCancel}>Cancel</Button>
</Grid>
<Grid item>
<Button
onClick={handleSubmit(onSubmit)}
disabled={isSubmitting}
variant="contained"
color="primary"
type="submit"
disableElevation
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Update Contract
</Button>
</Grid>
</Grid>
</FormControl>
)
}
@@ -0,0 +1,43 @@
import React from 'react'
import Typography from '@material-ui/core/Typography'
import Grid from '@material-ui/core/Grid'
import { CircularProgress } from '@material-ui/core'
import { Alert, AlertTitle } from '@material-ui/lab'
type ConfirmationProps = {
isLoading: boolean
progressMessage: string
SuccessMessage: React.ReactNode
failureMessage: string
error: Error | null
}
export const Confirmation = ({
isLoading,
progressMessage,
SuccessMessage,
failureMessage,
error,
}: ConfirmationProps) => {
return isLoading ? (
<>
<Typography variant="h6" gutterBottom>
{progressMessage}
</Typography>
<Grid item xs={12} sm={6}>
<CircularProgress />
</Grid>
</>
) : (
<>
{error === null ? (
SuccessMessage
) : (
<Alert severity="error">
<AlertTitle>{error.name}</AlertTitle>
<strong>{failureMessage}</strong> - {error.message}
</Alert>
)}
</>
)
}
@@ -0,0 +1,50 @@
import React, { useState } from 'react'
import { Button } from '@material-ui/core'
import { Check } from '@material-ui/icons'
import { green } from '@material-ui/core/colors'
import { clipboard } from '@tauri-apps/api'
const copy = (text: string): Promise<{ success: boolean; value: string }> => {
return new Promise((resolve, reject) => {
clipboard
.writeText(text)
.then(() => resolve({ success: true, value: text }))
.catch((e) => reject({ success: false, value: 'Failed to copy: ' + e }))
})
}
export const handleCopy = async ({
text,
cb,
}: {
text: string
cb: (success: boolean) => void
}) => {
const res = await copy(text)
if (res.success) {
setTimeout(() => {
cb(true)
}, 750)
} else {
console.log(res.value)
}
}
export const CopyToClipboard = ({ text }: { text: string }) => {
const [copied, setCopied] = useState(false)
const updateCopyStatus = (isCopied: boolean) => setCopied(isCopied)
return (
<Button
size="small"
variant={copied ? 'text' : 'outlined'}
aria-label="save"
onClick={() => handleCopy({ text, cb: updateCopyStatus })}
endIcon={copied && <Check />}
style={copied ? { background: green[500], color: 'white' } : {}}
>
{copied ? 'Copied' : 'Copy'}
</Button>
)
}
+20
View File
@@ -0,0 +1,20 @@
import React from 'react'
import { FallbackProps } from 'react-error-boundary'
import { Alert, AlertTitle } from '@material-ui/lab'
import { Button } from '@material-ui/core'
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
return (
<div>
<Alert severity="error">
<AlertTitle>{error.name}</AlertTitle>
{error.message}
</Alert>
<Alert severity="error">
<AlertTitle>Stack trace</AlertTitle>
{error.stack}
</Alert>
<Button onClick={resetErrorBoundary}>Back to safety</Button>
</div>
)
}
+41
View File
@@ -0,0 +1,41 @@
import React from 'react'
import { Box, CircularProgress } from '@material-ui/core'
type TLoading = {
size?: 'small' | 'medium' | 'large' | 'x-large'
Icon?: React.ReactNode
}
export const Loading: React.FC<TLoading> = ({ size = 'medium', Icon }) => {
return (
<Box style={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
size={
size === 'small'
? 24
: size === 'large'
? 60
: size === 'x-large'
? 72
: 36
}
/>
{Icon && (
<Box
style={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{Icon}
</Box>
)}
</Box>
)
}
+156
View File
@@ -0,0 +1,156 @@
import React, { useContext } from 'react'
import { Link, useLocation } from 'react-router-dom'
import {
List,
ListItem,
ListItemIcon,
ListItemText,
Theme,
Typography,
} from '@material-ui/core'
import {
AccountBalanceWalletRounded,
ArrowBack,
ArrowForward,
AttachMoney,
Cancel,
ExitToApp,
HowToVote,
MoneyOff,
Description,
Settings,
VpnLockSharp,
} from '@material-ui/icons'
import { makeStyles, useTheme } from '@material-ui/styles'
import clsx from 'clsx'
import { ADMIN_ADDRESS, ClientContext } from '../context/main'
const RoutesSchema = () => {
const routes = [
{
label: 'Balance',
route: '/balance',
Icon: <AccountBalanceWalletRounded />,
},
{
label: 'Send',
route: '/send',
Icon: <ArrowForward />,
},
{
label: 'Receive',
route: '/receive',
Icon: <ArrowBack />,
},
{
label: 'Bond',
route: '/bond',
Icon: <AttachMoney />,
},
{
label: 'Unbond',
route: '/unbond',
Icon: <MoneyOff />,
},
{
label: 'Delegate',
route: '/delegate',
Icon: <HowToVote />,
},
{
label: 'Undelegate',
route: '/undelegate',
Icon: <Cancel />,
},
{
label: 'SOCKS5',
route: '/socks5',
Icon: <VpnLockSharp />,
},
]
if (process.env.NODE_ENV) {
routes.push({
label: 'Docs',
route: '/docs',
Icon: <Description />,
})
}
return routes
}
const useStyles = makeStyles((theme: Theme) => ({
navItem: {
color: theme.palette.common.white,
fontSize: 24,
},
selected: {
color: theme.palette.primary.main,
},
}))
export const Nav = () => {
const classes = useStyles()
const { clientDetails, handleShowAdmin, logOut } = useContext(ClientContext)
const location = useLocation()
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<List>
{RoutesSchema().map((r, i) => (
<ListItem button component={Link} to={r.route} key={i}>
<ListItemIcon
className={clsx([
classes.navItem,
location.pathname === r.route ? classes.selected : undefined,
])}
>
{r.Icon}
</ListItemIcon>
<ListItemText
primary={r.label}
primaryTypographyProps={{
className: clsx([
classes.navItem,
location.pathname === r.route ? classes.selected : undefined,
]),
}}
/>
</ListItem>
))}
{clientDetails?.client_address === ADMIN_ADDRESS && (
<ListItem button onClick={handleShowAdmin}>
<ListItemIcon className={classes.navItem}>
<Settings />
</ListItemIcon>
<ListItemText
primary="Admin"
primaryTypographyProps={{
className: classes.navItem,
}}
/>
</ListItem>
)}
<ListItem button onClick={logOut}>
<ListItemIcon className={classes.navItem}>
<ExitToApp />
</ListItemIcon>
<ListItemText
primary="Log out"
primaryTypographyProps={{
className: classes.navItem,
}}
/>
</ListItem>
</List>
</div>
)
}
@@ -0,0 +1,161 @@
import React, { useContext, useEffect, useState } from 'react'
import {
Box,
CardContent,
CircularProgress,
IconButton,
Theme,
Tooltip,
Typography,
useTheme,
} from '@material-ui/core'
import { ClientContext } from '../context/main'
import {
ArrowForwardSharp,
CheckCircleOutline,
FileCopy,
PowerSettingsNew,
Refresh,
} from '@material-ui/icons'
import { NymCard } from './NymCard'
import { Alert } from '@material-ui/lab'
import { handleCopy } from './CopyToClipboard'
import { truncate } from '../utils'
import { useHistory } from 'react-router'
export const BalanceCard = () => {
const { getBalance } = useContext(ClientContext)
const theme = useTheme()
useEffect(getBalance.fetchBalance, [])
return (
<div style={{ margin: theme.spacing(3) }}>
<NymCard
title="Balance"
subheader="Current wallet balance"
noPadding
Action={
<Tooltip title="Refresh balance">
<IconButton onClick={getBalance.fetchBalance} size="small">
<Refresh />
</IconButton>
</Tooltip>
}
>
<CardContent>
<div style={{ display: 'flex', justifyContent: 'center' }}>
{getBalance.isLoading ? (
<CircularProgress size={24} />
) : getBalance.error ? (
<Alert severity="error" style={{ width: '100%' }}>
{getBalance.error}
</Alert>
) : (
<Typography variant="h6">
{getBalance.balance?.printable_balance}
</Typography>
)}
</div>
</CardContent>
</NymCard>
</div>
)
}
enum EnumCopyState {
copying,
copySuccess,
}
export const AddressCard = () => {
const { clientDetails } = useContext(ClientContext)
const [copyState, setCopyState] = useState<EnumCopyState>()
const theme = useTheme()
return (
<div style={{ margin: theme.spacing(3) }}>
<NymCard
title="Address"
subheader="Wallet payments address"
noPadding
Action={
<Tooltip title={!copyState ? 'Copy address' : 'Copied'}>
<span>
<IconButton
disabled={!!copyState}
onClick={async () => {
setCopyState(EnumCopyState.copying)
await handleCopy({
text: clientDetails?.client_address || '',
cb: (isCopied) => {
if (isCopied) {
setCopyState(EnumCopyState.copySuccess)
setTimeout(() => {
setCopyState(undefined)
}, 2500)
}
},
})
}}
>
{copyState === EnumCopyState.copying ? (
<CircularProgress size={24} />
) : copyState === EnumCopyState.copySuccess ? (
<CheckCircleOutline
style={{ color: theme.palette.success.main }}
/>
) : (
<FileCopy />
)}
</IconButton>
</span>
</Tooltip>
}
>
<CardContent>
<Typography
style={{ fontWeight: theme.typography.fontWeightRegular }}
>
{truncate(clientDetails?.client_address!, 35)}
</Typography>
</CardContent>
</NymCard>
</div>
)
}
export const SockS5 = () => {
const theme: Theme = useTheme()
const history = useHistory()
const { ss5IsActive, bandwidthLimit, toggleSs5 } = useContext(ClientContext)
if (bandwidthLimit === 0) return null
return (
<div style={{ margin: theme.spacing(3) }}>
<NymCard
title="Socks5"
Icon={
<IconButton onClick={toggleSs5}>
<PowerSettingsNew
style={{
color: ss5IsActive
? theme.palette.success.main
: theme.palette.error.main,
}}
/>
</IconButton>
}
Action={
<Box style={{ marginTop: theme.spacing(1) }}>
<IconButton onClick={() => history.push('/socks5')}>
<ArrowForwardSharp />
</IconButton>
</Box>
}
/>
</div>
)
}
@@ -0,0 +1,13 @@
import React from 'react'
import { Link } from 'react-router-dom'
import { Alert, AlertTitle } from '@material-ui/lab'
export const NoClientError = () => {
return (
<Alert severity="error">
<AlertTitle>No client detected</AlertTitle>
Have you signed in? Try to go back to{' '}
<Link to="/signin">the main page</Link> and try again
</Alert>
)
}
@@ -0,0 +1,48 @@
import {
FormControl,
FormControlLabel,
FormLabel,
Radio,
RadioGroup,
} from '@material-ui/core'
import React from 'react'
import { EnumNodeType } from '../types/global'
export const NodeTypeSelector = ({
disabled,
nodeType,
setNodeType,
}: {
disabled: boolean
nodeType: EnumNodeType
setNodeType: (nodeType: EnumNodeType) => void
}) => {
const handleNodeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) =>
setNodeType(e.target.value as EnumNodeType)
return (
<FormControl component="fieldset">
<FormLabel component="legend">Select node type</FormLabel>
<RadioGroup
aria-label="nodeType"
name="nodeTypeRadio"
value={nodeType}
onChange={handleNodeTypeChange}
style={{ display: 'block' }}
>
<FormControlLabel
value={EnumNodeType.mixnode}
control={<Radio />}
label="Mixnode"
disabled={disabled}
/>
<FormControlLabel
value={EnumNodeType.gateway}
control={<Radio />}
label="Gateway"
disabled={disabled}
/>
</RadioGroup>
</FormControl>
)
}
+39
View File
@@ -0,0 +1,39 @@
import React from 'react'
import { Card, CardContent, CardHeader, useTheme } from '@material-ui/core'
export const NymCard: React.FC<{
title: string
subheader?: string
Action?: React.ReactNode
Icon?: React.ReactNode
noPadding?: boolean
style?: {}
}> = ({ title, subheader, Action, noPadding, Icon, style = {}, children }) => {
const theme = useTheme()
return (
<Card variant="outlined" style={{ ...style }}>
<CardHeader
title={title}
subheader={subheader}
titleTypographyProps={{ variant: 'h5' }}
subheaderTypographyProps={{ variant: 'subtitle1' }}
action={Action}
avatar={Icon}
style={{
padding: theme.spacing(2.5),
borderBottom: `1px solid ${theme.palette.grey[200]}`,
}}
/>
{children && (
<CardContent
style={{
background: theme.palette.grey[50],
padding: noPadding ? 0 : theme.spacing(2, 5),
}}
>
{children}
</CardContent>
)}
</Card>
)
}
@@ -0,0 +1,33 @@
import React from 'react'
import { CircularProgress, Theme } from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
export enum EnumRequestStatus {
initial = 'initial',
error = 'error',
loading = 'loading',
success = 'success',
}
export const RequestStatus = ({
status,
Success,
Error,
}: {
status: EnumRequestStatus
Success: React.ReactNode
Error: React.ReactNode
}) => {
const theme: Theme = useTheme()
return (
<div style={{ padding: theme.spacing(3, 5) }}>
{status === EnumRequestStatus.loading && (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={48} />
</div>
)}
{status === EnumRequestStatus.success && Success}
{status === EnumRequestStatus.error && Error}
</div>
)
}
+10
View File
@@ -0,0 +1,10 @@
export * from './AdminForm'
export * from './Error'
export * from './Confirmation'
export * from './CopyToClipboard'
export * from './NymCard'
export * from './Nav'
export * from './NavigationCards'
export * from './NodeTypeSelector'
export * from './RequestStatus'
export * from './NoClientError'
+110
View File
@@ -0,0 +1,110 @@
import React, { createContext, useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom'
import { useSnackbar } from 'notistack'
import { TClientDetails, TSignInWithMnemonic } from '../types'
import { TUseGetBalance, useGetBalance } from '../hooks/useGetBalance'
import { Button } from '@material-ui/core'
import { theme } from '../theme'
export const ADMIN_ADDRESS = 'punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0'
type TClientContext = {
clientDetails?: TClientDetails
getBalance: TUseGetBalance
showAdmin: boolean
ss5IsActive: boolean
bandwidthLimit: number
bandwidthUsed: number
handleSetBandwidthLimit: (bandwidth: number) => void
toggleSs5: () => void
handleShowAdmin: () => void
logIn: (clientDetails: TSignInWithMnemonic) => void
logOut: () => void
}
export const ClientContext = createContext({} as TClientContext)
export const ClientContextProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const [clientDetails, setClientDetails] = useState<TClientDetails>()
const [showAdmin, setShowAdmin] = useState(false)
const [ss5IsActive, setss5IsActive] = useState(false)
const [bandwidthLimit, setBandwidthLimit] = useState(0)
const [bandwidthUsed, setBandwidthUsed] = useState(0)
const history = useHistory()
const getBalance = useGetBalance()
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
useEffect(() => {
!clientDetails ? history.push('/signin') : history.push('/balance')
}, [clientDetails])
const handleSetBandwidthLimit = (bandwidth: number) =>
setBandwidthLimit(bandwidth)
useEffect(() => {
let timer
if (ss5IsActive && bandwidthUsed < bandwidthLimit) {
timer = setTimeout(() => {
setBandwidthUsed((used) => used + 50)
}, 1000)
} else if (ss5IsActive && bandwidthUsed === bandwidthLimit) {
setBandwidthLimit(0)
setBandwidthUsed(0)
setss5IsActive(false)
enqueueSnackbar(
"You're out of bandwidth. You'll need to purchase more to continue using Socks5",
{
variant: 'error',
anchorOrigin: { horizontal: 'center', vertical: 'bottom' },
persist: true,
action: (key) => (
<Button
style={{
color: theme.palette.common.white,
}}
onClick={() => closeSnackbar(key)}
>
Dismiss
</Button>
),
}
)
clearTimeout(timer)
}
}, [ss5IsActive, bandwidthUsed, bandwidthLimit, handleSetBandwidthLimit])
const logIn = async (clientDetails: TSignInWithMnemonic) =>
setClientDetails(clientDetails)
const logOut = () => setClientDetails(undefined)
const handleShowAdmin = () => setShowAdmin((show) => !show)
const toggleSs5 = () => setss5IsActive((active) => !active)
return (
<ClientContext.Provider
value={{
clientDetails,
getBalance,
showAdmin,
ss5IsActive,
bandwidthLimit,
bandwidthUsed,
toggleSs5,
handleSetBandwidthLimit,
handleShowAdmin,
logIn,
logOut,
}}
>
{children}
</ClientContext.Provider>
)
}
@@ -0,0 +1,39 @@
import { useEffect, useState } from 'react'
import { checkGatewayOwnership, checkMixnodeOwnership } from '../requests'
import { EnumNodeType, TNodeOwnership } from '../types'
export const useCheckOwnership = () => {
const [ownership, setOwnership] = useState<TNodeOwnership>({
hasOwnership: false,
nodeType: undefined,
})
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string>()
const checkOwnership = async () => {
const status = {} as TNodeOwnership
setIsLoading(true)
try {
const ownsMixnode = await checkMixnodeOwnership()
const ownsGateway = await checkGatewayOwnership()
if (ownsMixnode) {
status.hasOwnership = true
status.nodeType = EnumNodeType.mixnode
}
if (ownsGateway) {
status.hasOwnership = true
status.nodeType = EnumNodeType.gateway
}
setOwnership(status)
} catch (e) {
setError(e as string)
}
}
return { isLoading, error, ownership, checkOwnership }
}
+36
View File
@@ -0,0 +1,36 @@
import { useState } from 'react'
import { invoke } from '@tauri-apps/api'
import { Balance } from '../types'
export type TUseGetBalance = {
error?: string
balance?: Balance
isLoading: boolean
fetchBalance: () => void
}
export const useGetBalance = (): TUseGetBalance => {
const [balance, setBalance] = useState<Balance>()
const [error, setError] = useState<string>()
const [isLoading, setIsLoading] = useState(false)
const fetchBalance = () => {
setIsLoading(true)
setError(undefined)
invoke('get_balance')
.then((balance) => {
setBalance(balance as Balance)
})
.catch(setError)
setTimeout(() => {
setIsLoading(false)
}, 1000)
}
return {
error,
isLoading,
balance,
fetchBalance,
}
}
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 5389.9 5389.9" style="enable-background:new 0 0 5389.9 5389.9;" xml:space="preserve">
<style type="text/css">
.st0{fill:#121726;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:#FFFFFF;}
</style>
<g>
<g>
<circle class="st0" cx="2695" cy="2695" r="2585"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0" y1="8058.165" x2="5390" y2="8058.165" gradientTransform="matrix(1 0 0 -1 0 10753.165)">
<stop offset="0" style="stop-color:#F77846"/>
<stop offset="1" style="stop-color:#ED3572"/>
</linearGradient>
<path class="st1" d="M2695,5390c-182.8,0-365.5-18.4-543-54.8c-173.1-35.4-343.3-88.3-506-157.1
c-159.7-67.6-313.7-151.2-457.8-248.5c-142.7-96.4-276.8-207.1-398.8-329c-121.9-121.9-232.6-256.1-329-398.8
C363,4057.8,279.4,3903.8,211.8,3744C143,3581.4,90.2,3411.1,54.8,3238C18.4,3060.5,0,2877.8,0,2695c0-182.8,18.4-365.5,54.8-543
c35.4-173.1,88.3-343.3,157.1-506c67.6-159.7,151.2-313.7,248.5-457.8c96.4-142.7,207.1-276.8,329-398.8s256.1-232.6,398.8-329
c144.1-97.3,298.1-180.9,457.8-248.5c162.7-68.8,332.9-121.7,506-157.1C2329.5,18.4,2512.2,0,2695,0c182.8,0,365.5,18.4,543,54.8
c173.1,35.4,343.3,88.3,506,157.1c159.7,67.6,313.7,151.2,457.8,248.5c142.7,96.4,276.8,207.1,398.8,329
c121.9,121.9,232.6,256.1,329,398.8c97.3,144.1,180.9,298.1,248.5,457.8c68.8,162.7,121.7,332.9,157.1,506
c36.3,177.5,54.8,360.2,54.8,543c0,182.8-18.4,365.5-54.8,543c-35.4,173.1-88.3,343.3-157.1,506
c-67.6,159.7-151.2,313.7-248.5,457.8c-96.4,142.7-207.1,276.8-329,398.8c-121.9,121.9-256.1,232.6-398.8,329
c-144.1,97.3-298.1,180.9-457.8,248.5c-162.7,68.8-332.9,121.7-506,157.1C3060.5,5371.6,2877.8,5390,2695,5390z M2695,220
c-168,0-335.9,16.9-498.9,50.3c-158.9,32.5-315.1,81-464.4,144.2c-146.6,62-288.1,138.8-420.4,228.2
c-131.1,88.6-254.3,190.3-366.4,302.3c-112,112-213.7,235.3-302.3,366.4c-89.4,132.3-166.2,273.7-228.2,420.4
c-63.2,149.3-111.7,305.6-144.2,464.4C236.9,2359.1,220,2527,220,2695s16.9,335.9,50.3,498.9c32.5,158.9,81,315.1,144.2,464.4
c62,146.6,138.8,288.1,228.2,420.4c88.6,131.1,190.3,254.3,302.3,366.4c112,112,235.3,213.7,366.4,302.3
c132.3,89.4,273.7,166.2,420.4,228.2c149.3,63.2,305.6,111.7,464.4,144.2c163.1,33.4,330.9,50.3,498.9,50.3s335.9-16.9,498.9-50.3
c158.9-32.5,315.1-81,464.4-144.2c146.6-62,288.1-138.8,420.4-228.2c131.1-88.6,254.3-190.3,366.4-302.3
c112-112,213.7-235.3,302.3-366.4c89.4-132.3,166.2-273.7,228.2-420.4c63.2-149.3,111.7-305.6,144.2-464.4
c33.4-163.1,50.3-330.9,50.3-498.9s-16.9-335.9-50.3-498.9c-32.5-158.9-81-315.1-144.2-464.4c-62-146.6-138.8-288.1-228.2-420.4
c-88.6-131.1-190.3-254.3-302.3-366.4c-112-112-235.3-213.7-366.4-302.3c-132.3-89.4-273.7-166.2-420.4-228.2
c-149.3-63.2-305.6-111.7-464.4-144.2C3030.9,236.9,2863,220,2695,220z"/>
</g>
</g>
<path class="st2" d="M1958.5,3160.4h-269.6l-735.8-725.3v725.3H734.6v-930.9h276.2l735.8,725.1v-725.1h211.9V3160.4z M4378.9,2229.5
l-335.7,330.9l-335.7-330.9h-276.2v930.9h218.4v-725.3l345.4,340.6c26.7,26.3,69.6,26.3,96.3,0l345.4-340.6v725.3h218.4v-930.9
H4378.9z M2589.1,2715.4v445h218.4v-445l502.7-485.9H3034l-335.9,330.9l-335.7-330.9h-276.2L2589.1,2715.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

+3
View File
@@ -0,0 +1,3 @@
declare module '*.jpg'
declare module '*.png'
declare module '*.svg'
+51
View File
@@ -0,0 +1,51 @@
import React, { useContext } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom'
import { ErrorBoundary } from 'react-error-boundary'
import { CssBaseline, ThemeProvider } from '@material-ui/core'
import { Routes } from './routes'
import { theme } from './theme'
import { ClientContext, ClientContextProvider } from './context/main'
import { ApplicationLayout } from './layouts'
import { SignIn } from './routes/sign-in'
import { Admin, ErrorFallback } from './components'
import { SnackbarProvider } from 'notistack'
const Pages = () => {
const { clientDetails } = useContext(ClientContext)
return (
<>
{!clientDetails ? (
<SignIn />
) : (
<ApplicationLayout>
<>
<Admin />
<Routes />
</>
</ApplicationLayout>
)}
</>
)
}
const App = () => {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ThemeProvider theme={theme}>
<SnackbarProvider maxSnack={3}>
<CssBaseline />
<Router>
<ClientContextProvider>
<Pages />
</ClientContextProvider>
</Router>
</SnackbarProvider>
</ThemeProvider>
</ErrorBoundary>
)
}
const root = document.getElementById('root')
ReactDOM.render(<App />, root)
+72
View File
@@ -0,0 +1,72 @@
import React from 'react'
import { Divider } from '@material-ui/core'
import { AddressCard, BalanceCard, Nav, SockS5 } from '../components'
import Logo from '../images/logo-background.svg'
import { theme } from '../theme'
export const ApplicationLayout = ({
children,
}: {
children: React.ReactElement
}) => {
return (
<div
style={{
height: '100vh',
width: '100vw',
display: 'grid',
gridTemplateColumns: '400px auto',
gridTemplateRows: '100%',
gridColumnGap: '8px',
gridRowGap: '0px',
overflow: 'hidden',
}}
>
<div
style={{
gridArea: '1 / 1 / 2 / 2',
background: '#121726',
overflow: 'auto',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: theme.spacing(6),
}}
>
<img src={Logo} style={{ width: 75 }} />
</div>
<Divider
light
variant="middle"
style={{
background: theme.palette.grey[100],
marginTop: theme.spacing(6),
}}
/>
<div style={{ marginTop: theme.spacing(10) }}>
<BalanceCard />
<AddressCard />
<SockS5 />
</div>
<div style={{ marginTop: theme.spacing(7) }}>
<Nav />
</div>
<div />
</div>
<div
style={{
gridArea: '1 / 2 / 2 / 3',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{children}
</div>
</div>
)
}
@@ -0,0 +1,26 @@
import React from 'react'
import { Grid, Theme, useTheme } from '@material-ui/core'
export const Layout = ({ children }: { children: React.ReactElement }) => {
const theme: Theme = useTheme()
return (
<div
style={{
padding: theme.spacing(5),
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'auto',
}}
>
<Grid container justifyContent="center" style={{ margin: '0 auto' }}>
<Grid item xs={12} md={8}>
{children}
</Grid>
</Grid>
</div>
)
}
+2
View File
@@ -0,0 +1,2 @@
export * from './AppLayout'
export * from './ContentLayout'
+85
View File
@@ -0,0 +1,85 @@
import { invoke } from '@tauri-apps/api'
import {
Balance,
Coin,
DelegationResult,
EnumNodeType,
Gateway,
MixNode,
Operation,
TauriStateParams,
TauriTxResult,
TCreateAccount,
TSignInWithMnemonic,
} from '../types'
export const createAccount = async (): Promise<TCreateAccount> =>
await invoke('create_new_account')
export const signInWithMnemonic = async (
mnemonic: string
): Promise<TSignInWithMnemonic> =>
await invoke('connect_with_mnemonic', { mnemonic })
export const minorToMajor = async (amount: string): Promise<Coin> =>
await invoke('minor_to_major', { amount })
export const majorToMinor = async (amount: string): Promise<Coin> =>
await invoke('major_to_minor', { amount })
export const getGasFee = async (operation: Operation): Promise<Coin> =>
await invoke('get_fee', { operation })
export const delegate = async ({
type,
identity,
amount,
}: {
type: EnumNodeType
identity: string
amount: Coin
}): Promise<DelegationResult> =>
await invoke(`delegate_to_${type}`, { identity, amount })
export const undelegate = async ({
type,
identity,
}: {
type: EnumNodeType
identity: string
}): Promise<DelegationResult> =>
await invoke(`undelegate_from_${type}`, { identity })
export const send = async (args: {
amount: Coin
address: string
memo: string
}): Promise<TauriTxResult> => await invoke('send', args)
export const checkMixnodeOwnership = async (): Promise<boolean> =>
await invoke('owns_mixnode')
export const checkGatewayOwnership = async (): Promise<boolean> =>
await invoke('owns_gateway')
export const bond = async ({
type,
data,
amount,
}: {
type: EnumNodeType
data: MixNode | Gateway
amount: Coin
}): Promise<any> => await invoke(`bond_${type}`, { [type]: data, bond: amount })
export const unbond = async (type: EnumNodeType) =>
await invoke(`unbond_${type}`)
export const getBalance = async (): Promise<Balance> =>
await invoke('get_balance')
export const getContractParams = async (): Promise<TauriStateParams> =>
await invoke('get_state_params')
export const setContractParams = async (
params: TauriStateParams
): Promise<TauriStateParams> => await invoke('update_state_params', { params })
+12
View File
@@ -0,0 +1,12 @@
import React, { useState } from 'react'
import { Layout } from '../layouts'
export const NotFound = () => {
return (
<Layout>
<>
<h1>404</h1>
</>
</Layout>
)
}
+135
View File
@@ -0,0 +1,135 @@
import React from 'react'
import {
Box,
Button,
Card,
CardContent,
CardHeader,
Chip,
IconButton,
Theme,
Typography,
} from '@material-ui/core'
import {
Cancel,
CheckCircle,
PowerSettingsNew,
PowerSettingsNewSharp,
SecuritySharp,
} from '@material-ui/icons'
import { useTheme } from '@material-ui/styles'
const ActiveChip = () => {
const theme: Theme = useTheme()
return (
<Chip
label="Secure"
style={{
color: theme.palette.common.white,
backgroundColor: theme.palette.success.main,
}}
icon={<CheckCircle style={{ color: theme.palette.common.white }} />}
/>
)
}
const InactiveChip = () => {
const theme: Theme = useTheme()
return (
<Chip
label="Offline"
style={{
color: theme.palette.common.white,
backgroundColor: theme.palette.error.main,
}}
icon={<Cancel style={{ color: theme.palette.common.white }} />}
/>
)
}
export const TopCard: React.FC<{
isActive: boolean
disabled: boolean
plan: string
toggleIsActive: () => void
}> = ({ isActive, disabled, plan, toggleIsActive }) => {
const theme: Theme = useTheme()
return (
<Card style={{ padding: theme.spacing(1.5) }} variant="outlined">
<CardHeader
title={<Typography variant="h5">Package: {plan}</Typography>}
avatar={isActive ? <ActiveChip /> : <InactiveChip />}
action={
<IconButton
onClick={toggleIsActive}
disabled={disabled}
style={
!disabled
? {
color: isActive
? theme.palette.success.main
: theme.palette.error.main,
}
: {}
}
>
<PowerSettingsNew />
</IconButton>
}
/>
</Card>
)
}
export const MainCard: React.FC<{
isActive: boolean
disabled?: boolean
buyBandwidth: () => void
toggleIsActive: () => void
}> = ({ isActive, disabled, buyBandwidth, toggleIsActive }) => {
const theme: Theme = useTheme()
return (
<div style={{ position: 'relative', width: '100%' }}>
<Card variant={'outlined'} style={{ padding: theme.spacing(2) }}>
<CardHeader
title={<Typography> SOCKS5</Typography>}
subheader={
isActive
? "You're protected with SOCKS5"
: 'SOCKS5 is not currently active'
}
/>
<CardContent>
<Box display="flex" justifyContent="flex-end">
<Button
color="primary"
variant="contained"
endIcon={<SecuritySharp />}
style={{
color: theme.palette.common.white,
marginRight: theme.spacing(1.5),
}}
size="large"
disableElevation
onClick={buyBandwidth}
>
Puchase bandwidth
</Button>
<Button
variant="outlined"
color="primary"
endIcon={<PowerSettingsNewSharp />}
size="large"
disableElevation
onClick={toggleIsActive}
disabled={disabled}
>
{isActive ? 'Disabled' : 'Enable'}
</Button>
</Box>
</CardContent>
</Card>
</div>
)
}
@@ -0,0 +1,60 @@
import React, { useContext } from 'react'
import { Box, Grid, Theme } from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
import { NymCard } from '../../components'
import { ClientContext } from '../../context/main'
import { MainCard, TopCard } from './Cards'
import { InboundCard, LimitCard, OutboundCard } from './DataCards'
import { Info } from './Info'
type TDashboardProps = {
plan: string
buyBandwidth: () => void
}
export const Dashboard: React.FC<TDashboardProps> = ({
plan,
buyBandwidth,
}) => {
const { ss5IsActive, toggleSs5, bandwidthLimit, bandwidthUsed } =
useContext(ClientContext)
const theme: Theme = useTheme()
return (
<NymCard
title="SOCKS5 Dashboard"
subheader="Monitor your SOCKS5 usage"
Action={<Info />}
>
<Box padding={theme.spacing(0.5)}>
<Grid container spacing={6}>
<Grid item xs={12}>
<TopCard
isActive={ss5IsActive}
toggleIsActive={toggleSs5}
plan={plan}
disabled={bandwidthLimit === bandwidthUsed}
/>
</Grid>
<Grid item xs={12}>
<MainCard
isActive={ss5IsActive}
toggleIsActive={toggleSs5}
disabled={bandwidthLimit === bandwidthUsed}
buyBandwidth={buyBandwidth}
/>
</Grid>
<Grid item xs={4}>
<OutboundCard isActive={ss5IsActive} />
</Grid>
<Grid item xs={4}>
<InboundCard isActive={ss5IsActive} />
</Grid>
<Grid item xs={4}>
<LimitCard isActive={ss5IsActive} />
</Grid>
</Grid>
</Box>
</NymCard>
)
}
@@ -0,0 +1,170 @@
import React, { useContext } from 'react'
import {
Box,
Card,
CardContent,
CardHeader,
CircularProgress,
Grid,
Theme,
Typography,
} from '@material-ui/core'
import { ToggleData } from './Toggle'
import { ArrowDownwardOutlined, ArrowUpwardOutlined } from '@material-ui/icons'
import { makeStyles } from '@material-ui/styles'
import clsx from 'clsx'
import { ClientContext } from '../../context/main'
const useStyles = makeStyles((theme: Theme) => ({
card: {
padding: theme.spacing(2),
height: 250,
},
icon: {
fontSize: 60,
},
iconActive: {
color: theme.palette.primary.main,
},
iconInactive: {
color: theme.palette.grey[800],
},
}))
export const OutboundCard: React.FC<{ isActive?: boolean }> = ({
isActive,
}) => {
const classes = useStyles()
return (
<Card className={classes.card} variant="outlined">
<CardHeader title="Outbound" action={<ToggleData />} />
<CardContent>
<Grid container direction="column" alignItems="center">
<Grid item>
<ArrowUpwardOutlined
className={clsx(
classes.icon,
isActive ? classes.iconActive : classes.iconInactive
)}
/>
</Grid>
<Grid item>
{!isActive ? (
<Typography variant="h3">-</Typography>
) : (
<>
<Typography variant="h3">
298
<Typography component="span" color="textSecondary">
mb
</Typography>
</Typography>
</>
)}
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export const InboundCard: React.FC<{ isActive?: boolean }> = ({ isActive }) => {
const classes = useStyles()
const { bandwidthUsed } = useContext(ClientContext)
return (
<Card className={classes.card} variant="outlined">
<CardHeader title="Inbound" action={<ToggleData />} />
<CardContent>
<Grid container direction="column" alignItems="center">
<Grid item>
<ArrowDownwardOutlined
className={clsx(
classes.icon,
isActive ? classes.iconActive : classes.iconInactive
)}
/>
</Grid>
<Grid item>
{!isActive ? (
<Typography variant="h3">-</Typography>
) : (
<>
<Typography variant="h3">
{bandwidthUsed}
<Typography component="span" color="textSecondary">
mb
</Typography>
</Typography>
</>
)}
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export const LimitCard: React.FC<{ isActive: boolean }> = ({ isActive }) => {
const classes = useStyles()
const { bandwidthLimit, bandwidthUsed } = useContext(ClientContext)
return (
<Card className={classes.card} variant="outlined">
<CardHeader title="Usage" action={<ToggleData />} />
<Grid container direction="column" alignItems="center">
<Grid item>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={100}
size={120}
style={{
color: isActive ? '#eee' : '#aaa',
transition: 'color 0.5s ease-in-out',
}}
/>
<Box>
<CircularProgress
variant="determinate"
value={!isActive ? 0 : (bandwidthUsed / bandwidthLimit) * 100}
size={120}
style={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
}}
/>
</Box>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{' '}
{!isActive ? (
<Typography variant="h3">-</Typography>
) : (
<>
<Typography variant="h5">{bandwidthLimit}</Typography>
<Typography variant="caption" color="textSecondary">
mb
</Typography>
</>
)}
</Box>
</Box>
</Grid>
</Grid>
</Card>
)
}
+46
View File
@@ -0,0 +1,46 @@
import React, { useState } from 'react'
import { Box, IconButton, Popover, Theme, Typography } from '@material-ui/core'
import { HelpOutlineSharp } from '@material-ui/icons'
import { useTheme } from '@material-ui/styles'
export const Info = () => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>()
const open = Boolean(anchorEl)
const theme: Theme = useTheme()
return (
<>
<IconButton
size="small"
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(() => event.currentTarget)
}}
>
<HelpOutlineSharp />
</IconButton>
<Popover
open={open}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<Box padding={theme.spacing(0.5)} maxWidth="400px">
<Typography variant="h6" style={{ marginBottom: theme.spacing(1) }}>
What is SOCKS5?
</Typography>
<Typography variant="body2">
A SOCKS5 proxy is a private alternative to a VPN that protects the
traffic within a specific source, such as an application. When you
use a SOCKS5 proxy, data packets from the configured source are
routed through a remote server. This server changes the IP address
associated with these data packets before they reach their final
destination
</Typography>
</Box>
</Popover>
</>
)
}
+168
View File
@@ -0,0 +1,168 @@
import React, { useState } from 'react'
import {
Box,
Button,
Card,
CardActions,
CardContent,
CardHeader,
Chip,
Grid,
TextField,
Theme,
Typography,
} from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
import { SecuritySharp } from '@material-ui/icons'
import { Autocomplete } from '@material-ui/lab'
import { NymCard } from '../../components'
import { Info } from './Info'
type TSetupProps = {
handleSelectPlan: (plan: string) => void
}
export const Setup: React.FC<TSetupProps> = ({ handleSelectPlan }) => {
const theme: Theme = useTheme()
const [userSelection, setUserSelection] = useState<string | null>(null)
return (
<NymCard
title="SOCKS5 - Purchase bandwidth"
subheader="Purchase badwidth to get started with SOCKS5"
Action={<Info />}
>
<Box padding={theme.spacing(0.5)}>
<Grid container direction="column" alignItems="center" spacing={5}>
<Grid item container spacing={3} justifyContent="space-evenly">
<Grid item xs={12} lg={3}>
<OptionCard
title="500MB"
cost="500 PUNK"
onSelect={handleSelectPlan}
/>
</Grid>
<Grid item xs={12} lg={3}>
<OptionCard
title="1GB"
cost="750 PUNK"
onSelect={handleSelectPlan}
/>
</Grid>
<Grid item xs={12} lg={3}>
<OptionCard
title="10GB"
cost="7000 PUNK"
onSelect={handleSelectPlan}
/>
</Grid>
</Grid>
<Grid item>
<Typography variant="h5">- OR -</Typography>
</Grid>
<Grid
container
item
justifyContent="center"
alignItems="center"
style={{ margin: 'auto' }}
>
<Grid item>
<Autocomplete
disablePortal
onChange={(_, val: string | null) => setUserSelection(val)}
style={{ width: 500 }}
id="bandwidth-options"
options={[
'1MB',
'25MB',
'50MB',
'100MB',
'500MB',
'1GB',
'5GB',
'10GB',
'20GB',
'50GB',
]}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="Other options"
/>
)}
/>
</Grid>
<Grid item>
<Button
onClick={() =>
userSelection ? handleSelectPlan(userSelection) : undefined
}
variant="outlined"
color="primary"
size="small"
disabled={!userSelection}
style={{ marginLeft: theme.spacing(1) }}
>
Select
</Button>
</Grid>
</Grid>
</Grid>
</Box>
</NymCard>
)
}
type TOptionProps = {
title: string
cost: string
isPrimary?: boolean
onSelect: TSetupProps['handleSelectPlan']
}
const OptionCard: React.FC<TOptionProps> = ({
title,
cost,
isPrimary,
onSelect,
}) => {
const theme: Theme = useTheme()
return (
<Card
variant="outlined"
style={{ padding: theme.spacing(2), position: 'relative', width: 300 }}
>
<CardHeader
title={
<Box display="flex" alignItems="end" justifyContent="flex-start">
<SecuritySharp
style={{ marginRight: theme.spacing(0.5) }}
color="action"
/>
<Typography variant="h5">{title}</Typography>
</Box>
}
color="primary"
action={<Chip label={cost} />}
/>
<CardActions>
<Box display="flex" justifyContent="center" width="100%">
<Button
color="primary"
variant={isPrimary ? 'contained' : 'outlined'}
disableElevation
size="small"
onClick={() => onSelect(title)}
>
Select
</Button>
</Box>
</CardActions>
</Card>
)
}
+64
View File
@@ -0,0 +1,64 @@
import React, { useState } from 'react'
import { Grid, Paper, Theme, Typography } from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
enum EnumOptions {
Mb = 'Mb',
Gb = 'Gb',
}
const ToggleOption = ({
title,
isSelected,
setSelected,
}: {
title: EnumOptions
isSelected: boolean
setSelected: (selection: EnumOptions) => void
}) => {
const theme: Theme = useTheme()
return (
<Typography
variant="caption"
onClick={() => setSelected(title)}
style={{
cursor: 'pointer',
color: isSelected ? theme.palette.grey[900] : theme.palette.grey[500],
}}
>
{title}
</Typography>
)
}
export const ToggleData = () => {
const theme: Theme = useTheme()
const [selected, setSeleted] = useState(EnumOptions['Mb'])
return (
<Paper
elevation={0}
style={{
width: 75,
backgroundColor: theme.palette.grey[100],
}}
>
<Grid container spacing={1} justifyContent="center">
<Grid item>
<ToggleOption
title={EnumOptions['Mb']}
isSelected={selected === EnumOptions['Mb']}
setSelected={(selection) => setSeleted(selection)}
/>
</Grid>
<Grid item>
<ToggleOption
title={EnumOptions['Gb']}
isSelected={selected === EnumOptions['Gb']}
setSelected={(selection) => setSeleted(selection)}
/>
</Grid>
</Grid>
</Paper>
)
}
+53
View File
@@ -0,0 +1,53 @@
import React, { useContext, useState } from 'react'
import { Box } from '@material-ui/core'
import { SecuritySharp } from '@material-ui/icons'
import { Dashboard } from './Dashboard'
import { Layout } from '../../layouts'
import { Setup } from './Setup'
import { theme } from '../../theme'
import { Loading } from '../../components/Loading'
import { ClientContext } from '../../context/main'
export const Socks5 = () => {
const [isLoading, setIsLoading] = useState(false)
const [plan, setPlan] = useState<string>()
const { handleSetBandwidthLimit } = useContext(ClientContext)
return (
<Layout>
<>
{isLoading && (
<Box
display="flex"
alignItems="center"
justifyContent="center"
padding={theme.spacing(1)}
>
<Loading
size="x-large"
Icon={<SecuritySharp color="primary" style={{ fontSize: 24 }} />}
/>
</Box>
)}
{!isLoading && !!plan && (
<Dashboard plan={plan} buyBandwidth={() => setPlan(undefined)} />
)}
{!isLoading && !plan && (
<Setup
handleSelectPlan={(plan: string) => {
setIsLoading(true)
setTimeout(() => {
setIsLoading(false)
setPlan(plan)
handleSetBandwidthLimit(500)
}, 2000)
}}
/>
)}
</>
</Layout>
)
}
+60
View File
@@ -0,0 +1,60 @@
import React, { useEffect } from 'react'
import { Button, CircularProgress, Grid } from '@material-ui/core'
import { Alert } from '@material-ui/lab'
import { Refresh } from '@material-ui/icons'
import { NymCard } from '../components'
import { Layout } from '../layouts'
import { theme } from '../theme'
import { useGetBalance } from '../hooks/useGetBalance'
export const Balance = () => {
const { balance, isLoading, error, fetchBalance } = useGetBalance()
useEffect(fetchBalance, [])
const RefreshAction = () => (
<Button
variant="contained"
size="small"
color="primary"
type="submit"
onClick={fetchBalance}
disabled={isLoading}
disableElevation
startIcon={<Refresh />}
endIcon={isLoading && <CircularProgress size={20} />}
style={{ marginRight: theme.spacing(2) }}
>
Refresh
</Button>
)
return (
<Layout>
<NymCard title="Check Balance">
<Grid container direction="column" spacing={2}>
<Grid item>
{error && (
<Alert
severity="error"
action={<RefreshAction />}
style={{ padding: theme.spacing(2) }}
>
{error}
</Alert>
)}
{!error && (
<Alert
severity="success"
style={{ padding: theme.spacing(2, 3) }}
action={<RefreshAction />}
>
{'The current balance is ' + balance?.printable_balance}
</Alert>
)}
</Grid>
</Grid>
</NymCard>
</Layout>
)
}
+381
View File
@@ -0,0 +1,381 @@
import React, { useContext } from 'react'
import {
Button,
Checkbox,
CircularProgress,
FormControl,
FormControlLabel,
Grid,
InputAdornment,
TextField,
Theme,
} from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
import { Alert } from '@material-ui/lab'
import { yupResolver } from '@hookform/resolvers/yup'
import { useForm } from 'react-hook-form'
import { EnumNodeType } from '../../types/global'
import { NodeTypeSelector } from '../../components/NodeTypeSelector'
import { bond, majorToMinor } from '../../requests'
import { validationSchema } from './validationSchema'
import { Coin, Gateway, MixNode } from '../../types'
import { ClientContext } from '../../context/main'
import { checkHasEnoughFunds } from '../../utils'
type TBondFormFields = {
withAdvancedOptions: boolean
nodeType: EnumNodeType
identityKey: string
sphinxKey: string
amount: string
host: string
version: string
location?: string
mixPort: number
verlocPort: number
clientsPort: number
httpApiPort: number
}
const defaultValues = {
withAdvancedOptions: false,
nodeType: EnumNodeType.mixnode,
identityKey: '',
sphinxKey: '',
amount: '',
host: '',
version: '',
location: undefined,
mixPort: 1789,
verlocPort: 1790,
httpApiPort: 8000,
clientsPort: 9000,
}
const formatData = (data: TBondFormFields) => {
const payload: { [key: string]: any } = {
identity_key: data.identityKey,
sphinx_key: data.sphinxKey,
host: data.host,
version: data.version,
mix_port: data.mixPort,
}
if (data.nodeType === EnumNodeType.mixnode) {
payload.verloc_port = data.verlocPort
payload.http_api_port = data.httpApiPort
return payload as MixNode
} else {
payload.clients_port = data.clientsPort
payload.location = data.location
return payload as Gateway
}
}
export const BondForm = ({
disabled,
fees,
onError,
onSuccess,
}: {
disabled: boolean
fees?: { [key in EnumNodeType]: Coin }
onError: (message?: string) => void
onSuccess: (message?: string) => void
}) => {
const {
register,
handleSubmit,
setValue,
setError,
watch,
formState: { errors, isSubmitting },
} = useForm<TBondFormFields>({
resolver: yupResolver(validationSchema),
defaultValues,
})
const { getBalance } = useContext(ClientContext)
const watchNodeType = watch('nodeType', defaultValues.nodeType)
const watchAdvancedOptions = watch(
'withAdvancedOptions',
defaultValues.withAdvancedOptions
)
const onSubmit = async (data: TBondFormFields) => {
const hasEnoughFunds = await checkHasEnoughFunds(data.amount)
if (!hasEnoughFunds) {
return setError('amount', { message: 'Not enough funds in wallet' })
}
const formattedData = formatData(data)
const amount = await majorToMinor(data.amount)
await bond({ type: data.nodeType, data: formattedData, amount })
.then(() => {
getBalance.fetchBalance()
onSuccess(`Successfully bonded to ${data.identityKey}`)
})
.catch((e) => {
onError(e)
})
}
const theme: Theme = useTheme()
return (
<FormControl fullWidth>
<div style={{ padding: theme.spacing(3, 5) }}>
<Grid container spacing={3}>
<Grid container item justifyContent="space-between">
<Grid item>
<NodeTypeSelector
nodeType={watchNodeType}
setNodeType={(nodeType) => {
setValue('nodeType', nodeType)
if (nodeType === EnumNodeType.mixnode)
setValue('location', undefined)
}}
disabled={disabled}
/>
</Grid>
{fees && (
<Grid item>
<Alert severity="info">
{`A fee of ${
watchNodeType === EnumNodeType.mixnode
? fees.mixnode.amount
: fees.gateway.amount
} PUNK will apply to this transaction`}
</Alert>
</Grid>
)}
</Grid>
<Grid item xs={12}>
<TextField
{...register('identityKey')}
variant="outlined"
required
id="identityKey"
name="identityKey"
label="Identity key"
fullWidth
error={!!errors.identityKey}
helperText={errors.identityKey?.message}
disabled={disabled}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('sphinxKey')}
variant="outlined"
required
id="sphinxKey"
name="sphinxKey"
label="Sphinx key"
error={!!errors.sphinxKey}
helperText={errors.sphinxKey?.message}
fullWidth
disabled={disabled}
/>
</Grid>
<Grid item xs={12} sm={9}>
<TextField
{...register('amount')}
variant="outlined"
required
id="amount"
name="amount"
label="Amount to bond"
fullWidth
error={!!errors.amount}
helperText={errors.amount?.message}
InputProps={{
endAdornment: (
<InputAdornment position="end">punks</InputAdornment>
),
}}
disabled={disabled}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
{...register('host')}
variant="outlined"
required
id="host"
name="host"
label="Host"
fullWidth
error={!!errors.host}
helperText={errors.host?.message}
disabled={disabled}
/>
</Grid>
{/* if it's a gateway - get location */}
<Grid item xs={6}>
{watchNodeType === EnumNodeType.gateway && (
<TextField
{...register('location')}
variant="outlined"
required
id="location"
name="location"
label="Location"
fullWidth
error={!!errors.location}
helperText={errors.location?.message}
disabled={disabled}
/>
)}
</Grid>
<Grid item xs={12} sm={6}>
<TextField
{...register('version')}
variant="outlined"
required
id="version"
name="version"
label="Version"
fullWidth
error={!!errors.version}
helperText={errors.version?.message}
disabled={disabled}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={
<Checkbox
checked={watchAdvancedOptions}
onChange={() => {
if (watchAdvancedOptions) {
setValue('mixPort', defaultValues.mixPort, {
shouldValidate: true,
})
setValue('clientsPort', defaultValues.clientsPort, {
shouldValidate: true,
})
setValue('verlocPort', defaultValues.verlocPort, {
shouldValidate: true,
})
setValue('httpApiPort', defaultValues.httpApiPort, {
shouldValidate: true,
})
setValue('withAdvancedOptions', false)
resizeTo
} else {
setValue('withAdvancedOptions', true)
}
}}
/>
}
label="Use advanced options"
/>
</Grid>
{watchAdvancedOptions && (
<>
<Grid item xs={12} sm={4}>
<TextField
{...register('mixPort', { valueAsNumber: true })}
variant="outlined"
id="mixPort"
name="mixPort"
label="Mix Port"
fullWidth
error={!!errors.mixPort}
helperText={
errors.mixPort?.message && 'A valid port value is required'
}
disabled={disabled}
/>
</Grid>
{watchNodeType === EnumNodeType.mixnode ? (
<>
<Grid item xs={12} sm={4}>
<TextField
{...register('verlocPort', { valueAsNumber: true })}
variant="outlined"
id="verlocPort"
name="verlocPort"
label="Verloc Port"
fullWidth
error={!!errors.verlocPort}
helperText={
errors.verlocPort?.message &&
'A valid port value is required'
}
disabled={disabled}
/>
</Grid>
<Grid item xs={12} sm={4}>
<TextField
{...register('httpApiPort', { valueAsNumber: true })}
variant="outlined"
id="httpApiPort"
name="httpApiPort"
label="HTTP API Port"
fullWidth
error={!!errors.httpApiPort}
helperText={
errors.httpApiPort?.message &&
'A valid port value is required'
}
disabled={disabled}
/>
</Grid>
</>
) : (
<Grid item xs={12} sm={4}>
<TextField
{...register('clientsPort', { valueAsNumber: true })}
variant="outlined"
id="clientsPort"
name="clientsPort"
label="client WS API Port"
fullWidth
error={!!errors.clientsPort}
helperText={
errors.clientsPort?.message &&
'A valid port value is required'
}
disabled={disabled}
/>
</Grid>
)}
</>
)}
</Grid>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
borderTop: `1px solid ${theme.palette.grey[200]}`,
background: theme.palette.grey[100],
padding: theme.spacing(2),
}}
>
<Button
disabled={isSubmitting || disabled}
variant="contained"
color="primary"
type="submit"
size="large"
disableElevation
onClick={handleSubmit(onSubmit)}
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Bond
</Button>
</div>
</FormControl>
)
}
+128
View File
@@ -0,0 +1,128 @@
import React, { useContext, useEffect, useState } from 'react'
import { Box, Button, CircularProgress, Theme } from '@material-ui/core'
import { Alert } from '@material-ui/lab'
import { useTheme } from '@material-ui/styles'
import { BondForm } from './BondForm'
import { NymCard } from '../../components'
import {
EnumRequestStatus,
RequestStatus,
} from '../../components/RequestStatus'
import { Layout } from '../../layouts'
import { getGasFee, unbond } from '../../requests'
import { TFee } from '../../types'
import { useCheckOwnership } from '../../hooks/useCheckOwnership'
import { ClientContext } from '../../context/main'
export const Bond = () => {
const [status, setStatus] = useState(EnumRequestStatus.initial)
const [message, setMessage] = useState<string>()
const [fees, setFees] = useState<TFee>()
const { checkOwnership, ownership } = useCheckOwnership()
const { getBalance } = useContext(ClientContext)
const theme: Theme = useTheme()
useEffect(() => {
if (status === EnumRequestStatus.initial) {
const initialiseForm = async () => {
await checkOwnership()
setFees({
mixnode: await getGasFee('BondMixnode'),
gateway: await getGasFee('BondGateway'),
})
setStatus(EnumRequestStatus.initial)
}
initialiseForm()
}
}, [status])
return (
<Layout>
<NymCard title="Bond" subheader="Bond a node or gateway" noPadding>
{ownership?.hasOwnership && (
<Alert
severity="warning"
action={
<Button
disabled={status === EnumRequestStatus.loading}
onClick={async () => {
setStatus(EnumRequestStatus.loading)
await unbond(ownership.nodeType!)
getBalance.fetchBalance()
setStatus(EnumRequestStatus.initial)
}}
>
Unbond
</Button>
}
style={{ margin: theme.spacing(2) }}
>
{`Looks like you already have a ${ownership.nodeType} bonded.`}
</Alert>
)}
{status === EnumRequestStatus.loading && (
<Box
style={{
display: 'flex',
justifyContent: 'center',
padding: theme.spacing(3),
}}
>
<CircularProgress size={48} />
</Box>
)}
{status === EnumRequestStatus.initial && (
<BondForm
fees={!ownership.hasOwnership ? fees : undefined}
onError={(e?: string) => {
setMessage(e)
setStatus(EnumRequestStatus.error)
}}
onSuccess={(message?: string) => {
setMessage(message)
setStatus(EnumRequestStatus.success)
}}
disabled={ownership?.hasOwnership}
/>
)}
{(status === EnumRequestStatus.error ||
status === EnumRequestStatus.success) && (
<>
<RequestStatus
status={status}
Success={
<Alert severity="success">Successfully bonded node</Alert>
}
Error={
<Alert severity="error">
An error occurred with the request: {message}
</Alert>
}
/>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
borderTop: `1px solid ${theme.palette.grey[200]}`,
background: theme.palette.grey[100],
padding: theme.spacing(2),
}}
>
<Button
onClick={() => {
setStatus(EnumRequestStatus.initial)
checkOwnership()
}}
>
Again?
</Button>
</div>
</>
)}
</NymCard>
</Layout>
)
}
@@ -0,0 +1,85 @@
import * as Yup from 'yup'
import {
isValidHostname,
validateAmount,
validateKey,
validateLocation,
validateRawPort,
validateVersion,
} from '../../utils'
export const validationSchema = Yup.object().shape({
identityKey: Yup.string()
.required('An indentity key is required')
.test('valid-id-key', 'A valid identity key is required', function (value) {
return validateKey(value || '')
}),
sphinxKey: Yup.string()
.required('A sphinx key is required')
.test(
'valid-sphinx-key',
'A valid sphinx key is required',
function (value) {
return validateKey(value || '')
}
),
amount: Yup.string()
.required('An amount is required')
.test(
'valid-amount',
'A valid amount is required (min 100 punks)',
function (value) {
return validateAmount(value || '', '100000000')
// minimum amount needs to come from the backend - replace when available
}
),
host: Yup.string()
.required('A host is required')
.test('valid-host', 'A valid host is required', function (value) {
return !!value ? isValidHostname(value) : false
}),
version: Yup.string()
.required('A version is required')
.test('valid-version', 'A valid version is required', function (value) {
return !!value ? validateVersion(value) : false
}),
location: Yup.lazy((value) => {
if (!!value) {
return Yup.string()
.required('A location is required')
.test(
'valid-location',
'A valid version is required',
function (value) {
return !!value ? validateLocation(value) : false
}
)
}
return Yup.mixed().notRequired()
}),
mixPort: Yup.number()
.required('A mixport is required')
.test('valid-mixport', 'A valid mixport is required', function (value) {
return !!value ? validateRawPort(value) : false
}),
verlocPort: Yup.number()
.required('A verloc port is required')
.test('valid-verloc', 'A valid verloc port is required', function (value) {
return !!value ? validateRawPort(value) : false
}),
httpApiPort: Yup.number()
.required('A http-api port is required')
.test('valid-http', 'A valid http-api port is required', function (value) {
return !!value ? validateRawPort(value) : false
}),
clientsPort: Yup.number()
.required('A clients port is required')
.test(
'valid-clients',
'A valid clients port is required',
function (value) {
return !!value ? validateRawPort(value) : false
}
),
})
@@ -0,0 +1,167 @@
import React, { useContext } from 'react'
import {
Button,
CircularProgress,
FormControl,
Grid,
InputAdornment,
TextField,
Theme,
useTheme,
} from '@material-ui/core'
import { useForm } from 'react-hook-form'
import { NodeTypeSelector } from '../../components/NodeTypeSelector'
import { EnumNodeType, TFee } from '../../types'
import { yupResolver } from '@hookform/resolvers/yup'
import { validationSchema } from './validationSchema'
import { Alert } from '@material-ui/lab'
import { ClientContext } from '../../context/main'
import { delegate, majorToMinor } from '../../requests'
import { checkHasEnoughFunds } from '../../utils'
type TDelegateForm = {
nodeType: EnumNodeType
identity: string
amount: string
}
const defaultValues: TDelegateForm = {
nodeType: EnumNodeType.mixnode,
identity: '',
amount: '',
}
export const DelegateForm = ({
fees,
onError,
onSuccess,
}: {
fees: TFee
onError: (message?: string) => void
onSuccess: (message?: string) => void
}) => {
const theme = useTheme<Theme>()
const {
register,
setValue,
watch,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm<TDelegateForm>({
defaultValues,
resolver: yupResolver(validationSchema),
})
const watchNodeType = watch('nodeType', defaultValues.nodeType)
const { getBalance } = useContext(ClientContext)
const onSubmit = async (data: TDelegateForm) => {
const hasEnoughFunds = await checkHasEnoughFunds(data.amount)
if (!hasEnoughFunds) {
return setError('amount', {
message: 'Not enough funds in wallet',
})
}
const amount = await majorToMinor(data.amount)
await delegate({
type: data.nodeType,
identity: data.identity,
amount,
})
.then((res) => {
onSuccess(
`Successfully delegated ${data.amount} punk to ${res.source_address}`
)
getBalance.fetchBalance()
})
.catch((e) => {
console.log(e)
onError(e)
})
}
return (
<FormControl fullWidth>
<div style={{ padding: theme.spacing(3, 5) }}>
<Grid container spacing={3}>
<Grid container item xs={12} justifyContent="space-between">
<Grid item>
<NodeTypeSelector
nodeType={watchNodeType}
setNodeType={(nodeType) => setValue('nodeType', nodeType)}
disabled={isSubmitting}
/>
</Grid>
<Grid item>
<Alert severity="info">
{`A fee of ${
watchNodeType === EnumNodeType.mixnode
? fees.mixnode.amount
: fees.gateway.amount
} PUNK will apply to this transaction`}
</Alert>
</Grid>
</Grid>
<Grid item xs={12}>
<TextField
{...register('identity')}
required
variant="outlined"
id="identity"
name="identity"
label="Node identity"
fullWidth
error={!!errors.identity}
helperText={errors?.identity?.message}
/>
</Grid>
<Grid item xs={12} lg={6}>
<TextField
{...register('amount')}
required
variant="outlined"
id="amount"
name="amount"
label="Amount to delegate"
fullWidth
error={!!errors.amount}
helperText={errors?.amount?.message}
InputProps={{
endAdornment: (
<InputAdornment position="end">punks</InputAdornment>
),
}}
/>
</Grid>
</Grid>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
borderTop: `1px solid ${theme.palette.grey[200]}`,
background: theme.palette.grey[100],
padding: theme.spacing(2),
}}
>
<Button
onClick={handleSubmit(onSubmit)}
disabled={isSubmitting}
variant="contained"
color="primary"
type="submit"
disableElevation
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Delegate stake
</Button>
</div>
</FormControl>
)
}
+110
View File
@@ -0,0 +1,110 @@
import React, { useEffect, useState } from 'react'
import { Box, Button, CircularProgress, Theme } from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
import { DelegateForm } from './DelegateForm'
import { Layout } from '../../layouts'
import { NymCard } from '../../components'
import {
EnumRequestStatus,
RequestStatus,
} from '../../components/RequestStatus'
import { Alert, AlertTitle } from '@material-ui/lab'
import { TFee } from '../../types'
import { getGasFee } from '../../requests'
export const Delegate = () => {
const [status, setStatus] = useState<EnumRequestStatus>(
EnumRequestStatus.initial
)
const [message, setMessage] = useState<string>()
const [isLoading, setIsLoading] = useState(true)
const [fees, setFees] = useState<TFee>()
useEffect(() => {
const getFees = async () => {
const mixnode = await getGasFee('DelegateToMixnode')
const gateway = await getGasFee('DelegateToGateway')
setFees({
mixnode: mixnode,
gateway: gateway,
})
setIsLoading(false)
}
getFees()
}, [])
const theme: Theme = useTheme()
return (
<Layout>
<NymCard
title="Delegate"
subheader="Delegate to mixnode or gateway"
noPadding
>
{isLoading && (
<Box
style={{
display: 'flex',
justifyContent: 'center',
padding: theme.spacing(3),
}}
>
<CircularProgress size={48} />
</Box>
)}
<>
{status === EnumRequestStatus.initial && fees && (
<DelegateForm
fees={fees}
onError={(message?: string) => {
setStatus(EnumRequestStatus.error)
setMessage(message)
}}
onSuccess={(message?: string) => {
setStatus(EnumRequestStatus.success)
setMessage(message)
}}
/>
)}
{status !== EnumRequestStatus.initial && (
<>
<RequestStatus
status={status}
Error={
<Alert severity="error">
An error occurred with the request: {message}
</Alert>
}
Success={
<Alert severity="success">
<AlertTitle>Delegation complete</AlertTitle>
{message}
</Alert>
}
/>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
borderTop: `1px solid ${theme.palette.grey[200]}`,
background: theme.palette.grey[100],
padding: theme.spacing(2),
}}
>
<Button
onClick={() => {
setStatus(EnumRequestStatus.initial)
}}
>
Finish
</Button>
</div>
</>
)}
</>
</NymCard>
</Layout>
)
}
@@ -0,0 +1,17 @@
import * as Yup from 'yup'
import { validateAmount, validateKey } from '../../utils'
export const validationSchema = Yup.object().shape({
identity: Yup.string()
.required()
.test(
'valid-id-key',
'A valid identity key is required e.g. 824WyExLUWvLE2mpSHBatN4AoByuLzfnHFeHWiBYzg4z',
(value) => (!!value ? validateKey(value) : false)
),
amount: Yup.string()
.required()
.test('valid-amount-key', 'A valid amount is required', (value) =>
!!value ? validateAmount(value, '0') : false
),
})
+51
View File
@@ -0,0 +1,51 @@
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import { NotFound } from './404'
import { Balance } from './balance'
import { Bond } from './bond'
import { Delegate } from './delegate'
import { Receive } from './receive'
import { Send } from './send'
import { SignIn } from './sign-in'
import { Unbond } from './unbond'
import { Undelegate } from './undelegate'
import { InternalDocs } from './internal-docs'
import { Socks5 } from './SOCKS5'
export const Routes = () => (
<Switch>
<Route path="/signin">
<SignIn />
</Route>
<Route path="/balance">
<Balance />
</Route>
<Route path="/send">
<Send />
</Route>
<Route path="/receive">
<Receive />
</Route>
<Route path="/bond">
<Bond />
</Route>
<Route path="/unbond">
<Unbond />
</Route>
<Route path="/delegate">
<Delegate />
</Route>
<Route path="/undelegate">
<Undelegate />
</Route>
<Route path="/socks5">
<Socks5 />
</Route>
<Route path="/docs">
<InternalDocs />
</Route>
<Route path="*">
<NotFound />
</Route>
</Switch>
)

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