2017-12-13 11:01:37 +00:00
|
|
|
/* @flow */
|
2018-05-05 11:52:03 +00:00
|
|
|
import Web3 from 'web3';
|
2018-09-12 11:25:21 +00:00
|
|
|
import BigNumber from 'bignumber.js';
|
|
|
|
|
|
|
|
import EthereumjsUnits from 'ethereumjs-units';
|
2018-09-18 09:17:07 +00:00
|
|
|
// import InputDataDecoder from 'ethereum-input-data-decoder';
|
2018-09-22 17:21:33 +00:00
|
|
|
import type { EstimateGasOptions } from 'web3';
|
2018-08-14 13:18:16 +00:00
|
|
|
import * as WEB3 from 'actions/constants/web3';
|
|
|
|
import * as PENDING from 'actions/constants/pendingTx';
|
2018-10-08 15:51:25 +00:00
|
|
|
import * as ethUtils from 'utils/ethUtils';
|
2017-12-13 11:01:37 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
import type { Dispatch, GetState, ThunkAction, PromiseAction } from 'flowtype';
|
2018-04-16 21:19:50 +00:00
|
|
|
|
2018-08-14 13:18:16 +00:00
|
|
|
import type { Account } from 'reducers/AccountsReducer';
|
|
|
|
import type { Web3Instance } from 'reducers/Web3Reducer';
|
|
|
|
import type { Token } from 'reducers/TokensReducer';
|
|
|
|
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
2018-08-14 14:06:34 +00:00
|
|
|
import * as TokenActions from './TokenActions';
|
2018-04-16 21:19:50 +00:00
|
|
|
|
|
|
|
export type Web3UpdateBlockAction = {
|
|
|
|
type: typeof WEB3.BLOCK_UPDATED,
|
|
|
|
network: string,
|
2019-03-04 12:33:02 +00:00
|
|
|
blockHash: string,
|
2018-03-08 16:10:53 +00:00
|
|
|
};
|
|
|
|
|
2018-04-16 21:19:50 +00:00
|
|
|
export type Web3UpdateGasPriceAction = {
|
|
|
|
type: typeof WEB3.GAS_PRICE_UPDATED,
|
|
|
|
network: string,
|
2019-03-04 12:33:02 +00:00
|
|
|
gasPrice: string,
|
2018-03-08 16:10:53 +00:00
|
|
|
};
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export type Web3Action =
|
|
|
|
| {
|
|
|
|
type: typeof WEB3.READY,
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: typeof WEB3.START,
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
type: typeof WEB3.CREATE | typeof WEB3.DISCONNECT,
|
|
|
|
instance: Web3Instance,
|
|
|
|
}
|
|
|
|
| Web3UpdateBlockAction
|
|
|
|
| Web3UpdateGasPriceAction;
|
|
|
|
|
|
|
|
export const initWeb3 = (
|
|
|
|
network: string,
|
|
|
|
urlIndex: number = 0
|
|
|
|
): PromiseAction<Web3Instance> => async (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): Promise<Web3Instance> =>
|
|
|
|
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;
|
|
|
|
}
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
// requested web3 wasn't initialized or is disconnected
|
|
|
|
// initialize again
|
|
|
|
const { config, ERC20Abi } = getState().localStorage;
|
|
|
|
const networkData = config.networks.find(c => c.shortcut === network);
|
|
|
|
if (!networkData) {
|
|
|
|
// network not found
|
|
|
|
reject(new Error(`Network ${network} not found in application config.`));
|
|
|
|
return;
|
|
|
|
}
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
// get first url
|
|
|
|
const url = networkData.web3[urlIndex];
|
|
|
|
if (!url) {
|
|
|
|
reject(new Error('Web3 backend is not responding'));
|
|
|
|
return;
|
|
|
|
}
|
2018-05-09 09:15:12 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const web3 = new Web3(new Web3.providers.WebsocketProvider(url));
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const onConnect = async () => {
|
|
|
|
const latestBlock = await web3.eth.getBlockNumber();
|
|
|
|
const gasPrice = await web3.eth.getGasPrice();
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const newInstance = {
|
|
|
|
network,
|
|
|
|
web3,
|
|
|
|
chainId: networkData.chainId,
|
|
|
|
erc20: new web3.eth.Contract(ERC20Abi),
|
|
|
|
latestBlock,
|
|
|
|
gasPrice,
|
|
|
|
};
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
dispatch({
|
|
|
|
type: WEB3.CREATE,
|
|
|
|
instance: newInstance,
|
|
|
|
});
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
resolve(newInstance);
|
|
|
|
};
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
const onEnd = async () => {
|
|
|
|
web3.currentProvider.reset();
|
|
|
|
const oldInstance = getState().web3.find(w3 => w3.network === network);
|
|
|
|
|
|
|
|
if (oldInstance && oldInstance.web3.currentProvider.connected) {
|
|
|
|
// backend disconnects
|
|
|
|
// dispatch({
|
|
|
|
// type: 'WEB3.DISCONNECT',
|
|
|
|
// network
|
|
|
|
// });
|
|
|
|
} else {
|
|
|
|
// backend initialization error for given url, try next one
|
|
|
|
try {
|
|
|
|
const otherWeb3 = await dispatch(initWeb3(network, urlIndex + 1));
|
|
|
|
resolve(otherWeb3);
|
|
|
|
} catch (error) {
|
2019-05-15 16:03:29 +00:00
|
|
|
console.error(error);
|
2019-03-04 12:33:02 +00:00
|
|
|
reject(error);
|
|
|
|
}
|
2018-05-09 09:15:12 +00:00
|
|
|
}
|
2019-03-04 12:33:02 +00:00
|
|
|
};
|
2018-05-07 16:04:04 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
web3.currentProvider.on('connect', onConnect);
|
|
|
|
web3.currentProvider.on('end', onEnd);
|
|
|
|
web3.currentProvider.on('error', onEnd);
|
|
|
|
});
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2020-03-30 18:46:07 +00:00
|
|
|
// not used since connect@8
|
|
|
|
// export const discoverAccount = (descriptor: string, network: string): PromiseAction<any> => async (
|
|
|
|
// dispatch: Dispatch
|
|
|
|
// ): Promise<any> => {
|
|
|
|
// const instance: Web3Instance = await dispatch(initWeb3(network));
|
|
|
|
// const balance = await instance.web3.eth.getBalance(descriptor);
|
|
|
|
// const nonce = await instance.web3.eth.getTransactionCount(descriptor);
|
|
|
|
// return {
|
|
|
|
// descriptor,
|
|
|
|
// transactions: 0,
|
|
|
|
// block: 0,
|
|
|
|
// balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
|
|
|
// availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
|
|
|
// nonce,
|
|
|
|
// };
|
|
|
|
// };
|
2018-03-08 16:10:53 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export const resolvePendingTransactions = (network: string): PromiseAction<void> => async (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): Promise<void> => {
|
2018-09-22 17:21:33 +00:00
|
|
|
const instance: Web3Instance = await dispatch(initWeb3(network));
|
2018-09-12 11:25:21 +00:00
|
|
|
const pending = getState().pending.filter(p => p.network === network);
|
2019-03-04 12:33:02 +00:00
|
|
|
pending.forEach(async tx => {
|
2020-03-30 18:46:07 +00:00
|
|
|
const status = await instance.web3.eth.getTransaction(tx.txid);
|
2018-09-12 11:25:21 +00:00
|
|
|
if (!status) {
|
2018-07-30 10:52:13 +00:00
|
|
|
dispatch({
|
2018-09-26 16:46:49 +00:00
|
|
|
type: PENDING.TX_REJECTED,
|
2020-03-30 18:46:07 +00:00
|
|
|
hash: tx.txid,
|
2018-07-30 10:52:13 +00:00
|
|
|
});
|
2018-09-12 11:25:21 +00:00
|
|
|
} else {
|
2020-03-30 18:46:07 +00:00
|
|
|
const receipt = await instance.web3.eth.getTransactionReceipt(tx.txid);
|
2018-09-12 11:25:21 +00:00
|
|
|
if (receipt) {
|
|
|
|
if (status.gas !== receipt.gasUsed) {
|
2018-07-30 10:52:13 +00:00
|
|
|
dispatch({
|
2018-09-12 11:25:21 +00:00
|
|
|
type: PENDING.TX_TOKEN_ERROR,
|
2020-03-30 18:46:07 +00:00
|
|
|
hash: tx.txid,
|
2018-07-30 10:52:13 +00:00
|
|
|
});
|
|
|
|
}
|
2018-09-12 11:25:21 +00:00
|
|
|
dispatch({
|
|
|
|
type: PENDING.TX_RESOLVED,
|
2020-03-30 18:46:07 +00:00
|
|
|
hash: tx.txid,
|
2018-09-12 11:25:21 +00:00
|
|
|
});
|
|
|
|
}
|
2018-07-30 10:52:13 +00:00
|
|
|
}
|
2018-09-23 06:19:38 +00:00
|
|
|
});
|
2018-09-22 17:21:33 +00:00
|
|
|
};
|
2017-12-13 11:01:37 +00:00
|
|
|
|
2018-09-22 17:21:33 +00:00
|
|
|
/*
|
|
|
|
export const getPendingInfo = (network: string, txid: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
|
|
|
|
const instance: Web3Instance = await dispatch(initWeb3(network));
|
2018-09-12 11:25:21 +00:00
|
|
|
const tx = await instance.web3.eth.getTransaction(txid);
|
2018-07-30 10:52:13 +00:00
|
|
|
|
2018-09-22 17:21:33 +00:00
|
|
|
|
2018-09-12 11:25:21 +00:00
|
|
|
if (tx.input !== "0x") {
|
|
|
|
// find token:
|
|
|
|
// tx.to <= smart contract address
|
2018-07-30 10:52:13 +00:00
|
|
|
|
2018-09-12 11:25:21 +00:00
|
|
|
// 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));
|
2018-07-30 10:52:13 +00:00
|
|
|
}
|
2018-09-22 17:21:33 +00:00
|
|
|
|
2018-07-30 10:52:13 +00:00
|
|
|
|
2018-09-12 11:25:21 +00:00
|
|
|
}
|
2018-09-22 17:21:33 +00:00
|
|
|
|
2018-09-12 11:25:21 +00:00
|
|
|
// return tx;
|
2018-09-22 17:21:33 +00:00
|
|
|
};
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2018-09-22 17:21:33 +00:00
|
|
|
export const getTxInput = (): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
|
|
|
|
const instance: Web3Instance = await dispatch(initWeb3('ropsten'));
|
2018-09-12 11:25:21 +00:00
|
|
|
// const inputData = instance.web3.utils.hexToAscii("0xa9059cbb00000000000000000000000073d0385f4d8e00c5e6504c6030f47bf6212736a80000000000000000000000000000000000000000000000000000000000000001");
|
|
|
|
// console.warn("input data!", inputData);
|
2018-09-22 17:21:33 +00:00
|
|
|
};
|
|
|
|
*/
|
2017-12-13 11:01:37 +00:00
|
|
|
|
2020-03-30 18:46:07 +00:00
|
|
|
// not used since connect@8
|
|
|
|
// export const updateAccount = (
|
|
|
|
// account: Account,
|
|
|
|
// newAccount: any,
|
|
|
|
// network: string
|
|
|
|
// ): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
|
|
|
|
// const instance: Web3Instance = await dispatch(initWeb3(network));
|
|
|
|
// const balance = await instance.web3.eth.getBalance(account.descriptor);
|
|
|
|
// const nonce = await instance.web3.eth.getTransactionCount(account.descriptor);
|
|
|
|
// const empty = nonce <= 0 && balance === '0';
|
|
|
|
// dispatch(
|
|
|
|
// AccountsActions.update({
|
|
|
|
// networkType: 'ethereum',
|
|
|
|
// ...account,
|
|
|
|
// ...newAccount,
|
|
|
|
// empty,
|
|
|
|
// nonce,
|
|
|
|
// balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
|
|
|
// availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// // update tokens for this account
|
|
|
|
// dispatch(updateAccountTokens(account));
|
|
|
|
// };
|
2018-09-14 10:20:59 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
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.descriptor
|
|
|
|
);
|
|
|
|
tokens.forEach(async token => {
|
2018-09-22 17:21:33 +00:00
|
|
|
const balance = await dispatch(getTokenBalance(token));
|
2018-09-13 12:40:41 +00:00
|
|
|
// const newBalance: string = balance.dividedBy(Math.pow(10, token.decimals)).toString(10);
|
|
|
|
if (balance !== token.balance) {
|
2019-03-04 12:33:02 +00:00
|
|
|
dispatch(TokenActions.setBalance(token.address, token.ethAddress, balance));
|
2018-09-13 12:40:41 +00:00
|
|
|
}
|
2018-09-23 06:19:38 +00:00
|
|
|
});
|
2018-09-22 17:21:33 +00:00
|
|
|
};
|
2018-09-12 11:25:21 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export const getTokenInfo = (
|
|
|
|
address: string,
|
|
|
|
network: string
|
|
|
|
): PromiseAction<NetworkToken> => async (dispatch: Dispatch): Promise<NetworkToken> => {
|
2018-09-22 17:21:33 +00:00
|
|
|
const instance: Web3Instance = await dispatch(initWeb3(network));
|
2018-09-12 11:25:21 +00:00
|
|
|
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 {
|
2018-07-30 10:52:13 +00:00
|
|
|
address,
|
2018-09-12 11:25:21 +00:00
|
|
|
name,
|
|
|
|
symbol,
|
|
|
|
decimals,
|
2018-07-30 10:52:13 +00:00
|
|
|
};
|
2018-09-12 11:25:21 +00:00
|
|
|
};
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export const getTokenBalance = (token: Token): PromiseAction<string> => async (
|
|
|
|
dispatch: Dispatch
|
|
|
|
): Promise<string> => {
|
2018-09-22 17:21:33 +00:00
|
|
|
const instance = await dispatch(initWeb3(token.network));
|
2018-09-12 11:25:21 +00:00
|
|
|
const contract = instance.erc20.clone();
|
|
|
|
contract.options.address = token.address;
|
2018-02-20 09:30:36 +00:00
|
|
|
|
2018-09-12 11:25:21 +00:00
|
|
|
const balance = await contract.methods.balanceOf(token.ethAddress).call();
|
2018-09-22 17:21:33 +00:00
|
|
|
return new BigNumber(balance).dividedBy(10 ** token.decimals).toString(10);
|
2018-09-12 11:25:21 +00:00
|
|
|
};
|
2018-05-05 11:52:03 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export const getCurrentGasPrice = (network: string): PromiseAction<string> => async (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): Promise<string> => {
|
2018-09-12 11:25:21 +00:00
|
|
|
const instance = getState().web3.find(w3 => w3.network === network);
|
|
|
|
if (instance) {
|
|
|
|
return EthereumjsUnits.convert(instance.gasPrice, 'wei', 'gwei');
|
|
|
|
}
|
2018-09-22 17:21:33 +00:00
|
|
|
return '0';
|
|
|
|
};
|
2018-09-12 11:25:21 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export const updateGasPrice = (network: string): PromiseAction<void> => async (
|
|
|
|
dispatch: Dispatch
|
|
|
|
): Promise<void> => {
|
2018-09-13 17:21:40 +00:00
|
|
|
try {
|
2018-09-22 17:21:33 +00:00
|
|
|
const instance = await dispatch(initWeb3(network));
|
2018-09-13 17:21:40 +00:00
|
|
|
const gasPrice = await instance.web3.eth.getGasPrice();
|
|
|
|
if (instance.gasPrice !== gasPrice) {
|
|
|
|
dispatch({
|
|
|
|
type: WEB3.GAS_PRICE_UPDATED,
|
|
|
|
network,
|
2018-09-22 17:21:33 +00:00
|
|
|
gasPrice,
|
2018-09-13 17:21:40 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// silent action
|
|
|
|
// nothing happens if this fails
|
|
|
|
}
|
2018-09-22 17:21:33 +00:00
|
|
|
};
|
2018-07-30 10:52:13 +00:00
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export const estimateGasLimit = (
|
|
|
|
network: string,
|
|
|
|
$options: EstimateGasOptions
|
|
|
|
): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
|
2018-09-22 13:39:56 +00:00
|
|
|
const instance = await dispatch(initWeb3(network));
|
2018-10-08 15:51:25 +00:00
|
|
|
const data = ethUtils.sanitizeHex($options.data);
|
2018-09-22 13:39:56 +00:00
|
|
|
const options = {
|
|
|
|
...$options,
|
|
|
|
to: '0x0000000000000000000000000000000000000000',
|
|
|
|
data,
|
2019-03-04 12:33:02 +00:00
|
|
|
value: instance.web3.utils.toHex(
|
|
|
|
EthereumjsUnits.convert($options.value || '0', 'ether', 'wei')
|
|
|
|
),
|
|
|
|
gasPrice: instance.web3.utils.toHex(
|
|
|
|
EthereumjsUnits.convert($options.gasPrice, 'gwei', 'wei')
|
|
|
|
),
|
2018-09-22 13:39:56 +00:00
|
|
|
};
|
2018-07-30 10:52:13 +00:00
|
|
|
|
2018-09-12 11:25:21 +00:00
|
|
|
const limit = await instance.web3.eth.estimateGas(options);
|
2018-09-22 13:39:56 +00:00
|
|
|
return limit.toString();
|
2018-09-12 11:25:21 +00:00
|
|
|
};
|
|
|
|
|
2019-03-04 12:33:02 +00:00
|
|
|
export const disconnect = (network: string): ThunkAction => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: GetState
|
|
|
|
): void => {
|
2018-09-13 12:40:41 +00:00
|
|
|
// 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();
|
2018-09-22 17:21:33 +00:00
|
|
|
|
2018-09-13 12:40:41 +00:00
|
|
|
// remove instance from reducer
|
|
|
|
dispatch({
|
|
|
|
type: WEB3.DISCONNECT,
|
2018-09-22 17:21:33 +00:00
|
|
|
instance,
|
2018-09-13 12:40:41 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|