diff --git a/Cargo.lock b/Cargo.lock index f2aa634bd6..842ed16b9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3612,9 +3612,11 @@ name = "nym_wallet" version = "0.1.0" dependencies = [ "bip39", + "coconut-interface", "config", "cosmos_sdk", "cosmwasm-std", + "credentials", "dirs", "mixnet-contract", "serde", @@ -3625,6 +3627,7 @@ dependencies = [ "thiserror", "tokio", "ts-rs", + "url", "validator-client", ] diff --git a/common/client-libs/validator-client/src/nymd/fee_helpers.rs b/common/client-libs/validator-client/src/nymd/fee_helpers.rs index c2d70cc187..966d6b494d 100644 --- a/common/client-libs/validator-client/src/nymd/fee_helpers.rs +++ b/common/client-libs/validator-client/src/nymd/fee_helpers.rs @@ -5,6 +5,7 @@ use crate::nymd::GasPrice; use cosmos_sdk::tx::{Fee, Gas}; use cosmos_sdk::Coin; use cosmwasm_std::Uint128; +use std::fmt; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum Operation { @@ -37,6 +38,27 @@ pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin { } } +impl fmt::Display for Operation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Operation::Upload => f.write_str("Upload"), + Operation::Init => f.write_str("Init"), + Operation::Migrate => f.write_str("Migrate"), + Operation::ChangeAdmin => f.write_str("ChangeAdmin"), + Operation::Send => f.write_str("Send"), + Operation::BondMixnode => f.write_str("BondMixnode"), + Operation::UnbondMixnode => f.write_str("UnbondMixnode"), + Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"), + Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"), + Operation::BondGateway => f.write_str("BondGateway"), + Operation::UnbondGateway => f.write_str("UnbondGateway"), + Operation::DelegateToGateway => f.write_str("DelegateToGateway"), + Operation::UndelegateFromGateway => f.write_str("UndelegateFromGateway"), + Operation::UpdateStateParams => f.write_str("UpdateStateParams"), + } + } + } + impl Operation { // TODO: some value tweaking pub(crate) fn default_gas_limit(&self) -> Gas { diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index ff57978983..7f7d74b167 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -122,6 +122,14 @@ impl NymdClient { self.custom_gas_limits.insert(operation, limit); } + pub fn get_gas_price(&self) -> GasPrice { + self.gas_price.clone() + } + + pub fn get_custom_gas_limits(&self) -> HashMap { + self.custom_gas_limits.clone() + } + pub fn contract_address(&self) -> Result<&AccountId, NymdError> { self.contract_address .as_ref() diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 2066a5878f..8307e69b91 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct ValidatorDetails { // it is assumed those values are always valid since they're being provided in our defaults file pub nymd_url: String, diff --git a/tauri-wallet/src-tauri/Cargo.toml b/tauri-wallet/src-tauri/Cargo.toml index df2e032080..90b5930368 100644 --- a/tauri-wallet/src-tauri/Cargo.toml +++ b/tauri-wallet/src-tauri/Cargo.toml @@ -25,6 +25,7 @@ bip39 = "1.0" thiserror = "1.0" tendermint-rpc = "0.21.0" ts-rs = "3.0" +url = "2.0" cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev = "ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = [ "rpc", @@ -38,6 +39,8 @@ validator-client = { path = "../../common/client-libs/validator-client", feature ] } mixnet-contract = { path = "../../common/mixnet-contract" } config = { path = "../../common/config" } +coconut-interface = { path = "../../common/coconut-interface" } +credentials = { path = "../../common/credentials" } [features] default = ["custom-protocol"] diff --git a/tauri-wallet/src-tauri/src/coconut.rs b/tauri-wallet/src-tauri/src/coconut.rs new file mode 100644 index 0000000000..3288617278 --- /dev/null +++ b/tauri-wallet/src-tauri/src/coconut.rs @@ -0,0 +1,143 @@ +use std::sync::Arc; +use tokio::sync::RwLock; +use coconut_interface::{ + self, Credential, Signature, Theta, VerificationKey, + }; +use crate::state::State; +use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key}; +use url::Url; + + +#[tauri::command] +pub async fn randomise_credential( + idx: usize, + state: tauri::State<'_, Arc>>, +) -> Result, String> { + let mut state = state.write().await; + let signature = state.signatures.remove(idx); + let new = signature.randomise(state.params()?); + state.signatures.insert(idx, new); + Ok(state.signatures.clone()) +} + +#[tauri::command] +pub async fn delete_credential( + idx: usize, + state: tauri::State<'_, Arc>>, +) -> Result, String> { + let mut state = state.write().await; + state.signatures.remove(idx); + Ok(state.signatures.clone()) +} + +#[tauri::command] +pub async fn list_credentials( + state: tauri::State<'_, Arc>>, +) -> Result, String> { + let state = state.read().await; + Ok(state.signatures.clone()) +} + +#[tauri::command] +pub async fn verify_credential( + idx: usize, + validator_urls: Vec, + state: tauri::State<'_, Arc>>, +) -> Result { + // the API needs to be improved but at least it should compile (in theory) + let verification_key = + get_aggregated_verification_key(validator_urls.clone(), state.clone()).await?; + let theta = prove_credential(idx, validator_urls, state.clone()).await?; + + let state = state.read().await; + + let credential = Credential::new( + state.n_attributes(), + theta, + &state.public_attributes(), + state + .signatures + .get(idx) + .ok_or("Got invalid signature idx")?, + ); + + Ok(credential.verify(&verification_key).await) +} + +async fn prove_credential( + idx: usize, + validator_urls: Vec, + state: tauri::State<'_, Arc>>, + ) -> Result { + let verification_key = get_aggregated_verification_key(validator_urls, state.clone()).await?; + let state = state.read().await; + + if let Some(signature) = state.signatures.get(idx) { + match coconut_interface::prove_credential( + state.params()?, + &verification_key, + signature, + &state.private_attributes(), + ) { + Ok(theta) => Ok(theta), + Err(e) => Err(format!("{}", e)), + } + } else { + Err("Got invalid Signature idx".to_string()) + } + } + + async fn get_aggregated_verification_key( + validator_urls: Vec, + state: tauri::State<'_, Arc>>, + ) -> Result { + if let Some(verification_key) = &state.read().await.aggregated_verification_key { + return Ok(verification_key.clone()); + } + + let parsed_urls = parse_url_validators(&validator_urls)?; + let key = obtain_aggregate_verification_key(&parsed_urls) + .await + .map_err(|err| format!("failed to obtain aggregate verification key - {:?}", err))?; + + state + .write() + .await + .aggregated_verification_key + .replace(key.clone()); + + Ok(key) + } + + fn parse_url_validators(raw: &[String]) -> Result, String> { + let mut parsed_urls = Vec::with_capacity(raw.len()); + for url in raw { + let parsed_url: Url = url + .parse() + .map_err(|err| format!("one of validator urls is malformed - {}", err))?; + parsed_urls.push(parsed_url) + } + Ok(parsed_urls) + } + +#[tauri::command] +pub async fn get_credential( + validator_urls: Vec, + state: tauri::State<'_, Arc>>, +) -> Result, String> { + let guard = state.read().await; + let parsed_urls = parse_url_validators(&validator_urls)?; + + let signature = obtain_aggregate_signature( + guard.params()?, + &guard.public_attributes(), + &guard.private_attributes(), + &parsed_urls, + ) + .await + .map_err(|err| format!("failed to obtain aggregate signature - {:?}", err))?; + + let mut state = state.write().await; + state.signatures.push(signature); + Ok(state.signatures.clone()) +} \ No newline at end of file diff --git a/tauri-wallet/src-tauri/src/config/mod.rs b/tauri-wallet/src-tauri/src/config/mod.rs index 011744e0bf..90fbf670fa 100644 --- a/tauri-wallet/src-tauri/src/config/mod.rs +++ b/tauri-wallet/src-tauri/src/config/mod.rs @@ -14,13 +14,13 @@ use template::config_template; use crate::error::BackendError; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct Config { base: Base, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct Base { validators: Vec, diff --git a/tauri-wallet/src-tauri/src/main.rs b/tauri-wallet/src-tauri/src/main.rs index ef2f0d7449..c3500c989c 100644 --- a/tauri-wallet/src-tauri/src/main.rs +++ b/tauri-wallet/src-tauri/src/main.rs @@ -12,22 +12,30 @@ use cosmwasm_std::Coin as CosmWasmCoin; use error::BackendError; use mixnet_contract::{Gateway, MixNode}; use serde::{Deserialize, Serialize}; +use tendermint_rpc::endpoint::broadcast::tx_commit::Response; use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::fmt; use std::str::FromStr; -use ts_rs::{export, TS}; -use validator_client::nymd::{NymdClient, SigningNymdClient}; - -mod config; -mod error; -// mod nymd_client; - use std::sync::Arc; use tokio::sync::RwLock; +use ts_rs::{export, TS}; +use validator_client::nymd::GasPrice; +use validator_client::nymd::{NymdClient, SigningNymdClient}; + +mod coconut; +mod config; +mod error; +mod state; + +use crate::coconut::{ + delete_credential, get_credential, list_credentials, randomise_credential, verify_credential, +}; +use crate::state::State; use crate::config::Config; +#[macro_export] macro_rules! format_err { ($e:expr) => { format!("line {}: {}", line!(), $e) @@ -71,6 +79,36 @@ impl FromStr for Denom { } } +#[derive(Deserialize, Serialize, TS)] +struct TauriTxResult { + code: u32, + gas_wanted: u64, + gas_used: u64, + block_height: u64, + details: TransactionDetails, +} + +#[derive(Deserialize, Serialize, TS)] +struct TransactionDetails { + from_address: String, + to_address: String, + amount: Coin +} + + +impl TauriTxResult { + fn new(t: Response, details: TransactionDetails) -> TauriTxResult { + TauriTxResult { + code: t.check_tx.code.value(), + gas_wanted: t.check_tx.gas_wanted.value(), + gas_used: t.check_tx.gas_used.value(), + block_height: t.height.value(), + details + } + } +} + + // Proxy types to allow TS generation #[derive(TS, Serialize, Deserialize, Clone)] struct Coin { @@ -78,6 +116,15 @@ struct Coin { denom: String, } +impl From for Coin { + fn from(g: GasPrice) -> Coin { + Coin { + amount: g.amount.to_string(), + denom: g.denom.to_string(), + } + } +} + impl fmt::Display for Coin { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&format!("{} {}", self.amount, self.denom)) @@ -153,12 +200,6 @@ impl From for Coin { } } -#[derive(Debug, Default)] -struct State { - config: Config, - signing_client: Option>, -} - #[tauri::command] fn major_to_minor(amount: String) -> Result { let coin = Coin { @@ -189,7 +230,7 @@ async fn connect_with_mnemonic( let client; { let r_state = state.read().await; - client = _connect_with_mnemonic(mnemonic, &r_state.config); + client = _connect_with_mnemonic(mnemonic, &r_state.config()); } let mut ret = HashMap::new(); @@ -209,7 +250,7 @@ async fn connect_with_mnemonic( }, ); let mut w_state = state.write().await; - w_state.signing_client = Some(client); + w_state.set_client(client); Ok(ret) } @@ -217,73 +258,53 @@ async fn connect_with_mnemonic( #[tauri::command] async fn get_balance(state: tauri::State<'_, Arc>>) -> Result { let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.get_balance(client.address()).await { - Ok(Some(coin)) => { - let coin = Coin { - amount: coin.amount.to_string(), - denom: coin.denom.to_string(), - }; - Ok(Balance { - coin: coin.clone(), - printable_balance: coin.to_major().to_string(), - }) - } - Ok(None) => Err(format!( - "No balance available for address {}", - client.address() - )), - Err(e) => Err(BackendError::from(e).to_string()), + let client = r_state.client()?; + match client.get_balance(client.address()).await { + Ok(Some(coin)) => { + let coin = Coin { + amount: coin.amount.to_string(), + denom: coin.denom.to_string(), + }; + Ok(Balance { + coin: coin.clone(), + printable_balance: coin.to_major().to_string(), + }) } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + Ok(None) => Err(format!( + "No balance available for address {}", + client.address() + )), + Err(e) => Err(BackendError::from(e).to_string()), } } #[tauri::command] async fn owns_mixnode(state: tauri::State<'_, Arc>>) -> Result { let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.owns_mixnode(client.address()).await { - Ok(o) => Ok(o), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.owns_mixnode(client.address()).await { + Ok(o) => Ok(o), + Err(e) => Err(format_err!(e)), } } #[tauri::command] async fn owns_gateway(state: tauri::State<'_, Arc>>) -> Result { let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.owns_gateway(client.address()).await { - Ok(o) => Ok(o), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.owns_gateway(client.address()).await { + Ok(o) => Ok(o), + Err(e) => Err(format_err!(e)), } } #[tauri::command] async fn unbond_mixnode(state: tauri::State<'_, Arc>>) -> Result<(), String> { let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.unbond_mixnode().await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.unbond_mixnode().await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } @@ -298,15 +319,10 @@ async fn bond_mixnode( Ok(b) => b, Err(e) => return Err(format_err!(e)), }; - if let Some(client) = &r_state.signing_client { - match client.bond_mixnode(mixnode, bond).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.bond_mixnode(mixnode, bond).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } @@ -321,15 +337,10 @@ async fn delegate_to_mixnode( Ok(b) => b, Err(e) => return Err(format_err!(e)), }; - if let Some(client) = &r_state.signing_client { - match client.delegate_to_mixnode(identity, bond).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.delegate_to_mixnode(identity, bond).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } @@ -339,15 +350,10 @@ async fn undelegate_from_mixnode( state: tauri::State<'_, Arc>>, ) -> Result<(), String> { let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.remove_mixnode_delegation(identity).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.remove_mixnode_delegation(identity).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } @@ -362,15 +368,10 @@ async fn delegate_to_gateway( Ok(b) => b, Err(e) => return Err(format_err!(e)), }; - if let Some(client) = &r_state.signing_client { - match client.delegate_to_gateway(identity, bond).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.delegate_to_gateway(identity, bond).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } @@ -380,15 +381,10 @@ async fn undelegate_from_gateway( state: tauri::State<'_, Arc>>, ) -> Result<(), String> { let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.remove_gateway_delegation(identity).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.remove_gateway_delegation(identity).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } @@ -403,56 +399,71 @@ async fn bond_gateway( Ok(b) => b, Err(e) => return Err(format_err!(e)), }; - if let Some(client) = &r_state.signing_client { - match client.bond_gateway(gateway, bond).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.bond_gateway(gateway, bond).await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } #[tauri::command] async fn unbond_gateway(state: tauri::State<'_, Arc>>) -> Result<(), String> { let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.unbond_gateway().await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.unbond_gateway().await { + Ok(_result) => Ok(()), + Err(e) => Err(format_err!(e)), } } #[tauri::command] -async fn send(address: &str, amount: Coin, memo: String, state: tauri::State<'_, Arc>>,) -> Result<(), String> { +async fn send( + address: &str, + amount: Coin, + memo: String, + state: tauri::State<'_, Arc>>, +) -> Result { let address = match AccountId::from_str(address) { Ok(addy) => addy, - Err(e) => return Err(format_err!(e)) + Err(e) => return Err(format_err!(e)), }; - let amount: CosmosCoin = match amount.try_into() { + let cosmos_amount: CosmosCoin = match amount.clone().try_into() { Ok(b) => b, Err(e) => return Err(format_err!(e)), }; let r_state = state.read().await; - if let Some(client) = &r_state.signing_client { - match client.send(&address, vec!(amount), memo).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } - } else { - Err(String::from( - "Client has not been initialized yet, connect with mnemonic to initialize", - )) + let client = r_state.client()?; + match client.send(&address, vec![cosmos_amount], memo).await { + Ok(result) => Ok(TauriTxResult::new(result, TransactionDetails { + from_address: client.address().to_string(), + to_address: address.to_string(), + amount: amount.into() + })), + Err(e) => Err(format_err!(e)), } } +#[tauri::command] +async fn get_gas_price(state: tauri::State<'_, Arc>>) -> Result { + let r_state = state.read().await; + let client = r_state.client()?; + let coin = client.get_gas_price().into(); + Ok(coin) +} + +#[tauri::command] +async fn get_gas_limits( + state: tauri::State<'_, Arc>>, +) -> Result, String> { + let r_state = state.read().await; + let client = r_state.client()?; + let mut limits = HashMap::new(); + for (k, v) in client.get_custom_gas_limits() { + limits.insert(k.to_string(), v.value()); + } + Ok(limits) +} + fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> NymdClient { match NymdClient::connect_with_mnemonic( config.get_nymd_validator_url().unwrap(), @@ -482,7 +493,14 @@ fn main() { undelegate_from_mixnode, delegate_to_gateway, undelegate_from_gateway, - send + send, + get_gas_price, + get_gas_limits, + get_credential, + randomise_credential, + delete_credential, + list_credentials, + verify_credential ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); @@ -492,5 +510,7 @@ export! { MixNode => "../src/types/rust/mixnode.ts", Coin => "../src/types/rust/coin.ts", Balance => "../src/types/rust/balance.ts", - Gateway => "../src/types/rust/gateway.ts" + Gateway => "../src/types/rust/gateway.ts", + TauriTxResult => "../src/types/rust/tauritxresult.ts", + TransactionDetails => "../src/types/rust/transactiondetails.ts" } diff --git a/tauri-wallet/src-tauri/src/state.rs b/tauri-wallet/src-tauri/src/state.rs new file mode 100644 index 0000000000..b437aba84c --- /dev/null +++ b/tauri-wallet/src-tauri/src/state.rs @@ -0,0 +1,55 @@ +use crate::config::Config; +use crate::format_err; +use coconut_interface::{ + self, Attribute, Parameters, Signature, VerificationKey, +}; +use validator_client::nymd::{NymdClient, SigningNymdClient}; + +#[derive(Default)] +pub struct State { + config: Config, + signing_client: Option>, + // Coconut stuff + pub signatures: Vec, + n_attributes: u32, + params: Option, + public_attributes: Vec, + private_attributes: Vec, + pub aggregated_verification_key: Option, +} + +impl State { + pub fn client(&self) -> Result<&NymdClient, String> { + self.signing_client.as_ref().ok_or_else(|| { + "Client has not been initialized yet, connect with mnemonic to initialize".to_string() + }) + } + + pub fn config(&self) -> Config { + self.config.clone() + } + + pub fn set_client(&mut self, signing_client: NymdClient) { + self.signing_client = Some(signing_client) + } + + pub fn params(&self) -> Result<&Parameters, String> { + self + .params + .as_ref() + .ok_or_else(|| format_err!("Parameters are not set!")) + } + + pub fn private_attributes(&self) -> Vec { + self.private_attributes.clone() + } + + pub fn public_attributes(&self) -> Vec { + self.public_attributes.clone() + } + + pub fn n_attributes(&self) -> u32 { + self.n_attributes + } + +} diff --git a/tauri-wallet/src/routes/internal-docs/ApiList.tsx b/tauri-wallet/src/routes/internal-docs/ApiList.tsx index 0f144a0460..f88f9446f4 100644 --- a/tauri-wallet/src/routes/internal-docs/ApiList.tsx +++ b/tauri-wallet/src/routes/internal-docs/ApiList.tsx @@ -136,6 +136,22 @@ export const ApiList = () => { }} /> + + + + + + ) } diff --git a/tauri-wallet/src/types/rust/tauritxresult.ts b/tauri-wallet/src/types/rust/tauritxresult.ts new file mode 100644 index 0000000000..92d812300f --- /dev/null +++ b/tauri-wallet/src/types/rust/tauritxresult.ts @@ -0,0 +1,9 @@ +import { TransactionDetails } from "./transactiondetails"; + +export interface TauriTxResult { + code: number; + gas_wanted: number; + gas_used: number; + block_height: number; + details: TransactionDetails; +} \ No newline at end of file diff --git a/tauri-wallet/src/types/rust/transactiondetails.ts b/tauri-wallet/src/types/rust/transactiondetails.ts new file mode 100644 index 0000000000..f24b695511 --- /dev/null +++ b/tauri-wallet/src/types/rust/transactiondetails.ts @@ -0,0 +1,7 @@ +import { Coin } from "./coin"; + +export interface TransactionDetails { + from_address: string; + to_address: string; + amount: Coin; +} \ No newline at end of file