Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52ced9d3ad | |||
| 5eb2249ad5 | |||
| 008fc045b7 | |||
| 38cc3b9e38 | |||
| 989285e009 | |||
| c6e438600f | |||
| 1dab01ca33 | |||
| 53e0432934 | |||
| a1f2d33397 | |||
| 49aa79b5df | |||
| 6f3b5fad15 | |||
| 0efa909374 | |||
| 43b102782e | |||
| d6e6bab6a6 | |||
| 1a3cdc3128 | |||
| 39f75907e5 | |||
| 029c2698d7 | |||
| 60548770ba | |||
| 39cfc2057b | |||
| 4fbae2d5bd | |||
| 9feee4c174 | |||
| bca6b45273 | |||
| d9733a97ae | |||
| e66dc01966 | |||
| 2b4aca0194 | |||
| 06aa09afcd | |||
| ddc09bbb2e | |||
| 3003be5e68 | |||
| cb2ce87ab7 | |||
| 2ad65fc455 | |||
| cb292b38c0 | |||
| b710f796f9 | |||
| fb5a9a7a3e | |||
| 071720c1cc | |||
| 6679944689 | |||
| bec7814756 | |||
| 592b3b3bb1 | |||
| c2ff786f41 | |||
| 07b6114adf | |||
| 07155b4fe5 | |||
| 8b635a44b8 | |||
| 568862a571 | |||
| 7868725fed | |||
| d30667d8e4 | |||
| 3483a9e7f9 | |||
| 893b28c51c | |||
| 47ffdfe496 | |||
| 51bced8766 | |||
| d4286aeb76 | |||
| ce93e5f677 | |||
| 4b691d19a0 | |||
| 0e86761a13 | |||
| 55463da0da | |||
| 518daeaf20 | |||
| f6ec12db94 | |||
| 1e921186ad | |||
| d653ecde1e | |||
| 5b9b4743dc | |||
| 5f9b384efa | |||
| d69526fad9 | |||
| e10a4e8fca | |||
| 9016878868 | |||
| 1ca6e4a002 | |||
| f9c73183db | |||
| d296100ffa | |||
| 06669fe114 | |||
| 8022fc7e31 | |||
| 2b915fd0aa | |||
| 4e45c54d17 | |||
| 0113141efd | |||
| 614d096929 | |||
| fe7df3a909 | |||
| b14f6ab159 | |||
| e6040b0ecc | |||
| 60e6d23fd8 | |||
| d30038c570 | |||
| e3813b4868 | |||
| 61a0e5bbfb |
@@ -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)]
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)>,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(¶ms);
|
||||
|
||||
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(¶ms2);
|
||||
|
||||
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(¶ms3);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,3 +14,4 @@ export * from './NetworkSelector';
|
||||
export * from './ClientAddress';
|
||||
export * from './InfoToolTip';
|
||||
export * from './Title';
|
||||
export * from './TokenPoolSelector';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
@@ -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');
|
||||
};
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 +1 @@
|
||||
|
||||
export type Denom = "Major" | "Minor";
|
||||
export type Denom = 'Major' | 'Minor';
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface Epoch {
|
||||
id: number;
|
||||
start: bigint;
|
||||
end: bigint;
|
||||
duration_seconds: bigint;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 +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 +1 @@
|
||||
|
||||
export type Network = "QA" | "SANDBOX" | "MAINNET";
|
||||
export type Network = 'QA' | 'SANDBOX' | 'MAINNET';
|
||||
|
||||
@@ -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 +1 @@
|
||||
|
||||
export type Period = "Before" | { In: number } | "After";
|
||||
export type Period = 'Before' | { In: number } | 'After';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user