1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-24 09:18:09 +00:00

Merge branch 'master' into unit-tests

This commit is contained in:
Vladimir Volek 2018-09-20 14:39:37 +02:00 committed by GitHub
commit 70d7c73a77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1992 additions and 990 deletions

View File

@ -7,6 +7,7 @@
.*/node_modules/redux/.*
.*/node_modules/react-router/.*
.*/node_modules/react-router-redux/.*
.*/node_modules/oboe/test/.*
.*/_old/.*
.*/public/.*
@ -41,4 +42,5 @@ module.name_mapper='^views' -> '<PROJECT_ROOT>/src/views'
module.name_mapper='^data' -> '<PROJECT_ROOT>/src/data'
module.name_mapper='^services' -> '<PROJECT_ROOT>/src/services'
module.name_mapper='^support' -> '<PROJECT_ROOT>/src/support'
module.name_mapper='^public' -> '<PROJECT_ROOT>/public'
module.system=haste

50
LICENSE.md Normal file
View File

@ -0,0 +1,50 @@
# TREZOR REFERENCE SOURCE LICENSE (T-RSL)
This license governs use of the accompanying software. If you use the software,
you accept this license. If you do not accept the license, do not use the
software.
## 1. Definitions
The terms "reproduce," "reproduction" and "distribution" have the same meaning
here as under U.S. copyright law.
"You" means the licensee of the software.
"Your company" means the company you worked for when you downloaded the
software.
"Reference use" means use of the software within your company as a reference,
in read only form, for the sole purposes of debugging your products,
maintaining your products, or enhancing the interoperability of your products
with the software, and specifically excludes the right to distribute the
software outside of your company.
"Licensed patents" means any Licensor patent claims which read directly on the
software as distributed by the Licensor under this license.
## 2. Grant of Rights
(A) Copyright Grant - Subject to the terms of this license, the Licensor grants
you a non-transferable, non-exclusive, worldwide, royalty-free copyright
license to reproduce the software for reference use.
(B) Patent Grant - Subject to the terms of this license, the Licensor grants
you a non-transferable, non-exclusive, worldwide, royalty-free patent license
under licensed patents for reference use.
## 3. Limitations
(A) No Trademark License - This license does not grant you any rights to use
the Licensor's name, logo, or trademarks.
(B) If you begin patent litigation against the Licensor over patents that you
think may apply to the software (including a cross-claim or counterclaim in
a lawsuit), your license to the software ends automatically.
(C) The software is licensed "as-is." You bear the risk of using it. The
Licensor gives no express warranties, guarantees or conditions. You may have
additional consumer rights under your local laws which this license cannot
change. To the extent permitted under your local laws, the Licensor excludes
the implied warranties of merchantability, fitness for a particular purpose and
non-infringement.

View File

@ -49,6 +49,7 @@
"react-router-redux": "next",
"react-scale-text": "^1.2.2",
"react-select": "2.0.0",
"react-sticky-el": "^1.0.20",
"react-transition-group": "^2.2.1",
"redbox-react": "^1.6.0",
"redux": "4.0.0",
@ -58,8 +59,8 @@
"styled-components": "^3.3.3",
"styled-media-query": "^2.0.2",
"styled-normalize": "^8.0.0",
"trezor-connect": "5.0.30",
"web3": "^0.19.0",
"trezor-connect": "^5.0.32",
"web3": "1.0.0-beta.35",
"webpack": "^4.16.3",
"webpack-bundle-analyzer": "^2.13.1",
"whatwg-fetch": "^2.0.4",
@ -91,8 +92,6 @@
"file-loader": "1.1.11",
"flow-bin": "0.72.0",
"jest": "^23.4.2",
"less": "^3.0.1",
"less-loader": "4.1.0",
"stylelint": "^8.0.0",
"stylelint-config-standard": "^18.2.0",
"stylelint-config-styled-components": "^0.1.1",

View File

@ -3,21 +3,15 @@
{
"name": "Ethereum",
"symbol": "ETH",
"network": "ethereum",
"network": "eth",
"bip44": "m/44'/60'/0'/0",
"chainId": 1,
"defaultGasPrice": 64,
"defaultGasLimit": 21000,
"defaultGasLimitTokens": 200000,
"tokens": "./data/ethereumTokens.json",
"backends": [
{
"name": "TREZOR Wallet - Ethereum",
"urls": [
"https://mainnet.infura.io/QGyVKozSUEh2YhL4s2G4",
"http://88.208.115.69"
]
}
"web3": [
"wss://eth2.trezor.io/geth"
],
"explorer": {
"tx": "https://etherscan.io/tx/",
@ -27,21 +21,15 @@
{
"name": "Ethereum Classic",
"symbol": "ETC",
"network": "ethereum-classic",
"network": "etc",
"chainId": 61,
"bip44": "m/44'/61'/0'/0",
"defaultGasPrice": 64,
"defaultGasLimit": 21000,
"defaultGasLimitTokens": 200000,
"tokens": "./data/ethereumClassicTokens.json",
"backends": [
{
"name": "TREZOR Wallet - Ethereum",
"urls": [
"https://etc-geth.0xinfra.com/",
"https://mew.epool.io/"
]
}
"web3": [
"wss://etc2.trezor.io/geth"
],
"explorer": {
"tx": "https://gastracker.io/tx/",
@ -50,60 +38,31 @@
},
{
"name": "Ethereum Ropsten",
"symbol": "tETH",
"network": "ropsten",
"symbol": "tROP",
"network": "trop",
"chainId": 3,
"bip44": "m/44'/60'/0'/0",
"defaultGasPrice": 64,
"defaultGasLimit": 21000,
"defaultGasLimitTokens": 200000,
"tokens": "./data/ropstenTokens.json",
"backends": [
{
"name": "TREZOR Wallet - Ethereum",
"urls": [
"https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4",
"http://10.34.2.5:8545"
]
}
"web3": [
"wss://ropsten1.trezor.io/geth"
],
"explorer": {
"tx": "https://ropsten.etherscan.io/tx/",
"address": "https://ropsten.etherscan.io/address/"
}
},
{
"name": "Ethereum Rinkeby",
"symbol": "tETH",
"network": "rinkeby",
"chainId": 4,
"bip44": "m/44'/61'/0'/0",
"defaultGasPrice": 64,
"defaultGasLimit": 21000,
"defaultGasLimitTokens": 200000,
"tokens": "./data/rinkebyTokens.json",
"backends": [
{
"name": "TREZOR Wallet - Ethereum",
"urls": [
"https://rinkeby.infura.io/QGyVKozSUEh2YhL4s2G4"
]
}
],
"explorer": {
"tx": "https://rinkeby.etherscan.io/tx/",
"address": "https://rinkeby.etherscan.io/address/"
}
}
],
"fiatValueTickers": [
{
"network": "ethereum",
"network": "eth",
"url": "https://api.coinmarketcap.com/v1/ticker/ethereum/"
},
{
"network": "ethereum-classic",
"network": "etc",
"url": "https://api.coinmarketcap.com/v1/ticker/ethereum-classic/"
}
],

View File

@ -4,5 +4,17 @@
"name": "PLASMA",
"symbol" :"PLASMA",
"decimals": 6
},
{
"address": "0x58cda554935e4a1f2acbe15f8757400af275e084",
"name": "Trezor01",
"symbol": "T01",
"decimals": 0
},
{
"address": "0xa04761a776af2bed654a041430a063fd9d20fad4",
"name": "Trezor13",
"symbol": "T013",
"decimals": 13
}
]

View File

@ -2,7 +2,7 @@
import * as ACCOUNT from 'actions/constants/account';
import type { Action, TrezorDevice } from 'flowtype';
import type { State } from 'reducers/AccountsReducer';
import type { Account, State } from 'reducers/AccountsReducer';
export type AccountFromStorageAction = {
type: typeof ACCOUNT.FROM_STORAGE,
@ -11,11 +11,12 @@ export type AccountFromStorageAction = {
export type AccountCreateAction = {
type: typeof ACCOUNT.CREATE,
device: TrezorDevice,
network: string,
index: number,
path: Array<number>,
address: string
payload: Account,
}
export type AccountUpdateAction = {
type: typeof ACCOUNT.UPDATE,
payload: Account,
}
export type AccountSetBalanceAction = {
@ -36,9 +37,10 @@ export type AccountSetNonceAction = {
export type AccountAction =
AccountFromStorageAction
| AccountCreateAction
| AccountSetBalanceAction
| AccountSetNonceAction;
| AccountCreateAction
| AccountUpdateAction
| AccountSetBalanceAction
| AccountSetNonceAction;
export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => ({
type: ACCOUNT.SET_BALANCE,
@ -55,3 +57,8 @@ export const setNonce = (address: string, network: string, deviceState: string,
deviceState,
nonce,
});
export const update = (account: Account): Action => ({
type: ACCOUNT.UPDATE,
payload: account
});

View File

@ -0,0 +1,210 @@
/* @flow */
import Web3 from 'web3';
import HDKey from 'hdkey';
import EthereumjsUtil from 'ethereumjs-util';
import EthereumjsUnits from 'ethereumjs-units';
import EthereumjsTx from 'ethereumjs-tx';
import TrezorConnect from 'trezor-connect';
import BigNumber from 'bignumber.js';
import type { EstimateGasOptions } from 'web3';
import type { TransactionStatus, TransactionReceipt } from 'web3';
import { strip } from 'utils/ethUtils';
import * as BLOCKCHAIN from 'actions/constants/blockchain';
import * as WEB3 from 'actions/constants/web3';
import * as PENDING from 'actions/constants/pendingTx';
import * as AccountsActions from './AccountsActions';
import * as Web3Actions from './Web3Actions';
import type {
TrezorDevice,
Dispatch,
GetState,
Action,
AsyncAction,
PromiseAction,
ThunkAction,
} from 'flowtype';
import type { EthereumAccount } from 'trezor-connect';
import type { Token } from 'reducers/TokensReducer';
import type { NetworkToken } from 'reducers/LocalStorageReducer';
export type BlockchainAction = {
type: typeof BLOCKCHAIN.READY,
}
export const discoverAccount = (device: TrezorDevice, address: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch, getState: GetState): Promise<EthereumAccount> => {
// get data from connect
// Temporary disabled, enable after trezor-connect@5.0.32 release
const txs = await TrezorConnect.ethereumGetAccountInfo({
account: {
address,
block: 0,
transactions: 0,
balance: "0",
nonce: 0
},
coin: network,
});
if (!txs.success) {
throw new Error(txs.payload.error);
}
// blockbook web3 fallback
const web3account = await dispatch( Web3Actions.discoverAccount(address, network) );
// return { transactions: txs.payload, ...web3account };
return {
address,
transactions: txs.payload.transactions,
block: txs.payload.block,
balance: web3account.balance,
nonce: web3account.nonce,
};
};
export const getTokenInfo = (input: string, network: string): PromiseAction<NetworkToken> => async (dispatch: Dispatch, getState: GetState): Promise<NetworkToken> => {
return await dispatch( Web3Actions.getTokenInfo(input, network) );
}
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
return await dispatch( Web3Actions.getTokenBalance(token) );
}
export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAction<BigNumber> => async (dispatch: Dispatch, getState: GetState): Promise<BigNumber> => {
try {
const gasPrice = await dispatch( Web3Actions.getCurrentGasPrice(network) );
return new BigNumber(gasPrice);
} catch (error) {
return new BigNumber(defaultGasPrice);
}
}
export const estimateGasLimit = (network: string, data: string, value: string, gasPrice: string): PromiseAction<number> => async (dispatch: Dispatch, getState: GetState): Promise<number> => {
return await dispatch( Web3Actions.estimateGasLimit(network, { to: '', data, value, gasPrice }) );
}
export const onBlockMined = (coinInfo: any): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
// incoming "coinInfo" from TrezorConnect is CoinInfo | EthereumNetwork type
const network: string = coinInfo.shortcut.toLowerCase();
// try to resolve pending transactions
await dispatch( Web3Actions.resolvePendingTransactions(network) );
await dispatch( Web3Actions.updateGasPrice(network) );
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,
});
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
dispatch( AccountsActions.update( { ...accounts[i], block: a.block }) );
// 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]) );
}
});
}
}
}
// not used for now, waiting for fix in blockbook
export const onNotification = (payload: any): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
// this event can be triggered multiple times
// 1. check if pair [txid + address] is already in reducer
const network: string = payload.coin.shortcut.toLowerCase();
const address: string = EthereumjsUtil.toChecksumAddress(payload.tx.address);
const txInfo = await dispatch( Web3Actions.getPendingInfo(network, payload.tx.txid) );
// const exists = getState().pending.filter(p => p.id === payload.tx.txid && p.address === address);
const exists = getState().pending.filter(p => {
return p.address === address;
});
if (exists.length < 1) {
if (txInfo) {
dispatch({
type: PENDING.ADD,
payload: {
type: 'send',
id: payload.tx.txid,
network,
currency: "tETH",
amount: txInfo.value,
total: "0",
tx: {},
nonce: txInfo.nonce,
address,
rejected: false
}
});
} else {
// tx info not found (yet?)
// dispatch({
// type: PENDING.ADD_UNKNOWN,
// payload: {
// network,
// ...payload.tx,
// }
// });
}
}
}
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const addresses: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.address);
// $FlowIssue: trezor-connect@5.0.32
return await TrezorConnect.blockchainSubscribe({
// accounts: addresses,
accounts: [],
coin: network
});
}
// Conditionally subscribe to blockchain backend
// called after TrezorConnect.init successfully emits TRANSPORT.START event
// checks if there are discovery processes loaded from LocalStorage
// if so starts subscription to proper networks
export const init = (): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
if (getState().discovery.length > 0) {
// get unique networks
const networks: Array<string> = [];
getState().discovery.forEach(discovery => {
if (networks.indexOf(discovery.network) < 0) {
networks.push(discovery.network);
}
});
// subscribe
for (let i = 0; i < networks.length; i++) {
await dispatch( subscribe(networks[i]) );
}
}
// continue wallet initialization
dispatch({
type: BLOCKCHAIN.READY
});
}
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
// disconnect and remove Web3 webscocket instance if exists
export const error = (payload: any): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
dispatch( Web3Actions.disconnect(payload.coin) );
}

