mirror of
https://github.com/trezor/trezor-wallet
synced 2025-06-28 02:42:35 +00:00
Merge pull request #625 from trezor/feature/passphrase-redesign
[WIP] Feature/passphrase redesign
This commit is contained in:
commit
bb6a13812b
@ -1,4 +1,5 @@
|
|||||||
[include]
|
[include]
|
||||||
|
.*/node_modules/hd-wallet/lib
|
||||||
|
|
||||||
[ignore]
|
[ignore]
|
||||||
.*/node_modules/fbjs/.*
|
.*/node_modules/fbjs/.*
|
||||||
@ -7,6 +8,9 @@
|
|||||||
.*/node_modules/redux/.*
|
.*/node_modules/redux/.*
|
||||||
.*/node_modules/react-router/.*
|
.*/node_modules/react-router/.*
|
||||||
.*/node_modules/oboe/test/.*
|
.*/node_modules/oboe/test/.*
|
||||||
|
.*/node_modules/trezor-link/lib/lowlevel/webusb.js.flow
|
||||||
|
.*/node_modules/hd-wallet/lib/utils/stream.js.flow
|
||||||
|
.*/node_modules/protobufjs-old-fixed-webpack/src/bower.json
|
||||||
.*/_old/.*
|
.*/_old/.*
|
||||||
.*/scripts/solidity/.*
|
.*/scripts/solidity/.*
|
||||||
.*/build/.*
|
.*/build/.*
|
||||||
@ -19,7 +23,6 @@
|
|||||||
./src/flowtype/npm/react-router-dom_v4.x.x.js
|
./src/flowtype/npm/react-router-dom_v4.x.x.js
|
||||||
; ./src/flowtype/npm/react-intl_v2.x.x.js // TODO: uncomment to get proper flow support for intl (needs some flow fixing)
|
; ./src/flowtype/npm/react-intl_v2.x.x.js // TODO: uncomment to get proper flow support for intl (needs some flow fixing)
|
||||||
./src/flowtype/npm/connected-react-router.js
|
./src/flowtype/npm/connected-react-router.js
|
||||||
./src/flowtype/npm/bignumber.js
|
|
||||||
./src/flowtype/npm/ethereum-types.js
|
./src/flowtype/npm/ethereum-types.js
|
||||||
./src/flowtype/npm/web3.js
|
./src/flowtype/npm/web3.js
|
||||||
./src/flowtype/css.js
|
./src/flowtype/css.js
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/polyfill": "^7.2.5",
|
"@babel/polyfill": "^7.2.5",
|
||||||
"@hot-loader/react-dom": "16.8.6",
|
"@hot-loader/react-dom": "16.8.6",
|
||||||
"bignumber.js": "8.0.2",
|
"bignumber.js": "9.0.0",
|
||||||
"color-hash": "^1.0.3",
|
"color-hash": "^1.0.3",
|
||||||
"commander": "^2.19.0",
|
"commander": "^2.19.0",
|
||||||
"connected-react-router": "6.4.0",
|
"connected-react-router": "6.4.0",
|
||||||
@ -47,7 +47,6 @@
|
|||||||
"ethereumjs-util": "^6.0.0",
|
"ethereumjs-util": "^6.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"git-revision-webpack-plugin": "^3.0.3",
|
"git-revision-webpack-plugin": "^3.0.3",
|
||||||
"hdkey": "^1.1.0",
|
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"jest-fetch-mock": "^2.1.0",
|
"jest-fetch-mock": "^2.1.0",
|
||||||
@ -81,7 +80,7 @@
|
|||||||
"styled-components": "^4.1.3",
|
"styled-components": "^4.1.3",
|
||||||
"styled-normalize": "^8.0.6",
|
"styled-normalize": "^8.0.6",
|
||||||
"trezor-bridge-communicator": "1.0.2",
|
"trezor-bridge-communicator": "1.0.2",
|
||||||
"trezor-connect": "7.0.2",
|
"trezor-connect": "8.1.0-beta.8",
|
||||||
"trezor-ui-components": "^1.0.0-beta.20",
|
"trezor-ui-components": "^1.0.0-beta.20",
|
||||||
"wallet-address-validator": "^0.2.4",
|
"wallet-address-validator": "^0.2.4",
|
||||||
"web3": "1.0.0-beta.36",
|
"web3": "1.0.0-beta.36",
|
||||||
@ -124,7 +123,7 @@
|
|||||||
"eslint-plugin-prettier": "^3.0.1",
|
"eslint-plugin-prettier": "^3.0.1",
|
||||||
"eslint-plugin-react": "^7.12.4",
|
"eslint-plugin-react": "^7.12.4",
|
||||||
"file-loader": "3.0.1",
|
"file-loader": "3.0.1",
|
||||||
"flow-bin": "0.90",
|
"flow-bin": "0.121.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.16.4",
|
||||||
"prettier-eslint": "^9.0.0",
|
"prettier-eslint": "^9.0.0",
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"name": "Ethereum",
|
"name": "Ethereum",
|
||||||
"symbol": "ETH",
|
"symbol": "ETH",
|
||||||
"shortcut": "eth",
|
"shortcut": "eth",
|
||||||
"bip44": "m/44'/60'/0'/0",
|
"bip44": "m/44'/60'/0'/0/a",
|
||||||
"chainId": 1,
|
"chainId": 1,
|
||||||
"defaultGasPrice": 64,
|
"defaultGasPrice": 64,
|
||||||
"defaultGasLimit": 21000,
|
"defaultGasLimit": 21000,
|
||||||
@ -26,8 +26,8 @@
|
|||||||
"wss://eth2.trezor.io/geth"
|
"wss://eth2.trezor.io/geth"
|
||||||
],
|
],
|
||||||
"explorer": {
|
"explorer": {
|
||||||
"tx": "https://etherscan.io/tx/",
|
"tx": "https://eth1.trezor.io/tx/",
|
||||||
"address": "https://etherscan.io/address/"
|
"address": "https://eth1.trezor.io/address/"
|
||||||
},
|
},
|
||||||
"hasSignVerify": true
|
"hasSignVerify": true
|
||||||
},
|
},
|
||||||
@ -38,7 +38,7 @@
|
|||||||
"symbol": "ETC",
|
"symbol": "ETC",
|
||||||
"shortcut": "etc",
|
"shortcut": "etc",
|
||||||
"chainId": 61,
|
"chainId": 61,
|
||||||
"bip44": "m/44'/61'/0'/0",
|
"bip44": "m/44'/61'/0'/0/a",
|
||||||
"defaultGasPrice": 64,
|
"defaultGasPrice": 64,
|
||||||
"defaultGasLimit": 21000,
|
"defaultGasLimit": 21000,
|
||||||
"defaultGasLimitTokens": 200000,
|
"defaultGasLimitTokens": 200000,
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"symbol": "tROP",
|
"symbol": "tROP",
|
||||||
"shortcut": "trop",
|
"shortcut": "trop",
|
||||||
"chainId": 3,
|
"chainId": 3,
|
||||||
"bip44": "m/44'/60'/0'/0",
|
"bip44": "m/44'/60'/0'/0/a",
|
||||||
"defaultGasPrice": 64,
|
"defaultGasPrice": 64,
|
||||||
"defaultGasLimit": 21000,
|
"defaultGasLimit": 21000,
|
||||||
"defaultGasLimitTokens": 200000,
|
"defaultGasLimitTokens": 200000,
|
||||||
@ -83,8 +83,8 @@
|
|||||||
"wss://ropsten1.trezor.io/geth"
|
"wss://ropsten1.trezor.io/geth"
|
||||||
],
|
],
|
||||||
"explorer": {
|
"explorer": {
|
||||||
"tx": "https://ropsten.etherscan.io/tx/",
|
"tx": "https://ropsten1.trezor.io/tx/",
|
||||||
"address": "https://ropsten.etherscan.io/address/"
|
"address": "https://ropsten1.trezor.io/address/"
|
||||||
},
|
},
|
||||||
"hasSignVerify": true
|
"hasSignVerify": true
|
||||||
},
|
},
|
||||||
|
@ -76,32 +76,31 @@ export const subscribe = (networkName: string): PromiseAction<void> => async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onBlockMined = (
|
export const onBlockMined = (payload: BlockchainBlock): PromiseAction<void> => async (
|
||||||
payload: $ElementType<BlockchainBlock, 'payload'>
|
dispatch: Dispatch,
|
||||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const { block } = payload;
|
|
||||||
if (getState().router.location.state.network !== shortcut) return;
|
|
||||||
|
|
||||||
const { config } = getState().localStorage;
|
const { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === shortcut);
|
const network = config.networks.find(c => c.shortcut === shortcut);
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
|
|
||||||
switch (network.type) {
|
switch (network.type) {
|
||||||
case 'ethereum':
|
case 'ethereum':
|
||||||
await dispatch(EthereumBlockchainActions.onBlockMined(shortcut));
|
await dispatch(EthereumBlockchainActions.onBlockMined(network));
|
||||||
break;
|
break;
|
||||||
case 'ripple':
|
case 'ripple':
|
||||||
await dispatch(RippleBlockchainActions.onBlockMined(shortcut, block));
|
await dispatch(RippleBlockchainActions.onBlockMined(network));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onNotification = (
|
export const onNotification = (payload: BlockchainNotification): PromiseAction<void> => async (
|
||||||
payload: $ElementType<BlockchainNotification, 'payload'>
|
dispatch: Dispatch,
|
||||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const { config } = getState().localStorage;
|
const { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === shortcut);
|
const network = config.networks.find(c => c.shortcut === shortcut);
|
||||||
@ -109,11 +108,10 @@ export const onNotification = (
|
|||||||
|
|
||||||
switch (network.type) {
|
switch (network.type) {
|
||||||
case 'ethereum':
|
case 'ethereum':
|
||||||
// this is not working until blockchain-link will start support blockbook backends
|
await dispatch(EthereumBlockchainActions.onNotification(payload, network));
|
||||||
await dispatch(EthereumBlockchainActions.onNotification(payload));
|
|
||||||
break;
|
break;
|
||||||
case 'ripple':
|
case 'ripple':
|
||||||
await dispatch(RippleBlockchainActions.onNotification(payload));
|
await dispatch(RippleBlockchainActions.onNotification(payload, network));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -122,9 +120,10 @@ export const onNotification = (
|
|||||||
|
|
||||||
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
||||||
// disconnect and remove Web3 websocket instance if exists
|
// disconnect and remove Web3 websocket instance if exists
|
||||||
export const onError = (
|
export const onError = (payload: BlockchainError): PromiseAction<void> => async (
|
||||||
payload: $ElementType<BlockchainError, 'payload'>
|
dispatch: Dispatch,
|
||||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
getState: GetState
|
||||||
|
): Promise<void> => {
|
||||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const { config } = getState().localStorage;
|
const { config } = getState().localStorage;
|
||||||
const network = config.networks.find(c => c.shortcut === shortcut);
|
const network = config.networks.find(c => c.shortcut === shortcut);
|
||||||
|
@ -4,10 +4,9 @@ import * as ACCOUNT from 'actions/constants/account';
|
|||||||
import * as IMPORT from 'actions/constants/importAccount';
|
import * as IMPORT from 'actions/constants/importAccount';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import type { AsyncAction, Account, TrezorDevice, Network, Dispatch, GetState } from 'flowtype';
|
import type { AsyncAction, Account, TrezorDevice, Network, Dispatch, GetState } from 'flowtype';
|
||||||
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
|
||||||
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import { toDecimalAmount } from 'utils/formatUtils';
|
import { enhanceAccount } from 'utils/accountUtils';
|
||||||
|
|
||||||
export type ImportAccountAction =
|
export type ImportAccountAction =
|
||||||
| {
|
| {
|
||||||
@ -23,10 +22,7 @@ export type ImportAccountAction =
|
|||||||
|
|
||||||
const findIndex = (accounts: Array<Account>, network: Network, device: TrezorDevice): number => {
|
const findIndex = (accounts: Array<Account>, network: Network, device: TrezorDevice): number => {
|
||||||
return accounts.filter(
|
return accounts.filter(
|
||||||
a =>
|
a => a.imported === true && a.network === network.shortcut && a.deviceID === device.id
|
||||||
a.imported === true &&
|
|
||||||
a.network === network.shortcut &&
|
|
||||||
a.deviceID === (device.features || {}).device_id
|
|
||||||
).length;
|
).length;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,113 +31,22 @@ export const importAddress = (
|
|||||||
network: Network,
|
network: Network,
|
||||||
device: ?TrezorDevice
|
device: ?TrezorDevice
|
||||||
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
if (!device) return;
|
if (!device || !device.features) return;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: IMPORT.START,
|
type: IMPORT.START,
|
||||||
});
|
});
|
||||||
|
|
||||||
let payload;
|
const response = await TrezorConnect.getAccountInfo({
|
||||||
try {
|
|
||||||
if (network.type === 'ethereum') {
|
|
||||||
const account = await dispatch(
|
|
||||||
BlockchainActions.discoverAccount(device, address, network.shortcut)
|
|
||||||
);
|
|
||||||
|
|
||||||
const index = findIndex(getState().accounts, network, device);
|
|
||||||
|
|
||||||
const empty = account.nonce <= 0 && account.balance === '0';
|
|
||||||
payload = {
|
|
||||||
imported: true,
|
|
||||||
index,
|
|
||||||
network: network.shortcut,
|
|
||||||
deviceID: device.features ? device.features.device_id : '0',
|
|
||||||
deviceState: device.state || '0',
|
|
||||||
accountPath: account.path || [],
|
|
||||||
descriptor: account.descriptor,
|
|
||||||
|
|
||||||
balance: account.balance,
|
|
||||||
availableBalance: account.balance,
|
|
||||||
block: account.block,
|
|
||||||
transactions: account.transactions,
|
|
||||||
empty,
|
|
||||||
|
|
||||||
networkType: 'ethereum',
|
|
||||||
nonce: account.nonce,
|
|
||||||
};
|
|
||||||
dispatch({
|
|
||||||
type: ACCOUNT.CREATE,
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
dispatch({
|
|
||||||
type: IMPORT.SUCCESS,
|
|
||||||
});
|
|
||||||
dispatch(LocalStorageActions.setImportedAccount(payload));
|
|
||||||
dispatch({
|
|
||||||
type: NOTIFICATION.ADD,
|
|
||||||
payload: {
|
|
||||||
variant: 'success',
|
|
||||||
title: 'The account has been successfully imported',
|
|
||||||
cancelable: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (network.type === 'ripple') {
|
|
||||||
const response = await TrezorConnect.rippleGetAccountInfo({
|
|
||||||
account: {
|
|
||||||
descriptor: address,
|
descriptor: address,
|
||||||
},
|
|
||||||
coin: network.shortcut,
|
coin: network.shortcut,
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle TREZOR response error
|
// handle TREZOR response error
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
throw new Error(response.payload.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = response.payload;
|
|
||||||
const empty = account.sequence <= 0 && account.balance === '0';
|
|
||||||
const index = findIndex(getState().accounts, network, device);
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
imported: true,
|
|
||||||
index,
|
|
||||||
network: network.shortcut,
|
|
||||||
deviceID: device.features ? device.features.device_id : '0',
|
|
||||||
deviceState: device.state || '0',
|
|
||||||
accountPath: account.path || [],
|
|
||||||
descriptor: account.descriptor,
|
|
||||||
|
|
||||||
balance: toDecimalAmount(account.balance, network.decimals),
|
|
||||||
availableBalance: toDecimalAmount(account.availableBalance, network.decimals),
|
|
||||||
block: account.block,
|
|
||||||
transactions: account.transactions,
|
|
||||||
empty,
|
|
||||||
|
|
||||||
networkType: 'ripple',
|
|
||||||
sequence: account.sequence,
|
|
||||||
reserve: toDecimalAmount(account.reserve, network.decimals),
|
|
||||||
};
|
|
||||||
dispatch({
|
|
||||||
type: ACCOUNT.CREATE,
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
dispatch({
|
|
||||||
type: IMPORT.SUCCESS,
|
|
||||||
});
|
|
||||||
dispatch(LocalStorageActions.setImportedAccount(payload));
|
|
||||||
dispatch({
|
|
||||||
type: NOTIFICATION.ADD,
|
|
||||||
payload: {
|
|
||||||
variant: 'success',
|
|
||||||
title: 'The account has been successfully imported',
|
|
||||||
cancelable: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: IMPORT.FAIL,
|
type: IMPORT.FAIL,
|
||||||
error: error.message,
|
error: response.payload.error,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -149,9 +54,36 @@ export const importAddress = (
|
|||||||
payload: {
|
payload: {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
title: 'Import account error',
|
title: 'Import account error',
|
||||||
message: error.message,
|
message: response.payload.error,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const index = findIndex(getState().accounts, network, device);
|
||||||
|
const account = enhanceAccount(response.payload, {
|
||||||
|
imported: true,
|
||||||
|
index,
|
||||||
|
network,
|
||||||
|
device,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ACCOUNT.CREATE,
|
||||||
|
payload: account,
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: IMPORT.SUCCESS,
|
||||||
|
});
|
||||||
|
dispatch(LocalStorageActions.setImportedAccount(account));
|
||||||
|
dispatch({
|
||||||
|
type: NOTIFICATION.ADD,
|
||||||
|
payload: {
|
||||||
|
variant: 'success',
|
||||||
|
title: 'The account has been successfully imported',
|
||||||
|
cancelable: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -213,7 +213,8 @@ const loadJSON = (): AsyncAction => async (dispatch: Dispatch): Promise<void> =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const VERSION: string = '2';
|
// version 3 since trezor-connect@8 implementation
|
||||||
|
const VERSION: string = '3';
|
||||||
|
|
||||||
const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
|
const loadStorageData = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
// validate version
|
// validate version
|
||||||
@ -454,9 +455,8 @@ export const removeImportedAccounts = (device: TrezorDevice): ThunkAction => (
|
|||||||
const importedAccounts: ?Array<Account> = getImportedAccounts();
|
const importedAccounts: ?Array<Account> = getImportedAccounts();
|
||||||
if (!importedAccounts) return;
|
if (!importedAccounts) return;
|
||||||
|
|
||||||
const deviceId = device.features ? device.features.device_id : null;
|
|
||||||
const filteredImportedAccounts = importedAccounts.filter(
|
const filteredImportedAccounts = importedAccounts.filter(
|
||||||
account => account.deviceID !== deviceId
|
account => account.deviceID !== device.id
|
||||||
);
|
);
|
||||||
storageUtils.remove(TYPE, KEY_IMPORTED_ACCOUNTS);
|
storageUtils.remove(TYPE, KEY_IMPORTED_ACCOUNTS);
|
||||||
filteredImportedAccounts.forEach(account => {
|
filteredImportedAccounts.forEach(account => {
|
||||||
|
@ -33,14 +33,14 @@ export const onPinSubmit = (value: string): Action => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (
|
export const onPassphraseSubmit = (
|
||||||
dispatch: Dispatch,
|
passphrase: string,
|
||||||
getState: GetState
|
passphraseOnDevice: boolean = false
|
||||||
): Promise<void> => {
|
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const { modal } = getState();
|
const { modal } = getState();
|
||||||
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
|
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
|
||||||
|
|
||||||
if (passphrase === '') {
|
if (passphrase === '' && !passphraseOnDevice) {
|
||||||
// set standard wallet type if passphrase is blank
|
// set standard wallet type if passphrase is blank
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CONNECT.UPDATE_WALLET_TYPE,
|
type: CONNECT.UPDATE_WALLET_TYPE,
|
||||||
@ -54,6 +54,7 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (
|
|||||||
payload: {
|
payload: {
|
||||||
value: passphrase,
|
value: passphrase,
|
||||||
save: true,
|
save: true,
|
||||||
|
passphraseOnDevice,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ export const onDeviceConnect = (device: Device): ThunkAction => (
|
|||||||
device.features &&
|
device.features &&
|
||||||
modal.device &&
|
modal.device &&
|
||||||
modal.device.features &&
|
modal.device.features &&
|
||||||
modal.device.features.device_id === device.features.device_id
|
modal.device.id === device.id
|
||||||
) {
|
) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: MODAL.CLOSE,
|
type: MODAL.CLOSE,
|
||||||
|
@ -54,7 +54,7 @@ export const showUnverifiedAddress = (): Action => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
//export const showAddress = (address_n: string): AsyncAction => {
|
//export const showAddress = (address_n: string): AsyncAction => {
|
||||||
export const showAddress = (path: Array<number>): AsyncAction => async (
|
export const showAddress = (path: string): AsyncAction => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: GetState
|
getState: GetState
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
@ -65,14 +65,14 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
|
|||||||
device = devices.find(
|
device = devices.find(
|
||||||
d =>
|
d =>
|
||||||
d.features &&
|
d.features &&
|
||||||
d.features.device_id === params.device &&
|
d.id === params.device &&
|
||||||
d.instance === parseInt(params.deviceInstance, 10)
|
d.instance === parseInt(params.deviceInstance, 10)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
device = devices.find(
|
device = devices.find(
|
||||||
d =>
|
d =>
|
||||||
((!d.features || d.mode === 'bootloader') && d.path === params.device) ||
|
((!d.features || d.mode === 'bootloader') && d.path === params.device) ||
|
||||||
(d.features && d.features.device_id === params.device)
|
(d.features && d.id === params.device)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,21 +218,22 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
|
|||||||
getState: GetState
|
getState: GetState
|
||||||
): ?string => {
|
): ?string => {
|
||||||
let url: ?string;
|
let url: ?string;
|
||||||
|
const prefix = `/device/${device.id || device.path}`;
|
||||||
if (!device.features) {
|
if (!device.features) {
|
||||||
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
|
url = `${prefix}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
|
||||||
} else if (device.mode === 'bootloader') {
|
} else if (device.mode === 'bootloader') {
|
||||||
// device in bootloader doesn't have device_id
|
// device in bootloader doesn't have device_id
|
||||||
url = `/device/${device.path}/bootloader`;
|
url = `${prefix}/bootloader`;
|
||||||
} else if (device.mode === 'initialize') {
|
} else if (device.mode === 'initialize') {
|
||||||
url = `/device/${device.features.device_id}/initialize`;
|
url = `${prefix}/initialize`;
|
||||||
} else if (device.mode === 'seedless') {
|
} else if (device.mode === 'seedless') {
|
||||||
url = `/device/${device.features.device_id}/seedless`;
|
url = `${prefix}/seedless`;
|
||||||
} else if (device.firmware === 'required') {
|
} else if (device.firmware === 'required') {
|
||||||
url = `/device/${device.features.device_id}/firmware-update`;
|
url = `${prefix}/firmware-update`;
|
||||||
} else if (typeof device.instance === 'number') {
|
} else if (typeof device.instance === 'number') {
|
||||||
url = `/device/${device.features.device_id}:${device.instance}`;
|
url = `${prefix}:${device.instance}`;
|
||||||
} else {
|
} else {
|
||||||
url = `/device/${device.features.device_id}`;
|
url = `${prefix}`;
|
||||||
// make sure that device is not TrezorDevice type
|
// make sure that device is not TrezorDevice type
|
||||||
if (!device.hasOwnProperty('ts')) {
|
if (!device.hasOwnProperty('ts')) {
|
||||||
// it is device from trezor-connect triggered by DEVICE.CONNECT event
|
// it is device from trezor-connect triggered by DEVICE.CONNECT event
|
||||||
@ -379,10 +380,8 @@ export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void =>
|
|||||||
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (
|
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (
|
||||||
dispatch: Dispatch
|
dispatch: Dispatch
|
||||||
): void => {
|
): void => {
|
||||||
if (device.features) {
|
if (device.id) {
|
||||||
const devUrl: string = `${device.features.device_id}${
|
const devUrl: string = `${device.id}${device.instance ? `:${device.instance}` : ''}`;
|
||||||
device.instance ? `:${device.instance}` : ''
|
|
||||||
}`;
|
|
||||||
dispatch(goto(`/device/${devUrl}/settings`));
|
dispatch(goto(`/device/${devUrl}/settings`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -403,8 +402,8 @@ export const gotoFirmwareUpdate = (): ThunkAction => (
|
|||||||
getState: GetState
|
getState: GetState
|
||||||
): void => {
|
): void => {
|
||||||
const { selectedDevice } = getState().wallet;
|
const { selectedDevice } = getState().wallet;
|
||||||
if (!selectedDevice || !selectedDevice.features) return;
|
if (!selectedDevice || !selectedDevice.id) return;
|
||||||
const devUrl: string = `${selectedDevice.features.device_id}${
|
const devUrl: string = `${selectedDevice.id}${
|
||||||
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
|
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
|
||||||
}`;
|
}`;
|
||||||
dispatch(goto(`/device/${devUrl}/firmware-update`));
|
dispatch(goto(`/device/${devUrl}/firmware-update`));
|
||||||
@ -415,8 +414,8 @@ export const gotoFirmwareUpdate = (): ThunkAction => (
|
|||||||
*/
|
*/
|
||||||
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const { selectedDevice } = getState().wallet;
|
const { selectedDevice } = getState().wallet;
|
||||||
if (!selectedDevice || !selectedDevice.features) return;
|
if (!selectedDevice || !selectedDevice.id) return;
|
||||||
const devUrl: string = `${selectedDevice.features.device_id}${
|
const devUrl: string = `${selectedDevice.id}${
|
||||||
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
|
selectedDevice.instance ? `:${selectedDevice.instance}` : ''
|
||||||
}`;
|
}`;
|
||||||
dispatch(goto(`/device/${devUrl}/backup`));
|
dispatch(goto(`/device/${devUrl}/backup`));
|
||||||
|
@ -39,7 +39,7 @@ export type SignVerifyAction =
|
|||||||
message: ?string,
|
message: ?string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sign = (path: Array<number>, message: string, hex: boolean = false): AsyncAction => async (
|
const sign = (path: string, message: string, hex: boolean = false): AsyncAction => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: GetState
|
getState: GetState
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import * as TOKEN from 'actions/constants/token';
|
import * as TOKEN from 'actions/constants/token';
|
||||||
|
import { toDecimalAmount } from 'utils/formatUtils';
|
||||||
|
|
||||||
|
import type { TokenInfo } from 'trezor-connect';
|
||||||
import type { GetState, AsyncAction, Action, Dispatch } from 'flowtype';
|
import type { GetState, AsyncAction, Action, Dispatch } from 'flowtype';
|
||||||
import type { State, Token } from 'reducers/TokensReducer';
|
import type { State, Token } from 'reducers/TokensReducer';
|
||||||
import type { Account } from 'reducers/AccountsReducer';
|
import type { Account } from 'reducers/AccountsReducer';
|
||||||
@ -60,21 +62,19 @@ export const setBalance = (
|
|||||||
ethAddress: string,
|
ethAddress: string,
|
||||||
balance: string
|
balance: string
|
||||||
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const newState: Array<Token> = [...getState().tokens];
|
const { tokens } = getState();
|
||||||
const token: ?Token = newState.find(
|
const index = tokens.findIndex(t => t.address === tokenAddress && t.ethAddress === ethAddress);
|
||||||
t => t.address === tokenAddress && t.ethAddress === ethAddress
|
if (index >= 0) {
|
||||||
);
|
const token = tokens[index];
|
||||||
if (token) {
|
const payload = tokens.slice();
|
||||||
const others = newState.filter(t => t !== token);
|
payload[index] = {
|
||||||
dispatch({
|
|
||||||
type: TOKEN.SET_BALANCE,
|
|
||||||
payload: others.concat([
|
|
||||||
{
|
|
||||||
...token,
|
...token,
|
||||||
loaded: true,
|
loaded: true,
|
||||||
balance,
|
balance,
|
||||||
},
|
};
|
||||||
]),
|
dispatch({
|
||||||
|
type: TOKEN.SET_BALANCE,
|
||||||
|
payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -103,6 +103,27 @@ export const add = (token: NetworkToken, account: Account): AsyncAction => async
|
|||||||
dispatch(setBalance(token.address, account.descriptor, tokenBalance));
|
dispatch(setBalance(token.address, account.descriptor, tokenBalance));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createAccountTokens = (account: Account, tokens: TokenInfo[]): AsyncAction => async (
|
||||||
|
dispatch: Dispatch
|
||||||
|
): Promise<void> => {
|
||||||
|
tokens.forEach(t => {
|
||||||
|
dispatch({
|
||||||
|
type: TOKEN.ADD,
|
||||||
|
payload: {
|
||||||
|
address: t.address,
|
||||||
|
balance: toDecimalAmount(t.balance || '0', t.decimals),
|
||||||
|
decimals: t.decimals,
|
||||||
|
deviceState: account.deviceState,
|
||||||
|
ethAddress: account.descriptor,
|
||||||
|
loaded: true,
|
||||||
|
name: t.name || '',
|
||||||
|
network: account.network,
|
||||||
|
symbol: t.symbol || '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const remove = (token: Token): Action => ({
|
export const remove = (token: Token): Action => ({
|
||||||
type: TOKEN.REMOVE,
|
type: TOKEN.REMOVE,
|
||||||
token,
|
token,
|
||||||
|
@ -17,16 +17,6 @@ import * as RouterActions from 'actions/RouterActions';
|
|||||||
import * as deviceUtils from 'utils/device';
|
import * as deviceUtils from 'utils/device';
|
||||||
import * as buildUtils from 'utils/build';
|
import * as buildUtils from 'utils/build';
|
||||||
|
|
||||||
import type {
|
|
||||||
DeviceMessage,
|
|
||||||
DeviceMessageType,
|
|
||||||
UiMessage,
|
|
||||||
UiMessageType,
|
|
||||||
TransportMessage,
|
|
||||||
TransportMessageType,
|
|
||||||
BlockchainEvent,
|
|
||||||
} from 'trezor-connect';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
GetState,
|
||||||
@ -113,62 +103,32 @@ export const init = (): AsyncAction => async (
|
|||||||
getState: GetState
|
getState: GetState
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
// set listeners
|
// set listeners
|
||||||
TrezorConnect.on(
|
TrezorConnect.on(DEVICE_EVENT, event => {
|
||||||
DEVICE_EVENT,
|
|
||||||
(event: DeviceMessage): void => {
|
|
||||||
// post event to reducers
|
|
||||||
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
|
|
||||||
dispatch({
|
|
||||||
type,
|
|
||||||
device: event.payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
TrezorConnect.on(
|
|
||||||
UI_EVENT,
|
|
||||||
(event: UiMessage): void => {
|
|
||||||
// post event to reducers
|
|
||||||
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
|
|
||||||
dispatch({
|
|
||||||
type,
|
|
||||||
payload: event.payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
TrezorConnect.on(
|
|
||||||
TRANSPORT_EVENT,
|
|
||||||
(event: TransportMessage): void => {
|
|
||||||
// post event to reducers
|
|
||||||
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
|
|
||||||
dispatch({
|
|
||||||
type,
|
|
||||||
payload: event.payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// post event to reducers
|
|
||||||
TrezorConnect.on(
|
|
||||||
BLOCKCHAIN_EVENT,
|
|
||||||
(event: BlockchainEvent): void => {
|
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
|
TrezorConnect.on(UI_EVENT, event => {
|
||||||
|
dispatch(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
TrezorConnect.on(TRANSPORT_EVENT, event => {
|
||||||
|
dispatch(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
// post event to reducers
|
||||||
|
TrezorConnect.on(BLOCKCHAIN_EVENT, event => {
|
||||||
|
dispatch(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
let connectSrc;
|
||||||
if (buildUtils.isDev()) {
|
if (buildUtils.isDev()) {
|
||||||
// eslint-disable-next-line
|
connectSrc = typeof LOCAL === 'string' ? LOCAL : undefined;
|
||||||
window.__TREZOR_CONNECT_SRC =
|
|
||||||
typeof LOCAL === 'string'
|
|
||||||
? LOCAL
|
|
||||||
: 'https://connect.corp.sldev.cz/fix/v7-ripple-lib-error/'; // eslint-disable-line no-underscore-dangle
|
|
||||||
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://localhost:8088/'; // eslint-disable-line no-underscore-dangle
|
|
||||||
window.TrezorConnect = TrezorConnect;
|
window.TrezorConnect = TrezorConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await TrezorConnect.init({
|
await TrezorConnect.init({
|
||||||
|
connectSrc,
|
||||||
transportReconnect: true,
|
transportReconnect: true,
|
||||||
debug: false,
|
debug: false,
|
||||||
popup: false,
|
popup: false,
|
||||||
@ -223,10 +183,18 @@ export const requestWalletType = (): AsyncAction => async (
|
|||||||
if (!isDeviceReady) return;
|
if (!isDeviceReady) return;
|
||||||
|
|
||||||
if (selected.features && selected.features.passphrase_protection) {
|
if (selected.features && selected.features.passphrase_protection) {
|
||||||
|
if (selected.features.passphrase_always_on_device) {
|
||||||
|
dispatch({
|
||||||
|
type: CONNECT.RECEIVE_WALLET_TYPE,
|
||||||
|
device: selected,
|
||||||
|
hidden: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CONNECT.REQUEST_WALLET_TYPE,
|
type: CONNECT.REQUEST_WALLET_TYPE,
|
||||||
device: selected,
|
device: selected,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CONNECT.RECEIVE_WALLET_TYPE,
|
type: CONNECT.RECEIVE_WALLET_TYPE,
|
||||||
@ -298,12 +266,7 @@ export const deviceDisconnect = (device: Device): AsyncAction => async (
|
|||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (device.features) {
|
if (device.features) {
|
||||||
const instances = getState().devices.filter(
|
const instances = getState().devices.filter(
|
||||||
d =>
|
d => d.features && device.features && d.state && !d.remember && d.id === device.id
|
||||||
d.features &&
|
|
||||||
device.features &&
|
|
||||||
d.state &&
|
|
||||||
!d.remember &&
|
|
||||||
d.features.device_id === device.features.device_id
|
|
||||||
);
|
);
|
||||||
if (instances.length > 0) {
|
if (instances.length > 0) {
|
||||||
const isSelected = deviceUtils.isSelectedDevice(
|
const isSelected = deviceUtils.isSelectedDevice(
|
||||||
@ -401,8 +364,16 @@ export const duplicateDeviceOld = (device: TrezorDevice): AsyncAction => async (
|
|||||||
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (
|
export const duplicateDevice = (device: TrezorDevice): AsyncAction => async (
|
||||||
dispatch: Dispatch
|
dispatch: Dispatch
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
if (device.features && device.features.passphrase_always_on_device) {
|
||||||
|
dispatch({
|
||||||
|
type: CONNECT.RECEIVE_WALLET_TYPE,
|
||||||
|
device,
|
||||||
|
hidden: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: CONNECT.REQUEST_WALLET_TYPE,
|
type: CONNECT.REQUEST_WALLET_TYPE,
|
||||||
device,
|
device,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,7 @@ type EthereumTxRequest = {
|
|||||||
data: string,
|
data: string,
|
||||||
gasLimit: string,
|
gasLimit: string,
|
||||||
gasPrice: string,
|
gasPrice: string,
|
||||||
nonce: number,
|
nonce: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const prepareEthereumTx = (
|
export const prepareEthereumTx = (
|
||||||
@ -54,9 +54,6 @@ export const prepareEthereumTx = (
|
|||||||
nonce: toHex(tx.nonce),
|
nonce: toHex(tx.nonce),
|
||||||
gasLimit: toHex(tx.gasLimit),
|
gasLimit: toHex(tx.gasLimit),
|
||||||
gasPrice: toHex(EthereumjsUnits.convert(tx.gasPrice, 'gwei', 'wei')),
|
gasPrice: toHex(EthereumjsUnits.convert(tx.gasPrice, 'gwei', 'wei')),
|
||||||
r: '',
|
|
||||||
s: '',
|
|
||||||
v: '',
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ export const clearUnavailableDevicesData = (prevState: State, device: Device): T
|
|||||||
d =>
|
d =>
|
||||||
d.features &&
|
d.features &&
|
||||||
device.features &&
|
device.features &&
|
||||||
d.features.device_id === device.features.device_id &&
|
d.id === device.id &&
|
||||||
d.features.passphrase_protection !== device.features.passphrase_protection
|
d.features.passphrase_protection !== device.features.passphrase_protection
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -11,13 +11,11 @@ import * as ethUtils from 'utils/ethUtils';
|
|||||||
|
|
||||||
import type { Dispatch, GetState, ThunkAction, PromiseAction } from 'flowtype';
|
import type { Dispatch, GetState, ThunkAction, PromiseAction } from 'flowtype';
|
||||||
|
|
||||||
import type { EthereumAccount } from 'trezor-connect';
|
|
||||||
import type { Account } from 'reducers/AccountsReducer';
|
import type { Account } from 'reducers/AccountsReducer';
|
||||||
import type { Web3Instance } from 'reducers/Web3Reducer';
|
import type { Web3Instance } from 'reducers/Web3Reducer';
|
||||||
import type { Token } from 'reducers/TokensReducer';
|
import type { Token } from 'reducers/TokensReducer';
|
||||||
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
||||||
import * as TokenActions from './TokenActions';
|
import * as TokenActions from './TokenActions';
|
||||||
import * as AccountsActions from './AccountsActions';
|
|
||||||
|
|
||||||
export type Web3UpdateBlockAction = {
|
export type Web3UpdateBlockAction = {
|
||||||
type: typeof WEB3.BLOCK_UPDATED,
|
type: typeof WEB3.BLOCK_UPDATED,
|
||||||
@ -127,22 +125,22 @@ export const initWeb3 = (
|
|||||||
web3.currentProvider.on('error', onEnd);
|
web3.currentProvider.on('error', onEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const discoverAccount = (
|
// not used since connect@8
|
||||||
descriptor: string,
|
// export const discoverAccount = (descriptor: string, network: string): PromiseAction<any> => async (
|
||||||
network: string
|
// dispatch: Dispatch
|
||||||
): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
|
// ): Promise<any> => {
|
||||||
const instance: Web3Instance = await dispatch(initWeb3(network));
|
// const instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const balance = await instance.web3.eth.getBalance(descriptor);
|
// const balance = await instance.web3.eth.getBalance(descriptor);
|
||||||
const nonce = await instance.web3.eth.getTransactionCount(descriptor);
|
// const nonce = await instance.web3.eth.getTransactionCount(descriptor);
|
||||||
return {
|
// return {
|
||||||
descriptor,
|
// descriptor,
|
||||||
transactions: 0,
|
// transactions: 0,
|
||||||
block: 0,
|
// block: 0,
|
||||||
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
// balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
// availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
nonce,
|
// nonce,
|
||||||
};
|
// };
|
||||||
};
|
// };
|
||||||
|
|
||||||
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (
|
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
@ -151,24 +149,24 @@ export const resolvePendingTransactions = (network: string): PromiseAction<void>
|
|||||||
const instance: Web3Instance = await dispatch(initWeb3(network));
|
const instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const pending = getState().pending.filter(p => p.network === network);
|
const pending = getState().pending.filter(p => p.network === network);
|
||||||
pending.forEach(async tx => {
|
pending.forEach(async tx => {
|
||||||
const status = await instance.web3.eth.getTransaction(tx.hash);
|
const status = await instance.web3.eth.getTransaction(tx.txid);
|
||||||
if (!status) {
|
if (!status) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.TX_REJECTED,
|
type: PENDING.TX_REJECTED,
|
||||||
hash: tx.hash,
|
hash: tx.txid,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const receipt = await instance.web3.eth.getTransactionReceipt(tx.hash);
|
const receipt = await instance.web3.eth.getTransactionReceipt(tx.txid);
|
||||||
if (receipt) {
|
if (receipt) {
|
||||||
if (status.gas !== receipt.gasUsed) {
|
if (status.gas !== receipt.gasUsed) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.TX_TOKEN_ERROR,
|
type: PENDING.TX_TOKEN_ERROR,
|
||||||
hash: tx.hash,
|
hash: tx.txid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.TX_RESOLVED,
|
type: PENDING.TX_RESOLVED,
|
||||||
hash: tx.hash,
|
hash: tx.txid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,30 +203,31 @@ export const getTxInput = (): PromiseAction<void> => async (dispatch: Dispatch):
|
|||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const updateAccount = (
|
// not used since connect@8
|
||||||
account: Account,
|
// export const updateAccount = (
|
||||||
newAccount: EthereumAccount,
|
// account: Account,
|
||||||
network: string
|
// newAccount: any,
|
||||||
): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
|
// network: string
|
||||||
const instance: Web3Instance = await dispatch(initWeb3(network));
|
// ): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
|
||||||
const balance = await instance.web3.eth.getBalance(account.descriptor);
|
// const instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const nonce = await instance.web3.eth.getTransactionCount(account.descriptor);
|
// const balance = await instance.web3.eth.getBalance(account.descriptor);
|
||||||
const empty = nonce <= 0 && balance === '0';
|
// const nonce = await instance.web3.eth.getTransactionCount(account.descriptor);
|
||||||
dispatch(
|
// const empty = nonce <= 0 && balance === '0';
|
||||||
AccountsActions.update({
|
// dispatch(
|
||||||
networkType: 'ethereum',
|
// AccountsActions.update({
|
||||||
...account,
|
// networkType: 'ethereum',
|
||||||
...newAccount,
|
// ...account,
|
||||||
empty,
|
// ...newAccount,
|
||||||
nonce,
|
// empty,
|
||||||
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
// nonce,
|
||||||
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
// balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
})
|
// availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
);
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
// update tokens for this account
|
// // update tokens for this account
|
||||||
dispatch(updateAccountTokens(account));
|
// dispatch(updateAccountTokens(account));
|
||||||
};
|
// };
|
||||||
|
|
||||||
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (
|
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
|
@ -3,47 +3,14 @@
|
|||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
import * as AccountsActions from 'actions/AccountsActions';
|
||||||
|
import * as Web3Actions from 'actions/Web3Actions';
|
||||||
|
import { mergeAccount, enhanceTransaction } from 'utils/accountUtils';
|
||||||
|
|
||||||
import type { TrezorDevice, Dispatch, GetState, PromiseAction } from 'flowtype';
|
import type { Dispatch, GetState, PromiseAction, Network } from 'flowtype';
|
||||||
import type { EthereumAccount, BlockchainNotification } from 'trezor-connect';
|
import type { BlockchainNotification } from 'trezor-connect';
|
||||||
import type { Token } from 'reducers/TokensReducer';
|
import type { Token } from 'reducers/TokensReducer';
|
||||||
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
||||||
import * as Web3Actions from 'actions/Web3Actions';
|
|
||||||
import * as AccountsActions from 'actions/AccountsActions';
|
|
||||||
|
|
||||||
export const discoverAccount = (
|
|
||||||
device: TrezorDevice,
|
|
||||||
descriptor: string,
|
|
||||||
network: string
|
|
||||||
): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
|
|
||||||
// get data from connect
|
|
||||||
const txs = await TrezorConnect.ethereumGetAccountInfo({
|
|
||||||
account: {
|
|
||||||
descriptor,
|
|
||||||
block: 0,
|
|
||||||
transactions: 0,
|
|
||||||
balance: '0',
|
|
||||||
availableBalance: '0',
|
|
||||||
nonce: 0,
|
|
||||||
},
|
|
||||||
coin: network,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!txs.success) {
|
|
||||||
throw new Error(txs.payload.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockbook web3 fallback
|
|
||||||
const web3account = await dispatch(Web3Actions.discoverAccount(descriptor, network));
|
|
||||||
return {
|
|
||||||
descriptor,
|
|
||||||
transactions: txs.payload.transactions,
|
|
||||||
block: txs.payload.block,
|
|
||||||
balance: web3account.balance,
|
|
||||||
availableBalance: web3account.balance,
|
|
||||||
nonce: web3account.nonce,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTokenInfo = (input: string, network: string): PromiseAction<NetworkToken> => async (
|
export const getTokenInfo = (input: string, network: string): PromiseAction<NetworkToken> => async (
|
||||||
dispatch: Dispatch
|
dispatch: Dispatch
|
||||||
@ -103,9 +70,9 @@ export const subscribe = (network: string): PromiseAction<void> => async (
|
|||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: GetState
|
getState: GetState
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const accounts: Array<string> = getState()
|
const accounts = getState()
|
||||||
.accounts.filter(a => a.network === network)
|
.accounts.filter(a => a.network === network)
|
||||||
.map(a => a.descriptor); // eslint-disable-line no-unused-vars
|
.map(a => ({ descriptor: a.descriptor }));
|
||||||
const response = await TrezorConnect.blockchainSubscribe({
|
const response = await TrezorConnect.blockchainSubscribe({
|
||||||
accounts,
|
accounts,
|
||||||
coin: network,
|
coin: network,
|
||||||
@ -115,7 +82,7 @@ export const subscribe = (network: string): PromiseAction<void> => async (
|
|||||||
await dispatch(Web3Actions.initWeb3(network));
|
await dispatch(Web3Actions.initWeb3(network));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onBlockMined = (network: string): PromiseAction<void> => async (
|
export const onBlockMined = (network: Network): PromiseAction<void> => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: GetState
|
getState: GetState
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
@ -123,56 +90,52 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (
|
|||||||
// check latest saved transaction blockhash against blockhheight
|
// check latest saved transaction blockhash against blockhheight
|
||||||
|
|
||||||
// try to resolve pending transactions
|
// try to resolve pending transactions
|
||||||
await dispatch(Web3Actions.resolvePendingTransactions(network));
|
await dispatch(Web3Actions.resolvePendingTransactions(network.shortcut));
|
||||||
|
|
||||||
await dispatch(Web3Actions.updateGasPrice(network));
|
await dispatch(Web3Actions.updateGasPrice(network.shortcut));
|
||||||
|
|
||||||
|
const accounts = getState().accounts.filter(a => a.network === network.shortcut);
|
||||||
|
if (accounts.length === 0) return;
|
||||||
|
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||||
|
if (!blockchain) return; // flowtype fallback
|
||||||
|
|
||||||
const accounts: Array<any> = getState().accounts.filter(a => a.network === network);
|
|
||||||
if (accounts.length > 0) {
|
|
||||||
// find out which account changed
|
// find out which account changed
|
||||||
const response = await TrezorConnect.ethereumGetAccountInfo({
|
const bundle = accounts.map(a => ({ descriptor: a.descriptor, coin: network.shortcut }));
|
||||||
accounts,
|
const response = await TrezorConnect.getAccountInfo({ bundle });
|
||||||
coin: network,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
if (!response.success) return;
|
||||||
response.payload.forEach((a, i) => {
|
|
||||||
if (a.transactions > 0) {
|
|
||||||
// load additional data from Web3 (balance, nonce, tokens)
|
|
||||||
dispatch(Web3Actions.updateAccount(accounts[i], a, network));
|
|
||||||
} else {
|
|
||||||
// there are no new txs, just update block
|
|
||||||
// TODO: There still could be internal transactions as a result of contract
|
|
||||||
// If that's the case, account balance won't be updated
|
|
||||||
// Currently waiting for deprecating web3 and utilising new blockbook
|
|
||||||
dispatch(AccountsActions.update({ ...accounts[i], block: a.block }));
|
|
||||||
|
|
||||||
// HACK: since blockbook can't work with smart contracts for now
|
response.payload.forEach((info, i) => {
|
||||||
// try to update tokens balances added to this account using Web3
|
dispatch(
|
||||||
|
AccountsActions.update(mergeAccount(info, accounts[i], network, blockchain.block))
|
||||||
|
);
|
||||||
dispatch(Web3Actions.updateAccountTokens(accounts[i]));
|
dispatch(Web3Actions.updateAccountTokens(accounts[i]));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onNotification = (
|
export const onNotification = (
|
||||||
payload: $ElementType<BlockchainNotification, 'payload'>
|
payload: BlockchainNotification,
|
||||||
|
network: Network
|
||||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const { notification } = payload;
|
const { descriptor, tx } = payload.notification;
|
||||||
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
const account = getState().accounts.find(a => a.descriptor === descriptor);
|
||||||
if (!account) return;
|
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||||
|
if (!account || !blockchain) return;
|
||||||
if (!notification.blockHeight) {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.ADD,
|
type: PENDING.ADD,
|
||||||
payload: {
|
payload: enhanceTransaction(account, tx, network),
|
||||||
...notification,
|
|
||||||
deviceState: account.deviceState,
|
|
||||||
network: account.network,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
const response = await TrezorConnect.getAccountInfo({
|
||||||
|
descriptor: account.descriptor,
|
||||||
|
coin: account.network,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) return;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
AccountsActions.update(mergeAccount(response.payload, account, network, blockchain.block))
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onError = (network: string): PromiseAction<void> => async (
|
export const onError = (network: string): PromiseAction<void> => async (
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import EthereumjsUtil from 'ethereumjs-util';
|
|
||||||
import * as DISCOVERY from 'actions/constants/discovery';
|
import * as DISCOVERY from 'actions/constants/discovery';
|
||||||
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
// import { createAccountTokens } from 'actions/TokenActions';
|
||||||
|
import { enhanceAccount } from 'utils/accountUtils';
|
||||||
import type { PromiseAction, Dispatch, GetState, TrezorDevice, Network, Account } from 'flowtype';
|
import type { PromiseAction, Dispatch, GetState, TrezorDevice, Network, Account } from 'flowtype';
|
||||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
import type { Discovery } from 'reducers/DiscoveryReducer';
|
||||||
|
|
||||||
@ -13,49 +12,17 @@ export type DiscoveryStartAction = {
|
|||||||
networkType: 'ethereum',
|
networkType: 'ethereum',
|
||||||
network: Network,
|
network: Network,
|
||||||
device: TrezorDevice,
|
device: TrezorDevice,
|
||||||
publicKey: string,
|
|
||||||
chainCode: string,
|
|
||||||
basePath: Array<number>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// first iteration
|
|
||||||
// generate public key for this account
|
|
||||||
// start discovery process
|
|
||||||
export const begin = (
|
export const begin = (
|
||||||
device: TrezorDevice,
|
device: TrezorDevice,
|
||||||
network: Network
|
network: Network
|
||||||
): PromiseAction<DiscoveryStartAction> => async (): Promise<DiscoveryStartAction> => {
|
): PromiseAction<DiscoveryStartAction> => async (): Promise<DiscoveryStartAction> => ({
|
||||||
// get xpub from TREZOR
|
|
||||||
const response = await TrezorConnect.getPublicKey({
|
|
||||||
device: {
|
|
||||||
path: device.path,
|
|
||||||
instance: device.instance,
|
|
||||||
state: device.state,
|
|
||||||
},
|
|
||||||
path: network.bip44,
|
|
||||||
keepSession: true, // acquire and hold session
|
|
||||||
//useEmptyPassphrase: !device.instance,
|
|
||||||
useEmptyPassphrase: device.useEmptyPassphrase,
|
|
||||||
network: network.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle TREZOR response error
|
|
||||||
if (!response.success) {
|
|
||||||
throw new Error(response.payload.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const basePath: Array<number> = response.payload.path;
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: DISCOVERY.START,
|
type: DISCOVERY.START,
|
||||||
networkType: 'ethereum',
|
networkType: 'ethereum',
|
||||||
network,
|
network,
|
||||||
device,
|
device,
|
||||||
publicKey: response.payload.publicKey,
|
});
|
||||||
chainCode: response.payload.chainCode,
|
|
||||||
basePath,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const discoverAccount = (
|
export const discoverAccount = (
|
||||||
device: TrezorDevice,
|
device: TrezorDevice,
|
||||||
@ -65,38 +32,37 @@ export const discoverAccount = (
|
|||||||
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
||||||
if (!network) throw new Error('Discovery network not found');
|
if (!network) throw new Error('Discovery network not found');
|
||||||
|
|
||||||
const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`);
|
const { accountIndex } = discoveryProcess;
|
||||||
const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex);
|
const path = network.bip44.slice(0).replace('a', accountIndex.toString());
|
||||||
const publicAddress: string = EthereumjsUtil.publicToAddress(
|
|
||||||
derivedKey.publicKey,
|
|
||||||
true
|
|
||||||
).toString('hex');
|
|
||||||
const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress);
|
|
||||||
|
|
||||||
// TODO: check if address was created before
|
const response = await TrezorConnect.getAccountInfo({
|
||||||
const account = await dispatch(
|
device: {
|
||||||
BlockchainActions.discoverAccount(device, ethAddress, network.shortcut)
|
path: device.path,
|
||||||
);
|
instance: device.instance,
|
||||||
|
state: device.state,
|
||||||
|
},
|
||||||
|
path,
|
||||||
|
details: 'tokenBalances',
|
||||||
|
pageSize: 1,
|
||||||
|
keepSession: true, // acquire and hold session
|
||||||
|
useEmptyPassphrase: device.useEmptyPassphrase,
|
||||||
|
coin: network.shortcut,
|
||||||
|
});
|
||||||
|
|
||||||
// const accountIsEmpty = account.transactions <= 0 && account.nonce <= 0 && account.balance === '0';
|
// handle TREZOR response error
|
||||||
const empty = account.nonce <= 0 && account.balance === '0';
|
if (!response.success) {
|
||||||
|
throw new Error(response.payload.error);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
const account = enhanceAccount(response.payload, {
|
||||||
imported: false,
|
|
||||||
index: discoveryProcess.accountIndex,
|
index: discoveryProcess.accountIndex,
|
||||||
network: network.shortcut,
|
network,
|
||||||
deviceID: device.features ? device.features.device_id : '0',
|
device,
|
||||||
deviceState: device.state || '0',
|
});
|
||||||
accountPath: path,
|
|
||||||
descriptor: ethAddress,
|
|
||||||
|
|
||||||
balance: account.balance,
|
// if (response.payload.tokens) {
|
||||||
availableBalance: account.balance,
|
// dispatch(createAccountTokens(account, response.payload.tokens));
|
||||||
block: account.block,
|
// }
|
||||||
transactions: account.transactions,
|
|
||||||
empty,
|
|
||||||
|
|
||||||
networkType: 'ethereum',
|
return account;
|
||||||
nonce: account.nonce,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -486,7 +486,7 @@ export const onGasPriceChange = (gasPrice: string): ThunkAction => (
|
|||||||
): void => {
|
): void => {
|
||||||
const state: State = getState().sendFormEthereum;
|
const state: State = getState().sendFormEthereum;
|
||||||
// switch to custom fee level
|
// switch to custom fee level
|
||||||
let newSelectedFeeLevel = state.selectedFeeLevel;
|
let newSelectedFeeLevel;
|
||||||
if (state.selectedFeeLevel.value !== 'Custom')
|
if (state.selectedFeeLevel.value !== 'Custom')
|
||||||
newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
|
newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
|
||||||
|
|
||||||
@ -498,7 +498,7 @@ export const onGasPriceChange = (gasPrice: string): ThunkAction => (
|
|||||||
untouched: false,
|
untouched: false,
|
||||||
touched: { ...state.touched, gasPrice: true },
|
touched: { ...state.touched, gasPrice: true },
|
||||||
gasPrice,
|
gasPrice,
|
||||||
selectedFeeLevel: newSelectedFeeLevel,
|
selectedFeeLevel: newSelectedFeeLevel || state.selectedFeeLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -673,9 +673,12 @@ export const onSend = (): AsyncAction => async (
|
|||||||
|
|
||||||
const currentState: State = getState().sendFormEthereum;
|
const currentState: State = getState().sendFormEthereum;
|
||||||
|
|
||||||
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
const isToken = currentState.currency !== currentState.networkSymbol;
|
||||||
const pendingNonce: number = reducerUtils.getPendingSequence(pending);
|
const pendingNonce = new BigNumber(reducerUtils.getPendingSequence(pending));
|
||||||
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
|
const nonce =
|
||||||
|
pendingNonce.gt(0) && pendingNonce.gt(account.nonce)
|
||||||
|
? pendingNonce.toString()
|
||||||
|
: account.nonce;
|
||||||
|
|
||||||
const txData = await dispatch(
|
const txData = await dispatch(
|
||||||
prepareEthereumTx({
|
prepareEthereumTx({
|
||||||
@ -727,12 +730,15 @@ export const onSend = (): AsyncAction => async (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
txData.r = signedTransaction.payload.r;
|
|
||||||
txData.s = signedTransaction.payload.s;
|
|
||||||
txData.v = signedTransaction.payload.v;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const serializedTx: string = await dispatch(serializeEthereumTx(txData));
|
const serializedTx: string = await dispatch(
|
||||||
|
serializeEthereumTx({
|
||||||
|
...txData,
|
||||||
|
r: signedTransaction.payload.r,
|
||||||
|
s: signedTransaction.payload.s,
|
||||||
|
v: signedTransaction.payload.v,
|
||||||
|
})
|
||||||
|
);
|
||||||
const push = await TrezorConnect.pushTransaction({
|
const push = await TrezorConnect.pushTransaction({
|
||||||
tx: serializedTx,
|
tx: serializedTx,
|
||||||
coin: network.shortcut,
|
coin: network.shortcut,
|
||||||
@ -746,58 +752,6 @@ export const onSend = (): AsyncAction => async (
|
|||||||
|
|
||||||
dispatch({ type: SEND.TX_COMPLETE });
|
dispatch({ type: SEND.TX_COMPLETE });
|
||||||
|
|
||||||
// ugly blockbook workaround:
|
|
||||||
// since blockbook can't emit pending notifications
|
|
||||||
// need to trigger this event from here, where we know everything about this transaction
|
|
||||||
// blockchainNotification is 'trezor-connect' BlockchainLinkTransaction type
|
|
||||||
const fee = ValidationActions.calculateFee(currentState.gasLimit, currentState.gasPrice);
|
|
||||||
const blockchainNotification = {
|
|
||||||
type: 'send',
|
|
||||||
descriptor: account.descriptor,
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
addresses: [account.descriptor],
|
|
||||||
amount: currentState.amount,
|
|
||||||
fee,
|
|
||||||
total: currentState.total,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
addresses: [currentState.address],
|
|
||||||
amount: currentState.amount,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
hash: txid,
|
|
||||||
amount: currentState.amount,
|
|
||||||
fee,
|
|
||||||
total: currentState.total,
|
|
||||||
|
|
||||||
sequence: nonce,
|
|
||||||
tokens: isToken
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: currentState.currency,
|
|
||||||
shortcut: currentState.currency,
|
|
||||||
value: currentState.amount,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: undefined,
|
|
||||||
|
|
||||||
blockHeight: 0,
|
|
||||||
blockHash: undefined,
|
|
||||||
timestamp: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
BlockchainActions.onNotification({
|
|
||||||
// $FlowIssue: missing coinInfo declaration
|
|
||||||
coin: {},
|
|
||||||
notification: blockchainNotification,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// workaround end
|
|
||||||
|
|
||||||
// clear session storage
|
// clear session storage
|
||||||
dispatch(SessionStorageActions.clear());
|
dispatch(SessionStorageActions.clear());
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import TrezorConnect from 'trezor-connect';
|
|||||||
import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
import * as AccountsActions from 'actions/AccountsActions';
|
import * as AccountsActions from 'actions/AccountsActions';
|
||||||
import { toDecimalAmount } from 'utils/formatUtils';
|
import { mergeAccount, enhanceTransaction } from 'utils/accountUtils';
|
||||||
import { observeChanges } from 'reducers/utils';
|
import { observeChanges } from 'reducers/utils';
|
||||||
|
|
||||||
import type { BlockchainNotification } from 'trezor-connect';
|
import type { BlockchainNotification } from 'trezor-connect';
|
||||||
@ -20,10 +20,10 @@ import type {
|
|||||||
export const subscribe = (network: string): PromiseAction<void> => async (
|
export const subscribe = (network: string): PromiseAction<void> => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: GetState
|
getState: GetState
|
||||||
): Promise<void> => {
|
) => {
|
||||||
const accounts: Array<string> = getState()
|
const accounts = getState()
|
||||||
.accounts.filter(a => a.network === network)
|
.accounts.filter(a => a.network === network)
|
||||||
.map(a => a.descriptor);
|
.map(a => ({ descriptor: a.descriptor }));
|
||||||
await TrezorConnect.blockchainSubscribe({
|
await TrezorConnect.blockchainSubscribe({
|
||||||
accounts,
|
accounts,
|
||||||
coin: network,
|
coin: network,
|
||||||
@ -47,161 +47,81 @@ export const getFeeLevels = (network: Network): PayloadAction<Array<BlockchainFe
|
|||||||
return blockchain.feeLevels;
|
return blockchain.feeLevels;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onBlockMined = (networkShortcut: string, block: number): PromiseAction<void> => async (
|
export const onBlockMined = (network: Network): PromiseAction<void> => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: GetState
|
getState: GetState
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const blockchain = getState().blockchain.find(b => b.shortcut === networkShortcut);
|
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||||
if (!blockchain) return; // flowtype fallback
|
if (!blockchain) return; // flowtype fallback
|
||||||
|
|
||||||
// if last update was more than 5 minutes ago
|
// if last update was more than 5 minutes ago
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
if (blockchain.feeTimestamp < now - 300000) {
|
if (blockchain.feeTimestamp < now - 300000) {
|
||||||
const feeRequest = await TrezorConnect.blockchainEstimateFee({
|
const feeRequest = await TrezorConnect.blockchainEstimateFee({
|
||||||
coin: networkShortcut,
|
request: {
|
||||||
|
feeLevels: 'smart',
|
||||||
|
},
|
||||||
|
coin: network.shortcut,
|
||||||
});
|
});
|
||||||
if (feeRequest.success && observeChanges(blockchain.feeLevels, feeRequest.payload)) {
|
if (feeRequest.success && observeChanges(blockchain.feeLevels, feeRequest.payload)) {
|
||||||
// check if downloaded fee levels are different
|
// check if downloaded fee levels are different
|
||||||
dispatch({
|
dispatch({
|
||||||
type: BLOCKCHAIN.UPDATE_FEE,
|
type: BLOCKCHAIN.UPDATE_FEE,
|
||||||
shortcut: networkShortcut,
|
shortcut: network.shortcut,
|
||||||
feeLevels: feeRequest.payload,
|
feeLevels: feeRequest.payload.levels.map(l => ({
|
||||||
|
name: 'Normal',
|
||||||
|
value: l.feePerUnit,
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check for blockchain rollbacks here!
|
// TODO: check for blockchain rollbacks here!
|
||||||
|
|
||||||
const accounts: Array<any> = getState().accounts.filter(a => a.network === networkShortcut);
|
const accounts = getState().accounts.filter(a => a.network === network.shortcut);
|
||||||
if (accounts.length === 0) return;
|
if (accounts.length === 0) return;
|
||||||
const { networks } = getState().localStorage.config;
|
|
||||||
const network = networks.find(c => c.shortcut === networkShortcut);
|
|
||||||
if (!network) return;
|
|
||||||
|
|
||||||
// HACK: Since Connect always returns account.transactions as 0
|
const bundle = accounts.map(a => ({ descriptor: a.descriptor, coin: network.shortcut }));
|
||||||
// we don't have info about new transactions for the account since last update.
|
const response = await TrezorConnect.getAccountInfo({ bundle });
|
||||||
// Untill there is a better solution compare accounts block.
|
|
||||||
// If we missed some blocks (wallet was offline) we'll update the account
|
|
||||||
// If we are update to date with the last block that means wallet was online
|
|
||||||
// and we would get Blockchain notification about new transaction if needed
|
|
||||||
accounts.forEach(async account => {
|
|
||||||
const missingBlocks = account.block !== block - 1;
|
|
||||||
if (!missingBlocks) {
|
|
||||||
// account was last updated on account.block, current block is +1, we didn't miss single block
|
|
||||||
// if there was new tx, blockchain notification would let us know
|
|
||||||
// so just update the block for the account
|
|
||||||
dispatch(
|
|
||||||
AccountsActions.update({
|
|
||||||
...account,
|
|
||||||
block,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// we missed some blocks (wallet was offline). get updated account info from connect
|
|
||||||
const response = await TrezorConnect.rippleGetAccountInfo({
|
|
||||||
account: {
|
|
||||||
descriptor: account.descriptor,
|
|
||||||
},
|
|
||||||
level: 'transactions',
|
|
||||||
coin: networkShortcut,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.success) return;
|
if (!response.success) return;
|
||||||
|
|
||||||
const updatedAccount = response.payload;
|
response.payload.forEach((info, i) => {
|
||||||
|
|
||||||
// new txs
|
|
||||||
dispatch(
|
dispatch(
|
||||||
AccountsActions.update({
|
AccountsActions.update(mergeAccount(info, accounts[i], network, blockchain.block))
|
||||||
...account,
|
|
||||||
balance: toDecimalAmount(updatedAccount.balance, network.decimals),
|
|
||||||
availableBalance: toDecimalAmount(
|
|
||||||
updatedAccount.availableBalance,
|
|
||||||
network.decimals
|
|
||||||
),
|
|
||||||
block: updatedAccount.block,
|
|
||||||
sequence: updatedAccount.sequence,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onNotification = (
|
export const onNotification = (
|
||||||
payload: $ElementType<BlockchainNotification, 'payload'>
|
payload: BlockchainNotification,
|
||||||
|
network: Network
|
||||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const { notification } = payload;
|
const { descriptor, tx } = payload.notification;
|
||||||
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
const account = getState().accounts.find(a => a.descriptor === descriptor);
|
||||||
if (!account) return;
|
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||||
const { network } = getState().selectedAccount;
|
if (!account || !blockchain) return;
|
||||||
if (!network) return; // flowtype fallback
|
|
||||||
|
|
||||||
if (!notification.blockHeight) {
|
if (!tx.blockHeight) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.ADD,
|
type: PENDING.ADD,
|
||||||
payload: {
|
payload: enhanceTransaction(account, tx, network),
|
||||||
...notification,
|
|
||||||
deviceState: account.deviceState,
|
|
||||||
network: account.network,
|
|
||||||
|
|
||||||
amount: toDecimalAmount(notification.amount, network.decimals),
|
|
||||||
total:
|
|
||||||
notification.type === 'send'
|
|
||||||
? toDecimalAmount(notification.total, network.decimals)
|
|
||||||
: toDecimalAmount(notification.amount, network.decimals),
|
|
||||||
fee: toDecimalAmount(notification.fee, network.decimals),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// todo: replace "send success" notification with link to explorer
|
|
||||||
} else {
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.TX_RESOLVED,
|
type: PENDING.TX_RESOLVED,
|
||||||
hash: notification.hash,
|
hash: tx.txid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of tx sent between two Trezor accounts there is a possibility that only 1 notification will be received
|
const response = await TrezorConnect.getAccountInfo({
|
||||||
// therefore we need to find target account and update data for it as well
|
descriptor: account.descriptor,
|
||||||
const accountsToUpdate = [account];
|
coin: account.network,
|
||||||
const targetAddress =
|
|
||||||
notification.type === 'send'
|
|
||||||
? notification.outputs[0].addresses[0]
|
|
||||||
: notification.inputs[0].addresses[0];
|
|
||||||
|
|
||||||
const targetAccount = getState().accounts.find(a => a.descriptor === targetAddress);
|
|
||||||
if (targetAccount) {
|
|
||||||
accountsToUpdate.push(targetAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
accountsToUpdate.forEach(async a => {
|
|
||||||
const response = await TrezorConnect.rippleGetAccountInfo({
|
|
||||||
account: {
|
|
||||||
descriptor: a.descriptor,
|
|
||||||
from: a.block,
|
|
||||||
history: false,
|
|
||||||
},
|
|
||||||
coin: a.network,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.success) {
|
if (!response.success) return;
|
||||||
const updatedAccount = response.payload;
|
|
||||||
const empty = updatedAccount.sequence <= 0 && updatedAccount.balance === '0';
|
|
||||||
dispatch(
|
dispatch(
|
||||||
AccountsActions.update({
|
AccountsActions.update(mergeAccount(response.payload, account, network, blockchain.block))
|
||||||
networkType: 'ripple',
|
|
||||||
...a,
|
|
||||||
balance: toDecimalAmount(updatedAccount.balance, network.decimals),
|
|
||||||
availableBalance: toDecimalAmount(
|
|
||||||
updatedAccount.availableBalance,
|
|
||||||
network.decimals
|
|
||||||
),
|
|
||||||
block: updatedAccount.block,
|
|
||||||
sequence: updatedAccount.sequence,
|
|
||||||
reserve: toDecimalAmount(updatedAccount.reserve, network.decimals),
|
|
||||||
empty,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import * as DISCOVERY from 'actions/constants/discovery';
|
import * as DISCOVERY from 'actions/constants/discovery';
|
||||||
import { toDecimalAmount } from 'utils/formatUtils';
|
import { enhanceAccount } from 'utils/accountUtils';
|
||||||
|
|
||||||
import type { PromiseAction, GetState, Dispatch, TrezorDevice, Network, Account } from 'flowtype';
|
import type { PromiseAction, GetState, Dispatch, TrezorDevice, Network, Account } from 'flowtype';
|
||||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
import type { Discovery } from 'reducers/DiscoveryReducer';
|
||||||
@ -35,16 +35,13 @@ export const discoverAccount = (
|
|||||||
const { accountIndex } = discoveryProcess;
|
const { accountIndex } = discoveryProcess;
|
||||||
const path = network.bip44.slice(0).replace('a', accountIndex.toString());
|
const path = network.bip44.slice(0).replace('a', accountIndex.toString());
|
||||||
|
|
||||||
const response = await TrezorConnect.rippleGetAccountInfo({
|
const response = await TrezorConnect.getAccountInfo({
|
||||||
device: {
|
device: {
|
||||||
path: device.path,
|
path: device.path,
|
||||||
instance: device.instance,
|
instance: device.instance,
|
||||||
state: device.state,
|
state: device.state,
|
||||||
},
|
},
|
||||||
account: {
|
|
||||||
path,
|
path,
|
||||||
block: 0,
|
|
||||||
},
|
|
||||||
keepSession: true, // acquire and hold session
|
keepSession: true, // acquire and hold session
|
||||||
useEmptyPassphrase: device.useEmptyPassphrase,
|
useEmptyPassphrase: device.useEmptyPassphrase,
|
||||||
coin: network.shortcut,
|
coin: network.shortcut,
|
||||||
@ -55,26 +52,9 @@ export const discoverAccount = (
|
|||||||
throw new Error(response.payload.error);
|
throw new Error(response.payload.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = response.payload;
|
return enhanceAccount(response.payload, {
|
||||||
const empty = account.sequence <= 0 && account.balance === '0';
|
|
||||||
|
|
||||||
return {
|
|
||||||
imported: false,
|
|
||||||
index: discoveryProcess.accountIndex,
|
index: discoveryProcess.accountIndex,
|
||||||
network: network.shortcut,
|
network,
|
||||||
deviceID: device.features ? device.features.device_id : '0',
|
device,
|
||||||
deviceState: device.state || '0',
|
});
|
||||||
accountPath: account.path || [],
|
|
||||||
descriptor: account.descriptor,
|
|
||||||
|
|
||||||
balance: toDecimalAmount(account.balance, network.decimals),
|
|
||||||
availableBalance: toDecimalAmount(account.availableBalance, network.decimals),
|
|
||||||
block: account.block,
|
|
||||||
transactions: account.transactions,
|
|
||||||
empty,
|
|
||||||
|
|
||||||
networkType: 'ripple',
|
|
||||||
sequence: account.sequence,
|
|
||||||
reserve: toDecimalAmount(account.reserve, network.decimals),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { Link } from 'trezor-ui-components';
|
||||||
import TrezorConnect from 'trezor-connect';
|
import TrezorConnect from 'trezor-connect';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import * as SEND from 'actions/constants/send';
|
import * as SEND from 'actions/constants/send';
|
||||||
@ -374,7 +375,7 @@ export const onFeeChange = (fee: string): ThunkAction => (
|
|||||||
const state: State = getState().sendFormRipple;
|
const state: State = getState().sendFormRipple;
|
||||||
|
|
||||||
// switch to custom fee level
|
// switch to custom fee level
|
||||||
let newSelectedFeeLevel = state.selectedFeeLevel;
|
let newSelectedFeeLevel;
|
||||||
if (state.selectedFeeLevel.value !== 'Custom')
|
if (state.selectedFeeLevel.value !== 'Custom')
|
||||||
newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
|
newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
|
||||||
|
|
||||||
@ -385,7 +386,7 @@ export const onFeeChange = (fee: string): ThunkAction => (
|
|||||||
...state,
|
...state,
|
||||||
untouched: false,
|
untouched: false,
|
||||||
touched: { ...state.touched, fee: true },
|
touched: { ...state.touched, fee: true },
|
||||||
selectedFeeLevel: newSelectedFeeLevel,
|
selectedFeeLevel: newSelectedFeeLevel || state.selectedFeeLevel,
|
||||||
fee,
|
fee,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -501,7 +502,11 @@ export const onSend = (): AsyncAction => async (
|
|||||||
payload: {
|
payload: {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
title: <FormattedMessage {...l10nMessages.TR_TRANSACTION_SUCCESS} />,
|
title: <FormattedMessage {...l10nMessages.TR_TRANSACTION_SUCCESS} />,
|
||||||
message: txid,
|
message: (
|
||||||
|
<Link href={`${network.explorer.tx}${txid}`} isGreen>
|
||||||
|
<FormattedMessage {...l10nMessages.TR_SEE_TRANSACTION_DETAILS} />
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
actions: [],
|
actions: [],
|
||||||
},
|
},
|
||||||
|
@ -173,16 +173,14 @@ const addressBalanceValidation = ($state: State): PromiseAction<void> => async (
|
|||||||
if (!network) return;
|
if (!network) return;
|
||||||
|
|
||||||
let minAmount: string = '0';
|
let minAmount: string = '0';
|
||||||
const response = await TrezorConnect.rippleGetAccountInfo({
|
const response = await TrezorConnect.getAccountInfo({
|
||||||
account: {
|
|
||||||
descriptor: $state.address,
|
descriptor: $state.address,
|
||||||
},
|
|
||||||
coin: network.shortcut,
|
coin: network.shortcut,
|
||||||
});
|
});
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
const empty = response.payload.sequence <= 0 && response.payload.balance === '0';
|
if (response.payload.empty) {
|
||||||
if (empty) {
|
const reserve = response.payload.misc ? response.payload.misc.reserve : '0';
|
||||||
minAmount = toDecimalAmount(response.payload.reserve, network.decimals);
|
minAmount = toDecimalAmount(reserve || '0', network.decimals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { Header } from 'trezor-ui-components';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import type { toggleSidebar as toggleSidebarType } from 'actions/WalletActions';
|
import { toggleSidebar as toggleSidebarAction } from 'actions/WalletActions';
|
||||||
import l10nMessages from './index.messages';
|
import l10nMessages from './index.messages';
|
||||||
|
|
||||||
import LanguagePicker from './components/LanguagePicker/Container';
|
import LanguagePicker from './components/LanguagePicker/Container';
|
||||||
@ -13,7 +13,7 @@ import LanguagePicker from './components/LanguagePicker/Container';
|
|||||||
type MyProps = {
|
type MyProps = {
|
||||||
sidebarEnabled?: boolean,
|
sidebarEnabled?: boolean,
|
||||||
sidebarOpened?: ?boolean,
|
sidebarOpened?: ?boolean,
|
||||||
toggleSidebar?: toggleSidebarType,
|
toggleSidebar?: typeof toggleSidebarAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MyHeader = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: MyProps) => (
|
const MyHeader = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: MyProps) => (
|
||||||
|
@ -63,7 +63,7 @@ const Value = styled.div`
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
color: ${colors.GREEN_SECONDARY};
|
color: ${colors.GREEN_SECONDARY};
|
||||||
|
|
||||||
&.send {
|
&.sent {
|
||||||
color: ${colors.ERROR_PRIMARY};
|
color: ${colors.ERROR_PRIMARY};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -77,28 +77,27 @@ const Fee = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TransactionItem = ({ tx, network }: Props) => {
|
const TransactionItem = ({ tx, network }: Props) => {
|
||||||
const url = `${network.explorer.tx}${tx.hash}`;
|
const url = `${network.explorer.tx}${tx.txid}`;
|
||||||
const date = typeof tx.timestamp === 'string' ? tx.timestamp : undefined; // TODO: format date
|
const date = typeof tx.blockTime === 'number' ? tx.blockTime : undefined; // TODO: format date
|
||||||
const addresses = (tx.type === 'send' ? tx.outputs : tx.inputs).reduce(
|
const addresses = tx.targets.reduce((arr, item) => arr.concat(item.addresses), []);
|
||||||
(arr, item) => arr.concat(item.addresses),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const operation = tx.type === 'send' ? '-' : '+';
|
const operation = tx.type === 'sent' ? '-' : '+';
|
||||||
const amount = tx.tokens ? (
|
const amount =
|
||||||
|
tx.tokens.length > 0 ? (
|
||||||
tx.tokens.map(t => (
|
tx.tokens.map(t => (
|
||||||
<Amount key={t.value}>
|
<Amount key={t.symbol}>
|
||||||
{operation}
|
{operation}
|
||||||
{t.value} {t.shortcut}
|
{t.amount} {t.symbol}
|
||||||
</Amount>
|
</Amount>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Amount>
|
<Amount>
|
||||||
{operation}
|
{operation}
|
||||||
{tx.total} {network.symbol}
|
{tx.amount} {network.symbol}
|
||||||
</Amount>
|
</Amount>
|
||||||
);
|
);
|
||||||
const fee = tx.tokens && tx.type === 'send' ? `${tx.fee} ${network.symbol}` : undefined;
|
const fee =
|
||||||
|
tx.tokens.length > 0 && tx.type === 'sent' ? `${tx.fee} ${network.symbol}` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
@ -113,7 +112,7 @@ const TransactionItem = ({ tx, network }: Props) => {
|
|||||||
))}
|
))}
|
||||||
{!tx.blockHeight && (
|
{!tx.blockHeight && (
|
||||||
<TransactionHash href={url} isGray>
|
<TransactionHash href={url} isGray>
|
||||||
Transaction hash: {tx.hash}
|
Transaction hash: {tx.txid}
|
||||||
</TransactionHash>
|
</TransactionHash>
|
||||||
)}
|
)}
|
||||||
</Addresses>
|
</Addresses>
|
||||||
|
@ -30,20 +30,10 @@ const DeviceIcon = ({
|
|||||||
color = COLORS.TEXT_SECONDARY,
|
color = COLORS.TEXT_SECONDARY,
|
||||||
hoverColor,
|
hoverColor,
|
||||||
onClick,
|
onClick,
|
||||||
...rest
|
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const majorVersion = device.features ? device.features.major_version : 2;
|
const majorVersion = device.features ? device.features.major_version : 2;
|
||||||
const icon = getDeviceIcon(majorVersion);
|
const icon = getDeviceIcon(majorVersion);
|
||||||
return (
|
return <Icon icon={icon} hoverColor={hoverColor} onClick={onClick} color={color} size={size} />;
|
||||||
<Icon
|
|
||||||
icon={icon}
|
|
||||||
hoverColor={hoverColor}
|
|
||||||
onClick={onClick}
|
|
||||||
color={color}
|
|
||||||
size={size}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DeviceIcon.propTypes = {
|
DeviceIcon.propTypes = {
|
||||||
|
@ -16,6 +16,7 @@ import Pin from 'components/modals/pin/Pin';
|
|||||||
import InvalidPin from 'components/modals/pin/Invalid';
|
import InvalidPin from 'components/modals/pin/Invalid';
|
||||||
import Passphrase from 'components/modals/passphrase/Passphrase';
|
import Passphrase from 'components/modals/passphrase/Passphrase';
|
||||||
import PassphraseType from 'components/modals/passphrase/Type';
|
import PassphraseType from 'components/modals/passphrase/Type';
|
||||||
|
import PassphraseOnDevice from 'components/modals/passphrase/OnDevice';
|
||||||
import ConfirmSignTx from 'components/modals/confirm/SignTx';
|
import ConfirmSignTx from 'components/modals/confirm/SignTx';
|
||||||
import ConfirmAction from 'components/modals/confirm/Action';
|
import ConfirmAction from 'components/modals/confirm/Action';
|
||||||
import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddress';
|
import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddress';
|
||||||
@ -97,6 +98,10 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case UI.REQUEST_PASSPHRASE_ON_DEVICE:
|
||||||
|
case 'ButtonRequest_PassphraseEntry':
|
||||||
|
return <PassphraseOnDevice device={modal.device} />;
|
||||||
|
|
||||||
case 'ButtonRequest_ProtectCall':
|
case 'ButtonRequest_ProtectCall':
|
||||||
return <ConfirmAction device={modal.device} />;
|
return <ConfirmAction device={modal.device} />;
|
||||||
|
|
||||||
|
43
src/components/modals/passphrase/OnDevice/index.js
Normal file
43
src/components/modals/passphrase/OnDevice/index.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { H5, P, colors } from 'trezor-ui-components';
|
||||||
|
import DeviceIcon from 'components/images/DeviceIcon';
|
||||||
|
|
||||||
|
import type { TrezorDevice } from 'flowtype';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
device: TrezorDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
max-width: 360px;
|
||||||
|
padding: 30px 48px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledDeviceIcon = styled(DeviceIcon)`
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div``;
|
||||||
|
|
||||||
|
const PassphraseType = (props: Props) => (
|
||||||
|
<Wrapper>
|
||||||
|
<Header>
|
||||||
|
<StyledDeviceIcon device={props.device} size={32} color={colors.TEXT_SECONDARY} />
|
||||||
|
<H5>Enter passphrase on {props.device.label} device</H5>
|
||||||
|
<P size="small">
|
||||||
|
If you enter a wrong passphrase, you will not unlock the desired hidden wallet.
|
||||||
|
</P>
|
||||||
|
</Header>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
PassphraseType.propTypes = {
|
||||||
|
device: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PassphraseType;
|
@ -73,6 +73,10 @@ const LinkButton = styled(Button)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const OnDeviceButton = styled(Button)`
|
||||||
|
margin: 10px 0 10px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
class Passphrase extends PureComponent<Props, State> {
|
class Passphrase extends PureComponent<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -169,7 +173,10 @@ class Passphrase extends PureComponent<Props, State> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
submitPassphrase(shouldLeavePassphraseBlank: boolean = false) {
|
submitPassphrase(
|
||||||
|
shouldLeavePassphraseBlank: boolean = false,
|
||||||
|
passphraseOnDevice: boolean = false
|
||||||
|
) {
|
||||||
const { onPassphraseSubmit } = this.props;
|
const { onPassphraseSubmit } = this.props;
|
||||||
const passphrase = this.state.passphraseInputValue;
|
const passphrase = this.state.passphraseInputValue;
|
||||||
|
|
||||||
@ -181,7 +188,7 @@ class Passphrase extends PureComponent<Props, State> {
|
|||||||
isPassphraseHidden: true,
|
isPassphraseHidden: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
onPassphraseSubmit(shouldLeavePassphraseBlank ? '' : passphrase);
|
onPassphraseSubmit(shouldLeavePassphraseBlank ? '' : passphrase, passphraseOnDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyPress(event: KeyboardEvent) {
|
handleKeyPress(event: KeyboardEvent) {
|
||||||
@ -209,6 +216,13 @@ class Passphrase extends PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { device } = this.props;
|
||||||
|
const onDeviceOffer = !!(
|
||||||
|
device.features &&
|
||||||
|
device.features.capabilities &&
|
||||||
|
device.features.capabilities.includes('Capability_PassphraseEntry')
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<H5>
|
<H5>
|
||||||
@ -270,6 +284,11 @@ class Passphrase extends PureComponent<Props, State> {
|
|||||||
<Button isDisabled={!!error} onClick={() => this.submitPassphrase()}>
|
<Button isDisabled={!!error} onClick={() => this.submitPassphrase()}>
|
||||||
<FormattedMessage {...l10nMessages.TR_ENTER} />
|
<FormattedMessage {...l10nMessages.TR_ENTER} />
|
||||||
</Button>
|
</Button>
|
||||||
|
{onDeviceOffer && (
|
||||||
|
<OnDeviceButton isWhite onClick={() => this.submitPassphrase(false, true)}>
|
||||||
|
Enter passphrase on device
|
||||||
|
</OnDeviceButton>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
<Footer>
|
<Footer>
|
||||||
<P size="small">
|
<P size="small">
|
||||||
|
@ -36,33 +36,19 @@ import type { ImportAccountAction } from 'actions/ImportAccountActions';
|
|||||||
import type { FiatRateAction } from 'services/CoingeckoService'; // this service has no action file, all is written inside one file
|
import type { FiatRateAction } from 'services/CoingeckoService'; // this service has no action file, all is written inside one file
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Device,
|
KnownDevice,
|
||||||
Features,
|
UnknownDevice,
|
||||||
DeviceStatus,
|
TransportEvent,
|
||||||
FirmwareRelease,
|
|
||||||
DeviceFirmwareStatus,
|
|
||||||
DeviceMode,
|
|
||||||
DeviceMessageType,
|
|
||||||
TransportMessageType,
|
|
||||||
UiMessageType,
|
|
||||||
BlockchainEvent,
|
BlockchainEvent,
|
||||||
BlockchainLinkTransaction,
|
UiEvent,
|
||||||
|
DeviceEvent,
|
||||||
|
AccountTransaction,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
|
|
||||||
import type { RouterAction, LocationState } from 'connected-react-router';
|
import type { RouterAction, LocationState } from 'connected-react-router';
|
||||||
|
|
||||||
export type AcquiredDevice = $Exact<{
|
export type ExtendedDevice = {|
|
||||||
+type: 'acquired',
|
|
||||||
path: string,
|
|
||||||
+label: string,
|
|
||||||
+features: Features,
|
|
||||||
+firmware: DeviceFirmwareStatus,
|
|
||||||
+firmwareRelease: ?FirmwareRelease,
|
|
||||||
status: DeviceStatus,
|
|
||||||
+mode: DeviceMode,
|
|
||||||
state: ?string,
|
|
||||||
useEmptyPassphrase: boolean,
|
useEmptyPassphrase: boolean,
|
||||||
|
|
||||||
remember: boolean, // device should be remembered
|
remember: boolean, // device should be remembered
|
||||||
connected: boolean, // device is connected
|
connected: boolean, // device is connected
|
||||||
available: boolean, // device cannot be used because of features.passphrase_protection is different then expected
|
available: boolean, // device cannot be used because of features.passphrase_protection is different then expected
|
||||||
@ -70,29 +56,16 @@ export type AcquiredDevice = $Exact<{
|
|||||||
instanceLabel: string,
|
instanceLabel: string,
|
||||||
instanceName: ?string,
|
instanceName: ?string,
|
||||||
ts: number,
|
ts: number,
|
||||||
}>;
|
|};
|
||||||
|
|
||||||
export type UnknownDevice = $Exact<{
|
export type AcquiredDevice = {| ...KnownDevice, ...ExtendedDevice |};
|
||||||
+type: 'unacquired' | 'unreadable',
|
export type UnacquiredDevice = {| ...UnknownDevice, ...ExtendedDevice |};
|
||||||
path: string,
|
|
||||||
+label: string,
|
|
||||||
+features: null,
|
|
||||||
state: ?string,
|
|
||||||
useEmptyPassphrase: boolean,
|
|
||||||
|
|
||||||
remember: boolean, // device should be remembered
|
|
||||||
connected: boolean, // device is connected
|
|
||||||
available: boolean, // device cannot be used because of features.passphrase_protection is different then expected
|
|
||||||
instance?: number,
|
|
||||||
instanceLabel: string,
|
|
||||||
instanceName: ?string,
|
|
||||||
ts: number,
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type { Device } from 'trezor-connect';
|
export type { Device } from 'trezor-connect';
|
||||||
export type TrezorDevice = AcquiredDevice | UnknownDevice;
|
export type TrezorDevice = AcquiredDevice | UnacquiredDevice;
|
||||||
|
|
||||||
export type Transaction = BlockchainLinkTransaction & {
|
export type Transaction = AccountTransaction & {
|
||||||
|
descriptor: string,
|
||||||
deviceState: string,
|
deviceState: string,
|
||||||
network: string,
|
network: string,
|
||||||
rejected?: boolean,
|
rejected?: boolean,
|
||||||
@ -100,38 +73,11 @@ export type Transaction = BlockchainLinkTransaction & {
|
|||||||
|
|
||||||
export type RouterLocationState = LocationState;
|
export type RouterLocationState = LocationState;
|
||||||
|
|
||||||
// Cast event from TrezorConnect event listener to react Action
|
|
||||||
type DeviceEventAction = {
|
|
||||||
type: DeviceMessageType,
|
|
||||||
device: Device,
|
|
||||||
};
|
|
||||||
|
|
||||||
type TransportEventAction = {
|
|
||||||
type: TransportMessageType,
|
|
||||||
payload: any,
|
|
||||||
};
|
|
||||||
|
|
||||||
type UiEventAction = {
|
|
||||||
type: UiMessageType,
|
|
||||||
payload: any,
|
|
||||||
// payload: {
|
|
||||||
// device: Device;
|
|
||||||
// code?: string;
|
|
||||||
// },
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: join this message with uiMessage
|
|
||||||
type IFrameHandshake = {
|
|
||||||
type: 'iframe_handshake',
|
|
||||||
payload: any,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| RouterAction
|
| RouterAction
|
||||||
| IFrameHandshake
|
| TransportEvent
|
||||||
| TransportEventAction
|
| DeviceEvent
|
||||||
| DeviceEventAction
|
| UiEvent
|
||||||
| UiEventAction
|
|
||||||
| BlockchainEvent
|
| BlockchainEvent
|
||||||
| SelectedAccountAction
|
| SelectedAccountAction
|
||||||
| AccountAction
|
| AccountAction
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
declare module 'bignumber.js' {
|
|
||||||
declare type $npm$big$number$object = number | string | T_BigNumber
|
|
||||||
declare type $npm$cmp$result = -1 | 0 | 1
|
|
||||||
declare type DIGIT = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
||||||
declare type ROUND_DOWN = 0
|
|
||||||
declare type ROUND_HALF_UP = 1
|
|
||||||
declare type ROUND_HALF_EVEN = 2
|
|
||||||
declare type ROUND_UP = 3
|
|
||||||
declare type RM = ROUND_DOWN | ROUND_HALF_UP | ROUND_HALF_EVEN | ROUND_UP
|
|
||||||
|
|
||||||
declare class T_BigNumber {
|
|
||||||
// Properties
|
|
||||||
static DP: number;
|
|
||||||
static RM: RM;
|
|
||||||
static E_NEG: number;
|
|
||||||
static E_POS: number;
|
|
||||||
|
|
||||||
c: Array<DIGIT>;
|
|
||||||
e: number;
|
|
||||||
s: -1 | 1;
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
static (value: $npm$big$number$object): T_BigNumber;
|
|
||||||
constructor(value: $npm$big$number$object): T_BigNumber;
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
abs(): T_BigNumber;
|
|
||||||
div(n: $npm$big$number$object): T_BigNumber;
|
|
||||||
dividedBy(n: $npm$big$number$object): T_BigNumber;
|
|
||||||
eq(n: $npm$big$number$object): boolean;
|
|
||||||
gt(n: $npm$big$number$object): boolean;
|
|
||||||
isGreaterThan(n: $npm$big$number$object): boolean;
|
|
||||||
gte(n: $npm$big$number$object): boolean;
|
|
||||||
lt(n: $npm$big$number$object): boolean;
|
|
||||||
isLessThan(n: $npm$big$number$object): boolean;
|
|
||||||
lte(n: $npm$big$number$object): boolean;
|
|
||||||
isLessThanOrEqualTo(n: $npm$big$number$object): boolean;
|
|
||||||
isNaN(): boolean;
|
|
||||||
minus(n: $npm$big$number$object): T_BigNumber;
|
|
||||||
mod(n: $npm$big$number$object): T_BigNumber;
|
|
||||||
plus(n: $npm$big$number$object): T_BigNumber;
|
|
||||||
pow(exp: number): BigNumber;
|
|
||||||
sqrt(): T_BigNumber;
|
|
||||||
times(n: $npm$big$number$object): T_BigNumber;
|
|
||||||
toExponential(dp: ?number): string;
|
|
||||||
toFixed(dp: ?number): string;
|
|
||||||
toPrecision(sd: ?number): string;
|
|
||||||
toString(format?: number): string;
|
|
||||||
toNumber(): number;
|
|
||||||
valueOf(): string;
|
|
||||||
toJSON(): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
//declare module.exports: typeof T_BigNumber
|
|
||||||
declare export default typeof T_BigNumber;
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
declare module 'redux' {
|
declare module 'redux' {
|
||||||
/*
|
/*
|
||||||
S = State
|
S = State
|
||||||
@ -10,7 +8,7 @@ declare module 'redux' {
|
|||||||
|
|
||||||
declare export type DispatchAPI<A> = (action: A) => A;
|
declare export type DispatchAPI<A> = (action: A) => A;
|
||||||
// old Dispatch needs to stay as it is, because also "react-redux" is using this type
|
// old Dispatch needs to stay as it is, because also "react-redux" is using this type
|
||||||
declare export type Dispatch<A: { type: $Subtype<string> }> = DispatchAPI<A>;
|
declare export type Dispatch<A: { type: * }> = DispatchAPI<A>;
|
||||||
|
|
||||||
declare export type ThunkAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => void;
|
declare export type ThunkAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => void;
|
||||||
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
||||||
@ -21,7 +19,7 @@ declare module 'redux' {
|
|||||||
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
||||||
declare export type PromiseDispatch<S, A> = <R>(action: PromiseAction<S, A, R>) => Promise<R>;
|
declare export type PromiseDispatch<S, A> = <R>(action: PromiseAction<S, A, R>) => Promise<R>;
|
||||||
declare export type PayloadDispatch<S, A> = <R>(action: PayloadAction<S, A, R>) => R;
|
declare export type PayloadDispatch<S, A> = <R>(action: PayloadAction<S, A, R>) => R;
|
||||||
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
|
declare export type PlainDispatch<A: { type: * }> = DispatchAPI<A>;
|
||||||
/* NEW: Dispatch is now a combination of these different dispatch types */
|
/* NEW: Dispatch is now a combination of these different dispatch types */
|
||||||
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A> & PayloadDispatch<S, A>;
|
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A> & PayloadDispatch<S, A>;
|
||||||
|
|
||||||
|
@ -6,13 +6,13 @@ import * as ACCOUNT from 'actions/constants/account';
|
|||||||
|
|
||||||
import type { Action, TrezorDevice } from 'flowtype';
|
import type { Action, TrezorDevice } from 'flowtype';
|
||||||
|
|
||||||
type AccountCommon = {
|
type AccountCommon = {|
|
||||||
+imported: boolean,
|
+imported: boolean,
|
||||||
+index: number,
|
+index: number,
|
||||||
+network: string, // network id (shortcut)
|
+network: string, // network id (shortcut)
|
||||||
+deviceID: string, // empty for imported accounts
|
+deviceID: string, // empty for imported accounts
|
||||||
+deviceState: string, // empty for imported accounts
|
+deviceState: string, // empty for imported accounts
|
||||||
+accountPath: Array<number>, // empty for imported accounts
|
+accountPath: string, // empty for imported accounts
|
||||||
+descriptor: string, // address or xpub
|
+descriptor: string, // address or xpub
|
||||||
|
|
||||||
balance: string,
|
balance: string,
|
||||||
@ -21,22 +21,31 @@ type AccountCommon = {
|
|||||||
empty: boolean, // account without transactions
|
empty: boolean, // account without transactions
|
||||||
|
|
||||||
transactions: number, // deprecated
|
transactions: number, // deprecated
|
||||||
};
|
|};
|
||||||
|
|
||||||
export type Account =
|
export type Account =
|
||||||
| (AccountCommon & {
|
| {|
|
||||||
|
...AccountCommon,
|
||||||
|
...{|
|
||||||
networkType: 'ethereum',
|
networkType: 'ethereum',
|
||||||
nonce: number,
|
nonce: string,
|
||||||
})
|
|},
|
||||||
| (AccountCommon & {
|
|}
|
||||||
|
| {|
|
||||||
|
...AccountCommon,
|
||||||
|
...{|
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
sequence: number,
|
sequence: number,
|
||||||
reserve: string,
|
reserve: string,
|
||||||
})
|
|},
|
||||||
| (AccountCommon & {
|
|}
|
||||||
|
| {|
|
||||||
|
...AccountCommon,
|
||||||
|
...{|
|
||||||
networkType: 'bitcoin',
|
networkType: 'bitcoin',
|
||||||
addressIndex: number,
|
addressIndex: number,
|
||||||
});
|
|},
|
||||||
|
|};
|
||||||
|
|
||||||
export type State = Array<Account>;
|
export type State = Array<Account>;
|
||||||
|
|
||||||
@ -51,14 +60,12 @@ export const findDeviceAccounts = (
|
|||||||
return state.filter(
|
return state.filter(
|
||||||
addr =>
|
addr =>
|
||||||
(addr.deviceState === device.state ||
|
(addr.deviceState === device.state ||
|
||||||
(addr.imported && addr.deviceID === (device.features || {}).device_id)) &&
|
(addr.imported && addr.deviceID === device.id)) &&
|
||||||
addr.network === network
|
addr.network === network
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return state.filter(
|
return state.filter(
|
||||||
addr =>
|
addr => addr.deviceState === device.state || (addr.imported && addr.deviceID === device.id)
|
||||||
addr.deviceState === device.state ||
|
|
||||||
(addr.imported && addr.deviceID === (device.features || {}).device_id)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { BLOCKCHAIN as BLOCKCHAIN_EVENT } from 'trezor-connect';
|
|||||||
import * as BLOCKCHAIN_ACTION from 'actions/constants/blockchain';
|
import * as BLOCKCHAIN_ACTION from 'actions/constants/blockchain';
|
||||||
|
|
||||||
import type { Action } from 'flowtype';
|
import type { Action } from 'flowtype';
|
||||||
import type { BlockchainConnect, BlockchainError, BlockchainBlock } from 'trezor-connect';
|
import type { BlockchainInfo, BlockchainError, BlockchainBlock } from 'trezor-connect';
|
||||||
|
|
||||||
export type BlockchainFeeLevel = {
|
export type BlockchainFeeLevel = {
|
||||||
name: string,
|
name: string,
|
||||||
@ -50,16 +50,15 @@ const onStartSubscribe = (state: State, shortcut: string): State => {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onConnect = (state: State, action: BlockchainConnect): State => {
|
const onConnect = (state: State, info: BlockchainInfo): State => {
|
||||||
const shortcut = action.payload.coin.shortcut.toLowerCase();
|
const shortcut = info.coin.shortcut.toLowerCase();
|
||||||
const network = state.find(b => b.shortcut === shortcut);
|
const network = state.find(b => b.shortcut === shortcut);
|
||||||
const { info } = action.payload;
|
|
||||||
if (network) {
|
if (network) {
|
||||||
const others = state.filter(b => b !== network);
|
const others = state.filter(b => b !== network);
|
||||||
return others.concat([
|
return others.concat([
|
||||||
{
|
{
|
||||||
...network,
|
...network,
|
||||||
block: info.block,
|
block: info.blockHeight,
|
||||||
connected: true,
|
connected: true,
|
||||||
connecting: false,
|
connecting: false,
|
||||||
reconnectionAttempts: 0,
|
reconnectionAttempts: 0,
|
||||||
@ -73,15 +72,15 @@ const onConnect = (state: State, action: BlockchainConnect): State => {
|
|||||||
connected: true,
|
connected: true,
|
||||||
connecting: false,
|
connecting: false,
|
||||||
reconnectionAttempts: 0,
|
reconnectionAttempts: 0,
|
||||||
block: info.block,
|
block: info.blockHeight,
|
||||||
feeTimestamp: 0,
|
feeTimestamp: 0,
|
||||||
feeLevels: [],
|
feeLevels: [],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onError = (state: State, action: BlockchainError): State => {
|
const onError = (state: State, payload: BlockchainError): State => {
|
||||||
const shortcut = action.payload.coin.shortcut.toLowerCase();
|
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const network = state.find(b => b.shortcut === shortcut);
|
const network = state.find(b => b.shortcut === shortcut);
|
||||||
if (network) {
|
if (network) {
|
||||||
const others = state.filter(b => b !== network);
|
const others = state.filter(b => b !== network);
|
||||||
@ -108,15 +107,15 @@ const onError = (state: State, action: BlockchainError): State => {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBlock = (state: State, action: BlockchainBlock): State => {
|
const onBlock = (state: State, payload: BlockchainBlock): State => {
|
||||||
const shortcut = action.payload.coin.shortcut.toLowerCase();
|
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const network = state.find(b => b.shortcut === shortcut);
|
const network = state.find(b => b.shortcut === shortcut);
|
||||||
if (network) {
|
if (network) {
|
||||||
const others = state.filter(b => b !== network);
|
const others = state.filter(b => b !== network);
|
||||||
return others.concat([
|
return others.concat([
|
||||||
{
|
{
|
||||||
...network,
|
...network,
|
||||||
block: action.payload.block,
|
block: payload.blockHeight,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -143,11 +142,11 @@ export default (state: State = initialState, action: Action): State => {
|
|||||||
case BLOCKCHAIN_ACTION.START_SUBSCRIBE:
|
case BLOCKCHAIN_ACTION.START_SUBSCRIBE:
|
||||||
return onStartSubscribe(state, action.shortcut);
|
return onStartSubscribe(state, action.shortcut);
|
||||||
case BLOCKCHAIN_EVENT.CONNECT:
|
case BLOCKCHAIN_EVENT.CONNECT:
|
||||||
return onConnect(state, action);
|
return onConnect(state, action.payload);
|
||||||
case BLOCKCHAIN_EVENT.ERROR:
|
case BLOCKCHAIN_EVENT.ERROR:
|
||||||
return onError(state, action);
|
return onError(state, action.payload);
|
||||||
case BLOCKCHAIN_EVENT.BLOCK:
|
case BLOCKCHAIN_EVENT.BLOCK:
|
||||||
return onBlock(state, action);
|
return onBlock(state, action.payload);
|
||||||
case BLOCKCHAIN_ACTION.UPDATE_FEE:
|
case BLOCKCHAIN_ACTION.UPDATE_FEE:
|
||||||
return updateFee(state, action.shortcut, action.feeLevels);
|
return updateFee(state, action.shortcut, action.feeLevels);
|
||||||
|
|
||||||
|
@ -62,8 +62,9 @@ const mergeDevices = (current: TrezorDevice, upcoming: Device | TrezorDevice): T
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...upcoming,
|
...upcoming,
|
||||||
features: null,
|
|
||||||
...extended,
|
...extended,
|
||||||
|
features: null,
|
||||||
|
state: null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,9 +80,7 @@ const addDevice = (state: State, device: Device): State => {
|
|||||||
}
|
}
|
||||||
otherDevices = state.filter(d => affectedDevices.indexOf(d) === -1);
|
otherDevices = state.filter(d => affectedDevices.indexOf(d) === -1);
|
||||||
} else {
|
} else {
|
||||||
affectedDevices = state.filter(
|
affectedDevices = state.filter(d => d.features && device.features && d.id === device.id);
|
||||||
d => d.features && device.features && d.features.device_id === device.features.device_id
|
|
||||||
);
|
|
||||||
const unacquiredDevices = state.filter(d => d.path.length > 0 && d.path === device.path);
|
const unacquiredDevices = state.filter(d => d.path.length > 0 && d.path === device.path);
|
||||||
otherDevices = state.filter(
|
otherDevices = state.filter(
|
||||||
d => affectedDevices.indexOf(d) < 0 && unacquiredDevices.indexOf(d) < 0
|
d => affectedDevices.indexOf(d) < 0 && unacquiredDevices.indexOf(d) < 0
|
||||||
@ -191,7 +190,7 @@ const changeDevice = (state: State, device: Device | TrezorDevice, extended: Obj
|
|||||||
d =>
|
d =>
|
||||||
(d.features &&
|
(d.features &&
|
||||||
device.features &&
|
device.features &&
|
||||||
d.features.device_id === device.features.device_id &&
|
d.id === device.id &&
|
||||||
d.features.passphrase_protection === device.features.passphrase_protection) ||
|
d.features.passphrase_protection === device.features.passphrase_protection) ||
|
||||||
(d.features && d.path.length > 0 && d.path === device.path)
|
(d.features && d.path.length > 0 && d.path === device.path)
|
||||||
);
|
);
|
||||||
@ -247,7 +246,7 @@ const forgetDevice = (state: State, device: TrezorDevice): State =>
|
|||||||
state.filter(
|
state.filter(
|
||||||
d =>
|
d =>
|
||||||
d.remember ||
|
d.remember ||
|
||||||
(d.features && device.features && d.features.device_id !== device.features.device_id) ||
|
(d.features && device.features && d.id !== device.id) ||
|
||||||
(!d.features && d.path !== device.path)
|
(!d.features && d.path !== device.path)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -261,9 +260,7 @@ const forgetSingleDevice = (state: State, device: TrezorDevice): State => {
|
|||||||
|
|
||||||
const disconnectDevice = (state: State, device: Device): State => {
|
const disconnectDevice = (state: State, device: Device): State => {
|
||||||
const affectedDevices: State = state.filter(
|
const affectedDevices: State = state.filter(
|
||||||
d =>
|
d => d.path === device.path || (d.features && device.features && d.id === device.id)
|
||||||
d.path === device.path ||
|
|
||||||
(d.features && device.features && d.features.device_id === device.features.device_id)
|
|
||||||
);
|
);
|
||||||
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
|
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
|
||||||
|
|
||||||
@ -308,9 +305,7 @@ const onSelectedDevice = (state: State, device: ?TrezorDevice): State => {
|
|||||||
|
|
||||||
const onChangeWalletType = (state: State, device: TrezorDevice, hidden: boolean): State => {
|
const onChangeWalletType = (state: State, device: TrezorDevice, hidden: boolean): State => {
|
||||||
const affectedDevices: State = state.filter(
|
const affectedDevices: State = state.filter(
|
||||||
d =>
|
d => d.path === device.path || (d.features && device.features && d.id === device.id)
|
||||||
d.path === device.path ||
|
|
||||||
(d.features && device.features && d.features.device_id === device.features.device_id)
|
|
||||||
);
|
);
|
||||||
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
|
const otherDevices: State = state.filter(d => affectedDevices.indexOf(d) === -1);
|
||||||
if (affectedDevices.length > 0) {
|
if (affectedDevices.length > 0) {
|
||||||
@ -354,16 +349,16 @@ export default function devices(state: State = initialState, action: Action): St
|
|||||||
|
|
||||||
case DEVICE.CONNECT:
|
case DEVICE.CONNECT:
|
||||||
case DEVICE.CONNECT_UNACQUIRED:
|
case DEVICE.CONNECT_UNACQUIRED:
|
||||||
return addDevice(state, action.device);
|
return addDevice(state, action.payload);
|
||||||
|
|
||||||
case DEVICE.CHANGED:
|
case DEVICE.CHANGED:
|
||||||
// return changeDevice(state, { ...action.device, connected: true, available: true });
|
// return changeDevice(state, { ...action.device, connected: true, available: true });
|
||||||
return changeDevice(state, action.device, { connected: true, available: true });
|
return changeDevice(state, action.payload, { connected: true, available: true });
|
||||||
// TODO: check if available will propagate to unavailable
|
// TODO: check if available will propagate to unavailable
|
||||||
|
|
||||||
case DEVICE.DISCONNECT:
|
case DEVICE.DISCONNECT:
|
||||||
// case DEVICE.DISCONNECT_UNACQUIRED:
|
// case DEVICE.DISCONNECT_UNACQUIRED:
|
||||||
return disconnectDevice(state, action.device);
|
return disconnectDevice(state, action.payload);
|
||||||
|
|
||||||
case WALLET.SET_SELECTED_DEVICE:
|
case WALLET.SET_SELECTED_DEVICE:
|
||||||
return onSelectedDevice(state, action.device);
|
return onSelectedDevice(state, action.device);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import HDKey from 'hdkey';
|
|
||||||
|
|
||||||
import * as DISCOVERY from 'actions/constants/discovery';
|
import * as DISCOVERY from 'actions/constants/discovery';
|
||||||
import * as ACCOUNT from 'actions/constants/account';
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
@ -27,10 +25,6 @@ export type Discovery = {
|
|||||||
waitingForBlockchain: boolean,
|
waitingForBlockchain: boolean,
|
||||||
fwNotSupported: boolean,
|
fwNotSupported: boolean,
|
||||||
fwOutdated: boolean,
|
fwOutdated: boolean,
|
||||||
|
|
||||||
publicKey: string, // used in ethereum only
|
|
||||||
chainCode: string, // used in ethereum only
|
|
||||||
hdKey: HDKey, // used in ethereum only
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type State = Array<Discovery>;
|
export type State = Array<Discovery>;
|
||||||
@ -46,10 +40,6 @@ const defaultDiscovery: Discovery = {
|
|||||||
waitingForBlockchain: false,
|
waitingForBlockchain: false,
|
||||||
fwNotSupported: false,
|
fwNotSupported: false,
|
||||||
fwOutdated: false,
|
fwOutdated: false,
|
||||||
|
|
||||||
publicKey: '',
|
|
||||||
chainCode: '',
|
|
||||||
hdKey: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const findIndex = (state: State, network: string, deviceState: string): number =>
|
const findIndex = (state: State, network: string, deviceState: string): number =>
|
||||||
@ -63,18 +53,6 @@ const start = (state: State, action: DiscoveryStartAction): State => {
|
|||||||
deviceState,
|
deviceState,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (action.networkType === 'ethereum') {
|
|
||||||
const hdKey = new HDKey();
|
|
||||||
hdKey.publicKey = Buffer.from(action.publicKey, 'hex');
|
|
||||||
hdKey.chainCode = Buffer.from(action.chainCode, 'hex');
|
|
||||||
|
|
||||||
instance.hdKey = hdKey;
|
|
||||||
instance.publicKey = action.publicKey;
|
|
||||||
instance.chainCode = action.chainCode;
|
|
||||||
|
|
||||||
instance.basePath = action.basePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newState: State = [...state];
|
const newState: State = [...state];
|
||||||
const index: number = findIndex(state, action.network.shortcut, deviceState);
|
const index: number = findIndex(state, action.network.shortcut, deviceState);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
@ -202,15 +180,8 @@ export default function discovery(state: State = initialState, action: Action):
|
|||||||
return notSupported(state, action);
|
return notSupported(state, action);
|
||||||
case DISCOVERY.FROM_STORAGE:
|
case DISCOVERY.FROM_STORAGE:
|
||||||
return action.payload.map(d => {
|
return action.payload.map(d => {
|
||||||
if (d.publicKey.length < 1) return d;
|
|
||||||
// recreate ethereum discovery HDKey
|
|
||||||
// deprecated: will be removed after switching to blockbook
|
|
||||||
const hdKey: HDKey = new HDKey();
|
|
||||||
hdKey.publicKey = Buffer.from(d.publicKey, 'hex');
|
|
||||||
hdKey.chainCode = Buffer.from(d.chainCode, 'hex');
|
|
||||||
return {
|
return {
|
||||||
...d,
|
...d,
|
||||||
hdKey,
|
|
||||||
interrupted: false,
|
interrupted: false,
|
||||||
waitingForDevice: false,
|
waitingForDevice: false,
|
||||||
waitingForBlockchain: false,
|
waitingForBlockchain: false,
|
||||||
|
@ -67,7 +67,7 @@ export default function modal(state: State = initialState, action: Action): Stat
|
|||||||
case DEVICE.DISCONNECT:
|
case DEVICE.DISCONNECT:
|
||||||
if (
|
if (
|
||||||
state.context === MODAL.CONTEXT_DEVICE &&
|
state.context === MODAL.CONTEXT_DEVICE &&
|
||||||
action.device.path === state.device.path
|
action.payload.path === state.device.path
|
||||||
) {
|
) {
|
||||||
return initialState;
|
return initialState;
|
||||||
}
|
}
|
||||||
@ -76,16 +76,17 @@ export default function modal(state: State = initialState, action: Action): Stat
|
|||||||
case UI.REQUEST_PIN:
|
case UI.REQUEST_PIN:
|
||||||
case UI.INVALID_PIN:
|
case UI.INVALID_PIN:
|
||||||
case UI.REQUEST_PASSPHRASE:
|
case UI.REQUEST_PASSPHRASE:
|
||||||
|
case UI.REQUEST_PASSPHRASE_ON_DEVICE:
|
||||||
return {
|
return {
|
||||||
context: MODAL.CONTEXT_DEVICE,
|
context: MODAL.CONTEXT_DEVICE,
|
||||||
device: action.payload.device,
|
device: (action.payload.device: any), // should be TrezorDevice, but ath this point it doesn't matter, Device is enough
|
||||||
windowType: action.type,
|
windowType: action.type,
|
||||||
};
|
};
|
||||||
|
|
||||||
case UI.REQUEST_BUTTON:
|
case UI.REQUEST_BUTTON:
|
||||||
return {
|
return {
|
||||||
context: MODAL.CONTEXT_DEVICE,
|
context: MODAL.CONTEXT_DEVICE,
|
||||||
device: action.payload.device,
|
device: (action.payload.device: any), // should be TrezorDevice, but ath this point it doesn't matter, Device is enough
|
||||||
windowType: action.payload.code,
|
windowType: action.payload.code,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ const closeNotification = (state: State, payload: any): State => {
|
|||||||
export default function notification(state: State = initialState, action: Action): State {
|
export default function notification(state: State = initialState, action: Action): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case DEVICE.DISCONNECT: {
|
case DEVICE.DISCONNECT: {
|
||||||
const { path } = action.device; // Flow warning
|
const { path } = action.payload;
|
||||||
return state.filter(entry => entry.devicePath !== path);
|
return state.filter(entry => entry.devicePath !== path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,12 +17,12 @@ const add = (state: State, payload: Transaction): State => {
|
|||||||
const removeByDeviceState = (state: State, deviceState: ?string): State =>
|
const removeByDeviceState = (state: State, deviceState: ?string): State =>
|
||||||
state.filter(tx => tx.deviceState !== deviceState);
|
state.filter(tx => tx.deviceState !== deviceState);
|
||||||
|
|
||||||
const removeByHash = (state: State, hash: string): State => state.filter(tx => tx.hash !== hash);
|
const removeByHash = (state: State, hash: string): State => state.filter(tx => tx.txid !== hash);
|
||||||
|
|
||||||
const reject = (state: State, hash: string): State =>
|
const reject = (state: State, hash: string): State =>
|
||||||
state.map(tx => {
|
state.map(tx => {
|
||||||
if (tx.hash === hash && !tx.rejected) {
|
if (tx.txid === hash && !tx.rejected) {
|
||||||
return { ...tx, rejected: true };
|
return Object.assign({}, { rejected: true }, tx);
|
||||||
}
|
}
|
||||||
return tx;
|
return tx;
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import { TRANSPORT, UI } from 'trezor-connect';
|
import { TRANSPORT, IFRAME } from 'trezor-connect';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
|
import type { TransportInfo, BridgeInfo } from 'trezor-connect';
|
||||||
import type { Action } from 'flowtype';
|
import type { Action } from 'flowtype';
|
||||||
|
|
||||||
export type SelectedDevice = {
|
export type SelectedDevice = {
|
||||||
@ -9,26 +9,14 @@ export type SelectedDevice = {
|
|||||||
instance: ?number,
|
instance: ?number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LatestBridge = {
|
|
||||||
version: Array<number>,
|
|
||||||
directory: string,
|
|
||||||
packages: Array<{ name: string, url: string, signature?: string, preferred: boolean }>,
|
|
||||||
changelog: Array<string>,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
initialized: boolean,
|
initialized: boolean,
|
||||||
error: ?string,
|
error: ?string,
|
||||||
transport:
|
transport:
|
||||||
| {
|
| TransportInfo
|
||||||
type: string,
|
|
||||||
version: string,
|
|
||||||
outdated: boolean,
|
|
||||||
bridge: LatestBridge,
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: null,
|
type: null,
|
||||||
bridge: LatestBridge,
|
bridge: ?BridgeInfo,
|
||||||
},
|
},
|
||||||
// browserState: {
|
// browserState: {
|
||||||
// name: string;
|
// name: string;
|
||||||
@ -46,12 +34,7 @@ const initialState: State = {
|
|||||||
error: null,
|
error: null,
|
||||||
transport: {
|
transport: {
|
||||||
type: null,
|
type: null,
|
||||||
bridge: {
|
bridge: null,
|
||||||
version: [],
|
|
||||||
directory: '',
|
|
||||||
packages: [],
|
|
||||||
changelog: [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
browserState: {},
|
browserState: {},
|
||||||
acquiringDevice: false,
|
acquiringDevice: false,
|
||||||
@ -66,11 +49,10 @@ export default function connect(state: State = initialState, action: Action): St
|
|||||||
error: action.error,
|
error: action.error,
|
||||||
};
|
};
|
||||||
// trezor-connect iframe loaded
|
// trezor-connect iframe loaded
|
||||||
case UI.IFRAME_HANDSHAKE:
|
case IFRAME.LOADED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
browserState: action.payload.browser,
|
|
||||||
};
|
};
|
||||||
// trezor-connect (trezor-link) initialized
|
// trezor-connect (trezor-link) initialized
|
||||||
case TRANSPORT.START:
|
case TRANSPORT.START:
|
||||||
|
@ -98,7 +98,7 @@ export default function wallet(state: State = initialState, action: Action): Sta
|
|||||||
};
|
};
|
||||||
|
|
||||||
case DEVICE.DISCONNECT:
|
case DEVICE.DISCONNECT:
|
||||||
if (state.disconnectRequest && action.device.path === state.disconnectRequest.path) {
|
if (state.disconnectRequest && action.payload.path === state.disconnectRequest.path) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
disconnectRequest: null,
|
disconnectRequest: null,
|
||||||
|
@ -27,11 +27,7 @@ export const getSelectedDevice = (state: State): ?TrezorDevice => {
|
|||||||
if (d.mode === 'bootloader' && d.path === locationState.device) {
|
if (d.mode === 'bootloader' && d.path === locationState.device) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (d.features && d.id === locationState.device && d.instance === instance) {
|
||||||
d.features &&
|
|
||||||
d.features.device_id === locationState.device &&
|
|
||||||
d.instance === instance
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -46,7 +42,7 @@ export const findDevice = (
|
|||||||
): ?TrezorDevice =>
|
): ?TrezorDevice =>
|
||||||
devices.find(d => {
|
devices.find(d => {
|
||||||
// TODO: && (instance && d.instance === instance)
|
// TODO: && (instance && d.instance === instance)
|
||||||
if (d.features && d.features.device_id === deviceId && d.state === deviceState) {
|
if (d.features && d.id === deviceId && d.state === deviceState) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -60,9 +56,7 @@ export const getDuplicateInstanceNumber = (
|
|||||||
// find device(s) with the same features.device_id
|
// find device(s) with the same features.device_id
|
||||||
// and sort them by instance number
|
// and sort them by instance number
|
||||||
const affectedDevices: Array<TrezorDevice> = devices
|
const affectedDevices: Array<TrezorDevice> = devices
|
||||||
.filter(
|
.filter(d => d.features && device.features && d.id === device.id)
|
||||||
d => d.features && device.features && d.features.device_id === device.features.device_id
|
|
||||||
)
|
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (!a.instance) {
|
if (!a.instance) {
|
||||||
return -1;
|
return -1;
|
||||||
@ -92,8 +86,7 @@ export const getSelectedAccount = (state: State): ?Account => {
|
|||||||
return state.accounts.find(
|
return state.accounts.find(
|
||||||
a =>
|
a =>
|
||||||
a.imported === isImported &&
|
a.imported === isImported &&
|
||||||
(a.deviceState === device.state ||
|
(a.deviceState === device.state || (a.imported && a.deviceID === device.id)) &&
|
||||||
(a.imported && a.deviceID === (device.features || {}).device_id)) &&
|
|
||||||
a.index === index &&
|
a.index === index &&
|
||||||
a.network === locationState.network
|
a.network === locationState.network
|
||||||
);
|
);
|
||||||
@ -129,8 +122,8 @@ export const getAccountPendingTx = (
|
|||||||
|
|
||||||
export const getPendingSequence = (pending: Array<Transaction>): number =>
|
export const getPendingSequence = (pending: Array<Transaction>): number =>
|
||||||
pending.reduce((value: number, tx: Transaction): number => {
|
pending.reduce((value: number, tx: Transaction): number => {
|
||||||
if (tx.rejected) return value;
|
if (!tx.ethereumSpecific || tx.rejected) return value;
|
||||||
return Math.max(value, tx.sequence + 1);
|
return Math.max(value, tx.ethereumSpecific.nonce + 1);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
export const getPendingAmount = (
|
export const getPendingAmount = (
|
||||||
@ -139,7 +132,7 @@ export const getPendingAmount = (
|
|||||||
token: boolean = false
|
token: boolean = false
|
||||||
): BigNumber =>
|
): BigNumber =>
|
||||||
pending.reduce((value: BigNumber, tx: Transaction): BigNumber => {
|
pending.reduce((value: BigNumber, tx: Transaction): BigNumber => {
|
||||||
if (tx.type !== 'send') return value;
|
if (tx.type !== 'sent') return value;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// regular transactions
|
// regular transactions
|
||||||
// add fees from token txs and amount from regular txs
|
// add fees from token txs and amount from regular txs
|
||||||
@ -147,9 +140,9 @@ export const getPendingAmount = (
|
|||||||
}
|
}
|
||||||
if (tx.tokens) {
|
if (tx.tokens) {
|
||||||
// token transactions
|
// token transactions
|
||||||
const allTokens = tx.tokens.filter(t => t.shortcut === currency);
|
const allTokens = tx.tokens.filter(t => t.symbol === currency);
|
||||||
const tokensValue: BigNumber = allTokens.reduce(
|
const tokensValue: BigNumber = allTokens.reduce(
|
||||||
(tv, t) => new BigNumber(value).plus(t.value),
|
(tv, t) => new BigNumber(value).plus(t.amount),
|
||||||
new BigNumber('0')
|
new BigNumber('0')
|
||||||
);
|
);
|
||||||
return new BigNumber(value).plus(tokensValue);
|
return new BigNumber(value).plus(tokensValue);
|
||||||
|
@ -34,10 +34,10 @@ const LogService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case DEVICE.CONNECT:
|
case DEVICE.CONNECT:
|
||||||
api.dispatch(LogActions.add('Device connected', action.device));
|
api.dispatch(LogActions.add('Device connected', action.payload));
|
||||||
break;
|
break;
|
||||||
case DEVICE.DISCONNECT:
|
case DEVICE.DISCONNECT:
|
||||||
api.dispatch(LogActions.add('Device disconnected', action.device));
|
api.dispatch(LogActions.add('Device disconnected', action.payload));
|
||||||
break;
|
break;
|
||||||
case DISCOVERY.START:
|
case DISCOVERY.START:
|
||||||
api.dispatch(LogActions.add('Discovery started', action));
|
api.dispatch(LogActions.add('Discovery started', action));
|
||||||
|
@ -30,7 +30,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
} else if (action.type === BLOCKCHAIN_READY) {
|
} else if (action.type === BLOCKCHAIN_READY) {
|
||||||
api.dispatch(TrezorConnectActions.postInit());
|
api.dispatch(TrezorConnectActions.postInit());
|
||||||
} else if (action.type === DEVICE.DISCONNECT) {
|
} else if (action.type === DEVICE.DISCONNECT) {
|
||||||
api.dispatch(TrezorConnectActions.deviceDisconnect(action.device));
|
api.dispatch(TrezorConnectActions.deviceDisconnect(action.payload));
|
||||||
} else if (action.type === CONNECT.REMEMBER_REQUEST) {
|
} else if (action.type === CONNECT.REMEMBER_REQUEST) {
|
||||||
api.dispatch(ModalActions.onRememberRequest(prevModalState));
|
api.dispatch(ModalActions.onRememberRequest(prevModalState));
|
||||||
} else if (action.type === CONNECT.FORGET) {
|
} else if (action.type === CONNECT.FORGET) {
|
||||||
@ -48,7 +48,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
}
|
}
|
||||||
} else if (action.type === DEVICE.CONNECT || action.type === DEVICE.CONNECT_UNACQUIRED) {
|
} else if (action.type === DEVICE.CONNECT || action.type === DEVICE.CONNECT_UNACQUIRED) {
|
||||||
api.dispatch(ModalActions.onDeviceConnect(action.device));
|
api.dispatch(ModalActions.onDeviceConnect(action.payload));
|
||||||
} else if (action.type === CONNECT.DUPLICATE) {
|
} else if (action.type === CONNECT.DUPLICATE) {
|
||||||
api.dispatch(RouterActions.selectDevice(action.device));
|
api.dispatch(RouterActions.selectDevice(action.device));
|
||||||
} else if (action.type === BLOCKCHAIN.BLOCK) {
|
} else if (action.type === BLOCKCHAIN.BLOCK) {
|
||||||
|
@ -56,7 +56,7 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
|||||||
api.dispatch(TrezorConnectActions.requestWalletType());
|
api.dispatch(TrezorConnectActions.requestWalletType());
|
||||||
break;
|
break;
|
||||||
case DEVICE.CONNECT:
|
case DEVICE.CONNECT:
|
||||||
api.dispatch(WalletActions.clearUnavailableDevicesData(prevState, action.device));
|
api.dispatch(WalletActions.clearUnavailableDevicesData(prevState, action.payload));
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
|
@ -42,9 +42,9 @@ describe('device utils', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('isWebUSB', () => {
|
it('isWebUSB', () => {
|
||||||
expect(utils.isWebUSB({ type: 'webusb', version: '1.6.0' })).toBe(true);
|
expect(utils.isWebUSB({ type: 'WebUsbPlugin', version: '1.6.0' })).toBe(true);
|
||||||
expect(utils.isWebUSB({ type: 'aaaa', version: 'aaaaaa' })).toBe(false);
|
expect(utils.isWebUSB({ type: 'aaaa', version: 'aaaaaa' })).toBe(false);
|
||||||
expect(utils.isWebUSB({ type: 'webusb' })).toBe(true);
|
expect(utils.isWebUSB({ type: 'WebUsbPlugin' })).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isDisabled', () => {
|
it('isDisabled', () => {
|
||||||
|
114
src/utils/accountUtils.js
Normal file
114
src/utils/accountUtils.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/* @flow */
|
||||||
|
import { toDecimalAmount } from 'utils/formatUtils';
|
||||||
|
import type { AccountInfo, AccountTransaction } from 'trezor-connect';
|
||||||
|
import type { Account, Transaction, Network, TrezorDevice } from 'flowtype';
|
||||||
|
|
||||||
|
// Merge fresh AccountInfo into existing Account
|
||||||
|
export const mergeAccount = (
|
||||||
|
info: AccountInfo,
|
||||||
|
account: Account,
|
||||||
|
network: Network,
|
||||||
|
block: number
|
||||||
|
): Account => {
|
||||||
|
if (account.networkType === 'ethereum') {
|
||||||
|
const nonce = info.misc && info.misc.nonce ? info.misc.nonce : '0';
|
||||||
|
return {
|
||||||
|
networkType: 'ethereum',
|
||||||
|
...account,
|
||||||
|
balance: toDecimalAmount(info.balance, network.decimals),
|
||||||
|
availableBalance: toDecimalAmount(info.availableBalance, network.decimals),
|
||||||
|
block,
|
||||||
|
transactions: info.history.total,
|
||||||
|
empty: account.empty,
|
||||||
|
nonce,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.networkType === 'ripple') {
|
||||||
|
const sequence = info.misc && info.misc.sequence ? info.misc.sequence : 0;
|
||||||
|
const reserve = info.misc && info.misc.reserve ? info.misc.reserve : '0';
|
||||||
|
return {
|
||||||
|
...account,
|
||||||
|
balance: toDecimalAmount(info.balance, network.decimals),
|
||||||
|
availableBalance: toDecimalAmount(info.availableBalance, network.decimals),
|
||||||
|
block,
|
||||||
|
empty: info.empty,
|
||||||
|
|
||||||
|
networkType: 'ripple',
|
||||||
|
sequence,
|
||||||
|
reserve: toDecimalAmount(reserve || '0', network.decimals),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return account;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EnhanceAccountOptions = {
|
||||||
|
index: number,
|
||||||
|
network: Network,
|
||||||
|
device: TrezorDevice,
|
||||||
|
imported?: boolean,
|
||||||
|
block?: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Account from AccountInfo
|
||||||
|
export const enhanceAccount = (account: AccountInfo, options: EnhanceAccountOptions): Account => {
|
||||||
|
if (options.network.type === 'ethereum') {
|
||||||
|
const nonce = account.misc && account.misc.nonce ? account.misc.nonce : '0';
|
||||||
|
return {
|
||||||
|
imported: !!options.imported,
|
||||||
|
index: options.index,
|
||||||
|
network: options.network.shortcut,
|
||||||
|
deviceID: options.device.id || '0',
|
||||||
|
deviceState: options.device.state || '0',
|
||||||
|
accountPath: account.path,
|
||||||
|
descriptor: account.descriptor,
|
||||||
|
|
||||||
|
balance: toDecimalAmount(account.balance, options.network.decimals),
|
||||||
|
availableBalance: toDecimalAmount(account.availableBalance, options.network.decimals),
|
||||||
|
block: options.block || 0,
|
||||||
|
transactions: account.history.total,
|
||||||
|
empty: account.empty,
|
||||||
|
|
||||||
|
networkType: 'ethereum',
|
||||||
|
nonce,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sequence = account.misc && account.misc.sequence ? account.misc.sequence : 0;
|
||||||
|
const reserve = account.misc && account.misc.reserve ? account.misc.reserve : '0';
|
||||||
|
return {
|
||||||
|
imported: !!options.imported,
|
||||||
|
index: options.index,
|
||||||
|
network: options.network.shortcut,
|
||||||
|
deviceID: options.device.id || '0',
|
||||||
|
deviceState: options.device.state || '0',
|
||||||
|
accountPath: account.path,
|
||||||
|
descriptor: account.descriptor,
|
||||||
|
|
||||||
|
balance: toDecimalAmount(account.balance, options.network.decimals),
|
||||||
|
availableBalance: toDecimalAmount(account.availableBalance, options.network.decimals),
|
||||||
|
block: options.block || 0,
|
||||||
|
transactions: 0,
|
||||||
|
empty: account.empty,
|
||||||
|
|
||||||
|
networkType: 'ripple',
|
||||||
|
sequence,
|
||||||
|
reserve: toDecimalAmount(reserve, options.network.decimals),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const enhanceTransaction = (
|
||||||
|
account: Account,
|
||||||
|
tx: AccountTransaction,
|
||||||
|
network: Network
|
||||||
|
): Transaction => {
|
||||||
|
return {
|
||||||
|
...tx,
|
||||||
|
descriptor: account.descriptor,
|
||||||
|
deviceState: account.deviceState,
|
||||||
|
network: account.network,
|
||||||
|
amount: toDecimalAmount(tx.amount, network.decimals),
|
||||||
|
fee: toDecimalAmount(tx.fee, network.decimals),
|
||||||
|
};
|
||||||
|
};
|
@ -79,7 +79,8 @@ export const getStatusName = (deviceStatus: string, intl: IntlShape): string =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isWebUSB = (transport: Transport) => !!(transport.type && transport.type === 'webusb');
|
export const isWebUSB = (transport: Transport) =>
|
||||||
|
!!(transport.type && transport.type === 'WebUsbPlugin');
|
||||||
|
|
||||||
export const isDisabled = (
|
export const isDisabled = (
|
||||||
selectedDevice: TrezorDevice,
|
selectedDevice: TrezorDevice,
|
||||||
|
@ -4,7 +4,7 @@ import type { TrezorDevice } from 'flowtype';
|
|||||||
|
|
||||||
const getOldWalletUrl = (device: ?TrezorDevice): string => {
|
const getOldWalletUrl = (device: ?TrezorDevice): string => {
|
||||||
if (!device || !device.firmwareRelease) return urlConstants.OLD_WALLET_BETA;
|
if (!device || !device.firmwareRelease) return urlConstants.OLD_WALLET_BETA;
|
||||||
const release = device.firmwareRelease;
|
const { release } = device.firmwareRelease;
|
||||||
const url = release.channel === 'beta' ? urlConstants.OLD_WALLET_BETA : urlConstants.OLD_WALLET;
|
const url = release.channel === 'beta' ? urlConstants.OLD_WALLET_BETA : urlConstants.OLD_WALLET;
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
@ -12,7 +12,7 @@ const getOldWalletUrl = (device: ?TrezorDevice): string => {
|
|||||||
// TODO: use uri template to build urls
|
// TODO: use uri template to build urls
|
||||||
const getOldWalletReleaseUrl = (device: ?TrezorDevice): string => {
|
const getOldWalletReleaseUrl = (device: ?TrezorDevice): string => {
|
||||||
if (!device || !device.firmwareRelease) return urlConstants.OLD_WALLET_BETA;
|
if (!device || !device.firmwareRelease) return urlConstants.OLD_WALLET_BETA;
|
||||||
const release = device.firmwareRelease;
|
const { release } = device.firmwareRelease;
|
||||||
const url = getOldWalletUrl(device);
|
const url = getOldWalletUrl(device);
|
||||||
const version = release.version.join('.');
|
const version = release.version.join('.');
|
||||||
return `${url}?fw=${version}`;
|
return `${url}?fw=${version}`;
|
||||||
|
@ -79,7 +79,6 @@ const LearnMoreText = styled.span`
|
|||||||
const SelectWrapper = styled(Select)`
|
const SelectWrapper = styled(Select)`
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
width: 180px;
|
width: 180px;
|
||||||
margin-bottom: 5px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Download = styled.div`
|
const Download = styled.div`
|
||||||
@ -117,21 +116,24 @@ const GoBack = styled.span`
|
|||||||
class InstallBridge extends PureComponent<Props, State> {
|
class InstallBridge extends PureComponent<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
const { transport } = props;
|
||||||
|
const packages = transport.bridge ? transport.bridge.packages : [];
|
||||||
|
const latestVersion = transport.bridge ? transport.bridge.version.join('.') : '';
|
||||||
|
|
||||||
const installers = props.transport.bridge.packages.map(p => ({
|
const installers = packages.map(p => ({
|
||||||
label: p.name,
|
label: p.name,
|
||||||
value: p.url,
|
value: p.url,
|
||||||
signature: p.signature,
|
signature: p.signature,
|
||||||
preferred: p.preferred,
|
preferred: !!p.preferred,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const currentTarget: ?InstallTarget = installers.find(i => i.preferred === true);
|
const currentTarget: ?InstallTarget = installers.find(i => i.preferred === true);
|
||||||
this.state = {
|
this.state = {
|
||||||
currentVersion:
|
currentVersion:
|
||||||
props.transport.type && props.transport.type === 'bridge'
|
transport.type && transport.type === 'bridge'
|
||||||
? `Your version ${props.transport.version}`
|
? `Your version ${transport.version}`
|
||||||
: 'Not installed',
|
: 'Not installed',
|
||||||
latestVersion: props.transport.bridge.version.join('.'),
|
latestVersion,
|
||||||
installers,
|
installers,
|
||||||
target: currentTarget || installers[0],
|
target: currentTarget || installers[0],
|
||||||
uri: 'https://wallet.trezor.io/data/',
|
uri: 'https://wallet.trezor.io/data/',
|
||||||
|
@ -46,8 +46,8 @@ class CoinMenu extends PureComponent<Props> {
|
|||||||
getBaseUrl() {
|
getBaseUrl() {
|
||||||
const { selectedDevice } = this.props.wallet;
|
const { selectedDevice } = this.props.wallet;
|
||||||
let baseUrl = '';
|
let baseUrl = '';
|
||||||
if (selectedDevice && selectedDevice.features) {
|
if (selectedDevice && selectedDevice.id) {
|
||||||
baseUrl = `/device/${selectedDevice.features.device_id}`;
|
baseUrl = `/device/${selectedDevice.id}`;
|
||||||
if (selectedDevice.instance) {
|
if (selectedDevice.instance) {
|
||||||
baseUrl += `:${selectedDevice.instance}`;
|
baseUrl += `:${selectedDevice.instance}`;
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,10 @@ class MenuItems extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
if (!this.showDeviceMenu()) return null;
|
if (!this.showDeviceMenu()) return null;
|
||||||
const { device } = this.props;
|
const { device } = this.props;
|
||||||
|
const changeWalletButton =
|
||||||
|
device.features && device.features.passphrase_always_on_device
|
||||||
|
? l10nMessages.TR_CHANGE_PASSPHRASE
|
||||||
|
: l10nMessages.TR_CHANGE_WALLET_TYPE;
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{/* <Item onClick={() => {
|
{/* <Item onClick={() => {
|
||||||
@ -92,7 +96,7 @@ class MenuItems extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<Label>
|
<Label>
|
||||||
<FormattedMessage {...l10nMessages.TR_CHANGE_WALLET_TYPE} />
|
<FormattedMessage {...changeWalletButton} />
|
||||||
</Label>
|
</Label>
|
||||||
</Item>
|
</Item>
|
||||||
)}
|
)}
|
||||||
|
@ -7,6 +7,10 @@ const definedMessages: Messages = defineMessages({
|
|||||||
id: 'TR_CHANGE_WALLET_TYPE',
|
id: 'TR_CHANGE_WALLET_TYPE',
|
||||||
defaultMessage: 'Change wallet type',
|
defaultMessage: 'Change wallet type',
|
||||||
},
|
},
|
||||||
|
TR_CHANGE_PASSPHRASE: {
|
||||||
|
id: 'TR_CHANGE_PASSPHRASE',
|
||||||
|
defaultMessage: 'Change passphrase',
|
||||||
|
},
|
||||||
TR_RENEW_SESSION: {
|
TR_RENEW_SESSION: {
|
||||||
id: 'TR_RENEW_SESSION',
|
id: 'TR_RENEW_SESSION',
|
||||||
defaultMessage: 'Renew session',
|
defaultMessage: 'Renew session',
|
||||||
|
@ -31,7 +31,7 @@ const PendingTransactions = (props: Props) => {
|
|||||||
<NoTransactions>There are no pending transactions</NoTransactions>
|
<NoTransactions>There are no pending transactions</NoTransactions>
|
||||||
)}
|
)}
|
||||||
{pending.map(tx => (
|
{pending.map(tx => (
|
||||||
<Transaction key={tx.hash} network={props.network} tx={tx} />
|
<Transaction key={tx.txid} network={props.network} tx={tx} />
|
||||||
))}
|
))}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -521,14 +521,13 @@ const AccountSend = (props: Props) => {
|
|||||||
</AdvancedForm>
|
</AdvancedForm>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.selectedAccount.pending.length > 0 ||
|
{props.selectedAccount.pending.length > 0 && (
|
||||||
(account.imported && (
|
|
||||||
<PendingTransactions
|
<PendingTransactions
|
||||||
pending={props.selectedAccount.pending}
|
pending={props.selectedAccount.pending}
|
||||||
tokens={props.selectedAccount.tokens}
|
tokens={props.selectedAccount.tokens}
|
||||||
network={network}
|
network={network}
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -173,7 +173,7 @@ const AccountSummary = (props: Props) => {
|
|||||||
{tokens.length < 1 && <AddTokenMessage />}
|
{tokens.length < 1 && <AddTokenMessage />}
|
||||||
{tokens.map(token => (
|
{tokens.map(token => (
|
||||||
<AddedToken
|
<AddedToken
|
||||||
key={token.symbol}
|
key={token.address}
|
||||||
token={token}
|
token={token}
|
||||||
pending={pending}
|
pending={pending}
|
||||||
removeToken={props.removeToken}
|
removeToken={props.removeToken}
|
||||||
|
@ -63,8 +63,8 @@ const StyledH4 = styled(H4)`
|
|||||||
|
|
||||||
const getBaseUrl = device => {
|
const getBaseUrl = device => {
|
||||||
let baseUrl = '';
|
let baseUrl = '';
|
||||||
if (device && device.features) {
|
if (device && device.id) {
|
||||||
baseUrl = `/device/${device.features.device_id}`;
|
baseUrl = `/device/${device.id}`;
|
||||||
if (device.instance) {
|
if (device.instance) {
|
||||||
baseUrl += `:${device.instance}`;
|
baseUrl += `:${device.instance}`;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user