mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-28 03:08:30 +00:00
Merge pull request #25 from satoshilabs/feature/hybrid-backend
Feature/hybrid backend
This commit is contained in:
commit
507f80fde4
@ -47,6 +47,7 @@
|
|||||||
"react-router-redux": "next",
|
"react-router-redux": "next",
|
||||||
"react-scale-text": "^1.2.2",
|
"react-scale-text": "^1.2.2",
|
||||||
"react-select": "2.0.0",
|
"react-select": "2.0.0",
|
||||||
|
"react-sticky-el": "^1.0.20",
|
||||||
"react-transition-group": "^2.2.1",
|
"react-transition-group": "^2.2.1",
|
||||||
"redbox-react": "^1.6.0",
|
"redbox-react": "^1.6.0",
|
||||||
"redux": "4.0.0",
|
"redux": "4.0.0",
|
||||||
@ -56,8 +57,8 @@
|
|||||||
"styled-components": "^3.3.3",
|
"styled-components": "^3.3.3",
|
||||||
"styled-media-query": "^2.0.2",
|
"styled-media-query": "^2.0.2",
|
||||||
"styled-normalize": "^8.0.0",
|
"styled-normalize": "^8.0.0",
|
||||||
"trezor-connect": "5.0.30",
|
"trezor-connect": "5.0.31",
|
||||||
"web3": "^0.19.0",
|
"web3": "1.0.0-beta.35",
|
||||||
"webpack": "^4.16.3",
|
"webpack": "^4.16.3",
|
||||||
"webpack-bundle-analyzer": "^2.13.1",
|
"webpack-bundle-analyzer": "^2.13.1",
|
||||||
"whatwg-fetch": "^2.0.4",
|
"whatwg-fetch": "^2.0.4",
|
||||||
|
@ -3,21 +3,15 @@
|
|||||||
{
|
{
|
||||||
"name": "Ethereum",
|
"name": "Ethereum",
|
||||||
"symbol": "ETH",
|
"symbol": "ETH",
|
||||||
"network": "ethereum",
|
"network": "eth",
|
||||||
"bip44": "m/44'/60'/0'/0",
|
"bip44": "m/44'/60'/0'/0",
|
||||||
"chainId": 1,
|
"chainId": 1,
|
||||||
"defaultGasPrice": 64,
|
"defaultGasPrice": 64,
|
||||||
"defaultGasLimit": 21000,
|
"defaultGasLimit": 21000,
|
||||||
"defaultGasLimitTokens": 200000,
|
"defaultGasLimitTokens": 200000,
|
||||||
"tokens": "./data/ethereumTokens.json",
|
"tokens": "./data/ethereumTokens.json",
|
||||||
"backends": [
|
"web3": [
|
||||||
{
|
"wss://eth2.trezor.io/geth"
|
||||||
"name": "TREZOR Wallet - Ethereum",
|
|
||||||
"urls": [
|
|
||||||
"https://mainnet.infura.io/QGyVKozSUEh2YhL4s2G4",
|
|
||||||
"http://88.208.115.69"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"explorer": {
|
"explorer": {
|
||||||
"tx": "https://etherscan.io/tx/",
|
"tx": "https://etherscan.io/tx/",
|
||||||
@ -27,21 +21,15 @@
|
|||||||
{
|
{
|
||||||
"name": "Ethereum Classic",
|
"name": "Ethereum Classic",
|
||||||
"symbol": "ETC",
|
"symbol": "ETC",
|
||||||
"network": "ethereum-classic",
|
"network": "etc",
|
||||||
"chainId": 61,
|
"chainId": 61,
|
||||||
"bip44": "m/44'/61'/0'/0",
|
"bip44": "m/44'/61'/0'/0",
|
||||||
"defaultGasPrice": 64,
|
"defaultGasPrice": 64,
|
||||||
"defaultGasLimit": 21000,
|
"defaultGasLimit": 21000,
|
||||||
"defaultGasLimitTokens": 200000,
|
"defaultGasLimitTokens": 200000,
|
||||||
"tokens": "./data/ethereumClassicTokens.json",
|
"tokens": "./data/ethereumClassicTokens.json",
|
||||||
"backends": [
|
"web3": [
|
||||||
{
|
"wss://etc2.trezor.io/geth"
|
||||||
"name": "TREZOR Wallet - Ethereum",
|
|
||||||
"urls": [
|
|
||||||
"https://etc-geth.0xinfra.com/",
|
|
||||||
"https://mew.epool.io/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"explorer": {
|
"explorer": {
|
||||||
"tx": "https://gastracker.io/tx/",
|
"tx": "https://gastracker.io/tx/",
|
||||||
@ -50,50 +38,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Ethereum Ropsten",
|
"name": "Ethereum Ropsten",
|
||||||
"symbol": "tETH",
|
"symbol": "tROP",
|
||||||
"network": "ropsten",
|
"network": "trop",
|
||||||
"chainId": 3,
|
"chainId": 3,
|
||||||
"bip44": "m/44'/60'/0'/0",
|
"bip44": "m/44'/60'/0'/0",
|
||||||
"defaultGasPrice": 64,
|
"defaultGasPrice": 64,
|
||||||
"defaultGasLimit": 21000,
|
"defaultGasLimit": 21000,
|
||||||
"defaultGasLimitTokens": 200000,
|
"defaultGasLimitTokens": 200000,
|
||||||
"tokens": "./data/ropstenTokens.json",
|
"tokens": "./data/ropstenTokens.json",
|
||||||
"backends": [
|
"web3": [
|
||||||
{
|
"wss://ropsten1.trezor.io/geth"
|
||||||
"name": "TREZOR Wallet - Ethereum",
|
|
||||||
"urls": [
|
|
||||||
"https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4",
|
|
||||||
"http://10.34.2.5:8545"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"explorer": {
|
"explorer": {
|
||||||
"tx": "https://ropsten.etherscan.io/tx/",
|
"tx": "https://ropsten.etherscan.io/tx/",
|
||||||
"address": "https://ropsten.etherscan.io/address/"
|
"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/"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -4,5 +4,17 @@
|
|||||||
"name": "PLASMA",
|
"name": "PLASMA",
|
||||||
"symbol" :"PLASMA",
|
"symbol" :"PLASMA",
|
||||||
"decimals": 6
|
"decimals": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x58cda554935e4a1f2acbe15f8757400af275e084",
|
||||||
|
"name": "Trezor01",
|
||||||
|
"symbol": "T01",
|
||||||
|
"decimals": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0xa04761a776af2bed654a041430a063fd9d20fad4",
|
||||||
|
"name": "Trezor13",
|
||||||
|
"symbol": "T013",
|
||||||
|
"decimals": 13
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import * as ACCOUNT from 'actions/constants/account';
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
import type { Action, TrezorDevice } from 'flowtype';
|
import type { Action, TrezorDevice } from 'flowtype';
|
||||||
import type { State } from 'reducers/AccountsReducer';
|
import type { Account, State } from 'reducers/AccountsReducer';
|
||||||
|
|
||||||
export type AccountFromStorageAction = {
|
export type AccountFromStorageAction = {
|
||||||
type: typeof ACCOUNT.FROM_STORAGE,
|
type: typeof ACCOUNT.FROM_STORAGE,
|
||||||
@ -11,11 +11,12 @@ export type AccountFromStorageAction = {
|
|||||||
|
|
||||||
export type AccountCreateAction = {
|
export type AccountCreateAction = {
|
||||||
type: typeof ACCOUNT.CREATE,
|
type: typeof ACCOUNT.CREATE,
|
||||||
device: TrezorDevice,
|
payload: Account,
|
||||||
network: string,
|
}
|
||||||
index: number,
|
|
||||||
path: Array<number>,
|
export type AccountUpdateAction = {
|
||||||
address: string
|
type: typeof ACCOUNT.UPDATE,
|
||||||
|
payload: Account,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccountSetBalanceAction = {
|
export type AccountSetBalanceAction = {
|
||||||
@ -36,9 +37,10 @@ export type AccountSetNonceAction = {
|
|||||||
|
|
||||||
export type AccountAction =
|
export type AccountAction =
|
||||||
AccountFromStorageAction
|
AccountFromStorageAction
|
||||||
| AccountCreateAction
|
| AccountCreateAction
|
||||||
| AccountSetBalanceAction
|
| AccountUpdateAction
|
||||||
| AccountSetNonceAction;
|
| AccountSetBalanceAction
|
||||||
|
| AccountSetNonceAction;
|
||||||
|
|
||||||
export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => ({
|
export const setBalance = (address: string, network: string, deviceState: string, balance: string): Action => ({
|
||||||
type: ACCOUNT.SET_BALANCE,
|
type: ACCOUNT.SET_BALANCE,
|
||||||
@ -55,3 +57,8 @@ export const setNonce = (address: string, network: string, deviceState: string,
|
|||||||
deviceState,
|
deviceState,
|
||||||
nonce,
|
nonce,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const update = (account: Account): Action => ({
|
||||||
|
type: ACCOUNT.UPDATE,
|
||||||
|
payload: account
|
||||||
|
});
|
||||||
|
210
src/actions/BlockchainActions.js
Normal file
210
src/actions/BlockchainActions.js
Normal 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], ...a }) );
|
||||||
|
|
||||||
|
// 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) );
|
||||||
|
}
|
@ -6,12 +6,12 @@ import * as DISCOVERY from 'actions/constants/discovery';
|
|||||||
import * as ACCOUNT from 'actions/constants/account';
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import type {
|
import type {
|
||||||
ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice,
|
ThunkAction, AsyncAction, PromiseAction, Action, GetState, Dispatch, TrezorDevice,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import type { Discovery, State } from 'reducers/DiscoveryReducer';
|
import type { Discovery, State } from 'reducers/DiscoveryReducer';
|
||||||
import * as AccountsActions from './AccountsActions';
|
import * as AccountsActions from './AccountsActions';
|
||||||
|
import * as BlockchainActions from './BlockchainActions';
|
||||||
import { getNonceAsync, getBalanceAsync } from './Web3Actions';
|
import { setBalance as setTokenBalance } from './TokenActions';
|
||||||
|
|
||||||
|
|
||||||
export type DiscoveryStartAction = {
|
export type DiscoveryStartAction = {
|
||||||
@ -24,7 +24,7 @@ export type DiscoveryStartAction = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type DiscoveryWaitingAction = {
|
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,
|
device: TrezorDevice,
|
||||||
network: string
|
network: string
|
||||||
}
|
}
|
||||||
@ -44,147 +44,9 @@ export type DiscoveryAction = {
|
|||||||
type: typeof DISCOVERY.FROM_STORAGE,
|
type: typeof DISCOVERY.FROM_STORAGE,
|
||||||
payload: State
|
payload: State
|
||||||
} | DiscoveryStartAction
|
} | DiscoveryStartAction
|
||||||
| DiscoveryWaitingAction
|
| DiscoveryWaitingAction
|
||||||
| DiscoveryStopAction
|
| DiscoveryStopAction
|
||||||
| DiscoveryCompleteAction;
|
| 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const start = (device: TrezorDevice, network: string, ignoreCompleted?: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const selected = getState().wallet.selectedDevice;
|
const selected = getState().wallet.selectedDevice;
|
||||||
@ -203,26 +65,9 @@ export const start = (device: TrezorDevice, network: string, ignoreCompleted?: b
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const web3 = getState().web3.find(w3 => w3.network === network);
|
const discovery: State = getState().discovery;
|
||||||
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 discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network);
|
const discoveryProcess: ?Discovery = discovery.find(d => d.deviceState === device.state && d.network === network);
|
||||||
|
|
||||||
|
|
||||||
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
|
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: DISCOVERY.WAITING_FOR_DEVICE,
|
type: DISCOVERY.WAITING_FOR_DEVICE,
|
||||||
@ -232,15 +77,25 @@ export const start = (device: TrezorDevice, network: string, ignoreCompleted?: b
|
|||||||
return;
|
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) {
|
if (!discoveryProcess) {
|
||||||
dispatch(begin(device, network));
|
dispatch(begin(device, network))
|
||||||
} else if (discoveryProcess.completed && !ignoreCompleted) {
|
} else if (discoveryProcess.completed && !ignoreCompleted) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: DISCOVERY.COMPLETE,
|
type: DISCOVERY.COMPLETE,
|
||||||
device,
|
device,
|
||||||
network,
|
network,
|
||||||
});
|
});
|
||||||
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) {
|
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice || discoveryProcess.waitingForBlockchain) {
|
||||||
// discovery cycle was interrupted
|
// discovery cycle was interrupted
|
||||||
// start from beginning
|
// start from beginning
|
||||||
dispatch(begin(device, network));
|
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 { config } = getState().localStorage;
|
||||||
const coinToDiscover = config.coins.find(c => c.network === network);
|
const coinToDiscover = config.coins.find(c => c.network === network);
|
||||||
if (!coinToDiscover) return;
|
if (!coinToDiscover) return;
|
||||||
@ -272,10 +130,8 @@ begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch:
|
|||||||
useEmptyPassphrase: !device.instance,
|
useEmptyPassphrase: !device.instance,
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle TREZOR response error
|
// handle TREZOR response error
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
// TODO: check message
|
|
||||||
console.warn('DISCOVERY ERROR', response);
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: NOTIFICATION.ADD,
|
type: NOTIFICATION.ADD,
|
||||||
payload: {
|
payload: {
|
||||||
@ -312,14 +168,113 @@ begin = (device: TrezorDevice, network: string): AsyncAction => async (dispatch:
|
|||||||
basePath,
|
basePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// next iteration
|
||||||
dispatch(start(device, network));
|
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 => {
|
export const restore = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const selected = getState().wallet.selectedDevice;
|
const selected = getState().wallet.selectedDevice;
|
||||||
|
|
||||||
if (selected && selected.connected && selected.features) {
|
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) {
|
if (discoveryProcess) {
|
||||||
dispatch(start(selected, discoveryProcess.network));
|
dispatch(start(selected, discoveryProcess.network));
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ import type { State, PendingTx } from 'reducers/PendingTxReducer';
|
|||||||
export type PendingTxAction = {
|
export type PendingTxAction = {
|
||||||
type: typeof PENDING.FROM_STORAGE,
|
type: typeof PENDING.FROM_STORAGE,
|
||||||
payload: State
|
payload: State
|
||||||
|
} | {
|
||||||
|
type: typeof PENDING.ADD,
|
||||||
|
payload: PendingTx
|
||||||
} | {
|
} | {
|
||||||
type: typeof PENDING.TX_RESOLVED,
|
type: typeof PENDING.TX_RESOLVED,
|
||||||
tx: PendingTx,
|
tx: PendingTx,
|
||||||
|
@ -47,8 +47,7 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
|
|||||||
|| prevState.accounts !== state.accounts
|
|| prevState.accounts !== state.accounts
|
||||||
|| prevState.discovery !== state.discovery
|
|| prevState.discovery !== state.discovery
|
||||||
|| prevState.tokens !== state.tokens
|
|| prevState.tokens !== state.tokens
|
||||||
|| prevState.pending !== state.pending
|
|| prevState.pending !== state.pending) {
|
||||||
|| prevState.web3 !== state.web3) {
|
|
||||||
if (locationChange) {
|
if (locationChange) {
|
||||||
// dispose current account view
|
// dispose current account view
|
||||||
dispatch(dispose());
|
dispatch(dispose());
|
||||||
@ -59,7 +58,6 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
|
|||||||
const discovery = stateUtils.getDiscoveryProcess(state);
|
const discovery = stateUtils.getDiscoveryProcess(state);
|
||||||
const tokens = stateUtils.getAccountTokens(state, account);
|
const tokens = stateUtils.getAccountTokens(state, account);
|
||||||
const pending = stateUtils.getAccountPendingTx(state.pending, account);
|
const pending = stateUtils.getAccountPendingTx(state.pending, account);
|
||||||
const web3 = stateUtils.getWeb3(state);
|
|
||||||
|
|
||||||
const payload: $ElementType<State, 'selectedAccount'> = {
|
const payload: $ElementType<State, 'selectedAccount'> = {
|
||||||
location: location.pathname,
|
location: location.pathname,
|
||||||
@ -68,7 +66,6 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
|
|||||||
discovery,
|
discovery,
|
||||||
tokens,
|
tokens,
|
||||||
pending,
|
pending,
|
||||||
web3,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let needUpdate: boolean = false;
|
let needUpdate: boolean = false;
|
||||||
|
@ -12,6 +12,7 @@ import * as SEND from 'actions/constants/send';
|
|||||||
import { initialState } from 'reducers/SendFormReducer';
|
import { initialState } from 'reducers/SendFormReducer';
|
||||||
import { findToken } from 'reducers/TokensReducer';
|
import { findToken } from 'reducers/TokensReducer';
|
||||||
import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils';
|
import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils';
|
||||||
|
import * as stateUtils from 'reducers/utils';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
@ -27,7 +28,8 @@ import type { State, FeeLevel } from 'reducers/SendFormReducer';
|
|||||||
import type { Account } from 'reducers/AccountsReducer';
|
import type { Account } from 'reducers/AccountsReducer';
|
||||||
import type { Props } from 'views/Wallet/views/AccountSend/Container';
|
import type { Props } from 'views/Wallet/views/AccountSend/Container';
|
||||||
import * as SessionStorageActions from './SessionStorageActions';
|
import * as SessionStorageActions from './SessionStorageActions';
|
||||||
import { estimateGas, pushTx } from './Web3Actions';
|
import { prepareEthereumTx, serializeEthereumTx } from './TxActions';
|
||||||
|
import * as BlockchainActions from './BlockchainActions';
|
||||||
|
|
||||||
export type SendTxAction = {
|
export type SendTxAction = {
|
||||||
type: typeof SEND.TX_COMPLETE,
|
type: typeof SEND.TX_COMPLETE,
|
||||||
@ -223,14 +225,13 @@ export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLi
|
|||||||
|
|
||||||
|
|
||||||
// initialize component
|
// initialize component
|
||||||
export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
network,
|
network,
|
||||||
web3,
|
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
|
|
||||||
if (!account || !network || !web3) return;
|
if (!account || !network) return;
|
||||||
|
|
||||||
const stateFromStorage = SessionStorageActions.load(getState().router.location.pathname);
|
const stateFromStorage = SessionStorageActions.load(getState().router.location.pathname);
|
||||||
if (stateFromStorage) {
|
if (stateFromStorage) {
|
||||||
@ -243,7 +244,10 @@ export const init = (): ThunkAction => (dispatch: Dispatch, getState: GetState):
|
|||||||
|
|
||||||
// TODO: check if there are some unfinished tx in localStorage
|
// 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 gasLimit: string = network.defaultGasLimit.toString();
|
||||||
const feeLevels: Array<FeeLevel> = getFeeLevels(network.symbol, gasPrice, gasLimit);
|
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 estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
web3,
|
|
||||||
network,
|
network,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
if (!web3 || !network) return;
|
if (!network) return;
|
||||||
|
|
||||||
const w3 = web3.web3;
|
|
||||||
|
|
||||||
const state: State = getState().sendForm;
|
const state: State = getState().sendForm;
|
||||||
const requestedData = state.data;
|
const requestedData = state.data;
|
||||||
@ -732,14 +733,7 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: allow data starting with 0x ...
|
const gasLimit: number = await dispatch( BlockchainActions.estimateGasLimit(network.network, state.data, state.amount, state.gasPrice) );
|
||||||
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')),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (getState().sendForm.data === requestedData) {
|
if (getState().sendForm.data === requestedData) {
|
||||||
dispatch(onGasLimitChange(gasLimit.toString()));
|
dispatch(onGasLimitChange(gasLimit.toString()));
|
||||||
@ -777,56 +771,29 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
network,
|
network,
|
||||||
web3,
|
|
||||||
pending,
|
pending,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
if (!account || !web3 || !network) return;
|
|
||||||
|
if (!account || !network) return;
|
||||||
|
|
||||||
const currentState: State = getState().sendForm;
|
const currentState: State = getState().sendForm;
|
||||||
|
|
||||||
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
const isToken: boolean = currentState.currency !== currentState.networkSymbol;
|
||||||
const w3 = web3.web3;
|
|
||||||
|
|
||||||
const address_n = account.addressPath;
|
const address_n = account.addressPath;
|
||||||
|
const pendingNonce: number = stateUtils.getPendingNonce(pending);
|
||||||
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 nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
|
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
|
||||||
|
|
||||||
console.warn('NONCE', nonce, account.nonce, pendingNonce);
|
const txData = await dispatch( prepareEthereumTx({
|
||||||
|
network: network.network,
|
||||||
const txData = {
|
token: isToken ? findToken(getState().tokens, account.address, currentState.currency, account.deviceState) : null,
|
||||||
address_n,
|
from: account.address,
|
||||||
// from: currentAddress.address
|
to: currentState.address,
|
||||||
to: txAddress,
|
amount: currentState.amount,
|
||||||
value: txAmount,
|
data: currentState.data,
|
||||||
data,
|
gasLimit: currentState.gasLimit,
|
||||||
chainId: web3.chainId,
|
gasPrice: currentState.gasPrice,
|
||||||
nonce: w3.toHex(nonce),
|
nonce
|
||||||
gasLimit: w3.toHex(currentState.gasLimit),
|
}) );
|
||||||
gasPrice: w3.toHex(EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei')),
|
|
||||||
r: '',
|
|
||||||
s: '',
|
|
||||||
v: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
@ -861,9 +828,17 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
txData.v = signedTransaction.payload.v;
|
txData.v = signedTransaction.payload.v;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tx = new EthereumjsTx(txData);
|
const serializedTx: string = await dispatch( serializeEthereumTx(txData) );
|
||||||
const serializedTx = `0x${tx.serialize().toString('hex')}`;
|
const push = await TrezorConnect.pushTransaction({
|
||||||
const txid: string = await pushTx(w3, serializedTx);
|
tx: serializedTx,
|
||||||
|
coin: network.network
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!push.success) {
|
||||||
|
throw new Error( push.payload.error );
|
||||||
|
}
|
||||||
|
|
||||||
|
const txid = push.payload.txid;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.TX_COMPLETE,
|
type: SEND.TX_COMPLETE,
|
||||||
@ -871,7 +846,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
selectedCurrency: currentState.currency,
|
selectedCurrency: currentState.currency,
|
||||||
amount: currentState.amount,
|
amount: currentState.amount,
|
||||||
total: currentState.total,
|
total: currentState.total,
|
||||||
tx,
|
tx: txData,
|
||||||
nonce,
|
nonce,
|
||||||
txid,
|
txid,
|
||||||
txData,
|
txData,
|
||||||
|
@ -9,7 +9,7 @@ import type {
|
|||||||
import type { State, Token } from 'reducers/TokensReducer';
|
import type { State, Token } from 'reducers/TokensReducer';
|
||||||
import type { Account } from 'reducers/AccountsReducer';
|
import type { Account } from 'reducers/AccountsReducer';
|
||||||
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
||||||
import { getTokenInfoAsync, getTokenBalanceAsync } from './Web3Actions';
|
import * as BlockchainActions from './BlockchainActions';
|
||||||
|
|
||||||
export type TokenAction = {
|
export type TokenAction = {
|
||||||
type: typeof TOKEN.FROM_STORAGE,
|
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)
|
// when options is a large list (>200 items)
|
||||||
return result.slice(0, 100);
|
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) {
|
if (info) {
|
||||||
return [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> => {
|
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> => {
|
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 = {
|
const tkn: Token = {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
deviceState: account.deviceState,
|
deviceState: account.deviceState,
|
||||||
@ -88,7 +81,7 @@ export const add = (token: NetworkToken, account: Account): AsyncAction => async
|
|||||||
payload: tkn,
|
payload: tkn,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, tkn);
|
const tokenBalance = await dispatch( BlockchainActions.getTokenBalance(tkn) );
|
||||||
dispatch(setBalance(token.address, account.address, tokenBalance));
|
dispatch(setBalance(token.address, account.address, tokenBalance));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import TrezorConnect, {
|
import TrezorConnect, {
|
||||||
DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT,
|
UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
@ -9,14 +9,15 @@ import { getDuplicateInstanceNumber } from 'reducers/utils';
|
|||||||
|
|
||||||
import { push } from 'react-router-redux';
|
import { push } from 'react-router-redux';
|
||||||
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
DeviceMessage,
|
DeviceMessage,
|
||||||
UiMessage,
|
|
||||||
TransportMessage,
|
|
||||||
DeviceMessageType,
|
DeviceMessageType,
|
||||||
TransportMessageType,
|
UiMessage,
|
||||||
UiMessageType,
|
UiMessageType,
|
||||||
|
TransportMessage,
|
||||||
|
TransportMessageType,
|
||||||
|
BlockchainMessage,
|
||||||
|
BlockchainMessageType,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
|
|
||||||
import type {
|
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
|
// $FlowIssue LOCAL not declared
|
||||||
|
window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/';
|
||||||
// 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 {
|
try {
|
||||||
await TrezorConnect.init({
|
await TrezorConnect.init({
|
||||||
|
69
src/actions/TxActions.js
Normal file
69
src/actions/TxActions.js
Normal 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() );
|
||||||
|
}
|
@ -5,8 +5,11 @@ import { LOCATION_CHANGE } from 'react-router-redux';
|
|||||||
import * as WALLET from 'actions/constants/wallet';
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
import * as stateUtils from 'reducers/utils';
|
import * as stateUtils from 'reducers/utils';
|
||||||
|
|
||||||
import type
|
import type {
|
||||||
{
|
Account,
|
||||||
|
Coin,
|
||||||
|
Discovery,
|
||||||
|
Token,
|
||||||
Device,
|
Device,
|
||||||
TrezorDevice,
|
TrezorDevice,
|
||||||
RouterLocationState,
|
RouterLocationState,
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
|
import HDKey from 'hdkey';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import type {
|
import EthereumjsUtil from 'ethereumjs-util';
|
||||||
ContractFactory,
|
import EthereumjsUnits from 'ethereumjs-units';
|
||||||
EstimateGasOptions,
|
import EthereumjsTx from 'ethereumjs-tx';
|
||||||
TransactionStatus,
|
// import InputDataDecoder from 'ethereum-input-data-decoder';
|
||||||
TransactionReceipt,
|
import TrezorConnect from 'trezor-connect';
|
||||||
} from 'web3';
|
import type { EstimateGasOptions, TransactionStatus, TransactionReceipt } from 'web3';
|
||||||
import type BigNumber from 'bignumber.js';
|
import { strip } from 'utils/ethUtils';
|
||||||
import * as WEB3 from 'actions/constants/web3';
|
import * as WEB3 from 'actions/constants/web3';
|
||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
GetState,
|
||||||
|
ThunkAction,
|
||||||
AsyncAction,
|
AsyncAction,
|
||||||
|
PromiseAction,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
|
import type { EthereumAccount } from 'trezor-connect';
|
||||||
import type { Account } from 'reducers/AccountsReducer';
|
import type { Account } from 'reducers/AccountsReducer';
|
||||||
import type { PendingTx } from 'reducers/PendingTxReducer';
|
import type { PendingTx } from 'reducers/PendingTxReducer';
|
||||||
import type { Web3Instance } from 'reducers/Web3Reducer';
|
import type { Web3Instance } from 'reducers/Web3Reducer';
|
||||||
@ -40,379 +45,266 @@ export type Web3UpdateGasPriceAction = {
|
|||||||
export type Web3Action = {
|
export type Web3Action = {
|
||||||
type: typeof WEB3.READY,
|
type: typeof WEB3.READY,
|
||||||
} | {
|
} | {
|
||||||
type: typeof WEB3.CREATE,
|
type: typeof WEB3.START,
|
||||||
|
} | {
|
||||||
|
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
|
||||||
instance: Web3Instance
|
instance: Web3Instance
|
||||||
}
|
} | Web3UpdateBlockAction
|
||||||
| Web3UpdateBlockAction
|
| Web3UpdateGasPriceAction;
|
||||||
| Web3UpdateGasPriceAction;
|
|
||||||
|
|
||||||
export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
|
export const initWeb3 = (network: string, urlIndex: number = 0): PromiseAction<Web3Instance> => async (dispatch: Dispatch, getState: GetState): Promise<Web3Instance> => {
|
||||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
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 { config, ERC20Abi } = getState().localStorage;
|
||||||
|
const coin = config.coins.find(c => c.network === network);
|
||||||
const coin = config.coins[coinIndex];
|
|
||||||
if (!coin) {
|
if (!coin) {
|
||||||
// all instances done
|
// coin not found
|
||||||
dispatch({
|
reject(new Error(`Network ${ network} not found in application config.`));
|
||||||
type: WEB3.READY,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { network } = coin;
|
// get first url
|
||||||
const urls = coin.backends[0].urls;
|
const url = coin.web3[ urlIndex ];
|
||||||
|
if (!url) {
|
||||||
let web3host: string = urls[0];
|
reject(new Error('Web3 backend is not responding'));
|
||||||
|
|
||||||
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));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const erc20 = web3.eth.contract(ERC20Abi);
|
const web3 = new Web3( new Web3.providers.WebsocketProvider(url) );
|
||||||
|
|
||||||
dispatch({
|
const onConnect = async () => {
|
||||||
type: WEB3.CREATE,
|
|
||||||
instance: {
|
const latestBlock = await web3.eth.getBlockNumber();
|
||||||
|
const gasPrice = await web3.eth.getGasPrice();
|
||||||
|
|
||||||
|
const instance = {
|
||||||
network,
|
network,
|
||||||
web3,
|
web3,
|
||||||
chainId: coin.chainId,
|
chainId: coin.chainId,
|
||||||
erc20,
|
erc20: new web3.eth.Contract(ERC20Abi),
|
||||||
latestBlock: '0',
|
latestBlock,
|
||||||
gasPrice: '0',
|
gasPrice,
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockHash) {
|
dispatch({
|
||||||
dispatch({
|
type: WEB3.CREATE,
|
||||||
type: WEB3.BLOCK_UPDATED,
|
instance,
|
||||||
network,
|
});
|
||||||
blockHash,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: filter only current device
|
resolve(instance);
|
||||||
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));
|
|
||||||
|
|
||||||
// dispatch( getBalance(account) );
|
const onEnd = async () => {
|
||||||
// TODO: check if nonce was updated,
|
|
||||||
// update tokens balance,
|
web3.currentProvider.reset();
|
||||||
// update account balance,
|
const instance = getState().web3.find(w3 => w3.network === network);
|
||||||
// update pending transactions
|
|
||||||
|
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);
|
web3.currentProvider.on('connect', onConnect);
|
||||||
tokens.forEach(token => dispatch(getTokenBalance(token)));
|
web3.currentProvider.on('end', onEnd);
|
||||||
|
web3.currentProvider.on('error', onEnd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(getGasPrice(network));
|
export const discoverAccount = (address: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch, getState: GetState): Promise<EthereumAccount> => {
|
||||||
|
const instance: Web3Instance = await dispatch( initWeb3(network) );
|
||||||
const pending = getState().pending.filter(p => p.network === network);
|
const balance = await instance.web3.eth.getBalance(address);
|
||||||
pending.forEach(pendingTx => dispatch(getTransactionReceipt(pendingTx)));
|
const nonce = await instance.web3.eth.getTransactionCount(address);
|
||||||
};
|
return {
|
||||||
|
address,
|
||||||
// latestBlockFilter.watch(onBlockMined);
|
transactions: 0,
|
||||||
onBlockMined(new Error('manually_triggered_error'), undefined);
|
block: 0,
|
||||||
|
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
|
nonce
|
||||||
// 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 resolvePendingTransactions = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
export function getGasPrice(network: string): AsyncAction {
|
const instance: Web3Instance = await dispatch( initWeb3(network) );
|
||||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
const pending = getState().pending.filter(p => p.network === network);
|
||||||
const index: number = getState().web3.findIndex(w3 => w3.network === network);
|
for (const tx of pending) {
|
||||||
|
const status = await instance.web3.eth.getTransaction(tx.id);
|
||||||
const web3instance = getState().web3[index];
|
if (!status) {
|
||||||
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) {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.TX_NOT_FOUND,
|
type: PENDING.TX_NOT_FOUND,
|
||||||
tx,
|
tx,
|
||||||
});
|
});
|
||||||
} else if (status && status.blockNumber) {
|
} else {
|
||||||
web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => {
|
const receipt = await instance.web3.eth.getTransactionReceipt(tx.id);
|
||||||
if (receipt) {
|
if (receipt) {
|
||||||
if (status.gas !== receipt.gasUsed) {
|
if (status.gas !== receipt.gasUsed) {
|
||||||
dispatch({
|
|
||||||
type: PENDING.TX_TOKEN_ERROR,
|
|
||||||
tx,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.TX_RESOLVED,
|
type: PENDING.TX_TOKEN_ERROR,
|
||||||
tx,
|
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) => {
|
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
|
||||||
web3.eth.getTransaction(txid, (error, result) => {
|
const instance = await dispatch( initWeb3(token.network) );
|
||||||
if (error) {
|
const contract = instance.erc20.clone();
|
||||||
reject(error);
|
contract.options.address = token.address;
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getBalanceAsync = (web3: Web3, address: string): Promise<BigNumber> => new Promise((resolve, reject) => {
|
const balance = await contract.methods.balanceOf(token.ethAddress).call();
|
||||||
web3.eth.getBalance(address, (error: Error, result: BigNumber) => {
|
return new BigNumber(balance).dividedBy(Math.pow(10, token.decimals)).toString(10);
|
||||||
if (error) {
|
};
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise<string> => new Promise((resolve, reject) => {
|
export const getCurrentGasPrice = (network: string): PromiseAction<string> => async (dispatch: Dispatch, getState: GetState): Promise<string> => {
|
||||||
const contract = erc20.at(token.address);
|
const instance = getState().web3.find(w3 => w3.network === network);
|
||||||
contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => {
|
if (instance) {
|
||||||
if (error) {
|
return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei');
|
||||||
reject(error);
|
} else {
|
||||||
} else {
|
throw "0";
|
||||||
const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
|
}
|
||||||
resolve(newBalance);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getNonceAsync = (web3: Web3, address: string): Promise<number> => new Promise((resolve, reject) => {
|
export const updateGasPrice = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
web3.eth.getTransactionCount(address, (error: Error, result: number) => {
|
try {
|
||||||
if (error) {
|
const instance = await dispatch( initWeb3(network) );
|
||||||
reject(error);
|
const gasPrice = await instance.web3.eth.getGasPrice();
|
||||||
} else {
|
if (instance.gasPrice !== gasPrice) {
|
||||||
resolve(result);
|
dispatch({
|
||||||
}
|
type: WEB3.GAS_PRICE_UPDATED,
|
||||||
});
|
network,
|
||||||
});
|
gasPrice
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -6,6 +6,7 @@ export const DISPOSE: 'account__dispose' = 'account__dispose';
|
|||||||
|
|
||||||
export const CREATE: 'account__create' = 'account__create';
|
export const CREATE: 'account__create' = 'account__create';
|
||||||
export const REMOVE: 'account__remove' = 'account__remove';
|
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_BALANCE: 'account__set_balance' = 'account__set_balance';
|
||||||
export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce';
|
export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce';
|
||||||
export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage';
|
export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage';
|
||||||
|
6
src/actions/constants/blockchain.js
Normal file
6
src/actions/constants/blockchain.js
Normal 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';
|
@ -5,5 +5,5 @@ export const START: 'discovery__start' = 'discovery__start';
|
|||||||
export const STOP: 'discovery__stop' = 'discovery__stop';
|
export const STOP: 'discovery__stop' = 'discovery__stop';
|
||||||
export const COMPLETE: 'discovery__complete' = 'discovery__complete';
|
export const COMPLETE: 'discovery__complete' = 'discovery__complete';
|
||||||
export const WAITING_FOR_DEVICE: 'discovery__waiting_for_device' = 'discovery__waiting_for_device';
|
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';
|
export const FROM_STORAGE: 'discovery__from_storage' = 'discovery__from_storage';
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
export const FROM_STORAGE: 'pending__from_storage' = 'pending__from_storage';
|
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_RESOLVED: 'pending__tx_resolved' = 'pending__tx_resolved';
|
||||||
export const TX_NOT_FOUND: 'pending__tx_not_found' = 'pending__tx_not_found';
|
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';
|
export const TX_TOKEN_ERROR: 'pending__tx_token_error' = 'pending__tx_token_error';
|
@ -8,3 +8,4 @@ export const READY: 'web3__ready' = 'web3__ready';
|
|||||||
export const BLOCK_UPDATED: 'web3__block_updated' = 'web3__block_updated';
|
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 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';
|
@ -7,6 +7,8 @@ import type {
|
|||||||
Middleware as ReduxMiddleware,
|
Middleware as ReduxMiddleware,
|
||||||
ThunkAction as ReduxThunkAction,
|
ThunkAction as ReduxThunkAction,
|
||||||
AsyncAction as ReduxAsyncAction,
|
AsyncAction as ReduxAsyncAction,
|
||||||
|
PromiseAction as ReduxPromiseAction,
|
||||||
|
ThunkDispatch as ReduxThunkDispatch,
|
||||||
PlainDispatch as ReduxPlainDispatch,
|
PlainDispatch as ReduxPlainDispatch,
|
||||||
} from 'redux';
|
} from 'redux';
|
||||||
|
|
||||||
@ -15,6 +17,7 @@ import type { ReducersState } from 'reducers';
|
|||||||
// Actions
|
// Actions
|
||||||
import type { SelectedAccountAction } from 'actions/SelectedAccountActions';
|
import type { SelectedAccountAction } from 'actions/SelectedAccountActions';
|
||||||
import type { AccountAction } from 'actions/AccountsActions';
|
import type { AccountAction } from 'actions/AccountsActions';
|
||||||
|
import type { BlockchainAction } from 'actions/BlockchainActions';
|
||||||
import type { DiscoveryAction } from 'actions/DiscoveryActions';
|
import type { DiscoveryAction } from 'actions/DiscoveryActions';
|
||||||
import type { StorageAction } from 'actions/LocalStorageActions';
|
import type { StorageAction } from 'actions/LocalStorageActions';
|
||||||
import type { LogAction } from 'actions/LogActions';
|
import type { LogAction } from 'actions/LogActions';
|
||||||
@ -37,6 +40,7 @@ import type {
|
|||||||
DeviceFirmwareStatus,
|
DeviceFirmwareStatus,
|
||||||
DeviceMessageType,
|
DeviceMessageType,
|
||||||
TransportMessageType,
|
TransportMessageType,
|
||||||
|
BlockchainMessageType,
|
||||||
UiMessageType,
|
UiMessageType,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
|
|
||||||
@ -102,6 +106,11 @@ type UiEventAction = {
|
|||||||
// },
|
// },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BlockchainEventAction = {
|
||||||
|
type: BlockchainMessageType,
|
||||||
|
payload: any,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: join this message with uiMessage
|
// TODO: join this message with uiMessage
|
||||||
type IFrameHandshake = {
|
type IFrameHandshake = {
|
||||||
type: 'iframe_handshake',
|
type: 'iframe_handshake',
|
||||||
@ -114,9 +123,11 @@ export type Action =
|
|||||||
| TransportEventAction
|
| TransportEventAction
|
||||||
| DeviceEventAction
|
| DeviceEventAction
|
||||||
| UiEventAction
|
| UiEventAction
|
||||||
|
| BlockchainEventAction
|
||||||
|
|
||||||
| SelectedAccountAction
|
| SelectedAccountAction
|
||||||
| AccountAction
|
| AccountAction
|
||||||
|
| BlockchainAction
|
||||||
| DiscoveryAction
|
| DiscoveryAction
|
||||||
| StorageAction
|
| StorageAction
|
||||||
| LogAction
|
| LogAction
|
||||||
@ -154,6 +165,7 @@ export type Middleware = ReduxMiddleware<State, Action>;
|
|||||||
|
|
||||||
export type ThunkAction = ReduxThunkAction<State, Action>;
|
export type ThunkAction = ReduxThunkAction<State, Action>;
|
||||||
export type AsyncAction = ReduxAsyncAction<State, Action>;
|
export type AsyncAction = ReduxAsyncAction<State, Action>;
|
||||||
|
export type PromiseAction<R> = ReduxPromiseAction<State, Action, R>;
|
||||||
|
|
||||||
export type Store = ReduxStore<State, Action>;
|
export type Store = ReduxStore<State, Action>;
|
||||||
export type GetState = () => State;
|
export type GetState = () => State;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
declare module 'bignumber.js' {
|
declare module 'bignumber.js' {
|
||||||
declare type $npm$big$number$object = number | string | T_BigNumber
|
declare type $npm$big$number$object = number | string | T_BigNumber
|
||||||
declare type $npm$cmp$result = -1 | 0 | 1
|
declare type $npm$cmp$result = -1 | 0 | 1
|
||||||
@ -24,7 +26,7 @@ declare module 'bignumber.js' {
|
|||||||
constructor(value: $npm$big$number$object): T_BigNumber;
|
constructor(value: $npm$big$number$object): T_BigNumber;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
abs(): BigNumber;
|
abs(): T_BigNumber;
|
||||||
cmp(n: $npm$big$number$object): $npm$cmp$result;
|
cmp(n: $npm$big$number$object): $npm$cmp$result;
|
||||||
div(n: $npm$big$number$object): T_BigNumber;
|
div(n: $npm$big$number$object): T_BigNumber;
|
||||||
dividedBy(n: $npm$big$number$object): T_BigNumber;
|
dividedBy(n: $npm$big$number$object): T_BigNumber;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
declare module 'redux' {
|
declare module 'redux' {
|
||||||
/*
|
/*
|
||||||
|
|
||||||
S = State
|
S = State
|
||||||
A = Action
|
A = Action
|
||||||
D = Dispatch
|
D = Dispatch
|
||||||
|
R = Promise response
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare export type DispatchAPI<A> = (action: A) => A;
|
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 ThunkAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => void;
|
||||||
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
||||||
|
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 ThunkDispatch<S, A> = (action: ThunkAction<S, A>) => void;
|
||||||
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
||||||
|
declare export type PromiseDispatch<S, A> = <R>(action: PromiseAction<S, A, R>) => Promise<R>;
|
||||||
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
|
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
|
||||||
/* NEW: Dispatch is now a combination of these different dispatch types */
|
/* NEW: Dispatch is now a combination of these different dispatch types */
|
||||||
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A>;
|
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A>;
|
||||||
|
|
||||||
declare export type MiddlewareAPI<S, A> = {
|
declare export type MiddlewareAPI<S, A> = {
|
||||||
// dispatch: Dispatch<S, A>;
|
// dispatch: Dispatch<S, A>;
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
import type BigNumber from 'bignumber.js';
|
import type BigNumber from 'bignumber.js';
|
||||||
import type { EthereumUnitT, EthereumAddressT } from 'ethereum-types';
|
import type { EthereumUnitT, EthereumAddressT } from 'ethereum-types';
|
||||||
|
|
||||||
declare module 'web3' {
|
declare module 'web3' {
|
||||||
declare type ProviderT = {
|
declare type HttpProviderT = {
|
||||||
host: string;
|
host: string;
|
||||||
timeout: number;
|
timeout: number;
|
||||||
isConnected: () => boolean;
|
isConnected: () => boolean;
|
||||||
@ -10,14 +12,28 @@ declare module 'web3' {
|
|||||||
sendAsync: (payload: any, callback: (error: Error, result: any) => void) => any;
|
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 {
|
declare class Web3T {
|
||||||
static providers: {
|
static providers: {
|
||||||
HttpProvider: (host: string, timeout?: number) => ProviderT;
|
HttpProvider: (host: string, timeout?: number) => HttpProviderT;
|
||||||
|
WebsocketProvider: (host: string, options?: any) => WebsocketProviderT;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(ProviderT): Web3T;
|
// constructor(HttpProviderT): Web3T;
|
||||||
currentProvider: ProviderT;
|
constructor(WebsocketProviderT): Web3T;
|
||||||
|
// currentProvider: HttpProviderT;
|
||||||
|
currentProvider: WebsocketProviderT;
|
||||||
eth: Eth;
|
eth: Eth;
|
||||||
|
utils: Utils;
|
||||||
|
|
||||||
toHex: (str: string | number) => string;
|
toHex: (str: string | number) => string;
|
||||||
isAddress: (address: string) => boolean;
|
isAddress: (address: string) => boolean;
|
||||||
@ -78,20 +94,33 @@ declare module 'web3' {
|
|||||||
transactionIndex: number
|
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 {
|
declare class Eth {
|
||||||
getGasPrice: (callback: (error: Error, gasPrice: string) => void) => void,
|
getBalance: (address: string) => Promise<string>;
|
||||||
getBalance: (address: string, callback: (error: Error, balance: BigNumber) => void) => void,
|
getTransactionCount: (address: string) => Promise<number>;
|
||||||
getTransactionCount: (address: string, callback: (error: Error, result: number) => void) => void,
|
estimateGas: (options: EstimateGasOptions) => Promise<number>;
|
||||||
getTransaction: (txid: string, callback: (error: Error, result: TransactionStatus) => void) => void,
|
getGasPrice: () => Promise<string>;
|
||||||
getTransactionReceipt: (txid: string, callback: (error: Error, result: TransactionReceipt) => void) => void,
|
getBlockNumber: () => Promise<number>;
|
||||||
getBlockNumber: (callback: (error: Error, blockNumber: number) => void) => void,
|
Contract: (abi: Array<Object>, options?: any) => Contract;
|
||||||
getBlock: (hash: string, callback: (error: Error, result: any) => void) => void,
|
sendSignedTransaction: (tx: string) => PromiseEvent<TransactionReceipt>;
|
||||||
// getAccounts: (callback: (error: Error, accounts: Array<EthereumAddressT>) => void) => void,
|
getTransaction: (txid: string) => Promise<TransactionStatus>;
|
||||||
// sign: (payload: string, signer: EthereumAddressT) => Promise<string>,
|
getTransactionReceipt: (txid: string) => Promise<TransactionReceipt>;
|
||||||
contract: (abi: Array<Object>) => ContractFactory,
|
subscribe: (type: string, callback: Function) => any;
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare export class Filter {
|
declare export class Filter {
|
||||||
@ -99,108 +128,40 @@ declare module 'web3' {
|
|||||||
stopWatching: (callback: any) => void,
|
stopWatching: (callback: any) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
declare export class ContractFactory {
|
declare type ContractMethod<T> = {
|
||||||
// constructor(abi: Array<Object>);
|
call: () => Promise<T>;
|
||||||
eth: Eth;
|
|
||||||
abi: Array<Object>;
|
|
||||||
at: (address: string, callback: ?(error: Error, contract: Contract) => void) => Contract; // TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare export class Contract {
|
declare export class Contract {
|
||||||
name: {
|
clone: () => Contract;
|
||||||
call: (callback: (error: Error, name: string) => void) => void;
|
|
||||||
},
|
options: {
|
||||||
symbol: {
|
address: string;
|
||||||
call: (callback: (error: Error, symbol: string) => void) => void;
|
jsonInterface: JSON;
|
||||||
},
|
};
|
||||||
decimals: {
|
|
||||||
call: (callback: (error: Error, decimals: BigNumber) => void) => void;
|
methods: {
|
||||||
},
|
name: () => ContractMethod<string>;
|
||||||
balanceOf: (address: string, callback: (error: Error, balance: BigNumber) => void) => void,
|
symbol: () => ContractMethod<string>;
|
||||||
transfer: any,
|
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 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
@ -7,7 +7,6 @@ import * as ACCOUNT from 'actions/constants/account';
|
|||||||
|
|
||||||
import type { Action, TrezorDevice } from 'flowtype';
|
import type { Action, TrezorDevice } from 'flowtype';
|
||||||
import type {
|
import type {
|
||||||
AccountCreateAction,
|
|
||||||
AccountSetBalanceAction,
|
AccountSetBalanceAction,
|
||||||
AccountSetNonceAction,
|
AccountSetNonceAction,
|
||||||
} from 'actions/AccountsActions';
|
} from 'actions/AccountsActions';
|
||||||
@ -22,6 +21,8 @@ export type Account = {
|
|||||||
+address: string;
|
+address: string;
|
||||||
balance: string;
|
balance: string;
|
||||||
nonce: number;
|
nonce: number;
|
||||||
|
block: number;
|
||||||
|
transactions: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = Array<Account>;
|
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);
|
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
|
// TODO check with device_id
|
||||||
// check if account was created before
|
// 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(a => a.address === account.address && a.network === account.network && a.deviceState === account.deviceState);
|
||||||
const exist: ?Account = state.find(account => account.address === action.address && account.network === action.network && account.deviceState === action.device.state);
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
const newState: State = [ ...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];
|
|
||||||
newState.push(account);
|
newState.push(account);
|
||||||
return newState;
|
return newState;
|
||||||
};
|
};
|
||||||
@ -74,8 +61,16 @@ const clear = (state: State, devices: Array<TrezorDevice>): State => {
|
|||||||
return newState;
|
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 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];
|
const newState: State = [...state];
|
||||||
newState[index].loaded = true;
|
newState[index].loaded = true;
|
||||||
newState[index].balance = action.balance;
|
newState[index].balance = action.balance;
|
||||||
@ -93,7 +88,7 @@ const setNonce = (state: State, action: AccountSetNonceAction): State => {
|
|||||||
export default (state: State = initialState, action: Action): State => {
|
export default (state: State = initialState, action: Action): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ACCOUNT.CREATE:
|
case ACCOUNT.CREATE:
|
||||||
return createAccount(state, action);
|
return createAccount(state, action.payload);
|
||||||
|
|
||||||
case CONNECT.FORGET:
|
case CONNECT.FORGET:
|
||||||
case CONNECT.FORGET_SINGLE:
|
case CONNECT.FORGET_SINGLE:
|
||||||
@ -105,6 +100,9 @@ export default (state: State = initialState, action: Action): State => {
|
|||||||
//case CONNECT.FORGET_SINGLE :
|
//case CONNECT.FORGET_SINGLE :
|
||||||
// return forgetAccounts(state, action);
|
// return forgetAccounts(state, action);
|
||||||
|
|
||||||
|
case ACCOUNT.UPDATE :
|
||||||
|
return updateAccount(state, action.payload);
|
||||||
|
|
||||||
case ACCOUNT.SET_BALANCE:
|
case ACCOUNT.SET_BALANCE:
|
||||||
return setBalance(state, action);
|
return setBalance(state, action);
|
||||||
case ACCOUNT.SET_NONCE:
|
case ACCOUNT.SET_NONCE:
|
||||||
|
64
src/reducers/BlockchainReducer.js
Normal file
64
src/reducers/BlockchainReducer.js
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
@ -16,9 +16,7 @@ import type {
|
|||||||
DiscoveryCompleteAction,
|
DiscoveryCompleteAction,
|
||||||
} from 'actions/DiscoveryActions';
|
} from 'actions/DiscoveryActions';
|
||||||
|
|
||||||
import type {
|
import type { Account } from './AccountsReducer';
|
||||||
AccountCreateAction,
|
|
||||||
} from 'actions/AccountsActions';
|
|
||||||
|
|
||||||
export type Discovery = {
|
export type Discovery = {
|
||||||
network: string;
|
network: string;
|
||||||
@ -31,7 +29,7 @@ export type Discovery = {
|
|||||||
interrupted: boolean;
|
interrupted: boolean;
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
waitingForDevice: boolean;
|
waitingForDevice: boolean;
|
||||||
waitingForBackend: boolean;
|
waitingForBlockchain: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = Array<Discovery>;
|
export type State = Array<Discovery>;
|
||||||
@ -55,7 +53,7 @@ const start = (state: State, action: DiscoveryStartAction): State => {
|
|||||||
interrupted: false,
|
interrupted: false,
|
||||||
completed: false,
|
completed: false,
|
||||||
waitingForDevice: false,
|
waitingForDevice: false,
|
||||||
waitingForBackend: false,
|
waitingForBlockchain: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newState: State = [...state];
|
const newState: State = [...state];
|
||||||
@ -75,8 +73,8 @@ const complete = (state: State, action: DiscoveryCompleteAction): State => {
|
|||||||
return newState;
|
return newState;
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountCreate = (state: State, action: AccountCreateAction): State => {
|
const accountCreate = (state: State, account: Account): State => {
|
||||||
const index: number = findIndex(state, action.network, action.device.state || '0');
|
const index: number = findIndex(state, account.network, account.deviceState);
|
||||||
const newState: State = [...state];
|
const newState: State = [...state];
|
||||||
newState[index].accountIndex++;
|
newState[index].accountIndex++;
|
||||||
return newState;
|
return newState;
|
||||||
@ -98,6 +96,7 @@ const stop = (state: State, action: DiscoveryStopAction): State => {
|
|||||||
if (d.deviceState === action.device.state && !d.completed) {
|
if (d.deviceState === action.device.state && !d.completed) {
|
||||||
d.interrupted = true;
|
d.interrupted = true;
|
||||||
d.waitingForDevice = false;
|
d.waitingForDevice = false;
|
||||||
|
d.waitingForBlockchain = false;
|
||||||
}
|
}
|
||||||
return d;
|
return d;
|
||||||
});
|
});
|
||||||
@ -116,7 +115,7 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State =
|
|||||||
interrupted: false,
|
interrupted: false,
|
||||||
completed: false,
|
completed: false,
|
||||||
waitingForDevice: true,
|
waitingForDevice: true,
|
||||||
waitingForBackend: false,
|
waitingForBlockchain: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const index: number = findIndex(state, action.network, deviceState);
|
const index: number = findIndex(state, action.network, deviceState);
|
||||||
@ -130,7 +129,7 @@ const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State =
|
|||||||
return newState;
|
return newState;
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State => {
|
const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): State => {
|
||||||
const deviceState: string = action.device.state || '0';
|
const deviceState: string = action.device.state || '0';
|
||||||
const instance: Discovery = {
|
const instance: Discovery = {
|
||||||
network: action.network,
|
network: action.network,
|
||||||
@ -143,7 +142,7 @@ const waitingForBackend = (state: State, action: DiscoveryWaitingAction): State
|
|||||||
interrupted: false,
|
interrupted: false,
|
||||||
completed: false,
|
completed: false,
|
||||||
waitingForDevice: false,
|
waitingForDevice: false,
|
||||||
waitingForBackend: true,
|
waitingForBlockchain: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const index: number = findIndex(state, action.network, deviceState);
|
const index: number = findIndex(state, action.network, deviceState);
|
||||||
@ -162,15 +161,15 @@ export default function discovery(state: State = initialState, action: Action):
|
|||||||
case DISCOVERY.START:
|
case DISCOVERY.START:
|
||||||
return start(state, action);
|
return start(state, action);
|
||||||
case ACCOUNT.CREATE:
|
case ACCOUNT.CREATE:
|
||||||
return accountCreate(state, action);
|
return accountCreate(state, action.payload);
|
||||||
case DISCOVERY.STOP:
|
case DISCOVERY.STOP:
|
||||||
return stop(state, action);
|
return stop(state, action);
|
||||||
case DISCOVERY.COMPLETE:
|
case DISCOVERY.COMPLETE:
|
||||||
return complete(state, action);
|
return complete(state, action);
|
||||||
case DISCOVERY.WAITING_FOR_DEVICE:
|
case DISCOVERY.WAITING_FOR_DEVICE:
|
||||||
return waitingForDevice(state, action);
|
return waitingForDevice(state, action);
|
||||||
case DISCOVERY.WAITING_FOR_BACKEND:
|
case DISCOVERY.WAITING_FOR_BLOCKCHAIN:
|
||||||
return waitingForBackend(state, action);
|
return waitingForBlockchain(state, action);
|
||||||
case DISCOVERY.FROM_STORAGE:
|
case DISCOVERY.FROM_STORAGE:
|
||||||
return action.payload.map((d) => {
|
return action.payload.map((d) => {
|
||||||
const hdKey: HDKey = new HDKey();
|
const hdKey: HDKey = new HDKey();
|
||||||
@ -181,7 +180,7 @@ export default function discovery(state: State = initialState, action: Action):
|
|||||||
hdKey,
|
hdKey,
|
||||||
interrupted: false,
|
interrupted: false,
|
||||||
waitingForDevice: false,
|
waitingForDevice: false,
|
||||||
waitingForBackend: false,
|
waitingForBlockchain: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
case CONNECT.FORGET:
|
case CONNECT.FORGET:
|
||||||
|
@ -22,7 +22,8 @@ export type Coin = {
|
|||||||
backends: Array<{
|
backends: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
urls: Array<string>;
|
urls: Array<string>;
|
||||||
}>
|
}>;
|
||||||
|
web3: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NetworkToken = {
|
export type NetworkToken = {
|
||||||
|
@ -6,6 +6,7 @@ import type { Action } from 'flowtype';
|
|||||||
import type { SendTxAction } from 'actions/SendFormActions';
|
import type { SendTxAction } from 'actions/SendFormActions';
|
||||||
|
|
||||||
export type PendingTx = {
|
export type PendingTx = {
|
||||||
|
+type: 'send' | 'recv';
|
||||||
+id: string;
|
+id: string;
|
||||||
+network: string;
|
+network: string;
|
||||||
+currency: string;
|
+currency: string;
|
||||||
@ -24,6 +25,7 @@ const initialState: State = [];
|
|||||||
const add = (state: State, action: SendTxAction): State => {
|
const add = (state: State, action: SendTxAction): State => {
|
||||||
const newState = [...state];
|
const newState = [...state];
|
||||||
newState.push({
|
newState.push({
|
||||||
|
type: 'send',
|
||||||
id: action.txid,
|
id: action.txid,
|
||||||
network: action.account.network,
|
network: action.account.network,
|
||||||
currency: action.selectedCurrency,
|
currency: action.selectedCurrency,
|
||||||
@ -37,6 +39,12 @@ const add = (state: State, action: SendTxAction): State => {
|
|||||||
return newState;
|
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 remove = (state: State, id: string): State => state.filter(tx => tx.id !== id);
|
||||||
|
|
||||||
const reject = (state: State, id: string): State => state.map((tx) => {
|
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:
|
case SEND.TX_COMPLETE:
|
||||||
return add(state, action);
|
return add(state, action);
|
||||||
|
|
||||||
|
// case PENDING.ADD:
|
||||||
|
// return add(state, action.payload);
|
||||||
case PENDING.TX_RESOLVED:
|
case PENDING.TX_RESOLVED:
|
||||||
return remove(state, action.tx.id);
|
return remove(state, action.tx.id);
|
||||||
case PENDING.TX_NOT_FOUND:
|
case PENDING.TX_NOT_FOUND:
|
||||||
|
@ -10,7 +10,6 @@ import type {
|
|||||||
Token,
|
Token,
|
||||||
PendingTx,
|
PendingTx,
|
||||||
Discovery,
|
Discovery,
|
||||||
Web3Instance,
|
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
@ -20,7 +19,6 @@ export type State = {
|
|||||||
network: ?Coin;
|
network: ?Coin;
|
||||||
tokens: Array<Token>,
|
tokens: Array<Token>,
|
||||||
pending: Array<PendingTx>,
|
pending: Array<PendingTx>,
|
||||||
web3: ?Web3Instance,
|
|
||||||
discovery: ?Discovery
|
discovery: ?Discovery
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,7 +28,6 @@ export const initialState: State = {
|
|||||||
network: null,
|
network: null,
|
||||||
tokens: [],
|
tokens: [],
|
||||||
pending: [],
|
pending: [],
|
||||||
web3: null,
|
|
||||||
discovery: null,
|
discovery: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
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 MODAL from 'actions/constants/modal';
|
||||||
import * as WEB3 from 'actions/constants/web3';
|
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ export default function wallet(state: State = initialState, action: Action): Sta
|
|||||||
initialPathname: action.pathname,
|
initialPathname: action.pathname,
|
||||||
};
|
};
|
||||||
|
|
||||||
case WEB3.READY:
|
case TRANSPORT.START:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
ready: true,
|
ready: true,
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
import Web3 from 'web3';
|
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 * as WEB3 from 'actions/constants/web3';
|
||||||
|
|
||||||
import type { Action } from 'flowtype';
|
import type { Action } from 'flowtype';
|
||||||
@ -18,7 +19,7 @@ export type Web3Instance = {
|
|||||||
chainId: number;
|
chainId: number;
|
||||||
latestBlock: any;
|
latestBlock: any;
|
||||||
gasPrice: string;
|
gasPrice: string;
|
||||||
erc20: ContractFactory;
|
erc20: Contract;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = Array<Web3Instance>;
|
export type State = Array<Web3Instance>;
|
||||||
@ -26,8 +27,13 @@ export type State = Array<Web3Instance>;
|
|||||||
const initialState: State = [];
|
const initialState: State = [];
|
||||||
|
|
||||||
const createWeb3 = (state: State, instance: Web3Instance): State => {
|
const createWeb3 = (state: State, instance: Web3Instance): State => {
|
||||||
|
const index: number = state.findIndex(w3 => w3.network === instance.network);
|
||||||
const newState: Array<Web3Instance> = [...state];
|
const newState: Array<Web3Instance> = [...state];
|
||||||
newState.push(instance);
|
if (index >= 0) {
|
||||||
|
newState[index] = instance;
|
||||||
|
} else {
|
||||||
|
newState.push(instance);
|
||||||
|
}
|
||||||
return newState;
|
return newState;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,6 +51,13 @@ const updateGasPrice = (state: State, action: Web3UpdateGasPriceAction): State =
|
|||||||
return newState;
|
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 {
|
export default function web3(state: State = initialState, action: Action): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case WEB3.CREATE:
|
case WEB3.CREATE:
|
||||||
@ -53,6 +66,8 @@ export default function web3(state: State = initialState, action: Action): State
|
|||||||
return updateLatestBlock(state, action);
|
return updateLatestBlock(state, action);
|
||||||
case WEB3.GAS_PRICE_UPDATED:
|
case WEB3.GAS_PRICE_UPDATED:
|
||||||
return updateGasPrice(state, action);
|
return updateGasPrice(state, action);
|
||||||
|
case WEB3.DISCONNECT:
|
||||||
|
return disconnect(state, action.instance);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import pending from 'reducers/PendingTxReducer';
|
|||||||
import fiat from 'reducers/FiatRateReducer';
|
import fiat from 'reducers/FiatRateReducer';
|
||||||
import wallet from 'reducers/WalletReducer';
|
import wallet from 'reducers/WalletReducer';
|
||||||
import devices from 'reducers/DevicesReducer';
|
import devices from 'reducers/DevicesReducer';
|
||||||
|
import blockchain from 'reducers/BlockchainReducer';
|
||||||
|
|
||||||
const reducers = {
|
const reducers = {
|
||||||
router: routerReducer,
|
router: routerReducer,
|
||||||
@ -39,6 +40,7 @@ const reducers = {
|
|||||||
fiat,
|
fiat,
|
||||||
wallet,
|
wallet,
|
||||||
devices,
|
devices,
|
||||||
|
blockchain
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Reducers = typeof reducers;
|
export type Reducers = typeof reducers;
|
||||||
|
@ -72,7 +72,6 @@ const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
|||||||
__unloading = true;
|
__unloading = true;
|
||||||
} else if (action.type === LOCATION_CHANGE && !__unloading) {
|
} else if (action.type === LOCATION_CHANGE && !__unloading) {
|
||||||
const { location } = api.getState().router;
|
const { location } = api.getState().router;
|
||||||
const { web3 } = api.getState();
|
|
||||||
const { devices } = api.getState();
|
const { devices } = api.getState();
|
||||||
const { error } = api.getState().connect;
|
const { error } = api.getState().connect;
|
||||||
|
|
||||||
@ -92,8 +91,8 @@ const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
|||||||
redirectPath = '/';
|
redirectPath = '/';
|
||||||
} else {
|
} else {
|
||||||
const isModalOpened: boolean = api.getState().modal.opened;
|
const isModalOpened: boolean = api.getState().modal.opened;
|
||||||
// if web3 wasn't initialized yet or there are no devices attached or initialization error occurs
|
// there are no devices attached or initialization error occurs
|
||||||
const landingPage: boolean = web3.length < 1 || devices.length < 1 || error !== null;
|
const landingPage: boolean = devices.length < 1 || error !== null;
|
||||||
|
|
||||||
// modal is still opened and currentPath is still valid
|
// 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)
|
// example 1 (valid blocking): url changes while passphrase modal opened but device is still connected (we want user to finish this action)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import { push } from 'react-router-redux';
|
import { push } from 'react-router-redux';
|
||||||
|
|
||||||
import {
|
import TrezorConnect, {
|
||||||
TRANSPORT, DEVICE,
|
TRANSPORT, DEVICE_EVENT, UI_EVENT, UI, DEVICE, BLOCKCHAIN
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
import * as DiscoveryActions from 'actions/DiscoveryActions';
|
import * as DiscoveryActions from 'actions/DiscoveryActions';
|
||||||
|
import * as BlockchainActions from 'actions/BlockchainActions';
|
||||||
import * as ModalActions from 'actions/ModalActions';
|
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 STORAGE from 'actions/constants/localStorage';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
|
import { READY as BLOCKCHAIN_READY } from 'actions/constants/blockchain';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Middleware,
|
Middleware,
|
||||||
@ -32,9 +32,9 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
} else if (action.type === TRANSPORT.ERROR) {
|
} else if (action.type === TRANSPORT.ERROR) {
|
||||||
// TODO: check if modal is open
|
// TODO: check if modal is open
|
||||||
// api.dispatch( push('/') );
|
// api.dispatch( push('/') );
|
||||||
} else if (action.type === TRANSPORT.START && api.getState().web3.length < 1) {
|
} else if (action.type === TRANSPORT.START) {
|
||||||
api.dispatch(initWeb3());
|
api.dispatch(BlockchainActions.init());
|
||||||
} else if (action.type === WEB3.READY) {
|
} else if (action.type === BLOCKCHAIN_READY) {
|
||||||
api.dispatch(TrezorConnectActions.postInit());
|
api.dispatch(TrezorConnectActions.postInit());
|
||||||
} else if (action.type === DEVICE.DISCONNECT) {
|
} else if (action.type === DEVICE.DISCONNECT) {
|
||||||
api.dispatch(TrezorConnectActions.deviceDisconnect(action.device));
|
api.dispatch(TrezorConnectActions.deviceDisconnect(action.device));
|
||||||
@ -63,6 +63,12 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
api.dispatch(TrezorConnectActions.onSelectDevice(action.device));
|
api.dispatch(TrezorConnectActions.onSelectDevice(action.device));
|
||||||
} else if (action.type === CONNECT.COIN_CHANGED) {
|
} else if (action.type === CONNECT.COIN_CHANGED) {
|
||||||
api.dispatch(TrezorConnectActions.coinChanged(action.payload.network));
|
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;
|
return action;
|
||||||
|
29
src/store.js
29
src/store.js
@ -6,13 +6,14 @@ import thunk from 'redux-thunk';
|
|||||||
// import createHistory from 'history/createBrowserHistory';
|
// import createHistory from 'history/createBrowserHistory';
|
||||||
// import { useRouterHistory } from 'react-router';
|
// import { useRouterHistory } from 'react-router';
|
||||||
import createHistory from 'history/createHashHistory';
|
import createHistory from 'history/createHashHistory';
|
||||||
|
import { createLogger } from 'redux-logger';
|
||||||
import reducers from 'reducers';
|
import reducers from 'reducers';
|
||||||
import services from 'services';
|
import services from 'services';
|
||||||
|
|
||||||
import Raven from 'raven-js';
|
import Raven from 'raven-js';
|
||||||
import RavenMiddleware from 'redux-raven-middleware';
|
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 });
|
export const history: History = createHistory({ queryKey: false });
|
||||||
|
|
||||||
@ -30,17 +31,29 @@ const middleware = [
|
|||||||
|
|
||||||
let composedEnhancers: any;
|
let composedEnhancers: any;
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
// const excludeLogger = (getState: GetState, action: Action): boolean => {
|
const excludeLogger = (getState: GetState, action: Action): boolean => {
|
||||||
// //'@@router/LOCATION_CHANGE'
|
//'@@router/LOCATION_CHANGE'
|
||||||
// const excluded: Array<string> = ['LOG_TO_EXCLUDE', 'log__add'];
|
const excluded: Array<?string> = ['LOG_TO_EXCLUDE', 'log__add', undefined];
|
||||||
// const pass: Array<string> = excluded.filter(act => action.type === act);
|
const pass: Array<?string> = excluded.filter(act => action.type === act);
|
||||||
// return pass.length === 0;
|
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(
|
composedEnhancers = compose(
|
||||||
applyMiddleware(...middleware, ...services),
|
applyMiddleware(logger, ...middleware, ...services),
|
||||||
...enhancers,
|
...enhancers,
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
composedEnhancers = compose(
|
composedEnhancers = compose(
|
||||||
applyMiddleware(...middleware, ...services),
|
applyMiddleware(...middleware, ...services),
|
||||||
|
@ -8,7 +8,6 @@ import LandingPage from './index';
|
|||||||
export type StateProps = {
|
export type StateProps = {
|
||||||
localStorage: $ElementType<State, 'localStorage'>,
|
localStorage: $ElementType<State, 'localStorage'>,
|
||||||
modal: $ElementType<State, 'modal'>,
|
modal: $ElementType<State, 'modal'>,
|
||||||
web3: $ElementType<State, 'web3'>,
|
|
||||||
wallet: $ElementType<State, 'wallet'>,
|
wallet: $ElementType<State, 'wallet'>,
|
||||||
connect: $ElementType<State, 'connect'>,
|
connect: $ElementType<State, 'connect'>,
|
||||||
router: $ElementType<State, 'router'>,
|
router: $ElementType<State, 'router'>,
|
||||||
@ -29,7 +28,6 @@ export type Props = StateProps & DispatchProps;
|
|||||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
|
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
|
||||||
localStorage: state.localStorage,
|
localStorage: state.localStorage,
|
||||||
modal: state.modal,
|
modal: state.modal,
|
||||||
web3: state.web3,
|
|
||||||
wallet: state.wallet,
|
wallet: state.wallet,
|
||||||
connect: state.connect,
|
connect: state.connect,
|
||||||
router: state.router,
|
router: state.router,
|
||||||
|
@ -103,7 +103,6 @@ export default class InstallBridge extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onChange(value: InstallTarget) {
|
onChange(value: InstallTarget) {
|
||||||
console.warn(value);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
target: value,
|
target: value,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Notification } from 'components/Notification';
|
import { Notification } from 'components/Notification';
|
||||||
|
import { reconnect } from 'actions/DiscoveryActions';
|
||||||
|
|
||||||
import type { State } from 'flowtype';
|
import type { State } from 'flowtype';
|
||||||
|
|
||||||
@ -8,11 +9,12 @@ export type StateProps = {
|
|||||||
className: string;
|
className: string;
|
||||||
selectedAccount: $ElementType<State, 'selectedAccount'>,
|
selectedAccount: $ElementType<State, 'selectedAccount'>,
|
||||||
wallet: $ElementType<State, 'wallet'>,
|
wallet: $ElementType<State, 'wallet'>,
|
||||||
|
blockchain: $ElementType<State, 'blockchain'>,
|
||||||
children?: React.Node
|
children?: React.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DispatchProps = {
|
export type DispatchProps = {
|
||||||
|
blockchainReconnect: typeof reconnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = StateProps & DispatchProps;
|
export type Props = StateProps & DispatchProps;
|
||||||
@ -28,8 +30,28 @@ const SelectedAccount = (props: Props) => {
|
|||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
discovery,
|
discovery,
|
||||||
|
network
|
||||||
} = accountState;
|
} = 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...
|
// account not found (yet). checking why...
|
||||||
if (!account) {
|
if (!account) {
|
||||||
if (!discovery || discovery.waitingForDevice) {
|
if (!discovery || discovery.waitingForDevice) {
|
||||||
@ -57,11 +79,6 @@ const SelectedAccount = (props: Props) => {
|
|||||||
message="Connect device to load accounts"
|
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) {
|
} if (discovery.completed) {
|
||||||
// case 5: account not found and discovery is completed
|
// case 5: account not found and discovery is completed
|
||||||
return (
|
return (
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { reconnect } from 'actions/DiscoveryActions';
|
||||||
import { showAddress } from 'actions/ReceiveActions';
|
import { showAddress } from 'actions/ReceiveActions';
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
@ -28,12 +29,14 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
className: 'receive',
|
className: 'receive',
|
||||||
selectedAccount: state.selectedAccount,
|
selectedAccount: state.selectedAccount,
|
||||||
wallet: state.wallet,
|
wallet: state.wallet,
|
||||||
|
blockchain: state.blockchain,
|
||||||
|
|
||||||
receive: state.receive,
|
receive: state.receive,
|
||||||
modal: state.modal,
|
modal: state.modal,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||||
|
blockchainReconnect: bindActionCreators(reconnect, dispatch),
|
||||||
showAddress: bindActionCreators(showAddress, dispatch),
|
showAddress: bindActionCreators(showAddress, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import * as React from 'react';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { reconnect } from 'actions/DiscoveryActions';
|
||||||
import SendFormActions from 'actions/SendFormActions';
|
import SendFormActions from 'actions/SendFormActions';
|
||||||
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
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 type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from 'views/Wallet/components/SelectedAccount';
|
||||||
import AccountSend from './index';
|
import AccountSend from './index';
|
||||||
|
|
||||||
|
|
||||||
type OwnProps = { }
|
type OwnProps = { }
|
||||||
|
|
||||||
export type StateProps = BaseStateProps & {
|
export type StateProps = BaseStateProps & {
|
||||||
@ -33,6 +33,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
className: 'send-from',
|
className: 'send-from',
|
||||||
selectedAccount: state.selectedAccount,
|
selectedAccount: state.selectedAccount,
|
||||||
wallet: state.wallet,
|
wallet: state.wallet,
|
||||||
|
blockchain: state.blockchain,
|
||||||
|
|
||||||
sendForm: state.sendForm,
|
sendForm: state.sendForm,
|
||||||
fiat: state.fiat,
|
fiat: state.fiat,
|
||||||
@ -40,6 +41,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||||
|
blockchainReconnect: bindActionCreators(reconnect, dispatch),
|
||||||
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
||||||
saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch),
|
saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch),
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import { bindActionCreators } from 'redux';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
|
import { reconnect } from 'actions/DiscoveryActions';
|
||||||
import * as TokenActions from 'actions/TokenActions';
|
import * as TokenActions from 'actions/TokenActions';
|
||||||
|
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
@ -30,6 +31,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
className: 'summary',
|
className: 'summary',
|
||||||
selectedAccount: state.selectedAccount,
|
selectedAccount: state.selectedAccount,
|
||||||
wallet: state.wallet,
|
wallet: state.wallet,
|
||||||
|
blockchain: state.blockchain,
|
||||||
|
|
||||||
tokens: state.tokens,
|
tokens: state.tokens,
|
||||||
summary: state.summary,
|
summary: state.summary,
|
||||||
@ -38,6 +40,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||||
|
blockchainReconnect: bindActionCreators(reconnect, dispatch),
|
||||||
addToken: bindActionCreators(TokenActions.add, dispatch),
|
addToken: bindActionCreators(TokenActions.add, dispatch),
|
||||||
loadTokens: bindActionCreators(TokenActions.load, dispatch),
|
loadTokens: bindActionCreators(TokenActions.load, dispatch),
|
||||||
removeToken: bindActionCreators(TokenActions.remove, dispatch),
|
removeToken: bindActionCreators(TokenActions.remove, dispatch),
|
||||||
|
@ -69,7 +69,7 @@ const AccountSummary = (props: Props) => {
|
|||||||
} = props.selectedAccount;
|
} = props.selectedAccount;
|
||||||
|
|
||||||
// flow
|
// flow
|
||||||
if (!device || !account || !network) return null;
|
if (!device || !account || !network) return <SelectedAccount {...props} />;
|
||||||
|
|
||||||
const explorerLink: string = `${network.explorer.address}${account.address}`;
|
const explorerLink: string = `${network.explorer.address}${account.address}`;
|
||||||
const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol);
|
const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol);
|
||||||
|
@ -2,6 +2,7 @@ import webpack from 'webpack';
|
|||||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||||
|
import MiniCssExtractPlugin from '../../trezor-connect/node_modules/mini-css-extract-plugin';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TREZOR_CONNECT_ROOT,
|
TREZOR_CONNECT_ROOT,
|
||||||
@ -47,20 +48,17 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
test: /\.js?$/,
|
test: /\.js?$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
|
use: ['babel-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
use: [
|
use: [
|
||||||
'babel-loader',
|
|
||||||
{
|
{
|
||||||
loader: 'eslint-loader',
|
loader: MiniCssExtractPlugin.loader,
|
||||||
options: {
|
options: { publicPath: '../' },
|
||||||
emitWarning: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'stylelint-custom-processor-loader',
|
|
||||||
options: {
|
|
||||||
configPath: '.stylelintrc',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
`${TREZOR_CONNECT_ROOT}/node_modules/css-loader`,
|
||||||
|
`${TREZOR_CONNECT_ROOT}/node_modules/less-loader`,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -113,6 +111,11 @@ module.exports = {
|
|||||||
hints: false,
|
hints: false,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: '[name].css',
|
||||||
|
chunkFilename: '[id].css',
|
||||||
|
}),
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
chunks: ['index'],
|
chunks: ['index'],
|
||||||
template: `${SRC}index.html`,
|
template: `${SRC}index.html`,
|
||||||
|
Loading…
Reference in New Issue
Block a user