View File

@ -6,12 +6,12 @@ import * as DISCOVERY from 'actions/constants/discovery';
import * as ACCOUNT from 'actions/constants/account';
import * as NOTIFICATION from 'actions/constants/notification';
import type {
ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice,
ThunkAction, AsyncAction, PromiseAction, Action, GetState, Dispatch, TrezorDevice,
} from 'flowtype';
import type { Discovery, State } from 'reducers/DiscoveryReducer';
import * as AccountsActions from './AccountsActions';
import { getNonceAsync, getBalanceAsync } from './Web3Actions';
import * as BlockchainActions from './BlockchainActions';
import { setBalance as setTokenBalance } from './TokenActions';
export type DiscoveryStartAction = {
@ -24,7 +24,7 @@ export type DiscoveryStartAction = {
}
export type DiscoveryWaitingAction = {
type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BACKEND,
type: typeof DISCOVERY.WAITING_FOR_DEVICE | typeof DISCOVERY.WAITING_FOR_BLOCKCHAIN,
device: TrezorDevice,
network: string
}
@ -44,147 +44,9 @@ export type DiscoveryAction = {
type: typeof DISCOVERY.FROM_STORAGE,
payload: State
} | DiscoveryStartAction
| DiscoveryWaitingAction
| DiscoveryStopAction
| DiscoveryCompleteAction;
// Because start() is calling begin() and begin() is calling start() one of them must be declared first
// otherwise eslint will start complaining
let begin;
const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { completed } = discoveryProcess;
discoveryProcess.completed = false;
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 { network } = discoveryProcess;
// TODO: check if address was created before
// verify address with TREZOR
const verifyAddress = await TrezorConnect.ethereumGetAddress({
device: {
path: device.path,
instance: device.instance,
state: device.state,
},
path,
showOnTrezor: false,
keepSession: true,
useEmptyPassphrase: !device.instance,
});
if (discoveryProcess.interrupted) return;
// TODO: with block-book (Martin)
// const discoveryA = await TrezorConnect.accountDiscovery({
// device: {
// path: device.path,
// instance: device.instance,
// state: device.state
// },
// });
// if (discoveryProcess.interrupted) return;
if (verifyAddress && verifyAddress.success) {
//const trezorAddress: string = '0x' + verifyAddress.payload.address;
const trezorAddress: string = EthereumjsUtil.toChecksumAddress(verifyAddress.payload.address);
if (trezorAddress !== ethAddress) {
// throw inconsistent state error
console.warn('Inconsistent state', trezorAddress, ethAddress);
dispatch({
type: NOTIFICATION.ADD,
payload: {
type: 'error',
title: 'Address validation error',
message: `Addresses are different. TREZOR: ${trezorAddress} HDKey: ${ethAddress}`,
cancelable: true,
actions: [
{
label: 'Try again',
callback: () => {
dispatch(start(device, discoveryProcess.network));
},
},
],
},
});
return;
}
} else {
// handle TREZOR communication error
dispatch({
type: NOTIFICATION.ADD,
payload: {
type: 'error',
title: 'Address validation error',
message: verifyAddress.payload.error,
cancelable: true,
actions: [
{
label: 'Try again',
callback: () => {
dispatch(start(device, discoveryProcess.network));
},
},
],
},
});
return;
}
const web3instance = getState().web3.find(w3 => w3.network === network);
if (!web3instance) return;
const balance = await getBalanceAsync(web3instance.web3, ethAddress);
if (discoveryProcess.interrupted) return;
const nonce: number = await getNonceAsync(web3instance.web3, ethAddress);
if (discoveryProcess.interrupted) return;
const addressIsEmpty = nonce < 1 && !balance.greaterThan(0);
if (!addressIsEmpty || (addressIsEmpty && completed) || (addressIsEmpty && discoveryProcess.accountIndex === 0)) {
dispatch({
type: ACCOUNT.CREATE,
device,
network,
index: discoveryProcess.accountIndex,
path,
address: ethAddress,
});
dispatch(
AccountsActions.setBalance(ethAddress, network, device.state || 'undefined', web3instance.web3.fromWei(balance.toString(), 'ether')),
);
dispatch(AccountsActions.setNonce(ethAddress, network, device.state || 'undefined', nonce));
if (!completed) { dispatch(discoverAccount(device, discoveryProcess)); }
}
if (addressIsEmpty) {
// release acquired sesssion
await TrezorConnect.getFeatures({
device: {
path: device.path,
instance: device.instance,
state: device.state,
},
keepSession: false,
useEmptyPassphrase: !device.instance,
});
if (discoveryProcess.interrupted) return;
dispatch({
type: DISCOVERY.COMPLETE,
device,
network,
});
}
};
| DiscoveryWaitingAction
| DiscoveryStopAction
| DiscoveryCompleteAction;
export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const selected = getState().wallet.selectedDevice;
@ -203,26 +65,9 @@ export const start = (device: TrezorDevice, network: string, ignoreCompleted?: b
return;
}
const web3 = getState().web3.find(w3 => w3.network === network);
if (!web3) {
console.error('Start discovery: Web3 does not exist', network);
return;
}
if (!web3.web3.currentProvider.isConnected()) {
console.error('Start discovery: Web3 is not connected', network);
dispatch({
type: DISCOVERY.WAITING_FOR_BACKEND,
device,
network,
});
return;
}
const { discovery }: { discovery: State } = getState();
const discovery: State = getState().discovery;
const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network);
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
dispatch({
type: DISCOVERY.WAITING_FOR_DEVICE,
@ -232,15 +77,25 @@ export const start = (device: TrezorDevice, network: string, ignoreCompleted?: b
return;
}
const blockchain = getState().blockchain.find(b => b.name === network);
if (blockchain && !blockchain.connected && (!discoveryProcess || !discoveryProcess.completed)) {
dispatch({
type: DISCOVERY.WAITING_FOR_BLOCKCHAIN,
device,
network,
});
return;
}
if (!discoveryProcess) {
dispatch(begin(device, network));
dispatch(begin(device, network))
} else if (discoveryProcess.completed && !ignoreCompleted) {
dispatch({
type: DISCOVERY.COMPLETE,
device,
network,
});
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) {
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) {
// discovery cycle was interrupted
// start from beginning
dispatch(begin(device, network));
@ -249,7 +104,10 @@ export const start = (device: TrezorDevice, network: string, ignoreCompleted?: b
}
};
begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
// first iteration
// generate public key for this account
// start discovery process
const begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { config } = getState().localStorage;
const coinToDiscover = config.coins.find(c => c.network === network);
if (!coinToDiscover) return;
@ -272,10 +130,8 @@ begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch:
useEmptyPassphrase: !device.instance,
});
// handle TREZOR response error
// handle TREZOR response error
if (!response.success) {
// TODO: check message
console.warn('DISCOVERY ERROR', response);
dispatch({
type: NOTIFICATION.ADD,
payload: {
@ -312,14 +168,113 @@ begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch:
basePath,
});
// next iteration
dispatch(start(device, network));
};
const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { completed } = discoveryProcess;
discoveryProcess.completed = false;
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 { network } = discoveryProcess;
// TODO: check if address was created before
try {
const account = await dispatch( BlockchainActions.discoverAccount(device, ethAddress, network) );
if (discoveryProcess.interrupted) return;
// const accountIsEmpty = account.transactions <= 0 && account.nonce <= 0 && account.balance === '0';
const accountIsEmpty = account.nonce <= 0 && account.balance === '0';
if (!accountIsEmpty || (accountIsEmpty && completed) || (accountIsEmpty && discoveryProcess.accountIndex === 0)) {
dispatch({
type: ACCOUNT.CREATE,
payload: {
index: discoveryProcess.accountIndex,
loaded: true,
network,
deviceID: device.features ? device.features.device_id : '0',
deviceState: device.state || '0',
addressPath: path,
address: ethAddress,
balance: account.balance,
nonce: account.nonce,
block: account.block,
transactions: account.transactions
}
});
}
if (accountIsEmpty) {
dispatch( finish(device, discoveryProcess) );
} else {
if (!completed) { dispatch( discoverAccount(device, discoveryProcess) ); }
}
} catch (error) {
dispatch({
type: DISCOVERY.STOP,
device
});
dispatch({
type: NOTIFICATION.ADD,
payload: {
type: 'error',
title: 'Account discovery error',
message: error.message,
cancelable: true,
actions: [
{
label: 'Try again',
callback: () => {
dispatch(start(device, discoveryProcess.network));
},
},
],
},
});
}
};
const finish = (device: TrezorDevice, discoveryProcess: Discovery): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
await TrezorConnect.getFeatures({
device: {
path: device.path,
instance: device.instance,
state: device.state,
},
keepSession: false,
useEmptyPassphrase: !device.instance,
});
await dispatch( BlockchainActions.subscribe(discoveryProcess.network) );
if (discoveryProcess.interrupted) return;
dispatch({
type: DISCOVERY.COMPLETE,
device,
network: discoveryProcess.network,
});
}
export const reconnect = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
await dispatch(BlockchainActions.subscribe(network));
dispatch(restore());
}
export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const selected = getState().wallet.selectedDevice;
if (selected && selected.connected && selected.features) {
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && d.waitingForDevice);
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.deviceState === selected.state && (d.interrupted || d.waitingForDevice || d.waitingForBlockchain));
if (discoveryProcess) {
dispatch(start(selected, discoveryProcess.network));
}

