merge backend updates
This commit is contained in:
Generated
+286
-310
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ network-defaults = { path = "../../network-defaults" }
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
config = { path = "../../config", optional = true}
|
||||
cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", commit="ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev="ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
prost = { version = "0.7", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
|
||||
@@ -25,6 +25,8 @@ bip39 = "1.0"
|
||||
thiserror = "1.0"
|
||||
tendermint-rpc = "0.21.0"
|
||||
|
||||
cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev="ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = ["rpc", "bip32", "cosmwasm"] }
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
] }
|
||||
|
||||
@@ -86,9 +86,9 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn get_mixnet_contract_address(&self) -> String {
|
||||
// self.base.mixnet_contract_address.clone()
|
||||
// }
|
||||
pub fn get_mixnet_contract_address(&self) -> String {
|
||||
self.base.mixnet_contract_address.clone()
|
||||
}
|
||||
|
||||
// pub fn get_mnemonic(&self) -> String {
|
||||
// self.base.mnemonic.clone()
|
||||
|
||||
@@ -8,6 +8,7 @@ use error::BackendError;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use validator_client::nymd::{NymdClient, SigningNymdClient};
|
||||
use cosmos_sdk::AccountId;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
@@ -24,11 +25,17 @@ struct State {
|
||||
signing_client: Option<NymdClient<SigningNymdClient>>,
|
||||
}
|
||||
|
||||
macro_rules! format_err {
|
||||
($e:expr) => {
|
||||
format!("line {}: {}", line!(), $e)
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn connect_with_mnemonic(
|
||||
mnemonic: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<bool, String> {
|
||||
) -> Result<HashMap<&str, String>, String> {
|
||||
let mnemonic = match Mnemonic::from_str(&mnemonic) {
|
||||
Ok(mnemonic) => mnemonic,
|
||||
Err(e) => return Err(BackendError::from(e).to_string()),
|
||||
@@ -39,9 +46,32 @@ async fn connect_with_mnemonic(
|
||||
client = _connect_with_mnemonic(mnemonic, &r_state.config);
|
||||
}
|
||||
|
||||
let mut ret = HashMap::new();
|
||||
ret.insert(
|
||||
"contract_address",
|
||||
match client.contract_address() {
|
||||
Ok(address) => address.to_string(),
|
||||
Err(e) => format_err!(e),
|
||||
},
|
||||
);
|
||||
ret.insert(
|
||||
"client_address",
|
||||
match client.contract_address() {
|
||||
Ok(address) => address.to_string(),
|
||||
Err(e) => format_err!(e),
|
||||
},
|
||||
);
|
||||
ret.insert(
|
||||
"denom",
|
||||
match client.denom() {
|
||||
Ok(denom) => denom.to_string(),
|
||||
Err(e) => format_err!(e),
|
||||
},
|
||||
);
|
||||
let mut w_state = state.write().await;
|
||||
w_state.signing_client = Some(client);
|
||||
Ok(true)
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -69,8 +99,11 @@ async fn get_balance(
|
||||
}
|
||||
|
||||
fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> NymdClient<SigningNymdClient> {
|
||||
match NymdClient::connect_with_mnemonic(config.get_nymd_validator_url().unwrap(), None, mnemonic)
|
||||
{
|
||||
match NymdClient::connect_with_mnemonic(
|
||||
config.get_nymd_validator_url().unwrap(),
|
||||
Some(AccountId::from_str(&config.get_mixnet_contract_address()).unwrap()),
|
||||
mnemonic,
|
||||
) {
|
||||
Ok(client) => client,
|
||||
Err(e) => panic!("{}", e),
|
||||
}
|
||||
@@ -83,23 +116,3 @@ fn main() {
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
mod test {
|
||||
#![allow(unused_imports)]
|
||||
use crate::Config;
|
||||
use crate::_connect_with_mnemonic;
|
||||
use bip39::Mnemonic;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_connect_with_mnemonic() {
|
||||
let config = Config::default();
|
||||
assert_eq!(
|
||||
"https://testnet-milhon-validator1.nymtech.net/",
|
||||
config.get_nymd_validator_url().unwrap().to_string()
|
||||
);
|
||||
let mnemonic = Mnemonic::from_str("riot worth swear negative toward hood absent crater release net detect weasel profit wash market key smart member prize cancel awkward famous sauce sport").unwrap();
|
||||
let client = _connect_with_mnemonic(mnemonic, &config);
|
||||
assert!(client.contract_address().is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "nym-wallet",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"width": 1024,
|
||||
"height": 768,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
|
||||
@@ -13,26 +13,34 @@ const copy = (text: string): Promise<{ success: boolean; value: string }> => {
|
||||
})
|
||||
}
|
||||
|
||||
export const handleCopy = async ({
|
||||
text,
|
||||
cb,
|
||||
}: {
|
||||
text: string
|
||||
cb: (success: boolean) => void
|
||||
}) => {
|
||||
cb(false)
|
||||
const res = await copy(text)
|
||||
console.log(res)
|
||||
if (res.success) {
|
||||
cb(true)
|
||||
} else {
|
||||
console.log(res.value)
|
||||
}
|
||||
}
|
||||
|
||||
export const CopyToClipboard = ({ text }: { text: string }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = async () => {
|
||||
setCopied(false)
|
||||
const res = await copy(text)
|
||||
console.log(res)
|
||||
if (res.success) {
|
||||
setCopied(true)
|
||||
} else {
|
||||
console.log(res.value)
|
||||
}
|
||||
}
|
||||
const updateCopyStatus = (isCopied: boolean) => setCopied(isCopied)
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
aria-label="save"
|
||||
onClick={handleCopy}
|
||||
onClick={() => handleCopy({ text, cb: updateCopyStatus })}
|
||||
endIcon={copied && <Check style={{ color: green[500] }} />}
|
||||
>
|
||||
{copied ? 'Copied' : 'Copy'}
|
||||
|
||||
@@ -16,11 +16,12 @@ import {
|
||||
ExitToApp,
|
||||
HowToVote,
|
||||
MoneyOff,
|
||||
Description
|
||||
} from '@material-ui/icons'
|
||||
import { makeStyles } from '@material-ui/styles'
|
||||
import { ClientContext } from '../context/main'
|
||||
|
||||
const routesSchema = [
|
||||
let routesSchema = [
|
||||
{
|
||||
label: 'Balance',
|
||||
route: '/balance',
|
||||
@@ -58,6 +59,16 @@ const routesSchema = [
|
||||
},
|
||||
]
|
||||
|
||||
if (process.env.NODE_ENV) {
|
||||
routesSchema.push(
|
||||
{
|
||||
label: 'Docs',
|
||||
route: '/docs',
|
||||
Icon: <Description />,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
navItem: {
|
||||
color: '#fff',
|
||||
|
||||
+12
-4
@@ -10,6 +10,7 @@ import { ClientContext } from '../context/main'
|
||||
import { FileCopy, Refresh } from '@material-ui/icons'
|
||||
import { NymCard } from './NymCard'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { handleCopy } from './CopyToClipboard'
|
||||
|
||||
export const BalanceCard = () => {
|
||||
const theme = useTheme()
|
||||
@@ -41,7 +42,7 @@ export const BalanceCard = () => {
|
||||
{balanceError}
|
||||
</Alert>
|
||||
) : (
|
||||
<Typography>{balance}</Typography>
|
||||
<Typography>{balance?.amount}</Typography>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -52,7 +53,7 @@ export const BalanceCard = () => {
|
||||
|
||||
export const AddressCard = () => {
|
||||
const theme = useTheme()
|
||||
const { address } = useContext(ClientContext)
|
||||
const { clientDetails } = useContext(ClientContext)
|
||||
return (
|
||||
<div style={{ margin: theme.spacing(3) }}>
|
||||
<NymCard
|
||||
@@ -60,12 +61,19 @@ export const AddressCard = () => {
|
||||
subheader="Wallet payments address"
|
||||
noPadding
|
||||
Action={
|
||||
<IconButton>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
handleCopy({
|
||||
text: clientDetails?.client_address || '',
|
||||
cb: () => {},
|
||||
})
|
||||
}
|
||||
>
|
||||
<FileCopy />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<CardContent>{address}</CardContent>
|
||||
<CardContent>{clientDetails?.client_address}</CardContent>
|
||||
</NymCard>
|
||||
</div>
|
||||
)
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
import { AddressCard, BalanceCard } from './BalanceCard'
|
||||
import { Divider } from '@material-ui/core'
|
||||
import { AddressCard, BalanceCard } from './NavigationCards'
|
||||
import { Nav } from './Nav'
|
||||
import Logo from '../images/logo.png'
|
||||
import { theme } from '../theme'
|
||||
import { Divider } from '@material-ui/core'
|
||||
|
||||
export const Page = ({ children }: { children: React.ReactElement }) => {
|
||||
return (
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import React, { createContext, useEffect, useState } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { TBalance, TClientDetails } from '../types/global'
|
||||
|
||||
type TClientContext = {
|
||||
isLoggedIn: boolean
|
||||
address: string
|
||||
balance?: string
|
||||
clientDetails?: TClientDetails
|
||||
balance?: TBalance
|
||||
balanceLoading: boolean
|
||||
balanceError?: string
|
||||
logIn: () => void
|
||||
logIn: (clientDetails: TClientDetails) => void
|
||||
logOut: () => void
|
||||
getBalance: () => void
|
||||
}
|
||||
@@ -20,10 +20,10 @@ export const ClientContextProvider = ({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false)
|
||||
const [balance, setBalance] = useState<string>()
|
||||
const [balance, setBalance] = useState<TBalance>()
|
||||
const [balanceError, setBalanceError] = useState<string>()
|
||||
const [balanceLoading, setBalanceLoading] = useState(false)
|
||||
const [clientDetails, setClientDetails] = useState<TClientDetails>()
|
||||
|
||||
const history = useHistory()
|
||||
|
||||
@@ -32,26 +32,27 @@ export const ClientContextProvider = ({
|
||||
setBalanceError(undefined)
|
||||
invoke('get_balance')
|
||||
.then((balance) => {
|
||||
setBalance(balance as string)
|
||||
setBalance(balance as TBalance)
|
||||
})
|
||||
.catch((e) => setBalanceError(e))
|
||||
setBalanceLoading(false)
|
||||
}
|
||||
|
||||
const logIn = () => setIsLoggedIn(true)
|
||||
const logOut = () => setIsLoggedIn(false)
|
||||
const logIn = (clientDetails: TClientDetails) =>
|
||||
setClientDetails(clientDetails)
|
||||
|
||||
const logOut = () => setClientDetails(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
!isLoggedIn ? history.push('/signin') : history.push('/bond')
|
||||
}, [isLoggedIn])
|
||||
!clientDetails ? history.push('/signin') : history.push('/bond')
|
||||
}, [clientDetails])
|
||||
|
||||
return (
|
||||
<ClientContext.Provider
|
||||
value={{
|
||||
isLoggedIn,
|
||||
balance,
|
||||
balanceError,
|
||||
address: 'punk1s63y29jf8f3ft64z0vh80g3c76ty8lnyr74eur',
|
||||
clientDetails,
|
||||
balanceLoading,
|
||||
logIn,
|
||||
logOut,
|
||||
|
||||
@@ -9,10 +9,11 @@ import { Send } from './send'
|
||||
import { SignIn } from './sign-in'
|
||||
import { Unbond } from './unbond'
|
||||
import { Undelegate } from './undelegate'
|
||||
import { InternalDocs } from './internal-docs'
|
||||
|
||||
export const Routes = () => (
|
||||
<Switch>
|
||||
<Route path="/" exact>
|
||||
<Route path="/signin" exact>
|
||||
<SignIn />
|
||||
</Route>
|
||||
<Route path="/balance">
|
||||
@@ -36,8 +37,8 @@ export const Routes = () => (
|
||||
<Route path="/undelegate">
|
||||
<Undelegate />
|
||||
</Route>
|
||||
<Route path="/signin">
|
||||
<SignIn />
|
||||
<Route path="/docs">
|
||||
<InternalDocs />
|
||||
</Route>
|
||||
<Route path="*">
|
||||
<NotFound />
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
List,
|
||||
ListItem,
|
||||
TextField,
|
||||
Theme,
|
||||
} from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { DocEntry } from './DocEntry'
|
||||
|
||||
export const ApiList = () => {
|
||||
const [advancedShown, setAdvancedShown] = React.useState(false)
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<List>
|
||||
<ListItem><DocEntry function={{ name: 'connect_with_mnemonic', args: [{ name: 'mnemonic', type: 'str' }] }} /></ListItem>
|
||||
<ListItem><DocEntry function={{ name: 'get_balance', args: [] }} /></ListItem>
|
||||
</List>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
Theme,
|
||||
} from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
|
||||
interface DocEntryProps {
|
||||
function: FunctionDef;
|
||||
}
|
||||
|
||||
interface FunctionDef {
|
||||
name: string,
|
||||
args: ArgDef[]
|
||||
}
|
||||
|
||||
interface ArgDef {
|
||||
name: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
const argKey = (functionName: string, arg: string) => `${functionName}_${arg}`
|
||||
|
||||
function collectArgs(functionName: string, args: ArgDef[]) {
|
||||
let invokeArgs = {}
|
||||
for (let arg of args) {
|
||||
invokeArgs[arg.name] = document.getElementById(argKey(functionName, arg.name)).value
|
||||
}
|
||||
return invokeArgs
|
||||
}
|
||||
|
||||
export const DocEntry = (props: DocEntryProps) => {
|
||||
const [card, setCard] = React.useState(<Card />)
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
const onClick = () => {
|
||||
invoke(props.function.name, collectArgs(props.function.name, props.function.args)).then(
|
||||
(result) => {
|
||||
setCard(<Card><CardContent>{JSON.stringify(result, null, 4)}</CardContent></Card>)
|
||||
}
|
||||
).catch(
|
||||
(e) => setCard(<Card><CardContent>{e}</CardContent></Card>)
|
||||
)
|
||||
}
|
||||
|
||||
let fields = []
|
||||
for (let arg of props.function.args) {
|
||||
fields.push(<TextField
|
||||
label={arg.name}
|
||||
id={argKey(props.function.name, arg.name)}
|
||||
key={argKey(props.function.name, arg.name)} />
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
disableElevation
|
||||
onClick={onClick}>
|
||||
{props.function.name}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
disableElevation
|
||||
onClick={() => setCard(<Card />)}>X
|
||||
</Button>
|
||||
<div>{fields}</div>
|
||||
<br />
|
||||
{card}
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Layout, Page, NymCard } from '../../components'
|
||||
import { ApiList } from './ApiList'
|
||||
|
||||
export const InternalDocs = () => {
|
||||
if (process.env.NODE_ENV == 'development') {
|
||||
return (
|
||||
<Page>
|
||||
<Layout>
|
||||
<NymCard title="Docs" subheader="Internal API docs" noPadding>
|
||||
<ApiList />
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { theme } from '../theme'
|
||||
import { ClientContext } from '../context/main'
|
||||
|
||||
export const Receive = () => {
|
||||
const { address } = useContext(ClientContext)
|
||||
const { clientDetails } = useContext(ClientContext)
|
||||
const matches = useMediaQuery('(min-width:769px)')
|
||||
|
||||
return (
|
||||
@@ -35,9 +35,9 @@ export const Receive = () => {
|
||||
variant={matches ? 'h5' : 'subtitle1'}
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
{address}
|
||||
{clientDetails?.client_address}
|
||||
</Typography>
|
||||
<CopyToClipboard text={address} />
|
||||
<CopyToClipboard text={clientDetails?.client_address || ''} />
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import logo from '../images/logo.png'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { ClientContext } from '../context/main'
|
||||
import { TClientDetails } from '../types/global'
|
||||
|
||||
export const SignIn = () => {
|
||||
const [mnemonic, setMnemonic] = useState<string>('')
|
||||
@@ -24,7 +24,6 @@ export const SignIn = () => {
|
||||
const { logIn } = useContext(ClientContext)
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
const history = useHistory()
|
||||
|
||||
const handleSignIn = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
@@ -33,8 +32,8 @@ export const SignIn = () => {
|
||||
setInputError(undefined)
|
||||
|
||||
invoke('connect_with_mnemonic', { mnemonic })
|
||||
.then(() => {
|
||||
logIn()
|
||||
.then((res) => {
|
||||
logIn(res as TClientDetails)
|
||||
})
|
||||
.catch((e) => {
|
||||
setInputError(e)
|
||||
|
||||
@@ -3,7 +3,23 @@ export enum EnumNodeType {
|
||||
Gateway = 'Gateway',
|
||||
}
|
||||
|
||||
export enum EnumDemon {
|
||||
upunk = 'upunk',
|
||||
punk = 'punk',
|
||||
}
|
||||
|
||||
export type TNodeOwnership = {
|
||||
ownsMixnode: boolean
|
||||
ownsGateway: boolean
|
||||
}
|
||||
|
||||
export type TBalance = {
|
||||
amount: string
|
||||
demon: EnumDemon
|
||||
}
|
||||
|
||||
export type TClientDetails = {
|
||||
client_address: string
|
||||
contract_address: string
|
||||
denom: EnumDemon
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user