Feature/wallet delegation UI squashed (#1326)
* Delegation UI: Update QA vars fmt re-map coin type for qa add correct bech32 address as the network-explorer-api was complaining clean up fmt Delegation components Show delegation story on paper Remove actions header from delegations list Add copy to clipboard for delegation list node ids Move tooltip Modals Extract modal styles Fix exports Rewards summary and redeem modal Factor out simple modal Delegations actions modals: delegate, delegate more, undelegate Coin mark and move logo stories Rust types React components handle currency Form field to enter and display an Identity Key Fix up build order Update README Flat buttons End adornment Currency form field Add more props Export components Add currency and mixnode fields Group stories into folders and add flow Change exports from shared packages to stop webpack bundling issues Fix logo import Add mock for tauri api in storybook that shows a console error for operations that are not mocked Delegations views and routes for wallet Delegations list show pending delegations and undelegations wip - delegations page status Add typescript type checking to storybook webpack config and more mocks for tauri Add more interstitial states and confirmation modals Copy change Move config to inside source tree Fix up `Console` typings Add wrapper around Tauri `invoke` that logs operations in development mode wip wip wip ts-rs: remove old files ts-rs: update paths to `ts-packages/types` ts-rs: remove old files ts-rs: export new types to `ts-packages/types` Add `MajorCurrencyAmount` to convert to and from TS types for various backend currency types New crate `nym-types` to provide types for frontend apps (wallet, explorer, etc) wip update type imports and fix some lint errors update packages update type imports update type imports update type imports update type imports start pulling out use of minorMajor and majorMinor update type imports update import Add missing types generated by ts-rs fix types Adding denom to account type updates Handle micro currency denoms Fix type conversion mistake Add clean target eslint: formatting Update React currency components to use `MajorCurrencyAmount` Add separators and extra props to currency components replace currency mapper with denom returning from service Adjust type while generation is broken start integrating new CurrencyFormField component update balance and vesting on client change (not only client address) Fix up conversion from cosmwasm coin to major currency for minor denoms Fix up typings and validations to remove more `Coin` usage fix conflict fix delegations form start fixing validation type update remove console log tidy up remove more unused types remove more unused types Fix `Coin` denom to be `minor` Fix up to minor_cosmos_coin Fix up send Remove `Coin` type Fix up exported types start delegation UI more UI work close actions modal on action select update label fix old delegateion form minor updates undo change to currency in stringD Fix up types Add feature flag for generating typescript Generate types behind feature flag Use custom cli tool to export `ts-rs` types `ts-rs-cli` moves files into place and fix up `Makefile` Update generations target Add missing types for generation Generate typescript types reorder imports use make generate-typescript for new types + type import updates update types Add delegate with everything Add get block to nymd client More conversions Get a big list of delegations with lots of stuff Add `avg_uptime_percent` component api updates ui updates and fixes Add delegation history and pending events Fix up addition Fix up pending delegation event types Filter pending delegation events add history and pending events set total delegations rebase fix breaking type change on delegate page Fix mixnode mapping Add back refresh and set periodic refresh upgrade to react router 6 Add logging Export new types for gas and transactions increase container size! add sendtx type update onOK to return MAjorCurrencyAmount align table items display dash if amount not availble work on delegate and undelegate Make serializable More types Fix up errors align item icon type updates Add operation to get all pending delegation/undelegation events Fix up logging Add more logging Fix undelegate error get pending delegation events remove unused import * Fix rebase errors * Integrate fees changes: - make operations available as requests (typed with any for now, needs changing) - move `FeeDetails` to `common/types` - mock `getGasFee()` * get wallet balance after transactions * fix duplicate key * use token pool selector * update wording * Created nymd internal coin * spell delegations correctly! * Additional From implementations plus a constructor * try_add * Changed client API to use the new coin type * CoinConverter trait * Made wallet compilable with the recent changes * Simplified the API by removing the generics in favour of explicit Coin type * Fixed validator api * integrate modal divider with modal component * handle undelegation of locked tokens * only return events table if there are events * Fixed up tests and clippy * Refactored missed coin-generic API methods * changelog * refresh on network or client details change * Bunch of temporary workaround to have wallet working-ish * Add claim and compound wallet endpoints, proc_macro to generate execute and simulate * CHANGELOG * Sort CHANGELOG lines * PR comments * allow sorting of pending events * fix lint errors * handle page overflow * handle reedem and vesting redeem requests * set up compound rewards * refresh locked tokens on page load * remove old delegations pages + remove settings modal + update network explorer url * update validation for hostname (prevent leading spaces) * add compound success case * display est fee until new simulations are used * fix up coin validation * tommy fixes * Show app version at bottom of nav * Show admin page when account matches account id from `.env` file `ADMIN_ADDRESS` map. Value is fetch from GH Actions secrets at build time. * Update change log Co-authored-by: tommy <tommyvez@protonmail.com> Co-authored-by: Mark Sinclair <mmsinclair@gmail.com> Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com> Co-authored-by: durch <durch@users.noreply.github.com>
This commit is contained in:
@@ -66,6 +66,7 @@ jobs:
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
ADMIN_ADDRESS: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||
run: yarn && yarn build
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
|
||||
@@ -44,6 +44,7 @@ jobs:
|
||||
env:
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
ADMIN_ADDRESS: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
@@ -65,6 +65,7 @@ jobs:
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
ADMIN_ADDRESS: ${{ secrets.WALLET_ADMIN_ADDRESS }}
|
||||
run: yarn build
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
|
||||
@@ -21,6 +21,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- wallet: compound and claim reward endpoints for operators and delegators ([#1302])
|
||||
- wallet: require password to switch accounts
|
||||
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
|
||||
- wallet: new delegation and rewards UI
|
||||
- wallet: show version in nav bar
|
||||
- wallet: contract admin route put back
|
||||
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
|
||||
|
||||
### Fixed
|
||||
|
||||
Generated
+84
-2
@@ -218,9 +218,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179"
|
||||
checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
|
||||
|
||||
[[package]]
|
||||
name = "binascii"
|
||||
@@ -3204,6 +3204,32 @@ dependencies = [
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-types"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"coconut-interface",
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"eyre",
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"reqwest",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vesting-contract",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "1.0.1"
|
||||
@@ -3251,6 +3277,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"topology",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"validator-api-requests",
|
||||
"validator-client",
|
||||
@@ -3258,6 +3285,24 @@ dependencies = [
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wallet-types"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"mixnet-contract-common",
|
||||
"nym-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"ts-rs",
|
||||
"validator-client",
|
||||
"vesting-contract",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymcoconut"
|
||||
version = "0.5.0"
|
||||
@@ -5399,6 +5444,28 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38"
|
||||
dependencies = [
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
@@ -6025,6 +6092,21 @@ dependencies = [
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"mixnet-contract-common",
|
||||
"nym-types",
|
||||
"nym-wallet-types",
|
||||
"ts-rs",
|
||||
"validator-api-requests",
|
||||
"validator-client",
|
||||
"vesting-contract-common",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "6.1.2"
|
||||
|
||||
+3
-1
@@ -58,6 +58,7 @@ members = [
|
||||
"common/socks5/requests",
|
||||
"common/task",
|
||||
"common/topology",
|
||||
"common/types",
|
||||
"common/wasm-utils",
|
||||
"explorer-api",
|
||||
"gateway",
|
||||
@@ -67,6 +68,7 @@ members = [
|
||||
"service-providers/network-statistics",
|
||||
"validator-api",
|
||||
"validator-api/validator-api-requests",
|
||||
"tools/ts-rs-cli"
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -79,4 +81,4 @@ default-members = [
|
||||
"explorer-api",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly"]
|
||||
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet"]
|
||||
|
||||
@@ -54,3 +54,7 @@ fmt-wallet:
|
||||
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
|
||||
|
||||
generate-typescript:
|
||||
cd tools/ts-rs-cli && cargo run && cd ../..
|
||||
yarn types:lint:fix
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M171.7,30.3001 C132.7,-8.7999 69.3001,-8.7999 30.3001,30.3001 C-8.7999,69.4001 -8.7999,132.7 30.3001,171.7 C69.4001,210.8 132.7,210.8 171.7,171.7 C210.8,132.7 210.8,69.3001 171.7,30.3001 Z M163.1,163.1 C128.8,197.4 73.1001,197.4 38.8001,163.1 C4.5001,128.8 4.5001,73.1001 38.8001,38.8001 C73.1001,4.5001 128.8,4.5001 163.1,38.8001 C197.5,73.2001 197.5,128.8 163.1,163.1 Z" id="Shape" fill="#fff"></path>
|
||||
<path d="M163.1,38.9 C128.8,4.60005 73.1002,4.60005 38.8002,38.9 C4.50019,73.2 4.50019,128.9 38.8002,163.2 C73.1002,197.5 128.8,197.5 163.1,163.2 C197.5,128.8 197.5,73.2 163.1,38.9 Z" id="Shape" fill="#000"></path>
|
||||
<g id="T" transform="translate(25, 25) scale(5,5)">
|
||||
<path d="M18.4804688,24 C19.203125,24 19.7182617,23.8608398 20.0258789,23.5825195 C20.3334961,23.3041992 20.4873047,22.9453125 20.4873047,22.5058594 C20.4873047,22.0566406 20.3334961,21.6928711 20.0258789,21.4145508 C19.7182617,21.1362305 19.203125,20.9970703 18.4804688,20.9970703 L18.4804688,20.9970703 L16.4589844,20.9970703 L16.4589844,9.24902344 L19.7548828,9.24902344 L19.7548828,12.0908203 C19.7548828,12.8134766 19.894043,13.3286133 20.1723633,13.6362305 C20.4506836,13.9438477 20.8095703,14.0976562 21.2490234,14.0976562 C21.6982422,14.0976562 22.0620117,13.9438477 22.340332,13.6362305 C22.6186523,13.3286133 22.7578125,12.8134766 22.7578125,12.0908203 L22.7578125,12.0908203 L22.7578125,6.24609375 L7.20117188,6.23144531 L7.20117188,12.0908203 C7.20117188,12.8134766 7.34033203,13.3286133 7.61865234,13.6362305 C7.89697266,13.9438477 8.25585938,14.0976562 8.6953125,14.0976562 C9.14453125,14.0976562 9.50830078,13.9438477 9.78662109,13.6362305 C10.0649414,13.3286133 10.2041016,12.8134766 10.2041016,12.0908203 L10.2041016,12.0908203 L10.2041016,9.24902344 L13.4560547,9.24902344 L13.4560547,20.9970703 L11.4492188,20.9970703 C10.7265625,20.9970703 10.2114258,21.1362305 9.90380859,21.4145508 C9.59619141,21.6928711 9.44238281,22.0517578 9.44238281,22.4912109 C9.44238281,22.9404297 9.59619141,23.3041992 9.90380859,23.5825195 C10.2114258,23.8608398 10.7265625,24 11.4492188,24 L11.4492188,24 L18.4804688,24 Z" id="T" fill="#fff"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="white"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="#070B15"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="white"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M171.7,30.3001 C132.7,-8.7999 69.3001,-8.7999 30.3001,30.3001 C-8.7999,69.4001 -8.7999,132.7 30.3001,171.7 C69.4001,210.8 132.7,210.8 171.7,171.7 C210.8,132.7 210.8,69.3001 171.7,30.3001 Z M163.1,163.1 C128.8,197.4 73.1001,197.4 38.8001,163.1 C4.5001,128.8 4.5001,73.1001 38.8001,38.8001 C73.1001,4.5001 128.8,4.5001 163.1,38.8001 C197.5,73.2001 197.5,128.8 163.1,163.1 Z" id="Shape" fill="#141521"></path>
|
||||
<path d="M163.1,38.9 C128.8,4.60005 73.1002,4.60005 38.8002,38.9 C4.50019,73.2 4.50019,128.9 38.8002,163.2 C73.1002,197.5 128.8,197.5 163.1,163.2 C197.5,128.8 197.5,73.2 163.1,38.9 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
<g id="T" transform="translate(25, 25) scale(5,5)">
|
||||
<path d="M18.4804688,24 C19.203125,24 19.7182617,23.8608398 20.0258789,23.5825195 C20.3334961,23.3041992 20.4873047,22.9453125 20.4873047,22.5058594 C20.4873047,22.0566406 20.3334961,21.6928711 20.0258789,21.4145508 C19.7182617,21.1362305 19.203125,20.9970703 18.4804688,20.9970703 L18.4804688,20.9970703 L16.4589844,20.9970703 L16.4589844,9.24902344 L19.7548828,9.24902344 L19.7548828,12.0908203 C19.7548828,12.8134766 19.894043,13.3286133 20.1723633,13.6362305 C20.4506836,13.9438477 20.8095703,14.0976562 21.2490234,14.0976562 C21.6982422,14.0976562 22.0620117,13.9438477 22.340332,13.6362305 C22.6186523,13.3286133 22.7578125,12.8134766 22.7578125,12.0908203 L22.7578125,12.0908203 L22.7578125,6.24609375 L7.20117188,6.23144531 L7.20117188,12.0908203 C7.20117188,12.8134766 7.34033203,13.3286133 7.61865234,13.6362305 C7.89697266,13.9438477 8.25585938,14.0976562 8.6953125,14.0976562 C9.14453125,14.0976562 9.50830078,13.9438477 9.78662109,13.6362305 C10.0649414,13.3286133 10.2041016,12.8134766 10.2041016,12.0908203 L10.2041016,12.0908203 L10.2041016,9.24902344 L13.4560547,9.24902344 L13.4560547,20.9970703 L11.4492188,20.9970703 C10.7265625,20.9970703 10.2114258,21.1362305 9.90380859,21.4145508 C9.59619141,21.6928711 9.44238281,22.0517578 9.44238281,22.4912109 C9.44238281,22.9404297 9.59619141,23.3041992 9.90380859,23.5825195 C10.2114258,23.8608398 10.7265625,24 11.4492188,24 L11.4492188,24 L18.4804688,24 Z" id="T" fill="#000" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M170.7 29.3001C131.7 -9.7999 68.3001 -9.7999 29.3001 29.3001C-9.7999 68.4001 -9.7999 131.7 29.3001 170.7C68.4001 209.8 131.7 209.8 170.7 170.7C209.8 131.7 209.8 68.3001 170.7 29.3001ZM162.1 162.1C127.8 196.4 72.1001 196.4 37.8001 162.1C3.5001 127.8 3.5001 72.1001 37.8001 37.8001C72.1001 3.5001 127.8 3.5001 162.1 37.8001C196.5 72.2001 196.5 127.8 162.1 162.1Z" fill="#141521"/>
|
||||
<path d="M162.1 37.9C127.8 3.60005 72.1002 3.60005 37.8002 37.9C3.50019 72.2 3.50019 127.9 37.8002 162.2C72.1002 196.5 127.8 196.5 162.1 162.2C196.5 127.8 196.5 72.2 162.1 37.9ZM63.0002 170.7C56.8002 167.4 51.1002 163.2 46.1002 158.4V41.7C51.3002 36.7 57.2002 32.5 63.6002 29.1L137 140.9V29.3C143.2 32.6 148.9 36.8 153.9 41.6V158.3C148.7 163.3 142.8 167.5 136.4 170.9L63.0002 59.1V170.7Z" fill="white"/>
|
||||
<path d="M154 158.3V41.7C148.9 36.9 143.2 32.7 137.1 29.4V140.9L63.5 29C57.1 32.4 51.2 36.6 46 41.6V158.3C51.1 163.1 56.8 167.3 62.9 170.6V59.1L136.5 171C142.9 167.6 148.8 163.3 154 158.3Z" fill="#141521"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -58,3 +58,5 @@ nymd-client = [
|
||||
"itertools",
|
||||
"cosmwasm-std",
|
||||
]
|
||||
generate-ts = []
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
use crate::nymd::error::NymdError;
|
||||
use cosmrs::tendermint::abci;
|
||||
use itertools::Itertools;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
|
||||
// as theirs logs
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Log {
|
||||
#[serde(default)]
|
||||
// weird thing is that the first msg_index seems to always be undefined on the raw logs
|
||||
|
||||
@@ -289,7 +289,17 @@ impl<C> NymdClient<C> {
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.client.get_block(None).await?.block.header.time)
|
||||
self.get_block_timestamp(None).await
|
||||
}
|
||||
|
||||
pub async fn get_block_timestamp(
|
||||
&self,
|
||||
height: Option<u32>,
|
||||
) -> Result<TendermintTime, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.client.get_block(height).await?.block.header.time)
|
||||
}
|
||||
|
||||
pub async fn get_current_block_height(&self) -> Result<Height, NymdError>
|
||||
|
||||
@@ -183,6 +183,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the total amount of delegated tokens that have vested
|
||||
async fn delegated_vesting(
|
||||
&self,
|
||||
vesting_account_address: &str,
|
||||
|
||||
@@ -18,12 +18,13 @@ fixed = { version = "1.1", features = ["serde"] }
|
||||
az = "1.1"
|
||||
log = "0.4.14"
|
||||
time = { version = "0.3.6", features = ["parsing", "formatting"] }
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
contracts-common = { path = "../contracts-common" }
|
||||
|
||||
[dev-dependencies]
|
||||
time = { version = "0.3.5", features = ["serde", "macros"] }
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
generate-ts = []
|
||||
|
||||
@@ -8,11 +8,6 @@ use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/gateway.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
|
||||
@@ -14,13 +14,10 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(
|
||||
export,
|
||||
export_to = "../../../nym-wallet/src/types/rust/rewardedsetnodestatus.ts"
|
||||
)
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/RewardedSetNodeStatus.ts")
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
pub enum RewardedSetNodeStatus {
|
||||
@@ -109,11 +106,6 @@ impl PendingUndelegate {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/mixnode.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
|
||||
@@ -12,6 +12,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
cw-storage-plus = "0.13.4"
|
||||
config = { path = "../../config" }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
generate-ts = []
|
||||
|
||||
@@ -14,10 +14,10 @@ pub fn one_ucoin() -> Coin {
|
||||
Coin::new(1, DENOM)
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../../../nym-wallet/src/types/rust/period.ts")
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Period.ts")
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, JsonSchema)]
|
||||
pub enum Period {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "nym-types"
|
||||
version = "1.0.0"
|
||||
description = "Nym common types"
|
||||
authors = ["Nym Technologies SA"]
|
||||
edition = "2021"
|
||||
rust-version = "1.58"
|
||||
|
||||
[dependencies]
|
||||
eyre = "0.6.5"
|
||||
log = "0.4"
|
||||
itertools = "0.10"
|
||||
reqwest = "0.11.9"
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
strum = { version = "0.23", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = "1.0.0-beta8"
|
||||
cosmrs = "0.7.0"
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
] }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
config = { path = "../../common/config" }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
# Used for Type conversion, can be extracted but its a lot of work
|
||||
vesting-contract = { path = "../../contracts/vesting" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
generate-ts = []
|
||||
@@ -0,0 +1,58 @@
|
||||
use crate::currency::{CurrencyDenom, MajorCurrencyAmount};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Account.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Account {
|
||||
pub contract_address: String,
|
||||
pub client_address: String,
|
||||
pub denom: CurrencyDenom,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(contract_address: String, client_address: String, denom: CurrencyDenom) -> Self {
|
||||
Account {
|
||||
contract_address,
|
||||
client_address,
|
||||
denom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/AccountWithMnemonic.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AccountWithMnemonic {
|
||||
pub account: Account,
|
||||
pub mnemonic: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/AccountEntry.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AccountEntry {
|
||||
pub id: String,
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Balance.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Balance {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub printable_balance: String,
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
use crate::error::TypesError;
|
||||
use cosmrs::Denom as CosmosDenom;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::{Add, Mul};
|
||||
use std::str::FromStr;
|
||||
use strum::{Display, EnumString, EnumVariantNames};
|
||||
use validator_client::nymd::{Coin, CosmosCoin};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/CurrencyDenom.ts")
|
||||
)]
|
||||
#[cfg_attr(feature = "generate-ts", ts(rename_all = "UPPERCASE"))]
|
||||
#[derive(
|
||||
Display,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
EnumString,
|
||||
EnumVariantNames,
|
||||
PartialEq,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[strum(serialize_all = "UPPERCASE")]
|
||||
// TODO: this shouldn't be an enum...
|
||||
pub enum CurrencyDenom {
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Nym,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Nymt,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Nyx,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Nyxt,
|
||||
}
|
||||
|
||||
impl CurrencyDenom {
|
||||
pub fn parse(value: &str) -> Result<CurrencyDenom, TypesError> {
|
||||
let mut denom = value.to_string();
|
||||
if denom.starts_with('u') {
|
||||
denom = denom[1..].to_string();
|
||||
}
|
||||
match CurrencyDenom::from_str(&denom) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(_e) => Err(TypesError::InvalidDenom(value.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CosmosDenom> for CurrencyDenom {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: CosmosDenom) -> Result<Self, Self::Error> {
|
||||
CurrencyDenom::parse(&value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct MajorAmountString(String); // see https://github.com/Aleph-Alpha/ts-rs/issues/51 for exporting type aliases
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Currency.ts")
|
||||
)]
|
||||
// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct MajorCurrencyAmount {
|
||||
// temporarly going back to original impl to speed up merge
|
||||
pub amount: MajorAmountString,
|
||||
pub denom: CurrencyDenom,
|
||||
// // temporary...
|
||||
// #[cfg_attr(feature = "generate-ts", ts(skip))]
|
||||
// pub coin: Coin,
|
||||
}
|
||||
|
||||
// impl JsonSchema for MajorCurrencyAmount {
|
||||
// fn schema_name() -> String {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
// tries to semi-replicate cosmos-sdk's DecCoin for being able to handle tokens with decimal amounts
|
||||
// https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/types/dec_coin.go
|
||||
pub struct DecCoin {
|
||||
//
|
||||
}
|
||||
|
||||
impl MajorCurrencyAmount {
|
||||
pub fn new(amount: &str, denom: CurrencyDenom) -> MajorCurrencyAmount {
|
||||
MajorCurrencyAmount {
|
||||
amount: MajorAmountString(amount.to_string()),
|
||||
denom,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero(denom: &CurrencyDenom) -> MajorCurrencyAmount {
|
||||
MajorCurrencyAmount::new("0", denom.clone())
|
||||
}
|
||||
//
|
||||
// pub fn from_cosmrs_coin(coin: &CosmosCoin) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// MajorCurrencyAmount::from_cosmrs_decimal_and_denom(coin.amount, coin.denom.to_string())
|
||||
// }
|
||||
//
|
||||
// pub fn from_minor_uint128_and_denom(
|
||||
// amount_minor: Uint128,
|
||||
// denom_minor: &str,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// MajorCurrencyAmount::from_minor_decimal_and_denom(
|
||||
// Decimal::from_atomics(amount_minor, 0)?,
|
||||
// denom_minor,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// pub fn from_minor_decimal_and_denom(
|
||||
// amount_minor: Decimal,
|
||||
// denom_minor: &str,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if !(denom_minor.starts_with('u') || denom_minor.starts_with('U')) {
|
||||
// return Err(TypesError::InvalidDenom(denom_minor.to_string()));
|
||||
// }
|
||||
// let major = amount_minor / Uint128::from(1_000_000u64);
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(&denom_minor[1..].to_string()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(major.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom_minor.to_string()))
|
||||
// }
|
||||
// pub fn from_decimal_and_denom(
|
||||
// amount: Decimal,
|
||||
// denom: String,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if denom.starts_with('u') || denom.starts_with('U') {
|
||||
// return MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom);
|
||||
// }
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(amount.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom))
|
||||
// }
|
||||
// pub fn from_cosmrs_decimal_and_denom(
|
||||
// amount: CosmosDecimal,
|
||||
// denom: String,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if denom.starts_with('u') || denom.starts_with('U') {
|
||||
// return match Decimal::from_str(&amount.to_string()) {
|
||||
// Ok(amount) => MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom),
|
||||
// Err(_e) => Err(TypesError::InvalidAmount(amount.to_string())),
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(amount.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom))
|
||||
// }
|
||||
//
|
||||
// pub fn into_cosmos_coin(self) -> CosmosCoin {
|
||||
// self.coin.into()
|
||||
// }
|
||||
//
|
||||
// pub fn to_minor_uint128(&self) -> Result<Uint128, TypesError> {
|
||||
// if self.amount.0.contains('.') {
|
||||
// // has a decimal point (Cosmos assumes "." is the decimal separator)
|
||||
// let parts = self.amount.0.split('.');
|
||||
// let str = parts.collect_vec();
|
||||
// if str.is_empty() || str.len() > 2 {
|
||||
// return Err(TypesError::InvalidAmount("Amount is invalid".to_string()));
|
||||
// }
|
||||
// if str.len() == 2 {
|
||||
// // has a decimal, so check decimal places first
|
||||
// if str[1].len() > 6 {
|
||||
// return Err(TypesError::InvalidDenom(
|
||||
// "Amount is invalid, only 6 decimal places of precision are allowed"
|
||||
// .to_string(),
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// // so multiple whole part by 1e6 and add decimal part
|
||||
// let whole_part = Uint128::from_str(str[0])? * Uint128::from(1_000_000u64);
|
||||
//
|
||||
// // TODO: has Rust got anything that deals with fixed point values, or parsing from format strings? Leading zeroes are causing issues
|
||||
// return match format!("0.{}", str[1]).parse::<f64>() {
|
||||
// Ok(decimal_part_float) => {
|
||||
// // this makes an assumption that 6 decimal places of f64 can never lose precision
|
||||
// let truncated = (decimal_part_float * 1_000_000.).trunc() as u32;
|
||||
// let decimal_part = Uint128::from(truncated);
|
||||
// let sum = whole_part + decimal_part;
|
||||
// Ok(sum)
|
||||
// }
|
||||
// Err(_e) => Err(TypesError::InvalidAmount(
|
||||
// "Amount decimal part is invalid".to_string(),
|
||||
// )),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let major = Uint128::from_str(&self.amount.0)?;
|
||||
// let scaled = major * Uint128::new(1_000_000u128);
|
||||
// Ok(scaled)
|
||||
// }
|
||||
|
||||
// pub fn denom_to_string(&self) -> String {
|
||||
// self.denom.to_string()
|
||||
// }
|
||||
}
|
||||
|
||||
impl Display for MajorCurrencyAmount {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {}", self.amount.0, self.denom)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: cleanup after merge
|
||||
impl From<CosmosCoin> for MajorCurrencyAmount {
|
||||
fn from(c: CosmosCoin) -> Self {
|
||||
MajorCurrencyAmount::from(Coin::from(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CosmWasmCoin> for MajorCurrencyAmount {
|
||||
fn from(c: CosmWasmCoin) -> Self {
|
||||
MajorCurrencyAmount::from(Coin::from(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Coin> for MajorCurrencyAmount {
|
||||
fn from(coin: Coin) -> Self {
|
||||
// current assumption: MajorCurrencyAmount is represented as decimal with 6 decimal points
|
||||
// unwrap is fine as we haven't exceeded decimal range since our coins are at max 1B in value
|
||||
// (this is a weak assumption, but for solving this merge conflict it's good enough temporary workaround)
|
||||
let amount = Decimal::from_atomics(coin.amount, 6).unwrap();
|
||||
MajorCurrencyAmount {
|
||||
amount: MajorAmountString(amount.to_string()),
|
||||
denom: CurrencyDenom::parse(&coin.denom).expect("this will go away after the merge..."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// temporary...
|
||||
impl From<MajorCurrencyAmount> for CosmosCoin {
|
||||
fn from(c: MajorCurrencyAmount) -> CosmosCoin {
|
||||
let c: Coin = c.into();
|
||||
c.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MajorCurrencyAmount> for CosmWasmCoin {
|
||||
fn from(c: MajorCurrencyAmount) -> CosmWasmCoin {
|
||||
let c: Coin = c.into();
|
||||
c.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MajorCurrencyAmount> for Coin {
|
||||
fn from(c: MajorCurrencyAmount) -> Coin {
|
||||
let decimal: Decimal = c
|
||||
.amount
|
||||
.0
|
||||
.parse()
|
||||
.expect("stringified amount should have been a valid decimal");
|
||||
|
||||
// again, temporary
|
||||
let exp = Uint128::new(1000000);
|
||||
let val = decimal.mul(exp);
|
||||
|
||||
// again, terrible assumption for denom, but it works temporarily...
|
||||
Coin {
|
||||
amount: val.u128(),
|
||||
denom: format!("u{}", c.denom).to_lowercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for MajorCurrencyAmount {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
// again, temporary workaround to help with merge
|
||||
(Coin::from(self).try_add(&Coin::from(rhs)))
|
||||
.expect("provided coins had different denoms")
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use cosmrs::Coin as CosmosCoin;
|
||||
use cosmrs::Decimal as CosmosDecimal;
|
||||
use cosmrs::Denom as CosmosDenom;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use cosmwasm_std::Decimal as CosmWasmDecimal;
|
||||
use serde_json::json;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
||||
#[test]
|
||||
fn json_to_major_currency_amount() {
|
||||
let nym = json!({
|
||||
"amount": "1",
|
||||
"denom": "NYM"
|
||||
});
|
||||
let nymt = json!({
|
||||
"amount": "1",
|
||||
"denom": "NYMT"
|
||||
});
|
||||
|
||||
let test_nym_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let test_nymt_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nymt);
|
||||
|
||||
let nym_amount = serde_json::from_value::<MajorCurrencyAmount>(nym).unwrap();
|
||||
let nymt_amount = serde_json::from_value::<MajorCurrencyAmount>(nymt).unwrap();
|
||||
|
||||
assert_eq!(nym_amount, test_nym_amount);
|
||||
assert_eq!(nymt_amount, test_nymt_amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_amount_json_to_major_currency_amount() {
|
||||
let one_micro_nym = json!({
|
||||
"amount": "0.000001",
|
||||
"denom": "NYM"
|
||||
});
|
||||
|
||||
let expected_nym_amount = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
|
||||
let actual_nym_amount =
|
||||
serde_json::from_value::<MajorCurrencyAmount>(one_micro_nym).unwrap();
|
||||
|
||||
assert_eq!(expected_nym_amount, actual_nym_amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_from_str() {
|
||||
assert_eq!(CurrencyDenom::from_str("nym").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("nymt").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
assert_eq!(CurrencyDenom::from_str("NYM").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("NYMT").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
assert_eq!(CurrencyDenom::from_str("NyM").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("NYmt").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("foo").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
|
||||
// denominations must all be major
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("unym").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("unymt").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1", CurrencyDenom::Nym).to_string(),
|
||||
"1 NYM"
|
||||
);
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1", CurrencyDenom::Nymt).to_string(),
|
||||
"1 NYMT"
|
||||
);
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1000000000000", CurrencyDenom::Nym).to_string(),
|
||||
"1000000000000 NYM"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_coin_to_major_currency() {
|
||||
let cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::from(cosmos_coin);
|
||||
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmwasm_coin_to_major_currency() {
|
||||
let coin = CosmWasmCoin {
|
||||
amount: Uint128::from(1u64),
|
||||
denom: "unym".to_string(),
|
||||
};
|
||||
println!(
|
||||
"from_atomics = {}",
|
||||
CosmWasmDecimal::from_atomics(coin.amount.clone(), 6)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
);
|
||||
let c: MajorCurrencyAmount = coin.into();
|
||||
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmwasm_coin_to_major_currency_2() {
|
||||
let coin = CosmWasmCoin {
|
||||
amount: Uint128::from(1_000_000u64),
|
||||
denom: "unym".to_string(),
|
||||
};
|
||||
println!(
|
||||
"from_atomics = {:?}",
|
||||
CosmWasmDecimal::from_atomics(coin.amount.clone(), 6)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
);
|
||||
let c: MajorCurrencyAmount = coin.into();
|
||||
assert_eq!(c, MajorCurrencyAmount::new("1", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn major_currency_to_minor_cosmos_coin() {
|
||||
let expected_cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
|
||||
let minor_cosmos_coin = c.into();
|
||||
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
|
||||
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn major_currency_to_minor_cosmos_coin_2() {
|
||||
let expected_cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1000000u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let minor_cosmos_coin = c.into();
|
||||
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
|
||||
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmos_coin_to_major_currency_string() {
|
||||
// check minor cosmos coin is converted to major value
|
||||
let cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::from(cosmos_coin);
|
||||
assert_eq!(c.to_string(), "0.000001 NYM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_to_string() {
|
||||
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let denom = c.denom.to_string();
|
||||
assert_eq!(denom, "NYM".to_string());
|
||||
}
|
||||
|
||||
fn amounts() -> Vec<&'static str> {
|
||||
vec![
|
||||
"1",
|
||||
"10",
|
||||
"100",
|
||||
"1000",
|
||||
"10000",
|
||||
"100000",
|
||||
"10000000",
|
||||
"100000000",
|
||||
"1000000000",
|
||||
"10000000000",
|
||||
"100000000000",
|
||||
"1000000000000",
|
||||
"10000000000000",
|
||||
"100000000000000",
|
||||
"1000000000000000",
|
||||
"10000000000000000",
|
||||
"100000000000000000",
|
||||
"1000000000000000000",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use log::error;
|
||||
use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent;
|
||||
use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate;
|
||||
use mixnet_contract_common::Delegation as MixnetContractDelegation;
|
||||
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Delegation.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct Delegation {
|
||||
pub owner: String,
|
||||
pub node_identity: String,
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<String>, // proxy address used to delegate the funds on behalf of anouther address
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractDelegation> for Delegation {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractDelegation) -> Result<Self, Self::Error> {
|
||||
let MixnetContractDelegation {
|
||||
owner,
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy,
|
||||
} = value;
|
||||
|
||||
let amount: MajorCurrencyAmount = amount.into();
|
||||
|
||||
Ok(Delegation {
|
||||
owner: owner.into_string(),
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/DelegationRecord.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct DelegationRecord {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub block_height: u64,
|
||||
pub delegated_on_iso_datetime: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/DelegationWithEverything.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct DelegationWithEverything {
|
||||
pub owner: String,
|
||||
pub node_identity: String,
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub total_delegation: Option<MajorCurrencyAmount>,
|
||||
pub pledge_amount: Option<MajorCurrencyAmount>,
|
||||
pub block_height: u64,
|
||||
pub delegated_on_iso_datetime: String,
|
||||
pub profit_margin_percent: Option<u8>,
|
||||
pub avg_uptime_percent: Option<u8>,
|
||||
pub stake_saturation: Option<f32>,
|
||||
pub proxy: Option<String>,
|
||||
pub accumulated_rewards: Option<MajorCurrencyAmount>,
|
||||
pub pending_events: Vec<DelegationEvent>,
|
||||
pub history: Vec<DelegationRecord>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/DelegationResult.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Clone, PartialEq, Debug)]
|
||||
pub struct DelegationResult {
|
||||
source_address: String,
|
||||
target_address: String,
|
||||
amount: Option<MajorCurrencyAmount>,
|
||||
}
|
||||
|
||||
impl DelegationResult {
|
||||
pub fn new(
|
||||
source_address: &str,
|
||||
target_address: &str,
|
||||
amount: Option<MajorCurrencyAmount>,
|
||||
) -> DelegationResult {
|
||||
DelegationResult {
|
||||
source_address: source_address.to_string(),
|
||||
target_address: target_address.to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractDelegation> for DelegationResult {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(delegation: MixnetContractDelegation) -> Result<Self, Self::Error> {
|
||||
let amount: MajorCurrencyAmount = delegation.amount.clone().into();
|
||||
Ok(DelegationResult {
|
||||
source_address: delegation.owner().to_string(),
|
||||
target_address: delegation.node_identity(),
|
||||
amount: Some(amount),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/DelegationEventKind.ts")
|
||||
)]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, JsonSchema, Debug)]
|
||||
pub enum DelegationEventKind {
|
||||
Delegate,
|
||||
Undelegate,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/DelegationEvent.ts")
|
||||
)]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, JsonSchema, Debug)]
|
||||
pub struct DelegationEvent {
|
||||
pub kind: DelegationEventKind,
|
||||
pub node_identity: String,
|
||||
pub address: String,
|
||||
pub amount: Option<MajorCurrencyAmount>,
|
||||
pub block_height: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<ContractDelegationEvent> for DelegationEvent {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(event: ContractDelegationEvent) -> Result<Self, Self::Error> {
|
||||
match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => {
|
||||
let amount: MajorCurrencyAmount = delegation.amount.into();
|
||||
Ok(DelegationEvent {
|
||||
kind: DelegationEventKind::Delegate,
|
||||
block_height: delegation.block_height,
|
||||
address: delegation.owner.into_string(),
|
||||
node_identity: delegation.node_identity,
|
||||
amount: Some(amount),
|
||||
})
|
||||
}
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => Ok(DelegationEvent {
|
||||
kind: DelegationEventKind::Undelegate,
|
||||
block_height: pending_undelegate.block_height(),
|
||||
address: pending_undelegate.delegate().into_string(),
|
||||
node_identity: pending_undelegate.mix_identity(),
|
||||
amount: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/PendingUndelegate.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, PartialEq, JsonSchema, Clone, Debug)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: String,
|
||||
delegate: String,
|
||||
proxy: Option<String>,
|
||||
block_height: u64,
|
||||
}
|
||||
|
||||
impl From<ContractPendingUndelegate> for PendingUndelegate {
|
||||
fn from(pending_undelegate: ContractPendingUndelegate) -> Self {
|
||||
PendingUndelegate {
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
delegate: pending_undelegate.delegate().to_string(),
|
||||
proxy: pending_undelegate.proxy().map(|p| p.to_string()),
|
||||
block_height: pending_undelegate.block_height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_contract_delegation_events(
|
||||
events: Vec<ContractDelegationEvent>,
|
||||
) -> Result<Vec<DelegationEvent>, TypesError> {
|
||||
let (events, errors): (Vec<_>, Vec<_>) = events
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.try_into())
|
||||
.partition(Result::is_ok);
|
||||
|
||||
if errors.is_empty() {
|
||||
let events = events
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.collect::<Vec<DelegationEvent>>();
|
||||
return Ok(events);
|
||||
}
|
||||
let errors = errors
|
||||
.into_iter()
|
||||
.filter_map(|e| e.err())
|
||||
.collect::<Vec<TypesError>>();
|
||||
|
||||
error!("Failed to convert delegations: {:?}", errors);
|
||||
Err(TypesError::DelegationsInvalid)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/DelegationSummaryResponse.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct DelegationsSummaryResponse {
|
||||
pub delegations: Vec<DelegationWithEverything>,
|
||||
pub total_delegations: MajorCurrencyAmount,
|
||||
pub total_rewards: MajorCurrencyAmount,
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
use validator_client::validator_api::error::ValidatorAPIError;
|
||||
use validator_client::{nymd::error::NymdError, ValidatorClientError};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TypesError {
|
||||
#[error("{source}")]
|
||||
NymdError {
|
||||
#[from]
|
||||
source: NymdError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
CosmwasmStd {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ErrorReport {
|
||||
#[from]
|
||||
source: eyre::Report,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ValidatorApiError {
|
||||
#[from]
|
||||
source: ValidatorAPIError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
IOError {
|
||||
#[from]
|
||||
source: io::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
MalformedUrlProvided {
|
||||
#[from]
|
||||
source: url::ParseError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
DecimalRangeExceeded {
|
||||
#[from]
|
||||
source: cosmwasm_std::DecimalRangeExceeded,
|
||||
},
|
||||
#[error("{0} is not a valid amount string")]
|
||||
InvalidAmount(String),
|
||||
#[error("{0} is not a valid denomination string")]
|
||||
InvalidDenom(String),
|
||||
#[error("Mixnode not found")]
|
||||
MixnodeNotFound(),
|
||||
#[error("Gateway bond is not valid")]
|
||||
InvalidGatewayBond(),
|
||||
#[error("Invalid delegations")]
|
||||
DelegationsInvalid,
|
||||
}
|
||||
|
||||
impl Serialize for TypesError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidatorClientError> for TypesError {
|
||||
fn from(e: ValidatorClientError) -> Self {
|
||||
match e {
|
||||
ValidatorClientError::ValidatorAPIError { source } => source.into(),
|
||||
ValidatorClientError::MalformedUrlProvided(e) => e.into(),
|
||||
ValidatorClientError::NymdError(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/FeeDetails.ts")
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FeeDetails {
|
||||
// expected to be used by the wallet in order to display detailed fee information to the user
|
||||
pub amount: Option<MajorCurrencyAmount>,
|
||||
#[cfg_attr(feature = "generate-ts", ts(skip))]
|
||||
pub fee: Fee,
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use cosmrs::tx::Gas as CosmrsGas;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::cosmwasm_client::types::GasInfo as ValidatorClientGasInfo;
|
||||
use validator_client::nymd::GasPrice;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Gas.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Gas {
|
||||
/// units of gas used
|
||||
pub gas_units: u64,
|
||||
//
|
||||
// /// gas units converted to fee as major coin amount
|
||||
// pub amount: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl Gas {
|
||||
pub fn from_cosmrs_gas(value: CosmrsGas, _denom_minor: &str) -> Result<Gas, TypesError> {
|
||||
Ok(Gas {
|
||||
gas_units: value.value(),
|
||||
})
|
||||
|
||||
// // TODO: use simulator struct to do conversion to fee
|
||||
// let value_u128 = Uint128::from(value.value());
|
||||
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
|
||||
// Ok(Gas {
|
||||
// gas_units: value.value(),
|
||||
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
|
||||
// })
|
||||
}
|
||||
pub fn from_u64(value: u64, _denom_minor: &str) -> Result<Gas, TypesError> {
|
||||
Ok(Gas { gas_units: value })
|
||||
// todo!()
|
||||
// // TODO: use simulator struct to do conversion to fee
|
||||
// let value_u128 = Uint128::from(value);
|
||||
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
|
||||
// Ok(Gas {
|
||||
// gas_units: value,
|
||||
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/GasInfo.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct GasInfo {
|
||||
/// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
pub gas_wanted: u64,
|
||||
|
||||
/// GasUsed is the amount of gas actually consumed.
|
||||
pub gas_used: u64,
|
||||
|
||||
/// gas units converted to fee as major coin amount
|
||||
pub fee: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl GasInfo {
|
||||
pub fn from_validator_client_gas_info(
|
||||
value: ValidatorClientGasInfo,
|
||||
denom_minor: &str,
|
||||
) -> Result<GasInfo, TypesError> {
|
||||
// terrible workaround, but I don't want to break the current flow (just yet)
|
||||
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
|
||||
let fee = (&gas_price) * value.gas_used;
|
||||
Ok(GasInfo {
|
||||
gas_wanted: value.gas_wanted.value(),
|
||||
gas_used: value.gas_used.value(),
|
||||
fee: fee.into(),
|
||||
})
|
||||
}
|
||||
pub fn from_u64(
|
||||
gas_wanted: u64,
|
||||
gas_used: u64,
|
||||
denom_minor: &str,
|
||||
) -> Result<GasInfo, TypesError> {
|
||||
// terrible workaround, but I don't want to break the current flow (just yet)
|
||||
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
|
||||
let fee = (&gas_price) * CosmrsGas::from(gas_used);
|
||||
Ok(GasInfo {
|
||||
gas_wanted,
|
||||
gas_used,
|
||||
fee: fee.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::{
|
||||
Gateway as MixnetContractGateway, GatewayBond as MixnetContractGatewayBond,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Gateway.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct Gateway {
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
pub clients_port: u16,
|
||||
pub location: String,
|
||||
pub sphinx_key: String,
|
||||
/// Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients
|
||||
pub identity_key: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl From<MixnetContractGateway> for Gateway {
|
||||
fn from(value: MixnetContractGateway) -> Self {
|
||||
let MixnetContractGateway {
|
||||
host,
|
||||
mix_port,
|
||||
clients_port,
|
||||
location,
|
||||
sphinx_key,
|
||||
identity_key,
|
||||
version,
|
||||
} = value;
|
||||
|
||||
Gateway {
|
||||
host,
|
||||
mix_port,
|
||||
clients_port,
|
||||
location,
|
||||
sphinx_key,
|
||||
identity_key,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/GatewayBond.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub pledge_amount: MajorCurrencyAmount,
|
||||
pub owner: String,
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
pub proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn from_mixnet_contract_gateway_bond(
|
||||
bond: Option<MixnetContractGatewayBond>,
|
||||
) -> Result<Option<GatewayBond>, TypesError> {
|
||||
match bond {
|
||||
Some(bond) => {
|
||||
let bond: GatewayBond = bond.try_into()?;
|
||||
Ok(Some(bond))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractGatewayBond> for GatewayBond {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractGatewayBond) -> Result<Self, Self::Error> {
|
||||
let MixnetContractGatewayBond {
|
||||
pledge_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy,
|
||||
} = value;
|
||||
|
||||
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
|
||||
|
||||
Ok(GatewayBond {
|
||||
pledge_amount,
|
||||
owner: owner.into_string(),
|
||||
block_height,
|
||||
gateway: gateway.into(),
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
pub mod account;
|
||||
pub mod currency;
|
||||
pub mod delegation;
|
||||
pub mod error;
|
||||
pub mod fees;
|
||||
pub mod gas;
|
||||
pub mod gateway;
|
||||
pub mod mixnode;
|
||||
pub mod transaction;
|
||||
pub mod vesting;
|
||||
@@ -0,0 +1,124 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::{
|
||||
Coin as CosmWasmCoin, MixNode as MixnetContractMixNode,
|
||||
MixNodeBond as MixnetContractMixNodeBond,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Mixnode.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
pub verloc_port: u16,
|
||||
pub http_api_port: u16,
|
||||
pub sphinx_key: String,
|
||||
/// Base58 encoded ed25519 EdDSA public key.
|
||||
pub identity_key: String,
|
||||
pub version: String,
|
||||
pub profit_margin_percent: u8,
|
||||
}
|
||||
|
||||
impl From<MixnetContractMixNode> for MixNode {
|
||||
fn from(value: MixnetContractMixNode) -> Self {
|
||||
let MixnetContractMixNode {
|
||||
host,
|
||||
mix_port,
|
||||
verloc_port,
|
||||
http_api_port,
|
||||
sphinx_key,
|
||||
identity_key,
|
||||
version,
|
||||
profit_margin_percent,
|
||||
} = value;
|
||||
|
||||
Self {
|
||||
host,
|
||||
mix_port,
|
||||
verloc_port,
|
||||
http_api_port,
|
||||
sphinx_key,
|
||||
identity_key,
|
||||
version,
|
||||
profit_margin_percent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/MixNodeBond.ts")
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
pub pledge_amount: MajorCurrencyAmount,
|
||||
pub total_delegation: MajorCurrencyAmount,
|
||||
pub owner: String,
|
||||
pub layer: String,
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<String>,
|
||||
pub accumulated_rewards: Option<MajorCurrencyAmount>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
pub fn from_mixnet_contract_mixnode_bond(
|
||||
bond: Option<MixnetContractMixNodeBond>,
|
||||
) -> Result<Option<MixNodeBond>, TypesError> {
|
||||
match bond {
|
||||
Some(bond) => {
|
||||
let bond: MixNodeBond = bond.try_into()?;
|
||||
Ok(Some(bond))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractMixNodeBond> for MixNodeBond {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractMixNodeBond) -> Result<Self, Self::Error> {
|
||||
let MixnetContractMixNodeBond {
|
||||
pledge_amount,
|
||||
total_delegation,
|
||||
owner,
|
||||
layer,
|
||||
block_height,
|
||||
mix_node,
|
||||
proxy,
|
||||
accumulated_rewards,
|
||||
} = value;
|
||||
|
||||
if pledge_amount.denom != total_delegation.denom {
|
||||
return Err(TypesError::InvalidDenom(
|
||||
"The pledge and delegation denominations do not match".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let denom = total_delegation.denom.clone();
|
||||
|
||||
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
|
||||
let total_delegation: MajorCurrencyAmount = total_delegation.into();
|
||||
let accumulated_rewards: Option<MajorCurrencyAmount> =
|
||||
accumulated_rewards.map(|r| CosmWasmCoin::new(r.u128(), denom).into());
|
||||
|
||||
Ok(MixNodeBond {
|
||||
pledge_amount,
|
||||
total_delegation,
|
||||
owner: owner.into_string(),
|
||||
layer: layer.into(),
|
||||
block_height,
|
||||
mix_node: mix_node.into(),
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
accumulated_rewards,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use crate::gas::GasInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::cosmwasm_client::types::ExecuteResult;
|
||||
use validator_client::nymd::TxResponse;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/SendTxResult.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct SendTxResult {
|
||||
pub block_height: u64,
|
||||
pub code: u32,
|
||||
pub details: TransactionDetails,
|
||||
pub gas_used: u64,
|
||||
pub gas_wanted: u64,
|
||||
pub tx_hash: String,
|
||||
// pub fee: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl SendTxResult {
|
||||
pub fn new(
|
||||
t: TxResponse,
|
||||
details: TransactionDetails,
|
||||
_denom_minor: &str,
|
||||
) -> Result<SendTxResult, TypesError> {
|
||||
Ok(SendTxResult {
|
||||
block_height: t.height.value(),
|
||||
code: t.tx_result.code.value(),
|
||||
details,
|
||||
gas_used: t.tx_result.gas_used.value(),
|
||||
gas_wanted: t.tx_result.gas_wanted.value(),
|
||||
tx_hash: t.hash.to_string(),
|
||||
// that is completely wrong: fee is what you told the validator to use beforehand
|
||||
// fee: MajorCurrencyAmount::from_decimal_and_denom(
|
||||
// Decimal::new(Uint128::from(t.tx_result.gas_used.value())),
|
||||
// denom_minor.to_string(),
|
||||
// )?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/TransactionDetails.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct TransactionDetails {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub from_address: String,
|
||||
pub to_address: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/TransactionExecuteResult.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TransactionExecuteResult {
|
||||
pub logs_json: String,
|
||||
pub data_json: String,
|
||||
pub transaction_hash: String,
|
||||
pub gas_info: GasInfo,
|
||||
pub fee: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl TransactionExecuteResult {
|
||||
pub fn from_execute_result(
|
||||
value: ExecuteResult,
|
||||
denom_minor: &str,
|
||||
) -> Result<TransactionExecuteResult, TypesError> {
|
||||
let gas_info = GasInfo::from_validator_client_gas_info(value.gas_info, denom_minor)?;
|
||||
let fee = gas_info.fee.clone();
|
||||
Ok(TransactionExecuteResult {
|
||||
gas_info,
|
||||
transaction_hash: value.transaction_hash.to_string(),
|
||||
data_json: ::serde_json::to_string_pretty(&value.data)?,
|
||||
logs_json: ::serde_json::to_string_pretty(&value.logs)?,
|
||||
fee,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/RpcTransactionResponse.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct RpcTransactionResponse {
|
||||
pub index: u32,
|
||||
pub tx_result_json: String,
|
||||
pub block_height: u64,
|
||||
pub transaction_hash: String,
|
||||
pub gas_info: GasInfo,
|
||||
// pub fee: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl RpcTransactionResponse {
|
||||
pub fn from_tx_response(
|
||||
value: &TxResponse,
|
||||
denom_minor: &str,
|
||||
) -> Result<RpcTransactionResponse, TypesError> {
|
||||
Ok(RpcTransactionResponse {
|
||||
index: value.index,
|
||||
gas_info: GasInfo::from_u64(
|
||||
value.tx_result.gas_wanted.value(),
|
||||
value.tx_result.gas_used.value(),
|
||||
denom_minor,
|
||||
)?,
|
||||
transaction_hash: value.hash.to_string(),
|
||||
tx_result_json: ::serde_json::to_string_pretty(&value.tx_result)?,
|
||||
block_height: value.height.value(),
|
||||
// wrong
|
||||
// fee: MajorCurrencyAmount::from_decimal_and_denom(
|
||||
// Decimal::new(Uint128::from(value.tx_result.gas_used.value())),
|
||||
// denom_minor.to_string(),
|
||||
// )?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vesting_contract::vesting::Account as VestingAccount;
|
||||
use vesting_contract::vesting::VestingPeriod as VestingVestingPeriod;
|
||||
use vesting_contract_common::OriginalVestingResponse as VestingOriginalVestingResponse;
|
||||
use vesting_contract_common::PledgeData as VestingPledgeData;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/PledgeData.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PledgeData {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub block_time: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingPledgeData> for PledgeData {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(data: VestingPledgeData) -> Result<Self, Self::Error> {
|
||||
let amount: MajorCurrencyAmount = data.amount().into();
|
||||
Ok(Self {
|
||||
amount,
|
||||
block_time: data.block_time().seconds(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
pub fn and_then(data: VestingPledgeData) -> Option<Self> {
|
||||
data.try_into().ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/OriginalVestingResponse.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: MajorCurrencyAmount,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingOriginalVestingResponse> for OriginalVestingResponse {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(data: VestingOriginalVestingResponse) -> Result<Self, Self::Error> {
|
||||
let amount = data.amount().into();
|
||||
Ok(Self {
|
||||
amount,
|
||||
number_of_periods: data.number_of_periods(),
|
||||
period_duration: data.period_duration(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/VestingAccountInfo.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VestingAccountInfo {
|
||||
owner_address: String,
|
||||
staking_address: Option<String>,
|
||||
start_time: u64,
|
||||
periods: Vec<VestingPeriod>,
|
||||
amount: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingAccount> for VestingAccountInfo {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(account: VestingAccount) -> Result<Self, Self::Error> {
|
||||
let mut periods = Vec::new();
|
||||
for period in account.periods() {
|
||||
periods.push(period.into());
|
||||
}
|
||||
let amount: MajorCurrencyAmount = account.coin().into();
|
||||
Ok(Self {
|
||||
owner_address: account.owner_address().to_string(),
|
||||
staking_address: account.staking_address().map(|a| a.to_string()),
|
||||
start_time: account.start_time().seconds(),
|
||||
periods,
|
||||
amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/VestingPeriod.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VestingPeriod {
|
||||
start_time: u64,
|
||||
period_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<VestingVestingPeriod> for VestingPeriod {
|
||||
fn from(period: VestingVestingPeriod) -> Self {
|
||||
Self {
|
||||
start_time: period.start_time,
|
||||
period_seconds: period.period_seconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+49
@@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
@@ -1056,6 +1062,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"time 0.3.6",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1627,6 +1634,15 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
@@ -1699,6 +1715,29 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "6.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc59f479df54269b400dd95bc3b7e81623b3e4b9c70c8ca7125ab8341eafa64e"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "6.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
@@ -1809,6 +1848,7 @@ dependencies = [
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1893,6 +1933,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"packages": [
|
||||
"ts-packages/*"
|
||||
"ts-packages/*",
|
||||
"nym-wallet"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
ADMIN_ADDRESS=
|
||||
ADMIN_ADDRESS={"MAINNET":[],"SANDBOX":[],"QA":["n1c8te4wlc25re97qw5nh8urtpek494zqx30lza2","n177krl498arsfupd2p9097kwfmuf07lkj6crmj9"]}
|
||||
Generated
+49
@@ -2785,6 +2785,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"time 0.3.7",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2997,6 +2998,49 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-types"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"coconut-interface",
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"eyre",
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"reqwest",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.23.0",
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vesting-contract",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wallet-types"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"mixnet-contract-common",
|
||||
"nym-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.23.0",
|
||||
"ts-rs",
|
||||
"validator-client",
|
||||
"vesting-contract",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym_wallet"
|
||||
version = "1.0.4"
|
||||
@@ -3009,6 +3053,7 @@ dependencies = [
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
@@ -3017,6 +3062,8 @@ dependencies = [
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"nym-types",
|
||||
"nym-wallet-types",
|
||||
"once_cell",
|
||||
"pretty_env_logger",
|
||||
"rand 0.6.5",
|
||||
@@ -5450,6 +5497,7 @@ dependencies = [
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5541,6 +5589,7 @@ dependencies = [
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -4,4 +4,5 @@ resolver = "2"
|
||||
members = [
|
||||
"src-tauri",
|
||||
"wallet-recovery-cli",
|
||||
"nym-wallet-types"
|
||||
]
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "nym-wallet-types"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.58"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
strum = { version = "0.23", features = ["derive"] }
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = "1.0.0-beta8"
|
||||
cosmrs = "0.7.0"
|
||||
|
||||
config = { path = "../../common/config" }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
] }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
# Used for Type conversion, can be extracted but its a lot of work
|
||||
vesting-contract = { path = "../../contracts/vesting" }
|
||||
|
||||
nym-types = { path = "../../common/types" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
generate-ts = []
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use cosmwasm_std::Uint128;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use mixnet_contract_common::ContractStateParams;
|
||||
use nym_types::error::TypesError;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "nym-wallet/src/types/rust/StateParams.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TauriContractStateParams {
|
||||
minimum_mixnode_pledge: String,
|
||||
minimum_gateway_pledge: String,
|
||||
mixnode_rewarded_set_size: u32,
|
||||
mixnode_active_set_size: u32,
|
||||
}
|
||||
|
||||
impl From<ContractStateParams> for TauriContractStateParams {
|
||||
fn from(p: ContractStateParams) -> TauriContractStateParams {
|
||||
TauriContractStateParams {
|
||||
minimum_mixnode_pledge: p.minimum_mixnode_pledge.to_string(),
|
||||
minimum_gateway_pledge: p.minimum_gateway_pledge.to_string(),
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TauriContractStateParams> for ContractStateParams {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(p: TauriContractStateParams) -> Result<ContractStateParams, Self::Error> {
|
||||
Ok(ContractStateParams {
|
||||
minimum_mixnode_pledge: Uint128::try_from(p.minimum_mixnode_pledge.as_str())?,
|
||||
minimum_gateway_pledge: Uint128::try_from(p.minimum_gateway_pledge.as_str())?,
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export, export_to = "nym-wallet/src/types/rust/AppEnv.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct AppEnv {
|
||||
pub ADMIN_ADDRESS: Option<String>,
|
||||
pub SHOW_TERMINAL: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use mixnet_contract_common::Interval;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "nym-wallet/src/types/rust/Epoch.ts")
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct Epoch {
|
||||
id: u32,
|
||||
start: i64,
|
||||
end: i64,
|
||||
duration_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<Interval> for Epoch {
|
||||
fn from(interval: Interval) -> Self {
|
||||
Self {
|
||||
id: interval.id(),
|
||||
start: interval.start_unix_timestamp(),
|
||||
end: interval.end_unix_timestamp(),
|
||||
duration_seconds: interval.length().as_secs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod admin;
|
||||
pub mod app;
|
||||
pub mod epoch;
|
||||
pub mod network;
|
||||
pub mod network_config;
|
||||
@@ -1,15 +1,21 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use config::defaults::all::Network as ConfigNetwork;
|
||||
use config::defaults::{mainnet, qa, sandbox};
|
||||
use cosmrs::Denom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use strum::EnumIter;
|
||||
|
||||
use config::defaults::all::Network as ConfigNetwork;
|
||||
use config::defaults::{mainnet, qa, sandbox};
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/network.ts"))]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "nym-wallet/src/types/rust/Network.ts")
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Deserialize, EnumIter, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum Network {
|
||||
QA,
|
||||
@@ -22,12 +28,12 @@ impl Network {
|
||||
self.to_string().to_lowercase()
|
||||
}
|
||||
|
||||
pub fn denom(&self) -> &str {
|
||||
pub fn denom(&self) -> Denom {
|
||||
match self {
|
||||
// network defaults should be correctly formatted
|
||||
Network::QA => qa::DENOM,
|
||||
Network::SANDBOX => sandbox::DENOM,
|
||||
Network::MAINNET => mainnet::DENOM,
|
||||
Network::QA => Denom::from_str(qa::DENOM).unwrap(),
|
||||
Network::SANDBOX => Denom::from_str(sandbox::DENOM).unwrap(),
|
||||
Network::MAINNET => Denom::from_str(mainnet::DENOM).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
// When the UI queries validator urls we use this type
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "nym-wallet/src/types/rust/ValidatorUrls.ts")
|
||||
)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ValidatorUrls {
|
||||
pub urls: Vec<ValidatorUrl>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export, export_to = "nym-wallet/src/types/rust/ValidatorUrl.ts")
|
||||
)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ValidatorUrl {
|
||||
pub url: String,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
// The type used when adding or removing validators, effectively the input.
|
||||
// NOTE: we should consider if we want to split this up
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export, export_to = "nym-wallet/src/types/rust/ValidatorUrls.ts")
|
||||
)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Validator {
|
||||
pub nymd_url: String,
|
||||
pub nymd_name: Option<String>,
|
||||
pub api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Validator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let nymd_url = format!("nymd_url: {}", self.nymd_url);
|
||||
let api_url = self
|
||||
.api_url
|
||||
.as_ref()
|
||||
.map(|api_url| format!(", api_url: {}", api_url))
|
||||
.unwrap_or_default();
|
||||
write!(f, "{nymd_url}{api_url}")
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,11 @@
|
||||
"@mui/icons-material": "^5.2.0",
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@mui/utils": "^5.7.0",
|
||||
"@nymproject/mui-theme": "^1.0.0",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/types": "^1.0.0",
|
||||
"@storybook/react": "^6.4.22",
|
||||
"@tauri-apps/api": "^1.0.0-rc.1",
|
||||
"bs58": "^4.0.1",
|
||||
"clsx": "^1.1.1",
|
||||
@@ -41,7 +44,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-hook-form": "^7.14.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "6",
|
||||
"semver": "^6.3.0",
|
||||
"string-to-color": "^2.2.2",
|
||||
"use-clipboard-copy": "^0.2.0",
|
||||
@@ -55,12 +58,6 @@
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/addon-actions": "^6.4.19",
|
||||
"@storybook/addon-essentials": "^6.4.19",
|
||||
"@storybook/addon-interactions": "^6.4.19",
|
||||
"@storybook/addon-links": "^6.4.19",
|
||||
"@storybook/react": "^6.4.19",
|
||||
"@storybook/testing-library": "^0.0.9",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@tauri-apps/cli": "^1.0.0-rc.5",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||
<title>Nym Wallet</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Wallet</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -38,7 +38,7 @@ strum = { version = "0.23", features = ["derive"] }
|
||||
tauri = { version = "=1.0.0-rc.2", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.19.1", features = ["sync", "time"] }
|
||||
tokio = { version = "1.10", features = ["sync", "time"] }
|
||||
toml = "0.5.8"
|
||||
url = "2.2"
|
||||
|
||||
@@ -48,6 +48,7 @@ base64 = "0.13"
|
||||
zeroize = "1.4.3"
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmrs = "0.7.0"
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
@@ -58,11 +59,14 @@ config = { path = "../../common/config" }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
# Used for Type conversion, can be extracted but its a lot of work
|
||||
vesting-contract = { path = "../../contracts/vesting" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
nym-wallet-types = { path = "../nym-wallet-types" }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
tempfile = "3.3.0"
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
generate-ts = []
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_config;
|
||||
use crate::platform_constants::{CONFIG_DIR_NAME, CONFIG_FILENAME};
|
||||
use crate::{error::BackendError, network::Network as WalletNetwork};
|
||||
use config::defaults::all::Network;
|
||||
use config::defaults::{all::SupportedNetworks, ValidatorDetails};
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::{fs, io, path::PathBuf};
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use url::Url;
|
||||
use validator_client::nymd::AccountId as CosmosAccountId;
|
||||
|
||||
use config::defaults::all::Network;
|
||||
use config::defaults::{all::SupportedNetworks, ValidatorDetails};
|
||||
use nym_wallet_types::network::Network as WalletNetwork;
|
||||
use nym_wallet_types::network_config;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::platform_constants::{CONFIG_DIR_NAME, CONFIG_FILENAME};
|
||||
|
||||
pub const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str =
|
||||
"https://nymtech.net/.wellknown/wallet/validators.json";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use nym_types::error::TypesError;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::io;
|
||||
use std::num::ParseIntError;
|
||||
@@ -7,6 +8,11 @@ use validator_client::{nymd::error::NymdError, ValidatorClientError};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BackendError {
|
||||
#[error("{source}")]
|
||||
TypesError {
|
||||
#[from]
|
||||
source: TypesError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
Bip39Error {
|
||||
#[from]
|
||||
@@ -70,16 +76,10 @@ pub enum BackendError {
|
||||
ClientNotInitialized,
|
||||
#[error("No balance available for address {0}")]
|
||||
NoBalance(String),
|
||||
#[error("{0} is not a valid denomination string")]
|
||||
InvalidDenom(String),
|
||||
#[error("{0} is not a valid network denomination string")]
|
||||
InvalidNetworkDenom(String),
|
||||
#[error("The provided network is not supported (yet)")]
|
||||
NetworkNotSupported(config::defaults::all::Network),
|
||||
#[error("Could not access the local data storage directory")]
|
||||
UnknownStorageDirectory,
|
||||
#[error("No validator API URL configured")]
|
||||
NoValidatorApiUrlConfigured,
|
||||
#[error("The wallet file already exists")]
|
||||
WalletFileAlreadyExists,
|
||||
#[error("The wallet file is not found")]
|
||||
@@ -96,10 +96,10 @@ pub enum BackendError {
|
||||
WalletMnemonicAlreadyExistsInWalletLogin,
|
||||
#[error("Adding a different password to the wallet not currently supported")]
|
||||
WalletDifferentPasswordDetected,
|
||||
#[error("Unexpted mnemonic account for login")]
|
||||
#[error("Unexpected mnemonic account for login")]
|
||||
WalletUnexpectedMnemonicAccount,
|
||||
#[error("Unexpted multiple account entry for login")]
|
||||
WalletUnexpectedMultipleAccounts,
|
||||
// #[error("Unexpected multiple account entry for login")]
|
||||
// WalletUnexpectedMultipleAccounts,
|
||||
#[error("Failed to derive address from mnemonic")]
|
||||
FailedToDeriveAddress,
|
||||
#[error("{0}")]
|
||||
|
||||
@@ -3,19 +3,14 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use crate::menu::AddDefaultSubmenus;
|
||||
use crate::operations::{mixnet, simulate, validator_api, vesting};
|
||||
use crate::state::State;
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
use std::sync::Arc;
|
||||
use tauri::Menu;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
mod coin;
|
||||
mod config;
|
||||
mod error;
|
||||
mod menu;
|
||||
mod network;
|
||||
mod network_config;
|
||||
mod operations;
|
||||
mod platform_constants;
|
||||
@@ -23,6 +18,14 @@ mod state;
|
||||
mod utils;
|
||||
mod wallet_storage;
|
||||
|
||||
use crate::menu::AddDefaultSubmenus;
|
||||
use crate::operations::mixnet;
|
||||
use crate::operations::simulate;
|
||||
use crate::operations::validator_api;
|
||||
use crate::operations::vesting;
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
@@ -32,7 +35,6 @@ fn main() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
mixnet::account::add_account_for_password,
|
||||
mixnet::account::connect_with_mnemonic,
|
||||
mixnet::account::create_new_account,
|
||||
mixnet::account::create_new_mnemonic,
|
||||
mixnet::account::create_password,
|
||||
mixnet::account::does_password_file_exist,
|
||||
@@ -59,7 +61,9 @@ fn main() {
|
||||
mixnet::delegate::delegate_to_mixnode,
|
||||
mixnet::delegate::get_delegator_rewards,
|
||||
mixnet::delegate::get_pending_delegation_events,
|
||||
mixnet::delegate::get_reverse_mix_delegations_paged,
|
||||
mixnet::delegate::get_delegation_summary,
|
||||
mixnet::delegate::get_all_pending_delegation_events,
|
||||
mixnet::delegate::get_all_mix_delegations,
|
||||
mixnet::delegate::undelegate_from_mixnode,
|
||||
mixnet::epoch::get_current_epoch,
|
||||
mixnet::rewards::claim_delegator_reward,
|
||||
@@ -76,8 +80,6 @@ fn main() {
|
||||
network_config::update_validator_urls,
|
||||
state::load_config_from_files,
|
||||
state::save_config_to_files,
|
||||
utils::major_to_minor,
|
||||
utils::minor_to_major,
|
||||
utils::owns_gateway,
|
||||
utils::owns_mixnode,
|
||||
utils::get_env,
|
||||
@@ -114,19 +116,18 @@ fn main() {
|
||||
vesting::queries::vesting_get_gateway_pledge,
|
||||
vesting::queries::vesting_get_mixnode_pledge,
|
||||
vesting::queries::vesting_start_time,
|
||||
// simulate endpoints
|
||||
simulate::admin::simulate_update_contract_settings,
|
||||
simulate::cosmos::simulate_send,
|
||||
simulate::mixnet::simulate_bond_gateway,
|
||||
simulate::mixnet::simulate_bond_mixnode,
|
||||
simulate::mixnet::simulate_unbond_gateway,
|
||||
simulate::mixnet::simulate_bond_mixnode,
|
||||
simulate::mixnet::simulate_unbond_mixnode,
|
||||
simulate::mixnet::simulate_update_mixnode,
|
||||
simulate::mixnet::simulate_delegate_to_mixnode,
|
||||
simulate::mixnet::simulate_undelegate_from_mixnode,
|
||||
simulate::cosmos::simulate_send,
|
||||
simulate::vesting::simulate_vesting_bond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
simulate::vesting::simulate_vesting_unbond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
simulate::vesting::simulate_vesting_unbond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_mixnode,
|
||||
simulate::vesting::simulate_withdraw_vested_coins,
|
||||
@@ -153,6 +154,10 @@ fn setup_logging() {
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() {
|
||||
log_builder.filter_module("nym_wallet::operations", log::LevelFilter::Trace);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
|
||||
@@ -1,52 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::network::Network as WalletNetwork;
|
||||
use crate::state::State;
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
// When the UI queries validator urls we use this type
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ValidatorUrls {
|
||||
pub urls: Vec<ValidatorUrl>,
|
||||
}
|
||||
use nym_wallet_types::network::Network as WalletNetwork;
|
||||
use nym_wallet_types::network_config::{Validator, ValidatorUrl, ValidatorUrls};
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurl.ts"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ValidatorUrl {
|
||||
pub url: String,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
// The type used when adding or removing validators, effectively the input.
|
||||
// NOTE: we should consider if we want to split this up
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Validator {
|
||||
pub nymd_url: String,
|
||||
pub nymd_name: Option<String>,
|
||||
pub api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Validator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let nymd_url = format!("nymd_url: {}", self.nymd_url);
|
||||
let api_url = self
|
||||
.api_url
|
||||
.as_ref()
|
||||
.map(|api_url| format!(", api_url: {}", api_url))
|
||||
.unwrap_or_default();
|
||||
write!(f, "{nymd_url}{api_url}")
|
||||
}
|
||||
}
|
||||
use crate::error::BackendError;
|
||||
use crate::state::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_validator_nymd_urls(
|
||||
|
||||
@@ -1,71 +1,27 @@
|
||||
use crate::coin::{Coin, Denom};
|
||||
use crate::config::{Config, CUSTOM_SIMULATED_GAS_MULTIPLIER};
|
||||
use crate::error::BackendError;
|
||||
use crate::network::Network as WalletNetwork;
|
||||
use crate::network_config;
|
||||
use crate::nymd_client;
|
||||
use crate::state::{State, WalletAccountIds};
|
||||
use crate::wallet_storage::{self, DEFAULT_LOGIN_ID};
|
||||
|
||||
use bip39::{Language, Mnemonic};
|
||||
use config::defaults::all::Network;
|
||||
use config::defaults::COSMOS_DERIVATION_PATH;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use itertools::Itertools;
|
||||
use nym_types::account::{Account, AccountEntry, Balance};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_wallet_types::network::Network as WalletNetwork;
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::sync::RwLock;
|
||||
use url::Url;
|
||||
use validator_client::nymd::bip32::DerivationPath;
|
||||
use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet};
|
||||
use validator_client::nymd::{AccountId as CosmosAccountId, SigningNymdClient};
|
||||
use validator_client::Client;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/account.ts"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Account {
|
||||
contract_address: String,
|
||||
client_address: String,
|
||||
denom: Denom,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(contract_address: String, client_address: String, denom: Denom) -> Self {
|
||||
Account {
|
||||
contract_address,
|
||||
client_address,
|
||||
denom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/createdaccount.ts"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreatedAccount {
|
||||
account: Account,
|
||||
mnemonic: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/createdaccount.ts"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AccountEntry {
|
||||
id: String,
|
||||
address: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/balance.ts"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Balance {
|
||||
coin: Coin,
|
||||
printable_balance: String,
|
||||
}
|
||||
use validator_client::{nymd::SigningNymdClient, Client};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn connect_with_mnemonic(
|
||||
@@ -80,17 +36,17 @@ pub async fn connect_with_mnemonic(
|
||||
pub async fn get_balance(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Balance, BackendError> {
|
||||
let denom = state.read().await.current_network().denom().to_owned();
|
||||
let denom = state.read().await.current_network().denom();
|
||||
match nymd_client!(state)
|
||||
.get_balance(nymd_client!(state).address(), denom.parse()?)
|
||||
.get_balance(nymd_client!(state).address(), denom)
|
||||
.await
|
||||
{
|
||||
Ok(Some(coin)) => {
|
||||
let coin = Coin::new(&coin.amount.to_string(), &Denom::from_str(&coin.denom)?);
|
||||
let amount = MajorCurrencyAmount::from(coin);
|
||||
let printable_balance = amount.to_string();
|
||||
Ok(Balance {
|
||||
coin: coin.clone(),
|
||||
// haha, that's so junky : )
|
||||
printable_balance: format!("{} {}", coin.to_major().amount(), &denom[1..]),
|
||||
amount,
|
||||
printable_balance,
|
||||
})
|
||||
}
|
||||
Ok(None) => Err(BackendError::NoBalance(
|
||||
@@ -100,18 +56,6 @@ pub async fn get_balance(
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_new_account(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<CreatedAccount, BackendError> {
|
||||
let rand_mnemonic = random_mnemonic();
|
||||
let account = connect_with_mnemonic(rand_mnemonic.to_string(), state).await?;
|
||||
Ok(CreatedAccount {
|
||||
account,
|
||||
mnemonic: rand_mnemonic.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_new_mnemonic() -> String {
|
||||
random_mnemonic().to_string()
|
||||
@@ -135,7 +79,7 @@ pub async fn switch_network(
|
||||
Account::new(
|
||||
client.nymd.mixnet_contract_address().to_string(),
|
||||
client.nymd.address().to_string(),
|
||||
denom.parse()?,
|
||||
denom.try_into()?,
|
||||
)
|
||||
};
|
||||
|
||||
@@ -218,7 +162,7 @@ async fn _connect_with_mnemonic(
|
||||
Some(client) => Ok(Account::new(
|
||||
client.nymd.mixnet_contract_address().to_string(),
|
||||
client.nymd.address().to_string(),
|
||||
default_network.denom().parse()?,
|
||||
default_network.denom().try_into()?,
|
||||
)),
|
||||
None => Err(BackendError::NetworkNotSupported(
|
||||
config::defaults::DEFAULT_NETWORK,
|
||||
@@ -467,7 +411,7 @@ pub async fn add_account_for_password(
|
||||
account_id: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<AccountEntry, BackendError> {
|
||||
log::info!("Adding account: {account_id}");
|
||||
log::info!("Adding account for the current password: {account_id}");
|
||||
let mnemonic = Mnemonic::from_str(mnemonic)?;
|
||||
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
|
||||
// Currently we only support a single, default, login id in the wallet
|
||||
@@ -523,7 +467,7 @@ async fn set_state_with_all_accounts(
|
||||
.iter()
|
||||
.map(|account| {
|
||||
let mnemonic = account.mnemonic();
|
||||
let addresses: HashMap<WalletNetwork, CosmosAccountId> = WalletNetwork::iter()
|
||||
let addresses: HashMap<WalletNetwork, cosmrs::AccountId> = WalletNetwork::iter()
|
||||
.map(|network| {
|
||||
let config_network: Network = network.into();
|
||||
(
|
||||
@@ -568,7 +512,7 @@ pub async fn remove_account_for_password(
|
||||
fn derive_address(
|
||||
mnemonic: bip39::Mnemonic,
|
||||
prefix: &str,
|
||||
) -> Result<CosmosAccountId, BackendError> {
|
||||
) -> Result<cosmrs::AccountId, BackendError> {
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?
|
||||
.try_derive_accounts()?
|
||||
.first()
|
||||
@@ -637,8 +581,6 @@ fn _show_mnemonic_for_account_in_password(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::wallet_storage::{
|
||||
@@ -646,6 +588,8 @@ mod tests {
|
||||
account_data::{MnemonicAccount, WalletAccount},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
// This decryptes a stored wallet file using the same procedure as when signing in. Most tests
|
||||
// related to the encryped wallet storage is in `wallet_storage`.
|
||||
#[test]
|
||||
|
||||
@@ -1,53 +1,24 @@
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use mixnet_contract_common::ContractStateParams;
|
||||
use nym_wallet_types::admin::TauriContractStateParams;
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use cosmwasm_std::Uint128;
|
||||
use mixnet_contract_common::ContractStateParams;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/stateparams.ts"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TauriContractStateParams {
|
||||
minimum_mixnode_pledge: String,
|
||||
minimum_gateway_pledge: String,
|
||||
mixnode_rewarded_set_size: u32,
|
||||
mixnode_active_set_size: u32,
|
||||
}
|
||||
|
||||
impl From<ContractStateParams> for TauriContractStateParams {
|
||||
fn from(p: ContractStateParams) -> TauriContractStateParams {
|
||||
TauriContractStateParams {
|
||||
minimum_mixnode_pledge: p.minimum_mixnode_pledge.to_string(),
|
||||
minimum_gateway_pledge: p.minimum_gateway_pledge.to_string(),
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TauriContractStateParams> for ContractStateParams {
|
||||
type Error = BackendError;
|
||||
|
||||
fn try_from(p: TauriContractStateParams) -> Result<ContractStateParams, Self::Error> {
|
||||
Ok(ContractStateParams {
|
||||
minimum_mixnode_pledge: Uint128::try_from(p.minimum_mixnode_pledge.as_str())?,
|
||||
minimum_gateway_pledge: Uint128::try_from(p.minimum_gateway_pledge.as_str())?,
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_contract_settings(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<TauriContractStateParams, BackendError> {
|
||||
Ok(nymd_client!(state).get_contract_settings().await?.into())
|
||||
log::info!(">>> Getting contract settings");
|
||||
let res = nymd_client!(state).get_contract_settings().await?.into();
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -57,8 +28,14 @@ pub async fn update_contract_settings(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<TauriContractStateParams, BackendError> {
|
||||
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
|
||||
log::info!(
|
||||
">>> Updating contract settings: {:?}",
|
||||
mixnet_contract_settings_params
|
||||
);
|
||||
nymd_client!(state)
|
||||
.update_contract_settings(mixnet_contract_settings_params.clone(), fee)
|
||||
.await?;
|
||||
Ok(mixnet_contract_settings_params.into())
|
||||
let res = mixnet_contract_settings_params.into();
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -1,60 +1,101 @@
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::{Gateway, MixNode};
|
||||
use cosmwasm_std::Uint128;
|
||||
use mixnet_contract_common::{GatewayBond, MixNodeBond};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::gateway::GatewayBond;
|
||||
use nym_types::mixnode::MixNodeBond;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::Fee;
|
||||
use validator_client::nymd::{CosmWasmCoin, Fee};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.bond_gateway(gateway, owner_signature, pledge, fee)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
log::info!(
|
||||
">>> Bond gateway: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
&gateway.identity_key,
|
||||
pledge,
|
||||
&pledge_minor,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.bond_gateway(gateway, owner_signature, pledge_minor, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unbond_gateway(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state).unbond_gateway(fee).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state).unbond_mixnode(fee).await?;
|
||||
Ok(())
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(">>> Unbond gateway, fee = {:?}", fee);
|
||||
let res = nymd_client!(state).unbond_gateway(fee).await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.bond_mixnode(mixnode, owner_signature, pledge, fee)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
log::info!(
|
||||
">>> Bond mixnode: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
mixnode.identity_key,
|
||||
pledge,
|
||||
pledge_minor,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.bond_mixnode(mixnode, owner_signature, pledge_minor, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(">>> Unbond mixnode, fee = {:?}", fee);
|
||||
let res = nymd_client!(state).unbond_mixnode(fee).await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -62,37 +103,72 @@ pub async fn update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(
|
||||
">>> Update mixnode: profit_margin_percent = {}, fee {:?}",
|
||||
profit_margin_percent,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<MixNodeBond>, BackendError> {
|
||||
log::info!(">>> Get mixnode bond details");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_mixnode(client.nymd.address()).await?;
|
||||
Ok(bond)
|
||||
let res = MixNodeBond::from_mixnet_contract_mixnode_bond(bond)?;
|
||||
log::info!(
|
||||
"<<< identity_key = {:?}",
|
||||
res.as_ref().map(|r| r.mix_node.identity_key.to_string())
|
||||
);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn gateway_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<GatewayBond>, BackendError> {
|
||||
log::info!(">>> Get gateway bond details");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_gateway(client.nymd.address()).await?;
|
||||
Ok(bond)
|
||||
let res = GatewayBond::from_mixnet_contract_gateway_bond(bond)?;
|
||||
log::info!(
|
||||
"<<< identity_key = {:?}",
|
||||
res.as_ref().map(|r| r.gateway.identity_key.to_string())
|
||||
);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_operator_rewards(
|
||||
address: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Uint128, BackendError> {
|
||||
Ok(nymd_client!(state).get_operator_rewards(address).await?)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
log::info!(">>> Get operator rewards for {}", address);
|
||||
let denom = state.read().await.current_network().denom();
|
||||
let rewards_as_minor = nymd_client!(state).get_operator_rewards(address).await?;
|
||||
let coin = CosmWasmCoin::new(rewards_as_minor.u128(), denom.as_ref());
|
||||
let amount: MajorCurrencyAmount = coin.into();
|
||||
log::info!(
|
||||
"<<< rewards_as_minor = {}, amount = {}",
|
||||
rewards_as_minor,
|
||||
amount
|
||||
);
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::utils::DelegationEvent;
|
||||
use crate::utils::DelegationResult;
|
||||
use cosmwasm_std::Uint128;
|
||||
use mixnet_contract_common::{IdentityKey, PagedDelegatorDelegationsResponse};
|
||||
use crate::vesting::delegate::get_pending_vesting_delegation_events;
|
||||
use crate::{api_client, nymd_client};
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use nym_types::currency::{CurrencyDenom, MajorCurrencyAmount};
|
||||
use nym_types::delegation::{
|
||||
from_contract_delegation_events, Delegation, DelegationEvent, DelegationRecord,
|
||||
DelegationWithEverything, DelegationsSummaryResponse,
|
||||
};
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::Fee;
|
||||
@@ -14,30 +19,44 @@ use validator_client::nymd::Fee;
|
||||
pub async fn get_pending_delegation_events(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<DelegationEvent>, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
log::info!(">>> Get pending delegation events");
|
||||
let events = nymd_client!(state)
|
||||
.get_pending_delegation_events(nymd_client!(state).address().to_string(), None)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.into())
|
||||
.collect::<Vec<DelegationEvent>>())
|
||||
.await?;
|
||||
log::info!("<<< {} pending delegation events", events.len());
|
||||
log::trace!("<<< pending delegation events = {:?}", events);
|
||||
|
||||
match from_contract_delegation_events(events) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
amount: MajorCurrencyAmount,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.delegate_to_mixnode(identity, delegation.clone(), fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let delegation = amount.clone().into();
|
||||
log::info!(
|
||||
">>> Delegate to mixnode: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}",
|
||||
identity,
|
||||
Some(delegation.into()),
|
||||
))
|
||||
amount,
|
||||
delegation,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.delegate_to_mixnode(identity, delegation, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -45,24 +64,240 @@ pub async fn undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
nymd_client!(state)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(
|
||||
">>> Undelegate from mixnode: identity_key = {}, fee = {:?}",
|
||||
identity,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.remove_mixnode_delegation(identity, fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
None,
|
||||
))
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
struct DelegationWithHistory {
|
||||
pub delegation: Delegation,
|
||||
pub amount_sum: MajorCurrencyAmount,
|
||||
pub history: Vec<DelegationRecord>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_reverse_mix_delegations_paged(
|
||||
pub async fn get_all_mix_delegations(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<PagedDelegatorDelegationsResponse, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
.get_delegator_delegations_paged(nymd_client!(state).address().to_string(), None, None)
|
||||
.await?)
|
||||
) -> Result<Vec<DelegationWithEverything>, BackendError> {
|
||||
log::info!(">>> Get all mixnode delegations");
|
||||
|
||||
// TODO: add endpoint to validator API to get a single mix node bond
|
||||
let mixnodes = api_client!(state).get_mixnodes().await?;
|
||||
|
||||
let address = nymd_client!(state).address().to_string();
|
||||
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let denom: CurrencyDenom = denom_minor.clone().try_into()?;
|
||||
|
||||
log::info!(" >>> Get delegations");
|
||||
let delegations = nymd_client!(state)
|
||||
.get_delegator_delegations_paged(address.clone(), None, None) // get all delegations, ignoring paging
|
||||
.await?
|
||||
.delegations;
|
||||
log::info!(" <<< {} delegations", delegations.len());
|
||||
|
||||
// first get pending events from the mixnet contract (operations made with unlocked tokens)
|
||||
let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?;
|
||||
|
||||
// then get pending events from the vesting contract (operations made with locked tokens)
|
||||
let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?;
|
||||
for event in pending_vesting_events {
|
||||
pending_events_for_account.push(event);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
" <<< {} pending delegation events for account",
|
||||
pending_events_for_account.len()
|
||||
);
|
||||
|
||||
let mut map: HashMap<String, DelegationWithHistory> = HashMap::new();
|
||||
|
||||
for pending_event in pending_events_for_account.clone() {
|
||||
if delegations
|
||||
.iter()
|
||||
.any(|d| d.node_identity == pending_event.node_identity)
|
||||
{
|
||||
let amount = pending_event
|
||||
.amount
|
||||
.unwrap_or_else(|| MajorCurrencyAmount::zero(&denom));
|
||||
let delegation = DelegationWithHistory {
|
||||
delegation: Delegation {
|
||||
amount: amount.clone(),
|
||||
node_identity: pending_event.node_identity,
|
||||
proxy: None,
|
||||
owner: pending_event.address,
|
||||
block_height: pending_event.block_height,
|
||||
},
|
||||
amount_sum: amount,
|
||||
history: vec![],
|
||||
};
|
||||
map.insert(delegation.delegation.node_identity.clone(), delegation);
|
||||
}
|
||||
}
|
||||
|
||||
for d in delegations {
|
||||
// create record of delegation
|
||||
let delegated_on_iso_datetime = nymd_client!(state)
|
||||
.get_block_timestamp(Some(d.block_height as u32))
|
||||
.await?
|
||||
.to_rfc3339();
|
||||
let amount: MajorCurrencyAmount = d.amount.clone().into();
|
||||
let record = DelegationRecord {
|
||||
amount: amount.clone(),
|
||||
block_height: d.block_height,
|
||||
delegated_on_iso_datetime,
|
||||
};
|
||||
|
||||
let entry = map
|
||||
.entry(d.node_identity.clone())
|
||||
.or_insert(DelegationWithHistory {
|
||||
delegation: d.try_into()?,
|
||||
history: vec![],
|
||||
amount_sum: MajorCurrencyAmount::zero(&amount.denom),
|
||||
});
|
||||
|
||||
entry.history.push(record);
|
||||
entry.amount_sum = entry.amount_sum.clone() + amount;
|
||||
}
|
||||
|
||||
let mut with_everything: Vec<DelegationWithEverything> = vec![];
|
||||
|
||||
for item in map {
|
||||
let d = item.1.delegation;
|
||||
let history = item.1.history;
|
||||
let Delegation {
|
||||
owner,
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy,
|
||||
} = d;
|
||||
|
||||
log::trace!(
|
||||
" --- Delegation: node_identity = {}, amount = {}",
|
||||
node_identity,
|
||||
amount
|
||||
);
|
||||
|
||||
let mixnode = mixnodes
|
||||
.iter()
|
||||
.find(|m| m.mix_node.identity_key == node_identity);
|
||||
|
||||
let pledge_amount: Option<MajorCurrencyAmount> =
|
||||
mixnode.and_then(|m| m.pledge_amount.clone().try_into().ok());
|
||||
|
||||
let total_delegation: Option<MajorCurrencyAmount> =
|
||||
mixnode.and_then(|m| m.total_delegation.clone().try_into().ok());
|
||||
|
||||
let profit_margin_percent: Option<u8> = mixnode.map(|m| m.mix_node.profit_margin_percent);
|
||||
|
||||
log::trace!(" >>> Get accumulated rewards: address = {}", address);
|
||||
let accumulated_rewards = match nymd_client!(state)
|
||||
.get_delegator_rewards(address.clone(), node_identity.clone(), proxy.clone())
|
||||
.await
|
||||
{
|
||||
Ok(rewards) => {
|
||||
let reward = CosmWasmCoin {
|
||||
denom: denom_minor.to_string(),
|
||||
amount: rewards,
|
||||
};
|
||||
let amount = MajorCurrencyAmount::from(reward);
|
||||
log::trace!(" <<< rewards = {}, amount = {}", rewards, amount);
|
||||
Some(amount)
|
||||
}
|
||||
Err(_) => {
|
||||
log::trace!(" <<< no rewards waiting");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let pending_events =
|
||||
filter_pending_events(&node_identity, pending_events_for_account.clone());
|
||||
log::trace!(
|
||||
" --- pending events for mixnode = {}",
|
||||
pending_events.len()
|
||||
);
|
||||
|
||||
log::trace!(
|
||||
" >>> Get stake saturation: node_identity = {}",
|
||||
node_identity
|
||||
);
|
||||
let stake_saturation = api_client!(state)
|
||||
.get_mixnode_stake_saturation(&node_identity)
|
||||
.await
|
||||
.ok()
|
||||
.map(|r| r.saturation);
|
||||
log::trace!(" <<< {:?}", stake_saturation);
|
||||
|
||||
log::trace!(
|
||||
" >>> Get average uptime percentage: node_identity = {}",
|
||||
node_identity
|
||||
);
|
||||
let avg_uptime_percent = api_client!(state)
|
||||
.get_mixnode_avg_uptime(&node_identity)
|
||||
.await
|
||||
.ok()
|
||||
.map(|r| r.avg_uptime);
|
||||
log::trace!(" <<< {:?}", avg_uptime_percent);
|
||||
|
||||
log::trace!(
|
||||
" >>> Convert delegated on block height to timestamp: block_height = {}",
|
||||
d.block_height
|
||||
);
|
||||
let timestamp = nymd_client!(state)
|
||||
.get_block_timestamp(Some(d.block_height as u32))
|
||||
.await?;
|
||||
let delegated_on_iso_datetime = timestamp.to_rfc3339();
|
||||
log::trace!(
|
||||
" <<< timestamp = {:?}, delegated_on_iso_datetime = {:?}",
|
||||
timestamp,
|
||||
delegated_on_iso_datetime
|
||||
);
|
||||
|
||||
with_everything.push(DelegationWithEverything {
|
||||
owner: owner.to_string(),
|
||||
node_identity: node_identity.to_string(),
|
||||
amount: item.1.amount_sum,
|
||||
block_height,
|
||||
proxy: proxy.clone(),
|
||||
delegated_on_iso_datetime,
|
||||
stake_saturation,
|
||||
accumulated_rewards,
|
||||
profit_margin_percent,
|
||||
pledge_amount,
|
||||
avg_uptime_percent,
|
||||
total_delegation,
|
||||
pending_events,
|
||||
history,
|
||||
})
|
||||
}
|
||||
log::trace!("<<< {:?}", with_everything);
|
||||
|
||||
Ok(with_everything)
|
||||
}
|
||||
|
||||
fn filter_pending_events(
|
||||
node_identity: &str,
|
||||
pending_events: Vec<DelegationEvent>,
|
||||
) -> Vec<DelegationEvent> {
|
||||
pending_events
|
||||
.iter()
|
||||
.filter(|e| e.node_identity == node_identity)
|
||||
.cloned()
|
||||
.collect::<Vec<DelegationEvent>>()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -71,8 +306,70 @@ pub async fn get_delegator_rewards(
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Uint128, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(
|
||||
">>> Get delegator rewards: mix_identity = {}, proxy = {:?}",
|
||||
mix_identity,
|
||||
proxy
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.get_delegator_rewards(address, mix_identity, proxy)
|
||||
.await?)
|
||||
.await?;
|
||||
let coin = CosmWasmCoin::new(res.u128(), denom_minor.as_ref());
|
||||
let amount = coin.into();
|
||||
log::info!(">>> res = {}, amount = {}", res, amount);
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_delegation_summary(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationsSummaryResponse, BackendError> {
|
||||
log::info!(">>> Get delegation summary");
|
||||
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let denom: CurrencyDenom = denom_minor.clone().try_into()?;
|
||||
|
||||
let delegations = get_all_mix_delegations(state.clone()).await?;
|
||||
let mut total_delegations = MajorCurrencyAmount::zero(&denom);
|
||||
let mut total_rewards = MajorCurrencyAmount::zero(&denom);
|
||||
|
||||
for d in delegations.clone() {
|
||||
total_delegations = total_delegations + d.amount;
|
||||
if let Some(rewards) = d.accumulated_rewards {
|
||||
total_rewards = total_rewards + rewards;
|
||||
}
|
||||
}
|
||||
log::info!(
|
||||
"<<< {} delegations, total_delegations = {}, total_rewards = {}",
|
||||
delegations.len(),
|
||||
total_delegations,
|
||||
total_rewards
|
||||
);
|
||||
log::trace!("<<< {:?}", delegations);
|
||||
|
||||
Ok(DelegationsSummaryResponse {
|
||||
delegations,
|
||||
total_delegations,
|
||||
total_rewards,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_all_pending_delegation_events(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<DelegationEvent>, BackendError> {
|
||||
log::info!(">>> Get all pending delegation events");
|
||||
|
||||
// get pending events from mixnet and vesting contract
|
||||
let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?;
|
||||
let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?;
|
||||
|
||||
// combine them
|
||||
for event in pending_vesting_events {
|
||||
pending_events_for_account.push(event);
|
||||
}
|
||||
|
||||
Ok(pending_events_for_account)
|
||||
}
|
||||
|
||||
@@ -1,36 +1,16 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use mixnet_contract_common::Interval;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use nym_wallet_types::epoch::Epoch;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/epoch.ts"))]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct Epoch {
|
||||
id: u32,
|
||||
start: i64,
|
||||
end: i64,
|
||||
duration_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<Interval> for Epoch {
|
||||
fn from(interval: Interval) -> Self {
|
||||
Self {
|
||||
id: interval.id(),
|
||||
start: interval.start_unix_timestamp(),
|
||||
end: interval.end_unix_timestamp(),
|
||||
duration_seconds: interval.length().as_secs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_current_epoch(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Epoch, BackendError> {
|
||||
log::info!(">>> Get curren epoch");
|
||||
let interval = nymd_client!(state).get_current_epoch().await?;
|
||||
log::info!("<<< curren epoch = {}", interval);
|
||||
Ok(interval.into())
|
||||
}
|
||||
|
||||
@@ -1,69 +1,46 @@
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::transaction::{SendTxResult, TransactionDetails};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{AccountId, Fee, TxResponse};
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/tauritxresult.ts"))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TauriTxResult {
|
||||
block_height: u64,
|
||||
code: u32,
|
||||
details: TransactionDetails,
|
||||
gas_used: u64,
|
||||
gas_wanted: u64,
|
||||
tx_hash: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/transactiondetails.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TransactionDetails {
|
||||
amount: Coin,
|
||||
from_address: String,
|
||||
to_address: String,
|
||||
}
|
||||
|
||||
impl TauriTxResult {
|
||||
fn new(t: TxResponse, details: TransactionDetails) -> TauriTxResult {
|
||||
TauriTxResult {
|
||||
block_height: t.height.value(),
|
||||
code: t.tx_result.code.value(),
|
||||
details,
|
||||
gas_used: t.tx_result.gas_used.value(),
|
||||
gas_wanted: t.tx_result.gas_wanted.value(),
|
||||
tx_hash: t.hash.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
use validator_client::nymd::{AccountId, Fee};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send(
|
||||
address: &str,
|
||||
amount: Coin,
|
||||
amount: MajorCurrencyAmount,
|
||||
memo: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<TauriTxResult, BackendError> {
|
||||
) -> Result<SendTxResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let address = AccountId::from_str(address)?;
|
||||
let amount = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
let result = nymd_client!(state)
|
||||
.send(&address, vec![amount.clone()], memo, fee)
|
||||
let from_address = nymd_client!(state).address().to_string();
|
||||
let amount2 = amount.clone().into();
|
||||
log::info!(
|
||||
">>> Send: amount = {}, minor_amount = {:?}, from = {}, to = {}, fee = {:?}",
|
||||
amount,
|
||||
amount2,
|
||||
from_address,
|
||||
address.as_ref(),
|
||||
fee,
|
||||
);
|
||||
let raw_res = nymd_client!(state)
|
||||
.send(&address, vec![amount2], memo, fee)
|
||||
.await?;
|
||||
Ok(TauriTxResult::new(
|
||||
result,
|
||||
log::info!("<<< tx hash = {}", raw_res.hash.to_string());
|
||||
let res = SendTxResult::new(
|
||||
raw_res,
|
||||
TransactionDetails {
|
||||
from_address: nymd_client!(state).address().to_string(),
|
||||
from_address,
|
||||
to_address: address.to_string(),
|
||||
amount: amount.into(),
|
||||
amount,
|
||||
},
|
||||
))
|
||||
denom_minor.as_ref(),
|
||||
)?;
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::mixnet::admin::TauriContractStateParams;
|
||||
use crate::simulate::{FeeDetails, SimulateResult};
|
||||
use crate::operations::simulate::{FeeDetails, SimulateResult};
|
||||
use crate::State;
|
||||
use mixnet_contract_common::{ContractStateParams, ExecuteMsg};
|
||||
use nym_wallet_types::admin::TauriContractStateParams;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -28,5 +28,5 @@ pub async fn simulate_update_contract_settings(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::simulate::{FeeDetails, SimulateResult};
|
||||
use crate::state::State;
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -13,13 +13,13 @@ use validator_client::nymd::{AccountId, MsgSend};
|
||||
#[tauri::command]
|
||||
pub async fn simulate_send(
|
||||
address: &str,
|
||||
amount: Coin,
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
|
||||
let to_address = AccountId::from_str(address)?;
|
||||
let amount = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
let amount = vec![amount.into()];
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let from_address = client.nymd.address().clone();
|
||||
@@ -29,9 +29,9 @@ pub async fn simulate_send(
|
||||
let msg = MsgSend {
|
||||
from_address,
|
||||
to_address,
|
||||
amount: vec![amount.into()],
|
||||
amount,
|
||||
};
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::simulate::{FeeDetails, SimulateResult};
|
||||
use crate::operations::simulate::{FeeDetails, SimulateResult};
|
||||
use crate::State;
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use mixnet_contract_common::{ExecuteMsg, Gateway, MixNode};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let pledge = pledge.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
@@ -36,7 +36,7 @@ pub async fn simulate_bond_gateway(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -56,18 +56,18 @@ pub async fn simulate_unbond_gateway(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let pledge = pledge.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
@@ -83,7 +83,7 @@ pub async fn simulate_bond_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -103,7 +103,7 @@ pub async fn simulate_unbond_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -126,17 +126,17 @@ pub async fn simulate_update_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let delegation = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
let delegation = amount.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
@@ -151,7 +151,7 @@ pub async fn simulate_delegate_to_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -174,7 +174,7 @@ pub async fn simulate_undelegate_from_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -185,7 +185,7 @@ pub async fn simulate_claim_operator_reward(
|
||||
.simulate_claim_operator_reward(None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -196,7 +196,7 @@ pub async fn simulate_compound_operator_reward(
|
||||
.simulate_compound_operator_reward(None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -208,7 +208,7 @@ pub async fn simulate_claim_delegator_reward(
|
||||
.simulate_claim_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -220,5 +220,5 @@ pub async fn simulate_compound_delegator_reward(
|
||||
.simulate_compound_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::coin::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::error::TypesError;
|
||||
use nym_types::fees::FeeDetails;
|
||||
use validator_client::nymd::cosmwasm_client::types::GasInfo;
|
||||
use validator_client::nymd::{tx, CosmosCoin, Fee, GasPrice};
|
||||
|
||||
@@ -28,21 +29,16 @@ impl SimulateResult {
|
||||
gas_price,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FeeDetails {
|
||||
// expected to be used by the wallet in order to display detailed fee information to the user
|
||||
pub amount: Option<Coin>,
|
||||
pub fee: Fee,
|
||||
}
|
||||
|
||||
impl SimulateResult {
|
||||
pub fn detailed_fee(&self) -> FeeDetails {
|
||||
FeeDetails {
|
||||
amount: self.to_fee_amount().map(Into::into),
|
||||
pub fn detailed_fee(&self) -> Result<FeeDetails, TypesError> {
|
||||
let amount: Option<MajorCurrencyAmount> = match self.to_fee_amount() {
|
||||
Some(a) => Some(a.into()),
|
||||
None => None,
|
||||
};
|
||||
Ok(FeeDetails {
|
||||
amount,
|
||||
fee: self.to_fee(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn to_fee_amount(&self) -> Option<CosmosCoin> {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::simulate::{FeeDetails, SimulateResult};
|
||||
use crate::operations::simulate::{FeeDetails, SimulateResult};
|
||||
use crate::State;
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use vesting_contract_common::ExecuteMsg;
|
||||
@@ -15,12 +15,12 @@ use vesting_contract_common::ExecuteMsg;
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let pledge = pledge.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
@@ -31,13 +31,13 @@ pub async fn simulate_vesting_bond_gateway(
|
||||
&ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
amount: pledge.into(),
|
||||
amount: pledge,
|
||||
},
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -57,18 +57,18 @@ pub async fn simulate_vesting_unbond_gateway(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let pledge = pledge.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
@@ -79,13 +79,13 @@ pub async fn simulate_vesting_bond_mixnode(
|
||||
&ExecuteMsg::BondMixnode {
|
||||
mix_node: mixnode,
|
||||
owner_signature,
|
||||
amount: pledge.into(),
|
||||
amount: pledge,
|
||||
},
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -105,7 +105,7 @@ pub async fn simulate_vesting_unbond_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -128,16 +128,16 @@ pub async fn simulate_vesting_update_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let amount = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
let amount = amount.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
@@ -145,14 +145,12 @@ pub async fn simulate_withdraw_vested_coins(
|
||||
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: amount.into(),
|
||||
},
|
||||
&ExecuteMsg::WithdrawVestedCoins { amount },
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -163,7 +161,7 @@ pub async fn simulate_vesting_claim_operator_reward(
|
||||
.simulate_vesting_claim_operator_reward(None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -174,7 +172,7 @@ pub async fn simulate_vesting_compound_operator_reward(
|
||||
.simulate_vesting_compound_operator_reward(None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -186,7 +184,7 @@ pub async fn simulate_vesting_claim_delegator_reward(
|
||||
.simulate_vesting_claim_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -198,5 +196,5 @@ pub async fn simulate_vesting_compound_delegator_reward(
|
||||
.simulate_vesting_compound_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
let gas_price = nymd_client!(state).gas_price().clone();
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()?)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::{Gateway, MixNode};
|
||||
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
@@ -10,62 +12,120 @@ use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
#[tauri::command]
|
||||
pub async fn vesting_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_bond_gateway(gateway, &owner_signature, pledge, fee)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
log::info!(
|
||||
">>> Bond gateway with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
gateway.identity_key,
|
||||
pledge,
|
||||
pledge_minor,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.vesting_bond_gateway(gateway, &owner_signature, pledge_minor, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_unbond_gateway(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state).vesting_unbond_gateway(fee).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state).vesting_unbond_mixnode(fee).await?;
|
||||
Ok(())
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(
|
||||
">>> Unbond gateway bonded with locked tokens, fee = {:?}",
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state).vesting_unbond_gateway(fee).await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
pledge: MajorCurrencyAmount,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_bond_mixnode(mixnode, &owner_signature, pledge, fee)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
log::info!(
|
||||
">>> Bond mixnode with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
mixnode.identity_key,
|
||||
pledge,
|
||||
pledge_minor,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.vesting_bond_mixnode(mixnode, &owner_signature, pledge_minor, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(
|
||||
">>> Unbond mixnode bonded with locked tokens, fee = {:?}",
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state).vesting_unbond_mixnode(fee).await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
amount: MajorCurrencyAmount,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let amount = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.withdraw_vested_coins(amount, fee)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let amount_minor = amount.clone().into();
|
||||
log::info!(
|
||||
">>> Withdraw vested liquid coins: amount = {}, amount_minor = {}, fee = {:?}",
|
||||
amount,
|
||||
amount_minor,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.withdraw_vested_coins(amount_minor, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -73,9 +133,20 @@ pub async fn vesting_update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(
|
||||
">>> Update mixnode bonded with locked tokens: profit_margin_percent = {}, fee = {:?}",
|
||||
profit_margin_percent,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.vesting_update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -1,48 +1,67 @@
|
||||
use crate::coin::Coin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::delegation::{from_contract_delegation_events, DelegationEvent};
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::utils::DelegationEvent;
|
||||
use crate::utils::DelegationResult;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_pending_vesting_delegation_events(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<DelegationEvent>, BackendError> {
|
||||
log::info!(">>> Get pending delegations from vesting contract");
|
||||
|
||||
let guard = state.read().await;
|
||||
let client = &guard.current_client()?.nymd;
|
||||
let vesting_contract = client.vesting_contract_address();
|
||||
|
||||
Ok(client
|
||||
let events = client
|
||||
.get_pending_delegation_events(
|
||||
client.address().to_string(),
|
||||
Some(vesting_contract.to_string()),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.into())
|
||||
.collect::<Vec<DelegationEvent>>())
|
||||
.await?;
|
||||
|
||||
log::info!("<<< {} events", events.len());
|
||||
log::trace!("<<< {:?}", events);
|
||||
|
||||
match from_contract_delegation_events(events) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
amount: MajorCurrencyAmount,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_delegate_to_mixnode(identity, delegation.clone(), fee)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
let delegation = amount.clone().into();
|
||||
log::info!(
|
||||
">>> Delegate to mixnode with locked tokens: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}",
|
||||
identity,
|
||||
amount,
|
||||
delegation,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.vesting_delegate_to_mixnode(identity, delegation, fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
Some(delegation.into()),
|
||||
))
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -50,13 +69,20 @@ pub async fn vesting_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
nymd_client!(state)
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().denom();
|
||||
log::info!(
|
||||
">>> Undelegate from mixnode delegated with locked tokens: identity_key = {}, fee = {:?}",
|
||||
identity,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.vesting_undelegate_from_mixnode(identity, fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
None,
|
||||
))
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -1,103 +1,4 @@
|
||||
use crate::coin::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vesting_contract::vesting::Account as VestingAccount;
|
||||
use vesting_contract::vesting::VestingPeriod as VestingVestingPeriod;
|
||||
use vesting_contract_common::OriginalVestingResponse as VestingOriginalVestingResponse;
|
||||
use vesting_contract_common::PledgeData as VestingPledgeData;
|
||||
|
||||
pub mod bond;
|
||||
pub mod delegate;
|
||||
pub mod queries;
|
||||
pub mod rewards;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/pledgedata.ts"))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PledgeData {
|
||||
pub amount: Coin,
|
||||
pub block_time: u64,
|
||||
}
|
||||
|
||||
impl From<VestingPledgeData> for PledgeData {
|
||||
fn from(data: VestingPledgeData) -> Self {
|
||||
Self {
|
||||
amount: data.amount().into(),
|
||||
block_time: data.block_time().seconds(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
fn and_then(data: VestingPledgeData) -> Option<Self> {
|
||||
Some(data.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/originalvestingresponse.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: Coin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
}
|
||||
|
||||
impl From<VestingOriginalVestingResponse> for OriginalVestingResponse {
|
||||
fn from(data: VestingOriginalVestingResponse) -> Self {
|
||||
Self {
|
||||
amount: data.amount().into(),
|
||||
number_of_periods: data.number_of_periods(),
|
||||
period_duration: data.period_duration(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/vestingaccountinfo.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VestingAccountInfo {
|
||||
owner_address: String,
|
||||
staking_address: Option<String>,
|
||||
start_time: u64,
|
||||
periods: Vec<VestingPeriod>,
|
||||
coin: Coin,
|
||||
}
|
||||
|
||||
impl From<VestingAccount> for VestingAccountInfo {
|
||||
fn from(account: VestingAccount) -> Self {
|
||||
let mut periods = Vec::new();
|
||||
for period in account.periods() {
|
||||
periods.push(period.into());
|
||||
}
|
||||
Self {
|
||||
owner_address: account.owner_address().to_string(),
|
||||
staking_address: account.staking_address().map(|a| a.to_string()),
|
||||
start_time: account.start_time().seconds(),
|
||||
periods,
|
||||
coin: account.coin().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/vestingperiod.ts"))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VestingPeriod {
|
||||
start_time: u64,
|
||||
period_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<VestingVestingPeriod> for VestingPeriod {
|
||||
fn from(period: VestingVestingPeriod) -> Self {
|
||||
Self {
|
||||
start_time: period.start_time,
|
||||
period_seconds: period.period_seconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,50 @@
|
||||
use super::VestingAccountInfo;
|
||||
use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use cosmwasm_std::Timestamp;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cosmwasm_std::Timestamp;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::vesting::VestingAccountInfo;
|
||||
use nym_types::vesting::{OriginalVestingResponse, PledgeData};
|
||||
use validator_client::nymd::VestingQueryClient;
|
||||
use vesting_contract_common::Period;
|
||||
|
||||
use super::{OriginalVestingResponse, PledgeData};
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn locked_coins(
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
log::info!(">>> Query locked coins");
|
||||
let res = nymd_client!(state)
|
||||
.locked_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
.into();
|
||||
log::info!("<<< locked coins = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn spendable_coins(
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
log::info!(">>> Query spendable coins");
|
||||
let res = nymd_client!(state)
|
||||
.spendable_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
.into();
|
||||
log::info!("<<< spendable coins = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -44,14 +52,17 @@ pub async fn vested_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
log::info!(">>> Query vested coins");
|
||||
let res = nymd_client!(state)
|
||||
.vested_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
.into();
|
||||
log::info!("<<< vested coins = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -59,14 +70,17 @@ pub async fn vesting_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
log::info!(">>> Query vesting coins");
|
||||
let res = nymd_client!(state)
|
||||
.vesting_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
.into();
|
||||
log::info!("<<< vesting coins = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -74,10 +88,13 @@ pub async fn vesting_start_time(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<u64, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
log::info!(">>> Query vesting start time");
|
||||
let res = nymd_client!(state)
|
||||
.vesting_start_time(vesting_account_address)
|
||||
.await?
|
||||
.seconds())
|
||||
.seconds();
|
||||
log::info!("<<< vesting start time = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -85,10 +102,13 @@ pub async fn vesting_end_time(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<u64, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
log::info!(">>> Query vesting end time");
|
||||
let res = nymd_client!(state)
|
||||
.vesting_end_time(vesting_account_address)
|
||||
.await?
|
||||
.seconds())
|
||||
.seconds();
|
||||
log::info!("<<< vesting end time = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -96,10 +116,13 @@ pub async fn original_vesting(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<OriginalVestingResponse, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
log::info!(">>> Query original vesting");
|
||||
let res = nymd_client!(state)
|
||||
.original_vesting(vesting_account_address)
|
||||
.await?
|
||||
.into())
|
||||
.try_into()?;
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -107,29 +130,36 @@ pub async fn delegated_free(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
log::info!(">>> Query delegated free");
|
||||
let res = nymd_client!(state)
|
||||
.delegated_free(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
.into();
|
||||
log::info!("<<< delegated free = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns the total amount of delegated tokens that have vested
|
||||
#[tauri::command]
|
||||
pub async fn delegated_vesting(
|
||||
block_time: Option<u64>,
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
log::info!(">>> Query delegated vesting");
|
||||
let res = nymd_client!(state)
|
||||
.delegated_vesting(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
.into();
|
||||
log::info!("<<< delegated_vesting = {}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -137,10 +167,13 @@ pub async fn vesting_get_mixnode_pledge(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<PledgeData>, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
log::info!(">>> Query vesting get mixnode pledge");
|
||||
let res = nymd_client!(state)
|
||||
.get_mixnode_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then))
|
||||
.and_then(PledgeData::and_then);
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -148,10 +181,13 @@ pub async fn vesting_get_gateway_pledge(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<PledgeData>, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
log::info!(">>> Query vesting get gateway pledge");
|
||||
let res = nymd_client!(state)
|
||||
.get_gateway_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then))
|
||||
.and_then(PledgeData::and_then);
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -159,9 +195,12 @@ pub async fn get_current_vesting_period(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Period, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
log::info!(">>> Query current vesting period");
|
||||
let res = nymd_client!(state)
|
||||
.get_current_vesting_period(address)
|
||||
.await?)
|
||||
.await?;
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -169,5 +208,8 @@ pub async fn get_account_info(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<VestingAccountInfo, BackendError> {
|
||||
Ok(nymd_client!(state).get_account(address).await?.into())
|
||||
log::info!(">>> Query account info");
|
||||
let res = nymd_client!(state).get_account(address).await?.try_into()?;
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::config;
|
||||
use crate::error::BackendError;
|
||||
use crate::network::Network;
|
||||
use crate::{config, network_config};
|
||||
use nym_wallet_types::network::Network;
|
||||
use nym_wallet_types::network_config;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
use validator_client::nymd::{AccountId as CosmosAccountId, SigningNymdClient};
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
use crate::coin::{Coin, Denom};
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent;
|
||||
use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate;
|
||||
use mixnet_contract_common::Delegation;
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_wallet_types::app::AppEnv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{tx, CosmosCoin, Gas, GasPrice};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/appEnv.ts"))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct AppEnv {
|
||||
pub ADMIN_ADDRESS: Option<String>,
|
||||
pub SHOW_TERMINAL: Option<String>,
|
||||
}
|
||||
use validator_client::nymd::{tx, Coin, CosmosCoin, Gas, GasPrice};
|
||||
|
||||
fn get_env_as_option(key: &str) -> Option<String> {
|
||||
match ::std::env::var(key) {
|
||||
@@ -34,18 +23,6 @@ pub fn get_env() -> AppEnv {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn major_to_minor(amount: &str) -> Coin {
|
||||
let coin = Coin::new(amount, &Denom::Major);
|
||||
coin.to_minor()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn minor_to_major(amount: &str) -> Coin {
|
||||
let coin = Coin::new(amount, &Denom::Minor);
|
||||
coin.to_major()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn owns_mixnode(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
@@ -169,86 +146,11 @@ impl Operation {
|
||||
pub async fn get_old_and_incorrect_hardcoded_fee(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
operation: Operation,
|
||||
) -> Result<Coin, BackendError> {
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
let mut approximate_fee = operation.default_fee(nymd_client!(state).gas_price());
|
||||
// on all our chains it should only ever contain a single type of currency
|
||||
assert_eq!(approximate_fee.amount.len(), 1);
|
||||
let coin: Coin = approximate_fee.amount.pop().unwrap().into();
|
||||
log::info!("hardcoded fee for {:?} is {:?}", operation, coin);
|
||||
Ok(coin.to_major())
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/delegationresult.ts"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DelegationResult {
|
||||
source_address: String,
|
||||
target_address: String,
|
||||
amount: Option<Coin>,
|
||||
}
|
||||
|
||||
impl DelegationResult {
|
||||
pub fn new(
|
||||
source_address: &str,
|
||||
target_address: &str,
|
||||
amount: Option<Coin>,
|
||||
) -> DelegationResult {
|
||||
DelegationResult {
|
||||
source_address: source_address.to_string(),
|
||||
target_address: target_address.to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Delegation> for DelegationResult {
|
||||
fn from(delegation: Delegation) -> Self {
|
||||
DelegationResult {
|
||||
source_address: delegation.owner().to_string(),
|
||||
target_address: delegation.node_identity(),
|
||||
amount: Some(delegation.amount.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/delegationevent.ts"))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum DelegationEvent {
|
||||
Delegate(DelegationResult),
|
||||
Undelegate(PendingUndelegate),
|
||||
}
|
||||
|
||||
impl From<ContractDelegationEvent> for DelegationEvent {
|
||||
fn from(event: ContractDelegationEvent) -> Self {
|
||||
match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => {
|
||||
DelegationEvent::Delegate(delegation.into())
|
||||
}
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => {
|
||||
DelegationEvent::Undelegate(pending_undelegate.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/pendingundelegate.ts"))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: String,
|
||||
delegate: String,
|
||||
proxy: Option<String>,
|
||||
block_height: u64,
|
||||
}
|
||||
|
||||
impl From<ContractPendingUndelegate> for PendingUndelegate {
|
||||
fn from(pending_undelegate: ContractPendingUndelegate) -> Self {
|
||||
PendingUndelegate {
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
delegate: pending_undelegate.delegate().to_string(),
|
||||
proxy: pending_undelegate.proxy().map(|p| p.to_string()),
|
||||
block_height: pending_undelegate.block_height(),
|
||||
}
|
||||
}
|
||||
Ok(coin.into())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@mui/material';
|
||||
import { AccountEntry } from 'src/types';
|
||||
import { AccountEntry } from '@nymproject/types';
|
||||
import { AccountAvatar } from './AccountAvatar';
|
||||
|
||||
export const AccountOverview = ({ account, onClick }: { account: AccountEntry; onClick: () => void }) => (
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Logout } from '@mui/icons-material';
|
||||
import TerminalIcon from '@mui/icons-material/Terminal';
|
||||
import { AppContext } from '../context/main';
|
||||
import { NetworkSelector } from './NetworkSelector';
|
||||
import { Node as NodeIcon } from '../svg-icons/node';
|
||||
import { MultiAccounts } from './Accounts';
|
||||
import { config } from '../../config';
|
||||
import { config } from '../config';
|
||||
|
||||
export const AppBar = () => {
|
||||
const { showSettings, logOut, handleShowSettings, handleShowTerminal, appEnv } = useContext(AppContext);
|
||||
const history = useHistory();
|
||||
const { logOut, handleShowTerminal, appEnv } = useContext(AppContext);
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent' }}>
|
||||
<Toolbar disableGutters>
|
||||
@@ -32,21 +31,13 @@ export const AppBar = () => {
|
||||
</IconButton>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item>
|
||||
<IconButton
|
||||
onClick={handleShowSettings}
|
||||
sx={{ color: showSettings ? 'primary.main' : 'nym.background.dark' }}
|
||||
size="small"
|
||||
>
|
||||
<NodeIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
await logOut();
|
||||
history.push('/');
|
||||
navigate('/');
|
||||
}}
|
||||
sx={{ color: 'nym.background.dark' }}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { getGasFee } from 'src/requests';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalListItem } from './ModalListItem';
|
||||
import { validateKey } from '../../utils';
|
||||
import { TokenPoolSelector, TPoolOption } from '../TokenPoolSelector';
|
||||
|
||||
const MIN_AMOUNT_TO_DELEGATE = 10;
|
||||
|
||||
export const DelegateModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string, amount: MajorCurrencyAmount, tokenPool: TPoolOption) => Promise<void>;
|
||||
identityKey?: string;
|
||||
onIdentityKeyChanged?: (identityKey: string) => void;
|
||||
onAmountChanged?: (amount: string) => void;
|
||||
header?: string;
|
||||
buttonText?: string;
|
||||
rewardInterval: string;
|
||||
accountBalance?: string;
|
||||
estimatedReward?: number;
|
||||
profitMarginPercentage?: number | null;
|
||||
nodeUptimePercentage?: number | null;
|
||||
feeOverride?: string;
|
||||
currency: CurrencyDenom;
|
||||
initialAmount?: string;
|
||||
hasVestingContract: boolean;
|
||||
}> = ({
|
||||
open,
|
||||
onIdentityKeyChanged,
|
||||
onAmountChanged,
|
||||
onClose,
|
||||
onOk,
|
||||
header,
|
||||
buttonText,
|
||||
identityKey: initialIdentityKey,
|
||||
rewardInterval,
|
||||
accountBalance,
|
||||
feeOverride,
|
||||
estimatedReward,
|
||||
currency,
|
||||
profitMarginPercentage,
|
||||
nodeUptimePercentage,
|
||||
initialAmount,
|
||||
hasVestingContract,
|
||||
}) => {
|
||||
const [identityKey, setIdentityKey] = useState<string | undefined>(initialIdentityKey);
|
||||
const [amount, setAmount] = useState<string | undefined>(initialAmount);
|
||||
const [isValidated, setValidated] = useState<boolean>(false);
|
||||
const [errorAmount, setErrorAmount] = useState<string | undefined>();
|
||||
const [tokenPool, setTokenPool] = useState<TPoolOption>('balance');
|
||||
const [fee, setFee] = useState<string>();
|
||||
|
||||
const getFee = async () => {
|
||||
if (feeOverride) setFee(feeOverride);
|
||||
else {
|
||||
const res = await getGasFee('BondMixnode');
|
||||
setFee(res.amount);
|
||||
}
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
let newValidatedValue = true;
|
||||
if (!identityKey || !validateKey(identityKey, 32)) {
|
||||
newValidatedValue = false;
|
||||
}
|
||||
if (amount && Number(amount) < MIN_AMOUNT_TO_DELEGATE) {
|
||||
setErrorAmount(`Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${currency}`);
|
||||
newValidatedValue = false;
|
||||
} else {
|
||||
setErrorAmount(undefined);
|
||||
}
|
||||
setValidated(newValidatedValue);
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
if (onOk && amount && identityKey) {
|
||||
onOk(identityKey, { amount, denom: currency }, tokenPool);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIdentityKeyChanged = (newIdentityKey: string) => {
|
||||
setIdentityKey(newIdentityKey);
|
||||
if (onIdentityKeyChanged) {
|
||||
onIdentityKeyChanged(newIdentityKey);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAmountChanged = (newAmount: MajorCurrencyAmount) => {
|
||||
setAmount(newAmount.amount);
|
||||
if (onAmountChanged) {
|
||||
onAmountChanged(newAmount.amount);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
validate();
|
||||
}, [amount, identityKey]);
|
||||
|
||||
React.useEffect(() => {
|
||||
getFee();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={handleOk}
|
||||
header={header || 'Delegate'}
|
||||
subHeader="Delegate to mixnode"
|
||||
okLabel={buttonText || 'Delegate stake'}
|
||||
okDisabled={!isValidated}
|
||||
>
|
||||
<IdentityKeyFormField
|
||||
required
|
||||
fullWidth
|
||||
placeholder="Node identity key"
|
||||
onChanged={handleIdentityKeyChanged}
|
||||
initialValue={initialIdentityKey}
|
||||
readOnly={Boolean(initialIdentityKey)}
|
||||
textFieldProps={{
|
||||
autoFocus: !initialIdentityKey,
|
||||
}}
|
||||
/>
|
||||
<Box display="flex" gap={2} alignItems="center" sx={{ mt: 2 }}>
|
||||
{hasVestingContract && <TokenPoolSelector disabled={false} onSelect={(pool) => setTokenPool(pool)} />}
|
||||
<CurrencyFormField
|
||||
required
|
||||
fullWidth
|
||||
placeholder="Amount"
|
||||
initialValue={initialAmount}
|
||||
autoFocus={Boolean(initialIdentityKey)}
|
||||
onChanged={handleAmountChanged}
|
||||
/>
|
||||
</Box>
|
||||
<Typography component="div" textAlign="right" variant="caption" sx={{ color: 'error.main' }}>
|
||||
{errorAmount}
|
||||
</Typography>
|
||||
<Stack direction="row" justifyContent="space-between" my={3}>
|
||||
<Typography fontWeight={600}>Account balance</Typography>
|
||||
<Typography fontWeight={600}>{accountBalance}</Typography>
|
||||
</Stack>
|
||||
<ModalListItem label="Rewards payout interval" value={rewardInterval} hidden divider />
|
||||
<ModalListItem
|
||||
label="Node profit margin"
|
||||
value={`${profitMarginPercentage ? `${profitMarginPercentage}%` : '-'}`}
|
||||
hidden={profitMarginPercentage === undefined}
|
||||
divider
|
||||
/>
|
||||
<ModalListItem
|
||||
label="Node uptime"
|
||||
value={`${nodeUptimePercentage ? `${nodeUptimePercentage}%` : '-'}`}
|
||||
hidden={nodeUptimePercentage === undefined}
|
||||
divider
|
||||
/>
|
||||
|
||||
<ModalListItem label="Node est. reward per epoch" value={`${estimatedReward} ${currency}`} hidden divider />
|
||||
<Stack direction="row" justifyContent="space-between" mt={4}>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { DelegationActions } from './DelegationActions';
|
||||
|
||||
export default {
|
||||
title: 'Delegation/Components/Delegation List Item Actions',
|
||||
component: DelegationActions,
|
||||
} as ComponentMeta<typeof DelegationActions>;
|
||||
|
||||
export const Default = () => <DelegationActions />;
|
||||
|
||||
export const RedeemingDisabled = () => <DelegationActions disableRedeemingRewards />;
|
||||
|
||||
export const PendingDelegation = () => <DelegationActions isPending={{ actionType: 'delegate', blockHeight: 1000 }} />;
|
||||
|
||||
export const PendingUndelegation = () => (
|
||||
<DelegationActions isPending={{ actionType: 'undelegate', blockHeight: 1000 }} />
|
||||
);
|
||||
@@ -0,0 +1,163 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { MoreVertSharp } from '@mui/icons-material';
|
||||
import { DelegationEventKind } from '@nymproject/types';
|
||||
import { Delegate, Undelegate } from '../../svg-icons';
|
||||
import { DelegateListItemPending } from './types';
|
||||
|
||||
export type DelegationListItemActions = 'delegate' | 'undelegate' | 'redeem' | 'compound';
|
||||
|
||||
const BUTTON_SIZE = '32px';
|
||||
const MIN_WIDTH = '150px';
|
||||
|
||||
export const DelegationActions: React.FC<{
|
||||
onActionClick?: (action: DelegationListItemActions) => void;
|
||||
isPending?: DelegateListItemPending;
|
||||
disableRedeemingRewards?: boolean;
|
||||
}> = ({ disableRedeemingRewards, onActionClick, isPending }) => {
|
||||
if (isPending) {
|
||||
return (
|
||||
<Box py={0.5} fontSize="inherit" minWidth={MIN_WIDTH} minHeight={BUTTON_SIZE}>
|
||||
<Tooltip title="There will be a new epoch roughly every hour when your changes will take effect" arrow>
|
||||
<Typography fontSize="inherit" color="text.disabled">
|
||||
Pending {isPending.actionType === 'delegate' ? 'delegation' : 'undelegation'}...
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack spacing={2} direction="row" minWidth={MIN_WIDTH}>
|
||||
<Tooltip title="Delegate more" arrow>
|
||||
<Button
|
||||
onClick={() => (onActionClick ? onActionClick('delegate') : undefined)}
|
||||
variant="contained"
|
||||
disableElevation
|
||||
sx={{ maxWidth: BUTTON_SIZE, minWidth: BUTTON_SIZE, height: BUTTON_SIZE, padding: 0 }}
|
||||
>
|
||||
<Delegate fontSize="small" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Undelegate" arrow>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{ maxWidth: BUTTON_SIZE, minWidth: BUTTON_SIZE, height: BUTTON_SIZE, padding: 0 }}
|
||||
onClick={() => (onActionClick ? onActionClick('undelegate') : undefined)}
|
||||
>
|
||||
<Undelegate fontSize="small" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={disableRedeemingRewards ? 'There are no rewards to redeem' : 'Redeem rewards'} arrow>
|
||||
<span>
|
||||
<Button
|
||||
disabled={disableRedeemingRewards}
|
||||
onClick={() => (onActionClick ? onActionClick('redeem') : undefined)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
sx={{ maxWidth: BUTTON_SIZE, minWidth: BUTTON_SIZE, height: BUTTON_SIZE, padding: 0 }}
|
||||
>
|
||||
R
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const DelegationActionsMenuItem = ({
|
||||
title,
|
||||
description,
|
||||
onClick,
|
||||
Icon,
|
||||
disabled,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
onClick?: () => void;
|
||||
Icon?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
}) => (
|
||||
<MenuItem sx={{ p: 2 }} onClick={onClick} disabled={disabled}>
|
||||
<ListItemIcon sx={{ color: 'black' }}>{Icon}</ListItemIcon>
|
||||
<ListItemText sx={{ color: 'black' }} primary={title} secondary={description} />
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
export const DelegationsActionsMenu: React.FC<{
|
||||
onActionClick?: (action: DelegationListItemActions) => void;
|
||||
isPending?: DelegationEventKind;
|
||||
disableRedeemingRewards?: boolean;
|
||||
disableDelegateMore?: boolean;
|
||||
}> = ({ disableRedeemingRewards, disableDelegateMore, onActionClick, isPending }) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
const handleActionSelect = (action: DelegationListItemActions) => {
|
||||
handleClose();
|
||||
onActionClick?.(action);
|
||||
};
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<Box py={0.5} fontSize="inherit" minWidth={MIN_WIDTH} minHeight={BUTTON_SIZE}>
|
||||
<Tooltip title="There will be a new epoch roughly every hour when your changes will take effect" arrow>
|
||||
<Typography fontSize="inherit" color="text.disabled">
|
||||
Pending {isPending === 'Delegate' ? 'delegation' : 'undelegation'}...
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton onClick={handleClick}>
|
||||
<MoreVertSharp />
|
||||
</IconButton>
|
||||
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
|
||||
<DelegationActionsMenuItem
|
||||
title="Delegate more"
|
||||
Icon={<Delegate />}
|
||||
onClick={() => handleActionSelect?.('delegate')}
|
||||
disabled={disableDelegateMore}
|
||||
/>
|
||||
<DelegationActionsMenuItem
|
||||
title="Undelegate"
|
||||
Icon={<Undelegate />}
|
||||
onClick={() => handleActionSelect?.('undelegate')}
|
||||
disabled={false}
|
||||
/>
|
||||
<DelegationActionsMenuItem
|
||||
title="Redeem"
|
||||
description="Trasfer your rewards to your balance"
|
||||
Icon={<Typography sx={{ pl: 1 }}>R</Typography>}
|
||||
onClick={() => handleActionSelect?.('redeem')}
|
||||
disabled={disableRedeemingRewards}
|
||||
/>
|
||||
<DelegationActionsMenuItem
|
||||
title="Compound"
|
||||
description="Add your rewards to this delegation"
|
||||
Icon={<Typography sx={{ pl: 1 }}>C</Typography>}
|
||||
onClick={() => handleActionSelect?.('compound')}
|
||||
disabled={disableRedeemingRewards}
|
||||
/>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { DelegationWithEverything } from '@nymproject/types';
|
||||
import { DelegationList } from './DelegationList';
|
||||
|
||||
export default {
|
||||
title: 'Delegation/Components/Delegation List',
|
||||
component: DelegationList,
|
||||
} as ComponentMeta<typeof DelegationList>;
|
||||
|
||||
const explorerUrl = 'https://sandbox-explorer.nymtech.net/network-components/mixnodes';
|
||||
|
||||
export const items: DelegationWithEverything[] = [
|
||||
{
|
||||
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
|
||||
accumulated_rewards: { amount: '0.05', denom: 'NYM' },
|
||||
amount: { amount: '10', denom: 'NYM' },
|
||||
profit_margin_percent: 0.1122323949234,
|
||||
owner: '',
|
||||
block_height: BigInt(100),
|
||||
stake_saturation: 0.5,
|
||||
proxy: '',
|
||||
avg_uptime_percent: 0.5,
|
||||
total_delegation: { amount: '0', denom: 'NYM' },
|
||||
pledge_amount: { amount: '0', denom: 'NYM' },
|
||||
pending_events: [],
|
||||
history: [],
|
||||
},
|
||||
{
|
||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
||||
accumulated_rewards: { amount: '0.1', denom: 'NYM' },
|
||||
amount: { amount: '100', denom: 'NYM' },
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
||||
profit_margin_percent: 0.89,
|
||||
owner: '',
|
||||
block_height: BigInt(4000),
|
||||
stake_saturation: 0.5,
|
||||
proxy: '',
|
||||
avg_uptime_percent: 0.1,
|
||||
total_delegation: { amount: '0', denom: 'NYM' },
|
||||
pledge_amount: { amount: '0', denom: 'NYM' },
|
||||
pending_events: [],
|
||||
history: [],
|
||||
},
|
||||
];
|
||||
|
||||
export const WithData = () => <DelegationList items={items} explorerUrl={explorerUrl} />;
|
||||
|
||||
export const Empty = () => <DelegationList items={[]} explorerUrl={explorerUrl} />;
|
||||
|
||||
export const OneItem = () => <DelegationList items={[items[0]]} explorerUrl={explorerUrl} />;
|
||||
|
||||
export const Loading = () => <DelegationList isLoading explorerUrl={explorerUrl} />;
|
||||
@@ -0,0 +1,203 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
Link,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableSortLabel,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import { visuallyHidden } from '@mui/utils';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
import { DelegationWithEverything } from '@nymproject/types';
|
||||
import { format } from 'date-fns';
|
||||
import { DelegationListItemActions, DelegationsActionsMenu } from './DelegationActions';
|
||||
|
||||
type Order = 'asc' | 'desc';
|
||||
|
||||
interface EnhancedTableProps {
|
||||
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof DelegationWithEverything) => void;
|
||||
order: Order;
|
||||
orderBy: string;
|
||||
}
|
||||
|
||||
interface HeadCell {
|
||||
id: keyof DelegationWithEverything;
|
||||
label: string;
|
||||
sortable: boolean;
|
||||
disablePadding?: boolean;
|
||||
align: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
const headCells: HeadCell[] = [
|
||||
{ id: 'node_identity', label: 'Node ID', sortable: true, align: 'left' },
|
||||
{ id: 'delegated_on_iso_datetime', label: 'Delegated on', sortable: true, align: 'center' },
|
||||
{ id: 'amount', label: 'Delegation', sortable: true, align: 'center' },
|
||||
{ id: 'accumulated_rewards', label: 'Reward', sortable: true, align: 'center' },
|
||||
{ id: 'profit_margin_percent', label: 'Profit margin', sortable: true, align: 'center' },
|
||||
{ id: 'stake_saturation', label: 'Stake saturation', sortable: true, align: 'center' },
|
||||
{ id: 'avg_uptime_percent', label: 'Uptime', sortable: true, align: 'center' },
|
||||
];
|
||||
|
||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
if (b[orderBy] < a[orderBy]) {
|
||||
return -1;
|
||||
}
|
||||
if (b[orderBy] > a[orderBy]) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getComparator<Key extends keyof DelegationWithEverything>(
|
||||
order: Order,
|
||||
orderBy: Key,
|
||||
): (a: DelegationWithEverything, b: DelegationWithEverything) => number {
|
||||
return order === 'desc'
|
||||
? (a, b) => descendingComparator(a, b, orderBy)
|
||||
: (a, b) => -descendingComparator(a, b, orderBy);
|
||||
}
|
||||
|
||||
const EnhancedTableHead: React.FC<EnhancedTableProps> = ({ order, orderBy, onRequestSort }) => {
|
||||
const createSortHandler = (property: keyof DelegationWithEverything) => (event: React.MouseEvent<unknown>) => {
|
||||
onRequestSort(event, property);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headCells.map((headCell) => (
|
||||
<TableCell
|
||||
key={headCell.id}
|
||||
align={headCell.align}
|
||||
padding={headCell.disablePadding ? 'none' : 'normal'}
|
||||
sortDirection={orderBy === headCell.id ? order : false}
|
||||
color="secondary"
|
||||
>
|
||||
<TableSortLabel
|
||||
active={orderBy === headCell.id}
|
||||
direction={orderBy === headCell.id ? order : 'asc'}
|
||||
onClick={createSortHandler(headCell.id)}
|
||||
IconComponent={ArrowDropDownIcon}
|
||||
>
|
||||
{headCell.label}
|
||||
{orderBy === headCell.id ? (
|
||||
<Box component="span" sx={visuallyHidden}>
|
||||
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
||||
</Box>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
))}
|
||||
<TableCell />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export const DelegationList: React.FC<{
|
||||
isLoading?: boolean;
|
||||
items?: DelegationWithEverything[];
|
||||
onItemActionClick?: (item: DelegationWithEverything, action: DelegationListItemActions) => void;
|
||||
explorerUrl: string;
|
||||
}> = ({ isLoading, items, onItemActionClick, explorerUrl }) => {
|
||||
const [order, setOrder] = React.useState<Order>('asc');
|
||||
const [orderBy, setOrderBy] = React.useState<keyof DelegationWithEverything>('delegated_on_iso_datetime');
|
||||
|
||||
const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof DelegationWithEverything) => {
|
||||
const isAsc = orderBy === property && order === 'asc';
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(property);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table sx={{ width: '100%' }}>
|
||||
<EnhancedTableHead order={order} orderBy={orderBy} onRequestSort={handleRequestSort} />
|
||||
<TableBody>
|
||||
{items?.length ? (
|
||||
items.sort(getComparator(order, orderBy)).map((item) => (
|
||||
<TableRow key={item.node_identity}>
|
||||
<TableCell>
|
||||
<CopyToClipboard
|
||||
sx={{ fontSize: 16, mr: 1 }}
|
||||
value={item.node_identity}
|
||||
tooltip={
|
||||
<>
|
||||
Copy identity key <strong>{item.node_identity}</strong> to clipboard
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
Click to view <strong>{item.node_identity}</strong> in the Network Explorer
|
||||
</>
|
||||
}
|
||||
placement="right"
|
||||
arrow
|
||||
>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={`${explorerUrl}/network-components/mixnode/${item.node_identity}`}
|
||||
color="inherit"
|
||||
underline="none"
|
||||
>
|
||||
{item.node_identity.slice(0, 6)}...{item.node_identity.slice(-6)}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="center">{format(new Date(item.delegated_on_iso_datetime), 'dd/MM/yyyy')}</TableCell>
|
||||
<TableCell align="center">{`${item.amount.amount} ${item.amount.denom}`}</TableCell>
|
||||
<TableCell align="center">
|
||||
{!item.accumulated_rewards
|
||||
? '-'
|
||||
: `${item.accumulated_rewards.amount} ${item.accumulated_rewards.denom}`}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{!item.profit_margin_percent ? '-' : `${item.profit_margin_percent}%`}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{!item.stake_saturation ? '-' : `${Math.round(item.stake_saturation * 100000) / 1000}%`}
|
||||
</TableCell>
|
||||
<TableCell align="center">{!item.avg_uptime_percent ? '-' : `${item.avg_uptime_percent}%`}</TableCell>
|
||||
<TableCell align="right">
|
||||
{!item.pending_events.length ? (
|
||||
<DelegationsActionsMenu
|
||||
isPending={undefined}
|
||||
onActionClick={(action) => (onItemActionClick ? onItemActionClick(item, action) : undefined)}
|
||||
disableRedeemingRewards={!item.accumulated_rewards || item.accumulated_rewards.amount === '0'}
|
||||
disableDelegateMore={(item?.stake_saturation || 0) > 100}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip
|
||||
title="There will be a new epoch roughly every hour when your changes will take effect"
|
||||
arrow
|
||||
>
|
||||
<Chip label="Pending events" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7}>
|
||||
<Box py={6} textAlign="center">
|
||||
{isLoading ? <CircularProgress /> : <span>You have not delegated to any mixnodes</span>}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Paper } from '@mui/material';
|
||||
import { Delegations } from './Delegations';
|
||||
import { items } from './DelegationList.stories';
|
||||
import { DelegationModal } from './DelegationModal';
|
||||
|
||||
const explorerUrl = 'https://sandbox-explorer.nymtech.net';
|
||||
|
||||
export default {
|
||||
title: 'Delegation/Components/Delegation Modals',
|
||||
component: Delegations,
|
||||
} as ComponentMeta<typeof Delegations>;
|
||||
|
||||
const transactionUrl =
|
||||
'https://sandbox-blocks.nymtech.net/transactions/11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CFB';
|
||||
const balance = '104 NYMT';
|
||||
const recipient = 'nymt1923pujepxfnv8dqyxqrl078s4ysf3xn2p7z2xa';
|
||||
|
||||
const Content: React.FC = () => (
|
||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
||||
<h2>Your Delegations</h2>
|
||||
<Delegations items={items} explorerUrl={explorerUrl} />
|
||||
</Paper>
|
||||
);
|
||||
|
||||
export const Loading = () => (
|
||||
<>
|
||||
<Content />
|
||||
<DelegationModal status="loading" action="delegate" open />
|
||||
</>
|
||||
);
|
||||
|
||||
export const DelegateSuccess = () => (
|
||||
<>
|
||||
<Content />
|
||||
<DelegationModal
|
||||
status="success"
|
||||
action="delegate"
|
||||
message="You delegated 5 NYM"
|
||||
recipient={recipient}
|
||||
balance={balance}
|
||||
transactionUrl={transactionUrl}
|
||||
open
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
export const UndelegateSuccess = () => (
|
||||
<>
|
||||
<Content />
|
||||
<DelegationModal
|
||||
status="success"
|
||||
action="undelegate"
|
||||
message="You undelegated 5 NYM"
|
||||
recipient={recipient}
|
||||
balance={balance}
|
||||
transactionUrl={transactionUrl}
|
||||
open
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
export const RedeemSuccess = () => (
|
||||
<>
|
||||
<Content />
|
||||
<DelegationModal
|
||||
status="success"
|
||||
action="redeem"
|
||||
message="42 NYM"
|
||||
recipient={recipient}
|
||||
balance={balance}
|
||||
transactionUrl={transactionUrl}
|
||||
open
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
export const RedeemAllSuccess = () => (
|
||||
<>
|
||||
<Content />
|
||||
<DelegationModal
|
||||
status="success"
|
||||
action="redeem-all"
|
||||
message="42 NYM"
|
||||
recipient={recipient}
|
||||
balance={balance}
|
||||
transactionUrl={transactionUrl}
|
||||
open
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
export const Error = () => (
|
||||
<>
|
||||
<Content />
|
||||
<DelegationModal
|
||||
status="error"
|
||||
action="redeem-all"
|
||||
message="Minim esse veniam Lorem id velit Lorem eu eu est. Excepteur labore sunt do proident proident sint aliquip consequat Lorem sint non nulla ad excepteur."
|
||||
recipient={recipient}
|
||||
balance={balance}
|
||||
transactionUrl={transactionUrl}
|
||||
open
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, CircularProgress, Link, Modal, Stack, Typography } from '@mui/material';
|
||||
import { modalStyle } from '../Modals/styles';
|
||||
import { TPoolOption } from '../TokenPoolSelector';
|
||||
|
||||
export type ActionType = 'delegate' | 'undelegate' | 'redeem' | 'redeem-all' | 'compound';
|
||||
|
||||
const actionToHeader = (action: ActionType): string => {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (action) {
|
||||
case 'redeem':
|
||||
return 'Rewards redeemed successfully';
|
||||
case 'redeem-all':
|
||||
return 'All rewards redeemed successfully';
|
||||
case 'delegate':
|
||||
return 'Delegation complete';
|
||||
case 'undelegate':
|
||||
return 'Undelegation complete';
|
||||
case 'compound':
|
||||
return 'Undelegation complete';
|
||||
}
|
||||
return 'Oh no! Something went wrong!';
|
||||
};
|
||||
|
||||
export type DelegationModalProps = {
|
||||
status: 'loading' | 'success' | 'error';
|
||||
action: ActionType;
|
||||
message?: string;
|
||||
recipient?: string;
|
||||
balance?: string;
|
||||
transactionUrl?: string;
|
||||
tokenPool?: TPoolOption;
|
||||
};
|
||||
|
||||
export const DelegationModal: React.FC<
|
||||
DelegationModalProps & {
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
> = ({ status, action, message, recipient, balance, transactionUrl, open, onClose, tokenPool, children }) => {
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<Modal open>
|
||||
<Box sx={modalStyle} textAlign="center">
|
||||
<Stack spacing={4} direction="row" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Typography>Please wait...</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'error') {
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Box sx={modalStyle} textAlign="center">
|
||||
<Typography color={(theme) => theme.palette.error.main} mb={1}>
|
||||
Oh no! Something went wrong...
|
||||
</Typography>
|
||||
<Typography my={5}>{message}</Typography>
|
||||
{children}
|
||||
<Button variant="contained" onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Box sx={modalStyle} textAlign="center">
|
||||
<Typography color={(theme) => theme.palette.success.main} mb={1}>
|
||||
{actionToHeader(action)}
|
||||
</Typography>
|
||||
<Typography mb={3}>{message}</Typography>
|
||||
|
||||
{recipient && (
|
||||
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
|
||||
Recipient: {recipient}
|
||||
</Typography>
|
||||
)}
|
||||
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
|
||||
Your current {tokenPool === 'locked' ? 'locked balance' : 'balance'}: {balance}
|
||||
</Typography>
|
||||
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
|
||||
Check the transaction hash{' '}
|
||||
<Link href={transactionUrl} target="_blank">
|
||||
here
|
||||
</Link>
|
||||
</Typography>
|
||||
{children}
|
||||
<Button variant="contained" sx={{ mt: 3 }} size="large" onClick={onClose}>
|
||||
Finish
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Paper } from '@mui/material';
|
||||
import { Delegations } from './Delegations';
|
||||
import { items } from './DelegationList.stories';
|
||||
|
||||
const explorerUrl = 'https://sandbox-explorer.nymtech.net';
|
||||
|
||||
export default {
|
||||
title: 'Delegation/Components/Delegations',
|
||||
component: Delegations,
|
||||
} as ComponentMeta<typeof Delegations>;
|
||||
|
||||
export const Default = () => (
|
||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
||||
<h2>Your Delegations</h2>
|
||||
<Delegations items={items} explorerUrl={explorerUrl} />
|
||||
</Paper>
|
||||
);
|
||||
|
||||
export const Empty = () => (
|
||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
||||
<h2>Your Delegations</h2>
|
||||
<Delegations items={[]} explorerUrl={explorerUrl} />
|
||||
</Paper>
|
||||
);
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Box, Link, Typography } from '@mui/material';
|
||||
import { DelegationWithEverything } from '@nymproject/types';
|
||||
import { DelegationList } from './DelegationList';
|
||||
import { DelegationListItemActions } from './DelegationActions';
|
||||
|
||||
export const Delegations: React.FC<{
|
||||
isLoading?: boolean;
|
||||
items?: DelegationWithEverything[];
|
||||
explorerUrl: string;
|
||||
onDelegationItemActionClick?: (item: DelegationWithEverything, action: DelegationListItemActions) => void;
|
||||
}> = ({ isLoading, items, explorerUrl, onDelegationItemActionClick }) => (
|
||||
<>
|
||||
<DelegationList
|
||||
isLoading={isLoading}
|
||||
items={items}
|
||||
explorerUrl={explorerUrl}
|
||||
onItemActionClick={onDelegationItemActionClick}
|
||||
/>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Link
|
||||
href={`${explorerUrl}/network-components/mixnodes/`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
underline="hover"
|
||||
color={(theme) => theme.palette.text.primary}
|
||||
>
|
||||
Check the{' '}
|
||||
<Typography color="primary.main" component="span">
|
||||
list of mixnodes
|
||||
</Typography>{' '}
|
||||
for uptime and performance to make delegation decisions
|
||||
</Link>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { ModalDivider } from '../Modals/ModalDivider';
|
||||
|
||||
export const ModalListItem: React.FC<{
|
||||
label: string;
|
||||
divider?: boolean;
|
||||
hidden?: boolean;
|
||||
value: React.ReactNode;
|
||||
}> = ({ label, value, hidden, divider }) => (
|
||||
<Box sx={{ display: hidden ? 'none' : 'block' }}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontSize="smaller">{label}:</Typography>
|
||||
<Typography fontSize="smaller">{value}</Typography>
|
||||
</Stack>
|
||||
{divider && <ModalDivider />}
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,129 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Button, Paper } from '@mui/material';
|
||||
import { DelegateModal } from './DelegateModal';
|
||||
import { UndelegateModal } from './UndelegateModal';
|
||||
|
||||
export default {
|
||||
title: 'Delegation/Components/Action Modals',
|
||||
};
|
||||
|
||||
const Background: React.FC<{ onOpen: () => void }> = ({ onOpen }) => (
|
||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
||||
<h2>Lorem ipsum</h2>
|
||||
<Button variant="contained" onClick={onOpen}>
|
||||
Show modal
|
||||
</Button>
|
||||
<p>
|
||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis sunt
|
||||
velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur culpa
|
||||
exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
||||
</p>
|
||||
<p>
|
||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip ad
|
||||
tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris tempor
|
||||
culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna elit ut
|
||||
mollit.
|
||||
</p>
|
||||
<p>
|
||||
Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum cupidatat
|
||||
duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua amet commodo
|
||||
est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate exercitation in
|
||||
duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt cillum ea aliquip
|
||||
consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet tempor quis excepteur
|
||||
minim culpa velit Lorem enim ad.
|
||||
</p>
|
||||
<p>
|
||||
Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et anim
|
||||
Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod aute aute
|
||||
irure anim dolore ipsum et veniam.
|
||||
</p>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
export const Delegate = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Background onOpen={() => setOpen(true)} />
|
||||
<DelegateModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="NYM"
|
||||
feeOverride="0.004375"
|
||||
estimatedReward={50.423}
|
||||
accountBalance="425.2345053"
|
||||
nodeUptimePercentage={99.28394}
|
||||
profitMarginPercentage={11.12334234}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DelegateBelowMinimum = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Background onOpen={() => setOpen(true)} />
|
||||
<DelegateModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="NYM"
|
||||
feeOverride="0.004375"
|
||||
estimatedReward={425.2345053}
|
||||
nodeUptimePercentage={99.28394}
|
||||
profitMarginPercentage={11.12334234}
|
||||
rewardInterval="weekly"
|
||||
initialAmount="0.1"
|
||||
hasVestingContract={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DelegateMore = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Background onOpen={() => setOpen(true)} />
|
||||
<DelegateModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
header="Delegate more"
|
||||
buttonText="Delegate more"
|
||||
currency="NYM"
|
||||
feeOverride="0.004375"
|
||||
estimatedReward={50.423}
|
||||
accountBalance="425.2345053"
|
||||
nodeUptimePercentage={99.28394}
|
||||
profitMarginPercentage={11.12334234}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Undelegate = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Background onOpen={() => setOpen(true)} />
|
||||
<UndelegateModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
currency="NYM"
|
||||
fee={0.004375}
|
||||
amount={150}
|
||||
identityKey="AA6RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujyxx"
|
||||
proxy={null}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { FC } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableSortLabel,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
import { DelegationEvent } from '@nymproject/types';
|
||||
import { ArrowDropDown } from '@mui/icons-material';
|
||||
import { visuallyHidden } from '@mui/utils';
|
||||
|
||||
type Order = 'asc' | 'desc';
|
||||
|
||||
interface HeadCell {
|
||||
id: keyof DelegationEvent;
|
||||
label: string;
|
||||
sortable: boolean;
|
||||
disablePadding?: boolean;
|
||||
}
|
||||
|
||||
interface EnhancedTableProps {
|
||||
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof DelegationEvent) => void;
|
||||
order: Order;
|
||||
orderBy: string;
|
||||
}
|
||||
|
||||
const headCells: HeadCell[] = [
|
||||
{ id: 'node_identity', label: 'Node ID', sortable: true },
|
||||
{ id: 'amount', label: 'Delegation', sortable: true },
|
||||
{ id: 'kind', label: 'Type', sortable: true },
|
||||
];
|
||||
|
||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
if (b[orderBy] < a[orderBy]) {
|
||||
return -1;
|
||||
}
|
||||
if (b[orderBy] > a[orderBy]) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getComparator<Key extends keyof DelegationEvent>(
|
||||
order: Order,
|
||||
orderBy: Key,
|
||||
): (a: DelegationEvent, b: DelegationEvent) => number {
|
||||
return order === 'desc'
|
||||
? (a, b) => descendingComparator(a, b, orderBy)
|
||||
: (a, b) => -descendingComparator(a, b, orderBy);
|
||||
}
|
||||
|
||||
const EnhancedTableHead: React.FC<EnhancedTableProps> = ({ order, orderBy, onRequestSort }) => {
|
||||
const createSortHandler = (property: keyof DelegationEvent) => (event: React.MouseEvent<unknown>) => {
|
||||
onRequestSort(event, property);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headCells.map((headCell) => (
|
||||
<TableCell
|
||||
key={headCell.id}
|
||||
align="left"
|
||||
padding={headCell.disablePadding ? 'none' : 'normal'}
|
||||
sortDirection={orderBy === headCell.id ? order : false}
|
||||
color="secondary"
|
||||
>
|
||||
<TableSortLabel
|
||||
active={orderBy === headCell.id}
|
||||
direction={orderBy === headCell.id ? order : 'asc'}
|
||||
onClick={createSortHandler(headCell.id)}
|
||||
IconComponent={ArrowDropDown}
|
||||
>
|
||||
{headCell.label}
|
||||
{orderBy === headCell.id ? (
|
||||
<Box component="span" sx={visuallyHidden}>
|
||||
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
||||
</Box>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
|
||||
export const PendingEvents: FC<{ pendingEvents: DelegationEvent[]; explorerUrl: string }> = ({
|
||||
pendingEvents,
|
||||
explorerUrl,
|
||||
}) => {
|
||||
const [order, setOrder] = React.useState<Order>('asc');
|
||||
const [orderBy, setOrderBy] = React.useState<keyof DelegationEvent>('node_identity');
|
||||
|
||||
const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof DelegationEvent) => {
|
||||
const isAsc = orderBy === property && order === 'asc';
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(property);
|
||||
};
|
||||
|
||||
if (pendingEvents.length === 0) return <Typography>No pending events</Typography>;
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table sx={{ width: '100%' }}>
|
||||
<EnhancedTableHead order={order} orderBy={orderBy} onRequestSort={handleRequestSort} />
|
||||
<TableBody>
|
||||
{pendingEvents.sort(getComparator(order, orderBy)).map((item, index) => (
|
||||
<TableRow key={item.node_identity + index}>
|
||||
<TableCell>
|
||||
<CopyToClipboard
|
||||
sx={{ fontSize: 16, mr: 1 }}
|
||||
value={item.node_identity}
|
||||
tooltip={
|
||||
<>
|
||||
Copy identity key <strong>{item.node_identity}</strong> to clipboard
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
Click to view <strong>{item.node_identity}</strong> in the Network Explorer
|
||||
</>
|
||||
}
|
||||
placement="right"
|
||||
arrow
|
||||
>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={`${explorerUrl}/network-components/mixnode/${item.node_identity}`}
|
||||
color="inherit"
|
||||
underline="none"
|
||||
>
|
||||
{item.node_identity.slice(0, 6)}...{item.node_identity.slice(-6)}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>{!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom}`}</TableCell>
|
||||
<TableCell>{item.kind === 'Delegate' ? 'Delegation' : 'Undelegation'}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const UndelegateModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string, proxy: string | null) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
currency: string;
|
||||
proxy: string | null;
|
||||
}> = ({ identityKey, open, onClose, onOk, amount, fee, currency, proxy }) => {
|
||||
const handleOk = () => {
|
||||
if (onOk) {
|
||||
onOk(identityKey, proxy);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={handleOk}
|
||||
header="Undelegate"
|
||||
subHeader="Undelegate from mixnode"
|
||||
okLabel="Undelegate stake"
|
||||
>
|
||||
<IdentityKeyFormField
|
||||
readOnly
|
||||
fullWidth
|
||||
placeholder="Node identity key"
|
||||
initialValue={identityKey}
|
||||
showTickOnValid={false}
|
||||
/>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between" my={3}>
|
||||
<Typography fontWeight={600}>Delegation amount:</Typography>
|
||||
<Typography fontWeight={600}>
|
||||
{amount} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Typography mb={5} fontSize="smaller">
|
||||
Tokens will be transferred to account you are logged in with now
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface DelegateListItem {
|
||||
/** Node identity key */
|
||||
id: string;
|
||||
/** Date of delegation */
|
||||
delegationDate: Date;
|
||||
/** Delegated amount as a string including the currency, e.g. 1.05 NYM */
|
||||
amount: string; // TODO: fix up
|
||||
/** Reward amount as a string, e.g. 1.05 NYM on mainnet */
|
||||
reward?: string;
|
||||
/** A number between 0 and 1 */
|
||||
profitMarginPercentage?: number;
|
||||
/** A number between 0 and 1 */
|
||||
uptimePercentage?: number;
|
||||
/** Is pending */
|
||||
isPending?: DelegateListItemPending;
|
||||
}
|
||||
|
||||
export interface DelegateListItemPending {
|
||||
/** Either the user is delegating or undelegating */
|
||||
actionType: 'delegate' | 'undelegate';
|
||||
/** Pending transaction */
|
||||
blockHeight: number;
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { Operation } from '../types';
|
||||
import { Operation } from '@nymproject/types';
|
||||
import { getGasFee } from '../requests';
|
||||
import { AppContext } from '../context/main';
|
||||
|
||||
export const Fee = ({ feeType }: { feeType: Operation }) => {
|
||||
const [fee, setFee] = useState<string>();
|
||||
const { currency } = useContext(AppContext);
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const getFee = async () => {
|
||||
const res = await getGasFee(feeType);
|
||||
@@ -20,7 +20,7 @@ export const Fee = ({ feeType }: { feeType: Operation }) => {
|
||||
if (fee) {
|
||||
return (
|
||||
<Typography sx={{ color: 'nym.fee', fontWeight: 600 }}>
|
||||
Estimated fee for this transaction: {`${fee} ${currency?.major}`}{' '}
|
||||
Est.fee for this transaction: {`${fee} ${clientDetails?.denom}`}{' '}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box, LinearProgress, Stack } from '@mui/material';
|
||||
import { NymWordmark } from '@nymproject/react';
|
||||
import { NymWordmark } from '@nymproject/react/logo/NymWordmark';
|
||||
import { AuthTheme } from 'src/theme';
|
||||
|
||||
export const LoadingPage = () => (
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box, SxProps } from '@mui/material';
|
||||
|
||||
export const ModalDivider: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => <Box borderTop="1px solid" borderColor="rgba(141, 147, 153, 0.2)" my={1} sx={sx} />;
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Button, Paper } from '@mui/material';
|
||||
import { SimpleModal } from './SimpleModal';
|
||||
import { ModalDivider } from './ModalDivider';
|
||||
|
||||
export default {
|
||||
title: 'Modals/Simple Modal',
|
||||
component: SimpleModal,
|
||||
} as ComponentMeta<typeof SimpleModal>;
|
||||
|
||||
export const Default = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
||||
<h2>Lorem ipsum</h2>
|
||||
<Button variant="contained" onClick={() => setOpen(true)}>
|
||||
Show modal
|
||||
</Button>
|
||||
<p>
|
||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis
|
||||
sunt velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur
|
||||
culpa exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
||||
</p>
|
||||
<p>
|
||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip
|
||||
ad tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris
|
||||
tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
||||
elit ut mollit.
|
||||
</p>
|
||||
<p>
|
||||
Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum
|
||||
cupidatat duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua
|
||||
amet commodo est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate
|
||||
exercitation in duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt
|
||||
cillum ea aliquip consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet
|
||||
tempor quis excepteur minim culpa velit Lorem enim ad.
|
||||
</p>
|
||||
<p>
|
||||
Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et
|
||||
anim Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod
|
||||
aute aute irure anim dolore ipsum et veniam.
|
||||
</p>
|
||||
</Paper>
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
header="This is a modal"
|
||||
subHeader="This is a sub header"
|
||||
okLabel="Click to continue"
|
||||
>
|
||||
<p>Lorem mollit minim duis cupidatat non. Consectetur sit deserunt</p>
|
||||
<p>
|
||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
||||
</p>
|
||||
<ModalDivider />
|
||||
<p>Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eius</p>
|
||||
<p>
|
||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
||||
elit ut mollit.
|
||||
</p>
|
||||
</SimpleModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const NoSubheader = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
||||
<h2>Lorem ipsum</h2>
|
||||
<Button variant="contained" onClick={() => setOpen(true)}>
|
||||
Show modal
|
||||
</Button>
|
||||
<p>
|
||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis
|
||||
sunt velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur
|
||||
culpa exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
||||
</p>
|
||||
<p>
|
||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip
|
||||
ad tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris
|
||||
tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
||||
elit ut mollit.
|
||||
</p>
|
||||
<p>
|
||||
Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum
|
||||
cupidatat duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua
|
||||
amet commodo est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate
|
||||
exercitation in duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt
|
||||
cillum ea aliquip consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet
|
||||
tempor quis excepteur minim culpa velit Lorem enim ad.
|
||||
</p>
|
||||
<p>
|
||||
Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et
|
||||
anim Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod
|
||||
aute aute irure anim dolore ipsum et veniam.
|
||||
</p>
|
||||
</Paper>
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
header="This is a modal"
|
||||
okLabel="Kaplow!"
|
||||
>
|
||||
<p>
|
||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
||||
elit ut mollit.
|
||||
</p>
|
||||
<ModalDivider />
|
||||
<p>
|
||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
||||
</p>
|
||||
</SimpleModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Modal, Stack, SxProps, Typography } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { modalStyle } from './styles';
|
||||
|
||||
export const SimpleModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: () => void;
|
||||
header: string;
|
||||
subHeader?: string;
|
||||
okLabel: string;
|
||||
okDisabled?: boolean;
|
||||
sx?: SxProps;
|
||||
}> = ({ open, onClose, okDisabled, onOk, header, subHeader, okLabel, sx, children }) => (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Box sx={{ ...modalStyle, ...sx }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography fontSize={22} fontWeight={600}>
|
||||
{header}
|
||||
</Typography>
|
||||
<CloseIcon onClick={onClose} cursor="pointer" />
|
||||
</Stack>
|
||||
{subHeader && (
|
||||
<Typography mt={0.5} mb={3} fontSize="small" color={(theme) => theme.palette.text.secondary}>
|
||||
{subHeader}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{children}
|
||||
|
||||
<Button variant="contained" fullWidth sx={{ mt: 3 }} size="large" onClick={onOk} disabled={okDisabled}>
|
||||
{okLabel}
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
export const modalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 500,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
borderRadius: '16px',
|
||||
p: 4,
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material';
|
||||
import { AppContext } from '../context/main';
|
||||
import { Bond, Delegate, Unbond, Undelegate } from '../svg-icons';
|
||||
import { Bond, Delegate, Unbond } from '../svg-icons';
|
||||
|
||||
const routesSchema = [
|
||||
{
|
||||
@@ -32,30 +32,27 @@ const routesSchema = [
|
||||
Icon: Unbond,
|
||||
},
|
||||
{
|
||||
label: 'Delegate',
|
||||
route: '/delegate',
|
||||
label: 'Delegation',
|
||||
route: '/delegation',
|
||||
Icon: Delegate,
|
||||
},
|
||||
{
|
||||
label: 'Undelegate',
|
||||
route: '/undelegate',
|
||||
Icon: Undelegate,
|
||||
label: 'Docs',
|
||||
route: '/docs',
|
||||
Icon: Description,
|
||||
mode: 'dev',
|
||||
},
|
||||
{
|
||||
label: 'Admin',
|
||||
route: '/admin',
|
||||
Icon: Settings,
|
||||
mode: 'admin',
|
||||
},
|
||||
];
|
||||
|
||||
export const Nav = () => {
|
||||
const { isAdminAddress, handleShowAdmin } = useContext(AppContext);
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isAdminAddress) {
|
||||
routesSchema.push({
|
||||
label: 'Docs',
|
||||
route: '/docs',
|
||||
Icon: Description,
|
||||
});
|
||||
}
|
||||
}, [isAdminAddress]);
|
||||
const { isAdminAddress } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -66,36 +63,38 @@ export const Nav = () => {
|
||||
}}
|
||||
>
|
||||
<List disablePadding>
|
||||
{routesSchema.map(({ Icon, route, label }) => (
|
||||
<ListItem disableGutters component={Link} to={route} key={label}>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 30,
|
||||
color: location.pathname === route ? 'primary.main' : 'common.white',
|
||||
}}
|
||||
>
|
||||
<Icon sx={{ fontSize: 20 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{
|
||||
color: location.pathname === route ? 'primary.main' : 'common.white',
|
||||
}}
|
||||
primary={label}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
{isAdminAddress && (
|
||||
<ListItem disableGutters onClick={handleShowAdmin}>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 30,
|
||||
}}
|
||||
>
|
||||
<Settings sx={{ fontSize: 20, color: 'white' }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Admin" sx={{ color: 'common.white' }} />
|
||||
</ListItem>
|
||||
)}
|
||||
{routesSchema
|
||||
.filter(({ mode }) => {
|
||||
if (!mode) {
|
||||
return true;
|
||||
}
|
||||
switch (mode) {
|
||||
case 'admin':
|
||||
return isAdminAddress;
|
||||
case 'dev':
|
||||
return isAdminAddress;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map(({ Icon, route, label }) => (
|
||||
<ListItem disableGutters component={Link} to={route} key={label}>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 30,
|
||||
color: location.pathname === route ? 'primary.main' : 'common.white',
|
||||
}}
|
||||
>
|
||||
<Icon sx={{ fontSize: 20 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{
|
||||
color: location.pathname === route ? 'primary.main' : 'common.white',
|
||||
}}
|
||||
primary={label}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { Button, List, ListItem, ListItemIcon, ListItemText, ListSubheader, Popover } from '@mui/material';
|
||||
import { ArrowDropDown, CheckSharp } from '@mui/icons-material';
|
||||
import { Network } from 'src/types';
|
||||
import { AppContext } from '../context/main';
|
||||
import { config } from '../../config';
|
||||
import { Network } from '../types';
|
||||
import { config } from '../config';
|
||||
|
||||
const networks: { networkName: Network; name: string }[] = [
|
||||
{ networkName: 'MAINNET', name: 'Nym Mainnet' },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { CircleOutlined, PauseCircleOutlined, CheckCircleOutline } from '@mui/icons-material';
|
||||
import { MixnodeStatus } from '../types';
|
||||
import { MixnodeStatus } from '@nymproject/types';
|
||||
|
||||
const Active = () => (
|
||||
<Typography sx={{ color: 'success.main', display: 'flex', alignItems: 'center' }}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material';
|
||||
import { EnumNodeType } from '../types/global';
|
||||
import { EnumNodeType } from '@nymproject/types';
|
||||
|
||||
export const NodeTypeSelector = ({
|
||||
disabled,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { NymLogo as NymLogoReact } from '@nymproject/react';
|
||||
import { NymLogo as NymLogoReact } from '@nymproject/react/logo/NymLogo';
|
||||
|
||||
const imgSize = {
|
||||
small: 40,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const CompoundModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, fee, currency, message }) => {
|
||||
const handleOk = () => {
|
||||
if (onOk) {
|
||||
onOk(identityKey);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={handleOk}
|
||||
header={message}
|
||||
subHeader="Compound rewards from delegations"
|
||||
okLabel="Compound rewards"
|
||||
>
|
||||
{identityKey && <IdentityKeyFormField readOnly fullWidth initialValue={identityKey} showTickOnValid={false} />}
|
||||
|
||||
<Stack direction="row" justifyContent="space-between" mb={4} mt={identityKey && 4}>
|
||||
<Typography>Rewards amount:</Typography>
|
||||
<Typography>
|
||||
{amount} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Typography mb={5} fontSize="smaller">
|
||||
Rewards will be transferred to account you are logged in with now
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{amount < fee && (
|
||||
<Alert color="warning" sx={{ mt: 3 }} icon={<WarningIcon />}>
|
||||
<AlertTitle>Warning: fees are greater than the reward</AlertTitle>
|
||||
The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue?
|
||||
</Alert>
|
||||
)}
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Button, Paper } from '@mui/material';
|
||||
import { RedeemModal } from './RedeemModal';
|
||||
|
||||
export default {
|
||||
title: 'Rewards/Components/Redeem Modals',
|
||||
component: RedeemModal,
|
||||
} as ComponentMeta<typeof RedeemModal>;
|
||||
|
||||
const Content: React.FC<{
|
||||
setOpen: (value: boolean) => void;
|
||||
}> = ({ setOpen }) => (
|
||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
||||
<h2>Lorem ipsum</h2>
|
||||
<Button variant="contained" onClick={() => setOpen(true)}>
|
||||
Show modal
|
||||
</Button>
|
||||
<p>
|
||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis sunt
|
||||
velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur culpa
|
||||
exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
||||
</p>
|
||||
<p>
|
||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip ad
|
||||
tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris tempor
|
||||
culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna elit ut
|
||||
mollit.
|
||||
</p>
|
||||
<p>
|
||||
Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum cupidatat
|
||||
duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua amet commodo
|
||||
est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate exercitation in
|
||||
duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt cillum ea aliquip
|
||||
consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet tempor quis excepteur
|
||||
minim culpa velit Lorem enim ad.
|
||||
</p>
|
||||
<p>
|
||||
Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et anim
|
||||
Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod aute aute
|
||||
irure anim dolore ipsum et veniam.
|
||||
</p>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
export const RedeemAllRewards = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Content setOpen={setOpen} />
|
||||
<RedeemModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
message="Redeem all rewards"
|
||||
currency="NYM"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
fee={0.004375}
|
||||
amount={425.65843}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const RedeemRewardForMixnode = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Content setOpen={setOpen} />
|
||||
<RedeemModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
message="Redeem rewards"
|
||||
currency="NYM"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
fee={0.004375}
|
||||
amount={425.65843}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FeeIsMoreThanAllRewards = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Content setOpen={setOpen} />
|
||||
<RedeemModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
message="Redeem all rewards"
|
||||
currency="NYM"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
fee={0.004375}
|
||||
amount={0.001}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FeeIsMoreThanMixnodeReward = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
return (
|
||||
<>
|
||||
<Content setOpen={setOpen} />
|
||||
<RedeemModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
message="Redeem rewards"
|
||||
currency="NYM"
|
||||
fee={0.004375}
|
||||
amount={0.001}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const RedeemModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, fee, currency, message }) => {
|
||||
const handleOk = () => {
|
||||
if (onOk) {
|
||||
onOk(identityKey);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={handleOk}
|
||||
header={message}
|
||||
subHeader="Rewards from delegations"
|
||||
okLabel="Redeem rewards"
|
||||
>
|
||||
{identityKey && <IdentityKeyFormField readOnly fullWidth initialValue={identityKey} showTickOnValid={false} />}
|
||||
|
||||
<Stack direction="row" justifyContent="space-between" mb={4} mt={identityKey && 4}>
|
||||
<Typography>Rewards amount:</Typography>
|
||||
<Typography>
|
||||
{amount} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Typography mb={5} fontSize="smaller">
|
||||
Rewards will be transferred to account you are logged in with now
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{amount < fee && (
|
||||
<Alert color="warning" sx={{ mt: 3 }} icon={<WarningIcon />}>
|
||||
<AlertTitle>Warning: fees are greater than the reward</AlertTitle>
|
||||
The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue?
|
||||
</Alert>
|
||||
)}
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Paper } from '@mui/material';
|
||||
import { RewardsSummary } from './RewardsSummary';
|
||||
|
||||
export default {
|
||||
title: 'Rewards/Components/Rewards Summary',
|
||||
component: RewardsSummary,
|
||||
} as ComponentMeta<typeof RewardsSummary>;
|
||||
|
||||
export const Default = () => (
|
||||
<Paper elevation={0} sx={{ px: 4, py: 2 }}>
|
||||
<RewardsSummary totalDelegation="860.123 NYM" totalRewards="4.86723 NYM" />
|
||||
</Paper>
|
||||
);
|
||||
|
||||
export const Empty = () => (
|
||||
<Paper elevation={0} sx={{ px: 4, py: 2 }}>
|
||||
<RewardsSummary />
|
||||
</Paper>
|
||||
);
|
||||
|
||||
export const Loading = () => (
|
||||
<Paper elevation={0} sx={{ px: 4, py: 2 }}>
|
||||
<RewardsSummary isLoading />
|
||||
</Paper>
|
||||
);
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { Button, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
export const RewardsSummary: React.FC<{
|
||||
isLoading?: boolean;
|
||||
totalDelegation?: string;
|
||||
totalRewards?: string;
|
||||
onClickRedeemAll?: () => void;
|
||||
}> = ({ isLoading, totalDelegation, totalRewards, onClickRedeemAll }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Stack direction="row" spacing={4}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Typography>Total delegations:</Typography>
|
||||
<Typography fontWeight={600}>
|
||||
{isLoading ? <CircularProgress size={theme.typography.fontSize} /> : totalDelegation || '-'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Typography>New rewards:</Typography>
|
||||
<Typography fontWeight={600}>
|
||||
{isLoading ? <CircularProgress size={theme.typography.fontSize} /> : totalRewards || '-'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Tooltip title="Redeeming all rewards at once will be cheaper" arrow placement="left">
|
||||
<span>
|
||||
{/* <Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={onClickRedeemAll}
|
||||
disabled={!totalRewards || isLoading}
|
||||
>
|
||||
Redeem all rewards
|
||||
</Button> */}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user