View File

@ -24,14 +24,14 @@ export const onPinSubmit = (value: string): Action => {
};
};
export const onPassphraseSubmit = (/* passphrase: string */): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
// const resp = await TrezorConnect.uiResponse({
// type: UI.RECEIVE_PASSPHRASE,
// payload: {
// value: passphrase,
// save: true,
// },
// });
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
const resp = await TrezorConnect.uiResponse({
type: UI.RECEIVE_PASSPHRASE,
payload: {
value: passphrase,
save: true,
},
});
dispatch({
type: MODAL.CLOSE,

View File

@ -7,6 +7,9 @@ import type { State, PendingTx } from 'reducers/PendingTxReducer';
export type PendingTxAction = {
type: typeof PENDING.FROM_STORAGE,
payload: State
} | {
type: typeof PENDING.ADD,
payload: PendingTx
} | {
type: typeof PENDING.TX_RESOLVED,
tx: PendingTx,

View File

@ -47,8 +47,7 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
|| prevState.accounts !== state.accounts
|| prevState.discovery !== state.discovery
|| prevState.tokens !== state.tokens
|| prevState.pending !== state.pending
|| prevState.web3 !== state.web3) {
|| prevState.pending !== state.pending) {
if (locationChange) {
// dispose current account view
dispatch(dispose());
@ -59,7 +58,6 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
const discovery = stateUtils.getDiscoveryProcess(state);
const tokens = stateUtils.getAccountTokens(state, account);
const pending = stateUtils.getAccountPendingTx(state.pending, account);
const web3 = stateUtils.getWeb3(state);
const payload: $ElementType<State, 'selectedAccount'> = {
location: location.pathname,
@ -68,7 +66,6 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
discovery,
tokens,
pending,
web3,
};
let needUpdate: boolean = false;

View File

@ -12,6 +12,7 @@ import * as SEND from 'actions/constants/send';
import { initialState } from 'reducers/SendFormReducer';
import { findToken } from 'reducers/TokensReducer';
import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils';
import * as stateUtils from 'reducers/utils';
import type {
Dispatch,
@ -27,7 +28,8 @@ import type { State, FeeLevel } from 'reducers/SendFormReducer';
import type { Account } from 'reducers/AccountsReducer';
import type { Props } from 'views/Wallet/views/AccountSend/Container';
import * as SessionStorageActions from './SessionStorageActions';
import { estimateGas, pushTx } from './Web3Actions';
import { prepareEthereumTx, serializeEthereumTx } from './TxActions';
import * as BlockchainActions from './BlockchainActions';
export type SendTxAction = {
type: typeof SEND.TX_COMPLETE,
@ -223,14 +225,13 @@ export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLi
// initialize component
export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const {
account,
network,
web3,
} = getState().selectedAccount;
if (!account || !network || !web3) return;
if (!account || !network) return;
const stateFromStorage = SessionStorageActions.load(getState().router.location.pathname);
if (stateFromStorage) {
@ -243,7 +244,10 @@ export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState):
// TODO: check if there are some unfinished tx in localStorage
const gasPrice: BigNumber = new BigNumber(EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei')) || new BigNumber(network.defaultGasPrice);
// const gasPrice: BigNumber = new BigNumber(EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei')) || new BigNumber(network.defaultGasPrice);
const gasPrice: BigNumber = await dispatch( BlockchainActions.getGasPrice(network.network, network.defaultGasPrice) );
// const gasPrice: BigNumber = new BigNumber(network.defaultGasPrice);
const gasLimit: string = network.defaultGasLimit.toString();
const feeLevels: Array<FeeLevel> = getFeeLevels(network.symbol, gasPrice, gasLimit);
@ -709,12 +713,9 @@ export const onNonceChange = (nonce: string): AsyncAction => async (dispatch: Di
const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const {
web3,
network,
} = getState().selectedAccount;
if (!web3 || !network) return;
const w3 = web3.web3;
if (!network) return;
const state: State = getState().sendForm;
const requestedData = state.data;
@ -732,14 +733,7 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
return;
}
// TODO: allow data starting with 0x ...
const data: string = `0x${state.data.length % 2 === 0 ? state.data : `0${state.data}`}`;
const gasLimit = await estimateGas(w3, {
to: '0x0000000000000000000000000000000000000000',
data,
value: w3.toHex(w3.toWei(state.amount, 'ether')),
gasPrice: w3.toHex(EthereumjsUnits.convert(state.gasPrice, 'gwei', 'wei')),
});
const gasLimit: number = await dispatch( BlockchainActions.estimateGasLimit(network.network, state.data, state.amount, state.gasPrice) );
if (getState().sendForm.data === requestedData) {
dispatch(onGasLimitChange(gasLimit.toString()));
@ -777,56 +771,29 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
const {
account,
network,
web3,
pending,
} = getState().selectedAccount;
if (!account || !web3 || !network) return;
if (!account || !network) return;
const currentState: State = getState().sendForm;
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
const w3 = web3.web3;
const address_n = account.addressPath;
let data: string = `0x${currentState.data}`;
let txAmount: string = w3.toHex(w3.toWei(currentState.amount, 'ether'));
let txAddress: string = currentState.address;
if (isToken) {
const token: ?Token = findToken(getState().tokens, account.address, currentState.currency, account.deviceState);
if (!token) return;
const contract = web3.erc20.at(token.address);
const amountValue: string = new BigNumber(currentState.amount).times(Math.pow(10, token.decimals)).toString(10);
data = contract.transfer.getData(currentState.address, amountValue, {
from: account.address,
gasLimit: currentState.gasLimit,
gasPrice: currentState.gasPrice,
});
txAmount = '0x00';
txAddress = token.address;
}
const pendingNonce: number = getPendingNonce(pending);
const pendingNonce: number = stateUtils.getPendingNonce(pending);
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
console.warn('NONCE', nonce, account.nonce, pendingNonce);
const txData = {
address_n,
// from: currentAddress.address
to: txAddress,
value: txAmount,
data,
chainId: web3.chainId,
nonce: w3.toHex(nonce),
gasLimit: w3.toHex(currentState.gasLimit),
gasPrice: w3.toHex(EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei')),
r: '',
s: '',
v: '',
};
const txData = await dispatch( prepareEthereumTx({
network: network.network,
token: isToken ? findToken(getState().tokens, account.address, currentState.currency, account.deviceState) : null,
from: account.address,
to: currentState.address,
amount: currentState.amount,
data: currentState.data,
gasLimit: currentState.gasLimit,
gasPrice: currentState.gasPrice,
nonce
}) );
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
if (!selected) return;
@ -861,9 +828,17 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
txData.v = signedTransaction.payload.v;
try {
const tx = new EthereumjsTx(txData);
const serializedTx = `0x${tx.serialize().toString('hex')}`;
const txid: string = await pushTx(w3, serializedTx);
const serializedTx: string = await dispatch( serializeEthereumTx(txData) );
const push = await TrezorConnect.pushTransaction({
tx: serializedTx,
coin: network.network
});
if (!push.success) {
throw new Error( push.payload.error );
}
const txid = push.payload.txid;
dispatch({
type: SEND.TX_COMPLETE,
@ -871,7 +846,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
selectedCurrency: currentState.currency,
amount: currentState.amount,
total: currentState.total,
tx,
tx: txData,
nonce,
txid,
txData,

View File

@ -9,7 +9,7 @@ import type {
import type { State, Token } from 'reducers/TokensReducer';
import type { Account } from 'reducers/AccountsReducer';
import type { NetworkToken } from 'reducers/LocalStorageReducer';
import { getTokenInfoAsync, getTokenBalanceAsync } from './Web3Actions';
import * as BlockchainActions from './BlockchainActions';
export type TokenAction = {
type: typeof TOKEN.FROM_STORAGE,
@ -42,15 +42,11 @@ export const load = (input: string, network: string): AsyncAction => async (disp
// when options is a large list (>200 items)
return result.slice(0, 100);
}
const web3instance = getState().web3.find(w3 => w3.network === network);
if (!web3instance) return;
const info = await getTokenInfoAsync(web3instance.erc20, input);
const info = await dispatch( BlockchainActions.getTokenInfo(input, network) );
if (info) {
return [info];
}
//await resolveAfter(300000);
//await resolveAfter(3000);
};
export const setBalance = (tokenAddress: string, ethAddress: string, balance: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
@ -68,9 +64,6 @@ export const setBalance = (tokenAddress: string, ethAddress: string, balance: st
};
export const add = (token: NetworkToken, account: Account): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.find(w3 => w3.network === account.network);
if (!web3instance) return;
const tkn: Token = {
loaded: false,
deviceState: account.deviceState,
@ -88,7 +81,7 @@ export const add = (token: NetworkToken, account: Account): AsyncAction => async
payload: tkn,
});
const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, tkn);
const tokenBalance = await dispatch( BlockchainActions.getTokenBalance(tkn) );
dispatch(setBalance(token.address, account.address, tokenBalance));
};

View File

@ -1,6 +1,6 @@
/* @flow */
import TrezorConnect, {
DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT,
UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT
} from 'trezor-connect';
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as NOTIFICATION from 'actions/constants/notification';
@ -9,14 +9,15 @@ import { getDuplicateInstanceNumber } from 'reducers/utils';
import { push } from 'react-router-redux';
import type {
DeviceMessage,
UiMessage,
TransportMessage,
DeviceMessageType,
TransportMessageType,
UiMessage,
UiMessageType,
TransportMessage,
TransportMessageType,
BlockchainMessage,
BlockchainMessageType,
} from 'trezor-connect';
import type {
@ -115,11 +116,18 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
});
});
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainMessage): void => {
// post event to reducers
const type: BlockchainMessageType = event.type; // assert flow type
dispatch({
type,
payload: event.payload,
});
});
// $FlowIssue LOCAL not declared
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/';
window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/';
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/';
//window.__TREZOR_CONNECT_SRC = 'https://sisyfos.trezor.io/connect/';
// window.__TREZOR_CONNECT_SRC = 'https://localhost:8088/';
try {
await TrezorConnect.init({

69
src/actions/TxActions.js Normal file
View File

@ -0,0 +1,69 @@
/* @flow */
import EthereumjsTx from 'ethereumjs-tx';
import EthereumjsUnits from 'ethereumjs-units';
import BigNumber from 'bignumber.js';
import { toHex } from 'web3-utils';
import { initWeb3 } from './Web3Actions';
import type {
Dispatch,
GetState,
PromiseAction,
} from 'flowtype';
import type {
EthereumTransaction
} from 'trezor-connect';
import type { Token } from 'reducers/TokensReducer';
type EthereumTxRequest = {
network: string;
token: ?Token;
from: string;
to: string;
amount: string;
data: string;
gasLimit: string;
gasPrice: string;
nonce: number;
}
export const prepareEthereumTx = (tx: EthereumTxRequest): PromiseAction<EthereumTransaction> => async (dispatch: Dispatch, getState: GetState): Promise<EthereumTransaction> => {
const instance = await dispatch( initWeb3(tx.network) );
const token = tx.token;
let data: string = `0x${tx.data}`; // TODO: check if already prefixed
let value: string = toHex( EthereumjsUnits.convert(tx.amount, 'ether', 'wei') );
let to: string = tx.to;
if (token) {
// smart contract transaction
const contract = instance.erc20.clone();
contract.options.address = token.address;
const tokenAmount: string = new BigNumber(tx.amount).times(Math.pow(10, token.decimals)).toString(10);
data = instance.erc20.methods.transfer(to, tokenAmount).encodeABI();
value = '0x00';
to = token.address;
}
return {
to,
value,
data,
chainId: instance.chainId,
nonce: toHex(tx.nonce),
gasLimit: toHex(tx.gasLimit),
gasPrice: toHex( EthereumjsUnits.convert(tx.gasPrice, 'gwei', 'wei') ),
r: '',
s: '',
v: '',
}
};
export const serializeEthereumTx = (tx: EthereumTransaction): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
const ethTx = new EthereumjsTx(tx);
return `0x${ ethTx.serialize().toString('hex') }`;
// return toHex( ethTx.serialize() );
}

View File

@ -5,8 +5,11 @@ import { LOCATION_CHANGE } from 'react-router-redux';
import * as WALLET from 'actions/constants/wallet';
import * as stateUtils from 'reducers/utils';
import type
{
import type {
Account,
Coin,
Discovery,
Token,
Device,
TrezorDevice,
RouterLocationState,

View File

@ -1,22 +1,27 @@
/* @flow */
import Web3 from 'web3';
import HDKey from 'hdkey';
import BigNumber from 'bignumber.js';
import type {
ContractFactory,
EstimateGasOptions,
TransactionStatus,
TransactionReceipt,
} from 'web3';
import type BigNumber from 'bignumber.js';
import EthereumjsUtil from 'ethereumjs-util';
import EthereumjsUnits from 'ethereumjs-units';
import EthereumjsTx from 'ethereumjs-tx';
// import InputDataDecoder from 'ethereum-input-data-decoder';
import TrezorConnect from 'trezor-connect';
import type { EstimateGasOptions, TransactionStatus, TransactionReceipt } from 'web3';
import { strip } from 'utils/ethUtils';
import * as WEB3 from 'actions/constants/web3';
import * as PENDING from 'actions/constants/pendingTx';
import type {
Dispatch,
GetState,
ThunkAction,
AsyncAction,
PromiseAction,
} from 'flowtype';
import type { EthereumAccount } from 'trezor-connect';
import type { Account } from 'reducers/AccountsReducer';
import type { PendingTx } from 'reducers/PendingTxReducer';
import type { Web3Instance } from 'reducers/Web3Reducer';
@ -40,379 +45,266 @@ export type Web3UpdateGasPriceAction = {
export type Web3Action = {
type: typeof WEB3.READY,
} | {
type: typeof WEB3.CREATE,
type: typeof WEB3.START,
} | {
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
instance: Web3Instance
}
| Web3UpdateBlockAction
| Web3UpdateGasPriceAction;
} | Web3UpdateBlockAction
| Web3UpdateGasPriceAction;
export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
export const initWeb3 = (network: string, urlIndex: number = 0): PromiseAction<Web3Instance> => async (dispatch: Dispatch, getState: GetState): Promise<Web3Instance> => {
return new Promise(async (resolve, reject) => {
// check if requested web was initialized before
const instance = getState().web3.find(w3 => w3.network === network);
if (instance && instance.web3.currentProvider.connected) {
resolve(instance);
return;
}
// requested web3 wasn't initialized or is disconnected
// initialize again
const { config, ERC20Abi } = getState().localStorage;
const coin = config.coins[coinIndex];
const coin = config.coins.find(c => c.network === network);
if (!coin) {
// all instances done
dispatch({
type: WEB3.READY,
});
// coin not found
reject(new Error(`Network ${ network} not found in application config.`));
return;
}
const { network } = coin;
const urls = coin.backends[0].urls;
let web3host: string = urls[0];
if (instance) {
const currentHost = instance.currentProvider.host;
const currentHostIndex: number = urls.indexOf(currentHost);
if (currentHostIndex + 1 < urls.length) {
web3host = urls[currentHostIndex + 1];
} else {
console.error(`TODO: Backend ${network} not working`, instance.currentProvider);
dispatch({
type: WEB3.CREATE,
instance: {
network,
web3: instance,
chainId: coin.chainId,
erc20: instance.eth.contract(ERC20Abi),
latestBlock: '0',
gasPrice: '0',
},
});
// try next coin
dispatch(init(null, coinIndex + 1));
return;
}
}
//const instance = new Web3(window.web3.currentProvider);
const web3 = new Web3(new Web3.providers.HttpProvider(web3host));
// instance = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') ); // UBQ
//instance = new Web3( new Web3.providers.HttpProvider('https://node.expanse.tech/') ); // EXP
//instance = new Web3( new Web3.providers.HttpProvider('http://10.34.0.91:8545/') );
//web3 = new Web3(new Web3.providers.HttpProvider("https://api.myetherapi.com/rop"));
//instance = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io2/QGyVKozSUEh2YhL4s2G4"));
//web3 = new Web3( new Web3.providers.HttpProvider("ws://34.230.234.51:30303") );
// initial check if backend is running
if (!web3.currentProvider.isConnected()) {
// try different url
dispatch(init(web3, coinIndex));
// get first url
const url = coin.web3[ urlIndex ];
if (!url) {
reject(new Error('Web3 backend is not responding'));
return;
}
const erc20 = web3.eth.contract(ERC20Abi);
const web3 = new Web3( new Web3.providers.WebsocketProvider(url) );
dispatch({
type: WEB3.CREATE,
instance: {
const onConnect = async () => {
const latestBlock = await web3.eth.getBlockNumber();
const gasPrice = await web3.eth.getGasPrice();
const instance = {
network,
web3,
chainId: coin.chainId,
erc20,
latestBlock: '0',
gasPrice: '0',
},
});
// dispatch({
// type: WEB3.GAS_PRICE_UPDATED,
// network,
// gasPrice
// });
// console.log("GET CHAIN", instance.version.network)
// instance.version.getWhisper((err, shh) => {
// console.log("-----whisperrr", error, shh)
// })
// const sshFilter = instance.ssh.filter('latest');
// sshFilter.watch((error, blockHash) => {
// console.warn("SSH", error, blockHash);
// });
//const shh = instance.shh.newIdentity();
// const latestBlockFilter = web3.eth.filter('latest');
const onBlockMined = async (error: ?Error, blockHash: ?string) => {
if (error) {
window.setTimeout(() => {
// try again
onBlockMined(new Error('manually_triggered_error'), undefined);
}, 30000);
erc20: new web3.eth.Contract(ERC20Abi),
latestBlock,
gasPrice,
}
if (blockHash) {
dispatch({
type: WEB3.BLOCK_UPDATED,
network,
blockHash,
});
}
dispatch({
type: WEB3.CREATE,
instance,
});
// TODO: filter only current device
const accounts = getState().accounts.filter(a => a.network === network);
for (const account of accounts) {
const nonce = await getNonceAsync(web3, account.address);
if (nonce !== account.nonce) {
dispatch(AccountsActions.setNonce(account.address, account.network, account.deviceState, nonce));
resolve(instance);
}
// dispatch( getBalance(account) );
// TODO: check if nonce was updated,
// update tokens balance,
// update account balance,
// update pending transactions
const onEnd = async () => {
web3.currentProvider.reset();
const instance = getState().web3.find(w3 => w3.network === network);
if (instance && instance.web3.currentProvider.connected) {
// backend disconnects
// dispatch({
// type: 'WEB3.DISCONNECT',
// network
// });
} else {
// backend initialization error for given url, try next one
try {
const web3 = await dispatch( initWeb3(network, urlIndex + 1) );
resolve(web3);
} catch (error) {
reject(error);
}
dispatch(getBalance(account));
// dispatch( getNonce(account) );
}
}
const tokens = getState().tokens.filter(t => t.network === network);
tokens.forEach(token => dispatch(getTokenBalance(token)));
web3.currentProvider.on('connect', onConnect);
web3.currentProvider.on('end', onEnd);
web3.currentProvider.on('error', onEnd);
});
}
dispatch(getGasPrice(network));
const pending = getState().pending.filter(p => p.network === network);
pending.forEach(pendingTx => dispatch(getTransactionReceipt(pendingTx)));
};
// latestBlockFilter.watch(onBlockMined);
onBlockMined(new Error('manually_triggered_error'), undefined);
// init next coin
dispatch(init(web3, coinIndex + 1));
// let instance2 = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') );
// console.log("INIT WEB3", instance, instance2);
// instance2.eth.getGasPrice((error, gasPrice) => {
// console.log("---gasss price from UBQ", gasPrice)
// });
export const discoverAccount = (address: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch, getState: GetState): Promise<EthereumAccount> => {
const instance: Web3Instance = await dispatch( initWeb3(network) );
const balance = await instance.web3.eth.getBalance(address);
const nonce = await instance.web3.eth.getTransactionCount(address);
return {
address,
transactions: 0,
block: 0,
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
nonce
};
}
export function getGasPrice(network: string): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const index: number = getState().web3.findIndex(w3 => w3.network === network);
const web3instance = getState().web3[index];
const { web3 } = web3instance;
web3.eth.getGasPrice((error, gasPrice) => {
if (!error) {
if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) {
dispatch({
type: WEB3.GAS_PRICE_UPDATED,
network,
gasPrice,
});
}
}
});
};
}
export function getBalance(account: Account): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0];
const { web3 } = web3instance;
web3.eth.getBalance(account.address, (error: Error, balance: BigNumber) => {
if (!error) {
const newBalance: string = web3.fromWei(balance.toString(), 'ether');
if (account.balance !== newBalance) {
dispatch(AccountsActions.setBalance(
account.address,
account.network,
account.deviceState,
newBalance,
));
// dispatch( loadHistory(addr) );
}
}
});
};
}
export function getTokenBalance(token: Token): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.filter(w3 => w3.network === token.network)[0];
const contract = web3instance.erc20.at(token.address);
contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => {
if (balance) {
const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
if (newBalance !== token.balance) {
dispatch(TokenActions.setBalance(
token.address,
token.ethAddress,
newBalance,
));
}
}
});
};
}
export function getNonce(account: Account): AsyncAction {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.filter(w3 => w3.network === account.network)[0];
const { web3 } = web3instance;
web3.eth.getTransactionCount(account.address, (error: Error, result: number) => {
if (!error) {
if (account.nonce !== result) {
dispatch(AccountsActions.setNonce(account.address, account.network, account.deviceState, result));
}
}
});
};
}
export const getTransactionReceipt = (tx: PendingTx): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const web3instance = getState().web3.filter(w3 => w3.network === tx.network)[0];
const { web3 } = web3instance;
web3.eth.getTransaction(tx.id, (error: Error, status: TransactionStatus) => {
if (!error && !status) {
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const instance: Web3Instance = await dispatch( initWeb3(network) );
const pending = getState().pending.filter(p => p.network === network);
for (const tx of pending) {
const status = await instance.web3.eth.getTransaction(tx.id);
if (!status) {
dispatch({
type: PENDING.TX_NOT_FOUND,
tx,
});
} else if (status && status.blockNumber) {
web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => {
if (receipt) {
if (status.gas !== receipt.gasUsed) {
dispatch({
type: PENDING.TX_TOKEN_ERROR,
tx,
});
}
} else {
const receipt = await instance.web3.eth.getTransactionReceipt(tx.id);
if (receipt) {
if (status.gas !== receipt.gasUsed) {
dispatch({
type: PENDING.TX_RESOLVED,
type: PENDING.TX_TOKEN_ERROR,
tx,
receipt,
});
}
});
dispatch({
type: PENDING.TX_RESOLVED,
tx,
receipt,
});
}
}
});
}
}
export const getPendingInfo = (network: string, txid: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const instance: Web3Instance = await dispatch( initWeb3(network) );
const tx = await instance.web3.eth.getTransaction(txid);
/*
if (tx.input !== "0x") {
// find token:
// tx.to <= smart contract address
// smart contract data
const decoder = new InputDataDecoder(instance.erc20.options.jsonInterface);
const data = decoder.decodeData(tx.input);
if (data.name === 'transfer') {
console.warn("DATA!", data.inputs[0], data.inputs[1].toString(10));
}
}
*/
// return tx;
}
export const getTxInput = (): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const instance: Web3Instance = await dispatch( initWeb3("ropsten") );
// const inputData = instance.web3.utils.hexToAscii("0xa9059cbb00000000000000000000000073d0385f4d8e00c5e6504c6030f47bf6212736a80000000000000000000000000000000000000000000000000000000000000001");
// console.warn("input data!", inputData);
}
export const updateAccount = (account: Account, newAccount: EthereumAccount, network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const instance: Web3Instance = await dispatch( initWeb3(network) );
const balance = await instance.web3.eth.getBalance(account.address);
const nonce = await instance.web3.eth.getTransactionCount(account.address);
dispatch( AccountsActions.update( { ...account, ...newAccount, balance: EthereumjsUnits.convert(balance, 'wei', 'ether'), nonce }) );
// update tokens for this account
dispatch( updateAccountTokens(account) );
}
export const updateAccountTokens = (account: Account): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const tokens = getState().tokens.filter(t => t.network === account.network && t.ethAddress === account.address);
for (const token of tokens) {
const balance = await dispatch( getTokenBalance(token) );
// const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
if (balance !== token.balance) {
dispatch(TokenActions.setBalance(
token.address,
token.ethAddress,
balance,
));
}
}
}
export const getTokenInfo = (address: string, network: string): PromiseAction<NetworkToken> => async (dispatch: Dispatch, getState: GetState): Promise<NetworkToken> => {
const instance: Web3Instance = await dispatch( initWeb3(network) );
const contract = instance.erc20.clone();
contract.options.address = address;
const name = await contract.methods.name().call();
const symbol = await contract.methods.symbol().call();
const decimals = await contract.methods.decimals().call();
return {
address,
name,
symbol,
decimals,
};
};
export const getTransaction = (web3: Web3, txid: string): Promise<any> => new Promise((resolve, reject) => {
web3.eth.getTransaction(txid, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
const instance = await dispatch( initWeb3(token.network) );
const contract = instance.erc20.clone();
contract.options.address = token.address;
export const getBalanceAsync = (web3: Web3, address: string): Promise<BigNumber> => new Promise((resolve, reject) => {
web3.eth.getBalance(address, (error: Error, result: BigNumber) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
const balance = await contract.methods.balanceOf(token.ethAddress).call();
return new BigNumber(balance).dividedBy(Math.pow(10, token.decimals)).toString(10);
};
export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise<string> => new Promise((resolve, reject) => {
const contract = erc20.at(token.address);
contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => {
if (error) {
reject(error);
} else {
const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
resolve(newBalance);
}
});
});
export const getCurrentGasPrice = (network: string): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
const instance = getState().web3.find(w3 => w3.network === network);
if (instance) {
return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei');
} else {
throw "0";
}
}
export const getNonceAsync = (web3: Web3, address: string): Promise<number> => new Promise((resolve, reject) => {
web3.eth.getTransactionCount(address, (error: Error, result: number) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise<?NetworkToken> => new Promise((resolve) => {
const contract = erc20.at(address, (error/* , res */) => {
// console.warn("callback", error, res)
});
const info: NetworkToken = {
address,
name: '',
symbol: '',
decimals: 0,
};
contract.name.call((error: Error, name: string) => {
if (error) {
resolve(null);
return;
}
info.name = name;
contract.symbol.call((error: Error, symbol: string) => {
if (error) {
resolve(null);
return;
}
info.symbol = symbol;
contract.decimals.call((error: Error, decimals: BigNumber) => {
if (decimals) {
info.decimals = decimals.toNumber();
resolve(info);
} else {
resolve(null);
}
export const updateGasPrice = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
try {
const instance = await dispatch( initWeb3(network) );
const gasPrice = await instance.web3.eth.getGasPrice();
if (instance.gasPrice !== gasPrice) {
dispatch({
type: WEB3.GAS_PRICE_UPDATED,
network,
gasPrice
});
}
} catch (e) {
// silent action
// nothing happens if this fails
}
}
export const estimateGasLimit = (network: string, options: EstimateGasOptions): PromiseAction<number> => async (dispatch: Dispatch, getState: GetState): Promise<number> => {
const instance = await dispatch( initWeb3(network) );
// TODO: allow data starting with 0x ...
options.to = '0x0000000000000000000000000000000000000000';
options.data = `0x${options.data.length % 2 === 0 ? options.data : `0${options.data}`}`;
options.value = instance.web3.utils.toHex( EthereumjsUnits.convert(options.value || '0', 'ether', 'wei') );
options.gasPrice = instance.web3.utils.toHex( EthereumjsUnits.convert(options.gasPrice, 'gwei', 'wei') );
const limit = await instance.web3.eth.estimateGas(options);
return limit;
};
export const disconnect = (coinInfo: any): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
// incoming "coinInfo" from TrezorConnect is CoinInfo | EthereumNetwork type
const network: string = coinInfo.shortcut.toLowerCase();
// check if Web3 was already initialized
const instance = getState().web3.find(w3 => w3.network === network);
if (instance) {
// reset current connection
instance.web3.currentProvider.reset();
instance.web3.currentProvider.connection.close();
// remove instance from reducer
dispatch({
type: WEB3.DISCONNECT,
instance
});
});
});
export const estimateGas = (web3: Web3, options: EstimateGasOptions): Promise<number> => new Promise((resolve, reject) => {
web3.eth.estimateGas(options, (error: ?Error, gas: ?number) => {
if (error) {
reject(error);
} else if (typeof gas === 'number') {
resolve(gas);
}
});
});
export const pushTx = (web3: Web3, tx: any): Promise<string> => new Promise((resolve, reject) => {
web3.eth.sendRawTransaction(tx, (error: Error, result: string) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
};

View File

@ -6,6 +6,7 @@ export const DISPOSE: 'account__dispose' = 'account__dispose';
export const CREATE: 'account__create' = 'account__create';
export const REMOVE: 'account__remove' = 'account__remove';
export const UPDATE: 'account__update' = 'account__update';
export const SET_BALANCE: 'account__set_balance' = 'account__set_balance';
export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce';
export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage';

View File

@ -0,0 +1,6 @@
/* @flow */
export const READY: 'blockchain__ready' = 'blockchain__ready';
export const CONNECTING: 'blockchain__connecting' = 'blockchain__connecting';
export const CONNECTED: 'blockchain__connected' = 'blockchain__connected';
export const DISCONNECTED: 'blockchain__disconnected' = 'blockchain__disconnected';

View File

@ -5,5 +5,5 @@ export const START: 'discovery__start' = 'discovery__start';
export const STOP: 'discovery__stop' = 'discovery__stop';
export const COMPLETE: 'discovery__complete' = 'discovery__complete';
export const WAITING_FOR_DEVICE: 'discovery__waiting_for_device' = 'discovery__waiting_for_device';
export const WAITING_FOR_BACKEND: 'discovery__waiting_for_backend' = 'discovery__waiting_for_backend';
export const WAITING_FOR_BLOCKCHAIN: 'discovery__waiting_for_blockchain' = 'discovery__waiting_for_blockchain';
export const FROM_STORAGE: 'discovery__from_storage' = 'discovery__from_storage';

View File

@ -2,6 +2,7 @@
export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage';
export const ADD: 'pending__add' = 'pending__add';
export const TX_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved';
export const TX_NOT_FOUND: 'pending__tx_not_found' = 'pending__tx_not_found';
export const TX_TOKEN_ERROR: 'pending__tx_token_error' = 'pending__tx_token_error';

View File

@ -7,4 +7,5 @@ export const CREATE: 'web3__create' = 'web3__create';
export const READY: 'web3__ready' = 'web3__ready';
export const BLOCK_UPDATED: 'web3__block_updated' = 'web3__block_updated';
export const GAS_PRICE_UPDATED: 'web3__gas_price_updated' = 'web3__gas_price_updated';
export const PENDING_TX_RESOLVED: 'web3__pending_tx_resolved' = 'web3__pending_tx_resolved';
export const PENDING_TX_RESOLVED: 'web3__pending_tx_resolved' = 'web3__pending_tx_resolved';
export const DISCONNECT: 'web3__disconnect' = 'web3__disconnect';

View File

@ -12,7 +12,7 @@ import icons from 'config/icons';
import colors from 'config/colors';
import Link from 'components/Link';
import { Props } from './index';
import type { Props } from 'components/modals/index';
type State = {
defaultName: string;

View File

@ -7,6 +7,8 @@ import type {
Middleware as ReduxMiddleware,
ThunkAction as ReduxThunkAction,
AsyncAction as ReduxAsyncAction,
PromiseAction as ReduxPromiseAction,
ThunkDispatch as ReduxThunkDispatch,
PlainDispatch as ReduxPlainDispatch,
} from 'redux';
@ -15,6 +17,7 @@ import type { ReducersState } from 'reducers';
// Actions
import type { SelectedAccountAction } from 'actions/SelectedAccountActions';
import type { AccountAction } from 'actions/AccountsActions';
import type { BlockchainAction } from 'actions/BlockchainActions';
import type { DiscoveryAction } from 'actions/DiscoveryActions';
import type { StorageAction } from 'actions/LocalStorageActions';
import type { LogAction } from 'actions/LogActions';
@ -37,6 +40,7 @@ import type {
DeviceFirmwareStatus,
DeviceMessageType,
TransportMessageType,
BlockchainMessageType,
UiMessageType,
} from 'trezor-connect';
@ -102,6 +106,11 @@ type UiEventAction = {
// },
}
type BlockchainEventAction = {
type: BlockchainMessageType,
payload: any,
}
// TODO: join this message with uiMessage
type IFrameHandshake = {
type: 'iframe_handshake',
@ -114,9 +123,11 @@ export type Action =
| TransportEventAction
| DeviceEventAction
| UiEventAction
| BlockchainEventAction
| SelectedAccountAction
| AccountAction
| BlockchainAction
| DiscoveryAction
| StorageAction
| LogAction
@ -154,6 +165,7 @@ export type Middleware = ReduxMiddleware<State, Action>;
export type ThunkAction = ReduxThunkAction<State, Action>;
export type AsyncAction = ReduxAsyncAction<State, Action>;
export type PromiseAction<R> = ReduxPromiseAction<State, Action, R>;
export type Store = ReduxStore<State, Action>;
export type GetState = () => State;

View File

@ -1,3 +1,5 @@
/* @flow */
declare module 'bignumber.js' {
declare type $npm$big$number$object = number | string | T_BigNumber
declare type $npm$cmp$result = -1 | 0 | 1
@ -24,7 +26,7 @@ declare module 'bignumber.js' {
constructor(value: $npm$big$number$object): T_BigNumber;
// Methods
abs(): BigNumber;
abs(): T_BigNumber;
cmp(n: $npm$big$number$object): $npm$cmp$result;
div(n: $npm$big$number$object): T_BigNumber;
dividedBy(n: $npm$big$number$object): T_BigNumber;

View File

@ -1,10 +1,12 @@
/* @flow */
declare module 'redux' {
/*
S = State
A = Action
D = Dispatch
R = Promise response
*/
declare export type DispatchAPI<A> = (action: A) => A;
@ -13,12 +15,14 @@ declare module 'redux' {
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 PromiseAction<S, A, R> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<R>;
declare export type ThunkDispatch<S, A> = (action: ThunkAction<S, A>) => 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 PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
/* 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>;
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A>;
declare export type MiddlewareAPI<S, A> = {
// dispatch: Dispatch<S, A>;

View File

@ -1,8 +1,10 @@
/* @flow */
import type BigNumber from 'bignumber.js';
import type { EthereumUnitT, EthereumAddressT } from 'ethereum-types';
declare module 'web3' {
declare type ProviderT = {
declare type HttpProviderT = {
host: string;
timeout: number;
isConnected: () => boolean;
@ -10,14 +12,28 @@ declare module 'web3' {
sendAsync: (payload: any, callback: (error: Error, result: any) => void) => any;
};
declare type WebsocketProviderT = {
connection: {
close: () => void;
}; // WebSocket type
on: (type: string, callback: () => any) => void;
removeAllListeners: (type: string) => void;
reset: () => void;
connected: boolean;
}
declare class Web3T {
static providers: {
HttpProvider: (host: string, timeout?: number) => ProviderT;
HttpProvider: (host: string, timeout?: number) => HttpProviderT;
WebsocketProvider: (host: string, options?: any) => WebsocketProviderT;
};
constructor(ProviderT): Web3T;
currentProvider: ProviderT;
// constructor(HttpProviderT): Web3T;
constructor(WebsocketProviderT): Web3T;
// currentProvider: HttpProviderT;
currentProvider: WebsocketProviderT;
eth: Eth;
utils: Utils;
toHex: (str: string | number) => string;
isAddress: (address: string) => boolean;
@ -78,20 +94,33 @@ declare module 'web3' {
transactionIndex: number
}
//declare function F_CardanoGetAddress(params: (P.$Common & CARDANO.$CardanoGetAddress)): Promise<CARDANO.CardanoGetAddress$>;
//declare function F_CardanoGetAddress(params: (P.$Common & { bundle: Array<CARDANO.$CardanoGetAddress> })): Promise<CARDANO.CardanoGetAddress$$>;
declare type PromiseEvent<T> = {
once: typeof F_PromiseEventOn;
on: typeof F_PromiseEventOn;
off: (type: string, callback: Function) => PromiseEvent<T>;
then: () => (result: T) => PromiseEvent<T>;
catch: () => (error: Error) => PromiseEvent<T>;
}
declare function F_PromiseEventOn<T>(type: 'transactionHash', callback: (hash: string) => void): PromiseEvent<T>;
declare function F_PromiseEventOn<T>(type: 'receipt', callback: (receipt: TransactionReceipt) => void): PromiseEvent<T>;
declare function F_PromiseEventOn<T>(type: 'confirmation', callback: (confirmations: number, receipt: TransactionReceipt) => void): PromiseEvent<T>;
declare function F_PromiseEventOn<T>(type: 'error', callback: (error: Error) => void): PromiseEvent<T>;
declare class Eth {
getGasPrice: (callback: (error: Error, gasPrice: string) => void) => void,
getBalance: (address: string, callback: (error: Error, balance: BigNumber) => void) => void,
getTransactionCount: (address: string, callback: (error: Error, result: number) => void) => void,
getTransaction: (txid: string, callback: (error: Error, result: TransactionStatus) => void) => void,
getTransactionReceipt: (txid: string, callback: (error: Error, result: TransactionReceipt) => void) => void,
getBlockNumber: (callback: (error: Error, blockNumber: number) => void) => void,
getBlock: (hash: string, callback: (error: Error, result: any) => void) => void,
// getAccounts: (callback: (error: Error, accounts: Array<EthereumAddressT>) => void) => void,
// sign: (payload: string, signer: EthereumAddressT) => Promise<string>,
contract: (abi: Array<Object>) => ContractFactory,
estimateGas: (options: EstimateGasOptions, callback: (error: ?Error, gas: ?number) => void) => void,
sendRawTransaction: (tx: any, callback: (error: Error, result: string) => void) => void,
filter: (type: string) => Filter; // return intance with "watch"
getBalance: (address: string) => Promise<string>;
getTransactionCount: (address: string) => Promise<number>;
estimateGas: (options: EstimateGasOptions) => Promise<number>;
getGasPrice: () => Promise<string>;
getBlockNumber: () => Promise<number>;
Contract: (abi: Array<Object>, options?: any) => Contract;
sendSignedTransaction: (tx: string) => PromiseEvent<TransactionReceipt>;
getTransaction: (txid: string) => Promise<TransactionStatus>;
getTransactionReceipt: (txid: string) => Promise<TransactionReceipt>;
subscribe: (type: string, callback: Function) => any;
}
declare export class Filter {
@ -99,108 +128,40 @@ declare module 'web3' {
stopWatching: (callback: any) => void,
}
declare export class ContractFactory {
// constructor(abi: Array<Object>);
eth: Eth;
abi: Array<Object>;
at: (address: string, callback: ?(error: Error, contract: Contract) => void) => Contract; // TODO
declare type ContractMethod<T> = {
call: () => Promise<T>;
}
declare export class Contract {
name: {
call: (callback: (error: Error, name: string) => void) => void;
},
symbol: {
call: (callback: (error: Error, symbol: string) => void) => void;
},
decimals: {
call: (callback: (error: Error, decimals: BigNumber) => void) => void;
},
balanceOf: (address: string, callback: (error: Error, balance: BigNumber) => void) => void,
transfer: any,
clone: () => Contract;
options: {
address: string;
jsonInterface: JSON;
};
methods: {
name: () => ContractMethod<string>;
symbol: () => ContractMethod<string>;
decimals: () => ContractMethod<number>;
balanceOf: (address: string) => ContractMethod<string>;
transfer: (to: string, amount: any) => {
encodeABI: () => string;
}
};
}
declare class Utils {
toHex: (str: string | number) => string;
hexToNumberString: (str: string) => string;
isAddress: (address: string) => boolean;
toWei: (number: BigNumber, unit?: EthereumUnitT) => BigNumber;
toWei: (number: string, unit?: EthereumUnitT) => string;
toDecimal: (number: BigNumber) => number;
toDecimal: (number: string) => number;
soliditySha3: (payload: string | number | BigNumber | Object) => String;
}
declare export default typeof Web3T;
}
//
//
/*declare module 'web3' {
module.exports = {
eth: {
_requestManager: any;
iban: {
(iban: string): void;
fromAddress: (address: string) => any;
fromBban: (bban: string) => any;
createIndirect: (options: any) => any;
isValid: (iban: string) => boolean;
};
sendIBANTransaction: any;
contract: (abi: any) => {
eth: any;
abi: any[];
new: (...args: any[]) => {
_eth: any;
transactionHash: any;
address: any;
abi: any[];
};
at: (address: any, callback: Function) => any;
getData: (...args: any[]) => any;
};
filter: (fil: any, callback: any, filterCreationErrorCallback: any) => {
requestManager: any;
options: any;
implementation: {
[x: string]: any;
};
filterId: any;
callbacks: any[];
getLogsCallbacks: any[];
pollFilters: any[];
formatter: any;
watch: (callback: any) => any;
stopWatching: (callback: any) => any;
get: (callback: any) => any;
};
namereg: () => {
eth: any;
abi: any[];
new: (...args: any[]) => {
_eth: any;
transactionHash: any;
address: any;
abi: any[];
};
at: (address: any, callback: Function) => any;
getData: (...args: any[]) => any;
};
icapNamereg: () => {
eth: any;
abi: any[];
new: (...args: any[]) => {
_eth: any;
transactionHash: any;
address: any;
abi: any[];
};
at: (address: any, callback: Function) => any;
getData: (...args: any[]) => any;
};
isSyncing: (callback: any) => {
requestManager: any;
pollId: string;
callbacks: any[];
lastSyncState: boolean;
addCallback: (callback: any) => any;
stopWatching: () => void;
};
}
}
}
*/
}

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -4,7 +4,7 @@ import { render } from 'react-dom';
import baseStyles from 'support/BaseStyles';
import { onBeforeUnload } from 'actions/WalletActions';
import App from 'views/index';
import store from 'store';
import store from './store';
const root: ?HTMLElement = document.getElementById('root');
if (root) {

View File

@ -7,7 +7,6 @@ import * as ACCOUNT from 'actions/constants/account';
import type { Action, TrezorDevice } from 'flowtype';
import type {
AccountCreateAction,
AccountSetBalanceAction,
AccountSetNonceAction,
} from 'actions/AccountsActions';
@ -22,6 +21,8 @@ export type Account = {
+address: string;
balance: string;
nonce: number;
block: number;
transactions: number;
}
export type State = Array<Account>;
@ -37,28 +38,14 @@ export const findDeviceAccounts = (state: State, device: TrezorDevice, network:
return state.filter(addr => addr.deviceState === device.state);
};
const createAccount = (state: State, action: AccountCreateAction): State => {
const createAccount = (state: State, account: Account): State => {
// TODO check with device_id
// check if account was created before
// const exist: ?Account = state.find(account => account.address === action.address && account.network === action.network && action.device.features && account.deviceID === action.device.features.device_id);
const exist: ?Account = state.find(account => account.address === action.address && account.network === action.network && account.deviceState === action.device.state);
const exist: ?Account = state.find(a => a.address === account.address && a.network === account.network && a.deviceState === account.deviceState);
if (exist) {
return state;
}
const account: Account = {
loaded: false,
network: action.network,
deviceID: action.device.features ? action.device.features.device_id : '0',
deviceState: action.device.state || 'undefined',
index: action.index,
addressPath: action.path,
address: action.address,
balance: '0',
nonce: 0,
};
const newState: State = [...state];
const newState: State = [ ...state ];
newState.push(account);
return newState;
};
@ -74,8 +61,16 @@ const clear = (state: State, devices: Array<TrezorDevice>): State => {
return newState;
};
const updateAccount = (state: State, account: Account): State => {
const index: number = state.findIndex(a => a.address === account.address && a.network === account.network && a.deviceState === account.deviceState);
const newState: State = [...state];
newState[index] = account;
return newState;
}
const setBalance = (state: State, action: AccountSetBalanceAction): State => {
const index: number = state.findIndex(account => account.address === action.address && account.network === action.network && account.deviceState === action.deviceState);
// const index: number = state.findIndex(account => account.address === action.address && account.network === action.network && account.deviceState === action.deviceState);
const index: number = state.findIndex(account => account.address === action.address && account.network === action.network);
const newState: State = [...state];
newState[index].loaded = true;
newState[index].balance = action.balance;
@ -93,7 +88,7 @@ const setNonce = (state: State, action: AccountSetNonceAction): State => {
export default (state: State = initialState, action: Action): State => {
switch (action.type) {
case ACCOUNT.CREATE:
return createAccount(state, action);
return createAccount(state, action.payload);
case CONNECT.FORGET:
case CONNECT.FORGET_SINGLE:
@ -105,6 +100,9 @@ export default (state: State = initialState, action: Action): State => {
//case CONNECT.FORGET_SINGLE :
// return forgetAccounts(state, action);
case ACCOUNT.UPDATE :
return updateAccount(state, action.payload);
case ACCOUNT.SET_BALANCE:
return setBalance(state, action);
case ACCOUNT.SET_NONCE:

View File

@ -0,0 +1,64 @@
/* @flow */
import { BLOCKCHAIN } from 'trezor-connect';
import type { Action } from 'flowtype';
export type BlockchainNetwork = {
+name: string;
connected: boolean;
}
export type State = Array<BlockchainNetwork>;
export const initialState: State = [];
const find = (state: State, name: string): number => {
return state.findIndex(b => b.name === name);
}
const connect = (state: State, action: any): State => {
const name = action.payload.coin.shortcut.toLowerCase();
const network: BlockchainNetwork = {
name,
connected: true,
}
const newState: State = [...state];
const index: number = find(newState, name);
if (index >= 0) {
newState[index] = network;
} else {
newState.push(network);
}
return newState;
};
const disconnect = (state: State, action: any): State => {
const name = action.payload.coin.shortcut.toLowerCase();
const network: BlockchainNetwork = {
name,
connected: false,
}
const newState: State = [...state];
const index: number = find(newState, name);
if (index >= 0) {
newState[index] = network;
} else {
newState.push(network);
}
return newState;
};
export default (state: State = initialState, action: Action): State => {
switch (action.type) {
case BLOCKCHAIN.CONNECT:
return connect(state, action);
case BLOCKCHAIN.ERROR:
return disconnect(state, action);
default:
return state;
}
};

View File

@ -16,9 +16,7 @@ import type {
DiscoveryCompleteAction,
} from 'actions/DiscoveryActions';
import type {
AccountCreateAction,
} from 'actions/AccountsActions';
import type { Account } from './AccountsReducer';
export type Discovery = {
network: string;
@ -31,7 +29,7 @@ export type Discovery = {
interrupted: boolean;
completed: boolean;
waitingForDevice: boolean;
waitingForBackend: boolean;
waitingForBlockchain: boolean;
}
export type State = Array<Discovery>;
@ -55,7 +53,7 @@ const start = (state: State, action: DiscoveryStartAction): State => {
interrupted: false,
completed: false,
waitingForDevice: false,
waitingForBackend: false,
waitingForBlockchain: false,
};
const newState: State = [...state];
@ -75,8 +73,8 @@ const complete = (state: State, action: DiscoveryCompleteAction): State => {
return newState;
};
const accountCreate = (state: State, action: AccountCreateAction): State => {
const index: number = findIndex(state, action.network, action.device.state || '0');
const accountCreate = (state: State, account: Account): State => {
const index: number = findIndex(state, account.network, account.deviceState);
const newState: State = [...state];
newState[index].accountIndex++;
return newState;
@ -98,6 +96,7 @@ const stop = (state: State, action: DiscoveryStopAction): State => {
if (d.deviceState === action.device.state && !d.completed) {
d.interrupted = true;
d.waitingForDevice = false;
d.waitingForBlockchain = false;
}
return d;
});
@ -116,7 +115,7 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State =
interrupted: false,
completed: false,
waitingForDevice: true,
waitingForBackend: false,
waitingForBlockchain: false,
};
const index: number = findIndex(state, action.network, deviceState);
@ -130,7 +129,7 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State =
return newState;
};
const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State => {
const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): State => {
const deviceState: string = action.device.state || '0';
const instance: Discovery = {
network: action.network,
@ -143,7 +142,7 @@ const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State
interrupted: false,
completed: false,
waitingForDevice: false,
waitingForBackend: true,
waitingForBlockchain: true,
};
const index: number = findIndex(state, action.network, deviceState);
@ -162,15 +161,15 @@ export default function discovery(state: State = initialState, action: Action):
case DISCOVERY.START:
return start(state, action);
case ACCOUNT.CREATE:
return accountCreate(state, action);
return accountCreate(state, action.payload);
case DISCOVERY.STOP:
return stop(state, action);
case DISCOVERY.COMPLETE:
return complete(state, action);
case DISCOVERY.WAITING_FOR_DEVICE:
return waitingForDevice(state, action);
case DISCOVERY.WAITING_FOR_BACKEND:
return waitingForBackend(state, action);
case DISCOVERY.WAITING_FOR_BLOCKCHAIN:
return waitingForBlockchain(state, action);
case DISCOVERY.FROM_STORAGE:
return action.payload.map((d) => {
const hdKey: HDKey = new HDKey();
@ -181,7 +180,7 @@ export default function discovery(state: State = initialState, action: Action):
hdKey,
interrupted: false,
waitingForDevice: false,
waitingForBackend: false,
waitingForBlockchain: false,
};
});
case CONNECT.FORGET:

View File

@ -22,7 +22,8 @@ export type Coin = {
backends: Array<{
name: string;
urls: Array<string>;
}>
}>;
web3: Array<string>;
}
export type NetworkToken = {

View File

@ -6,6 +6,7 @@ import type { Action } from 'flowtype';
import type { SendTxAction } from 'actions/SendFormActions';
export type PendingTx = {
+type: 'send' | 'recv';
+id: string;
+network: string;
+currency: string;
@ -24,6 +25,7 @@ const initialState: State = [];
const add = (state: State, action: SendTxAction): State => {
const newState = [...state];
newState.push({
type: 'send',
id: action.txid,
network: action.account.network,
currency: action.selectedCurrency,
@ -37,6 +39,12 @@ const add = (state: State, action: SendTxAction): State => {
return newState;
};
const add_NEW = (state: State, payload: any): State => {
const newState = [...state];
newState.push(payload);
return newState;
};
const remove = (state: State, id: string): State => state.filter(tx => tx.id !== id);
const reject = (state: State, id: string): State => state.map((tx) => {
@ -51,6 +59,8 @@ export default function pending(state: State = initialState, action: Action): St
case SEND.TX_COMPLETE:
return add(state, action);
// case PENDING.ADD:
// return add(state, action.payload);
case PENDING.TX_RESOLVED:
return remove(state, action.tx.id);
case PENDING.TX_NOT_FOUND:

View File

@ -10,7 +10,6 @@ import type {
Token,
PendingTx,
Discovery,
Web3Instance,
} from 'flowtype';
export type State = {
@ -20,7 +19,6 @@ export type State = {
network: ?Coin;
tokens: Array<Token>,
pending: Array<PendingTx>,
web3: ?Web3Instance,
discovery: ?Discovery
};
@ -30,7 +28,6 @@ export const initialState: State = {
network: null,
tokens: [],
pending: [],
web3: null,
discovery: null,
};

View File

@ -2,9 +2,8 @@
import { LOCATION_CHANGE } from 'react-router-redux';
import { DEVICE } from 'trezor-connect';
import { DEVICE, TRANSPORT } from 'trezor-connect';
import * as MODAL from 'actions/constants/modal';
import * as WEB3 from 'actions/constants/web3';
import * as WALLET from 'actions/constants/wallet';
import * as CONNECT from 'actions/constants/TrezorConnect';
@ -41,7 +40,7 @@ export default function wallet(state: State = initialState, action: Action): Sta
initialPathname: action.pathname,
};
case WEB3.READY:
case TRANSPORT.START:
return {
...state,
ready: true,

View File

@ -3,7 +3,8 @@
import Web3 from 'web3';
import type { ContractFactory } from 'web3';
import type { Contract } from 'web3';
import * as STORAGE from 'actions/constants/localStorage';
import * as WEB3 from 'actions/constants/web3';
import type { Action } from 'flowtype';
@ -18,7 +19,7 @@ export type Web3Instance = {
chainId: number;
latestBlock: any;
gasPrice: string;
erc20: ContractFactory;
erc20: Contract;
}
export type State = Array<Web3Instance>;
@ -26,8 +27,13 @@ export type State = Array<Web3Instance>;
const initialState: State = [];
const createWeb3 = (state: State, instance: Web3Instance): State => {
const index: number = state.findIndex(w3 => w3.network === instance.network);
const newState: Array<Web3Instance> = [...state];
newState.push(instance);
if (index >= 0) {
newState[index] = instance;
} else {
newState.push(instance);
}
return newState;
};
@ -45,6 +51,13 @@ const updateGasPrice = (state: State, action: Web3UpdateGasPriceAction): State =
return newState;
};
const disconnect = (state: State, instance: Web3Instance): State => {
const index: number = state.indexOf(instance);
const newState: Array<Web3Instance> = [...state];
newState.splice(index, 1);
return newState;
};
export default function web3(state: State = initialState, action: Action): State {
switch (action.type) {
case WEB3.CREATE:
@ -53,6 +66,8 @@ export default function web3(state: State = initialState, action: Action): State
return updateLatestBlock(state, action);
case WEB3.GAS_PRICE_UPDATED:
return updateGasPrice(state, action);
case WEB3.DISCONNECT:
return disconnect(state, action.instance);
default:
return state;
}

View File

@ -19,6 +19,7 @@ import pending from 'reducers/PendingTxReducer';
import fiat from 'reducers/FiatRateReducer';
import wallet from 'reducers/WalletReducer';
import devices from 'reducers/DevicesReducer';
import blockchain from 'reducers/BlockchainReducer';
const reducers = {
router: routerReducer,
@ -39,6 +40,7 @@ const reducers = {
fiat,
wallet,
devices,
blockchain
};
export type Reducers = typeof reducers;

View File

@ -72,7 +72,6 @@ const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
__unloading = true;
} else if (action.type === LOCATION_CHANGE && !__unloading) {
const { location } = api.getState().router;
const { web3 } = api.getState();
const { devices } = api.getState();
const { error } = api.getState().connect;
@ -92,8 +91,8 @@ const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
redirectPath = '/';
} else {
const isModalOpened: boolean = api.getState().modal.opened;
// if web3 wasn't initialized yet or there are no devices attached or initialization error occurs
const landingPage: boolean = web3.length < 1 || devices.length < 1 || error !== null;
// there are no devices attached or initialization error occurs
const landingPage: boolean = devices.length < 1 || error !== null;
// modal is still opened and currentPath is still valid
// example 1 (valid blocking): url changes while passphrase modal opened but device is still connected (we want user to finish this action)

View File

@ -1,16 +1,16 @@
/* @flow */
import { push } from 'react-router-redux';
import {
TRANSPORT, DEVICE,
import TrezorConnect, {
TRANSPORT, DEVICE_EVENT, UI_EVENT, UI, DEVICE, BLOCKCHAIN
} from 'trezor-connect';
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
import * as DiscoveryActions from 'actions/DiscoveryActions';
import * as BlockchainActions from 'actions/BlockchainActions';
import * as ModalActions from 'actions/ModalActions';
import { init as initWeb3 } from 'actions/Web3Actions';
import * as WEB3 from 'actions/constants/web3';
import * as STORAGE from 'actions/constants/localStorage';
import * as CONNECT from 'actions/constants/TrezorConnect';
import { READY as BLOCKCHAIN_READY } from 'actions/constants/blockchain';
import type {
Middleware,
@ -32,9 +32,9 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
} else if (action.type === TRANSPORT.ERROR) {
// TODO: check if modal is open
// api.dispatch( push('/') );
} else if (action.type === TRANSPORT.START && api.getState().web3.length < 1) {
api.dispatch(initWeb3());
} else if (action.type === WEB3.READY) {
} else if (action.type === TRANSPORT.START) {
api.dispatch(BlockchainActions.init());
} else if (action.type === BLOCKCHAIN_READY) {
api.dispatch(TrezorConnectActions.postInit());
} else if (action.type === DEVICE.DISCONNECT) {
api.dispatch(TrezorConnectActions.deviceDisconnect(action.device));
@ -63,6 +63,12 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
api.dispatch(TrezorConnectActions.onSelectDevice(action.device));
} else if (action.type === CONNECT.COIN_CHANGED) {
api.dispatch(TrezorConnectActions.coinChanged(action.payload.network));
} else if (action.type === BLOCKCHAIN.BLOCK) {
api.dispatch(BlockchainActions.onBlockMined(action.payload.coin));
} else if (action.type === BLOCKCHAIN.NOTIFICATION) {
// api.dispatch(BlockchainActions.onNotification(action.payload));
} else if (action.type === BLOCKCHAIN.ERROR) {
api.dispatch( BlockchainActions.error(action.payload) );
}
return action;

View File

@ -6,13 +6,14 @@ import thunk from 'redux-thunk';
// import createHistory from 'history/createBrowserHistory';
// import { useRouterHistory } from 'react-router';
import createHistory from 'history/createHashHistory';
import { createLogger } from 'redux-logger';
import reducers from 'reducers';
import services from 'services';
import Raven from 'raven-js';
import RavenMiddleware from 'redux-raven-middleware';
// import type { Action, GetState, Store } from 'flowtype';
import type { Action, GetState, Store } from 'flowtype';
export const history: History = createHistory({ queryKey: false });
@ -30,17 +31,29 @@ const middleware = [
let composedEnhancers: any;
if (process.env.NODE_ENV === 'development') {
// const excludeLogger = (getState: GetState, action: Action): boolean => {
// //'@@router/LOCATION_CHANGE'
// const excluded: Array<string> = ['LOG_TO_EXCLUDE', 'log__add'];
// const pass: Array<string> = excluded.filter(act => action.type === act);
// return pass.length === 0;
// };
const excludeLogger = (getState: GetState, action: Action): boolean => {
//'@@router/LOCATION_CHANGE'
const excluded: Array<?string> = ['LOG_TO_EXCLUDE', 'log__add', undefined];
const pass: Array<?string> = excluded.filter(act => action.type === act);
return pass.length === 0;
};
const logger = createLogger({
level: 'info',
predicate: excludeLogger,
collapsed: true,
});
const devToolsExtension: ?Function = window.devToolsExtension;
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension());
}
composedEnhancers = compose(
applyMiddleware(...middleware, ...services),
applyMiddleware(logger, ...middleware, ...services),
...enhancers,
);
} else {
composedEnhancers = compose(
applyMiddleware(...middleware, ...services),

View File

@ -4,7 +4,7 @@
// import root from 'window-or-global';
// import Promise from 'es6-promise';
export async function resolveAfter<T>(msec: number, value: any = null): Promise<T> {
export async function resolveAfter(msec: number, value?: any): Promise<any> {
await new Promise((resolve) => {
//root.setTimeout(resolve, msec, value);
window.setTimeout(resolve, msec, value);

View File

@ -8,7 +8,6 @@ import LandingPage from './index';
export type StateProps = {
localStorage: $ElementType<State, 'localStorage'>,
modal: $ElementType<State, 'modal'>,
web3: $ElementType<State, 'web3'>,
wallet: $ElementType<State, 'wallet'>,
connect: $ElementType<State, 'connect'>,
router: $ElementType<State, 'router'>,
@ -29,7 +28,6 @@ export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
localStorage: state.localStorage,
modal: state.modal,
web3: state.web3,
wallet: state.wallet,
connect: state.connect,
router: state.router,

View File

@ -103,7 +103,6 @@ export default class InstallBridge extends Component<Props, State> {
}
onChange(value: InstallTarget) {
console.warn(value);
this.setState({
target: value,
});

View File

@ -113,6 +113,9 @@ const AccountMenu = (props: Props): ?React$Element<string> => {
const { config } = props.localStorage;
const selectedCoin = config.coins.find(c => c.network === location.state.network);
if (!selected || !selectedCoin) return;
const fiatRate = props.fiat.find(f => f.network === selectedCoin.network);
const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network);

View File

@ -1,6 +1,7 @@
/* @flow */
import * as React from 'react';
import { Notification } from 'components/Notification';
import { reconnect } from 'actions/DiscoveryActions';
import type { State } from 'flowtype';
@ -8,11 +9,12 @@ export type StateProps = {
className: string;
selectedAccount: $ElementType<State, 'selectedAccount'>,
wallet: $ElementType<State, 'wallet'>,
blockchain: $ElementType<State, 'blockchain'>,
children?: React.Node
}
export type DispatchProps = {
blockchainReconnect: typeof reconnect;
}
export type Props = StateProps & DispatchProps;
@ -28,8 +30,28 @@ const SelectedAccount = (props: Props) => {
const {
account,
discovery,
network
} = accountState;
if (!network) return; // TODO: this shouldn't happen. change accountState reducer?
const blockchain = props.blockchain.find(b => b.name === network.network);
if (blockchain && !blockchain.connected) {
return (
<Notification
type="error"
title="Backend not connected"
actions={
[{
label: "Try again",
callback: async () => {
await props.blockchainReconnect(network.network);
}
}]
} />
);
}
// account not found (yet). checking why...
if (!account) {
if (!discovery || discovery.waitingForDevice) {
@ -57,11 +79,6 @@ const SelectedAccount = (props: Props) => {
message="Connect device to load accounts"
/>
);
} if (discovery.waitingForBackend) {
// case 4: backend is not working
return (
<Notification type="warning" title="Backend not working" />
);
} if (discovery.completed) {
// case 5: account not found and discovery is completed
return (

View File

@ -42,7 +42,7 @@ const TopNavigationAccount = (props) => {
const basePath = `/device/${urlParams.device}/network/${urlParams.network}/account/${urlParams.account}`;
return (
<Wrapper>
<Wrapper className="account-tabs">
<StyledNavLink exact to={`${basePath}`}>Summary</StyledNavLink>
<StyledNavLink to={`${basePath}/send`}>Send</StyledNavLink>
<StyledNavLink to={`${basePath}/receive`}>Receive</StyledNavLink>

View File

@ -20,7 +20,7 @@ import TopNavigationAccount from './components/TopNavigationAccount';
import TopNavigationDeviceSettings from './components/TopNavigationDeviceSettings';
type WalletContainerProps = {
// wallet: $ElementType<State, 'wallet'>,
wallet: $ElementType<State, 'wallet'>,
children?: React.Node
}

View File

@ -2,6 +2,7 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { reconnect } from 'actions/DiscoveryActions';
import { showAddress } from 'actions/ReceiveActions';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State, Dispatch } from 'flowtype';
@ -28,12 +29,14 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
className: 'receive',
selectedAccount: state.selectedAccount,
wallet: state.wallet,
blockchain: state.blockchain,
receive: state.receive,
modal: state.modal,
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
blockchainReconnect: bindActionCreators(reconnect, dispatch),
showAddress: bindActionCreators(showAddress, dispatch),
});

View File

@ -5,6 +5,7 @@ import * as React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { reconnect } from 'actions/DiscoveryActions';
import SendFormActions from 'actions/SendFormActions';
import * as SessionStorageActions from 'actions/SessionStorageActions';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
@ -12,7 +13,6 @@ import type { State, Dispatch } from 'flowtype';
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from 'views/Wallet/components/SelectedAccount';
import AccountSend from './index';
type OwnProps = { }
export type StateProps = BaseStateProps & {
@ -33,6 +33,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
className: 'send-from',
selectedAccount: state.selectedAccount,
wallet: state.wallet,
blockchain: state.blockchain,
sendForm: state.sendForm,
fiat: state.fiat,
@ -40,6 +41,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
blockchainReconnect: bindActionCreators(reconnect, dispatch),
sendFormActions: bindActionCreators(SendFormActions, dispatch),
saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch),
});

View File

@ -237,7 +237,7 @@ class AccountSend extends Component<Props, State> {
}
getTokensSelectData(tokens: Array<Token>, accountNetwork: any) {
const tokensSelectData = tokens.map(t => ({ value: t.symbol, label: t.symbol }));
const tokensSelectData: Array<{ value: string, label: string }> = tokens.map(t => ({ value: t.symbol, label: t.symbol }));
tokensSelectData.unshift({ value: accountNetwork.symbol, label: accountNetwork.symbol });
return tokensSelectData;

View File

@ -3,6 +3,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import { reconnect } from 'actions/DiscoveryActions';
import * as TokenActions from 'actions/TokenActions';
import type { State, Dispatch } from 'flowtype';
@ -30,6 +31,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
className: 'summary',
selectedAccount: state.selectedAccount,
wallet: state.wallet,
blockchain: state.blockchain,
tokens: state.tokens,
summary: state.summary,
@ -38,6 +40,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
blockchainReconnect: bindActionCreators(reconnect, dispatch),
addToken: bindActionCreators(TokenActions.add, dispatch),
loadTokens: bindActionCreators(TokenActions.load, dispatch),
removeToken: bindActionCreators(TokenActions.remove, dispatch),

View File

@ -97,7 +97,7 @@ class AccountBalance extends Component<Props, State> {
let fiat = '';
if (fiatRate) {
accountBalance = new BigNumber(this.props.balance);
fiatRateValue = new BigNumber(fiatRate.value);
fiatRateValue = new BigNumber(fiatRate.value).toFixed(2);
fiat = accountBalance.times(fiatRateValue).toFixed(2);
}
@ -127,7 +127,7 @@ class AccountBalance extends Component<Props, State> {
{fiatRate && (
<BalanceWrapper>
<Label>Rate</Label>
<FiatValue>${fiatRateValue.toFixed(2)}</FiatValue>
<FiatValue>${fiatRateValue}</FiatValue>
<CoinBalace>1.00 {selectedCoin.symbol}</CoinBalace>
</BalanceWrapper>
)}

View File

@ -69,7 +69,7 @@ const AccountSummary = (props: Props) => {
} = props.selectedAccount;
// flow
if (!device || !account || !network) return null;
if (!device || !account || !network) return <SelectedAccount {...props} />;
const explorerLink: string = `${network.explorer.address}${account.address}`;
const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol);

View File

@ -2,7 +2,7 @@ import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import FlowWebpackPlugin from 'flow-webpack-plugin';
import MiniCssExtractPlugin from '../../trezor-connect/node_modules/mini-css-extract-plugin';
import {
TREZOR_CONNECT_ROOT,
@ -48,21 +48,17 @@ module.exports = {
{
test: /\.js?$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.less$/,
use: [
'babel-loader',
{
loader: 'eslint-loader',
options: {
emitWarning: true,
},
},
{
loader: 'stylelint-custom-processor-loader',
options: {
emitWarning: true,
configPath: '.stylelintrc',
},
loader: MiniCssExtractPlugin.loader,
options: { publicPath: '../' },
},
`${TREZOR_CONNECT_ROOT}/node_modules/css-loader`,
`${TREZOR_CONNECT_ROOT}/node_modules/less-loader`,
],
},
{
@ -115,7 +111,10 @@ module.exports = {
hints: false,
},
plugins: [
new FlowWebpackPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new HtmlWebpackPlugin({
chunks: ['index'],
template: `${SRC}index.html`,

955
yarn.lock

File diff suppressed because it is too large Load Diff