Unverified Commit 29bcf80d authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub

major: load Network from Context, enable adding Network from QR (#681)

* load networks from db

* renaming networkTypes

* inject network Context into code

* inject network context part 2

* make lint happy

* add pathsIds into networkContext

* implement add network to app context function

* fix lint, first version save

* complete adding network

* add e2e test

* update tutorial
parent 84a7a175
Pipeline #105308 failed with stages
in 3 minutes and 3 seconds
......@@ -29,6 +29,7 @@ Any data transfer from or to the app happens using QR code. By doing so, the mos
- [Manage Accounts on Parity Signer](./docs/tutorials/Hierarchical-Deterministic-Key-Derivation.md)
- [Signing with MyCrypto](./docs/tutorials/MyCrypto-tutorial.md)
- [Signing with Fether](./docs/tutorials/Fether-tutorial.md)
- [Update New Network](./docs/tutorials/New-Network.md)
### Wiki
......
# Update Network Tutorial
Parity Signer support adding a new Substrate based network or update the existing network via QR code.
This tutorial will walk through how to add a new Rococo Network with Polkadot.js App.
## 1. Get the network metadata as QR Code
Switch to the network you want to play with on Polkadot.js app. Click `Settings` -> `MetaData`
![Network Metadata QR Code](images/Network-Metadata-QR.png)
Here we can see the chain specifications like `Network Name`, `Address Prefix`, and `Genesis Hash` etc. They are all the metaData of the network which is required by Parity Signer. The only item we could change is network color, it is used on Parity Signer to distinguish other networks.
On the right side is the QR Code we need.
Now on the Parity Signer app, click the QR scanner Button anywhere on the app, and scan this QR code, you will have the new Network added to Parity Signer. You can now create accounts under it and sign extrinsic with this network.
![Network Metadata Added on Parity Signer](images/Network-Metadata-Added.png)
Notice since the metadata is generally very big data, and currently, it is hard to sync with Parity Signer, so when signing the transactions on added networks, we cannot interpreter the extrinsic details at the moment. Please check on this [issue](https://github.com/paritytech/parity-signer/issues/457) for the update.
......@@ -16,7 +16,7 @@ In the v4 version, the data schema is significantly refactored in order to suppo
Furthermore, in the new Identity schema, we support hard spoon network in the Substrate network like Kusama, since the Substrate keypairs/accounts generated on Signer will decouple with network's genesisHash, but will use the path as identifier of their networks. From network genesisHash and path's meta data, the `accountId` is generated for signing.
```javascript
const networkParams = getNetwork(meta.path)
const networkParams = getSubstrateNetworkParams(meta.path)
const accountId = getAccountId(meta.address, networkParams.genesisHash)
```
Once the network is hard-spooned, users just need to update network params by scanning QR code, without requring new version of the Signer.
......
......@@ -29,6 +29,7 @@ import {
ScreenStack
} from './screens';
import { useNetworksContext, NetworksContext } from 'stores/NetworkContext';
import { useScannerContext, ScannerContext } from 'stores/ScannerContext';
import { useAccountContext, AccountsContext } from 'stores/AccountsContext';
import CustomAlert from 'components/CustomAlert';
......@@ -62,6 +63,7 @@ export default function App(props: AppProps): React.ReactElement {
const alertContext = useAlertContext();
const globalContext: GlobalState = useGlobalStateContext();
const seedRefContext = useSeedRefStore();
const networkContext = useNetworksContext();
const accountsContext = useAccountContext();
const scannerContext = useScannerContext();
......@@ -87,24 +89,26 @@ export default function App(props: AppProps): React.ReactElement {
return (
<SafeAreaProvider>
<AccountsContext.Provider value={accountsContext}>
<ScannerContext.Provider value={scannerContext}>
<GlobalStateContext.Provider value={globalContext}>
<AlertStateContext.Provider value={alertContext}>
<SeedRefsContext.Provider value={seedRefContext}>
<MenuProvider backHandler={true}>
<StatusBar
barStyle="light-content"
backgroundColor={colors.background.app}
/>
<CustomAlert />
<NavigationContainer>{renderStacks()}</NavigationContainer>
</MenuProvider>
</SeedRefsContext.Provider>
</AlertStateContext.Provider>
</GlobalStateContext.Provider>
</ScannerContext.Provider>
</AccountsContext.Provider>
<NetworksContext.Provider value={networkContext}>
<AccountsContext.Provider value={accountsContext}>
<ScannerContext.Provider value={scannerContext}>
<GlobalStateContext.Provider value={globalContext}>
<AlertStateContext.Provider value={alertContext}>
<SeedRefsContext.Provider value={seedRefContext}>
<MenuProvider backHandler={true}>
<StatusBar
barStyle="light-content"
backgroundColor={colors.background.app}
/>
<CustomAlert />
<NavigationContainer>{renderStacks()}</NavigationContainer>
</MenuProvider>
</SeedRefsContext.Provider>
</AlertStateContext.Provider>
</GlobalStateContext.Provider>
</ScannerContext.Provider>
</AccountsContext.Provider>
</NetworksContext.Provider>
</SafeAreaProvider>
);
}
......
......@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { ReactElement } from 'react';
import React, { ReactElement, useContext } from 'react';
import { StyleSheet, Text, View, ViewStyle } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
......@@ -23,7 +23,7 @@ import Address from './Address';
import TouchableItem from './TouchableItem';
import AccountPrefixedTitle from './AccountPrefixedTitle';
import { getNetworkParams } from 'utils/identitiesUtils';
import { NetworksContext } from 'stores/NetworkContext';
import Separator from 'components/Separator';
import fontStyles from 'styles/fontStyles';
import colors from 'styles/colors';
......@@ -66,7 +66,8 @@ export function NetworkCard({
testID?: string;
title: string;
}): ReactElement {
const networkParams = getNetworkParams(networkKey);
const { getNetwork } = useContext(NetworksContext);
const networkParams = getNetwork(networkKey ?? '');
const isDisabled = onPress === undefined;
return (
<TouchableItem testID={testID} disabled={isDisabled} onPress={onPress}>
......@@ -120,10 +121,11 @@ export default function AccountCard({
title,
titlePrefix
}: AccountCardProps): ReactElement {
const { getNetwork } = useContext(NetworksContext);
const defaultTitle = 'No name';
const displayTitle = title.length > 0 ? title : defaultTitle;
const seedTypeDisplay = seedType || '';
const network = getNetworkParams(networkKey);
const network = getNetwork(networkKey ?? '');
return (
<TouchableItem
......
......@@ -23,7 +23,7 @@ import FontAwesome from 'react-native-vector-icons/FontAwesome';
import colors from 'styles/colors';
import { NetworkProtocols } from 'constants/networkSpecs';
import { blockiesIcon } from 'utils/native';
import { NetworkParams, SubstrateNetworkParams } from 'types/networkSpecsTypes';
import { NetworkParams, SubstrateNetworkParams } from 'types/networkTypes';
export default function AccountIcon(props: {
address: string;
......
......@@ -33,7 +33,7 @@ import fonts from 'styles/fonts';
import { debounce } from 'utils/debounce';
import { brainWalletAddress, substrateAddress, words } from 'utils/native';
import { constructSURI } from 'utils/suri';
import { NetworkParams, SubstrateNetworkParams } from 'types/networkSpecsTypes';
import { NetworkParams, SubstrateNetworkParams } from 'types/networkTypes';
interface IconType {
address: string;
......
......@@ -19,7 +19,7 @@ import { Text, TextStyle } from 'react-native';
import fontStyles from 'styles/fontStyles';
import { NetworkProtocols } from 'constants/networkSpecs';
import { NetworkProtocol } from 'types/networkSpecsTypes';
import { NetworkProtocol } from 'types/networkTypes';
export default function Address(props: {
address: string;
......
......@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import React, { useContext } from 'react';
import {
Image,
Platform,
......@@ -29,10 +29,8 @@ import Icon from 'react-native-vector-icons/MaterialIcons';
import TransparentBackground from './TransparentBackground';
import {
SUBSTRATE_NETWORK_LIST,
SubstrateNetworkKeys
} from 'constants/networkSpecs';
import { NetworksContext } from 'stores/NetworkContext';
import { SubstrateNetworkKeys } from 'constants/networkSpecs';
import fontStyles from 'styles/fontStyles';
import colors from 'styles/colors';
......@@ -53,14 +51,14 @@ export function DerivationNetworkSelector({
networkKey: string;
setVisible: (shouldVisible: boolean) => void;
}): React.ReactElement {
const { getSubstrateNetwork } = useContext(NetworksContext);
const network = getSubstrateNetwork(networkKey);
return (
<View style={styles.body}>
<Text style={styles.label}>{ACCOUNT_NETWORK}</Text>
<Touchable onPress={(): void => setVisible(true)}>
<View style={styles.triggerWrapper}>
<Text style={styles.triggerLabel}>
{SUBSTRATE_NETWORK_LIST[networkKey].title}
</Text>
<Text style={styles.triggerLabel}>{network.title}</Text>
<Icon name="more-vert" size={25} color={colors.text.main} />
</View>
</Touchable>
......@@ -77,12 +75,13 @@ export function NetworkOptions({
visible: boolean;
setVisible: (shouldVisible: boolean) => void;
}): React.ReactElement {
const { networks } = useContext(NetworksContext);
const onNetworkSelected = (networkKey: string): void => {
setNetworkKey(networkKey);
setVisible(false);
};
const menuOptions = Object.entries(SUBSTRATE_NETWORK_LIST)
const menuOptions = Array.from(networks.entries())
.filter(([networkKey]) => !excludedNetworks.includes(networkKey))
.map(([networkKey, networkParams]) => {
return (
......
......@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import AntIcon from 'react-native-vector-icons/AntDesign';
......@@ -23,6 +23,7 @@ import AccountPrefixedTitle from './AccountPrefixedTitle';
import Address from './Address';
import TouchableItem from './TouchableItem';
import { NetworksContext } from 'stores/NetworkContext';
import Separator from 'components/Separator';
import {
defaultNetworkKey,
......@@ -34,9 +35,8 @@ import fontStyles from 'styles/fontStyles';
import { Identity } from 'types/identityTypes';
import {
isSubstrateNetworkParams,
isUnknownNetworkParams,
SubstrateNetworkParams
} from 'types/networkSpecsTypes';
isUnknownNetworkParams
} from 'types/networkTypes';
import { ButtonListener } from 'types/props';
import {
getAddressWithPath,
......@@ -64,6 +64,8 @@ export default function PathCard({
testID?: string;
titlePrefix?: string;
}): React.ReactElement {
const networksContext = useContext(NetworksContext);
const { networks, allNetworks } = networksContext;
const isNotEmptyName = name && name !== '';
const pathName = isNotEmptyName ? name : getPathName(path, identity);
const { isSeedRefValid, substrateAddress } = useSeedRef(
......@@ -71,15 +73,14 @@ export default function PathCard({
);
const [address, setAddress] = useState('');
const computedNetworkKey =
networkKey || getNetworkKeyByPath(path, identity.meta.get(path)!);
networkKey ||
getNetworkKeyByPath(path, identity.meta.get(path)!, networksContext);
useEffect(() => {
const getAddress = async (): Promise<void> => {
const existedAddress = getAddressWithPath(path, identity);
if (existedAddress !== '') return setAddress(existedAddress);
if (isSeedRefValid && isPathValid) {
const prefix = (NETWORK_LIST[
computedNetworkKey
] as SubstrateNetworkParams).prefix;
if (isSeedRefValid && isPathValid && networks.has(computedNetworkKey)) {
const prefix = networks.get(computedNetworkKey)!.prefix;
const generatedAddress = await substrateAddress(path, prefix);
return setAddress(generatedAddress);
}
......@@ -93,15 +94,18 @@ export default function PathCard({
networkKey,
computedNetworkKey,
isSeedRefValid,
substrateAddress
substrateAddress,
networks
]);
const isUnknownAddress = address === '';
const hasPassword = identity.meta.get(path)?.hasPassword ?? false;
const networkParams =
computedNetworkKey === UnknownNetworkKeys.UNKNOWN && !isUnknownAddress
computedNetworkKey === UnknownNetworkKeys.UNKNOWN &&
!isUnknownAddress &&
!allNetworks.has(computedNetworkKey)
? NETWORK_LIST[defaultNetworkKey]
: NETWORK_LIST[computedNetworkKey];
: allNetworks.get(computedNetworkKey)!;
const nonSubstrateCard = (
<>
......
......@@ -37,7 +37,7 @@ import {
isUnknownNetworkParams,
SubstrateNetworkParams,
UnknownNetworkParams
} from 'types/networkSpecsTypes';
} from 'types/networkTypes';
import { removeSlash } from 'utils/identitiesUtils';
import { useSeedRef } from 'utils/seedRefHooks';
import { unlockSeedPhrase } from 'utils/navigationHelpers';
......@@ -89,7 +89,7 @@ export default function PathGroupCard({
await accountsStore.deriveNewPath(
nextPath,
substrateAddress,
(networkParams as SubstrateNetworkParams).genesisHash,
networkParams as SubstrateNetworkParams,
name,
''
);
......
......@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { ReactElement, ReactNode } from 'react';
import React, { ReactElement, ReactNode, useContext } from 'react';
import { View, StyleSheet, Text, ViewStyle, TextStyle } from 'react-native';
import AntIcon from 'react-native-vector-icons/AntDesign';
import { Icon } from 'react-native-elements';
......@@ -22,9 +22,9 @@ import { Icon } from 'react-native-elements';
import ButtonIcon from './ButtonIcon';
import AccountIcon from './AccountIcon';
import { NetworksContext } from 'stores/NetworkContext';
import TouchableItem from 'components/TouchableItem';
import testIDs from 'e2e/testIDs';
import { NETWORK_LIST } from 'constants/networkSpecs';
import fontStyles from 'styles/fontStyles';
import fonts from 'styles/fonts';
import colors from 'styles/colors';
......@@ -115,6 +115,7 @@ export function LeftScreenHeading({
...baseStyles.text,
...baseStyles.t_left
};
const { getNetwork } = useContext(NetworksContext);
const isDisabled = onPress === undefined;
return (
<TouchableItem
......@@ -125,7 +126,7 @@ export function LeftScreenHeading({
<View style={{ alignItems: 'center', flexDirection: 'row' }}>
<AccountIcon
address={''}
network={NETWORK_LIST[networkKey]}
network={getNetwork(networkKey)}
style={baseStyles.networkIcon}
/>
<View>
......
......@@ -16,12 +16,14 @@
import colors from 'styles/colors';
import {
EthereumNetworkDefaultConstants,
EthereumNetworkParams,
NetworkParams,
NetworkProtocol,
SubstrateNetworkDefaultConstant,
SubstrateNetworkParams,
UnknownNetworkParams
} from 'types/networkSpecsTypes';
} from 'types/networkTypes';
export const unknownNetworkPathId = '';
......@@ -68,19 +70,31 @@ export const SubstrateNetworkKeys: Record<string, string> = Object.freeze({
WESTEND: '0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e'
});
export const unknownNetworkParams: UnknownNetworkParams = {
color: colors.signal.error,
order: 99,
pathId: unknownNetworkPathId,
prefix: 2,
protocol: NetworkProtocols.UNKNOWN,
secondaryColor: colors.background.card,
title: 'Unknown network'
};
export const dummySubstrateNetworkParams: SubstrateNetworkParams = {
...unknownNetworkParams,
decimals: 12,
deleted: false,
genesisHash: UnknownNetworkKeys.UNKNOWN,
logo: require('res/img/logos/Substrate_Dev.png'),
protocol: NetworkProtocols.SUBSTRATE,
unit: 'UNIT'
};
const unknownNetworkBase: Record<string, UnknownNetworkParams> = {
[UnknownNetworkKeys.UNKNOWN]: {
color: colors.signal.error,
order: 99,
pathId: unknownNetworkPathId,
prefix: 2,
protocol: NetworkProtocols.UNKNOWN,
secondaryColor: colors.background.card,
title: 'Unknown network'
}
[UnknownNetworkKeys.UNKNOWN]: unknownNetworkParams
};
const substrateNetworkBase: Record<string, Partial<SubstrateNetworkParams>> = {
const substrateNetworkBase: Record<string, SubstrateNetworkDefaultConstant> = {
[SubstrateNetworkKeys.CENTRIFUGE]: {
color: '#FCC367',
decimals: 18,
......@@ -179,7 +193,7 @@ const substrateNetworkBase: Record<string, Partial<SubstrateNetworkParams>> = {
}
};
const ethereumNetworkBase: Record<string, Partial<EthereumNetworkParams>> = {
const ethereumNetworkBase: Record<string, EthereumNetworkDefaultConstants> = {
[EthereumNetworkKeys.FRONTIER]: {
color: '#8B94B3',
ethereumChainId: EthereumNetworkKeys.FRONTIER,
......@@ -221,21 +235,31 @@ const ethereumDefaultValues = {
const substrateDefaultValues = {
color: '#4C4646',
deleted: false,
logo: require('res/img/logos/Substrate_Dev.png'),
protocol: NetworkProtocols.SUBSTRATE,
secondaryColor: colors.background.card
};
function setDefault(
networkBase: any,
defaultProps: object
): { [key: string]: any } {
return Object.keys(networkBase).reduce((acc, networkKey) => {
function setEthereumNetworkDefault(): Record<string, EthereumNetworkParams> {
return Object.keys(ethereumNetworkBase).reduce((acc, networkKey) => {
return {
...acc,
[networkKey]: {
...defaultProps,
...networkBase[networkKey]
...ethereumDefaultValues,
...ethereumNetworkBase[networkKey]
}
};
}, {});
}
function setSubstrateNetworkDefault(): Record<string, SubstrateNetworkParams> {
return Object.keys(substrateNetworkBase).reduce((acc, networkKey) => {
return {
...acc,
[networkKey]: {
...substrateDefaultValues,
...substrateNetworkBase[networkKey]
}
};
}, {});
......@@ -244,24 +268,16 @@ function setDefault(
export const ETHEREUM_NETWORK_LIST: Record<
string,
EthereumNetworkParams
> = Object.freeze(setDefault(ethereumNetworkBase, ethereumDefaultValues));
> = Object.freeze(setEthereumNetworkDefault());
export const SUBSTRATE_NETWORK_LIST: Record<
string,
SubstrateNetworkParams
> = Object.freeze(setDefault(substrateNetworkBase, substrateDefaultValues));
> = Object.freeze(setSubstrateNetworkDefault());
export const UNKNOWN_NETWORK: Record<
string,
UnknownNetworkParams
> = Object.freeze(unknownNetworkBase);
const substrateNetworkMetas = Object.values({
...SUBSTRATE_NETWORK_LIST,
...UNKNOWN_NETWORK
});
export const PATH_IDS_LIST = substrateNetworkMetas.map(
(meta: UnknownNetworkParams | SubstrateNetworkParams) => meta.pathId
);
export const NETWORK_LIST: Record<string, NetworkParams> = Object.freeze(
Object.assign(
{},
......
......@@ -18,20 +18,20 @@ import React, { ReactElement, useContext, useMemo, useState } from 'react';
import { BackHandler, FlatList, FlatListProps } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import { NETWORK_LIST } from 'constants/networkSpecs';
import { filterSubstrateNetworks } from 'modules/network/utils';
import { filterNetworks } from 'modules/network/utils';
import { NetworkCard } from 'components/AccountCard';
import { SafeAreaViewContainer } from 'components/SafeAreaContainer';
import ScreenHeading, { IdentityHeading } from 'components/ScreenHeading';
import testIDs from 'e2e/testIDs';
import { AlertStateContext } from 'stores/alertContext';
import { NetworksContext } from 'stores/NetworkContext';
import colors from 'styles/colors';
import {
isEthereumNetworkParams,
isSubstrateNetworkParams,
NetworkParams,
SubstrateNetworkParams
} from 'types/networkSpecsTypes';
} from 'types/networkTypes';
import { NavigationAccountIdentityProps } from 'types/props';
import { alertPathDerivationError } from 'utils/alertUtils';
import { withCurrentIdentity } from 'utils/HOC';
......@@ -53,6 +53,8 @@ function NetworkSelector({
const isNew = route.params?.isNew ?? false;
const [shouldShowMoreNetworks, setShouldShowMoreNetworks] = useState(false);
const { identities, currentIdentity } = accountsStore.state;
const networkContextState = useContext(NetworksContext);
const { getSubstrateNetwork, allNetworks } = networkContextState;
const seedRefHooks = useSeedRef(currentIdentity.encryptedSeed);
const { unlockWithoutPassword } = useUnlockSeed(seedRefHooks.isSeedRefValid);
......@@ -93,7 +95,7 @@ function NetworkSelector({
await accountsStore.deriveNewPath(
fullPath,
seedRefHooks.substrateAddress,
networkKey,
getSubstrateNetwork(networkKey),
`${networkParams.title} root`,
''
);
......@@ -108,7 +110,8 @@ function NetworkSelector({
try {
await accountsStore.deriveEthereumAccount(
seedRefHooks.brainWalletAddress,
networkKey
networkKey,
allNetworks
);
navigateToPathsList(navigation, networkKey);
} catch (e) {
......@@ -177,13 +180,13 @@ function NetworkSelector({
};
const availableNetworks = useMemo(
() => getExistedNetworkKeys(currentIdentity),
[currentIdentity]
() => getExistedNetworkKeys(currentIdentity, networkContextState),
[currentIdentity, networkContextState]
);
const networkList = useMemo(
() =>
filterSubstrateNetworks(NETWORK_LIST, (networkKey, shouldExclude) => {
filterNetworks(allNetworks, (networkKey, shouldExclude) => {
if (isNew && !shouldExclude) return true;
if (shouldShowMoreNetworks) {
......@@ -192,7 +195,7 @@ function NetworkSelector({
}
return availableNetworks.includes(networkKey);
}),
[availableNetworks, isNew, shouldShowMoreNetworks]
[availableNetworks, isNew, shouldShowMoreNetworks, allNetworks]
);
const renderNetwork = ({
......
......@@ -14,21 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import React, { useContext } from 'react';
import { NetworkCard } from 'components/AccountCard';
import NetworkInfoCard from 'modules/network/components/NetworkInfoCard';
import { SafeAreaScrollViewContainer } from 'components/SafeAreaContainer';
import { SUBSTRATE_NETWORK_LIST } from 'constants/networkSpecs';
import { NetworksContext } from 'stores/NetworkContext';
import { NavigationProps } from 'types/props';
import { getNetworkKeyByPathId } from 'utils/identitiesUtils';
import { getSubstrateNetworkKeyByPathId } from 'utils/identitiesUtils';