mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-14 04:19:09 +00:00
update Blockchain and Discovery using trezor-connect@8
This commit is contained in:
parent
486bfff397
commit
2231f42b37
@ -15,7 +15,7 @@
|
||||
"name": "Ethereum",
|
||||
"symbol": "ETH",
|
||||
"shortcut": "eth",
|
||||
"bip44": "m/44'/60'/0'/0",
|
||||
"bip44": "m/44'/60'/0'/0/a",
|
||||
"chainId": 1,
|
||||
"defaultGasPrice": 64,
|
||||
"defaultGasLimit": 21000,
|
||||
@ -26,8 +26,8 @@
|
||||
"wss://eth2.trezor.io/geth"
|
||||
],
|
||||
"explorer": {
|
||||
"tx": "https://etherscan.io/tx/",
|
||||
"address": "https://etherscan.io/address/"
|
||||
"tx": "https://eth1.trezor.io/tx/",
|
||||
"address": "https://eth1.trezor.io/tx/"
|
||||
},
|
||||
"hasSignVerify": true
|
||||
},
|
||||
@ -38,7 +38,7 @@
|
||||
"symbol": "ETC",
|
||||
"shortcut": "etc",
|
||||
"chainId": 61,
|
||||
"bip44": "m/44'/61'/0'/0",
|
||||
"bip44": "m/44'/61'/0'/0/a",
|
||||
"defaultGasPrice": 64,
|
||||
"defaultGasLimit": 21000,
|
||||
"defaultGasLimitTokens": 200000,
|
||||
@ -61,7 +61,7 @@
|
||||
"symbol": "tROP",
|
||||
"shortcut": "trop",
|
||||
"chainId": 3,
|
||||
"bip44": "m/44'/60'/0'/0",
|
||||
"bip44": "m/44'/60'/0'/0/a",
|
||||
"defaultGasPrice": 64,
|
||||
"defaultGasLimit": 21000,
|
||||
"defaultGasLimitTokens": 200000,
|
||||
@ -83,8 +83,8 @@
|
||||
"wss://ropsten1.trezor.io/geth"
|
||||
],
|
||||
"explorer": {
|
||||
"tx": "https://ropsten.etherscan.io/tx/",
|
||||
"address": "https://ropsten.etherscan.io/address/"
|
||||
"tx": "https://ropsten1.trezor.io/tx/",
|
||||
"address": "https://ropsten1.trezor.io/address/"
|
||||
},
|
||||
"hasSignVerify": true
|
||||
},
|
||||
|
@ -76,32 +76,31 @@ export const subscribe = (networkName: string): PromiseAction<void> => async (
|
||||
}
|
||||
};
|
||||
|
||||
export const onBlockMined = (
|
||||
payload: $ElementType<BlockchainBlock, 'payload'>
|
||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
export const onBlockMined = (payload: BlockchainBlock): PromiseAction<void> => async (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||
const { block } = payload;
|
||||
if (getState().router.location.state.network !== shortcut) return;
|
||||
|
||||
const { config } = getState().localStorage;
|
||||
const network = config.networks.find(c => c.shortcut === shortcut);
|
||||
if (!network) return;
|
||||
|
||||
switch (network.type) {
|
||||
case 'ethereum':
|
||||
await dispatch(EthereumBlockchainActions.onBlockMined(shortcut));
|
||||
await dispatch(EthereumBlockchainActions.onBlockMined(network));
|
||||
break;
|
||||
case 'ripple':
|
||||
await dispatch(RippleBlockchainActions.onBlockMined(shortcut, block));
|
||||
await dispatch(RippleBlockchainActions.onBlockMined(network));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export const onNotification = (
|
||||
payload: $ElementType<BlockchainNotification, 'payload'>
|
||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
export const onNotification = (payload: BlockchainNotification): PromiseAction<void> => async (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||
const { config } = getState().localStorage;
|
||||
const network = config.networks.find(c => c.shortcut === shortcut);
|
||||
@ -109,11 +108,10 @@ export const onNotification = (
|
||||
|
||||
switch (network.type) {
|
||||
case 'ethereum':
|
||||
// this is not working until blockchain-link will start support blockbook backends
|
||||
await dispatch(EthereumBlockchainActions.onNotification(payload));
|
||||
await dispatch(EthereumBlockchainActions.onNotification(payload, network));
|
||||
break;
|
||||
case 'ripple':
|
||||
await dispatch(RippleBlockchainActions.onNotification(payload));
|
||||
await dispatch(RippleBlockchainActions.onNotification(payload, network));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -122,9 +120,10 @@ export const onNotification = (
|
||||
|
||||
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
||||
// disconnect and remove Web3 websocket instance if exists
|
||||
export const onError = (
|
||||
payload: $ElementType<BlockchainError, 'payload'>
|
||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
export const onError = (payload: BlockchainError): PromiseAction<void> => async (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||
const { config } = getState().localStorage;
|
||||
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 NOTIFICATION from 'actions/constants/notification';
|
||||
import type { AsyncAction, Account, TrezorDevice, Network, Dispatch, GetState } from 'flowtype';
|
||||
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
||||
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
import { toDecimalAmount } from 'utils/formatUtils';
|
||||
import { enhanceAccount } from 'utils/accountUtils';
|
||||
|
||||
export type ImportAccountAction =
|
||||
| {
|
||||
@ -23,10 +22,7 @@ export type ImportAccountAction =
|
||||
|
||||
const findIndex = (accounts: Array<Account>, network: Network, device: TrezorDevice): number => {
|
||||
return accounts.filter(
|
||||
a =>
|
||||
a.imported === true &&
|
||||
a.network === network.shortcut &&
|
||||
a.deviceID === (device.features || {}).device_id
|
||||
a => a.imported === true && a.network === network.shortcut && a.deviceID === device.id
|
||||
).length;
|
||||
};
|
||||
|
||||
@ -35,113 +31,22 @@ export const importAddress = (
|
||||
network: Network,
|
||||
device: ?TrezorDevice
|
||||
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
if (!device) return;
|
||||
if (!device || !device.features) return;
|
||||
|
||||
dispatch({
|
||||
type: IMPORT.START,
|
||||
});
|
||||
|
||||
let payload;
|
||||
try {
|
||||
if (network.type === 'ethereum') {
|
||||
const account = await dispatch(
|
||||
BlockchainActions.discoverAccount(device, address, network.shortcut)
|
||||
);
|
||||
const response = await TrezorConnect.getAccountInfo({
|
||||
descriptor: address,
|
||||
coin: 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,
|
||||
},
|
||||
coin: network.shortcut,
|
||||
});
|
||||
|
||||
// handle TREZOR response error
|
||||
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) {
|
||||
// handle TREZOR response error
|
||||
if (!response.success) {
|
||||
dispatch({
|
||||
type: IMPORT.FAIL,
|
||||
error: error.message,
|
||||
error: response.payload.error,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
@ -149,9 +54,36 @@ export const importAddress = (
|
||||
payload: {
|
||||
variant: 'error',
|
||||
title: 'Import account error',
|
||||
message: error.message,
|
||||
message: response.payload.error,
|
||||
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,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -3,47 +3,14 @@
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
import BigNumber from 'bignumber.js';
|
||||
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 { EthereumAccount, BlockchainNotification } from 'trezor-connect';
|
||||
import type { Dispatch, GetState, PromiseAction, Network } from 'flowtype';
|
||||
import type { BlockchainNotification } from 'trezor-connect';
|
||||
import type { Token } from 'reducers/TokensReducer';
|
||||
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 (
|
||||
dispatch: Dispatch
|
||||
@ -103,9 +70,9 @@ export const subscribe = (network: string): PromiseAction<void> => async (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
const accounts: Array<string> = getState()
|
||||
const accounts = getState()
|
||||
.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({
|
||||
accounts,
|
||||
coin: network,
|
||||
@ -115,7 +82,7 @@ export const subscribe = (network: string): PromiseAction<void> => async (
|
||||
await dispatch(Web3Actions.initWeb3(network));
|
||||
};
|
||||
|
||||
export const onBlockMined = (network: string): PromiseAction<void> => async (
|
||||
export const onBlockMined = (network: Network): PromiseAction<void> => async (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
@ -123,56 +90,52 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (
|
||||
// check latest saved transaction blockhash against blockhheight
|
||||
|
||||
// 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: Array<any> = getState().accounts.filter(a => a.network === network);
|
||||
if (accounts.length > 0) {
|
||||
// find out which account changed
|
||||
const response = await TrezorConnect.ethereumGetAccountInfo({
|
||||
accounts,
|
||||
coin: network,
|
||||
});
|
||||
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
|
||||
|
||||
if (response.success) {
|
||||
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 }));
|
||||
// find out which account changed
|
||||
const bundle = accounts.map(a => ({ descriptor: a.descriptor, coin: network.shortcut }));
|
||||
const response = await TrezorConnect.getAccountInfo({ bundle });
|
||||
|
||||
// HACK: since blockbook can't work with smart contracts for now
|
||||
// try to update tokens balances added to this account using Web3
|
||||
dispatch(Web3Actions.updateAccountTokens(accounts[i]));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!response.success) return;
|
||||
|
||||
response.payload.forEach((info, i) => {
|
||||
dispatch(
|
||||
AccountsActions.update(mergeAccount(info, accounts[i], network, blockchain.block))
|
||||
);
|
||||
dispatch(Web3Actions.updateAccountTokens(accounts[i]));
|
||||
});
|
||||
};
|
||||
|
||||
export const onNotification = (
|
||||
payload: $ElementType<BlockchainNotification, 'payload'>
|
||||
payload: BlockchainNotification,
|
||||
network: Network
|
||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
const { notification } = payload;
|
||||
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
||||
if (!account) return;
|
||||
const { descriptor, tx } = payload.notification;
|
||||
const account = getState().accounts.find(a => a.descriptor === descriptor);
|
||||
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||
if (!account || !blockchain) return;
|
||||
dispatch({
|
||||
type: PENDING.ADD,
|
||||
payload: enhanceTransaction(account, tx, network),
|
||||
});
|
||||
|
||||
if (!notification.blockHeight) {
|
||||
dispatch({
|
||||
type: PENDING.ADD,
|
||||
payload: {
|
||||
...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 (
|
||||
|
@ -1,10 +1,8 @@
|
||||
/* @flow */
|
||||
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
import EthereumjsUtil from 'ethereumjs-util';
|
||||
import * as DISCOVERY from 'actions/constants/discovery';
|
||||
import * as BlockchainActions from 'actions/ethereum/BlockchainActions';
|
||||
|
||||
import { enhanceAccount } from 'utils/accountUtils';
|
||||
import type { PromiseAction, Dispatch, GetState, TrezorDevice, Network, Account } from 'flowtype';
|
||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
||||
|
||||
@ -13,49 +11,17 @@ export type DiscoveryStartAction = {
|
||||
networkType: 'ethereum',
|
||||
network: Network,
|
||||
device: TrezorDevice,
|
||||
publicKey: string,
|
||||
chainCode: string,
|
||||
basePath: Array<number>,
|
||||
};
|
||||
|
||||
// first iteration
|
||||
// generate public key for this account
|
||||
// start discovery process
|
||||
export const begin = (
|
||||
device: TrezorDevice,
|
||||
network: Network
|
||||
): 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,
|
||||
networkType: 'ethereum',
|
||||
network,
|
||||
device,
|
||||
publicKey: response.payload.publicKey,
|
||||
chainCode: response.payload.chainCode,
|
||||
basePath,
|
||||
};
|
||||
};
|
||||
): PromiseAction<DiscoveryStartAction> => async (): Promise<DiscoveryStartAction> => ({
|
||||
type: DISCOVERY.START,
|
||||
networkType: 'ethereum',
|
||||
network,
|
||||
device,
|
||||
});
|
||||
|
||||
export const discoverAccount = (
|
||||
device: TrezorDevice,
|
||||
@ -65,38 +31,31 @@ export const discoverAccount = (
|
||||
const network = config.networks.find(c => c.shortcut === discoveryProcess.network);
|
||||
if (!network) throw new Error('Discovery network not found');
|
||||
|
||||
const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`);
|
||||
const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex);
|
||||
const publicAddress: string = EthereumjsUtil.publicToAddress(
|
||||
derivedKey.publicKey,
|
||||
true
|
||||
).toString('hex');
|
||||
const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress);
|
||||
const { accountIndex } = discoveryProcess;
|
||||
const path = network.bip44.slice(0).replace('a', accountIndex.toString());
|
||||
|
||||
// TODO: check if address was created before
|
||||
const account = await dispatch(
|
||||
BlockchainActions.discoverAccount(device, ethAddress, network.shortcut)
|
||||
);
|
||||
const response = await TrezorConnect.getAccountInfo({
|
||||
device: {
|
||||
path: device.path,
|
||||
instance: device.instance,
|
||||
state: device.state,
|
||||
},
|
||||
path,
|
||||
// details: 'tokenBalances', TODO: load ERC20
|
||||
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';
|
||||
const empty = account.nonce <= 0 && account.balance === '0';
|
||||
// handle TREZOR response error
|
||||
if (!response.success) {
|
||||
throw new Error(response.payload.error);
|
||||
}
|
||||
|
||||
return {
|
||||
imported: false,
|
||||
return enhanceAccount(response.payload, {
|
||||
index: discoveryProcess.accountIndex,
|
||||
network: network.shortcut,
|
||||
deviceID: device.features ? device.features.device_id : '0',
|
||||
deviceState: device.state || '0',
|
||||
accountPath: path,
|
||||
descriptor: ethAddress,
|
||||
|
||||
balance: account.balance,
|
||||
availableBalance: account.balance,
|
||||
block: account.block,
|
||||
transactions: account.transactions,
|
||||
empty,
|
||||
|
||||
networkType: 'ethereum',
|
||||
nonce: account.nonce,
|
||||
};
|
||||
network,
|
||||
device,
|
||||
});
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import TrezorConnect from 'trezor-connect';
|
||||
import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
||||
import * as PENDING from 'actions/constants/pendingTx';
|
||||
import * as AccountsActions from 'actions/AccountsActions';
|
||||
import { toDecimalAmount } from 'utils/formatUtils';
|
||||
import { mergeAccount, enhanceTransaction } from 'utils/accountUtils';
|
||||
import { observeChanges } from 'reducers/utils';
|
||||
|
||||
import type { BlockchainNotification } from 'trezor-connect';
|
||||
@ -20,10 +20,10 @@ import type {
|
||||
export const subscribe = (network: string): PromiseAction<void> => async (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
): Promise<void> => {
|
||||
const accounts: Array<string> = getState()
|
||||
) => {
|
||||
const accounts = getState()
|
||||
.accounts.filter(a => a.network === network)
|
||||
.map(a => a.descriptor);
|
||||
.map(a => ({ descriptor: a.descriptor }));
|
||||
await TrezorConnect.blockchainSubscribe({
|
||||
accounts,
|
||||
coin: network,
|
||||
@ -47,161 +47,81 @@ export const getFeeLevels = (network: Network): PayloadAction<Array<BlockchainFe
|
||||
return blockchain.feeLevels;
|
||||
};
|
||||
|
||||
export const onBlockMined = (networkShortcut: string, block: number): PromiseAction<void> => async (
|
||||
export const onBlockMined = (network: Network): PromiseAction<void> => async (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
): 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 last update was more than 5 minutes ago
|
||||
const now = new Date().getTime();
|
||||
if (blockchain.feeTimestamp < now - 300000) {
|
||||
const feeRequest = await TrezorConnect.blockchainEstimateFee({
|
||||
coin: networkShortcut,
|
||||
request: {
|
||||
feeLevels: 'smart',
|
||||
},
|
||||
coin: network.shortcut,
|
||||
});
|
||||
if (feeRequest.success && observeChanges(blockchain.feeLevels, feeRequest.payload)) {
|
||||
// check if downloaded fee levels are different
|
||||
dispatch({
|
||||
type: BLOCKCHAIN.UPDATE_FEE,
|
||||
shortcut: networkShortcut,
|
||||
feeLevels: feeRequest.payload,
|
||||
shortcut: network.shortcut,
|
||||
feeLevels: feeRequest.payload.levels.map(l => ({
|
||||
name: 'Normal',
|
||||
value: l.feePerUnit,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
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
|
||||
// we don't have info about new transactions for the account since last update.
|
||||
// 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,
|
||||
});
|
||||
const bundle = accounts.map(a => ({ descriptor: a.descriptor, coin: network.shortcut }));
|
||||
const response = await TrezorConnect.getAccountInfo({ bundle });
|
||||
|
||||
if (!response.success) return;
|
||||
if (!response.success) return;
|
||||
|
||||
const updatedAccount = response.payload;
|
||||
|
||||
// new txs
|
||||
dispatch(
|
||||
AccountsActions.update({
|
||||
...account,
|
||||
balance: toDecimalAmount(updatedAccount.balance, network.decimals),
|
||||
availableBalance: toDecimalAmount(
|
||||
updatedAccount.availableBalance,
|
||||
network.decimals
|
||||
),
|
||||
block: updatedAccount.block,
|
||||
sequence: updatedAccount.sequence,
|
||||
})
|
||||
);
|
||||
}
|
||||
response.payload.forEach((info, i) => {
|
||||
dispatch(
|
||||
AccountsActions.update(mergeAccount(info, accounts[i], network, blockchain.block))
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const onNotification = (
|
||||
payload: $ElementType<BlockchainNotification, 'payload'>
|
||||
payload: BlockchainNotification,
|
||||
network: Network
|
||||
): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
const { notification } = payload;
|
||||
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
||||
if (!account) return;
|
||||
const { network } = getState().selectedAccount;
|
||||
if (!network) return; // flowtype fallback
|
||||
const { descriptor, tx } = payload.notification;
|
||||
const account = getState().accounts.find(a => a.descriptor === descriptor);
|
||||
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||
if (!account || !blockchain) return;
|
||||
|
||||
if (!notification.blockHeight) {
|
||||
if (!tx.blockHeight) {
|
||||
dispatch({
|
||||
type: PENDING.ADD,
|
||||
payload: {
|
||||
...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),
|
||||
},
|
||||
payload: enhanceTransaction(account, tx, network),
|
||||
});
|
||||
|
||||
// todo: replace "send success" notification with link to explorer
|
||||
} else {
|
||||
dispatch({
|
||||
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
|
||||
// therefore we need to find target account and update data for it as well
|
||||
const accountsToUpdate = [account];
|
||||
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) {
|
||||
const updatedAccount = response.payload;
|
||||
const empty = updatedAccount.sequence <= 0 && updatedAccount.balance === '0';
|
||||
dispatch(
|
||||
AccountsActions.update({
|
||||
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,
|
||||
})
|
||||
);
|
||||
}
|
||||
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))
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
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 { Discovery } from 'reducers/DiscoveryReducer';
|
||||
@ -35,16 +35,13 @@ export const discoverAccount = (
|
||||
const { accountIndex } = discoveryProcess;
|
||||
const path = network.bip44.slice(0).replace('a', accountIndex.toString());
|
||||
|
||||
const response = await TrezorConnect.rippleGetAccountInfo({
|
||||
const response = await TrezorConnect.getAccountInfo({
|
||||
device: {
|
||||
path: device.path,
|
||||
instance: device.instance,
|
||||
state: device.state,
|
||||
},
|
||||
account: {
|
||||
path,
|
||||
block: 0,
|
||||
},
|
||||
path,
|
||||
keepSession: true, // acquire and hold session
|
||||
useEmptyPassphrase: device.useEmptyPassphrase,
|
||||
coin: network.shortcut,
|
||||
@ -55,26 +52,9 @@ export const discoverAccount = (
|
||||
throw new Error(response.payload.error);
|
||||
}
|
||||
|
||||
const account = response.payload;
|
||||
const empty = account.sequence <= 0 && account.balance === '0';
|
||||
|
||||
return {
|
||||
imported: false,
|
||||
return enhanceAccount(response.payload, {
|
||||
index: discoveryProcess.accountIndex,
|
||||
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),
|
||||
};
|
||||
network,
|
||||
device,
|
||||
});
|
||||
};
|
||||
|
@ -6,13 +6,13 @@ import * as ACCOUNT from 'actions/constants/account';
|
||||
|
||||
import type { Action, TrezorDevice } from 'flowtype';
|
||||
|
||||
type AccountCommon = {
|
||||
type AccountCommon = {|
|
||||
+imported: boolean,
|
||||
+index: number,
|
||||
+network: string, // network id (shortcut)
|
||||
+deviceID: 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
|
||||
|
||||
balance: string,
|
||||
@ -21,22 +21,31 @@ type AccountCommon = {
|
||||
empty: boolean, // account without transactions
|
||||
|
||||
transactions: number, // deprecated
|
||||
};
|
||||
|};
|
||||
|
||||
export type Account =
|
||||
| (AccountCommon & {
|
||||
networkType: 'ethereum',
|
||||
nonce: number,
|
||||
})
|
||||
| (AccountCommon & {
|
||||
networkType: 'ripple',
|
||||
sequence: number,
|
||||
reserve: string,
|
||||
})
|
||||
| (AccountCommon & {
|
||||
networkType: 'bitcoin',
|
||||
addressIndex: number,
|
||||
});
|
||||
| {|
|
||||
...AccountCommon,
|
||||
...{|
|
||||
networkType: 'ethereum',
|
||||
nonce: string,
|
||||
|},
|
||||
|}
|
||||
| {|
|
||||
...AccountCommon,
|
||||
...{|
|
||||
networkType: 'ripple',
|
||||
sequence: number,
|
||||
reserve: string,
|
||||
|},
|
||||
|}
|
||||
| {|
|
||||
...AccountCommon,
|
||||
...{|
|
||||
networkType: 'bitcoin',
|
||||
addressIndex: number,
|
||||
|},
|
||||
|};
|
||||
|
||||
export type State = Array<Account>;
|
||||
|
||||
@ -51,14 +60,12 @@ export const findDeviceAccounts = (
|
||||
return state.filter(
|
||||
addr =>
|
||||
(addr.deviceState === device.state ||
|
||||
(addr.imported && addr.deviceID === (device.features || {}).device_id)) &&
|
||||
(addr.imported && addr.deviceID === device.id)) &&
|
||||
addr.network === network
|
||||
);
|
||||
}
|
||||
return state.filter(
|
||||
addr =>
|
||||
addr.deviceState === device.state ||
|
||||
(addr.imported && addr.deviceID === (device.features || {}).device_id)
|
||||
addr => addr.deviceState === device.state || (addr.imported && addr.deviceID === device.id)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { BLOCKCHAIN as BLOCKCHAIN_EVENT } from 'trezor-connect';
|
||||
import * as BLOCKCHAIN_ACTION from 'actions/constants/blockchain';
|
||||
|
||||
import type { Action } from 'flowtype';
|
||||
import type { BlockchainConnect, BlockchainError, BlockchainBlock } from 'trezor-connect';
|
||||
import type { BlockchainInfo, BlockchainError, BlockchainBlock } from 'trezor-connect';
|
||||
|
||||
export type BlockchainFeeLevel = {
|
||||
name: string,
|
||||
@ -50,16 +50,15 @@ const onStartSubscribe = (state: State, shortcut: string): State => {
|
||||
]);
|
||||
};
|
||||
|
||||
const onConnect = (state: State, action: BlockchainConnect): State => {
|
||||
const shortcut = action.payload.coin.shortcut.toLowerCase();
|
||||
const onConnect = (state: State, info: BlockchainInfo): State => {
|
||||
const shortcut = info.coin.shortcut.toLowerCase();
|
||||
const network = state.find(b => b.shortcut === shortcut);
|
||||
const { info } = action.payload;
|
||||
if (network) {
|
||||
const others = state.filter(b => b !== network);
|
||||
return others.concat([
|
||||
{
|
||||
...network,
|
||||
block: info.block,
|
||||
block: info.blockHeight,
|
||||
connected: true,
|
||||
connecting: false,
|
||||
reconnectionAttempts: 0,
|
||||
@ -73,15 +72,15 @@ const onConnect = (state: State, action: BlockchainConnect): State => {
|
||||
connected: true,
|
||||
connecting: false,
|
||||
reconnectionAttempts: 0,
|
||||
block: info.block,
|
||||
block: info.blockHeight,
|
||||
feeTimestamp: 0,
|
||||
feeLevels: [],
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const onError = (state: State, action: BlockchainError): State => {
|
||||
const shortcut = action.payload.coin.shortcut.toLowerCase();
|
||||
const onError = (state: State, payload: BlockchainError): State => {
|
||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||
const network = state.find(b => b.shortcut === shortcut);
|
||||
if (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 shortcut = action.payload.coin.shortcut.toLowerCase();
|
||||
const onBlock = (state: State, payload: BlockchainBlock): State => {
|
||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||
const network = state.find(b => b.shortcut === shortcut);
|
||||
if (network) {
|
||||
const others = state.filter(b => b !== network);
|
||||
return others.concat([
|
||||
{
|
||||
...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:
|
||||
return onStartSubscribe(state, action.shortcut);
|
||||
case BLOCKCHAIN_EVENT.CONNECT:
|
||||
return onConnect(state, action);
|
||||
return onConnect(state, action.payload);
|
||||
case BLOCKCHAIN_EVENT.ERROR:
|
||||
return onError(state, action);
|
||||
return onError(state, action.payload);
|
||||
case BLOCKCHAIN_EVENT.BLOCK:
|
||||
return onBlock(state, action);
|
||||
return onBlock(state, action.payload);
|
||||
case BLOCKCHAIN_ACTION.UPDATE_FEE:
|
||||
return updateFee(state, action.shortcut, action.feeLevels);
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
/* @flow */
|
||||
|
||||
import HDKey from 'hdkey';
|
||||
|
||||
import * as DISCOVERY from 'actions/constants/discovery';
|
||||
import * as ACCOUNT from 'actions/constants/account';
|
||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||
@ -27,10 +25,6 @@ export type Discovery = {
|
||||
waitingForBlockchain: boolean,
|
||||
fwNotSupported: 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>;
|
||||
@ -46,10 +40,6 @@ const defaultDiscovery: Discovery = {
|
||||
waitingForBlockchain: false,
|
||||
fwNotSupported: false,
|
||||
fwOutdated: false,
|
||||
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
hdKey: null,
|
||||
};
|
||||
|
||||
const findIndex = (state: State, network: string, deviceState: string): number =>
|
||||
@ -63,18 +53,6 @@ const start = (state: State, action: DiscoveryStartAction): State => {
|
||||
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 index: number = findIndex(state, action.network.shortcut, deviceState);
|
||||
if (index >= 0) {
|
||||
@ -202,15 +180,8 @@ export default function discovery(state: State = initialState, action: Action):
|
||||
return notSupported(state, action);
|
||||
case DISCOVERY.FROM_STORAGE:
|
||||
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 {
|
||||
...d,
|
||||
hdKey,
|
||||
interrupted: false,
|
||||
waitingForDevice: false,
|
||||
waitingForBlockchain: false,
|
||||
|
Loading…
Reference in New Issue
Block a user