merge backend updates

This commit is contained in:
fmtabbara
2021-08-31 11:05:51 +01:00
18 changed files with 548 additions and 381 deletions
Generated
+286 -310
View File
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 }
+2
View File
@@ -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",
] }
+3 -3
View File
@@ -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()
+37 -24
View File
@@ -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());
}
}
+2 -2
View File
@@ -52,8 +52,8 @@
"windows": [
{
"title": "nym-wallet",
"width": 1920,
"height": 1080,
"width": 1024,
"height": 768,
"resizable": true,
"fullscreen": false
}
+19 -11
View File
@@ -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'}
+12 -1
View File
@@ -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',
@@ -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>
)
+2 -2
View File
@@ -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 (
+14 -13
View File
@@ -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,
+4 -3
View File
@@ -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
}
+3 -3
View File
@@ -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>
+3 -4
View File
@@ -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)
+16
View File
@@ -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
}