Compare commits

...

78 Commits

Author SHA1 Message Date
Mark Sinclair 52ced9d3ad Generate webpack favicons from PNG
https://github.com/jantimon/favicons-webpack-plugin/issues/288 indicates that generating from an SVG sometimes freezes at 92%. This behaviour is seen on Windows wallet builds in CI.
2022-03-23 19:46:50 +00:00
durch 5eb2249ad5 Regenerate mixnet json schema 2022-03-23 20:40:24 +01:00
durch 008fc045b7 drop foreign key constraints 2022-03-23 20:35:09 +01:00
Mark Sinclair 38cc3b9e38 Add webpack thread loader to speed up wallet build time 2022-03-23 19:24:55 +00:00
Mark Sinclair 989285e009 Update nym-wallet-publish-windows10.yml 2022-03-23 18:29:24 +00:00
Mark Sinclair c6e438600f Update nym-wallet-publish-windows10.yml 2022-03-23 18:24:38 +00:00
Mark Sinclair 1dab01ca33 Upgrade GitHub Actions checkout 2022-03-23 18:10:14 +00:00
Mark Sinclair 53e0432934 Build dependencies before building nym-wallet 2022-03-23 17:27:06 +00:00
Mark Sinclair a1f2d33397 Enabled GitHub Action to build Windows release for nym wallet 2022-03-23 16:42:49 +00:00
Mark Sinclair 49aa79b5df Update nym-wallet-publish-windows10.yml 2022-03-23 16:40:04 +00:00
Mark Sinclair 6f3b5fad15 Update nym-wallet-publish-windows10.yml 2022-03-23 16:37:38 +00:00
Mark Sinclair 0efa909374 Configure nym-wallet Windows signing 2022-03-23 15:51:36 +00:00
Fouad 43b102782e Merge pull request #1158 from nymtech/feature/vesting-full
Feature/vesting full
2022-03-23 15:45:11 +00:00
Mark Sinclair d6e6bab6a6 Update nym-wallet-publish-windows10.yml 2022-03-23 15:43:13 +00:00
Mark Sinclair 1a3cdc3128 Update nym-wallet-publish-windows10.yml 2022-03-23 15:36:55 +00:00
Mark Sinclair 39f75907e5 Update nym-wallet-publish-windows10.yml 2022-03-23 15:35:41 +00:00
Mark Sinclair 029c2698d7 Update nym-wallet-publish-windows10.yml 2022-03-23 15:34:12 +00:00
Mark Sinclair 60548770ba Update nym-wallet-publish-windows10.yml 2022-03-23 15:19:14 +00:00
Mark Sinclair 39cfc2057b Update nym-wallet-publish-windows10.yml 2022-03-23 15:14:58 +00:00
Mark Sinclair 4fbae2d5bd Create GitHub Action to publish and sign Windows wallet 2022-03-23 15:12:07 +00:00
Jon Häggblad 9feee4c174 Fix some minor compiler and clippy warnings 2022-03-23 15:19:00 +01:00
fmtabbara bca6b45273 Merge branch 'develop' into feature/vesting-full 2022-03-23 12:43:31 +00:00
fmtabbara d9733a97ae use 'estimated' fee 2022-03-23 12:42:56 +00:00
fmtabbara e66dc01966 highlight pending delegation time 2022-03-23 12:00:13 +00:00
fmtabbara 2b4aca0194 prevent duplicates 2022-03-23 09:44:17 +00:00
fmtabbara 06aa09afcd UI bug fix 2022-03-22 15:44:39 +00:00
durch ddc09bbb2e Additive rewarding test stub 2022-03-22 12:09:41 +01:00
fmtabbara 3003be5e68 use end epoch for undelegation completion time 2022-03-22 10:54:31 +00:00
fmtabbara cb2ce87ab7 Merge branch 'develop' into feature/vesting-full 2022-03-22 10:24:50 +00:00
fmtabbara 2ad65fc455 display pending undelegations 2022-03-22 10:24:14 +00:00
Tommy Verrall cb292b38c0 Merge pull request #1156 from nymtech/wallet_get_current_epoch
get_current_epoch tauri
2022-03-22 10:17:26 +00:00
durch b710f796f9 get_current_epoch tauri 2022-03-22 11:10:41 +01:00
durch fb5a9a7a3e Fix opeartion order 2022-03-21 20:08:57 +01:00
Drazen Urch 071720c1cc Cleanup (#1155) 2022-03-21 18:24:40 +01:00
Drazen Urch 6679944689 Feature flag reward payments (#1154) 2022-03-21 17:30:08 +01:00
fmtabbara bec7814756 Merge branch 'develop' into feature/vesting-full 2022-03-21 11:24:07 +00:00
Drazen Urch 592b3b3bb1 Pending endpoints (#1150)
* Add QueryPendingDelegationEvents QueryMsg

* Add pending_delegation_events to nymd client

* Add pending delegation events to wallet

* Get rid of double epoch accounting

* Fix reward saving

* Try batching operations to reduce fees

* Bundle all transactions into one

* make nice
2022-03-21 10:07:51 +01:00
Jon Häggblad c2ff786f41 validator-api: fix clippy::needless-question-mark 2022-03-21 09:14:41 +01:00
fmtabbara 07b6114adf only create mnemonic on account creation 2022-03-17 18:00:58 +00:00
fmtabbara 07155b4fe5 dont disable token pool selector for delegations 2022-03-17 17:14:41 +00:00
fmtabbara 8b635a44b8 fix conflicts 2022-03-17 16:53:29 +00:00
fmtabbara 568862a571 fix conflicts + errors 2022-03-17 16:40:49 +00:00
fmtabbara 7868725fed revert console output 2022-03-14 14:18:02 +00:00
fmtabbara d30667d8e4 add error log 2022-03-14 13:04:57 +00:00
fmtabbara 3483a9e7f9 fix lint warnings 2022-03-14 11:00:47 +00:00
fmtabbara 893b28c51c fix eslint warning - func-names 2022-03-14 09:49:17 +00:00
fmtabbara 47ffdfe496 linting, conflict fixes, and general tidy 2022-03-13 01:20:46 +00:00
fmtabbara 51bced8766 merge develop 2022-03-12 22:17:33 +00:00
fmtabbara d4286aeb76 fix validation 2022-03-12 21:12:51 +00:00
fmtabbara ce93e5f677 bug fixes and minor ui updates 2022-03-10 18:59:45 +00:00
fmtabbara 4b691d19a0 Merge branch 'feature/vesting-delegations' into feature/vesting-token-pool-selector 2022-03-10 15:37:36 +00:00
fmtabbara 0e86761a13 Merge branch 'develop' into feature/vesting-delegations 2022-03-09 15:01:54 +00:00
fmtabbara 55463da0da merge develop 2022-03-09 12:23:26 +00:00
fmtabbara 518daeaf20 allow undelegation of unvested tokens 2022-03-09 12:16:20 +00:00
fmtabbara f6ec12db94 allow delegation of vested tokens 2022-03-09 12:15:29 +00:00
fmtabbara 1e921186ad merge develop 2022-03-09 10:06:01 +00:00
fmtabbara d653ecde1e remove unused import 2022-03-07 15:35:40 +00:00
fmtabbara 5b9b4743dc check locked funds when bonding 2022-03-07 12:20:54 +00:00
fmtabbara 5f9b384efa remove client address from send form 2022-03-07 10:38:57 +00:00
fmtabbara d69526fad9 Merge branch 'feature/vesting-token-pool-selector' of https://github.com/nymtech/nym into feature/vesting-token-pool-selector 2022-03-07 10:24:24 +00:00
fmtabbara e10a4e8fca fix fee position on the bond form 2022-03-07 10:24:08 +00:00
fmtabbara 9016878868 remove sig 2022-03-07 10:20:13 +00:00
fmtabbara 1ca6e4a002 [ci skip] Generate TS types 2022-03-04 13:36:49 +00:00
fmtabbara f9c73183db use new vesting requests and types 2022-03-04 13:35:01 +00:00
fmtabbara d296100ffa allow percent profit update on locked tokens mixnode 2022-03-04 13:34:15 +00:00
fmtabbara 06669fe114 don't show profile if in settings if no mixnode details 2022-03-04 13:33:00 +00:00
fmtabbara 8022fc7e31 get bond details when settings is opened or settings tab changes 2022-03-04 13:32:25 +00:00
fmtabbara 2b915fd0aa show locked balance if using locked pool 2022-03-04 13:31:40 +00:00
fmtabbara 4e45c54d17 allow unbonding for locked and balance tokens from bond screen 2022-03-04 13:31:17 +00:00
fmtabbara 0113141efd use token pool selector in bond form 2022-03-04 13:06:09 +00:00
fmtabbara 614d096929 handle clear vesting account info 2022-03-04 13:01:42 +00:00
fmtabbara fe7df3a909 get vesting pledge info for mixnode 2022-03-04 13:01:05 +00:00
fmtabbara b14f6ab159 get vesting pledge info for mixnode 2022-03-04 13:00:50 +00:00
fmtabbara e6040b0ecc refacor log in flow and add notifications 2022-03-04 12:59:30 +00:00
fmtabbara 60e6d23fd8 create token pool selector 2022-03-04 12:59:01 +00:00
fmtabbara d30038c570 Merge branch 'develop' into feature/vesting-token-pool-selector 2022-03-03 12:19:04 +00:00
fmtabbara e3813b4868 Merge branch 'develop' into feature/vesting-token-pool-selector 2022-03-03 12:06:56 +00:00
fmtabbara 61a0e5bbfb add token pool selector 2022-03-02 16:34:03 +00:00
118 changed files with 2294 additions and 1277 deletions
@@ -0,0 +1,71 @@
name: Publish Nym Wallet (Windows 10)
on:
release:
types: [created]
defaults:
run:
working-directory: nym-wallet
jobs:
publish-tauri:
strategy:
fail-fast: false
matrix:
platform: [windows10]
runs-on: ${{ matrix.platform }}
steps:
- name: Clean up first
continue-on-error: true
working-directory: .
run: |
cd ..
del /s /q /A:H nym
rmdir /s /q nym
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-wallet-`
if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-wallet-...')
- name: Import signing certificate
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
New-Item -ItemType directory -Path certificate
Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE
certutil -decode certificate/tempCert.txt certificate/certificate.pfx
Remove-Item -path certificate -include tempCert.txt
Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
- name: Node v16
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install app dependencies
run: yarn
- name: Build and sign it
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ENABLE_CODE_SIGNING: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: yarn build
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
files: nym-wallet/target/release/bundle/msi/*.msi
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

@@ -3,6 +3,7 @@
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, Interval, MixNodeBond};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
@@ -270,7 +271,20 @@ impl<C> Client<C> {
.u128())
}
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, ValidatorClientError>
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
) -> Result<Vec<DelegationEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_pending_delegation_events(owner_address)
.await?)
}
pub async fn get_current_epoch(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -14,6 +14,7 @@ use cosmrs::rpc::HttpClientUrl;
use cosmwasm_std::{Coin, Uint128};
pub use fee::gas_price::GasPrice;
use fee::helpers::Operation;
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{
ContractStateParams, Delegation, ExecuteMsg, Gateway, GatewayBond, GatewayOwnershipResponse,
IdentityKey, Interval, LayerDistribution, MixNode, MixNodeBond, MixOwnershipResponse,
@@ -317,7 +318,20 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_current_epoch(&self) -> Result<Option<Interval>, NymdError>
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
) -> Result<Vec<DelegationEvent>, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetPendingDelegationEvents { owner_address };
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
}
pub async fn get_current_epoch(&self) -> Result<Interval, NymdError>
where
C: CosmWasmClient + Sync,
{
@@ -410,7 +424,7 @@ impl<C> NymdClient<C> {
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetRewardPool {};
let request = QueryMsg::GetEpochsInInterval {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
@@ -56,7 +56,6 @@ pub(crate) mod string_rfc3339_offset_date_time {
}
}
/// Representation of rewarding interval.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
pub struct Interval {
id: u32,
@@ -67,11 +66,12 @@ pub struct Interval {
impl Interval {
/// Initialize epoch in the contract with default values.
/// FIXME: THIS unwrap!
pub fn init_epoch(env: Env) -> Self {
Interval {
id: 0,
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap(),
// I really don't see a way for this to fail, unless the blockchain is lying to us
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
.expect("Invalid timestamp from env.block.time"),
length: Duration::from_secs(3600),
}
}
@@ -62,9 +62,6 @@ pub enum ExecuteMsg {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
// id of the current rewarding interval
interval_id: u32,
},
// RewardNextMixDelegators {
// mix_identity: IdentityKey,
@@ -176,6 +173,9 @@ pub enum QueryMsg {
address: String,
mix_identity: IdentityKey,
},
GetPendingDelegationEvents {
owner_address: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+373 -93
View File
@@ -1,7 +1,107 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"anyOf": [
"oneOf": [
{
"type": "object",
"required": [
"reconcile_delegations"
],
"properties": {
"reconcile_delegations": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"checkpoint_mixnodes"
],
"properties": {
"checkpoint_mixnodes": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_operator_reward_on_behalf"
],
"properties": {
"compound_operator_reward_on_behalf": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_delegator_reward_on_behalf"
],
"properties": {
"compound_delegator_reward_on_behalf": {
"type": "object",
"required": [
"mix_identity",
"owner"
],
"properties": {
"mix_identity": {
"type": "string"
},
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_operator_reward"
],
"properties": {
"compound_operator_reward": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"compound_delegator_reward"
],
"properties": {
"compound_delegator_reward": {
"type": "object",
"required": [
"mix_identity"
],
"properties": {
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -11,11 +111,15 @@
"bond_mixnode": {
"type": "object",
"required": [
"mix_node"
"mix_node",
"owner_signature"
],
"properties": {
"mix_node": {
"$ref": "#/definitions/MixNode"
},
"owner_signature": {
"type": "string"
}
}
}
@@ -34,6 +138,54 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"update_mixnode_config"
],
"properties": {
"update_mixnode_config": {
"type": "object",
"required": [
"profit_margin_percent"
],
"properties": {
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"update_mixnode_config_on_behalf"
],
"properties": {
"update_mixnode_config_on_behalf": {
"type": "object",
"required": [
"owner",
"profit_margin_percent"
],
"properties": {
"owner": {
"type": "string"
},
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -43,11 +195,15 @@
"bond_gateway": {
"type": "object",
"required": [
"gateway"
"gateway",
"owner_signature"
],
"properties": {
"gateway": {
"$ref": "#/definitions/Gateway"
},
"owner_signature": {
"type": "string"
}
}
}
@@ -69,11 +225,11 @@
{
"type": "object",
"required": [
"update_contract_settings"
"update_contract_state_params"
],
"properties": {
"update_contract_settings": {
"$ref": "#/definitions/StateParams"
"update_contract_state_params": {
"$ref": "#/definitions/ContractStateParams"
}
},
"additionalProperties": false
@@ -118,46 +274,6 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"delegate_to_gateway"
],
"properties": {
"delegate_to_gateway": {
"type": "object",
"required": [
"gateway_identity"
],
"properties": {
"gateway_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"undelegate_from_gateway"
],
"properties": {
"undelegate_from_gateway": {
"type": "object",
"required": [
"gateway_identity"
],
"properties": {
"gateway_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -168,16 +284,14 @@
"type": "object",
"required": [
"identity",
"uptime"
"params"
],
"properties": {
"identity": {
"type": "string"
},
"uptime": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
"params": {
"$ref": "#/definitions/NodeRewardParams"
}
}
}
@@ -187,34 +301,216 @@
{
"type": "object",
"required": [
"reward_gateway"
"delegate_to_mixnode_on_behalf"
],
"properties": {
"reward_gateway": {
"delegate_to_mixnode_on_behalf": {
"type": "object",
"required": [
"identity",
"uptime"
"delegate",
"mix_identity"
],
"properties": {
"identity": {
"delegate": {
"type": "string"
},
"uptime": {
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"undelegate_from_mixnode_on_behalf"
],
"properties": {
"undelegate_from_mixnode_on_behalf": {
"type": "object",
"required": [
"delegate",
"mix_identity"
],
"properties": {
"delegate": {
"type": "string"
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"bond_mixnode_on_behalf"
],
"properties": {
"bond_mixnode_on_behalf": {
"type": "object",
"required": [
"mix_node",
"owner",
"owner_signature"
],
"properties": {
"mix_node": {
"$ref": "#/definitions/MixNode"
},
"owner": {
"type": "string"
},
"owner_signature": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"unbond_mixnode_on_behalf"
],
"properties": {
"unbond_mixnode_on_behalf": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"bond_gateway_on_behalf"
],
"properties": {
"bond_gateway_on_behalf": {
"type": "object",
"required": [
"gateway",
"owner",
"owner_signature"
],
"properties": {
"gateway": {
"$ref": "#/definitions/Gateway"
},
"owner": {
"type": "string"
},
"owner_signature": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"unbond_gateway_on_behalf"
],
"properties": {
"unbond_gateway_on_behalf": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"write_rewarded_set"
],
"properties": {
"write_rewarded_set": {
"type": "object",
"required": [
"expected_active_set_size",
"rewarded_set"
],
"properties": {
"expected_active_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"rewarded_set": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"advance_current_epoch"
],
"properties": {
"advance_current_epoch": {
"type": "object"
}
},
"additionalProperties": false
}
],
"definitions": {
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
"ContractStateParams": {
"type": "object",
"required": [
"minimum_gateway_pledge",
"minimum_mixnode_pledge",
"mixnode_active_set_size",
"mixnode_rewarded_set_size"
],
"properties": {
"minimum_gateway_pledge": {
"$ref": "#/definitions/Uint128"
},
"minimum_mixnode_pledge": {
"$ref": "#/definitions/Uint128"
},
"mixnode_active_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mixnode_rewarded_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
"Gateway": {
"type": "object",
@@ -263,6 +559,7 @@
"http_api_port",
"identity_key",
"mix_port",
"profit_margin_percent",
"sphinx_key",
"verloc_port",
"version"
@@ -285,6 +582,11 @@
"format": "uint16",
"minimum": 0.0
},
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"sphinx_key": {
"type": "string"
},
@@ -298,46 +600,24 @@
}
}
},
"StateParams": {
"NodeRewardParams": {
"type": "object",
"required": [
"epoch_length",
"gateway_bond_reward_rate",
"gateway_delegation_reward_rate",
"minimum_gateway_bond",
"minimum_mixnode_bond",
"mixnode_active_set_size",
"mixnode_bond_reward_rate",
"mixnode_delegation_reward_rate"
"in_active_set",
"reward_blockstamp",
"uptime"
],
"properties": {
"epoch_length": {
"in_active_set": {
"type": "boolean"
},
"reward_blockstamp": {
"type": "integer",
"format": "uint32",
"format": "uint64",
"minimum": 0.0
},
"gateway_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"gateway_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"minimum_gateway_bond": {
"uptime": {
"$ref": "#/definitions/Uint128"
},
"minimum_mixnode_bond": {
"$ref": "#/definitions/Uint128"
},
"mixnode_active_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mixnode_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"mixnode_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
}
}
},
@@ -346,4 +626,4 @@
"type": "string"
}
}
}
}
+9 -1
View File
@@ -1,5 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InstantiateMsg",
"type": "object"
"type": "object",
"required": [
"rewarding_validator_address"
],
"properties": {
"rewarding_validator_address": {
"type": "string"
}
}
}
+31 -5
View File
@@ -3,22 +3,29 @@
"title": "MixNodeBond",
"type": "object",
"required": [
"bond_amount",
"block_height",
"layer",
"mix_node",
"owner",
"pledge_amount",
"total_delegation"
],
"properties": {
"accumulated_rewards": {
"anyOf": [
{
"$ref": "#/definitions/Uint128"
},
{
"type": "null"
}
]
},
"block_height": {
"default": 0,
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"bond_amount": {
"$ref": "#/definitions/Coin"
},
"layer": {
"$ref": "#/definitions/Layer"
},
@@ -28,6 +35,19 @@
"owner": {
"$ref": "#/definitions/Addr"
},
"pledge_amount": {
"$ref": "#/definitions/Coin"
},
"proxy": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
},
{
"type": "null"
}
]
},
"total_delegation": {
"$ref": "#/definitions/Coin"
}
@@ -68,6 +88,7 @@
"http_api_port",
"identity_key",
"mix_port",
"profit_margin_percent",
"sphinx_key",
"verloc_port",
"version"
@@ -90,6 +111,11 @@
"format": "uint16",
"minimum": 0.0
},
"profit_margin_percent": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"sphinx_key": {
"type": "string"
},
+337 -125
View File
@@ -1,7 +1,19 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"anyOf": [
"oneOf": [
{
"type": "object",
"required": [
"get_contract_version"
],
"properties": {
"get_contract_version": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -71,7 +83,7 @@
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
"type": "string"
}
}
}
@@ -91,7 +103,7 @@
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
"type": "string"
}
}
}
@@ -101,10 +113,10 @@
{
"type": "object",
"required": [
"contract_settings_params"
"state_params"
],
"properties": {
"contract_settings_params": {
"state_params": {
"type": "object"
}
},
@@ -113,10 +125,58 @@
{
"type": "object",
"required": [
"get_mix_delegations"
"get_all_network_delegations"
],
"properties": {
"get_mix_delegations": {
"get_all_network_delegations": {
"type": "object",
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"array",
"null"
],
"items": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
},
{
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
],
"maxItems": 3,
"minItems": 3
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_mixnode_delegations"
],
"properties": {
"get_mixnode_delegations": {
"type": "object",
"required": [
"mix_identity"
@@ -134,14 +194,22 @@
"type": "string"
},
"start_after": {
"anyOf": [
"type": [
"array",
"null"
],
"items": [
{
"$ref": "#/definitions/Addr"
"type": "string"
},
{
"type": "null"
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
]
],
"maxItems": 2,
"minItems": 2
}
}
}
@@ -151,17 +219,17 @@
{
"type": "object",
"required": [
"get_reverse_mix_delegations"
"get_delegator_delegations"
],
"properties": {
"get_reverse_mix_delegations": {
"get_delegator_delegations": {
"type": "object",
"required": [
"delegation_owner"
"delegator"
],
"properties": {
"delegation_owner": {
"$ref": "#/definitions/Addr"
"delegator": {
"type": "string"
},
"limit": {
"type": [
@@ -185,18 +253,18 @@
{
"type": "object",
"required": [
"get_mix_delegation"
"get_delegation_details"
],
"properties": {
"get_mix_delegation": {
"get_delegation_details": {
"type": "object",
"required": [
"address",
"delegator",
"mix_identity"
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
"delegator": {
"type": "string"
},
"mix_identity": {
"type": "string"
@@ -206,102 +274,6 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_gateway_delegations"
],
"properties": {
"get_gateway_delegations": {
"type": "object",
"required": [
"gateway_identity"
],
"properties": {
"gateway_identity": {
"type": "string"
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
},
{
"type": "null"
}
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_reverse_gateway_delegations"
],
"properties": {
"get_reverse_gateway_delegations": {
"type": "object",
"required": [
"delegation_owner"
],
"properties": {
"delegation_owner": {
"$ref": "#/definitions/Addr"
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_gateway_delegation"
],
"properties": {
"get_gateway_delegation": {
"type": "object",
"required": [
"address",
"gateway_identity"
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
},
"gateway_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -313,12 +285,252 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_reward_pool"
],
"properties": {
"get_reward_pool": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_circulating_supply"
],
"properties": {
"get_circulating_supply": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_interval_reward_percent"
],
"properties": {
"get_interval_reward_percent": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_sybil_resistance_percent"
],
"properties": {
"get_sybil_resistance_percent": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_active_set_work_factor"
],
"properties": {
"get_active_set_work_factor": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarding_status"
],
"properties": {
"get_rewarding_status": {
"type": "object",
"required": [
"interval_id",
"mix_identity"
],
"properties": {
"interval_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarded_set"
],
"properties": {
"get_rewarded_set": {
"type": "object",
"properties": {
"height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarded_set_update_details"
],
"properties": {
"get_rewarded_set_update_details": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_current_rewarded_set_height"
],
"properties": {
"get_current_rewarded_set_height": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_rewarded_set_refresh_blocks"
],
"properties": {
"get_rewarded_set_refresh_blocks": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_current_epoch"
],
"properties": {
"get_current_epoch": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_epochs_in_interval"
],
"properties": {
"get_epochs_in_interval": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"query_operator_reward"
],
"properties": {
"query_operator_reward": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"query_delegator_reward"
],
"properties": {
"query_delegator_reward": {
"type": "object",
"required": [
"address",
"mix_identity"
],
"properties": {
"address": {
"type": "string"
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_pending_delegation_events"
],
"properties": {
"get_pending_delegation_events": {
"type": "object",
"required": [
"owner_address"
],
"properties": {
"owner_address": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
],
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
}
}
}
]
}
-94
View File
@@ -1,94 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "State",
"type": "object",
"required": [
"gateway_epoch_bond_reward",
"gateway_epoch_delegation_reward",
"mixnode_epoch_bond_reward",
"mixnode_epoch_delegation_reward",
"network_monitor_address",
"owner",
"params"
],
"properties": {
"gateway_epoch_bond_reward": {
"$ref": "#/definitions/Decimal"
},
"gateway_epoch_delegation_reward": {
"$ref": "#/definitions/Decimal"
},
"mixnode_epoch_bond_reward": {
"$ref": "#/definitions/Decimal"
},
"mixnode_epoch_delegation_reward": {
"$ref": "#/definitions/Decimal"
},
"network_monitor_address": {
"$ref": "#/definitions/Addr"
},
"owner": {
"$ref": "#/definitions/Addr"
},
"params": {
"$ref": "#/definitions/StateParams"
}
},
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"StateParams": {
"type": "object",
"required": [
"epoch_length",
"gateway_bond_reward_rate",
"gateway_delegation_reward_rate",
"minimum_gateway_bond",
"minimum_mixnode_bond",
"mixnode_active_set_size",
"mixnode_bond_reward_rate",
"mixnode_delegation_reward_rate"
],
"properties": {
"epoch_length": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"gateway_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"gateway_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"minimum_gateway_bond": {
"$ref": "#/definitions/Uint128"
},
"minimum_mixnode_bond": {
"$ref": "#/definitions/Uint128"
},
"mixnode_active_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mixnode_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"mixnode_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
}
}
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
}
+28 -23
View File
@@ -5,7 +5,9 @@ use crate::constants::{ACTIVE_SET_WORK_FACTOR, INTERVAL_REWARD_PERCENT, SYBIL_RE
use crate::delegations::queries::query_all_network_delegations_paged;
use crate::delegations::queries::query_delegator_delegations_paged;
use crate::delegations::queries::query_mixnode_delegation;
use crate::delegations::queries::query_mixnode_delegations_paged;
use crate::delegations::queries::{
query_mixnode_delegations_paged, query_pending_delegation_events,
};
use crate::error::ContractError;
use crate::gateways::queries::query_gateways_paged;
use crate::gateways::queries::query_owns_gateway;
@@ -14,7 +16,7 @@ use crate::interval::queries::{
query_current_rewarded_set_height, query_rewarded_set,
query_rewarded_set_refresh_minimum_blocks, query_rewarded_set_update_details,
};
use crate::interval::storage as interval_storage;
use crate::interval::transactions::init_epoch;
use crate::mixnet_contract_settings::models::ContractState;
use crate::mixnet_contract_settings::queries::{
query_contract_settings_params, query_contract_version,
@@ -31,7 +33,7 @@ use cosmwasm_std::{
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
};
use mixnet_contract_common::{
ContractStateParams, Delegation, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg,
ContractStateParams, Delegation, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
use time::OffsetDateTime;
@@ -77,13 +79,11 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, rewarding_validator_address);
let rewarding_interval = Interval::init_epoch(env.clone());
init_epoch(deps.storage, env)?;
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
mixnet_params_storage::LAYERS.save(deps.storage, &Default::default())?;
rewards_storage::REWARD_POOL.save(deps.storage, &Uint128::new(INITIAL_REWARD_POOL))?;
interval_storage::save_epoch(deps.storage, &rewarding_interval)?;
interval_storage::CURRENT_REWARDED_SET_HEIGHT.save(deps.storage, &env.block.height)?;
Ok(Response::default())
}
@@ -146,18 +146,9 @@ pub fn execute(
deps, info, params,
)
}
ExecuteMsg::RewardMixnode {
identity,
params,
interval_id,
} => crate::rewards::transactions::try_reward_mixnode(
deps,
env,
info,
identity,
params,
interval_id,
),
ExecuteMsg::RewardMixnode { identity, params } => {
crate::rewards::transactions::try_reward_mixnode(deps, env, info, identity, params)
}
ExecuteMsg::DelegateToMixnode { mix_identity } => {
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
}
@@ -238,9 +229,11 @@ pub fn execute(
rewarded_set,
expected_active_set_size,
),
ExecuteMsg::AdvanceCurrentEpoch {} => {
crate::interval::transactions::try_advance_epoch(env, deps.storage)
}
ExecuteMsg::AdvanceCurrentEpoch {} => crate::interval::transactions::try_advance_epoch(
env,
deps.storage,
info.sender.to_string(),
),
ExecuteMsg::CompoundDelegatorReward { mix_identity } => {
crate::rewards::transactions::try_compound_delegator_reward(
deps,
@@ -370,13 +363,18 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
address,
mix_identity,
)?),
QueryMsg::GetPendingDelegationEvents { owner_address } => to_binary(
&query_pending_delegation_events(deps.storage, owner_address)?,
),
};
Ok(query_res?)
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
// MIGRATE OLD DELEGATION STORAGE
// applied on QAnet
#[allow(dead_code)]
fn migrate_delegations(deps: DepsMut<'_>) -> Result<(), ContractError> {
use crate::delegations::storage::{
DelegationIndex, DELEGATION_MIXNODE_IDX_NAMESPACE, DELEGATION_OWNER_IDX_NAMESPACE,
DELEGATION_PK_NAMESPACE,
@@ -420,6 +418,13 @@ pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respons
&delegation,
)?;
}
Ok(())
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
// TODO: Uncomment for sandbox and mainnet
// migrate_delegations(deps)?;
Ok(Default::default())
}
@@ -7,11 +7,24 @@ use cosmwasm_std::Order;
use cosmwasm_std::StdResult;
use cosmwasm_std::{Api, Deps, Storage};
use cw_storage_plus::{Bound, PrimaryKey};
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{
Delegation, IdentityKey, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixDelegationsResponse,
};
pub(crate) fn query_pending_delegation_events(
storage: &dyn Storage,
owner_address: String,
) -> Result<Vec<DelegationEvent>, ContractError> {
Ok(storage::PENDING_DELEGATION_EVENTS
.sub_prefix(owner_address.as_bytes().to_vec())
.range(storage, None, None, Order::Ascending)
.filter_map(|r| r.ok())
.map(|(_key, delegation_event)| delegation_event)
.collect::<Vec<DelegationEvent>>())
}
pub(crate) fn query_all_network_delegations_paged(
deps: Deps<'_>,
start_after: Option<(IdentityKey, Vec<u8>, u64)>,
+2 -2
View File
@@ -10,9 +10,9 @@ pub const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo";
pub const DELEGATION_MIXNODE_IDX_NAMESPACE: &str = "dlm";
pub const PENDING_DELEGATION_EVENTS: Map<
(BlockHeight, IdentityKey, OwnerAddress),
(OwnerAddress, BlockHeight, IdentityKey),
DelegationEvent,
> = Map::new("pend");
> = Map::new("pend2");
// paged retrieval limits for all queries and transactions
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 500;
@@ -40,7 +40,7 @@ pub(crate) fn _try_reconcile_all_delegation_events(
let pending_delegation_events = PENDING_DELEGATION_EVENTS
.range(storage, None, None, Order::Ascending)
.filter_map(|r| r.ok())
.collect::<Vec<((u64, String, Vec<u8>), DelegationEvent)>>();
.collect::<Vec<((Vec<u8>, u64, String), DelegationEvent)>>();
let mut response = Response::new();
@@ -193,7 +193,7 @@ pub(crate) fn _try_delegate_to_mixnode(
}
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
let storage_key = (block_height, mix_identity.to_string(), maybe_proxy_storage);
let storage_key = (maybe_proxy_storage, block_height, mix_identity.to_string());
storage::PENDING_DELEGATION_EVENTS.save(
storage,
@@ -389,9 +389,9 @@ pub(crate) fn _try_remove_delegation_from_mixnode(
PENDING_DELEGATION_EVENTS.save(
deps.storage,
(
delegate.as_bytes().to_vec(),
env.block.height,
mix_identity.to_string(),
delegate.as_bytes().to_vec(),
),
&DelegationEvent::Undelegate(PendingUndelegate::new(
mix_identity.to_string(),
@@ -1069,7 +1069,7 @@ mod tests {
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let delegation = query_mixnode_delegation(
let _delegation = query_mixnode_delegation(
&deps.storage,
&deps.api,
identity.clone(),
+1 -1
View File
@@ -10,7 +10,7 @@ use mixnet_contract_common::{
RewardedSetUpdateDetails,
};
pub fn query_current_epoch(storage: &dyn Storage) -> Result<Option<Interval>, ContractError> {
pub fn query_current_epoch(storage: &dyn Storage) -> Result<Interval, ContractError> {
storage::current_epoch(storage)
}
+4 -2
View File
@@ -55,8 +55,10 @@ pub fn save_epoch_reward_params(
Ok(())
}
pub fn current_epoch(storage: &dyn Storage) -> Result<Option<Interval>, ContractError> {
Ok(CURRENT_EPOCH.may_load(storage)?)
pub fn current_epoch(storage: &dyn Storage) -> Result<Interval, ContractError> {
CURRENT_EPOCH
.load(storage)
.map_err(|_| ContractError::EpochNotInitialized)
}
pub(crate) fn save_rewarded_set(
+21 -24
View File
@@ -5,6 +5,7 @@ use super::storage;
use crate::error::ContractError;
use crate::error::ContractError::EpochInProgress;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::support::helpers::is_authorized;
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::events::{new_advance_interval_event, new_change_rewarded_set_event};
use mixnet_contract_common::{IdentityKey, Interval};
@@ -64,23 +65,26 @@ pub fn try_write_rewarded_set(
)))
}
fn init_epoch(storage: &mut dyn Storage, env: Env) -> Result<Interval, ContractError> {
pub fn init_epoch(storage: &mut dyn Storage, env: Env) -> Result<Interval, ContractError> {
let epoch = Interval::init_epoch(env);
storage::save_epoch(storage, &epoch)?;
Ok(epoch)
}
pub fn try_advance_epoch(env: Env, storage: &mut dyn Storage) -> Result<Response, ContractError> {
pub fn try_advance_epoch(
env: Env,
storage: &mut dyn Storage,
sender: String,
) -> Result<Response, ContractError> {
// in theory, we could have just changed the state and relied on its reversal upon failed
// execution, but better safe than sorry and do not modify the state at all unless we know
// all checks have succeeded.
let current_epoch = if let Some(epoch) = storage::current_epoch(storage)? {
epoch
} else {
let epoch = init_epoch(storage, env)?;
return Ok(Response::new().add_event(new_advance_interval_event(epoch)));
};
// Only rewarding validator can attempt to advance epoch
is_authorized(sender, storage)?;
let current_epoch = storage::current_epoch(storage)?;
if current_epoch.is_over(env.clone()) {
let next_epoch = current_epoch.next_on_chain(env);
@@ -102,9 +106,8 @@ mod tests {
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Timestamp;
use mixnet_contract_common::{Interval, RewardedSetNodeStatus};
use std::time::Duration;
use time::OffsetDateTime;
use mixnet_contract_common::RewardedSetNodeStatus;
use mixnet_params_storage::rewarding_validator_address;
#[test]
fn writing_rewarded_set() {
@@ -232,22 +235,15 @@ mod tests {
fn advancing_epoch() {
let mut env = mock_env();
let mut deps = test_helpers::init_contract();
// 1609459200 = 2021-01-01
// 1640995200 = 2022-01-01
// 1641081600 = 2022-01-02
// 1643673600 = 2022-02-01
// 1672531200 = 2023-01-01
let sender = rewarding_validator_address(&deps.storage).unwrap();
let _current_epoch = init_epoch(&mut deps.storage, env.clone()).unwrap();
// Works as its after the current epoch
env.block.time = Timestamp::from_seconds(1641081600);
assert!(try_advance_epoch(env.clone(), deps.as_mut().storage).is_ok());
assert!(try_advance_epoch(env.clone(), deps.as_mut().storage, sender.clone()).is_ok());
let current_epoch = crate::interval::storage::current_epoch(&mut deps.storage)
.unwrap()
.unwrap();
let current_epoch = crate::interval::storage::current_epoch(&mut deps.storage).unwrap();
// same if the current blocktime is set to BEFORE the first interval has even begun
// (say we decided to set the first interval to be some time in the future at initialisation)
@@ -258,7 +254,7 @@ mod tests {
epoch_start: current_epoch.start_unix_timestamp(),
epoch_end: current_epoch.end_unix_timestamp()
}),
try_advance_epoch(env.clone(), deps.as_mut().storage)
try_advance_epoch(env.clone(), deps.as_mut().storage, sender.clone(),)
);
// works otherwise
@@ -271,7 +267,7 @@ mod tests {
Response::new().add_event(new_advance_interval_event(expected_new_epoch));
assert_eq!(
Ok(expected_response),
try_advance_epoch(env.clone(), deps.as_mut().storage)
try_advance_epoch(env.clone(), deps.as_mut().storage, sender)
);
// interval way back in the past (i.e. 'somebody' failed to advance it for a long time)
@@ -280,9 +276,10 @@ mod tests {
let expected_new_epoch = current_epoch.next_on_chain(env.clone());
let expected_response =
Response::new().add_event(new_advance_interval_event(expected_new_epoch));
let sender = rewarding_validator_address(&deps.storage).unwrap();
assert_eq!(
Ok(expected_response),
try_advance_epoch(env.clone(), deps.as_mut().storage)
try_advance_epoch(env.clone(), deps.as_mut().storage, sender)
);
}
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use crate::mixnet_contract_settings::models::ContractState;
use cosmwasm_std::StdResult;
use cosmwasm_std::Storage;
@@ -10,6 +11,12 @@ use mixnet_contract_common::{Layer, LayerDistribution};
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new("config");
pub(crate) const LAYERS: Item<'_, LayerDistribution> = Item::new("layers");
pub fn rewarding_validator_address(storage: &dyn Storage) -> Result<String, ContractError> {
Ok(CONTRACT_STATE
.load(storage)
.map(|state| state.rewarding_validator_address.to_string())?)
}
pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
LAYERS
.update(storage, |mut distribution| {
+3 -6
View File
@@ -103,10 +103,9 @@ pub(crate) mod tests {
try_reward_mixnode(
deps.as_mut(),
env,
info,
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
0,
)
.unwrap();
@@ -145,10 +144,9 @@ pub(crate) mod tests {
try_reward_mixnode(
deps.as_mut(),
env.clone(),
info,
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
0,
)
.unwrap();
@@ -188,10 +186,9 @@ pub(crate) mod tests {
try_reward_mixnode(
deps.as_mut(),
env,
info.clone(),
info,
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
1,
)
.unwrap();
+229 -108
View File
@@ -6,11 +6,10 @@ use crate::constants;
use crate::delegations::storage as delegations_storage;
use crate::delegations::transactions::_try_delegate_to_mixnode;
use crate::error::ContractError;
use crate::interval::storage::{self as interval_storage};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage::mixnodes;
use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond};
use crate::rewards::helpers;
use crate::support::helpers::is_authorized;
use config::defaults::DENOM;
use cosmwasm_std::{Addr, Api, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128};
use mixnet_contract_common::events::{
@@ -296,59 +295,19 @@ pub fn calculate_delegator_reward(
Ok(accumulated_rewards)
}
/// Checks whether under the current context, any rewarding-related functionalities can be called.
/// The following must be true:
/// - the call has originated from the address of the authorised rewarding validator,
/// - the call has been made with the nonce corresponding to the current rewarding procedure,
///
/// # Arguments
///
/// * `storage`: reference (kinda) to the underlying storage pool of the contract used to read the current state
/// * `info`: contains the essential info for authorization, such as identity of the call
/// * `interval_id`: expected id of the current interval sent alongside the call
fn verify_rewarding_state(
storage: &dyn Storage,
info: MessageInfo,
interval_id: u32,
) -> Result<(), ContractError> {
let state = mixnet_params_storage::CONTRACT_STATE.load(storage)?;
// check if this is executed by the permitted validator, if not reject the transaction
if info.sender != state.rewarding_validator_address {
return Err(ContractError::Unauthorized);
}
let current_interval = if let Some(epoch) = interval_storage::current_epoch(storage)? {
epoch
} else {
return Err(ContractError::EpochNotInitialized);
};
// make sure the transaction is sent for the correct interval
// (guard ourselves against somebody trying to send stale results;
// realistically it's never going to happen in a single rewarding validator case
if interval_id != current_interval.id() {
Err(ContractError::InvalidIntervalId {
received: interval_id,
expected: current_interval.id(),
})
} else {
Ok(())
}
}
pub(crate) fn try_reward_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_identity: IdentityKey,
params: NodeRewardParams,
epoch_id: u32,
) -> Result<Response, ContractError> {
verify_rewarding_state(deps.storage, info, epoch_id)?;
is_authorized(info.sender.to_string(), deps.storage)?;
// verify_rewarding_state(deps.storage, info, epoch_id)?;
let epoch = crate::interval::storage::current_epoch(deps.storage)?;
// check if the mixnode hasn't been rewarded in this rewarding interval already
match storage::REWARDING_STATUS.may_load(deps.storage, (epoch_id, mix_identity.clone()))? {
match storage::REWARDING_STATUS.may_load(deps.storage, (epoch.id(), mix_identity.clone()))? {
None => (),
Some(RewardingStatus::Complete(_)) => {
return Err(ContractError::MixnodeAlreadyRewarded {
@@ -369,7 +328,7 @@ pub(crate) fn try_reward_mixnode(
None => {
return Ok(
Response::new().add_event(new_not_found_mix_operator_rewarding_event(
epoch_id,
epoch.id(),
&mix_identity,
)),
)
@@ -380,13 +339,13 @@ pub(crate) fn try_reward_mixnode(
if current_bond.block_height + constants::MINIMUM_BLOCK_AGE_FOR_REWARDING > env.block.height {
storage::REWARDING_STATUS.save(
deps.storage,
(epoch_id, mix_identity.clone()),
(epoch.id(), mix_identity.clone()),
&RewardingStatus::Complete(Default::default()),
)?;
return Ok(
Response::new().add_event(new_too_fresh_bond_mix_operator_rewarding_event(
epoch_id,
epoch.id(),
&mix_identity,
)),
);
@@ -399,13 +358,13 @@ pub(crate) fn try_reward_mixnode(
if params.uptime() == 0 {
storage::REWARDING_STATUS.save(
deps.storage,
(epoch_id, mix_identity.clone()),
(epoch.id(), mix_identity.clone()),
&RewardingStatus::Complete(Default::default()),
)?;
return Ok(
Response::new().add_event(new_zero_uptime_mix_operator_rewarding_event(
epoch_id,
epoch.id(),
&mix_identity,
)),
);
@@ -424,7 +383,15 @@ pub(crate) fn try_reward_mixnode(
let mut stored_bond: StoredMixnodeBond = current_bond.into();
// technically we don't have to set the total_delegation bucket, but it makes things easier
// in different places that we can guarantee that if node exists, so does the data behind the total delegation
stored_bond.epoch_rewards = Some(NodeEpochRewards::new(
node_reward_params,
stored_node_result,
epoch.id(),
));
let identity = stored_bond.identity();
crate::mixnodes::storage::mixnodes().save(
deps.storage,
identity,
@@ -432,12 +399,6 @@ pub(crate) fn try_reward_mixnode(
env.block.height,
)?;
stored_bond.epoch_rewards = Some(NodeEpochRewards::new(
node_reward_params,
stored_node_result,
epoch_id,
));
// Take rewards out of the rewarding pool
storage::decr_reward_pool(deps.storage, stored_node_result.reward())?;
@@ -447,13 +408,13 @@ pub(crate) fn try_reward_mixnode(
helpers::update_rewarding_status(
deps.storage,
epoch_id,
epoch.id(),
mix_identity.clone(),
rewarding_result,
)?;
Ok(Response::new().add_event(new_mix_operator_rewarding_event(
epoch_id,
epoch.id(),
&mix_identity,
node_reward_result,
node_pledge,
@@ -471,7 +432,9 @@ pub mod tests {
current_epoch_reward_params, save_epoch, save_epoch_reward_params,
};
use crate::interval::transactions::try_advance_epoch;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnet_contract_settings::storage::{
self as mixnet_params_storage, rewarding_validator_address,
};
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::storage::StoredMixnodeBond;
use crate::rewards::transactions::try_reward_mixnode;
@@ -493,10 +456,9 @@ pub mod tests {
fn rewarding_mixnodes_with_incorrect_interval_id() {
let mut deps = test_helpers::init_contract();
let mut env = mock_env();
let current_state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_mut().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let sender = rewarding_validator_address(&deps.storage).unwrap();
let info = mock_info(&sender, &coins(1000, DENOM));
crate::interval::transactions::init_epoch(&mut deps.storage, env.clone()).unwrap();
// bond the node
let node_owner: Addr = Addr::unchecked("node-owner");
@@ -506,7 +468,15 @@ pub mod tests {
deps.as_mut(),
);
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
// Reward once
let res = try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
);
assert!(res.is_ok());
let res = try_reward_mixnode(
deps.as_mut(),
@@ -514,15 +484,25 @@ pub mod tests {
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
1,
);
assert_eq!(
Err(ContractError::InvalidIntervalId {
received: 1,
expected: 0
}),
res
// Fails since mixnode was already rewarded in this epoch
assert!(res.is_err());
let rewarding_validator_address = rewarding_validator_address(&deps.storage).unwrap();
// Advance epoch
let premature_advance = try_advance_epoch(
env.clone(),
&mut deps.storage,
rewarding_validator_address.clone(),
);
assert!(premature_advance.is_err());
env.block.time = Timestamp::from_seconds(env.block.time.seconds() + 3600);
let timely_advance =
try_advance_epoch(env.clone(), &mut deps.storage, rewarding_validator_address);
assert!(timely_advance.is_ok());
let res = try_reward_mixnode(
deps.as_mut(),
@@ -530,23 +510,6 @@ pub mod tests {
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
2,
);
assert_eq!(
Err(ContractError::InvalidIntervalId {
received: 2,
expected: 0
}),
res
);
let res = try_reward_mixnode(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
0,
);
assert!(res.is_ok());
@@ -555,10 +518,9 @@ pub mod tests {
let res = try_reward_mixnode(
deps.as_mut(),
env.clone(),
info,
info.clone(),
node_identity,
tests::fixtures::node_reward_params_fixture(100),
1,
);
assert!(res.is_ok());
}
@@ -589,7 +551,6 @@ pub mod tests {
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
0,
);
assert!(res.is_ok());
@@ -600,7 +561,6 @@ pub mod tests {
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
0,
);
assert_eq!(
Err(ContractError::MixnodeAlreadyRewarded {
@@ -615,10 +575,9 @@ pub mod tests {
let res = try_reward_mixnode(
deps.as_mut(),
env,
info,
info.clone(),
node_identity,
tests::fixtures::node_reward_params_fixture(100),
1,
);
assert!(res.is_ok());
}
@@ -692,9 +651,7 @@ pub mod tests {
save_epoch(&mut deps.storage, &epoch).unwrap();
save_epoch_reward_params(epoch.id(), &mut deps.storage).unwrap();
let epoch_from_storage = interval_storage::current_epoch(&deps.storage)
.unwrap()
.unwrap();
let epoch_from_storage = crate::interval::storage::current_epoch(&deps.storage).unwrap();
assert_eq!(epoch_from_storage.id(), 0);
let res = try_reward_mixnode(
@@ -703,7 +660,6 @@ pub mod tests {
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
0,
)
.unwrap();
@@ -730,7 +686,10 @@ pub mod tests {
// reward can happen now, but only for bonded node
env.block.height += 1;
env.block.time = Timestamp::from_seconds(epoch.next().start_unix_timestamp() as u64);
try_advance_epoch(env.clone(), &mut deps.storage).unwrap();
let sender =
crate::mixnet_contract_settings::storage::rewarding_validator_address(&deps.storage)
.unwrap();
try_advance_epoch(env.clone(), &mut deps.storage, sender).unwrap();
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
@@ -740,7 +699,6 @@ pub mod tests {
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
1,
)
.unwrap();
@@ -787,7 +745,6 @@ pub mod tests {
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
2,
)
.unwrap();
@@ -821,6 +778,171 @@ pub mod tests {
// );
}
#[test]
fn test_reward_additivity() {
use crate::constants::INTERVAL_REWARD_PERCENT;
use crate::contract::INITIAL_REWARD_POOL;
type U128 = fixed::types::U75F53;
let mut deps = test_helpers::init_contract();
let mut env = mock_env();
let current_state = mixnet_params_storage::CONTRACT_STATE
.load(deps.as_ref().storage)
.unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let period_reward_pool = (INITIAL_REWARD_POOL / 100 / EPOCHS_IN_INTERVAL as u128)
* INTERVAL_REWARD_PERCENT as u128;
assert_eq!(period_reward_pool, 6_944_444_444);
let circulating_supply = storage::circulating_supply(&deps.storage).unwrap().u128();
assert_eq!(circulating_supply, 750_000_000_000_000u128);
let node_owner: Addr = Addr::unchecked("alice");
let node_identity = test_helpers::add_mixnode(
node_owner.as_str(),
coins(10_000_000_000, DENOM),
deps.as_mut(),
);
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("alice_d1", &[coin(8000_000000, DENOM)]),
node_identity.clone(),
)
.unwrap();
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("alice_d2", &[coin(2000_000000, DENOM)]),
node_identity.clone(),
)
.unwrap();
let node_owner: Addr = Addr::unchecked("bob");
let node_identity_2 = test_helpers::add_mixnode(
node_owner.as_str(),
coins(10_000_000_000, DENOM),
deps.as_mut(),
);
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("bob_d1", &[coin(8000_000000, DENOM)]),
node_identity_2.clone(),
)
.unwrap();
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("bob_d2", &[coin(2000_000000, DENOM)]),
node_identity_2.clone(),
)
.unwrap();
let node_owner: Addr = Addr::unchecked("alicebob");
let node_identity_3 = test_helpers::add_mixnode(
node_owner.as_str(),
coins(10_000_000_000 * 2, DENOM),
deps.as_mut(),
);
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("alicebob_d1", &[coin(8000_000000 * 2, DENOM)]),
node_identity_3.clone(),
)
.unwrap();
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("alicebob_d2", &[coin(2000_000000 * 2, DENOM)]),
node_identity_3.clone(),
)
.unwrap();
crate::delegations::transactions::_try_reconcile_all_delegation_events(
&mut deps.storage,
&deps.api,
)
.unwrap();
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity)
.unwrap()
.unwrap();
let mix_1_uptime = 100;
let mix_2 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity_2)
.unwrap()
.unwrap();
let mix_2_uptime = 50;
let mix_3 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity_3)
.unwrap()
.unwrap();
// average of 1 and 2
let mix_3_uptime = 75;
let epoch = Interval::init_epoch(env.clone());
save_epoch(&mut deps.storage, &epoch).unwrap();
save_epoch_reward_params(epoch.id(), &mut deps.storage).unwrap();
let interval_reward_params = current_epoch_reward_params(&deps.storage).unwrap();
let node_reward_params = NodeRewardParams::new(0, mix_1_uptime, true);
let node_reward_params_2 = NodeRewardParams::new(0, mix_2_uptime, true);
let node_reward_params_3 = NodeRewardParams::new(0, mix_3_uptime, true);
let mut params = RewardParams::new(interval_reward_params, node_reward_params);
let mut params2 = RewardParams::new(interval_reward_params, node_reward_params_2);
let mut params3 = RewardParams::new(interval_reward_params, node_reward_params_3);
params.set_reward_blockstamp(env.block.height);
params2.set_reward_blockstamp(env.block.height);
params3.set_reward_blockstamp(env.block.height);
assert_eq!(params.performance(), U128::from_num(1u32));
assert_eq!(params2.performance(), U128::from_num(0.5f32));
assert_eq!(params3.performance(), U128::from_num(0.75f32));
let mix_1_reward_result = mix_1.reward(&params);
assert_eq!(
mix_1_reward_result.sigma(),
U128::from_num(0.0000266666666666f64)
);
assert_eq!(
mix_1_reward_result.lambda(),
U128::from_num(0.0000133333333333f64)
);
assert_eq!(mix_1_reward_result.reward().int(), 259114u128);
let mix_2_reward_result = mix_2.reward(&params2);
assert_eq!(
mix_2_reward_result.sigma(),
U128::from_num(0.0000266666666666f64)
);
assert_eq!(
mix_2_reward_result.lambda(),
U128::from_num(0.0000133333333333f64)
);
assert_eq!(mix_2_reward_result.reward().int(), 129557u128);
let mix_3_reward_result = mix_3.reward(&params3);
// assert_eq!(mix_3_reward_result.reward().int(), mix_1_reward_result.reward().int() + mix_2_reward_result.reward().int());
}
#[test]
fn test_tokenomics_rewarding() {
use crate::constants::INTERVAL_REWARD_PERCENT;
@@ -933,10 +1055,9 @@ pub mod tests {
try_reward_mixnode(
deps.as_mut(),
env,
info,
info.clone(),
node_identity.clone(),
node_reward_params,
0,
)
.unwrap();
@@ -1014,13 +1135,14 @@ pub mod tests {
save_epoch(&mut deps.storage, &epoch).unwrap();
save_epoch_reward_params(epoch.id(), &mut deps.storage).unwrap();
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_reward_mixnode(
deps.as_mut(),
env.clone(),
mock_info(rewarding_validator_address.as_ref(), &[]),
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
0,
)
.unwrap();
@@ -1051,10 +1173,9 @@ pub mod tests {
try_reward_mixnode(
deps.as_mut(),
env,
info,
info.clone(),
node_identity.clone(),
tests::fixtures::node_reward_params_fixture(100),
1,
)
.unwrap();
}
+7
View File
@@ -8,6 +8,13 @@ use crate::error::ContractError;
use cosmwasm_std::{Addr, Deps, Storage};
use mixnet_contract_common::{reward_params::EpochRewardParams, IdentityKeyRef};
pub(crate) fn is_authorized(sender: String, storage: &dyn Storage) -> Result<(), ContractError> {
if sender != crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? {
return Err(ContractError::Unauthorized);
}
Ok(())
}
pub(crate) fn epoch_reward_params(
epoch_id: u32,
storage: &mut dyn Storage,
@@ -1,14 +1,10 @@
use std::time::Duration;
use crate::contract::INITIAL_MIXNODE_PLEDGE;
use crate::mixnodes::storage as mixnodes_storage;
use crate::{mixnodes::storage::StoredMixnodeBond, support::tests};
use az::UnwrappedAs;
use config::defaults::DENOM;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use cosmwasm_std::{coin, Addr, Coin};
use mixnet_contract_common::reward_params::NodeRewardParams;
use mixnet_contract_common::{Gateway, GatewayBond, Interval, Layer, MixNode};
use time::OffsetDateTime;
use mixnet_contract_common::{Gateway, GatewayBond, Layer, MixNode};
pub fn mix_node_fixture() -> MixNode {
MixNode {
+4 -2
View File
@@ -132,13 +132,15 @@ pub mod test_helpers {
// make sure current block time is within the expected next interval
env.block.time = Timestamp::from_seconds(
(interval_storage::current_epoch(storage)
.unwrap()
.unwrap()
.next()
.start_unix_timestamp()
+ 123) as u64,
);
interval::transactions::try_advance_epoch(env.clone(), storage).unwrap();
let sender =
crate::mixnet_contract_settings::storage::rewarding_validator_address(storage).unwrap();
interval::transactions::try_advance_epoch(env.clone(), storage, sender).unwrap();
}
}
+2
View File
@@ -11,6 +11,7 @@
"tauri:build": "yarn tauri build",
"tsc": "tsc --noEmit true",
"dev": "yarn run webpack:dev & yarn run tauri:dev",
"prebuild": "yarn --cwd .. build",
"build": "run-s webpack:prod tauri:build",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
@@ -91,6 +92,7 @@
"react-refresh": "^0.10.0",
"react-refresh-typescript": "^2.0.2",
"style-loader": "^3.2.1",
"thread-loader": "^3.0.4",
"ts-jest": "^27.0.5",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
+4
View File
@@ -36,6 +36,7 @@ fn main() {
.invoke_handler(tauri::generate_handler![
mixnet::account::connect_with_mnemonic,
mixnet::account::create_new_account,
mixnet::account::create_new_mnemonic,
mixnet::account::get_balance,
mixnet::account::logout,
mixnet::account::switch_network,
@@ -54,6 +55,8 @@ fn main() {
mixnet::delegate::get_delegator_rewards,
mixnet::delegate::get_reverse_mix_delegations_paged,
mixnet::delegate::undelegate_from_mixnode,
mixnet::delegate::get_pending_delegation_events,
mixnet::epoch::get_current_epoch,
mixnet::send::send,
utils::major_to_minor,
utils::minor_to_major,
@@ -71,6 +74,7 @@ fn main() {
vesting::bond::vesting_unbond_gateway,
vesting::bond::vesting_unbond_mixnode,
vesting::bond::withdraw_vested_coins,
vesting::bond::vesting_update_mixnode,
vesting::delegate::vesting_delegate_to_mixnode,
vesting::delegate::vesting_undelegate_from_mixnode,
vesting::queries::delegated_free,
@@ -100,6 +100,12 @@ pub async fn create_new_account(
})
}
#[tauri::command]
pub async fn create_new_mnemonic() -> Result<String, BackendError> {
let rand_mnemonic = random_mnemonic();
Ok(rand_mnemonic.to_string())
}
#[tauri::command]
pub async fn switch_network(
state: tauri::State<'_, Arc<RwLock<State>>>,
@@ -2,12 +2,27 @@ 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::{Coin as CosmWasmCoin, Uint128};
use mixnet_contract_common::{IdentityKey, PagedDelegatorDelegationsResponse};
use std::sync::Arc;
use tokio::sync::RwLock;
#[tauri::command]
pub async fn get_pending_delegation_events(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DelegationEvent>, BackendError> {
Ok(
nymd_client!(state)
.get_pending_delegation_events(nymd_client!(state).address().to_string())
.await?
.into_iter()
.map(|delegation_event| delegation_event.into())
.collect::<Vec<DelegationEvent>>(),
)
}
#[tauri::command]
pub async fn delegate_to_mixnode(
identity: &str,
@@ -0,0 +1,36 @@
use crate::error::BackendError;
use crate::nymd_client;
use crate::state::State;
use mixnet_contract_common::Interval;
use serde::{Deserialize, Serialize};
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> {
let interval = nymd_client!(state).get_current_epoch().await?;
Ok(interval.into())
}
@@ -2,4 +2,5 @@ pub mod account;
pub mod admin;
pub mod bond;
pub mod delegate;
pub mod epoch;
pub mod send;
@@ -64,3 +64,14 @@ pub async fn withdraw_vested_coins(
nymd_client!(state).withdraw_vested_coins(amount).await?;
Ok(())
}
#[tauri::command]
pub async fn vesting_update_mixnode(
profit_margin_percent: u8,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.vesting_update_mixnode_config(profit_margin_percent)
.await?;
Ok(())
}
@@ -13,13 +13,15 @@ use super::{OriginalVestingResponse, PledgeData};
#[tauri::command]
pub async fn locked_coins(
address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
nymd_client!(state)
.locked_coins(address, block_time.map(Timestamp::from_seconds))
.locked_coins(
nymd_client!(state).address().as_ref(),
block_time.map(Timestamp::from_seconds),
)
.await?
.into(),
)
@@ -27,14 +29,13 @@ pub async fn locked_coins(
#[tauri::command]
pub async fn spendable_coins(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
nymd_client!(state)
.spendable_coins(
vesting_account_address,
nymd_client!(state).address().as_ref(),
block_time.map(Timestamp::from_seconds),
)
.await?
+53
View File
@@ -3,6 +3,9 @@ use crate::error::BackendError;
use crate::nymd_client;
use crate::state::State;
use crate::Operation;
use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent;
use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate;
use mixnet_contract_common::Delegation;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -76,3 +79,53 @@ impl DelegationResult {
}
}
}
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(),
}
}
}
+2 -2
View File
@@ -40,9 +40,9 @@
"entitlements": null
},
"windows": {
"certificateThumbprint": null,
"certificateThumbprint": "6DB77B1F529A0804FE0E6843A3EB8A8CECFFD408",
"digestAlgorithm": "sha256",
"timestampUrl": ""
"timestampUrl": "http://timestamp.comodoca.com"
}
},
"updater": {
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Button, IconButton, Tooltip } from '@mui/material';
import { Check, ContentCopy } from '@mui/icons-material';
import { clipboard } from '@tauri-apps/api';
import { Console } from '../utils/console';
export const CopyToClipboard = ({
text = '',
@@ -19,7 +20,7 @@ export const CopyToClipboard = ({
await clipboard.writeText(_text);
setCopied(true);
} catch (e) {
console.log(`failed to copy: ${e}`);
Console.error(`failed to copy: ${e}`);
}
};
+1 -1
View File
@@ -20,7 +20,7 @@ export const Fee = ({ feeType }: { feeType: Operation }) => {
if (fee) {
return (
<Typography sx={{ color: 'nym.fee', fontWeight: 600 }}>
Fee for this transaction: {`${fee} ${currency?.major}`}{' '}
Estimated fee for this transaction: {`${fee} ${currency?.major}`}{' '}
</Typography>
);
}
+52 -10
View File
@@ -1,16 +1,58 @@
import { ListItem, ListItemText, Select } from '@mui/material';
import React, { useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { FormControl, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent, Typography } from '@mui/material';
import { ClientContext } from '../context/main';
type TPool = 'balance' | 'locked';
type TPoolOption = 'balance' | 'locked';
export const TokenPoolSelector: React.FC = () => {
const [value] = useState<TPool>();
export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: TPoolOption) => void }> = ({
disabled,
onSelect,
}) => {
const [value, setValue] = useState<TPoolOption>('balance');
const {
userBalance: { tokenAllocation, balance, fetchBalance, fetchTokenAllocation },
currency,
} = useContext(ClientContext);
useEffect(() => {
(async () => {
await fetchBalance();
await fetchTokenAllocation();
})();
}, []);
useEffect(() => {
onSelect(value);
}, [value]);
const handleChange = (e: SelectChangeEvent) => setValue(e.target.value as TPoolOption);
return (
<Select label="Token Pool" value={value}>
<ListItem>
<ListItemText primary="Balance" secondary="123 nymt" />
</ListItem>
</Select>
<FormControl fullWidth>
<InputLabel>Token pool</InputLabel>
<Select
label="Token Pool"
onChange={handleChange}
value={value}
disabled={disabled}
renderValue={(val) => <Typography sx={{ textTransform: 'capitalize' }}>{val}</Typography>}
>
<MenuItem value="balance">
<ListItemText
primary="Balance"
secondary={`${balance?.printable_balance}`}
secondaryTypographyProps={{ sx: { textTransform: 'uppercase' } }}
/>
</MenuItem>
<MenuItem value="locked">
{tokenAllocation && (
<ListItemText
primary="Locked"
secondary={`${+tokenAllocation.locked + +tokenAllocation.spendable} ${currency?.major}`}
/>
)}
</MenuItem>
</Select>
</FormControl>
);
};
+1
View File
@@ -14,3 +14,4 @@ export * from './NetworkSelector';
export * from './ClientAddress';
export * from './InfoToolTip';
export * from './Title';
export * from './TokenPoolSelector';
+13 -6
View File
@@ -1,10 +1,12 @@
import React, { createContext, useEffect, useMemo, useState } from 'react';
import React, { useMemo, createContext, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useSnackbar } from 'notistack';
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
import { config } from '../../config';
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signOut } from '../requests';
import { currencyMap } from '../utils';
import { Console } from '../utils/console';
export const { ADMIN_ADDRESS, IS_DEV_MODE } = config;
@@ -53,13 +55,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
const userBalance = useGetBalance(clientDetails?.client_address);
const history = useHistory();
const { enqueueSnackbar } = useSnackbar();
const loadAccount = async (n: Network) => {
try {
const client = await selectNetwork(n);
setClientDetails(client);
} catch (e) {
console.error(e);
enqueueSnackbar('Error loading account', { variant: 'error' });
Console.error(e as string);
} finally {
setCurrency(currencyMap(n));
}
@@ -71,7 +75,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
const mixnode = await getMixnodeBondDetails();
setMixnodeDetails(mixnode);
} catch (e) {
console.log(e);
Console.error(e as string);
}
};
@@ -80,7 +84,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
if (network) {
await loadAccount(network);
await getBondDetails();
userBalance.fetchBalance();
await userBalance.fetchBalance();
}
};
refreshAccount();
@@ -92,20 +96,23 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
await signInWithMnemonic(mnemonic || '');
await getBondDetails();
setNetwork('MAINNET');
history.push('/balance');
} catch (e) {
setIsLoading(false);
setError(e as string);
} finally {
history.push('/balance');
}
};
const logOut = async () => {
userBalance.clearAll();
setClientDetails(undefined);
setNetwork(undefined);
setError(undefined);
setIsLoading(false);
userBalance.clearAll();
setMixnodeDetails(undefined);
await signOut();
enqueueSnackbar('Successfully logged out', { variant: 'success' });
};
const handleShowAdmin = () => setShowAdmin((show) => !show);
+18 -8
View File
@@ -1,45 +1,55 @@
import { useCallback, useContext, useEffect, useState } from 'react';
import { Console } from '../utils/console';
import { ClientContext } from '../context/main';
import { checkGatewayOwnership, checkMixnodeOwnership } from '../requests';
import { checkGatewayOwnership, checkMixnodeOwnership, getVestingPledgeInfo } from '../requests';
import { EnumNodeType, TNodeOwnership } from '../types';
const initial = {
hasOwnership: false,
nodeType: undefined,
vestingPledge: undefined,
};
export const useCheckOwnership = () => {
const { clientDetails } = useContext(ClientContext);
const [ownership, setOwnership] = useState<TNodeOwnership>(initial);
const [isLoading, setIsLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string>();
const checkOwnership = useCallback(async () => {
const status = {} as TNodeOwnership;
setIsLoading(true);
try {
const ownsMixnode = await checkMixnodeOwnership();
const ownsGateway = await checkGatewayOwnership();
const [ownsMixnode, ownsGateway] = await Promise.all([checkMixnodeOwnership(), checkGatewayOwnership()]);
if (ownsMixnode) {
status.hasOwnership = true;
status.nodeType = EnumNodeType.mixnode;
status.vestingPledge = await getVestingPledgeInfo({
address: clientDetails?.client_address!,
type: EnumNodeType.mixnode,
});
}
if (ownsGateway) {
status.hasOwnership = true;
status.nodeType = EnumNodeType.gateway;
status.vestingPledge = await getVestingPledgeInfo({
address: clientDetails?.client_address!,
type: EnumNodeType.gateway,
});
}
setOwnership(status);
} catch (e) {
Console.error(e as string);
setError(e as string);
setIsLoading(false);
setOwnership(initial);
} finally {
setIsLoading(false);
}
}, []);
}, [clientDetails]);
useEffect(() => {
checkOwnership();
+7 -6
View File
@@ -11,6 +11,7 @@ import {
getCurrentVestingPeriod,
getVestingAccountInfo,
} from '../requests';
import { Console } from '../utils/console';
type TTokenAllocation = {
[key in 'vesting' | 'vested' | 'locked' | 'spendable']: Coin['amount'];
@@ -59,8 +60,8 @@ export const useGetBalance = (address?: string): TUseuserBalance => {
getOriginalVesting(address),
getVestingCoins(address),
getVestedCoins(address),
getLockedCoins(address),
getSpendableCoins(address),
getLockedCoins(),
getSpendableCoins(),
getCurrentVestingPeriod(address),
getVestingAccountInfo(address),
]);
@@ -76,7 +77,7 @@ export const useGetBalance = (address?: string): TUseuserBalance => {
} catch (e) {
clearTokenAllocation();
clearOriginalVesting();
console.error(e);
Console.error(e as string);
}
}
setIsLoading(false);
@@ -103,10 +104,10 @@ export const useGetBalance = (address?: string): TUseuserBalance => {
clearOriginalVesting();
};
const handleRefresh = (addr?: string) => {
const handleRefresh = async (addr?: string) => {
if (addr) {
fetchBalance();
fetchTokenAllocation();
await fetchBalance();
await fetchTokenAllocation();
} else {
clearAll();
}
+2 -2
View File
@@ -1,4 +1,4 @@
import React, { useContext, useLayoutEffect } from 'react';
import React, { useContext, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { ErrorBoundary } from 'react-error-boundary';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -14,7 +14,7 @@ import { maximizeWindow } from './utils';
const App = () => {
const { clientDetails } = useContext(ClientContext);
useLayoutEffect(() => {
useEffect(() => {
maximizeWindow();
}, []);
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react';
import { NymLogo } from '@nymproject/react';
import { Box, Container } from '@mui/material';
import { AppBar, Nav } from '../components';
import { NymLogo } from '@nymproject/react';
export const ApplicationLayout: React.FC = ({ children }) => (
<Box
-1
View File
@@ -19,7 +19,6 @@ const AdminForm: React.FC<{
const onSubmit = async (data: TauriContractStateParams) => {
await setContractParams(data);
console.log(data);
onCancel();
};
@@ -45,7 +45,7 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc
tooltipText="End of vesting schedule"
/>
</svg>
{nextPeriod && (
{!!nextPeriod && (
<Typography variant="caption" sx={{ color: 'grey.500', position: 'absolute', top: 15, left: 0 }}>
Next vesting period: {format(new Date(nextPeriod * 1000), 'HH:mm do MMM yyyy')}
</Typography>
+3 -2
View File
@@ -20,6 +20,7 @@ import { ClientContext } from '../../context/main';
import { withdrawVestedCoins } from '../../requests';
import { Period } from '../../types';
import { VestingTimeline } from './components/vesting-timeline';
import { Console } from '../../utils/console';
const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [
{ title: 'Locked', align: 'left' },
@@ -120,9 +121,9 @@ const TokenTransfer = () => {
};
export const VestingCard = () => {
const { userBalance } = useContext(ClientContext);
const [isLoading, setIsLoading] = useState(false);
const { userBalance } = useContext(ClientContext);
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const refreshBalances = async () => {
@@ -165,7 +166,7 @@ export const VestingCard = () => {
preventDuplicate: true,
});
} catch (e) {
console.log(e);
Console.error(e as string);
enqueueSnackbar('Token transfer failed. You may not have any transferable tokens at this time', {
variant: 'error',
preventDuplicate: true,
+27 -12
View File
@@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React, { useContext, useEffect } from 'react';
import {
Box,
Button,
@@ -12,17 +12,17 @@ import {
} from '@mui/material';
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { EnumNodeType } from '../../types/global';
import { NodeTypeSelector } from '../../components/NodeTypeSelector';
import { bond, majorToMinor } from '../../requests';
import { bond, vestingBond, majorToMinor } from '../../requests';
import { validationSchema } from './validationSchema';
import { Gateway, MixNode } from '../../types';
import { Gateway, MixNode, TBondArgs, EnumNodeType } from '../../types';
import { ClientContext } from '../../context/main';
import { Fee } from '../../components';
import { Fee, TokenPoolSelector } from '../../components';
type TBondFormFields = {
withAdvancedOptions: boolean;
nodeType: EnumNodeType;
tokenPool: string;
ownerSignature: string;
identityKey: string;
sphinxKey: string;
@@ -40,6 +40,7 @@ type TBondFormFields = {
const defaultValues = {
withAdvancedOptions: false,
nodeType: EnumNodeType.mixnode,
tokenPool: 'balance',
identityKey: '',
sphinxKey: '',
ownerSignature: '',
@@ -88,25 +89,33 @@ export const BondForm = ({
handleSubmit,
setValue,
watch,
reset,
formState: { errors, isSubmitting },
} = useForm<TBondFormFields>({
resolver: yupResolver(validationSchema),
defaultValues,
});
const { userBalance, currency, getBondDetails } = useContext(ClientContext);
const { userBalance, currency, clientDetails } = useContext(ClientContext);
useEffect(() => {
reset();
}, [clientDetails]);
const watchNodeType = watch('nodeType', defaultValues.nodeType);
const watchAdvancedOptions = watch('withAdvancedOptions', defaultValues.withAdvancedOptions);
const onSubmit = async (data: TBondFormFields) => {
const onSubmit = async (data: TBondFormFields, cb: (data: TBondArgs) => Promise<void>) => {
const formattedData = formatData(data);
const pledge = await majorToMinor(data.amount);
await bond({ type: data.nodeType, ownerSignature: data.ownerSignature, data: formattedData, pledge })
await cb({ type: data.nodeType, ownerSignature: data.ownerSignature, data: formattedData, pledge })
.then(async () => {
await getBondDetails();
userBalance.fetchBalance();
if (data.tokenPool === 'balance') {
await userBalance.fetchBalance();
} else {
await userBalance.fetchTokenAllocation();
}
onSuccess({ address: data.identityKey, amount: data.amount });
})
.catch((e) => {
@@ -174,6 +183,12 @@ export const BondForm = ({
/>
</Grid>
{userBalance.originalVesting && (
<Grid item xs={12} sm={6}>
<TokenPoolSelector onSelect={(pool) => setValue('tokenPool', pool)} disabled={disabled} />
</Grid>
)}
<Grid item xs={12} sm={6}>
<TextField
{...register('amount')}
@@ -348,7 +363,7 @@ export const BondForm = ({
)}
</>
)}
<Grid item>
<Grid item xs={12}>
{!disabled ? <Fee feeType={EnumNodeType.mixnode ? 'BondMixnode' : 'BondGateway'} /> : <div />}
</Grid>
</Grid>
@@ -369,7 +384,7 @@ export const BondForm = ({
type="submit"
data-testid="submit-button"
disableElevation
onClick={handleSubmit(onSubmit)}
onClick={handleSubmit((data) => onSubmit(data, data.tokenPool === 'balance' ? bond : vestingBond))}
endIcon={isSubmitting && <CircularProgress size={20} />}
size="large"
>
+8 -1
View File
@@ -2,15 +2,22 @@ import React, { useContext } from 'react';
import { Box } from '@mui/material';
import { SuccessReponse, TransactionDetails } from '../../components';
import { ClientContext } from '../../context/main';
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
const { userBalance, currency } = useContext(ClientContext);
const { ownership } = useCheckOwnership();
return (
<>
<SuccessReponse
title="Bonding Complete"
subtitle="Successfully bonded to node with following details"
caption={`Your current balance is: ${userBalance.balance?.printable_balance}`}
caption={
ownership.vestingPledge
? `Your current locked balance is: ${userBalance.tokenAllocation?.locked}${currency?.major}`
: `Your current balance is: ${userBalance.balance?.printable_balance}`
}
/>
{details && (
<Box sx={{ mt: 2 }}>
+23 -14
View File
@@ -1,12 +1,12 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Alert, Box, Button, CircularProgress } from '@mui/material';
import { useSnackbar } from 'notistack';
import { BondForm } from './BondForm';
import { SuccessView } from './SuccessView';
import { NymCard } from '../../components';
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
import { unbond } from '../../requests';
import { unbond, vestingUnbond } from '../../requests';
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
import { ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
export const Bond = () => {
@@ -14,18 +14,17 @@ export const Bond = () => {
const [error, setError] = useState<string>();
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>();
const { checkOwnership, ownership } = useCheckOwnership();
const { userBalance, getBondDetails } = useContext(ClientContext);
const { checkOwnership, ownership, isLoading } = useCheckOwnership();
const { enqueueSnackbar } = useSnackbar();
useEffect(() => {
if (status === EnumRequestStatus.initial) {
const initialiseForm = async () => {
await checkOwnership();
setStatus(EnumRequestStatus.initial);
};
initialiseForm();
}
}, [status]);
}, [status, checkOwnership]);
return (
<PageLayout>
@@ -36,7 +35,7 @@ export const Bond = () => {
</Box>
)}
{ownership?.hasOwnership && (
<Box sx={{ px: 3 }}>
<Box sx={{ px: 3, mb: 3 }}>
<Alert
severity="info"
action={
@@ -44,10 +43,17 @@ export const Bond = () => {
disabled={status === EnumRequestStatus.loading}
onClick={async () => {
setStatus(EnumRequestStatus.loading);
await unbond(ownership.nodeType!);
await getBondDetails();
await userBalance.fetchBalance();
setStatus(EnumRequestStatus.initial);
try {
if (ownership.vestingPledge) {
await vestingUnbond(ownership.nodeType!);
} else {
await unbond(ownership.nodeType!);
}
} catch (e) {
enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
} finally {
setStatus(EnumRequestStatus.initial);
}
}}
data-testid="unBond"
color="inherit"
@@ -71,7 +77,7 @@ export const Bond = () => {
<CircularProgress size={48} />
</Box>
)}
{status === EnumRequestStatus.initial && (
{status === EnumRequestStatus.initial && !ownership.hasOwnership && !isLoading && (
<BondForm
onError={(e?: string) => {
setError(e);
@@ -107,8 +113,11 @@ export const Bond = () => {
<Button
onClick={() => {
setStatus(EnumRequestStatus.initial);
checkOwnership();
}}
variant="contained"
color="primary"
size="large"
disableElevation
>
{status === EnumRequestStatus.error ? 'Again?' : 'Finish'}
</Button>
+22 -5
View File
@@ -1,6 +1,7 @@
import * as Yup from 'yup';
import {
checkHasEnoughFunds,
checkHasEnoughLockedTokens,
isValidHostname,
validateAmount,
validateKey,
@@ -13,36 +14,48 @@ export const validationSchema = Yup.object().shape({
identityKey: Yup.string()
.required('An indentity key is required')
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
sphinxKey: Yup.string()
.required('A sphinx key is required')
.test('valid-sphinx-key', 'A valid sphinx key is required', (value) => validateKey(value || '', 32)),
ownerSignature: Yup.string()
.required('Signature is required')
.test('valid-signature', 'A valid signature is required', (value) => validateKey(value || '', 64)),
profitMarginPercent: Yup.number().required('Profit Percentage is required').min(0).max(100),
amount: Yup.string()
.required('An amount is required')
.test('valid-amount', 'Pledge error', async function (value) {
.test('valid-amount', 'Pledge error', async function isValidAmount(value) {
const isValid = await validateAmount(value || '', '100000000');
if (!isValid) {
return this.createError({ message: 'A valid amount is required (min 100)' });
}
const hasEnough = await checkHasEnoughFunds(value || '');
if (!hasEnough) {
const hasEnoughBalance = await checkHasEnoughFunds(value || '');
const hasEnoughLocked = await checkHasEnoughLockedTokens(value || '');
if (this.parent.tokenPool === 'balance' && !hasEnoughBalance) {
return this.createError({ message: 'Not enough funds in wallet' });
}
if (this.parent.tokenPool === 'locked' && !hasEnoughLocked) {
return this.createError({ message: 'Not enough locked tokens' });
}
return true;
}),
host: Yup.string()
.required('A host is required')
.test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)),
version: Yup.string()
.required('A version is required')
.test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)),
location: Yup.lazy((locationValue) => {
if (locationValue) {
location: Yup.lazy((value) => {
if (value) {
return Yup.string()
.required('A location is required')
.test('valid-location', 'A valid version is required', (locationValueTest) =>
@@ -51,15 +64,19 @@ export const validationSchema = Yup.object().shape({
}
return Yup.mixed().notRequired();
}),
mixPort: Yup.number()
.required('A mixport is required')
.test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)),
verlocPort: Yup.number()
.required('A verloc port is required')
.test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)),
httpApiPort: Yup.number()
.required('A http-api port is required')
.test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)),
clientsPort: Yup.number()
.required('A clients port is required')
.test('valid-clients', 'A valid clients port is required', (value) => (value ? validateRawPort(value) : false)),
+36 -25
View File
@@ -1,24 +1,26 @@
import React, { useContext } from 'react';
import React, { useEffect, useContext } from 'react';
import { Box, Button, CircularProgress, FormControl, Grid, InputAdornment, TextField } from '@mui/material';
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { EnumNodeType } from '../../types';
import { DelegationResult, EnumNodeType, TDelegateArgs } from '../../types';
import { validationSchema } from './validationSchema';
import { ClientContext } from '../../context/main';
import { delegate, majorToMinor } from '../../requests';
import { checkHasEnoughFunds } from '../../utils';
import { Fee } from '../../components';
import { delegate, majorToMinor, vestingDelegateToMixnode } from '../../requests';
import { Fee, TokenPoolSelector } from '../../components';
import { Console } from '../../utils/console';
type TDelegateForm = {
nodeType: EnumNodeType;
identity: string;
amount: string;
tokenPool: string;
type: EnumNodeType;
};
const defaultValues: TDelegateForm = {
nodeType: EnumNodeType.mixnode,
identity: '',
amount: '',
tokenPool: 'balance',
type: EnumNodeType.mixnode,
};
export const DelegateForm = ({
@@ -31,37 +33,38 @@ export const DelegateForm = ({
const {
register,
handleSubmit,
setError,
setValue,
reset,
formState: { errors, isSubmitting },
} = useForm<TDelegateForm>({
defaultValues,
resolver: yupResolver(validationSchema),
});
const { userBalance, currency } = useContext(ClientContext);
const { userBalance, currency, clientDetails } = useContext(ClientContext);
const onSubmit = async (data: TDelegateForm) => {
const hasEnoughFunds = await checkHasEnoughFunds(data.amount);
if (!hasEnoughFunds) {
setError('amount', {
message: 'Not enough funds in wallet',
});
return;
}
useEffect(() => {
reset();
}, [clientDetails]);
const onSubmit = async (data: TDelegateForm, cb: (data: TDelegateArgs) => Promise<DelegationResult>) => {
const amount = await majorToMinor(data.amount);
await delegate({
type: data.nodeType,
await cb({
type: data.type,
identity: data.identity,
amount,
})
.then((res) => {
.then(async (res) => {
if (data.tokenPool === 'balance') {
await userBalance.fetchBalance();
} else {
await userBalance.fetchTokenAllocation();
}
onSuccess({ amount: data.amount, address: res.target_address });
userBalance.fetchBalance();
})
.catch((e) => {
console.log(e);
Console.error(e as string);
onError(e);
});
};
@@ -84,7 +87,13 @@ export const DelegateForm = ({
/>
</Grid>
<Grid item xs={12}>
{userBalance.originalVesting && (
<Grid item xs={6}>
<TokenPoolSelector onSelect={(pool) => setValue('tokenPool', pool)} disabled={false} />
</Grid>
)}
<Grid item xs={6}>
<TextField
{...register('amount')}
required
@@ -100,7 +109,7 @@ export const DelegateForm = ({
}}
/>
</Grid>
<Grid item>
<Grid item xs={12}>
<Fee feeType="DelegateToMixnode" />
</Grid>
</Grid>
@@ -115,7 +124,9 @@ export const DelegateForm = ({
}}
>
<Button
onClick={handleSubmit(onSubmit)}
onClick={handleSubmit((data) =>
onSubmit(data, data.tokenPool === 'balance' ? delegate : vestingDelegateToMixnode),
)}
disabled={isSubmitting}
data-testid="delegate-button"
variant="contained"
+10 -3
View File
@@ -1,5 +1,5 @@
import React, { useContext } from 'react';
import { Box } from '@mui/material';
import { Box, Stack, Typography } from '@mui/material';
import { SuccessReponse, TransactionDetails } from '../../components';
import { ClientContext } from '../../context/main';
@@ -8,8 +8,15 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
return (
<>
<SuccessReponse
title="Delegation Complete"
subtitle="Successfully delegated to node with following details"
title="Delegation Request Complete"
subtitle={
<Stack alignItems="center" spacing={1}>
<Typography>Successfully requested delegation to node </Typography>
<Typography sx={{ textDecoration: 'underline', fontWeight: 600 }}>
Note it may take up to one hour to take effect
</Typography>
</Stack>
}
caption={`Your current balance is: ${userBalance.balance?.printable_balance}`}
/>
{details && (
+3 -2
View File
@@ -53,14 +53,15 @@ export const Delegate = () => {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
bgcolor: 'grey.100',
p: 3,
pt: 0,
}}
>
<Button
data-testid="finish-button"
size="large"
disableElevation
variant="contained"
onClick={() => {
setStatus(EnumRequestStatus.initial);
}}
@@ -1,5 +1,5 @@
import * as Yup from 'yup';
import { validateAmount, validateKey } from '../../utils';
import { checkHasEnoughFunds, checkHasEnoughLockedTokens, validateAmount, validateKey } from '../../utils';
export const validationSchema = Yup.object().shape({
identity: Yup.string()
@@ -9,7 +9,27 @@ export const validationSchema = Yup.object().shape({
'A valid identity key is required e.g. 824WyExLUWvLE2mpSHBatN4AoByuLzfnHFeHWiBYzg4z',
(value) => (value ? validateKey(value, 32) : false),
),
amount: Yup.string()
.required()
.test('valid-amount-key', 'A valid amount is required', (value) => (value ? validateAmount(value, '0') : false)),
.test('valid-amount', 'A valid amount is required', async function isValidAmount(value) {
const isValid = await validateAmount(value || '', '0');
if (!isValid) {
return this.createError({ message: 'A valid amount is required' });
}
const hasEnoughBalance = await checkHasEnoughFunds(value || '');
const hasEnoughLocked = await checkHasEnoughLockedTokens(value || '');
if (this.parent.tokenPool === 'balance' && !hasEnoughBalance) {
return this.createError({ message: 'Not enough funds in wallet' });
}
if (this.parent.tokenPool === 'locked' && !hasEnoughLocked) {
return this.createError({ message: 'Not enough locked tokens' });
}
return true;
}),
});
+1 -5
View File
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
import { Grid, InputAdornment, TextField } from '@mui/material';
import { useFormContext } from 'react-hook-form';
import { ClientContext } from '../../context/main';
import { Fee, ClientAddress } from '../../components';
import { Fee } from '../../components';
export const SendForm = () => {
const {
@@ -13,10 +13,6 @@ export const SendForm = () => {
return (
<Grid container spacing={3}>
<Grid item xs={12}>
<ClientAddress withCopy />
</Grid>
<Grid item xs={12}>
<TextField
{...register('to')}
+3 -1
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import React, { useEffect, useContext, useState } from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
@@ -10,6 +11,7 @@ import { validationSchema } from './validationSchema';
import { TauriTxResult, TransactionDetails } from '../../types';
import { getGasFee, majorToMinor, send } from '../../requests';
import { checkHasEnoughFunds } from '../../utils';
import { Console } from '../../utils/console';
const defaultValues = {
amount: '',
@@ -97,7 +99,7 @@ export const SendWizard = () => {
.catch((e) => {
setRequestError(e);
setIsLoading(false);
console.error(e);
Console.error(e);
});
};
+8 -6
View File
@@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { Alert, Box, Dialog } from '@mui/material';
import { NymCard } from '../../components';
import { ClientContext } from '../../context/main';
@@ -15,11 +15,15 @@ const tabs = ['Profile', 'System variables', 'Node stats'];
export const Settings = () => {
const [selectedTab, setSelectedTab] = useState(0);
const { mixnodeDetails, showSettings, handleShowSettings, getBondDetails } = useContext(ClientContext);
const { mixnodeDetails, showSettings, getBondDetails, handleShowSettings } = useContext(ClientContext);
const { status, saturation, rewardEstimation, inclusionProbability } = useSettingsState(showSettings);
const handleTabChange = (_: React.SyntheticEvent, newTab: number) => setSelectedTab(newTab);
useEffect(() => {
getBondDetails();
}, [showSettings, selectedTab]);
return showSettings ? (
<Dialog open onClose={handleShowSettings} maxWidth="md" fullWidth>
<NymCard
@@ -39,13 +43,11 @@ export const Settings = () => {
You do not currently have a node running
</Alert>
)}
{selectedTab === 0 && mixnodeDetails && <Profile />}
{selectedTab === 1 && mixnodeDetails && (
{selectedTab === 0 && <Profile />}
{selectedTab === 1 && (
<SystemVariables
mixnodeDetails={mixnodeDetails.mix_node}
saturation={saturation}
rewardEstimation={rewardEstimation}
onUpdate={getBondDetails}
inclusionProbability={inclusionProbability}
/>
)}
@@ -4,6 +4,9 @@ import { ClientContext } from '../../context/main';
export const Profile = () => {
const { mixnodeDetails } = useContext(ClientContext);
if (!mixnodeDetails) return null;
return (
<>
<Box sx={{ p: 3 }}>
@@ -1,17 +1,16 @@
/* eslint-disable no-nested-ternary */
import React, { useContext, useState } from 'react';
import { Box, Button, CircularProgress, Grid, LinearProgress, Stack, TextField, Typography } from '@mui/material';
import { PercentOutlined } from '@mui/icons-material';
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { Fee, InfoTooltip } from '../../components';
import { InclusionProbabilityResponse, TMixnodeBondDetails } from '../../types';
import { validationSchema } from './validationSchema';
import { updateMixnode } from '../../requests';
import { Fee, InfoTooltip } from '../../components';
import { InclusionProbabilityResponse } from '../../types';
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
import { updateMixnode, vestingUpdateMixnode } from '../../requests';
import { ClientContext } from '../../context/main';
type TFormData = {
profitMarginPercent: string;
};
import { Console } from '../../utils/console';
const DataField = ({ title, info, Indicator }: { title: string; info: string; Indicator: React.ReactElement }) => (
<Grid container justifyContent="space-between">
@@ -49,19 +48,17 @@ const PercentIndicator = ({ value, warning }: { value: number; warning?: boolean
);
export const SystemVariables = ({
mixnodeDetails,
saturation,
rewardEstimation,
inclusionProbability,
onUpdate,
}: {
mixnodeDetails: TMixnodeBondDetails['mix_node'];
saturation: number;
rewardEstimation: number;
inclusionProbability: InclusionProbabilityResponse;
onUpdate: () => void;
}) => {
const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>();
const { currency, mixnodeDetails } = useContext(ClientContext);
const { ownership } = useCheckOwnership();
const {
register,
@@ -69,23 +66,26 @@ export const SystemVariables = ({
formState: { errors, isSubmitting },
} = useForm({
resolver: yupResolver(validationSchema),
defaultValues: { profitMarginPercent: mixnodeDetails.profit_margin_percent.toString() },
defaultValues: { profitMarginPercent: mixnodeDetails?.mix_node.profit_margin_percent },
});
const { userBalance, currency } = useContext(ClientContext);
const onSubmit = async (data: TFormData) => {
try {
await updateMixnode({ profitMarginPercent: +data.profitMarginPercent });
await userBalance.fetchBalance();
onUpdate();
setNodeUpdateResponse('success');
} catch (e) {
setNodeUpdateResponse('failed');
console.error(e);
const onSubmit = async (
profitMarginPercent: number | undefined,
cb: (profitMarginPercent: number) => Promise<any>,
) => {
if (profitMarginPercent) {
try {
await cb(profitMarginPercent);
setNodeUpdateResponse('success');
} catch (e) {
setNodeUpdateResponse('failed');
Console.log(e as string);
}
}
};
if (!mixnodeDetails) return null;
return (
<>
<Box sx={{ p: 3 }}>
@@ -149,7 +149,9 @@ export const SystemVariables = ({
<Button
variant="contained"
color="primary"
onClick={handleSubmit(onSubmit)}
onClick={handleSubmit((data) =>
onSubmit(data.profitMarginPercent, ownership.vestingPledge ? vestingUpdateMixnode : updateMixnode),
)}
disableElevation
endIcon={isSubmitting && <CircularProgress size={20} />}
disabled={Object.keys(errors).length > 0 || isSubmitting}
@@ -61,10 +61,12 @@ export const useSettingsState = (shouldUpdate: boolean) => {
useEffect(() => {
if (shouldUpdate && mixnodeDetails?.mix_node.identity_key) {
getStatus(mixnodeDetails?.mix_node.identity_key);
getStakeSaturation(mixnodeDetails?.mix_node.identity_key);
getRewardEstimation(mixnodeDetails?.mix_node.identity_key);
getMixnodeInclusionProbability(mixnodeDetails?.mix_node.identity_key);
(async () => {
await getStatus(mixnodeDetails?.mix_node.identity_key);
await getStakeSaturation(mixnodeDetails?.mix_node.identity_key);
await getRewardEstimation(mixnodeDetails?.mix_node.identity_key);
await getMixnodeInclusionProbability(mixnodeDetails?.mix_node.identity_key);
})();
} else {
reset();
}
@@ -1,11 +1,21 @@
import React, { useContext, useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Autocomplete, Box, Button, CircularProgress, FormControl, Grid, TextField } from '@mui/material';
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import {
ListItem,
ListItemText,
Box,
Autocomplete,
Button,
CircularProgress,
FormControl,
Grid,
TextField,
} from '@mui/material';
import { yupResolver } from '@hookform/resolvers/yup';
import { format } from 'date-fns';
import { validationSchema } from './validationSchema';
import { EnumNodeType, TDelegation, TFee } from '../../types';
import { ClientContext } from '../../context/main';
import { undelegate } from '../../requests';
import { EnumNodeType, PendingUndelegate, TDelegation } from '../../types';
import { undelegate, vestingUnelegateFromMixnode } from '../../requests';
import { Fee } from '../../components';
type TFormData = {
@@ -20,11 +30,14 @@ const defaultValues = {
export const UndelegateForm = ({
delegations,
pendingUndelegations,
currentEndEpoch,
onError,
onSuccess,
}: {
fees: TFee;
delegations?: TDelegation[];
pendingUndelegations?: PendingUndelegate[];
currentEndEpoch?: BigInt;
onError: (message?: string) => void;
onSuccess: (message?: string) => void;
}) => {
@@ -32,30 +45,33 @@ export const UndelegateForm = ({
control,
handleSubmit,
setValue,
watch,
formState: { errors, isSubmitting },
} = useForm<TFormData>({
defaultValues,
resolver: yupResolver(validationSchema),
});
const watchNodeType = watch('nodeType');
useEffect(() => {
setValue('identity', '');
}, [watchNodeType]);
const { userBalance } = useContext(ClientContext);
const onSubmit = async (data: TFormData) => {
await undelegate({
type: data.nodeType,
identity: data.identity,
})
.then(async (res) => {
onSuccess(`Successfully undelegated from ${res.target_address}`);
userBalance.fetchBalance();
})
.catch((e) => onError(e));
let res;
try {
res = await undelegate({
type: data.nodeType,
identity: data.identity,
});
if (!res) {
res = await vestingUnelegateFromMixnode(data.identity);
}
if (!res) {
onError('An error occurred when undelegating');
return;
}
onSuccess(`Successfully requested undelegation from ${res.target_address}`);
} catch (e) {
onError(e as string);
}
};
return (
@@ -69,8 +85,30 @@ export const UndelegateForm = ({
render={() => (
<Autocomplete
disabled={isSubmitting}
onChange={(_, value) => setValue('identity', value || '')}
getOptionDisabled={(opt) => pendingUndelegations?.some((item) => item.mix_identity === opt) || false}
options={delegations?.map((d) => d.node_identity) || []}
renderOption={(props, opt) => (
<ListItem
{...props}
onClick={(e: React.MouseEvent<HTMLLIElement>) => {
setValue('identity', opt);
props.onClick!(e);
}}
disablePadding
disableGutters
>
<ListItemText
primary={opt}
secondary={
pendingUndelegations?.some((item) => item.mix_identity === opt)
? `Pending - Expected time of completion: ${
currentEndEpoch ? format(new Date(Number(currentEndEpoch) * 1000), 'HH:mm') : 'N/A'
}`
: undefined
}
/>
</ListItem>
)}
renderInput={(params) => (
<TextField
{...params}
+20 -15
View File
@@ -2,17 +2,19 @@ import React, { useContext, useEffect, useState } from 'react';
import { Alert, AlertTitle, Box, Button, CircularProgress } from '@mui/material';
import { EnumRequestStatus, NymCard, RequestStatus } from '../../components';
import { UndelegateForm } from './UndelegateForm';
import { getGasFee, getReverseMixDelegations } from '../../requests';
import { TFee, TPagedDelegations } from '../../types';
import { getCurrentEpoch, getPendingDelegations, getReverseMixDelegations } from '../../requests';
import { Epoch, PendingUndelegate, TPagedDelegations } from '../../types';
import { ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
import { removeObjectDuplicates } from '../../utils';
export const Undelegate = () => {
const [message, setMessage] = useState<string>();
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial);
const [isLoading, setIsLoading] = useState(true);
const [fees, setFees] = useState<TFee>();
const [pagedDelegations, setPagesDelegations] = useState<TPagedDelegations>();
const [pendingUndelegations, setPendingUndelegations] = useState<PendingUndelegate[]>();
const [currentEndEpoch, setCurrentEndEpoch] = useState<Epoch['end']>();
const { clientDetails } = useContext(ClientContext);
@@ -21,16 +23,19 @@ export const Undelegate = () => {
setIsLoading(true);
try {
const [mixnodeFee, mixnodeDelegations] = await Promise.all([
getGasFee('UndelegateFromMixnode'),
getReverseMixDelegations(),
]);
const mixnodeDelegations = await getReverseMixDelegations();
const pendingEvents = await getPendingDelegations();
const pendingUndelegationEvents = pendingEvents
.filter((evt): evt is { Undelegate: PendingUndelegate } => 'Undelegate' in evt)
.map((e) => ({ ...e.Undelegate }));
const epoch = await getCurrentEpoch();
setFees({
mixnode: mixnodeFee,
setCurrentEndEpoch(epoch.end);
setPendingUndelegations(pendingUndelegationEvents);
setPagesDelegations({
...mixnodeDelegations,
delegations: removeObjectDuplicates(mixnodeDelegations.delegations, 'node_identity'),
});
setPagesDelegations(mixnodeDelegations);
} catch (e) {
setStatus(EnumRequestStatus.error);
setMessage(e as string);
@@ -58,10 +63,11 @@ export const Undelegate = () => {
</Box>
)}
<>
{status === EnumRequestStatus.initial && fees && pagedDelegations && (
{status === EnumRequestStatus.initial && pagedDelegations && (
<UndelegateForm
fees={fees}
delegations={pagedDelegations?.delegations}
pendingUndelegations={pendingUndelegations}
currentEndEpoch={currentEndEpoch}
onError={(m) => {
setMessage(m);
setStatus(EnumRequestStatus.error);
@@ -83,8 +89,7 @@ export const Undelegate = () => {
}
Success={
<Alert severity="success">
{' '}
<AlertTitle data-testid="undelegate-success">Undelegation complete</AlertTitle>
<AlertTitle data-testid="undelegate-success">Undelegation request complete</AlertTitle>
{message}
</Alert>
}
@@ -1,31 +1,30 @@
/* eslint-disable react/no-unused-prop-types */
/* eslint-disable no-unused-vars */
import React, { useEffect, useState } from 'react';
import { Alert, Button, Card, CardActions, CardContent, CardHeader, Stack, Typography } from '@mui/material';
import { createAccount } from '../../requests';
import { TCreateAccount } from '../../types';
import { createMnemonic } from '../../requests';
import { CopyToClipboard } from '../../components';
import { TPages } from './types';
export const CreateAccountContent: React.FC<{ page: TPages; showSignIn: () => void }> = ({ page, showSignIn }) => {
const [accountDetails, setAccountDetails] = useState<TCreateAccount>();
const [mnemonic, setMnemonic] = useState<string>();
const [error, setError] = useState<Error>();
const handleCreateAccount = async () => {
const handleCreateMnemonic = async () => {
setError(undefined);
try {
const account = await createAccount();
setAccountDetails(account);
const newMnemonic = await createMnemonic();
setMnemonic(newMnemonic);
} catch (e: any) {
setError(e);
}
};
useEffect(() => {
handleCreateAccount();
handleCreateMnemonic();
}, []);
return (
<Stack spacing={4} alignItems="center" sx={{ width: 700 }}>
<Stack spacing={4} alignItems="center" sx={{ width: 700 }} id={page}>
<Typography sx={{ color: 'common.white' }} variant="h4">
Congratulations
</Typography>
@@ -38,10 +37,10 @@ export const CreateAccountContent: React.FC<{ page: TPages; showSignIn: () => vo
<Card variant="outlined" sx={{ bgcolor: 'transparent', p: 2, borderColor: 'common.white' }}>
<CardHeader sx={{ color: 'common.white' }} title="Mnemonic" />
<CardContent sx={{ color: 'common.white' }} data-testid="mnemonic-phrase">
{accountDetails?.mnemonic}
{mnemonic}
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end' }}>
<CopyToClipboard text={accountDetails?.mnemonic || ''} light />
<CopyToClipboard text={mnemonic || ''} light />
</CardActions>
</Card>
{error && (
@@ -1,7 +1,7 @@
import React, { useContext, useState } from 'react';
import { NymLogo } from '@nymproject/react';
import { Alert, Button, CircularProgress, Grid, Stack, Typography } from '@mui/material';
import { ClientContext } from '../../context/main';
import { NymLogo } from '@nymproject/react';
export const SignInContent: React.FC = () => {
const [mnemonic] = useState<string>('');
@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import React, { useEffect, useState } from 'react';
import { LockOutlined } from '@mui/icons-material';
import { LinearProgress, Stack, Typography, Box } from '@mui/material';
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { useContext, useState } from 'react';
import { NymLogo } from '@nymproject/react';
import { CircularProgress, Stack, Box } from '@mui/material';
import { ExistingAccount, WelcomeContent } from './pages';
import { NymLogo } from '@nymproject/react';
import { TPages } from './types';
import { RenderPage } from './components';
import { CreateAccountContent } from './_legacy_create-account';
@@ -9,15 +9,28 @@ export const ExistingAccount: React.FC<{ page: TPages; onPrev: () => void }> = (
const [mnemonic, setMnemonic] = useState<string>('');
const { logIn, error } = useContext(ClientContext);
const handleSignIn = async (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
logIn(mnemonic);
const handleSignIn = async () => {
await logIn(mnemonic);
};
const handleSignInOnEnter = ({ key }: React.KeyboardEvent<HTMLDivElement>) => {
if (key.toLowerCase() === 'enter') {
logIn(mnemonic);
}
};
return (
<Stack spacing={2} sx={{ width: 400 }} alignItems="center">
<Subtitle subtitle="Enter your mnemonic from existing wallet" />
<TextField value={mnemonic} onChange={(e) => setMnemonic(e.target.value)} multiline rows={5} fullWidth />
<TextField
value={mnemonic}
onChange={(e) => setMnemonic(e.target.value)}
multiline
rows={5}
fullWidth
onKeyDown={handleSignInOnEnter}
/>
{error && (
<Alert severity="error" variant="outlined" data-testid="error" sx={{ color: 'error.light', width: '100%' }}>
{error}
+16 -4
View File
@@ -1,9 +1,21 @@
import { invoke } from '@tauri-apps/api';
import { Account, TCreateAccount } from '../types';
export const createAccount = async (): Promise<TCreateAccount> => invoke('create_new_account');
export const createAccount = async (): Promise<TCreateAccount> => {
const res: TCreateAccount = await invoke('create_new_account');
return res;
};
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> =>
invoke('connect_with_mnemonic', { mnemonic });
export const createMnemonic = async (): Promise<string> => {
const res: string = await invoke('create_new_mnemonic');
return res;
};
export const signOut = async () => invoke('logout');
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> => {
const res: Account = await invoke('connect_with_mnemonic', { mnemonic });
return res;
};
export const signOut = async (): Promise<void> => {
await invoke('logout');
};
+28 -19
View File
@@ -1,19 +1,14 @@
import { invoke } from '@tauri-apps/api';
import { Coin, DelegationResult, EnumNodeType, Gateway, MixNode, TauriTxResult } from '../types';
import { Console } from '../utils/console';
import { Coin, DelegationResult, EnumNodeType, TauriTxResult, TBondArgs } from '../types';
export const bond = async ({
type,
data,
pledge,
ownerSignature,
}: {
type: EnumNodeType;
data: MixNode | Gateway;
pledge: Coin;
ownerSignature: string;
}): Promise<any> => invoke(`bond_${type}`, { [type]: data, ownerSignature, pledge });
export const bond = async ({ type, data, pledge, ownerSignature }: TBondArgs): Promise<any> => {
await invoke(`bond_${type}`, { [type]: data, ownerSignature, pledge });
};
export const unbond = async (type: EnumNodeType) => invoke(`unbond_${type}`);
export const unbond = async (type: EnumNodeType): Promise<void> => {
await invoke(`unbond_${type}`);
};
export const delegate = async ({
type,
@@ -23,7 +18,10 @@ export const delegate = async ({
type: EnumNodeType;
identity: string;
amount: Coin;
}): Promise<DelegationResult> => invoke(`delegate_to_${type}`, { identity, amount });
}): Promise<DelegationResult> => {
const res: DelegationResult = await invoke(`delegate_to_${type}`, { identity, amount });
return res;
};
export const undelegate = async ({
type,
@@ -31,10 +29,21 @@ export const undelegate = async ({
}: {
type: EnumNodeType;
identity: string;
}): Promise<DelegationResult> => invoke(`undelegate_from_${type}`, { identity });
}): Promise<DelegationResult | undefined> => {
try {
const res: DelegationResult = await invoke(`undelegate_from_${type}`, { identity });
return res;
} catch (e) {
Console.log(e as string);
return undefined;
}
};
export const send = async (args: { amount: Coin; address: string; memo: string }): Promise<TauriTxResult> =>
invoke('send', args);
export const send = async (args: { amount: Coin; address: string; memo: string }): Promise<TauriTxResult> => {
const res: TauriTxResult = await invoke('send', args);
return res;
};
export const updateMixnode = async ({ profitMarginPercent }: { profitMarginPercent: number }) =>
invoke('update_mixnode', { profitMarginPercent });
export const updateMixnode = async (profitMarginPercent: number): Promise<void> => {
await invoke('update_mixnode', { profitMarginPercent });
};
+8 -2
View File
@@ -1,6 +1,12 @@
import { invoke } from '@tauri-apps/api';
import { Coin } from '../types';
export const minorToMajor = async (amount: string): Promise<Coin> => invoke('minor_to_major', { amount });
export const minorToMajor = async (amount: string): Promise<Coin> => {
const res: Coin = await invoke('minor_to_major', { amount });
return res;
};
export const majorToMinor = async (amount: string): Promise<Coin> => invoke('major_to_minor', { amount });
export const majorToMinor = async (amount: string): Promise<Coin> => {
const res: Coin = await invoke('major_to_minor', { amount });
return res;
};
+8 -3
View File
@@ -1,7 +1,12 @@
import { invoke } from '@tauri-apps/api';
import { TauriContractStateParams } from '../types';
export const getContractParams = async (): Promise<TauriContractStateParams> => invoke('get_contract_settings');
export const getContractParams = async (): Promise<TauriContractStateParams> => {
const res: TauriContractStateParams = await invoke('get_contract_settings');
return res;
};
export const setContractParams = async (params: TauriContractStateParams): Promise<TauriContractStateParams> =>
invoke('update_contract_settings', { params });
export const setContractParams = async (params: TauriContractStateParams): Promise<TauriContractStateParams> => {
const res: TauriContractStateParams = await invoke('update_contract_settings', { params });
return res;
};
+4 -1
View File
@@ -1,4 +1,7 @@
import { invoke } from '@tauri-apps/api';
import { Account, Network } from '../types';
export const selectNetwork = async (network: Network): Promise<Account> => invoke('switch_network', { network });
export const selectNetwork = async (network: Network): Promise<Account> => {
const res: Account = await invoke('switch_network', { network });
return res;
};
+56 -18
View File
@@ -1,4 +1,5 @@
import { invoke } from '@tauri-apps/api';
import { DelegationEvent } from 'src/types/rust/delegationevent';
import {
Balance,
Coin,
@@ -9,35 +10,72 @@ import {
StakeSaturationResponse,
TMixnodeBondDetails,
TPagedDelegations,
Epoch,
} from '../types';
export const getReverseMixDelegations = async (): Promise<TPagedDelegations> =>
invoke('get_reverse_mix_delegations_paged');
export const getReverseMixDelegations = async (): Promise<TPagedDelegations> => {
const res: TPagedDelegations = await invoke('get_reverse_mix_delegations_paged');
return res;
};
export const getReverseGatewayDelegations = async (): Promise<TPagedDelegations> =>
invoke('get_reverse_gateway_delegations_paged');
export const getReverseGatewayDelegations = async (): Promise<TPagedDelegations> => {
const res: TPagedDelegations = await invoke('get_reverse_gateway_delegations_paged');
return res;
};
export const getMixnodeBondDetails = async (): Promise<TMixnodeBondDetails | null> => invoke('mixnode_bond_details');
export const getPendingDelegations = async (): Promise<DelegationEvent[]> => {
const res: DelegationEvent[] = await invoke('get_pending_delegation_events');
return res;
};
export const getMixnodeStakeSaturation = async (identity: string): Promise<StakeSaturationResponse> =>
invoke('mixnode_stake_saturation', { identity });
export const getMixnodeBondDetails = async (): Promise<TMixnodeBondDetails | null> => {
const res: TMixnodeBondDetails = await invoke('mixnode_bond_details');
return res;
};
export const getMixnodeRewardEstimation = async (identity: string): Promise<RewardEstimationResponse> =>
invoke('mixnode_reward_estimation', { identity });
export const getMixnodeStakeSaturation = async (identity: string): Promise<StakeSaturationResponse> => {
const res: StakeSaturationResponse = await invoke('mixnode_stake_saturation', { identity });
return res;
};
export const getMixnodeStatus = async (identity: string): Promise<MixnodeStatusResponse> =>
invoke('mixnode_status', { identity });
export const getMixnodeRewardEstimation = async (identity: string): Promise<RewardEstimationResponse> => {
const res: RewardEstimationResponse = await invoke('mixnode_reward_estimation', { identity });
return res;
};
export const checkMixnodeOwnership = async (): Promise<boolean> => invoke('owns_mixnode');
export const getMixnodeStatus = async (identity: string): Promise<MixnodeStatusResponse> => {
const res: MixnodeStatusResponse = await invoke('mixnode_status', { identity });
return res;
};
export const checkGatewayOwnership = async (): Promise<boolean> => invoke('owns_gateway');
export const checkMixnodeOwnership = async (): Promise<boolean> => {
const res: boolean = await invoke('owns_mixnode');
return res;
};
export const checkGatewayOwnership = async (): Promise<boolean> => {
const res: boolean = await invoke('owns_gateway');
return res;
};
// NOTE: this uses OUTDATED defaults that might have no resemblance with the reality
// as for the actual transaction, the gas cost is being simulated beforehand
export const getGasFee = async (operation: Operation): Promise<Coin> =>
invoke('outdated_get_approximate_fee', { operation });
export const getGasFee = async (operation: Operation): Promise<Coin> => {
const res: Coin = await invoke('outdated_get_approximate_fee', { operation });
return res;
};
export const getInclusionProbability = async (identity: string): Promise<InclusionProbabilityResponse> =>
invoke('mixnode_inclusion_probability', { identity });
export const getInclusionProbability = async (identity: string): Promise<InclusionProbabilityResponse> => {
const res: InclusionProbabilityResponse = await invoke('mixnode_inclusion_probability', { identity });
return res;
};
export const userBalance = async (): Promise<Balance> => invoke('get_balance');
export const userBalance = async (): Promise<Balance> => {
const res: Balance = await invoke('get_balance');
return res;
};
export const getCurrentEpoch = async (): Promise<Epoch> => {
const res: Epoch = await invoke('get_current_epoch');
return res;
};
+86 -21
View File
@@ -1,40 +1,74 @@
import { invoke } from '@tauri-apps/api';
import { VestingAccountInfo } from 'src/types/rust/vestingaccountinfo';
import { majorToMinor, minorToMajor } from './coin';
import { Coin, DelegationResult, OriginalVestingResponse, Period } from '../types';
import {
Coin,
DelegationResult,
EnumNodeType,
Gateway,
MixNode,
OriginalVestingResponse,
Period,
PledgeData,
} from '../types';
export const getLockedCoins = async (address: string): Promise<Coin> => {
const res: Coin = await invoke('locked_coins', { address });
return minorToMajor(res.amount);
export const getLockedCoins = async (): Promise<Coin> => {
const coin: Coin = await invoke('locked_coins');
const major = await minorToMajor(coin.amount);
return major;
};
export const getSpendableCoins = async (vestingAccountAddress?: string): Promise<Coin> => {
const res: Coin = await invoke('spendable_coins', { vestingAccountAddress });
return minorToMajor(res.amount);
export const getSpendableCoins = async (): Promise<Coin> => {
const coin: Coin = await invoke('spendable_coins');
const major = await minorToMajor(coin.amount);
return major;
};
export const getVestingCoins = async (vestingAccountAddress: string): Promise<Coin> => {
const res: Coin = await invoke('vesting_coins', { vestingAccountAddress });
return minorToMajor(res.amount);
const coin: Coin = await invoke('vesting_coins', { vestingAccountAddress });
const major = await minorToMajor(coin.amount);
return major;
};
export const getVestedCoins = async (vestingAccountAddress: string): Promise<Coin> => {
const res: Coin = await invoke('vested_coins', { vestingAccountAddress });
return minorToMajor(res.amount);
const coin: Coin = await invoke('vested_coins', { vestingAccountAddress });
const major = await minorToMajor(coin.amount);
return major;
};
export const getOriginalVesting = async (vestingAccountAddress: string): Promise<OriginalVestingResponse> => {
const res: OriginalVestingResponse = await invoke('original_vesting', { vestingAccountAddress });
const majorValue = await minorToMajor(res.amount.amount);
return { ...res, amount: majorValue };
const major = await minorToMajor(res.amount.amount);
return { ...res, amount: major };
};
export const withdrawVestedCoins = async (amount: string) => {
export const withdrawVestedCoins = async (amount: string): Promise<void> => {
const minor = await majorToMinor(amount);
await invoke('withdraw_vested_coins', { amount: { amount: minor.amount, denom: 'Minor' } });
};
export const getCurrentVestingPeriod = async (address: string): Promise<Period> =>
invoke('get_current_vesting_period', { address });
export const getCurrentVestingPeriod = async (address: string): Promise<Period> => {
const res: Period = await invoke('get_current_vesting_period', { address });
return res;
};
export const vestingBond = async ({
type,
data,
pledge,
ownerSignature,
}: {
type: EnumNodeType;
data: MixNode | Gateway;
pledge: Coin;
ownerSignature: string;
}): Promise<void> => {
await invoke(`vesting_bond_${type}`, { [type]: data, ownerSignature, pledge });
};
export const vestingUnbond = async (type: EnumNodeType): Promise<void> => {
await invoke(`vesting_unbond_${type}`);
};
export const vestingDelegateToMixnode = async ({
identity,
@@ -42,10 +76,41 @@ export const vestingDelegateToMixnode = async ({
}: {
identity: string;
amount: Coin;
}): Promise<DelegationResult> => invoke('vesting_delegate_to_mixnode', { identity, amount });
}): Promise<DelegationResult> => {
const res: DelegationResult = await invoke('vesting_delegate_to_mixnode', { identity, amount });
return res;
};
export const vestingUnelegateFromMixnode = async (identity: string): Promise<DelegationResult> =>
invoke('vesting_delegate_to_mixnode', { identity });
export const vestingUnelegateFromMixnode = async (identity: string): Promise<DelegationResult> => {
const res: DelegationResult = await invoke('vesting_undelegate_from_mixnode', { identity });
return res;
};
export const getVestingAccountInfo = async (address: string): Promise<VestingAccountInfo> =>
invoke('get_account_info', { address });
export const getVestingAccountInfo = async (address: string): Promise<VestingAccountInfo> => {
const res: VestingAccountInfo = await invoke('get_account_info', { address });
return res;
};
export const getVestingPledgeInfo = async ({
address,
type,
}: {
address?: string;
type: EnumNodeType;
}): Promise<PledgeData | undefined> => {
try {
const res: PledgeData = await invoke(`vesting_get_${type}_pledge`, { address });
return res;
} catch (e) {
return undefined;
}
};
export const vestingUpdateMixnode = async (profitMarginPercent: number): Promise<void> => {
await invoke('vesting_update_mixnode', { profitMarginPercent });
};
export const vestingDelegatedFree = async (vestingAccountAddress: string): Promise<Coin> => {
const res: Coin = await invoke('delegated_free', { vestingAccountAddress });
return res;
};
+15 -1
View File
@@ -1,4 +1,4 @@
import { Coin, Denom, MixNode } from './rust';
import { Coin, Denom, Gateway, MixNode, PledgeData } from './rust';
export enum EnumNodeType {
mixnode = 'mixnode',
@@ -8,6 +8,7 @@ export enum EnumNodeType {
export type TNodeOwnership = {
hasOwnership: boolean;
nodeType?: EnumNodeType;
vestingPledge?: PledgeData;
};
export type TClientDetails = {
@@ -54,6 +55,19 @@ export type TMixnodeBondDetails = {
proxy: any;
};
export type TBondArgs = {
type: EnumNodeType;
data: MixNode | Gateway;
pledge: Coin;
ownerSignature: string;
};
export type TDelegateArgs = {
type: EnumNodeType;
identity: string;
amount: Coin;
};
export type TCurrency = {
minor: 'UNYM' | 'UNYMT';
major: 'NYM' | 'NYMT';
+6 -2
View File
@@ -1,3 +1,7 @@
import type { Denom } from "./denom";
import type { Denom } from './denom';
export interface Account { contract_address: string, client_address: string, denom: Denom, }
export interface Account {
contract_address: string;
client_address: string;
denom: Denom;
}
+5 -2
View File
@@ -1,3 +1,6 @@
import type { Coin } from "./coin";
import type { Coin } from './coin';
export interface Balance { coin: Coin, printable_balance: string, }
export interface Balance {
coin: Coin;
printable_balance: string;
}
+5 -2
View File
@@ -1,3 +1,6 @@
import type { Denom } from "./denom";
import type { Denom } from './denom';
export interface Coin { amount: string, denom: Denom, }
export interface Coin {
amount: string;
denom: Denom;
}
@@ -1,2 +1,4 @@
export interface CoreNodeStatusResponse { identity: string, count: number, }
export interface CoreNodeStatusResponse {
identity: string;
count: number;
}
+5 -2
View File
@@ -1,3 +1,6 @@
import type { Account } from "./account";
import type { Account } from './account';
export interface CreatedAccount { account: Account, mnemonic: string, }
export interface CreatedAccount {
account: Account;
mnemonic: string;
}
@@ -0,0 +1,4 @@
import type { DelegationResult } from './delegationresult';
import type { PendingUndelegate } from './pendingundelegate';
export type DelegationEvent = { Delegate: DelegationResult } | { Undelegate: PendingUndelegate };
@@ -1,3 +1,7 @@
import type { Coin } from "./coin";
import type { Coin } from './coin';
export interface DelegationResult { source_address: string, target_address: string, amount: Coin | null, }
export interface DelegationResult {
source_address: string;
target_address: string;
amount: Coin | null;
}
+1 -2
View File
@@ -1,2 +1 @@
export type Denom = "Major" | "Minor";
export type Denom = 'Major' | 'Minor';
+6
View File
@@ -0,0 +1,6 @@
export interface Epoch {
id: number;
start: bigint;
end: bigint;
duration_seconds: bigint;
}
+9 -2
View File
@@ -1,2 +1,9 @@
export interface Gateway { host: string, mix_port: number, clients_port: number, location: string, sphinx_key: string, identity_key: string, version: string, }
export interface Gateway {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
}
@@ -1,2 +1,4 @@
export interface InclusionProbabilityResponse { in_active: number, in_reserve: number, }
export interface InclusionProbabilityResponse {
in_active: number;
in_reserve: number;
}
+4
View File
@@ -18,4 +18,8 @@ export * from './stateparams';
export * from './tauritxresult';
export * from './transactiondetails';
export * from './vestingaccountinfo';
export * from './pledgedata';
export * from './vestingperiod';
export * from './pendingundelegate';
export * from './delegationevent';
export * from './epoch';
+10 -2
View File
@@ -1,2 +1,10 @@
export interface MixNode { host: string, mix_port: number, verloc_port: number, http_api_port: number, sphinx_key: string, identity_key: string, version: string, profit_margin_percent: number, }
export interface MixNode {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
}
+1 -2
View File
@@ -1,2 +1 @@
export type MixnodeStatus = "active" | "standby" | "inactive" | "not_found";
export type MixnodeStatus = 'active' | 'standby' | 'inactive' | 'not_found';
@@ -1,3 +1,5 @@
import type { MixnodeStatus } from "./mixnodestatus";
import type { MixnodeStatus } from './mixnodestatus';
export interface MixnodeStatusResponse { status: MixnodeStatus, }
export interface MixnodeStatusResponse {
status: MixnodeStatus;
}
+1 -2
View File
@@ -1,2 +1 @@
export type Network = "QA" | "SANDBOX" | "MAINNET";
export type Network = 'QA' | 'SANDBOX' | 'MAINNET';
+34 -2
View File
@@ -1,2 +1,34 @@
export type Operation = "Upload" | "Init" | "Migrate" | "ChangeAdmin" | "Send" | "BondMixnode" | "BondMixnodeOnBehalf" | "UnbondMixnode" | "UnbondMixnodeOnBehalf" | "UpdateMixnodeConfig" | "DelegateToMixnode" | "DelegateToMixnodeOnBehalf" | "UndelegateFromMixnode" | "UndelegateFromMixnodeOnBehalf" | "BondGateway" | "BondGatewayOnBehalf" | "UnbondGateway" | "UnbondGatewayOnBehalf" | "UpdateContractSettings" | "BeginMixnodeRewarding" | "FinishMixnodeRewarding" | "TrackUnbondGateway" | "TrackUnbondMixnode" | "WithdrawVestedCoins" | "TrackUndelegation" | "CreatePeriodicVestingAccount" | "AdvanceCurrentInterval" | "AdvanceCurrentEpoch" | "WriteRewardedSet" | "ClearRewardedSet" | "UpdateMixnetAddress" | "CheckpointMixnodes" | "ReconcileDelegations";
export type Operation =
| 'Upload'
| 'Init'
| 'Migrate'
| 'ChangeAdmin'
| 'Send'
| 'BondMixnode'
| 'BondMixnodeOnBehalf'
| 'UnbondMixnode'
| 'UnbondMixnodeOnBehalf'
| 'UpdateMixnodeConfig'
| 'DelegateToMixnode'
| 'DelegateToMixnodeOnBehalf'
| 'UndelegateFromMixnode'
| 'UndelegateFromMixnodeOnBehalf'
| 'BondGateway'
| 'BondGatewayOnBehalf'
| 'UnbondGateway'
| 'UnbondGatewayOnBehalf'
| 'UpdateContractSettings'
| 'BeginMixnodeRewarding'
| 'FinishMixnodeRewarding'
| 'TrackUnbondGateway'
| 'TrackUnbondMixnode'
| 'WithdrawVestedCoins'
| 'TrackUndelegation'
| 'CreatePeriodicVestingAccount'
| 'AdvanceCurrentInterval'
| 'AdvanceCurrentEpoch'
| 'WriteRewardedSet'
| 'ClearRewardedSet'
| 'UpdateMixnetAddress'
| 'CheckpointMixnodes'
| 'ReconcileDelegations';
@@ -1,3 +1,7 @@
import type { Coin } from "./coin";
import type { Coin } from './coin';
export interface OriginalVestingResponse { amount: Coin, number_of_periods: number, period_duration: bigint, }
export interface OriginalVestingResponse {
amount: Coin;
number_of_periods: number;
period_duration: bigint;
}
@@ -0,0 +1,6 @@
export interface PendingUndelegate {
mix_identity: string;
delegate: string;
proxy: string | null;
block_height: bigint;
}
+1 -2
View File
@@ -1,2 +1 @@
export type Period = "Before" | { In: number } | "After";
export type Period = 'Before' | { In: number } | 'After';
+5 -2
View File
@@ -1,3 +1,6 @@
import type { Coin } from "./coin";
import type { Coin } from './coin';
export interface PledgeData { amount: Coin, block_time: bigint, }
export interface PledgeData {
amount: Coin;
block_time: bigint;
}
@@ -1,2 +1 @@
export type RewardedSetNodeStatus = "Active" | "Standby";
export type RewardedSetNodeStatus = 'Active' | 'Standby';
@@ -1,2 +1,9 @@
export interface RewardEstimationResponse { estimated_total_node_reward: bigint, estimated_operator_reward: bigint, estimated_delegators_reward: bigint, current_interval_start: bigint, current_interval_end: bigint, current_interval_uptime: number, as_at: bigint, }
export interface RewardEstimationResponse {
estimated_total_node_reward: bigint;
estimated_operator_reward: bigint;
estimated_delegators_reward: bigint;
current_interval_start: bigint;
current_interval_end: bigint;
current_interval_uptime: number;
as_at: bigint;
}
@@ -1,2 +1,4 @@
export interface StakeSaturationResponse { saturation: number, as_at: bigint, }
export interface StakeSaturationResponse {
saturation: number;
as_at: bigint;
}
+6 -2
View File
@@ -1,2 +1,6 @@
export interface TauriContractStateParams { minimum_mixnode_pledge: string, minimum_gateway_pledge: string, mixnode_rewarded_set_size: number, mixnode_active_set_size: number, }
export interface TauriContractStateParams {
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
}

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