mirror of
https://github.com/trezor/trezor-wallet
synced 2025-03-02 17:26:13 +00:00
commit
cf76e8cf68
@ -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));
|
||||
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;
|
||||
|
||||
// // 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,
|
||||
// // }
|
||||
// // });
|
||||
// }
|
||||
// }
|
||||
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: [],
|
||||
},
|
||||
|
104
src/components/Transaction/index.js
Normal file
104
src/components/Transaction/index.js
Normal file
@ -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;
|
BIN
src/components/images/CoinLogo/images/txrp.png
Normal file
BIN
src/components/images/CoinLogo/images/txrp.png
Normal file
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';
|
||||
import type { Action, Transaction } 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,
|
||||
};
|
||||
|
||||
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 PendingTransactions = (props: Props) => {
|
||||
// const pending = props.pending.filter(tx => !tx.rejected).concat(testData);
|
||||
const pending = props.pending.filter(tx => !tx.rejected);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
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'],
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user