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.
trezor-wallet/src/actions/BlockchainActions.js

272 lines
9.9 KiB

/* @flow */
import TrezorConnect from 'trezor-connect';
import BigNumber from 'bignumber.js';
import * as BLOCKCHAIN from 'actions/constants/blockchain';
import * as PENDING from 'actions/constants/pendingTx';
import type {
TrezorDevice,
Dispatch,
GetState,
PromiseAction,
} from 'flowtype';
import type { EthereumAccount, BlockchainBlock, BlockchainNotification } from 'trezor-connect';
import type { Token } from 'reducers/TokensReducer';
import type { NetworkToken } from 'reducers/LocalStorageReducer';
6 years ago
import * as Web3Actions from './Web3Actions';
import * as AccountsActions from './AccountsActions';
export type BlockchainAction = {
type: typeof BLOCKCHAIN.READY,
} | {
type: typeof BLOCKCHAIN.UPDATE_FEE,
fee: string,
}
6 years ago
export const discoverAccount = (device: TrezorDevice, address: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): 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,
6 years ago
balance: '0',
nonce: 0,
},
coin: network,
});
if (!txs.success) {
throw new Error(txs.payload.error);
}
// blockbook web3 fallback
6 years ago
const web3account = await dispatch(Web3Actions.discoverAccount(address, network));
// return { transactions: txs.payload, ...web3account };
6 years ago
return {
address,
transactions: txs.payload.transactions,
block: txs.payload.block,
balance: web3account.balance,
nonce: web3account.nonce,
};
};
6 years ago
export const getTokenInfo = (input: string, network: string): PromiseAction<NetworkToken> => async (dispatch: Dispatch): Promise<NetworkToken> => dispatch(Web3Actions.getTokenInfo(input, network));
6 years ago
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => dispatch(Web3Actions.getTokenBalance(token));
6 years ago
export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAction<BigNumber> => async (dispatch: Dispatch): Promise<BigNumber> => {
try {
6 years ago
const gasPrice = await dispatch(Web3Actions.getCurrentGasPrice(network));
return gasPrice === '0' ? new BigNumber(defaultGasPrice) : new BigNumber(gasPrice);
} catch (error) {
return new BigNumber(defaultGasPrice);
}
6 years ago
};
const estimateProxy: Array<Promise<string>> = [];
export const estimateGasLimit = (network: string, data: string, value: string, gasPrice: string): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => {
// Since this method could be called multiple times in short period of time
// check for pending calls in proxy and if there more than two (first is current running and the second is waiting for result of first)
// TODO: should reject second call immediately?
if (estimateProxy.length > 0) {
// wait for proxy result (but do not process it)
await estimateProxy[0];
}
const call = dispatch(Web3Actions.estimateGasLimit(network, {
to: '',
data,
value,
gasPrice,
}));
// add current call to proxy
estimateProxy.push(call);
// wait for result
const result = await call;
// remove current call from proxy
estimateProxy.splice(0, 1);
// return result
return result;
};
export const onBlockMined = (payload: $ElementType<BlockchainBlock, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const network: string = payload.coin.shortcut.toLowerCase();
if (payload.coin.type === 'misc') {
if (getState().router.location.state.network === network) {
const fee = await TrezorConnect.blockchainGetFee({
coin: network,
});
if (!fee.success) return;
dispatch({
type: BLOCKCHAIN.UPDATE_FEE,
fee: fee.payload,
});
}
return;
}
// try to resolve pending transactions
6 years ago
await dispatch(Web3Actions.resolvePendingTransactions(network));
6 years ago
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,
});
6 years ago
if (response.success) {
response.payload.forEach((a, i) => {
if (a.transactions > 0) {
// load additional data from Web3 (balance, nonce, tokens)
6 years ago
dispatch(Web3Actions.updateAccount(accounts[i], a, network));
} else {
// there are no new txs, just update block
6 years ago
dispatch(AccountsActions.update({ ...accounts[i], block: a.block }));
// HACK: since blockbook can't work with smart contracts for now
// try to update tokens balances added to this account using Web3
6 years ago
dispatch(Web3Actions.updateAccountTokens(accounts[i]));
}
});
}
}
6 years ago
};
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { notification } = payload;
const account = getState().accounts.find(a => a.address === notification.address);
if (!account) return;
if (notification.status === 'pending') {
dispatch({
type: PENDING.ADD,
payload: {
type: notification.type,
hash: notification.hash,
network: account.network,
address: account.address,
currency: account.network,
amount: notification.amount,
total: notification.amount,
fee: notification.fee,
},
});
// todo: replace "send success" notification with link to explorer
} else if (notification.status === 'confirmed') {
dispatch({
type: PENDING.TX_RESOLVED,
hash: notification.hash,
});
}
const updatedAccount = await TrezorConnect.rippleGetAccountInfo({
account: {
address: account.address,
block: account.block,
history: false,
},
});
if (!updatedAccount.success) return;
dispatch(AccountsActions.update({
...account,
balance: updatedAccount.payload.balance,
block: updatedAccount.payload.block,
sequence: updatedAccount.payload.sequence,
}));
// todo: get transaction history here
// console.warn("OnBlAccount", account);
// 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 => 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,
// // }
// // });
// }
// }
6 years ago
};
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const accounts: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.address); // eslint-disable-line no-unused-vars
6 years ago
await TrezorConnect.blockchainSubscribe({
accounts,
6 years ago
coin: network,
});
if (network === 'ethereum') {
// init web3 instance if not exists
await dispatch(Web3Actions.initWeb3(network));
}
6 years ago
};
// 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> = [];
6 years ago
getState().discovery.forEach((discovery) => {
if (networks.indexOf(discovery.network) < 0) {
networks.push(discovery.network);
}
});
// subscribe
6 years ago
const results = networks.map(n => dispatch(subscribe(n)));
// wait for all subscriptions
await Promise.all(results);
}
// continue wallet initialization
dispatch({
6 years ago
type: BLOCKCHAIN.READY,
});
6 years ago
};
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
// disconnect and remove Web3 webscocket instance if exists
6 years ago
export const error = (payload: any): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {
dispatch(Web3Actions.disconnect(payload.coin));
};