Compare commits

...

1 Commits

Author SHA1 Message Date
fmtabbara 8f7e51c093 remove browser extension 2024-06-10 16:55:43 +01:00
102 changed files with 0 additions and 2800 deletions
-8
View File
@@ -1,8 +0,0 @@
{
"extends": [
"@nymproject/eslint-config-react-typescript"
],
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}
-6
View File
@@ -1,6 +0,0 @@
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2
}
-7
View File
@@ -1,7 +0,0 @@
RPC_URL=
VALIDATOR_URL=
PREFIX=
MIXNET_CONTRACT_ADDRESS=
VESTING_CONTRACT_ADDRESS=
DENOM=
BLOCK_EXPLORER_URL=
View File
-40
View File
@@ -1,40 +0,0 @@
# Nym Browser Extension
The Nym browser extension lets you access your Nym wallet via the browser.
## Getting started
You will need:
- NodeJS (use `nvm install` to automatically install the correct version)
- `npm`
- `yarn`
> **Note**: This project is part of a mono repo, so you will need to build the shared packages before starting. And any time they change, you'll need to rebuild them.
From the [root of the repository](../README.md) run the following to build shared packages:
```
yarn
yarn build
```
From the `nym-browser-extension` directory of the `nym` monorepo, run:
`yarn dev` to run the extension in dev mode.
You can then open a browser to http://localhost:9000 and start development.
OR
`yarn build` to build the extension.
The extension will build to the `nym-browser-extension/dist` directory.
## Load extension
To load the extension into a Chrome browser
- Go to `settings > extensions > manage extensions`
- Select `Load unpacked`
- Select the `nym-browser-extension/dist`
-86
View File
@@ -1,86 +0,0 @@
{
"name": "@nym/browser-extension",
"version": "0.1.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "yarn webpack serve --config webpack.dev.js",
"build": "yarn preinstall && webpack build --progress --config webpack.prod.js",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"lint:ts": "tsc --noEmit",
"storybook": "start-storybook -p 6006",
"storybook:build": "build-storybook"
},
"dependencies": {
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.7.0",
"@hookform/resolvers": "^3.1.0",
"@mui/icons-material": "^5.11.11",
"@mui/material": "^5.11.15",
"@mui/system": "^5.11.15",
"@nymproject/mui-theme": "^1.0.0",
"@nymproject/nym-validator-client": ">=1.2.0-rc.0 || 1",
"@nymproject/react": "^1.0.0",
"@nymproject/types": "^1.0.0",
"@nymproject/extension-storage": ">=1.2.0-rc.0 || 1",
"@storybook/react": "^6.5.16",
"big.js": "^6.2.1",
"crypto-js": "^4.1.1",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.3",
"react-hook-form": "^7.43.9",
"react-router-dom": "^6.9.0",
"zod": "^3.21.4"
},
"devDependencies": {
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@svgr/webpack": "^6.1.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^14.0.0",
"@types/big.js": "^6.1.6",
"@types/crypto-js": "4.1.1",
"@types/jest": "^27.0.1",
"@types/node": "^18.16.1",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"copy-webpack-plugin": "^11.0.0",
"dotenv-webpack": "^8.0.1",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-storybook": "^0.5.12",
"favicons-webpack-plugin": "^5.0.2",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"prettier": "^2.8.7",
"react-refresh": "^0.14.0",
"react-refresh-typescript": "^2.0.8",
"style-loader": "^3.3.1",
"ts-jest": "^27.0.5",
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.2",
"url-loader": "^4.1.1",
"util": "^0.12.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
}
}
-15
View File
@@ -1,15 +0,0 @@
import React from 'react';
import { NymBrowserExtThemeWithMode } from './theme/NymBrowserExtensionTheme';
import { AppRoutes } from './routes';
import { AppLayout } from './layouts/AppLayout';
import { AppProvider } from './context';
export const App = () => (
<NymBrowserExtThemeWithMode mode="light">
<AppProvider>
<AppLayout>
<AppRoutes />
</AppLayout>
</AppProvider>
</NymBrowserExtThemeWithMode>
);
@@ -1,47 +0,0 @@
import React from 'react';
import { Avatar, ListItem, ListItemAvatar, ListItemButton, ListItemText } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { useAppContext } from 'src/context';
import { AccountActions } from './Actions';
const AccountItem = ({
accountName,
disabled,
onSelect,
}: {
accountName: string;
disabled: boolean;
onSelect: () => void;
}) => (
<ListItem disableGutters disablePadding secondaryAction={<AccountActions accountName={accountName} />} divider>
<ListItemButton onClick={onSelect} disabled={disabled}>
<ListItemAvatar>
<Avatar>{accountName[0]}</Avatar>
</ListItemAvatar>
<ListItemText primary={accountName} secondary={disabled && '(Selected)'} />
</ListItemButton>
</ListItem>
);
export const AccountList = () => {
const navigate = useNavigate();
const { accounts, selectAccount, selectedAccount } = useAppContext();
const handleSelectAccount = async (accountName: string) => {
await selectAccount(accountName);
navigate('/user/balance');
};
return (
<>
{accounts.map((accountName) => (
<AccountItem
disabled={selectedAccount === accountName}
accountName={accountName}
key={accountName}
onSelect={() => handleSelectAccount(accountName)}
/>
))}
</>
);
};
@@ -1,55 +0,0 @@
import React from 'react';
import { IconButton, ListItemIcon, ListItemText, Menu, MenuItem } from '@mui/material';
import { MoreVert, VisibilityOutlined } from '@mui/icons-material';
import { useAppContext } from 'src/context';
type ActionType = {
title: string;
Icon: React.ReactNode;
onSelect: () => void;
};
const ActionItem = ({ action }: { action: ActionType }) => (
<MenuItem dense onClick={action.onSelect}>
<ListItemIcon>{action.Icon}</ListItemIcon>
<ListItemText>{action.title}</ListItemText>
</MenuItem>
);
export const AccountActions = ({ accountName }: { accountName: string }) => {
const { setShowSeedForAccount } = useAppContext();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const actions: Array<ActionType> = [
{
title: 'View seed phrase',
Icon: <VisibilityOutlined />,
onSelect: () => {
setShowSeedForAccount(accountName);
},
},
];
return (
<>
<IconButton onClick={handleClick}>
<MoreVert />
</IconButton>
<Menu anchorEl={anchorEl} id="account-menu" open={open} onClose={handleClose} onClick={handleClose}>
{actions.map((action) => (
<ActionItem action={action} key={action.title} />
))}
</Menu>
</>
);
};
@@ -1,68 +0,0 @@
import React, { useState } from 'react';
import { Box, Card, CardContent, Typography } from '@mui/material';
import { PasswordInput } from '@nymproject/react/textfields/Password';
import { ExtensionStorage } from '@nymproject/extension-storage';
import { Button, ConfirmationModal } from 'src/components/ui';
const ShowSeedButton = ({ handleShowSeedPhrase }: { handleShowSeedPhrase: () => void }) => (
<Button fullWidth variant="contained" onClick={handleShowSeedPhrase}>
Show seed phrase
</Button>
);
const DoneButton = ({ onDone }: { onDone: () => void }) => (
<Button fullWidth variant="contained" onClick={onDone}>
Done
</Button>
);
const Seed = ({ seed }: { seed: string }) => (
<Card>
<CardContent>
<Typography>{seed}</Typography>
</CardContent>
</Card>
);
export const ViewSeedPhrase = ({ accountName, onDone }: { accountName: string; onDone: () => void }) => {
const [seed, setSeed] = useState<string>();
const [password, setPassword] = useState('');
const [error, setError] = useState<string>();
const handleShowSeedPhrase = async () => {
try {
const storage = await new ExtensionStorage(password);
const accountSeed = await storage.read_mnemonic(accountName);
setSeed(accountSeed);
} catch (e) {
setError('Could not retrieve seed phrase. Please check your password');
}
};
return (
<ConfirmationModal
open
onClose={onDone}
title={seed ? 'Account seed phrase' : 'Password'}
subtitle={seed ? '' : 'Enter your account password'}
ConfirmButton={
seed ? <DoneButton onDone={onDone} /> : <ShowSeedButton handleShowSeedPhrase={handleShowSeedPhrase} />
}
>
{seed ? (
<Seed seed={seed} />
) : (
<Box sx={{ mt: 2 }}>
<PasswordInput
label="Password"
error={error}
password={password}
onUpdatePassword={(pw: string) => {
setPassword(pw);
}}
/>
</Box>
)}
</ConfirmationModal>
);
};
@@ -1,3 +0,0 @@
export * from './Accounts';
export * from './Actions';
export * from './ViewSeedPhrase';
@@ -1,16 +0,0 @@
import React from 'react';
import { Typography } from '@mui/material';
import { Stack } from '@mui/system';
import { ClientAddress } from '@nymproject/react/client-address/ClientAddress';
import { useAppContext } from 'src/context';
export const Address = () => {
const { client } = useAppContext();
return (
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography fontWeight={700}>Address</Typography>
<ClientAddress withCopy address={client?.address || ''} />
</Stack>
);
};
@@ -1,23 +0,0 @@
import React, { useEffect } from 'react';
import { Stack, Typography } from '@mui/material';
import { useAppContext } from 'src/context';
export const Balance = () => {
const { balance, fiatBalance, currency, getBalance } = useAppContext();
useEffect(() => {
getBalance();
}, []);
const fiat = fiatBalance ? `~ ${Intl.NumberFormat().format(fiatBalance)} ${currency.toUpperCase()}` : '-';
return (
<Stack alignItems="center" gap={1}>
<Typography sx={{ color: 'grey.600' }}>Available</Typography>
<Typography variant="h4" textAlign="center">
{balance} NYM
</Typography>
<Typography sx={{ color: 'grey.600' }}>{fiat}</Typography>
</Stack>
);
};
@@ -1,6 +0,0 @@
export * from './accounts';
export * from './address';
export * from './balance';
export * from './receive';
export * from './send';
export * from './ui';
@@ -1,35 +0,0 @@
import React from 'react';
import { Card, CardContent, Dialog, DialogContent, DialogTitle, IconButton, Stack, Typography } from '@mui/material';
import { QRCodeSVG } from 'qrcode.react';
import { useAppContext } from 'src/context';
import { ClientAddress } from '@nymproject/react/client-address/ClientAddress';
import { Close } from '@mui/icons-material';
export const ReceiveModal = ({ open, onClose }: { open: boolean; onClose: () => void }) => {
const { client } = useAppContext();
return (
<Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
<DialogTitle>
<Stack direction="row" justifyContent="space-between">
<Typography fontWeight={700}>Receive</Typography>
<IconButton size="small" onClick={onClose} sx={{ padding: 0 }}>
<Close fontSize="small" />
</IconButton>
</Stack>
</DialogTitle>
<DialogContent>
<Stack gap={1} alignItems="center">
<Card elevation={3} sx={{ my: 2, width: 200 }}>
<CardContent sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<QRCodeSVG value={client?.address || ''} />
</CardContent>
</Card>
<Typography variant="body2" fontWeight={700}>
Your Nym address
</Typography>
<ClientAddress address={client?.address || ''} withCopy smallIcons />
</Stack>
</DialogContent>
</Dialog>
);
};
@@ -1 +0,0 @@
export * from './ReceiveModal';
@@ -1,30 +0,0 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { ConfirmationModal, Button } from 'src/components/ui';
export const SendConfirmationModal = ({
amount,
txUrl,
onConfirm,
}: {
amount: string;
txUrl: string;
onConfirm: () => void;
}) => (
<ConfirmationModal
open
fullWidth
title="You sent"
ConfirmButton={
<Button fullWidth variant="contained" size="large" onClick={onConfirm}>
Done
</Button>
}
>
<Box>
<Typography variant="h6">{amount}</Typography>
<Link href={txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />
</Box>
</ConfirmationModal>
);
@@ -1 +0,0 @@
export * from './SendConfirmationModal';
@@ -1,14 +0,0 @@
import * as React from 'react';
import { AppBar as MUIAppBar } from '@mui/material/';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
export const AppBar = ({ Action }: { Action: React.ReactNode }) => (
<Box sx={{ flexGrow: 1 }}>
<MUIAppBar position="static" elevation={0} sx={{ bgcolor: 'rgba(103, 80, 164, 0.14)' }}>
<Toolbar variant="dense">{Action}</Toolbar>
</MUIAppBar>
</Box>
);
export default AppBar;
@@ -1,23 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { ArrowBackIosRounded } from '@mui/icons-material';
import { IconButton } from '@mui/material';
export const BackButton = ({ onBack }: { onBack?: () => void }) => {
const navigate = useNavigate();
const handleClick = () => {
if (onBack) {
onBack();
} else {
navigate(-1);
}
return undefined;
};
return (
<IconButton size="small" onClick={handleClick}>
<ArrowBackIosRounded fontSize="small" />
</IconButton>
);
};
@@ -1,6 +0,0 @@
import React from 'react';
import { Button as MUIButton, ButtonProps } from '@mui/material';
export const Button = (props: ButtonProps) => (
<MUIButton {...props} disableElevation sx={{ textTransform: 'initial' }} />
);
@@ -1,6 +0,0 @@
import React from 'react';
import { NymLogoBW } from '@nymproject/react/logo/NymLogoBW';
export const Logo = ({ small }: { small?: boolean }) => (
<NymLogoBW width={small ? '37.5px' : '75px'} height={small ? '37.5px' : '75px'} />
);
@@ -1,20 +0,0 @@
import React from 'react';
import { Stack, Typography } from '@mui/material';
import { Logo } from '../Logo';
import { Title } from '../Title';
export const LogoWithText = ({
logoSmall,
title,
description,
}: {
logoSmall?: boolean;
title: string;
description?: string;
}) => (
<Stack alignItems="center" justifyContent="center" gap={3}>
<Logo small={logoSmall} />
<Title>{title}</Title>
<Typography sx={{ color: 'grey.700', textAlign: 'center' }}>{description}</Typography>
</Stack>
);
@@ -1,55 +0,0 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Drawer from '@mui/material/Drawer';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import { AccountBalanceWalletRounded, AccountCircleRounded, ArrowDownwardRounded } from '@mui/icons-material';
import { Link } from 'react-router-dom';
const menuSchema = [
{
title: 'Accounts',
Icon: <AccountCircleRounded />,
path: '/user/accounts',
},
{
title: 'Balance',
Icon: <AccountBalanceWalletRounded />,
path: '/user/balance',
},
{
title: 'Send',
Icon: <ArrowDownwardRounded />,
path: '/user/send',
},
];
export const MenuDrawer = ({ open, onClose }: { open: boolean; onClose: () => void }) => {
const list = () => (
<Box sx={{ width: 250 }} role="presentation" onClick={() => {}}>
<List>
{menuSchema.map(({ title, Icon, path }) => (
<Link to={path} style={{ textDecoration: 'none', color: 'unset' }} key={title}>
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>{Icon}</ListItemIcon>
<ListItemText primary={title} />
</ListItemButton>
</ListItem>
</Link>
))}
</List>
</Box>
);
return (
<div>
<Drawer anchor="left" open={open} onClose={onClose}>
{list()}
</Drawer>
</div>
);
};
@@ -1,74 +0,0 @@
import React from 'react';
import {
Breakpoint,
Paper,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
SxProps,
Typography,
} from '@mui/material';
import { Button } from '../Button';
export interface ErrorModalProps {
open: boolean;
children?: React.ReactNode;
title: React.ReactNode | string;
subtitle?: React.ReactNode | string;
sx?: SxProps;
fullWidth?: boolean;
maxWidth?: Breakpoint;
backdropProps?: object;
onClose?: () => void;
}
export const ErrorModal = ({
open,
onClose,
children,
title,
subtitle,
sx,
fullWidth,
maxWidth,
backdropProps,
}: ErrorModalProps) => {
const Title = (
<DialogTitle id="responsive-dialog-title" sx={{ pb: 2 }}>
<Typography variant="body2" fontWeight={600}>
{title}
</Typography>
{subtitle &&
(typeof subtitle === 'string' ? (
<Typography fontWeight={400} variant="subtitle1" fontSize={12} sx={{ color: 'grey.400' }}>
{subtitle}
</Typography>
) : (
subtitle
))}
</DialogTitle>
);
return (
<Dialog
open={open}
onClose={onClose}
aria-labelledby="responsive-dialog-title"
maxWidth={maxWidth || 'sm'}
sx={{ textAlign: 'center', ...sx }}
fullWidth={fullWidth}
BackdropProps={backdropProps}
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
>
{Title}
<DialogContent>{children}</DialogContent>
<DialogActions sx={{ px: 3, pb: 3 }}>
<Button variant="contained" size="large" fullWidth onClick={onClose}>
Close
</Button>
</DialogActions>
</Dialog>
);
};
@@ -1,25 +0,0 @@
import React from 'react';
import { Box, CircularProgress, Modal, Stack, Typography, SxProps } from '@mui/material';
const modalStyle: SxProps = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 300,
bgcolor: 'background.paper',
boxShadow: 24,
borderRadius: '16px',
p: 4,
};
export const LoadingModal = ({ sx, backdropProps }: { sx?: SxProps; backdropProps?: object }) => (
<Modal open BackdropProps={backdropProps}>
<Box sx={{ border: (t) => `1px solid ${t.palette.grey[500]}`, ...modalStyle, ...sx }} textAlign="center">
<Stack spacing={4} direction="row" alignItems="center">
<CircularProgress />
<Typography sx={{ color: 'text.primary' }}>Please wait...</Typography>
</Stack>
</Box>
</Modal>
);
@@ -1,71 +0,0 @@
import React from 'react';
import {
Breakpoint,
Paper,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
SxProps,
Typography,
} from '@mui/material';
export interface ConfirmationModalProps {
open: boolean;
children?: React.ReactNode;
title: React.ReactNode | string;
subtitle?: React.ReactNode | string;
ConfirmButton: React.ReactNode;
sx?: SxProps;
fullWidth?: boolean;
maxWidth?: Breakpoint;
backdropProps?: object;
onClose?: () => void;
}
export const ConfirmationModal = ({
open,
onClose,
children,
title,
subtitle,
ConfirmButton,
sx,
fullWidth,
maxWidth,
backdropProps,
}: ConfirmationModalProps) => {
const Title = (
<DialogTitle id="responsive-dialog-title" sx={{ pb: 2 }}>
<Typography variant="body2" fontWeight={600}>
{title}
</Typography>
{subtitle &&
(typeof subtitle === 'string' ? (
<Typography fontWeight={400} variant="subtitle1" fontSize={12} sx={{ color: 'grey.400' }}>
{subtitle}
</Typography>
) : (
subtitle
))}
</DialogTitle>
);
return (
<Dialog
open={open}
onClose={onClose}
aria-labelledby="responsive-dialog-title"
maxWidth={maxWidth || 'sm'}
sx={{ textAlign: 'center', ...sx }}
fullWidth={fullWidth}
BackdropProps={backdropProps}
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
>
{Title}
<DialogContent>{children}</DialogContent>
<DialogActions sx={{ px: 3, pb: 3 }}>{ConfirmButton}</DialogActions>
</Dialog>
);
};
@@ -1,3 +0,0 @@
export * from './Modal';
export * from './LoadingModal';
export * from './ErrorModal';
@@ -1,10 +0,0 @@
import React from 'react';
import { Typography } from '@mui/material';
const FONT_WEIGHT = 400;
export const Title = ({ children }: { children: string }) => (
<Typography variant="h5" fontWeight={FONT_WEIGHT}>
{children}
</Typography>
);
@@ -1,8 +0,0 @@
export * from './AppBar';
export * from './Button';
export * from './BackButton';
export * from './Logo';
export * from './LogoWithText';
export * from './MenuDrawer';
export * from './Modal';
export * from './Title';
-8
View File
@@ -1,8 +0,0 @@
export const config = {
rpcUrl: process.env.RPC_URL || '',
validatorUrl: process.env.VALIDATOR_URL || '',
prefix: process.env.PREFIX || '',
mixnetContractAddress: process.env.MIXNET_CONTRACT_ADDRESS || '',
vestingContractAddress: process.env.VESTING_CONTRACT_ADDRESS || '',
denom: process.env.DENOM || '',
};
-109
View File
@@ -1,109 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import ValidatorClient from '@nymproject/nym-validator-client';
import { ExtensionStorage } from '@nymproject/extension-storage';
import { connectToValidator } from 'src/validator-client';
import { unymToNym } from 'src/utils/coin';
import { Currency, getTokenPrice } from 'src/utils/price';
type TAppContext = {
client?: ValidatorClient;
accounts: string[];
balance?: string;
fiatBalance?: number;
denom: 'NYM';
minorDenom: 'unym';
currency: Currency;
showSeedForAccount?: string;
selectedAccount: string;
storage?: ExtensionStorage;
selectAccount: (accountName: string) => Promise<void>;
setAccounts: (accounts: string[]) => void;
setShowSeedForAccount: (accountName?: string) => void;
handleUnlockWallet: (password: string) => void;
getBalance: () => void;
};
type TBalanceInNYMs = string;
const DEFAULT_ACCOUNT_NAME = 'Default account';
const AppContext = React.createContext({} as TAppContext);
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const [client, setClient] = useState<ValidatorClient>();
const [selectedAccount, setSelected] = useState<string>(DEFAULT_ACCOUNT_NAME);
const [balance, setBalance] = useState<TBalanceInNYMs>();
const [fiatBalance, setFiatBalance] = useState<number>();
const [accounts, setAccounts] = useState<string[]>([]);
const [showSeedForAccount, setShowSeedForAccount] = useState<string>();
const [storage, setStorage] = useState<ExtensionStorage>();
const denom = 'NYM';
const minorDenom = 'unym';
const currency = 'gbp';
const handleUnlockWallet = async (password: string) => {
const store = await new ExtensionStorage(password);
const mnemonic = await store.read_mnemonic(DEFAULT_ACCOUNT_NAME);
const userAccounts = await store.get_all_mnemonic_keys();
const clientFromMnemonic = await connectToValidator(mnemonic);
setStorage(store);
setAccounts(userAccounts);
setClient(clientFromMnemonic);
};
const selectAccount = async (accountName: string) => {
const mnemonic = await storage!.read_mnemonic(accountName);
const clientFromMnemonic = await connectToValidator(mnemonic);
setSelected(accountName);
setClient(clientFromMnemonic);
};
const getFiatBalance = async (bal: number) => {
const tokenPrice = await getTokenPrice('nym', currency);
const fiatBal = tokenPrice.nym.gbp * bal;
return fiatBal;
};
const getBalance = async () => {
const bal = await client?.getBalance(client.address);
if (bal) {
const nym = unymToNym(Number(bal.amount));
const fiat = await getFiatBalance(nym);
setFiatBalance(fiat);
setBalance(nym.toString());
}
};
useEffect(() => {
if (client) {
getBalance();
}
}, [client]);
const value = useMemo<TAppContext>(
() => ({
client,
accounts,
balance,
fiatBalance,
currency,
denom,
minorDenom,
selectedAccount,
storage,
handleUnlockWallet,
getBalance,
setShowSeedForAccount,
showSeedForAccount,
setAccounts,
selectAccount,
}),
[client, accounts, balance, fiatBalance, denom, minorDenom, selectedAccount, showSeedForAccount, storage],
);
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
export const useAppContext = () => React.useContext(AppContext);
@@ -1,3 +0,0 @@
export * from './app';
export * from './send';
export * from './register';
@@ -1,71 +0,0 @@
import React, { useMemo, useState } from 'react';
import { ExtensionStorage } from '@nymproject/extension-storage';
const RegisterContext = React.createContext({} as TRegisterContext);
type TRegisterContext = {
userPassword: string;
userMnemonic: string;
accountName: string;
checkAccountName: () => Promise<boolean>;
setUserPassword: (password: string) => void;
setUserMnemonic: (mnemonic: string) => void;
setAccountName: (name: string) => void;
createAccount: (args: { mnemonic: string; password: string; accName: string }) => Promise<void>;
importAccount: () => Promise<string[]>;
resetState: () => void;
};
export const RegisterContextProvider = ({ children }: { children: React.ReactNode }) => {
const [userPassword, setUserPassword] = useState('');
const [userMnemonic, setUserMnemonic] = useState('');
const [accountName, setAccountName] = useState('');
const resetState = () => {
setUserMnemonic('');
setUserPassword('');
setAccountName('');
};
const createAccount = async ({
mnemonic,
password,
accName,
}: {
mnemonic: string;
password: string;
accName: string;
}) => {
const storage = await new ExtensionStorage(password);
await storage.store_mnemonic(accName, mnemonic);
};
const importAccount = async () => {
const storage = await new ExtensionStorage(userPassword);
await storage.store_mnemonic(accountName, userMnemonic);
const accounts = await storage.get_all_mnemonic_keys();
return accounts;
};
const checkAccountName = async () => true;
const value = useMemo(
() => ({
userPassword,
setUserPassword,
userMnemonic,
accountName,
setAccountName,
setUserMnemonic,
createAccount,
checkAccountName,
importAccount,
resetState,
}),
[userPassword, userMnemonic, accountName],
);
return <RegisterContext.Provider value={value}>{children}</RegisterContext.Provider>;
};
export const useRegisterContext = () => React.useContext(RegisterContext);
-112
View File
@@ -1,112 +0,0 @@
import React, { useMemo, useState } from 'react';
import { DecCoin } from '@nymproject/types';
import { useNavigate } from 'react-router-dom';
import { nymToUnym } from 'src/utils/coin';
import { TTransaction } from 'src/types';
import { Fee, useGetFee } from 'src/hooks/useGetFee';
import { createFeeObject } from 'src/utils/fee';
import { useAppContext } from './app';
type TSendContext = {
address?: string;
amount?: DecCoin;
transaction?: TTransaction;
fee?: Fee;
handleChangeAddress: (address?: string) => void;
handleChangeAmount: (amount?: DecCoin) => void;
handleSend: () => void;
resetTx: () => void;
onDone: () => void;
handleGetFee: (address: string, amount: string) => Promise<void>;
};
const SendContext = React.createContext({} as TSendContext);
export const SendProvider = ({ children }: { children: React.ReactNode }) => {
const [address, setAddress] = useState<string>();
const [amount, setAmount] = useState<DecCoin>();
const [transaction, setTransaction] = useState<TTransaction>();
const { client, minorDenom } = useAppContext();
const navigate = useNavigate();
const handleChangeAddress = (_address?: string) => setAddress(_address);
const handleChangeAmount = (_amount?: DecCoin) => setAmount(_amount);
const { getFee, fee } = useGetFee();
const handleGetFee = async (addressVal: string, amountVal: string) => {
const unym = nymToUnym(Number(amountVal));
if (client) {
// client loses its 'this' context when passing the method
// TODO find a better way of doing this.
getFee(client.simulateSend.bind(client), {
signingAddress: client.address,
from: client.address,
to: addressVal,
amount: [{ amount: unym.toString(), denom: minorDenom }],
});
}
};
const handleSend = async () => {
setTransaction({ status: 'loading', type: 'send' });
let unyms;
if (!Number(amount?.amount)) {
setTransaction({ status: 'error', type: 'send', message: 'Amount is not a valid number' });
}
if (amount) {
unyms = nymToUnym(Number(amount.amount));
}
if (client && address && unyms) {
try {
const response = await client.send(
address,
[{ amount: unyms.toString(), denom: minorDenom }],
createFeeObject(fee?.unym),
);
setTransaction({ status: 'success', type: 'send', txHash: response?.transactionHash });
} catch (e) {
setTransaction({
status: 'error',
type: 'send',
message: e instanceof Error ? e.message : 'Error making send transaction. Please try again',
});
}
}
};
const resetTx = () => {
setTransaction(undefined);
};
const onDone = () => {
navigate('/user/balance');
};
const value = useMemo<TSendContext>(
() => ({
address,
amount,
transaction,
fee,
handleChangeAddress,
handleChangeAmount,
handleSend,
resetTx,
onDone,
handleGetFee,
}),
[address, amount, transaction, fee],
);
return <SendContext.Provider value={value}>{children}</SendContext.Provider>;
};
export const useSendContext = () => React.useContext(SendContext);
@@ -1,21 +0,0 @@
import { useState } from 'react';
export const useCreatePassword = () => {
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isSafePassword, setIsSafePassword] = useState(false);
const [hasReadTerms, setHasReadTerms] = useState(false);
const canProceed = isSafePassword && hasReadTerms && password === confirmPassword;
return {
password,
setPassword,
confirmPassword,
setConfirmPassword,
setIsSafePassword,
canProceed,
setHasReadTerms,
hasReadTerms,
};
};
@@ -1,40 +0,0 @@
import Big from 'big.js';
import { useState } from 'react';
import { unymToNym } from 'src/utils/coin';
export type Fee = { nym: number; unym: number };
export function useGetFee() {
const [fee, setFee] = useState<Fee>();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string>();
async function getFee<T>(txReq: (args: T) => Promise<number | undefined>, args: T) {
setError(undefined);
setIsLoading(true);
try {
const txFee = await txReq(args);
if (txFee) {
const feeWithMultiplyer = Big(txFee).mul(1);
console.log(fee);
const txFeeInNyms = unymToNym(feeWithMultiplyer);
setFee({ nym: Number(txFeeInNyms), unym: Number(feeWithMultiplyer) });
}
if (!txFee) {
setError('Unable to calculate fee');
}
} catch (e) {
console.error(e);
setError(`Unable to get estimated fee: ${e}`);
} finally {
setIsLoading(false);
}
}
return { fee, getFee, isLoading, error };
}
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nym browser extension</title>
</head>
<body style="width: 360px; height: 600px">
<div id="root"></div>
</body>
</html>
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
const rootDomElem = document.getElementById('root');
if (rootDomElem) {
const root = createRoot(rootDomElem);
root.render(<App />);
}
@@ -1,8 +0,0 @@
import { Container } from '@mui/material';
import React from 'react';
export const AppLayout = ({ children }: { children: React.ReactNode }) => (
<Container maxWidth="xs" disableGutters sx={{ height: '100vh' }}>
{children}
</Container>
);
@@ -1,29 +0,0 @@
import React from 'react';
import { Box } from '@mui/material';
import { LogoWithText } from 'src/components/ui';
const layoutStyle = {
height: '100%',
display: 'grid',
gridTemplateColumns: '1fr',
gridTemplateRows: 'repeat(3, 1fr)',
gridColumnGap: '0px',
gridRowGap: '0px',
p: 2,
};
export const CenteredLogoLayout = ({
title,
description,
Actions,
}: {
title: string;
description?: string;
Actions: React.ReactNode;
}) => (
<Box sx={layoutStyle}>
<Box />
<LogoWithText title={title} description={description} />
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'flex-end' }}>{Actions}</Box>
</Box>
);
@@ -1,38 +0,0 @@
import React, { useCallback, useState } from 'react';
import { Box, IconButton } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import { AppBar, BackButton, MenuDrawer } from 'src/components/ui';
import { useLocation } from 'react-router-dom';
const layoutStyle = {
display: 'grid',
gridTemplateColumns: '1fr',
gridTemplateRows: '50px 1fr',
gridColumnGap: '0px',
gridRowGap: '0px',
};
export const PageLayout = ({ children, onBack }: { children: React.ReactNode; onBack?: () => void }) => {
const [menuOpen, setMenuOpen] = useState(false);
const location = useLocation();
const MenuAction = useCallback(
() => (
<IconButton onClick={() => setMenuOpen(true)}>
<MenuIcon />
</IconButton>
),
[],
);
const Action = location.pathname.includes('balance') ? MenuAction : BackButton;
return (
<Box sx={layoutStyle}>
<AppBar Action={<Action onBack={onBack} />} />
<MenuDrawer open={menuOpen} onClose={() => setMenuOpen(false)} />
<Box sx={{ p: 2 }}>{children}</Box>
</Box>
);
};
@@ -1,37 +0,0 @@
import React from 'react';
import { Box } from '@mui/material';
import { BackButton, LogoWithText } from 'src/components/ui';
const layoutStyle = {
height: '100%',
display: 'grid',
gridTemplateColumns: '1fr',
gridTemplaterows: '1fr 2fr 1fr',
gridColumnGap: '0px',
gridRowGap: '0px',
position: 'relative',
p: 2,
};
export const TopLogoLayout = ({
title,
description,
children,
Actions,
}: {
title: string;
description?: string;
children: React.ReactNode;
Actions: React.ReactNode;
}) => (
<Box sx={layoutStyle}>
<Box sx={{ position: 'absolute', top: 16, left: 16 }}>
<BackButton />
</Box>
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'center' }}>
<LogoWithText logoSmall title={title} description={description} />
</Box>
<Box>{children}</Box>
<Box sx={{ display: 'flex', alignItems: 'flex-end', width: '100%' }}>{Actions}</Box>
</Box>
);
@@ -1,3 +0,0 @@
export * from './AppLayout';
export * from './CenteredLogo';
export * from './TopLogo';
-18
View File
@@ -1,18 +0,0 @@
{
"name": "Nym browser extension",
"description": "Nym browser extension - Wallet & credentials",
"version": "0.1.0",
"manifest_version": 3,
"action": {
"default_popup": "index.html",
"default_title": "Nym - Browser extension"
},
"icons": {
"16": "favicon-16x16.png",
"32": "favicon-32x32.png",
"48": "favicon-48x48.png"
},
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}
@@ -1,43 +0,0 @@
import React, { useEffect } from 'react';
import { PageLayout } from 'src/layouts/PageLayout';
import { Stack } from '@mui/material';
import { Add, ArrowDownward } from '@mui/icons-material';
import { AccountList, Button } from 'src/components';
import { ViewSeedPhrase } from 'src/components/accounts/ViewSeedPhrase';
import { useAppContext, useRegisterContext } from 'src/context';
import { useLocation, useNavigate } from 'react-router-dom';
export const Accounts = () => {
const { showSeedForAccount, setShowSeedForAccount } = useAppContext();
const { resetState } = useRegisterContext();
useEffect(() => {
resetState();
}, []);
const location = useLocation();
const navigate = useNavigate();
const handleAddAccount = () => navigate(`${location.pathname}/add-account`);
const handleImportAccount = () => navigate(`${location.pathname}/import-account`);
const onBack = () => navigate('/user/balance');
return (
<PageLayout onBack={onBack}>
{showSeedForAccount && (
<ViewSeedPhrase accountName={showSeedForAccount} onDone={() => setShowSeedForAccount(undefined)} />
)}
<AccountList />
<Stack gap={1} alignItems="start" sx={{ mt: 2 }}>
<Button startIcon={<Add />} onClick={handleAddAccount}>
Add account
</Button>
<Button startIcon={<ArrowDownward />} onClick={handleImportAccount}>
Import account
</Button>
</Stack>
</PageLayout>
);
};
@@ -1,16 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useRegisterContext } from 'src/context';
import { SeedPhraseTemplate } from 'src/pages/templates';
export const AddAccount = () => {
const { setUserMnemonic } = useRegisterContext();
const navigate = useNavigate();
const onNext = (seedPhrase: string) => {
setUserMnemonic(seedPhrase);
navigate('/user/accounts/name-account');
};
return <SeedPhraseTemplate onNext={onNext} />;
};
@@ -1,14 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { SetupCompleteTemplate } from 'src/pages/templates/Complete';
export const SetupComplete = () => {
const navigate = useNavigate();
const handleOnDone = () => {
navigate('/user/accounts');
};
return (
<SetupCompleteTemplate title="You're all set!" description="Account successfully imported" onDone={handleOnDone} />
);
};
@@ -1,43 +0,0 @@
import React, { useState } from 'react';
import { PasswordInput } from '@nymproject/react/textfields/Password';
import { Button } from 'src/components';
import { useAppContext, useRegisterContext } from 'src/context';
import { TopLogoLayout } from 'src/layouts';
import { useNavigate } from 'react-router-dom';
export const ConfirmPassword = () => {
const { setAccounts } = useAppContext();
const { userPassword, setUserPassword, importAccount } = useRegisterContext();
const [error, setError] = useState<string>();
const navigate = useNavigate();
const handleOnComplete = async () => {
try {
const accounts = await importAccount();
setAccounts(accounts);
navigate('/user/accounts/complete');
} catch (e) {
setError('Incorrect password. Please try again');
}
};
const onChange = (password: string) => {
setError(undefined);
setUserPassword(password);
};
return (
<TopLogoLayout
title="Confirm password"
description="Confirm password to import account"
Actions={
<Button fullWidth variant="contained" size="large" onClick={handleOnComplete} disabled={!!error}>
Confirm
</Button>
}
>
<PasswordInput value={userPassword} onUpdatePassword={onChange} error={error} />
</TopLogoLayout>
);
};
@@ -1,17 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useRegisterContext } from 'src/context/register';
import { ImportAccountTemplate } from '../templates';
export const ImportAccount = () => {
const { userMnemonic, setUserMnemonic } = useRegisterContext();
const navigate = useNavigate();
const handleOnNext = () => {
navigate('/user/accounts/name-account');
};
return (
<ImportAccountTemplate userMnemonic={userMnemonic} onChangeUserMnemonic={setUserMnemonic} onNext={handleOnNext} />
);
};
@@ -1,47 +0,0 @@
import React, { useState } from 'react';
import { TextField } from '@mui/material';
import { Button } from 'src/components';
import { useRegisterContext } from 'src/context/register';
import { TopLogoLayout } from 'src/layouts';
import { useNavigate } from 'react-router-dom';
import { useAppContext } from 'src/context';
export const NameAccount = () => {
const { accountName, setAccountName } = useRegisterContext();
const { storage } = useAppContext();
const navigate = useNavigate();
const [error, setError] = useState<string>();
const handleNext = async () => {
const accountNameExists = await storage?.has_mnemonic(accountName);
if (accountNameExists) {
setError('Account name already exists. Please choose another account name');
} else {
navigate('/user/accounts/confirm-password');
}
};
return (
<TopLogoLayout
title="Name account"
description="Give your account a unique name"
Actions={
<Button fullWidth variant="contained" size="large" onClick={handleNext} disabled={!!error}>
Next
</Button>
}
>
<TextField
fullWidth
value={accountName}
onChange={(e) => {
setError(undefined);
setAccountName(e.target.value);
}}
error={!!error}
helperText={error}
/>
</TopLogoLayout>
);
};
@@ -1,6 +0,0 @@
export * from './Accounts';
export * from './AddAccount';
export * from './Complete';
export * from './ConfirmPassword';
export * from './ImportAccount';
export * from './NameAccount';
@@ -1,24 +0,0 @@
import React from 'react';
import { Typography } from '@mui/material';
import { Box } from '@mui/system';
import { TopLogoLayout } from 'src/layouts';
const steps = [
'Make sure you have your mnemonic saved',
'Uninstal Nym extension wallet',
'Reinstal Nym extension wallet',
'Import your account using seed phrase',
'Create new password',
];
export const ForgotPassword = () => (
<TopLogoLayout title="Forgot password" description="Follow instructions below" Actions={<div />}>
<Box sx={{ px: 2 }}>
{steps.map((step, index) => (
<Typography sx={{ color: 'grey.700', my: 3 }} key={step} variant="body2">
{`${index + 1}. ${step}`}
</Typography>
))}
</Box>
</TopLogoLayout>
);
@@ -1,69 +0,0 @@
import React from 'react';
import { Stack, TextField } from '@mui/material';
import { useLocation, useNavigate } from 'react-router-dom';
import { Button } from 'src/components/ui';
import { CenteredLogoLayout } from 'src/layouts/CenteredLogo';
import { useAppContext } from 'src/context';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { validationSchema } from './validationSchema';
export const Login = () => {
const { handleUnlockWallet } = useAppContext();
const navigate = useNavigate();
const location = useLocation();
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm({ resolver: zodResolver(validationSchema), defaultValues: { password: '' } });
const onSubmit = async (data: { password: string }) => {
try {
await handleUnlockWallet(data.password);
} catch (e) {
setError('password', { message: 'Incorrect password. Please try again.' });
}
};
return (
<CenteredLogoLayout
title="Privacy crypto wallet"
Actions={
<Stack gap={1} width="100%" justifyContent="flex-end">
<TextField
{...register('password')}
placeholder="Password"
type="password"
sx={{ mb: 3 }}
helperText={errors.password?.message}
error={!!errors.password}
/>
<Button
onClick={handleSubmit(onSubmit)}
disabled={isSubmitting}
variant="contained"
disableElevation
size="large"
fullWidth
>
{isSubmitting ? 'Loading..' : 'Unlock'}
</Button>
<Button
variant="outlined"
disableElevation
size="large"
fullWidth
color="primary"
onClick={() => navigate(`${location.pathname}/forgot-password`)}
>
Forgot password?
</Button>
</Stack>
}
/>
);
};
@@ -1,2 +0,0 @@
export * from './Login';
export * from './ForgotPassword';
@@ -1,5 +0,0 @@
import * as z from 'zod';
export const validationSchema = z.object({
password: z.string().min(1, { message: 'Required' }),
});
@@ -1,59 +0,0 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Stack, IconButton, Typography } from '@mui/material';
import { ArrowDownwardRounded, ArrowUpwardRounded, TollRounded } from '@mui/icons-material';
import { PageLayout } from 'src/layouts/PageLayout';
import { Address, Balance, ReceiveModal } from 'src/components';
type ActionsSchema = Array<{
title: string;
Icon: React.ReactNode;
onClick: () => void;
}>;
const Actions = ({ actionsSchema }: { actionsSchema: ActionsSchema }) => (
<Box display="flex" justifyContent="space-evenly">
{actionsSchema.map(({ title, Icon, onClick }) => (
<Stack justifyContent="center" alignItems="center" key={title}>
<IconButton color="primary" size="large" onClick={onClick}>
{Icon}
</IconButton>
<Typography>{title}</Typography>
</Stack>
))}
</Box>
);
export const BalancePage = () => {
const [showReceiveModal, setShowReceiveModal] = useState(false);
const navigate = useNavigate();
const actionsSchema = [
{
title: 'Send',
Icon: <ArrowDownwardRounded fontSize="large" />,
onClick: () => navigate('/user/send'),
},
{
title: 'Receive',
Icon: <ArrowUpwardRounded fontSize="large" />,
onClick: () => setShowReceiveModal(true),
},
{
title: 'Buy',
Icon: <TollRounded fontSize="large" />,
onClick: () => navigate('/user/balance'),
},
];
return (
<PageLayout>
<Stack gap={6}>
<ReceiveModal open={showReceiveModal} onClose={() => setShowReceiveModal(false)} />
<Address />
<Balance />
<Actions actionsSchema={actionsSchema} />
</Stack>
</PageLayout>
);
};
@@ -1,3 +0,0 @@
import React from 'react';
export const Delegation = () => <h1>Delegation</h1>;
@@ -1,25 +0,0 @@
import React from 'react';
import { Stack } from '@mui/system';
import { Button } from 'src/components/ui';
import { CenteredLogoLayout } from 'src/layouts';
import { Link } from 'react-router-dom';
export const Home = () => (
<CenteredLogoLayout
title="Welcome to Nym"
Actions={
<Stack gap={2} width="100%" justifyContent="flex-end">
<Link to="/register/create-password" style={{ textDecoration: 'none' }}>
<Button variant="contained" disableElevation size="large" fullWidth>
Create new account
</Button>
</Link>
<Link to="/register/import-account" style={{ textDecoration: 'none' }}>
<Button variant="text" disableElevation size="large" fullWidth color="primary">
Import existing account
</Button>
</Link>
</Stack>
}
/>
);
-8
View File
@@ -1,8 +0,0 @@
export * from './accounts';
export * from './auth';
export * from './balance';
export * from './home';
export * from './receive';
export * from './send';
export * from './settings';
export * from './delegation';
@@ -1,3 +0,0 @@
import React from 'react';
export const Receive = () => <h1>Receive</h1>;
@@ -1,10 +0,0 @@
import React from 'react';
import { SetupCompleteTemplate } from '../templates/Complete';
export const SetupComplete = ({ onDone }: { onDone: () => void }) => (
<SetupCompleteTemplate
title="You're all set!"
description="Open the extension and sign in to begin your interchain journey"
onDone={onDone}
/>
);
@@ -1,16 +0,0 @@
import React from 'react';
import { useCreatePassword } from 'src/hooks/useCreatePassword';
import { useRegisterContext } from 'src/context/register';
import { CreatePasswordTemplate } from 'src/pages/templates/CreatePassword';
export const CreatePasswordOnExistingAccount = ({ onComplete }: { onComplete: () => void }) => {
const passwordState = useCreatePassword();
const { createAccount, userMnemonic } = useRegisterContext();
const handleOnComplete = async () => {
await createAccount({ mnemonic: userMnemonic, password: passwordState.password, accName: 'Default account' });
onComplete();
};
return <CreatePasswordTemplate {...passwordState} onNext={handleOnComplete} />;
};
@@ -1,16 +0,0 @@
import React from 'react';
import { useCreatePassword } from 'src/hooks/useCreatePassword';
import { useRegisterContext } from 'src/context/register';
import { CreatePasswordTemplate } from 'src/pages/templates/CreatePassword';
export const CreatePasswordOnNewAccount = ({ onNext }: { onNext: () => void }) => {
const passwordState = useCreatePassword();
const { setUserPassword } = useRegisterContext();
const handleCreateAccount = async () => {
await setUserPassword(passwordState.password);
onNext();
};
return <CreatePasswordTemplate {...passwordState} onNext={handleCreateAccount} />;
};
@@ -1,19 +0,0 @@
import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useRegisterContext } from 'src/context/register';
import { ImportAccountTemplate } from '../templates/ImportAccount';
export const ImportAccount = () => {
const navigate = useNavigate();
const location = useLocation();
const { setUserMnemonic, userMnemonic } = useRegisterContext();
const handleNext = async () => {
navigate(`${location.pathname}/create-password`);
};
return (
<ImportAccountTemplate userMnemonic={userMnemonic} onChangeUserMnemonic={setUserMnemonic} onNext={handleNext} />
);
};
@@ -1,17 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useRegisterContext } from 'src/context/register';
import { SeedPhraseTemplate } from '../templates/SeedPhrase';
export const SeedPhrase = () => {
const navigate = useNavigate();
const { createAccount, userPassword } = useRegisterContext();
const handleEncryptSeedPhrase = async (seedPhrase: string) => {
await createAccount({ mnemonic: seedPhrase, password: userPassword, accName: 'Default account' });
navigate('/register/complete');
};
return <SeedPhraseTemplate onNext={handleEncryptSeedPhrase} />;
};
@@ -1,5 +0,0 @@
export * from './Complete';
export * from './CreatePasswordOnExistingAccount';
export * from './CreatePasswordOnNewAccount';
export * from './ImportAccount';
export * from './SeedPhrase';
@@ -1,68 +0,0 @@
import React from 'react';
import { Box, Divider, ListItem, ListItemText, Stack, Typography } from '@mui/material';
import { Button } from 'src/components';
import { PageLayout } from 'src/layouts/PageLayout';
import { useAppContext, useSendContext } from 'src/context';
import { ErrorModal, LoadingModal } from 'src/components/ui/Modal';
import { SendConfirmationModal } from 'src/components/send/SendConfirmationModal';
import { blockExplorerUrl } from 'src/urls';
const InfoItem = ({ label, value }: { label: string; value: string }) => (
<Box>
<ListItem disableGutters disablePadding>
<ListItemText
primary={
<Typography fontSize="small" fontWeight={600}>
{label}
</Typography>
}
secondary={
<Typography fontSize="small" fontWeight={600}>
{value}
</Typography>
}
/>
</ListItem>
<Divider sx={{ my: 1 }} />
</Box>
);
export const SendConfirmationPage = ({ onCancel }: { onCancel: () => void }) => {
const { client, denom } = useAppContext();
const { address, amount, fee, handleSend, transaction, resetTx, onDone } = useSendContext();
const calculateTotal = () => (Number(fee?.nym) + Number(amount?.amount)).toString();
return (
<PageLayout>
{transaction?.status === 'success' && (
<SendConfirmationModal
amount={`${amount?.amount} ${denom}`}
txUrl={`${blockExplorerUrl}/transactions/${transaction.txHash}`}
onConfirm={onDone}
/>
)}
{transaction?.status === 'loading' && <LoadingModal />}
{transaction?.status === 'error' && (
<ErrorModal open title="Transaction failed" onClose={resetTx}>
<Typography>{transaction.message}</Typography>
</ErrorModal>
)}
<Stack gap={1} height="100%">
<InfoItem label="From" value={client?.address || ''} />
<InfoItem label="To" value={address || ''} />
<InfoItem label="Amount" value={`${amount?.amount} ${denom}`} />
<InfoItem label="Transaction fee" value={`${fee?.nym || '-'} ${denom}`} />
<InfoItem label="Total" value={`${calculateTotal()} ${denom}`} />
</Stack>
<Box display="flex" gap={2}>
<Button variant="outlined" size="large" fullWidth onClick={onCancel}>
Cancel
</Button>
<Button variant="contained" size="large" fullWidth onClick={handleSend}>
Send
</Button>
</Box>
</PageLayout>
);
};
@@ -1,80 +0,0 @@
import React, { useState } from 'react';
import { Box, Divider, Stack, Typography } from '@mui/material';
import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
import { DecCoin } from '@nymproject/types';
import { Address, Button } from 'src/components';
import { PageLayout } from 'src/layouts/PageLayout';
import { SendProvider, useAppContext, useSendContext } from 'src/context';
import { SendConfirmationPage } from './Confirmation';
const SendPage = ({ onConfirm }: { onConfirm: () => void }) => {
const [isValidAddress, setIsValidAddress] = useState(false);
const [isValidAmount, setIsValidAmount] = useState(false);
const { address, amount, handleChangeAddress, handleChangeAmount, handleGetFee } = useSendContext();
const { balance } = useAppContext();
const handleNext = async () => {
if (address && amount) {
await handleGetFee(address, amount.amount);
onConfirm();
}
};
return (
<PageLayout>
<Stack gap={4} height="100%">
<Address />
<WalletAddressFormField
showTickOnValid
label="Recipient address"
required
onChanged={(_address: string) => handleChangeAddress(_address)}
onValidate={setIsValidAddress}
initialValue={address}
/>
<CurrencyFormField
label="Amount"
initialValue={amount?.amount}
required
onChanged={(_amount: DecCoin) => handleChangeAmount(_amount)}
onValidate={(_: any, isValid: boolean) => setIsValidAmount(isValid)}
/>
<Box>
<Stack direction="row" justifyContent="space-between">
<Typography fontWeight={600}>Account balance</Typography>
<Typography fontWeight={600}>{balance} NYM</Typography>
</Stack>
<Divider sx={{ my: 2 }} />
<Typography variant="body2" sx={{ color: 'grey.600' }}>
Est. fee for this transaction will be calculated on the next page
</Typography>
</Box>
</Stack>
<Button
variant="contained"
size="large"
fullWidth
disabled={!(isValidAddress && isValidAmount)}
onClick={handleNext}
>
Next
</Button>
</PageLayout>
);
};
export const Send = () => {
const [showConfirmation, setShowConfirmation] = useState(false);
return (
<SendProvider>
{showConfirmation ? (
<SendConfirmationPage onCancel={() => setShowConfirmation(false)} />
) : (
<SendPage onConfirm={() => setShowConfirmation(true)} />
)}
</SendProvider>
);
};
@@ -1,3 +0,0 @@
import React from 'react';
export const Settings = () => <h1>Settings</h1>;
@@ -1,26 +0,0 @@
import React from 'react';
import { Box } from '@mui/material';
import { Button } from 'src/components/ui';
import { CenteredLogoLayout } from 'src/layouts';
export const SetupCompleteTemplate = ({
title,
description,
onDone,
}: {
title: string;
description: string;
onDone: () => void;
}) => (
<CenteredLogoLayout
title={title}
description={description}
Actions={
<Box width="100%">
<Button variant="contained" fullWidth size="large" onClick={onDone}>
Done
</Button>
</Box>
}
/>
);
@@ -1,63 +0,0 @@
import React from 'react';
import { FormControlLabel, Checkbox, Stack, Typography, Box } from '@mui/material';
import { TopLogoLayout } from 'src/layouts/TopLogo';
import { PasswordInput } from '@nymproject/react/textfields/Password';
import { PasswordStrength } from '@nymproject/react/password-strength/PasswordStrength';
import { Button } from 'src/components/ui';
type TCreatePassword = {
canProceed: boolean;
password: string;
confirmPassword: string;
hasReadTerms: boolean;
setHasReadTerms: (hasReadTerms: boolean) => void;
setIsSafePassword: (isSafe: boolean) => void;
setConfirmPassword: (password: string) => void;
onNext: () => void;
setPassword: (password: string) => void;
};
export const CreatePasswordTemplate = ({
canProceed,
onNext,
password,
setPassword,
confirmPassword,
setIsSafePassword,
setConfirmPassword,
setHasReadTerms,
hasReadTerms,
}: TCreatePassword) => (
<TopLogoLayout
title="Create Password"
description="Create a strong password - Min 8 characters, at least one capital letter, number and special character"
Actions={
<Button fullWidth variant="contained" size="large" disabled={!canProceed} onClick={onNext}>
Next
</Button>
}
>
<Stack spacing={1} mb={4}>
<PasswordInput
password={password}
onUpdatePassword={(_password: string) => setPassword(_password)}
label="Password"
/>
<PasswordStrength password={password} handleIsSafePassword={(isSafe) => setIsSafePassword(isSafe)} />
</Stack>
<Box mb={2}>
<PasswordInput
password={confirmPassword}
onUpdatePassword={(_password: string) => setConfirmPassword(_password)}
label="Confirm password"
/>
</Box>
<FormControlLabel
label={<Typography variant="caption">I have read and agree with the Terms of use</Typography>}
control={<Checkbox checked={hasReadTerms} onChange={(_, checked) => setHasReadTerms(checked)} />}
/>
</TopLogoLayout>
);
@@ -1,45 +0,0 @@
import React from 'react';
import { TextField } from '@mui/material';
import { Button } from 'src/components';
import { TopLogoLayout } from 'src/layouts';
export const ImportAccountTemplate = ({
userMnemonic,
onChangeUserMnemonic,
onNext,
}: {
userMnemonic: string;
onChangeUserMnemonic: (mnemonic: string) => void;
onNext: () => void;
}) => (
<TopLogoLayout
title="Import account"
description="Type the mnemonic for the account you want to import "
Actions={
<Button variant="contained" fullWidth size="large" onClick={onNext}>
Next
</Button>
}
>
<TextField
label="Mnemonic"
type="password"
value={userMnemonic}
onChange={(e) => onChangeUserMnemonic(e.target.value)}
multiline
autoFocus={false}
fullWidth
inputProps={{
style: {
height: '160px',
},
}}
InputLabelProps={{ shrink: true }}
sx={{
'input::-webkit-textfield-decoration-container': {
alignItems: 'start',
},
}}
/>
</TopLogoLayout>
);
@@ -1,60 +0,0 @@
import React, { useRef, useState } from 'react';
import { Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
import { TopLogoLayout } from 'src/layouts/TopLogo';
import { Button } from 'src/components/ui';
import { generateMnemonmic } from 'src/validator-client';
export const SeedPhraseTemplate = ({ onNext }: { onNext: (seedPhrase: string) => void }) => {
const [isConfirmed, setIsconfirmed] = useState(false);
const seedPhrase = useRef(generateMnemonmic());
return (
<TopLogoLayout
title="Seed phrase"
description="Save your seed phrase"
Actions={
<Button
fullWidth
variant="contained"
size="large"
disabled={!isConfirmed}
onClick={() => onNext(seedPhrase.current)}
>
Next
</Button>
}
>
<Stack spacing={2} sx={{ mt: 2 }}>
<Typography sx={{ textAlign: 'center', color: 'error.dark' }}>
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
</Typography>
<TextField
label="Mnemonic"
type="input"
value={seedPhrase.current}
multiline
autoFocus={false}
fullWidth
inputProps={{
style: {
height: '160px',
},
}}
InputLabelProps={{ shrink: true }}
sx={{
'input::-webkit-textfield-decoration-container': {
alignItems: 'start',
},
}}
/>
<FormControlLabel
label="I saved my mnemonic"
control={<Checkbox checked={isConfirmed} onChange={(_, checked) => setIsconfirmed(checked)} />}
/>
</Stack>
</TopLogoLayout>
);
};
@@ -1,4 +0,0 @@
export * from './Complete';
export * from './CreatePassword';
export * from './ImportAccount';
export * from './SeedPhrase';
@@ -1,35 +0,0 @@
import React, { useEffect, useState } from 'react';
import { BrowserRouter, MemoryRouter, Route, Routes } from 'react-router-dom';
import { Home } from 'src/pages';
import { ExtensionStorage } from '@nymproject/extension-storage';
import { RegisterRoutes } from './register';
import { UserRoutes } from './user';
import { LoginRoutes } from './login';
const Router = process.env.NODE_ENV === 'development' ? BrowserRouter : MemoryRouter;
export const AppRoutes = () => {
const [userHasAccount, setUserHasAccount] = useState(null);
useEffect(() => {
const checkUserHasAccount = async () => {
const hasAccount = await ExtensionStorage.exists();
setUserHasAccount(hasAccount);
};
checkUserHasAccount();
}, []);
if (userHasAccount === null) return null;
return (
<Router>
<Routes>
<Route path="/" element={userHasAccount ? <LoginRoutes /> : <Home />} />
<Route path="/login/*" element={<LoginRoutes />} />
<Route path="/register/*" element={<RegisterRoutes />} />
<Route path="/user/*" element={<UserRoutes />} />
</Routes>
</Router>
);
};
@@ -1,26 +0,0 @@
import React, { useEffect } from 'react';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { useAppContext } from 'src/context';
import { ForgotPassword, Login } from 'src/pages/auth';
export const LoginRoutes = () => {
const { client } = useAppContext();
const navigate = useNavigate();
useEffect(() => {
let route = '/login';
if (client) {
route = '/user/balance';
}
navigate(route);
}, [client]);
return (
<Routes>
<Route index element={<Login />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
</Routes>
);
};
@@ -1,38 +0,0 @@
import React from 'react';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { RegisterContextProvider } from 'src/context/register';
import { ImportAccount, SeedPhrase, SetupComplete } from 'src/pages/register';
import { CreatePasswordOnExistingAccount } from 'src/pages/register/CreatePasswordOnExistingAccount';
import { CreatePasswordOnNewAccount } from 'src/pages/register/CreatePasswordOnNewAccount';
export const RegisterRoutes = () => {
const navigate = useNavigate();
const handleSetUpComplete = () => {
navigate('/login');
};
return (
<RegisterContextProvider>
<Routes>
<Route
path="create-password"
element={<CreatePasswordOnNewAccount onNext={() => navigate('/register/seed-phrase')} />}
/>
<Route path="seed-phrase" element={<SeedPhrase />} />
<Route path="import-account" element={<ImportAccount />} />
<Route
path="import-account/create-password"
element={
<CreatePasswordOnExistingAccount
onComplete={() => {
navigate('/register/complete');
}}
/>
}
/>
<Route path="complete" element={<SetupComplete onDone={handleSetUpComplete} />} />
</Routes>
</RegisterContextProvider>
);
};
@@ -1,17 +0,0 @@
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import { RegisterContextProvider } from 'src/context/register';
import { Accounts, AddAccount, ConfirmPassword, ImportAccount, NameAccount, SetupComplete } from 'src/pages';
export const AccountRoutes = () => (
<RegisterContextProvider>
<Routes>
<Route path="/" element={<Accounts />} />
<Route path="/add-account" element={<AddAccount />} />
<Route path="/import-account" element={<ImportAccount />} />
<Route path="/name-account" element={<NameAccount />} />
<Route path="/confirm-password" element={<ConfirmPassword />} />
<Route path="/complete" element={<SetupComplete />} />
</Routes>
</RegisterContextProvider>
);
@@ -1,25 +0,0 @@
import React, { useEffect } from 'react';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { useAppContext } from 'src/context';
import { Delegation, BalancePage, Receive, Send, Settings } from 'src/pages';
import { AccountRoutes } from './accounts/accounts';
export const UserRoutes = () => {
const { client } = useAppContext();
const navigate = useNavigate();
useEffect(() => {
if (!client) navigate('/login');
}, [client]);
return (
<Routes>
<Route path="/accounts/*" element={<AccountRoutes />} />
<Route path="/balance" element={<BalancePage />} />
<Route path="/delegation" element={<Delegation />} />
<Route path="/receive" element={<Receive />} />
<Route path="/send" element={<Send />} />
<Route path="/settings" element={<Settings />} />
</Routes>
);
};
@@ -1,18 +0,0 @@
import React from 'react';
import { CssBaseline, PaletteMode } from '@mui/material';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { getDesignTokens } from './theme';
import '@assets/fonts/non-variable/fonts.css';
type TNymBrowserExtThemeProps = { mode: PaletteMode; children: React.ReactNode };
export const NymBrowserExtThemeWithMode = ({ mode, children }: TNymBrowserExtThemeProps) => {
const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
);
};
-66
View File
@@ -1,66 +0,0 @@
/* eslint-disable no-shadow,@typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface */
import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles';
import { PaletteMode } from '@mui/material';
/**
* If you are unfamiliar with Material UI theming, please read the following first:
* - https://mui.com/customization/theming/
* - https://mui.com/customization/palette/
* - https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette
*
* This file adds typings to the theme using Typescript's module augmentation.
*
* Read the following if you are unfamiliar with module augmentation and declaration merging. Then
* look at the recommendations from Material UI docs for implementation:
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
* - https://mui.com/customization/palette/#adding-new-colors
*
*
* IMPORTANT:
*
* The type augmentation must match MUI's definitions. So, notice the use of `interface` rather than
* `type Foo = { ... }` - this is necessary to merge the definitions.
*/
declare module '@mui/material/styles' {
/**
* This interface defines a palette used across Nym for branding
*/
interface NymPalette {
background: { light: string; dark: string };
}
interface NymPaletteVariant {
mode: PaletteMode;
}
/**
* A palette definition only for the Nym Browser Extension that extends the Nym palette
*/
interface NymBrowserExtPalette {
nymBrowserExt: NymPaletteVariant;
}
interface NymPaletteAndNymBrowserExtPalette {
nym: NymPalette & NymBrowserExtPalette;
}
type NymPaletteAndNymBrowserExtPaletteOptions = Partial<NymPaletteAndNymBrowserExtPalette>;
/**
* Add anything not palette related to the theme here
*/
interface NymTheme {}
/**
* This augments the definitions of the MUI Theme with the Nym theme, as well as
* a partial `ThemeOptions` type used by `createTheme`
*
* IMPORTANT: only add extensions to the interfaces above, do not modify the lines below
*/
interface Theme extends NymTheme {}
interface ThemeOptions extends Partial<NymTheme> {}
interface Palette extends NymPaletteAndNymBrowserExtPalette {}
interface PaletteOptions extends NymPaletteAndNymBrowserExtPaletteOptions {}
}
-138
View File
@@ -1,138 +0,0 @@
import { PaletteMode } from '@mui/material';
import {
PaletteOptions,
NymPalette,
NymBrowserExtPalette,
ThemeOptions,
createTheme,
NymPaletteVariant,
} from '@mui/material/styles';
//-----------------------------------------------------------------------------------------------
// Nym palette type definitions
//
/**
* The Nym palette.
*
* IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct
* colours for dark/light mode.
*/
const nymPalette: NymPalette = {
/** emphasises important elements */
background: { light: '#F4F6F8', dark: '#1D2125' },
};
const darkMode: NymPaletteVariant = {
mode: 'dark',
};
const lightMode: NymPaletteVariant = {
mode: 'light',
};
/**
* Nym palette specific to the Nym Wallet
*
* IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct
* colours for dark/light mode.
*/
const nymBrowserExtPalette = (variant: NymPaletteVariant): NymBrowserExtPalette => ({
nymBrowserExt: variant,
});
//-----------------------------------------------------------------------------------------------
// Nym palettes for light and dark mode
//
/**
* Map a Nym palette variant onto the MUI palette
*/
const variantToMUIPalette = (_: NymPaletteVariant): PaletteOptions => ({
primary: {
main: '#6750A4',
},
});
/**
* Returns the Network Explorer palette for light mode.
*/
const createLightModePalette = (): PaletteOptions => ({
nym: {
...nymPalette,
...nymBrowserExtPalette(lightMode),
},
...variantToMUIPalette(lightMode),
});
/**
* Returns the Network Explorer palette for dark mode.
*/
const createDarkModePalette = (): PaletteOptions => ({
nym: {
...nymPalette,
...nymBrowserExtPalette(darkMode),
},
...variantToMUIPalette(darkMode),
});
/**
* IMPORANT: if you need to get the default MUI theme, use the following
*
* import { createTheme as systemCreateTheme } from '@mui/system';
*
* // get the MUI system defaults for light mode
* const systemTheme = systemCreateTheme({ palette: { mode: 'light' } });
*
*
* return {
* // change `primary` to default MUI `success`
* primary: {
* main: systemTheme.palette.success.main,
* },
* nym: {
* ...nymPalette,
* ...nymWalletPalette,
* },
* };
*/
//-----------------------------------------------------------------------------------------------
// Nym theme overrides
//
/**
* Gets the theme options to be passed to `createTheme`.
*
* Based on pattern from https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette.
*
* @param mode The theme mode: 'light' or 'dark'
*/
export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
// first, create the palette from user's choice of light or dark mode
const { palette } = createTheme({
palette: {
mode,
...(mode === 'light' ? createLightModePalette() : createDarkModePalette()),
},
});
// then customise theme and components
return {
typography: {
fontFamily: [
'Open Sans',
'sans-serif',
'BlinkMacSystemFont',
'Roboto',
'Oxygen',
'Ubuntu',
'Helvetica Neue',
].join(','),
},
shape: {
borderRadius: 8,
},
palette,
};
};
@@ -1,5 +0,0 @@
export declare namespace NodeJS {
interface ProcessEnv {
development: string;
}
}
-1
View File
@@ -1 +0,0 @@
export * from './tx';
-9
View File
@@ -1,9 +0,0 @@
// TODO Add other transaction types later
type TTransactionType = 'send';
export type TTransaction = {
type: TTransactionType;
txHash?: string;
status: 'loading' | 'success' | 'error';
message?: string;
};
-4
View File
@@ -1,4 +0,0 @@
declare module '*.jpeg' {
const value: any;
export default value;
}
-4
View File
@@ -1,4 +0,0 @@
declare module '*.json' {
const content: any;
export default content;
}
-4
View File
@@ -1,4 +0,0 @@
declare module '*.png' {
const content: any;
export default content;
}
-4
View File
@@ -1,4 +0,0 @@
declare module '*.svg' {
const content: any;
export default content;
}
-2
View File
@@ -1,2 +0,0 @@
export const blockExplorerUrl = process.env.BLOCK_EXPLORER_URL;
export const coinGeckoPriceAPI = 'https://api.coingecko.com/api/v3/simple/price?';
-21
View File
@@ -1,21 +0,0 @@
import Big from 'big.js';
export const unymToNym = (unym: number | Big, dp = 4) => {
let nym;
try {
nym = Big(unym).div(1_000_000).toFixed(dp);
} catch (e: any) {
console.warn(`${unym} not a valid decimal number: ${e}`);
}
return Number(nym);
};
export const nymToUnym = (nym: number) => {
let unym;
try {
unym = Big(nym).mul(1_000_000);
} catch (e: any) {
console.warn(`unable to convert nym to unym: ${e}`);
}
return Number(unym);
};
@@ -1,8 +0,0 @@
import cryptojs from 'crypto-js';
const encrypt = (mnemonic: string, password: string) => cryptojs.AES.encrypt(mnemonic, password).toString();
const decrypt = (cipher: string, password: string) =>
cryptojs.AES.decrypt(cipher, password).toString(cryptojs.enc.Utf8);
export { encrypt, decrypt };
-8
View File
@@ -1,8 +0,0 @@
export const createFeeObject = (feeInUnyms?: number) => {
if (!feeInUnyms) return undefined;
return {
amount: [{ amount: feeInUnyms.toString(), denom: 'unym' }],
gas: '100000',
};
};
-18
View File
@@ -1,18 +0,0 @@
import { coinGeckoPriceAPI } from 'src/urls';
export type Currency = 'gbp' | 'usd';
export type TokenId = 'nym';
type ResponseMap<T extends TokenId, C extends Currency> = { [token in T]: { [currency in C]: number } };
const constructUrl = (tokenId: TokenId, currency: Currency) =>
`${coinGeckoPriceAPI}ids=${tokenId}&vs_currencies=${currency}`;
export async function getTokenPrice<T extends TokenId, C extends Currency>(
tokenId: T,
currency: C,
): Promise<ResponseMap<T, C>> {
const response = await fetch(constructUrl(tokenId, currency));
const json = await response.json();
return json;
}
@@ -1,15 +0,0 @@
import ValidatorClient from '@nymproject/nym-validator-client';
import { config } from 'src/config';
export const generateMnemonmic = () => ValidatorClient.randomMnemonic();
export const connectToValidator = async (mnemonic: string) =>
ValidatorClient.connect(
mnemonic,
config.rpcUrl,
config.validatorUrl,
config.prefix,
config.mixnetContractAddress,
config.vestingContractAddress,
config.denom,
);
@@ -1,73 +0,0 @@
import {
ExtensionStorage,
set_panic_hook
} from "@nymproject/storage-extension"
// // current limitation of rust-wasm for async stuff : (
// let client = null
async function main() {
// // sets up better stack traces in case of in-rust panics
set_panic_hook();
let storage = await new ExtensionStorage("my super duper password");
const goodMnemonic = "figure aspect pill salute review sponsor army city muffin engine army kid rival chunk unit insect blouse paddle velvet shallow box crawl grace never"
const badMnemonic = "foomp"
let readEmpty = await storage.read_mnemonic("my-mnemonic1")
console.log("value initial:", readEmpty);
try {
await storage.store_mnemonic("my-mnemonic1", badMnemonic);
} catch (e) {
console.log("store error: ",e)
}
let anotherRead = await storage.read_mnemonic("my-mnemonic1")
console.log("value bad store:", anotherRead);
await storage.store_mnemonic("my-mnemonic1", goodMnemonic)
let yetAnotherRead = await storage.read_mnemonic("my-mnemonic1")
console.log("value good store:", yetAnotherRead);
await storage.remove_mnemonic("my-mnemonic1")
let finalRead = await storage.read_mnemonic("my-mnemonic1")
console.log("value removed:", finalRead);
const anotherMnemonic = "salmon picture danger pill tomato hour hand chaos tray bargain frequent fuel scheme coil divert season lucky ginger mom stem mistake blanket lake suffer";
const oneMore = "cat quiz circle letter trade unhappy quarter garlic sting gravity zone stock scatter merge account barrel forward fame club chest camp under crop connect"
const key1 = "my-amazing-mnemonic"
const key2 = "my-other-mnemonic"
await storage.store_mnemonic(key1, anotherMnemonic)
await storage.store_mnemonic(key2, oneMore)
let allKeys = await storage.get_all_mnemonic_keys()
console.log("keys:", allKeys)
const anotherOne = "mammal fashion rice two marble high brain achieve first harsh infant timber flush cloud hunt address brand immune tip identify aspect call beyond once"
const anotherKey = "some-mnemonic"
let isPresent = await storage.has_mnemonic(anotherKey);
console.log("has mnemonic: ", isPresent)
await storage.store_mnemonic(anotherKey, anotherOne)
let isPresentNew = await storage.has_mnemonic(anotherKey);
console.log("has mnemonic: ", isPresentNew)
await storage.remove_mnemonic(anotherKey)
let isPresentEvenNewer = await storage.has_mnemonic(anotherKey);
console.log("has mnemonic: ", isPresentEvenNewer)
}
// Let's get started!
main();
@@ -1,17 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"noEmit": true
},
"include": [
".storybook/*.js",
"webpack.*.js",
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.stories.*"
],
"exclude": ["node_modules", "dist"]
}
-32
View File
@@ -1,32 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": false,
"jsx": "react-jsx",
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@assets/*": ["../assets/*"]
},
"noEmit": true
},
"exclude": [
"node_modules",
"dist",
"jest.config.js",
"webpack.config.js",
"webpack.prod.js",
"webpack.common.js",
"tsconfig.json"
]
}
-26
View File
@@ -1,26 +0,0 @@
const path = require('path');
const { mergeWithRules } = require('webpack-merge');
const { webpackCommon } = require('@nymproject/webpack');
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(webpackCommon(__dirname), {
entry: path.resolve(__dirname, 'src/index.tsx'),
output: {
clean: true,
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
resolve: {
fallback: {
crypto: 'crypto-browserify',
stream: 'stream-browserify',
},
},
experiments: { asyncWebAssembly: true },
});

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