mirror of https://github.com/trezor/trezor-wallet
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
14 KiB
428 lines
14 KiB
7 years ago
|
/* @flow */
|
||
6 years ago
|
|
||
7 years ago
|
|
||
6 years ago
|
import Web3 from 'web3';
|
||
7 years ago
|
import HDKey from 'hdkey';
|
||
6 years ago
|
|
||
7 years ago
|
import EthereumjsUtil from 'ethereumjs-util';
|
||
|
import EthereumjsTx from 'ethereumjs-tx';
|
||
|
import TrezorConnect from 'trezor-connect';
|
||
6 years ago
|
import type { ContractFactory, EstimateGasOptions } from 'web3';
|
||
|
import type BigNumber from 'bignumber.js';
|
||
|
import type { TransactionStatus, TransactionReceipt } from 'web3';
|
||
6 years ago
|
import { strip } from 'utils/ethUtils';
|
||
|
import * as WEB3 from 'actions/constants/web3';
|
||
|
import * as PENDING from 'actions/constants/pendingTx';
|
||
7 years ago
|
|
||
6 years ago
|
import type {
|
||
7 years ago
|
Dispatch,
|
||
|
GetState,
|
||
|
Action,
|
||
|
AsyncAction,
|
||
6 years ago
|
} from 'flowtype';
|
||
7 years ago
|
|
||
6 years ago
|
import type { Account } from 'reducers/AccountsReducer';
|
||
|
import type { PendingTx } from 'reducers/PendingTxReducer';
|
||
|
import type { Web3Instance } from 'reducers/Web3Reducer';
|
||
|
import type { Token } from 'reducers/TokensReducer';
|
||
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
||
6 years ago
|
import * as TokenActions from './TokenActions';
|
||
|
import * as AccountsActions from './AccountsActions';
|
||
7 years ago
|
|
||
|
export type Web3Action = {
|
||
|
type: typeof WEB3.READY,
|
||
6 years ago
|
} | {
|
||
|
type: typeof WEB3.CREATE,
|
||
|
instance: Web3Instance
|
||
|
}
|
||
7 years ago
|
| Web3UpdateBlockAction
|
||
7 years ago
|
| Web3UpdateGasPriceAction;
|
||
|
|
||
|
export type Web3UpdateBlockAction = {
|
||
|
type: typeof WEB3.BLOCK_UPDATED,
|
||
|
network: string,
|
||
6 years ago
|
blockHash: string
|
||
7 years ago
|
};
|
||
|
|
||
7 years ago
|
export type Web3UpdateGasPriceAction = {
|
||
|
type: typeof WEB3.GAS_PRICE_UPDATED,
|
||
|
network: string,
|
||
6 years ago
|
gasPrice: string
|
||
7 years ago
|
};
|
||
|
|
||
7 years ago
|
|
||
6 years ago
|
export function init(instance: ?Web3, coinIndex: number = 0): AsyncAction {
|
||
7 years ago
|
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||
7 years ago
|
const { config, ERC20Abi } = getState().localStorage;
|
||
7 years ago
|
|
||
6 years ago
|
const coin = config.coins[coinIndex];
|
||
7 years ago
|
if (!coin) {
|
||
|
// all instances done
|
||
|
dispatch({
|
||
|
type: WEB3.READY,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
|
||
7 years ago
|
const network = coin.network;
|
||
7 years ago
|
const urls = coin.backends[0].urls;
|
||
|
|
||
|
let web3host: string = urls[0];
|
||
|
|
||
6 years ago
|
if (instance) {
|
||
|
const currentHost = instance.currentProvider.host;
|
||
6 years ago
|
const currentHostIndex: number = urls.indexOf(currentHost);
|
||
7 years ago
|
|
||
|
if (currentHostIndex + 1 < urls.length) {
|
||
|
web3host = urls[currentHostIndex + 1];
|
||
|
} else {
|
||
6 years ago
|
console.error(`TODO: Backend ${network} not working`, instance.currentProvider);
|
||
6 years ago
|
|
||
|
dispatch({
|
||
|
type: WEB3.CREATE,
|
||
6 years ago
|
instance: {
|
||
|
network,
|
||
|
web3: instance,
|
||
|
chainId: coin.chainId,
|
||
|
erc20: instance.eth.contract(ERC20Abi),
|
||
|
latestBlock: '0',
|
||
|
gasPrice: '0',
|
||
6 years ago
|
},
|
||
6 years ago
|
});
|
||
|
|
||
7 years ago
|
// try next coin
|
||
6 years ago
|
dispatch(init(null, coinIndex + 1));
|
||
7 years ago
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//const instance = new Web3(window.web3.currentProvider);
|
||
6 years ago
|
const web3 = new Web3(new Web3.providers.HttpProvider(web3host));
|
||
7 years ago
|
|
||
|
// 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") );
|
||
|
|
||
6 years ago
|
|
||
7 years ago
|
// initial check if backend is running
|
||
6 years ago
|
if (!web3.currentProvider.isConnected()) {
|
||
|
// try different url
|
||
6 years ago
|
dispatch(init(web3, coinIndex));
|
||
6 years ago
|
return;
|
||
|
}
|
||
7 years ago
|
|
||
6 years ago
|
const erc20 = web3.eth.contract(ERC20Abi);
|
||
7 years ago
|
|
||
6 years ago
|
dispatch({
|
||
|
type: WEB3.CREATE,
|
||
6 years ago
|
instance: {
|
||
|
network,
|
||
6 years ago
|
web3,
|
||
6 years ago
|
chainId: coin.chainId,
|
||
|
erc20,
|
||
|
latestBlock: '0',
|
||
|
gasPrice: '0',
|
||
6 years ago
|
},
|
||
6 years ago
|
});
|
||
7 years ago
|
|
||
6 years ago
|
// dispatch({
|
||
|
// type: WEB3.GAS_PRICE_UPDATED,
|
||
|
// network,
|
||
|
// gasPrice
|
||
|
// });
|
||
7 years ago
|
|
||
|
|
||
6 years ago
|
// console.log("GET CHAIN", instance.version.network)
|
||
7 years ago
|
|
||
6 years ago
|
// instance.version.getWhisper((err, shh) => {
|
||
|
// console.log("-----whisperrr", error, shh)
|
||
|
// })
|
||
6 years ago
|
|
||
7 years ago
|
|
||
6 years ago
|
// const sshFilter = instance.ssh.filter('latest');
|
||
|
// sshFilter.watch((error, blockHash) => {
|
||
|
// console.warn("SSH", error, blockHash);
|
||
|
// });
|
||
7 years ago
|
|
||
6 years ago
|
//const shh = instance.shh.newIdentity();
|
||
7 years ago
|
|
||
6 years ago
|
const latestBlockFilter = web3.eth.filter('latest');
|
||
7 years ago
|
|
||
6 years ago
|
const onBlockMined = async (error: ?Error, blockHash: ?string) => {
|
||
|
if (error) {
|
||
6 years ago
|
window.setTimeout(() => {
|
||
|
// try again
|
||
6 years ago
|
onBlockMined(new Error('manually_triggered_error'), undefined);
|
||
6 years ago
|
}, 30000);
|
||
6 years ago
|
}
|
||
7 years ago
|
|
||
6 years ago
|
if (blockHash) {
|
||
|
dispatch({
|
||
|
type: WEB3.BLOCK_UPDATED,
|
||
|
network,
|
||
6 years ago
|
blockHash,
|
||
6 years ago
|
});
|
||
|
}
|
||
7 years ago
|
|
||
6 years ago
|
// TODO: filter only current device
|
||
|
const accounts = getState().accounts.filter(a => a.network === network);
|
||
|
for (const account of accounts) {
|
||
|
const nonce = await getNonceAsync(web3, account.address);
|
||
|
if (nonce !== account.nonce) {
|
||
6 years ago
|
dispatch(AccountsActions.setNonce(account.address, account.network, account.deviceState, nonce));
|
||
7 years ago
|
|
||
6 years ago
|
// dispatch( getBalance(account) );
|
||
6 years ago
|
// TODO: check if nonce was updated,
|
||
6 years ago
|
// update tokens balance,
|
||
6 years ago
|
// update account balance,
|
||
|
// update pending transactions
|
||
|
}
|
||
6 years ago
|
dispatch(getBalance(account));
|
||
6 years ago
|
// dispatch( getNonce(account) );
|
||
|
}
|
||
6 years ago
|
|
||
6 years ago
|
const tokens = getState().tokens.filter(t => t.network === network);
|
||
|
for (const token of tokens) {
|
||
6 years ago
|
dispatch(getTokenBalance(token));
|
||
6 years ago
|
}
|
||
7 years ago
|
|
||
6 years ago
|
dispatch(getGasPrice(network));
|
||
7 years ago
|
|
||
6 years ago
|
const pending = getState().pending.filter(p => p.network === network);
|
||
|
for (const tx of pending) {
|
||
6 years ago
|
dispatch(getTransactionReceipt(tx));
|
||
6 years ago
|
}
|
||
6 years ago
|
};
|
||
7 years ago
|
|
||
6 years ago
|
// latestBlockFilter.watch(onBlockMined);
|
||
6 years ago
|
onBlockMined(new Error('manually_triggered_error'), undefined);
|
||
7 years ago
|
|
||
|
|
||
6 years ago
|
// init next coin
|
||
6 years ago
|
dispatch(init(web3, coinIndex + 1));
|
||
|
|
||
7 years ago
|
|
||
|
// 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)
|
||
|
// });
|
||
6 years ago
|
};
|
||
7 years ago
|
}
|
||
|
|
||
|
|
||
7 years ago
|
export function getGasPrice(network: string): AsyncAction {
|
||
|
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||
7 years ago
|
const index: number = getState().web3.findIndex(w3 => w3.network === network);
|
||
7 years ago
|
|
||
6 years ago
|
const web3instance = getState().web3[index];
|
||
7 years ago
|
const web3 = web3instance.web3;
|
||
7 years ago
|
web3.eth.getGasPrice((error, gasPrice) => {
|
||
|
if (!error) {
|
||
7 years ago
|
if (web3instance.gasPrice && web3instance.gasPrice.toString() !== gasPrice.toString()) {
|
||
|
dispatch({
|
||
|
type: WEB3.GAS_PRICE_UPDATED,
|
||
6 years ago
|
network,
|
||
|
gasPrice,
|
||
7 years ago
|
});
|
||
|
}
|
||
7 years ago
|
}
|
||
|
});
|
||
6 years ago
|
};
|
||
7 years ago
|
}
|
||
7 years ago
|
|
||
7 years ago
|
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: Web3 = web3instance.web3;
|
||
7 years ago
|
|
||
6 years ago
|
web3.eth.getBalance(account.address, (error: Error, balance: BigNumber) => {
|
||
7 years ago
|
if (!error) {
|
||
|
const newBalance: string = web3.fromWei(balance.toString(), 'ether');
|
||
7 years ago
|
if (account.balance !== newBalance) {
|
||
6 years ago
|
dispatch(AccountsActions.setBalance(
|
||
7 years ago
|
account.address,
|
||
|
account.network,
|
||
6 years ago
|
account.deviceState,
|
||
6 years ago
|
newBalance,
|
||
7 years ago
|
));
|
||
7 years ago
|
|
||
|
// dispatch( loadHistory(addr) );
|
||
|
}
|
||
|
}
|
||
|
});
|
||
6 years ago
|
};
|
||
7 years ago
|
}
|
||
7 years ago
|
|
||
6 years ago
|
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 web3 = web3instance.web3;
|
||
|
const contract = web3instance.erc20.at(token.address);
|
||
|
|
||
6 years ago
|
contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => {
|
||
6 years ago
|
if (balance) {
|
||
6 years ago
|
const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
|
||
6 years ago
|
if (newBalance !== token.balance) {
|
||
|
dispatch(TokenActions.setBalance(
|
||
|
token.address,
|
||
|
token.ethAddress,
|
||
6 years ago
|
newBalance,
|
||
6 years ago
|
));
|
||
|
}
|
||
|
}
|
||
|
});
|
||
6 years ago
|
};
|
||
6 years ago
|
}
|
||
|
|
||
7 years ago
|
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];
|
||
7 years ago
|
const web3 = web3instance.web3;
|
||
|
|
||
7 years ago
|
web3.eth.getTransactionCount(account.address, (error: Error, result: number) => {
|
||
7 years ago
|
if (!error) {
|
||
7 years ago
|
if (account.nonce !== result) {
|
||
6 years ago
|
dispatch(AccountsActions.setNonce(account.address, account.network, account.deviceState, result));
|
||
7 years ago
|
}
|
||
|
}
|
||
|
});
|
||
6 years ago
|
};
|
||
7 years ago
|
}
|
||
|
|
||
6 years ago
|
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;
|
||
7 years ago
|
|
||
6 years ago
|
web3.eth.getTransaction(tx.id, (error: Error, status: TransactionStatus) => {
|
||
|
if (!error && !status) {
|
||
|
dispatch({
|
||
|
type: PENDING.TX_NOT_FOUND,
|
||
|
tx,
|
||
|
});
|
||
|
} else if (status && status.blockNumber) {
|
||
|
web3.eth.getTransactionReceipt(tx.id, (error: Error, receipt: TransactionReceipt) => {
|
||
|
if (receipt) {
|
||
|
if (status.gas !== receipt.gasUsed) {
|
||
6 years ago
|
dispatch({
|
||
6 years ago
|
type: PENDING.TX_TOKEN_ERROR,
|
||
6 years ago
|
tx,
|
||
6 years ago
|
});
|
||
6 years ago
|
}
|
||
6 years ago
|
dispatch({
|
||
|
type: PENDING.TX_RESOLVED,
|
||
|
tx,
|
||
|
receipt,
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
7 years ago
|
});
|
||
6 years ago
|
};
|
||
7 years ago
|
|
||
6 years ago
|
export const getTransaction = (web3: Web3, txid: string): Promise<any> => new Promise((resolve, reject) => {
|
||
|
web3.eth.getTransaction(txid, (error, result) => {
|
||
|
if (error) {
|
||
|
reject(error);
|
||
|
} else {
|
||
|
resolve(result);
|
||
|
}
|
||
7 years ago
|
});
|
||
6 years ago
|
});
|
||
|
|
||
|
export const getBalanceAsync = (web3: Web3, address: string): Promise<BigNumber> => new Promise((resolve, reject) => {
|
||
|
web3.eth.getBalance(address, (error: Error, result: BigNumber) => {
|
||
|
if (error) {
|
||
|
reject(error);
|
||
|
} else {
|
||
|
resolve(result);
|
||
|
}
|
||
7 years ago
|
});
|
||
6 years ago
|
});
|
||
|
|
||
|
export const getTokenBalanceAsync = (erc20: ContractFactory, token: Token): Promise<string> => new Promise((resolve, reject) => {
|
||
|
const contract = erc20.at(token.address);
|
||
|
contract.balanceOf(token.ethAddress, (error: Error, balance: BigNumber) => {
|
||
|
if (error) {
|
||
|
reject(error);
|
||
|
} else {
|
||
|
const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
|
||
|
resolve(newBalance);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
export const getNonceAsync = (web3: Web3, address: string): Promise<number> => new Promise((resolve, reject) => {
|
||
|
web3.eth.getTransactionCount(address, (error: Error, result: number) => {
|
||
|
if (error) {
|
||
|
reject(error);
|
||
|
} else {
|
||
|
resolve(result);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
7 years ago
|
|
||
6 years ago
|
|
||
|
export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise<?NetworkToken> => new Promise((resolve, reject) => {
|
||
|
const contract = erc20.at(address, (error, res) => {
|
||
|
// console.warn("callback", error, res)
|
||
7 years ago
|
});
|
||
|
|
||
6 years ago
|
const info: NetworkToken = {
|
||
|
address,
|
||
|
name: '',
|
||
|
symbol: '',
|
||
|
decimals: 0,
|
||
|
};
|
||
7 years ago
|
|
||
6 years ago
|
contract.name.call((error: Error, name: string) => {
|
||
|
if (error) {
|
||
|
resolve(null);
|
||
|
return;
|
||
|
}
|
||
|
info.name = name;
|
||
7 years ago
|
|
||
6 years ago
|
|
||
6 years ago
|
contract.symbol.call((error: Error, symbol: string) => {
|
||
6 years ago
|
if (error) {
|
||
6 years ago
|
resolve(null);
|
||
|
return;
|
||
7 years ago
|
}
|
||
6 years ago
|
info.symbol = symbol;
|
||
|
|
||
|
|
||
|
contract.decimals.call((error: Error, decimals: BigNumber) => {
|
||
|
if (decimals) {
|
||
|
info.decimals = decimals.toNumber();
|
||
|
resolve(info);
|
||
6 years ago
|
} else {
|
||
6 years ago
|
resolve(null);
|
||
7 years ago
|
}
|
||
6 years ago
|
});
|
||
7 years ago
|
});
|
||
|
});
|
||
6 years ago
|
});
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
});
|
||
|
});
|