Merge pull request #282 from trezor/fix/pending-txs

Fix/pending txs
pull/283/head
Vladimir Volek 6 years ago committed by GitHub
commit cf76e8cf68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -69,7 +69,7 @@
"rimraf": "^2.6.2",
"styled-components": "^4.1.2",
"styled-normalize": "^8.0.4",
"trezor-connect": "6.0.3-beta.4",
"trezor-connect": "6.0.3-beta.5",
"web3": "1.0.0-beta.35",
"webpack": "^4.16.3",
"webpack-build-notifier": "^0.1.29",

@ -1,17 +1,5 @@
{
"networks": [
{
"type": "ripple",
"name": "Ripple Testnet",
"testnet": true,
"symbol": "XRP",
"shortcut": "xrp",
"bip44": "m/44'/144'/a'/0/0",
"explorer": {
"tx": "https://sisyfos.trezor.io/ripple-testnet-explorer/tx/",
"address": "https://sisyfos.trezor.io/ripple-testnet-explorer/address/"
}
},
{
"type": "ethereum",
"name": "Ethereum",
@ -22,6 +10,7 @@
"defaultGasPrice": 64,
"defaultGasLimit": 21000,
"defaultGasLimitTokens": 200000,
"decimals": 18,
"tokens": "./data/ethereumTokens.json",
"web3": [
"wss://eth2.trezor.io/geth"
@ -41,6 +30,7 @@
"defaultGasPrice": 64,
"defaultGasLimit": 21000,
"defaultGasLimitTokens": 200000,
"decimals": 18,
"tokens": "./data/ethereumClassicTokens.json",
"web3": [
"wss://etc2.trezor.io/geth"
@ -61,6 +51,7 @@
"defaultGasPrice": 64,
"defaultGasLimit": 21000,
"defaultGasLimitTokens": 200000,
"decimals": 18,
"tokens": "./data/ropstenTokens.json",
"web3": [
"wss://ropsten1.trezor.io/geth"
@ -69,6 +60,31 @@
"tx": "https://ropsten.etherscan.io/tx/",
"address": "https://ropsten.etherscan.io/address/"
}
},
{
"type": "ripple",
"name": "Ripple",
"symbol": "XRP",
"shortcut": "xrp",
"bip44": "m/44'/144'/a'/0/0",
"decimals": 6,
"explorer": {
"tx": "https://xrpcharts.ripple.com/#/transactions/",
"address": "https://xrpcharts.ripple.com/#/graph/"
}
},
{
"type": "ripple",
"name": "Ripple Testnet",
"testnet": true,
"symbol": "tXRP",
"shortcut": "txrp",
"bip44": "m/44'/144'/a'/0/0",
"decimals": 6,
"explorer": {
"tx": "https://sisyfos.trezor.io/ripple-testnet-explorer/tx/",
"address": "https://sisyfos.trezor.io/ripple-testnet-explorer/address/"
}
}
],

@ -90,7 +90,7 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
switch (network.type) {
case 'ethereum':
// this is not working until blockchain-link will start support blockbook backends
await dispatch(EthereumBlockchainActions.onNotification());
await dispatch(EthereumBlockchainActions.onNotification(payload));
break;
case 'ripple':
await dispatch(RippleBlockchainActions.onNotification(payload));

@ -15,7 +15,6 @@ import * as storageUtils from 'utils/storage';
import { findAccountTokens } from 'reducers/TokensReducer';
import type { Account } from 'reducers/AccountsReducer';
import type { Token } from 'reducers/TokensReducer';
import type { PendingTx } from 'reducers/PendingTxReducer';
import type { Discovery } from 'reducers/DiscoveryReducer';
import type {
@ -24,6 +23,7 @@ import type {
AsyncAction,
GetState,
Dispatch,
Transaction,
} from 'flowtype';
import type { Config, Network, TokensCollection } from 'reducers/LocalStorageReducer';
@ -61,15 +61,15 @@ const findAccounts = (devices: Array<TrezorDevice>, accounts: Array<Account>): A
const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token> => accounts.reduce((arr, account) => arr.concat(findAccountTokens(tokens, account)), []);
const findDiscovery = (devices: Array<TrezorDevice>, discovery: Array<Discovery>): Array<Discovery> => devices.reduce((arr, dev) => arr.concat(discovery.filter(a => a.deviceState === dev.state && a.publicKey.length > 0)), []);
const findDiscovery = (devices: Array<TrezorDevice>, discovery: Array<Discovery>): Array<Discovery> => devices.reduce((arr, dev) => arr.concat(discovery.filter(d => d.deviceState === dev.state && d.completed)), []);
const findPendingTxs = (accounts: Array<Account>, pending: Array<PendingTx>): Array<PendingTx> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.address === account.address && p.network === account.network)), []);
const findPendingTxs = (accounts: Array<Account>, pending: Array<Transaction>): Array<Transaction> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.address === account.address && p.network === account.network)), []);
export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const devices: Array<TrezorDevice> = getState().devices.filter(d => d.features && d.remember === true);
const accounts: Array<Account> = findAccounts(devices, getState().accounts);
const tokens: Array<Token> = findTokens(accounts, getState().tokens);
const pending: Array<PendingTx> = findPendingTxs(accounts, getState().pending);
const pending: Array<Transaction> = findPendingTxs(accounts, getState().pending);
const discovery: Array<Discovery> = findDiscovery(devices, getState().discovery);
// save devices

@ -3,14 +3,15 @@
import * as PENDING from 'actions/constants/pendingTx';
import type { State, PendingTx } from 'reducers/PendingTxReducer';
import type { Transaction } from 'flowtype';
import type { State } from 'reducers/PendingTxReducer';
export type PendingTxAction = {
type: typeof PENDING.FROM_STORAGE,
payload: State
} | {
type: typeof PENDING.ADD,
payload: PendingTx
payload: Transaction
} | {
type: typeof PENDING.TX_RESOLVED,
hash: string,

@ -289,9 +289,7 @@ export const estimateGasLimit = (network: string, $options: EstimateGasOptions):
return limit.toString();
};
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();
export const disconnect = (network: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
// check if Web3 was already initialized
const instance = getState().web3.find(w3 => w3.network === network);
if (instance) {

@ -2,6 +2,7 @@
import TrezorConnect from 'trezor-connect';
import BigNumber from 'bignumber.js';
import * as PENDING from 'actions/constants/pendingTx';
import type {
TrezorDevice,
@ -9,7 +10,7 @@ import type {
GetState,
PromiseAction,
} from 'flowtype';
import type { EthereumAccount } from 'trezor-connect';
import type { EthereumAccount, BlockchainNotification } from 'trezor-connect';
import type { Token } from 'reducers/TokensReducer';
import type { NetworkToken } from 'reducers/LocalStorageReducer';
import * as Web3Actions from 'actions/Web3Actions';
@ -85,10 +86,11 @@ export const estimateGasLimit = (network: string, data: string, value: string, g
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
await TrezorConnect.blockchainSubscribe({
const response = await TrezorConnect.blockchainSubscribe({
accounts,
coin: network,
});
if (!response.success) return;
// init web3 instance if not exists
await dispatch(Web3Actions.initWeb3(network));
};
@ -125,45 +127,21 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (dis
}
};
export const onNotification = (/*network: string*/): PromiseAction<void> => async (): Promise<void> => {
// 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,
// // }
// // });
// }
// }
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: {
...notification,
deviceState: account.deviceState,
network: account.network,
},
});
}
};
export const onError = (network: string): PromiseAction<void> => async (dispatch: Dispatch): Promise<void> => {

@ -6,7 +6,6 @@ import BigNumber from 'bignumber.js';
import * as ACCOUNT from 'actions/constants/account';
import * as NOTIFICATION from 'actions/constants/notification';
import * as SEND from 'actions/constants/send';
import * as PENDING from 'actions/constants/pendingTx';
import * as WEB3 from 'actions/constants/web3';
import { initialState } from 'reducers/SendFormEthereumReducer';
import { findToken } from 'reducers/TokensReducer';
@ -525,21 +524,45 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
dispatch({ type: SEND.TX_COMPLETE });
dispatch({
type: PENDING.ADD,
payload: {
type: 'send',
deviceState: account.deviceState,
sequence: nonce,
hash: txid,
network: account.network,
address: account.address,
currency: currentState.currency,
amount: currentState.amount,
total: currentState.total,
fee: '0', // TODO: calculate fee
},
});
// ugly blockbook workaround:
// since blockbook can't emit pending notifications
// need to trigger this event from here, where we know everything about this transaction
// blockchainNotification is 'trezor-connect' BlockchainLinkTransaction type
const fee = ValidationActions.calculateFee(currentState.gasLimit, currentState.gasPrice);
const blockchainNotification = {
type: 'send',
status: 'pending',
confirmations: 0,
address: account.address,
inputs: [
{
addresses: [account.address],
amount: currentState.amount,
fee,
total: currentState.total,
},
],
outputs: [
{
addresses: [currentState.address],
amount: currentState.amount,
},
],
hash: txid,
amount: currentState.amount,
fee,
total: currentState.total,
sequence: nonce,
currency: isToken ? currentState.currency : undefined,
};
dispatch(BlockchainActions.onNotification({
// $FlowIssue: missing coinInfo declaration
coin: {},
notification: blockchainNotification,
}));
// workaround end
// clear session storage
dispatch(SessionStorageActions.clear());

@ -1,7 +1,7 @@
/* @flow */
import TrezorConnect from 'trezor-connect';
import * as BLOCKCHAIN from 'actions/constants/blockchain';
// import * as BLOCKCHAIN from 'actions/constants/blockchain';
import * as PENDING from 'actions/constants/pendingTx';
import * as AccountsActions from 'actions/AccountsActions';
import { toDecimalAmount } from 'utils/formatUtils';
@ -13,6 +13,7 @@ import type {
PromiseAction,
} from 'flowtype';
const DECIMALS: number = 6;
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);
@ -22,22 +23,47 @@ export const subscribe = (network: string): PromiseAction<void> => async (dispat
});
};
export const onBlockMined = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const fee = await TrezorConnect.blockchainGetFee({
coin: network,
});
if (!fee.success) return;
const blockchain = getState().blockchain.find(b => b.shortcut === network);
if (!blockchain) return;
if (fee.payload !== blockchain.fee) {
dispatch({
type: BLOCKCHAIN.UPDATE_FEE,
shortcut: network,
fee: fee.payload,
});
// const fee = await TrezorConnect.blockchainGetFee({
// coin: network,
// });
// if (!fee.success) return;
// if (fee.payload !== blockchain.fee) {
// dispatch({
// type: BLOCKCHAIN.UPDATE_FEE,
// shortcut: network,
// fee: fee.payload,
// });
// }
const accounts: Array<any> = getState().accounts.filter(a => a.network === network);
// console.warn('ACCOUNTS', accounts);
if (accounts.length > 0) {
// const response = await TrezorConnect.rippleGetAccountInfo({
// bundle: accounts,
// level: 'transactions',
// coin: network,
// });
// console.warn('APDEJT RESP', response);
// if (!response.success) return;
// response.payload.forEach((a, i) => {
// if (a.transactions.length > 0) {
// console.warn('APDEJTED!', a, i);
// dispatch(AccountsActions.update({
// ...accounts[i],
// balance: toDecimalAmount(a.balance, DECIMALS),
// availableBalance: toDecimalAmount(a.availableBalance, DECIMALS),
// block: a.block,
// sequence: a.sequence,
// }));
// }
// });
}
};
@ -50,16 +76,13 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
dispatch({
type: PENDING.ADD,
payload: {
type: notification.type,
...notification,
deviceState: account.deviceState,
sequence: account.sequence,
hash: notification.hash,
network: account.network,
address: account.address,
currency: account.network,
amount: notification.amount,
total: notification.amount,
fee: notification.fee,
amount: toDecimalAmount(notification.amount, DECIMALS),
total: notification.type === 'send' ? toDecimalAmount(notification.total, DECIMALS) : toDecimalAmount(notification.amount, DECIMALS),
fee: toDecimalAmount(notification.fee, DECIMALS),
},
});
@ -74,16 +97,17 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
const updatedAccount = await TrezorConnect.rippleGetAccountInfo({
account: {
address: account.address,
block: account.block,
from: account.block,
history: false,
},
coin: account.network,
});
if (!updatedAccount.success) return;
dispatch(AccountsActions.update({
...account,
balance: toDecimalAmount(updatedAccount.payload.balance, 6),
availableDevice: toDecimalAmount(updatedAccount.payload.availableBalance, 6),
balance: toDecimalAmount(updatedAccount.payload.balance, DECIMALS),
availableBalance: toDecimalAmount(updatedAccount.payload.availableBalance, DECIMALS),
block: updatedAccount.payload.block,
sequence: updatedAccount.payload.sequence,
}));

@ -48,6 +48,7 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover
},
keepSession: true, // acquire and hold session
useEmptyPassphrase: device.useEmptyPassphrase,
coin: network.shortcut,
});
// handle TREZOR response error

@ -1,6 +1,4 @@
/* @flow */
import React from 'react';
import Link from 'components/Link';
import TrezorConnect from 'trezor-connect';
import * as NOTIFICATION from 'actions/constants/notification';
import * as SEND from 'actions/constants/send';
@ -250,7 +248,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
payload: {
type: 'success',
title: 'Transaction success',
message: <Link href={`${network.explorer.tx}${txid}`} isGreen>See transaction detail</Link>,
message: txid,
cancelable: true,
actions: [],
},

@ -0,0 +1,104 @@
/* @flow */
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import colors from 'config/colors';
import Link from 'components/Link';
import type { Transaction, Network } from 'flowtype';
type Props = {
tx: Transaction,
network: Network,
};
const Wrapper = styled.div`
border-bottom: 1px solid ${colors.DIVIDER};
padding: 14px 0;
display: flex;
flex-direction: row;
&:last-child {
border-bottom: 0px;
}
`;
const Addresses = styled.div`
flex: 1;
`;
const Address = styled.div`
word-break: break-all;
padding: 2px 0px;
&:first-child {
padding-top: 0px;
}
&:last-child {
padding-bottom: 0px;
}
`;
const Date = styled(Link)`
font-size: 12px;
line-height: 18px;
padding-right: 8px;
border-bottom: 0px;
`;
const Value = styled.div`
padding-left: 8px;
white-space: nowrap;
text-align: right;
color: ${colors.GREEN_SECONDARY};
&.send {
color: ${colors.ERROR_PRIMARY};
}
`;
const Amount = styled.div`
border: 1px;
`;
const Fee = styled.div`
border: 1px;
`;
const TransactionItem = ({
tx,
network,
}: Props) => {
const url = `${network.explorer.tx}${tx.hash}`;
const date = typeof tx.timestamp === 'string' && tx.confirmations > 0 ? tx.timestamp : undefined; // TODO: format date
const addresses = (tx.type === 'send' ? tx.outputs : tx.inputs).reduce((arr, item) => arr.concat(item.addresses), []);
const currency = tx.currency || tx.network;
const isToken = currency !== tx.network;
const amount = isToken ? `${tx.amount} ${currency}` : `${tx.total} ${network.symbol}`;
const fee = isToken && tx.type === 'send' ? `${tx.fee} ${network.symbol}` : undefined;
const operation = tx.type === 'send' ? '-' : '+';
return (
<Wrapper>
{ date && (<Date href={url} isGray>{ date }</Date>)}
<Addresses>
{ addresses.map(addr => (<Address key={addr}>{addr}</Address>)) }
{ tx.confirmations <= 0 && (
<Date href={url} isGray>Transaction hash: {tx.hash}</Date>
)}
</Addresses>
<Value className={tx.type}>
<Amount>{operation}{amount}</Amount>
{ fee && (<Fee>{operation}{fee}</Fee>) }
</Value>
</Wrapper>
);
};
TransactionItem.propTypes = {
tx: PropTypes.object.isRequired,
network: PropTypes.object.isRequired,
};
export default TransactionItem;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -44,6 +44,7 @@ import type {
TransportMessageType,
UiMessageType,
BlockchainEvent,
BlockchainLinkTransaction,
} from 'trezor-connect';
import type { RouterAction, LocationState } from 'react-router-redux';
@ -84,11 +85,17 @@ export type UnknownDevice = $Exact<{
instanceLabel: string;
instanceName: ?string;
ts: number;
}>
}>;
export type { Device } from 'trezor-connect';
export type TrezorDevice = AcquiredDevice | UnknownDevice;
export type Transaction = BlockchainLinkTransaction & {
deviceState: string,
network: string,
rejected?: boolean,
};
export type RouterLocationState = LocationState;
// Cast event from TrezorConnect event listener to react Action
@ -152,7 +159,6 @@ export type { Account } from 'reducers/AccountsReducer';
export type { Discovery } from 'reducers/DiscoveryReducer';
export type { Token } from 'reducers/TokensReducer';
export type { Web3Instance } from 'reducers/Web3Reducer';
export type { PendingTx } from 'reducers/PendingTxReducer';
export type Accounts = $ElementType<State, 'accounts'>;
export type LocalStorage = $ElementType<State, 'localStorage'>;

@ -9,8 +9,9 @@ import type { BlockchainConnect, BlockchainError, BlockchainBlock } from 'trezor
export type BlockchainNetwork = {
+shortcut: string,
connected: boolean,
fee: string,
block: number,
reserved: string, // xrp specific
fee: string,
};
export type State = Array<BlockchainNetwork>;
@ -34,8 +35,9 @@ const onConnect = (state: State, action: BlockchainConnect): State => {
return state.concat([{
shortcut,
connected: true,
fee: info.fee,
block: info.block,
fee: info.fee,
reserved: info.reserved || '0',
}]);
};
@ -50,7 +52,13 @@ const onError = (state: State, action: BlockchainError): State => {
}]);
}
return state;
return state.concat([{
shortcut,
connected: false,
block: 0,
fee: '0',
reserved: '0',
}]);
};
const onBlock = (state: State, action: BlockchainBlock): State => {

@ -195,6 +195,9 @@ export default function discovery(state: State = initialState, action: Action):
return notSupported(state, action);
case DISCOVERY.FROM_STORAGE:
return action.payload.map((d) => {
if (d.publicKey.length < 1) return d;
// recreate ethereum discovery HDKey
// deprecated: will be removed after switching to blockbook
const hdKey: HDKey = new HDKey();
hdKey.publicKey = Buffer.from(d.publicKey, 'hex');
hdKey.chainCode = Buffer.from(d.chainCode, 'hex');

@ -21,6 +21,7 @@ export type Network = {
address: string;
};
tokens: string;
decimals: number,
backends: Array<{
name: string;
urls: Array<string>;

@ -2,27 +2,13 @@
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as PENDING from 'actions/constants/pendingTx';
import type { Action } from 'flowtype';
export type PendingTx = {
+type: 'send' | 'recv',
+deviceState: string,
+sequence: number,
+hash: string,
+network: string,
+address: string,
+currency: string,
+amount: string,
+total: string,
+fee: string,
rejected?: boolean,
};
import type { Action, Transaction } from 'flowtype';
export type State = Array<PendingTx>;
export type State = Array<Transaction>;
const initialState: State = [];
const add = (state: State, payload: PendingTx): State => {
const add = (state: State, payload: Transaction): State => {
const newState = [...state];
newState.push(payload);
return newState;

@ -6,7 +6,7 @@ import type {
Account,
Network,
Token,
PendingTx,
Transaction,
Discovery,
} from 'flowtype';
@ -34,7 +34,7 @@ export type State = {
account: ?Account,
network: ?Network,
tokens: Array<Token>,
pending: Array<PendingTx>,
pending: Array<Transaction>,
discovery: ?Discovery,
loader: ?Loader,
notification: ?Notification,

@ -9,7 +9,7 @@ import type {
Network,
Discovery,
Token,
PendingTx,
Transaction,
Web3Instance,
} from 'flowtype';
@ -83,18 +83,18 @@ export const getDiscoveryProcess = (state: State): ?Discovery => {
return state.discovery.find(d => d.deviceState === device.state && d.network === locationState.network);
};
export const getAccountPendingTx = (pending: Array<PendingTx>, account: ?Account): Array<PendingTx> => {
export const getAccountPendingTx = (pending: Array<Transaction>, account: ?Account): Array<Transaction> => {
const a = account;
if (!a) return [];
return pending.filter(p => p.network === a.network && p.address === a.address);
};
export const getPendingSequence = (pending: Array<PendingTx>): number => pending.reduce((value: number, tx: PendingTx) => {
export const getPendingSequence = (pending: Array<Transaction>): number => pending.reduce((value: number, tx: Transaction) => {
if (tx.rejected) return value;
return Math.max(value, tx.sequence + 1);
}, 0);
export const getPendingAmount = (pending: Array<PendingTx>, currency: string, token: boolean = false): BigNumber => pending.reduce((value: BigNumber, tx: PendingTx) => {
export const getPendingAmount = (pending: Array<Transaction>, currency: string, token: boolean = false): BigNumber => pending.reduce((value: BigNumber, tx: Transaction) => {
if (tx.currency === currency && !tx.rejected) {
return new BigNumber(value).plus(token ? tx.amount : tx.total);
}

@ -1,19 +1,17 @@
/* @flow */
import React, { PureComponent } from 'react';
import React from 'react';
import styled from 'styled-components';
import colors from 'config/colors';
import ColorHash from 'color-hash';
import { H2 } from 'components/Heading';
import Link from 'components/Link';
import ScaleText from 'react-scale-text';
import Transaction from 'components/Transaction';
import type { Network } from 'reducers/LocalStorageReducer';
import type { Token } from 'reducers/TokensReducer';
import type { BaseProps } from '../../index';
// import testData from './test.data';
type Props = {
pending: $PropertyType<$ElementType<BaseProps, 'selectedAccount'>, 'pending'>,
tokens: $PropertyType<$ElementType<BaseProps, 'selectedAccount'>, 'tokens'>,
network: Network
}
@ -22,153 +20,18 @@ const Wrapper = styled.div`
border-top: 1px solid ${colors.DIVIDER};
`;
const StyledLink = styled(Link)`
text-decoration: none;
`;
const TransactionWrapper = styled.div`
border-bottom: 1px solid ${colors.DIVIDER};
padding: 14px 0;
display: flex;
flex-direction: row;
align-items: center;
&:last-child {
border-bottom: 0px;
}
`;
const TransactionIcon = styled.div`
padding: 6px;
margin-right: 10px;
width: 36px;
height: 36px;
border-radius: 50%;
line-height: 25px;
text-transform: uppercase;
user-select: none;
text-align: center;
color: ${props => props.textColor};
background: ${props => props.background};
border-color: ${props => props.borderColor};
&:before {
content: ${props => props.color};
}
`;
const P = styled.p``;
const TransactionName = styled.div`
flex: 1;
`;
const TransactionAmount = styled.div`
color: colors.TEXT_SECONDARY;
`;
class PendingTransactions extends PureComponent<Props> {
getPendingTransactions() {
return this.props.pending.filter(tx => !tx.rejected);
}
getTransactionIconColors(tx: any) {
let iconColors = {
textColor: '#fff',
background: '#000',
borderColor: '#000',
};
const bgColorFactory = new ColorHash({ lightness: 0.7 });
const textColorFactory = new ColorHash();
const isSmartContractTx = tx.currency !== this.props.network.symbol;
if (isSmartContractTx) {
const token: ?Token = this.findTransactionToken(tx.currency);
if (!token) {
iconColors = {
textColor: '#ffffff',
background: '#000000',
borderColor: '#000000',
};
} else {
const bgColor: string = bgColorFactory.hex(token.name);
iconColors = {
textColor: textColorFactory.hex(token.name),
background: bgColor,
borderColor: bgColor,
};
}
} else {
iconColors = {
textColor: textColorFactory.hex(tx.network),
background: bgColorFactory.hex(tx.network),
borderColor: bgColorFactory.hex(tx.network),
};
}
return iconColors;
}
getTransactionSymbol(tx: any) {
let { symbol } = this.props.network;
const isSmartContractTx = tx.currency !== this.props.network.symbol;
if (isSmartContractTx) {
const token: ?Token = this.findTransactionToken(tx.currency);
symbol = token ? token.symbol.toUpperCase() : 'Unknown';
}
return symbol;
}
getTransactionName(tx: any) {
let { name } = this.props.network;
const isSmartContractTx = tx.currency !== this.props.network.symbol;
if (isSmartContractTx) {
const token: ?Token = this.findTransactionToken(tx.currency);
name = token ? token.symbol.toUpperCase() : 'Unknown';
}
return name;
}
findTransactionToken(transactionCurrency: string) {
return this.props.tokens.find(t => t.symbol === transactionCurrency);
}
render() {
return (
<Wrapper>
<H2>Pending transactions</H2>
{this.getPendingTransactions().map(tx => (
<TransactionWrapper
key={tx.hash}
>
<TransactionIcon
textColor={() => this.getTransactionIconColors(tx).textColor}
background={() => this.getTransactionIconColors(tx).background}
borderColor={() => this.getTransactionIconColors(tx).borderColor}
>
<ScaleText widthOnly>
<P>{this.getTransactionSymbol(tx)}</P>
</ScaleText>
</TransactionIcon>
<TransactionName>
<StyledLink
href={`${this.props.network.explorer.tx}${tx.hash}`}
isGray
>
{this.getTransactionName(tx)}
</StyledLink>
</TransactionName>
<TransactionAmount>
{tx.currency !== this.props.network.symbol ? tx.amount : tx.total} {this.getTransactionSymbol(tx)}
</TransactionAmount>
</TransactionWrapper>
))}
</Wrapper>
);
}
}
const PendingTransactions = (props: Props) => {
// const pending = props.pending.filter(tx => !tx.rejected).concat(testData);
const pending = props.pending.filter(tx => !tx.rejected);
return (
<Wrapper>
<H2>Pending transactions</H2>
{pending.map(tx => (
<Transaction key={tx.hash} network={props.network} tx={tx} />
))}
</Wrapper>
);
};
export default PendingTransactions;

@ -0,0 +1,99 @@
export default [
{
type: 'recv',
timestamp: '16:20',
address: 'a',
deviceState: 'a',
status: 'pending',
confirmations: 0,
inputs: [
{
addresses: ['in1'],
},
{
addresses: ['in2'],
},
],
outputs: [
{
addresses: ['out1', 'out2'],
},
],
sequence: 1,
hash: '1234',
network: 'eth',
currency: 'eth',
amount: '0.001',
total: '0.001001',
fee: '0.000001',
},
{
type: 'send',
address: 'a',
deviceState: 'a',
confirmations: 0,
status: 'pending',
inputs: [
{
addresses: ['in1', 'in2'],
},
],
outputs: [
{
addresses: ['out1'],
},
],
sequence: 1,
hash: '12345',
network: 'eth',
currency: 'T01',
amount: '0.001',
total: '0.001001',
fee: '0.000001',
},
{
address: '0x73d0385F4d8E00C5e6504C6030F47BF6212736A8',
amount: '1',
confirmations: 0,
currency: 'T01',
deviceState: '4058d01c7c964787b7d06f0f32ce229088e123a042bf95aad658f1b1b99c73fc',
fee: '0.0002',
hash: '0xbf6ac83bdf29abacbca91cd4100ddd5cd8de16e72911ea7d1daec17ccbfc6099',
inputs: [{
addresses: ['0x73d0385F4d8E00C5e6504C6030F47BF6212736A8'],
}],
network: 'trop',
outputs: [{
addresses: ['0xFA01a39f8Abaeb660c3137f14A310d0b414b2A15'],
}],
sequence: 249,
status: 'pending',
timestamp: '',
total: '0.0002',
type: 'send',
},
{
address: '0x73d0385F4d8E00C5e6504C6030F47BF6212736A8',
amount: '1',
confirmations: 0,
currency: 'trop',
deviceState: '4058d01c7c964787b7d06f0f32ce229088e123a042bf95aad658f1b1b99c73fc',
fee: '0.0002',
hash: '0xbf6ac83bdf29abacbca91cd4100ddd5cd8de16e72911ea7d1daec17ccbfc6099',
inputs: [{
addresses: ['0x73d0385F4d8E00C5e6504C6030F47BF6212736A8'],
}],
network: 'trop',
outputs: [{
addresses: ['0xFA01a39f8Abaeb660c3137f14A310d0b414b2A15'],
}],
sequence: 249,
status: 'pending',
timestamp: '',
total: '0.0002',
type: 'send',
},
];

@ -132,6 +132,7 @@ const ToggleAdvancedSettingsButton = styled(Button)`
const SendButton = styled(Button)`
min-width: ${props => (props.isAdvancedSettingsHidden ? '50%' : '100%')};
word-break: break-all;
@media screen and (max-width: ${SmallScreenWidth}) {
margin-top: ${props => (props.isAdvancedSettingsHidden ? '10px' : 0)};

@ -82,6 +82,7 @@ const ToggleAdvancedSettingsWrapper = styled.div`
const SendButton = styled(Button)`
min-width: ${props => (props.isAdvancedSettingsHidden ? '50%' : '100%')};
word-break: break-all;
@media screen and (max-width: ${SmallScreenWidth}) {
margin-top: ${props => (props.isAdvancedSettingsHidden ? '10px' : 0)};

@ -50,7 +50,7 @@ module.exports = {
rules: [
{
test: /\.js?$/,
exclude: [/node_modules/, /trezor-blockchain-link\/build\/workers/],
exclude: [/node_modules/, /blockchain-link\/build\/workers/],
use: ['babel-loader'],
},
{

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save