mirror of
https://github.com/trezor/trezor-wallet
synced 2025-01-03 21:00:55 +00:00
Merge pull request #288 from trezor/feature/ripple-custom-fee
Feature/ripple custom fee
This commit is contained in:
commit
10df59f1da
@ -70,7 +70,7 @@
|
|||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.2",
|
||||||
"styled-components": "^4.1.2",
|
"styled-components": "^4.1.2",
|
||||||
"styled-normalize": "^8.0.4",
|
"styled-normalize": "^8.0.4",
|
||||||
"trezor-connect": "6.0.3-beta.5",
|
"trezor-connect": "6.0.3-beta.10",
|
||||||
"wallet-address-validator": "^0.2.4",
|
"wallet-address-validator": "^0.2.4",
|
||||||
"web3": "1.0.0-beta.35",
|
"web3": "1.0.0-beta.35",
|
||||||
"webpack": "^4.16.3",
|
"webpack": "^4.16.3",
|
||||||
|
@ -52,6 +52,18 @@
|
|||||||
"defaultGasLimit": 21000,
|
"defaultGasLimit": 21000,
|
||||||
"defaultGasLimitTokens": 200000,
|
"defaultGasLimitTokens": 200000,
|
||||||
"decimals": 18,
|
"decimals": 18,
|
||||||
|
"fee": {
|
||||||
|
"defaultFee": "64",
|
||||||
|
"minFee": "10",
|
||||||
|
"maxFee": "10000",
|
||||||
|
"defaultGasLimit": "21000",
|
||||||
|
"defaultGasLimitTokens": "200000",
|
||||||
|
"levels": [
|
||||||
|
{ "name": "High", "value": "96", "multiplier": 1.5 },
|
||||||
|
{ "name": "Normal", "value": "64", "multiplier": 1, "recommended": true },
|
||||||
|
{ "name": "Low", "value": "48", "multiplier": 0.75 }
|
||||||
|
]
|
||||||
|
},
|
||||||
"tokens": "./data/ropstenTokens.json",
|
"tokens": "./data/ropstenTokens.json",
|
||||||
"web3": [
|
"web3": [
|
||||||
"wss://ropsten1.trezor.io/geth"
|
"wss://ropsten1.trezor.io/geth"
|
||||||
@ -68,6 +80,14 @@
|
|||||||
"shortcut": "xrp",
|
"shortcut": "xrp",
|
||||||
"bip44": "m/44'/144'/a'/0/0",
|
"bip44": "m/44'/144'/a'/0/0",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
|
"fee": {
|
||||||
|
"defaultFee": "12",
|
||||||
|
"minFee": "10",
|
||||||
|
"maxFee": "10000",
|
||||||
|
"levels": [
|
||||||
|
{"name": "Normal", "value": "12", "recommended": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
"explorer": {
|
"explorer": {
|
||||||
"tx": "https://xrpcharts.ripple.com/#/transactions/",
|
"tx": "https://xrpcharts.ripple.com/#/transactions/",
|
||||||
"address": "https://xrpcharts.ripple.com/#/graph/"
|
"address": "https://xrpcharts.ripple.com/#/graph/"
|
||||||
@ -81,6 +101,14 @@
|
|||||||
"shortcut": "txrp",
|
"shortcut": "txrp",
|
||||||
"bip44": "m/44'/144'/a'/0/0",
|
"bip44": "m/44'/144'/a'/0/0",
|
||||||
"decimals": 6,
|
"decimals": 6,
|
||||||
|
"fee": {
|
||||||
|
"defaultFee": "12",
|
||||||
|
"minFee": "10",
|
||||||
|
"maxFee": "10000",
|
||||||
|
"levels": [
|
||||||
|
{"name": "Normal", "value": "12", "recommended": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
"explorer": {
|
"explorer": {
|
||||||
"tx": "https://sisyfos.trezor.io/ripple-testnet-explorer/tx/",
|
"tx": "https://sisyfos.trezor.io/ripple-testnet-explorer/tx/",
|
||||||
"address": "https://sisyfos.trezor.io/ripple-testnet-explorer/address/"
|
"address": "https://sisyfos.trezor.io/ripple-testnet-explorer/address/"
|
||||||
|
@ -8,6 +8,7 @@ import type {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
GetState,
|
||||||
PromiseAction,
|
PromiseAction,
|
||||||
|
BlockchainFeeLevel,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import type { BlockchainBlock, BlockchainNotification, BlockchainError } from 'trezor-connect';
|
import type { BlockchainBlock, BlockchainNotification, BlockchainError } from 'trezor-connect';
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ export type BlockchainAction = {
|
|||||||
} | {
|
} | {
|
||||||
type: typeof BLOCKCHAIN.UPDATE_FEE,
|
type: typeof BLOCKCHAIN.UPDATE_FEE,
|
||||||
shortcut: string,
|
shortcut: string,
|
||||||
fee: string,
|
feeLevels: Array<BlockchainFeeLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conditionally subscribe to blockchain backend
|
// Conditionally subscribe to blockchain backend
|
||||||
@ -100,7 +101,7 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
// Handle BLOCKCHAIN.ERROR event from TrezorConnect
|
||||||
// disconnect and remove Web3 webscocket instance if exists
|
// disconnect and remove Web3 websocket instance if exists
|
||||||
export const onError = (payload: $ElementType<BlockchainError, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onError = (payload: $ElementType<BlockchainError, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const shortcut = payload.coin.shortcut.toLowerCase();
|
const shortcut = payload.coin.shortcut.toLowerCase();
|
||||||
const { config } = getState().localStorage;
|
const { config } = getState().localStorage;
|
||||||
|
@ -63,7 +63,7 @@ const findTokens = (accounts: Array<Account>, tokens: Array<Token>): Array<Token
|
|||||||
|
|
||||||
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 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<Transaction>): Array<Transaction> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.address === account.descriptor && p.network === account.network)), []);
|
const findPendingTxs = (accounts: Array<Account>, pending: Array<Transaction>): Array<Transaction> => accounts.reduce((result, account) => result.concat(pending.filter(p => p.descriptor === account.descriptor && p.network === account.network)), []);
|
||||||
|
|
||||||
export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const save = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const devices: Array<TrezorDevice> = getState().devices.filter(d => d.features && d.remember === true);
|
const devices: Array<TrezorDevice> = getState().devices.filter(d => d.features && d.remember === true);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import * as ACCOUNT from 'actions/constants/account';
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
import * as SEND from 'actions/constants/send';
|
import * as SEND from 'actions/constants/send';
|
||||||
import * as WEB3 from 'actions/constants/web3';
|
import * as WEB3 from 'actions/constants/web3';
|
||||||
|
import * as BLOCKCHAIN from 'actions/constants/blockchain';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
@ -36,6 +37,7 @@ export type SendFormAction = {
|
|||||||
const actions = [
|
const actions = [
|
||||||
ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
ACCOUNT.UPDATE_SELECTED_ACCOUNT,
|
||||||
WEB3.GAS_PRICE_UPDATED,
|
WEB3.GAS_PRICE_UPDATED,
|
||||||
|
BLOCKCHAIN.UPDATE_FEE,
|
||||||
...Object.values(SEND).filter(v => typeof v === 'string'),
|
...Object.values(SEND).filter(v => typeof v === 'string'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -120,15 +120,16 @@ export const initWeb3 = (network: string, urlIndex: number = 0): PromiseAction<W
|
|||||||
web3.currentProvider.on('error', onEnd);
|
web3.currentProvider.on('error', onEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const discoverAccount = (address: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
|
export const discoverAccount = (descriptor: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
|
||||||
const instance: Web3Instance = await dispatch(initWeb3(network));
|
const instance: Web3Instance = await dispatch(initWeb3(network));
|
||||||
const balance = await instance.web3.eth.getBalance(address);
|
const balance = await instance.web3.eth.getBalance(descriptor);
|
||||||
const nonce = await instance.web3.eth.getTransactionCount(address);
|
const nonce = await instance.web3.eth.getTransactionCount(descriptor);
|
||||||
return {
|
return {
|
||||||
address,
|
descriptor,
|
||||||
transactions: 0,
|
transactions: 0,
|
||||||
block: 0,
|
block: 0,
|
||||||
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
balance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
|
availableBalance: EthereumjsUnits.convert(balance, 'wei', 'ether'),
|
||||||
nonce,
|
nonce,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -16,14 +16,15 @@ import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
|||||||
import * as Web3Actions from 'actions/Web3Actions';
|
import * as Web3Actions from 'actions/Web3Actions';
|
||||||
import * as AccountsActions from 'actions/AccountsActions';
|
import * as AccountsActions from 'actions/AccountsActions';
|
||||||
|
|
||||||
export const discoverAccount = (device: TrezorDevice, address: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
|
export const discoverAccount = (device: TrezorDevice, descriptor: string, network: string): PromiseAction<EthereumAccount> => async (dispatch: Dispatch): Promise<EthereumAccount> => {
|
||||||
// get data from connect
|
// get data from connect
|
||||||
const txs = await TrezorConnect.ethereumGetAccountInfo({
|
const txs = await TrezorConnect.ethereumGetAccountInfo({
|
||||||
account: {
|
account: {
|
||||||
address,
|
descriptor,
|
||||||
block: 0,
|
block: 0,
|
||||||
transactions: 0,
|
transactions: 0,
|
||||||
balance: '0',
|
balance: '0',
|
||||||
|
availableBalance: '0',
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
},
|
},
|
||||||
coin: network,
|
coin: network,
|
||||||
@ -34,12 +35,13 @@ export const discoverAccount = (device: TrezorDevice, address: string, network:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// blockbook web3 fallback
|
// blockbook web3 fallback
|
||||||
const web3account = await dispatch(Web3Actions.discoverAccount(address, network));
|
const web3account = await dispatch(Web3Actions.discoverAccount(descriptor, network));
|
||||||
return {
|
return {
|
||||||
address,
|
descriptor,
|
||||||
transactions: txs.payload.transactions,
|
transactions: txs.payload.transactions,
|
||||||
block: txs.payload.block,
|
block: txs.payload.block,
|
||||||
balance: web3account.balance,
|
balance: web3account.balance,
|
||||||
|
availableBalance: web3account.balance,
|
||||||
nonce: web3account.nonce,
|
nonce: web3account.nonce,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -48,7 +50,6 @@ export const getTokenInfo = (input: string, network: string): PromiseAction<Netw
|
|||||||
|
|
||||||
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => dispatch(Web3Actions.getTokenBalance(token));
|
export const getTokenBalance = (token: Token): PromiseAction<string> => async (dispatch: Dispatch): Promise<string> => dispatch(Web3Actions.getTokenBalance(token));
|
||||||
|
|
||||||
|
|
||||||
export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAction<BigNumber> => async (dispatch: Dispatch): Promise<BigNumber> => {
|
export const getGasPrice = (network: string, defaultGasPrice: number): PromiseAction<BigNumber> => async (dispatch: Dispatch): Promise<BigNumber> => {
|
||||||
try {
|
try {
|
||||||
const gasPrice = await dispatch(Web3Actions.getCurrentGasPrice(network));
|
const gasPrice = await dispatch(Web3Actions.getCurrentGasPrice(network));
|
||||||
@ -96,6 +97,9 @@ export const subscribe = (network: string): PromiseAction<void> => async (dispat
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const onBlockMined = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onBlockMined = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
// TODO: handle rollback,
|
||||||
|
// check latest saved transaction blockhash against blockhheight
|
||||||
|
|
||||||
// try to resolve pending transactions
|
// try to resolve pending transactions
|
||||||
await dispatch(Web3Actions.resolvePendingTransactions(network));
|
await dispatch(Web3Actions.resolvePendingTransactions(network));
|
||||||
|
|
||||||
@ -129,10 +133,10 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (dis
|
|||||||
|
|
||||||
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const { notification } = payload;
|
const { notification } = payload;
|
||||||
const account = getState().accounts.find(a => a.descriptor === notification.address);
|
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
||||||
if (!account) return;
|
if (!account) return;
|
||||||
|
|
||||||
if (notification.status === 'pending') {
|
if (!notification.blockHeight) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.ADD,
|
type: PENDING.ADD,
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -530,9 +530,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
const fee = ValidationActions.calculateFee(currentState.gasLimit, currentState.gasPrice);
|
const fee = ValidationActions.calculateFee(currentState.gasLimit, currentState.gasPrice);
|
||||||
const blockchainNotification = {
|
const blockchainNotification = {
|
||||||
type: 'send',
|
type: 'send',
|
||||||
status: 'pending',
|
descriptor: account.descriptor,
|
||||||
confirmations: 0,
|
|
||||||
address: account.descriptor,
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
addresses: [account.descriptor],
|
addresses: [account.descriptor],
|
||||||
@ -553,7 +551,15 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
total: currentState.total,
|
total: currentState.total,
|
||||||
|
|
||||||
sequence: nonce,
|
sequence: nonce,
|
||||||
currency: isToken ? currentState.currency : undefined,
|
tokens: isToken ? [{
|
||||||
|
name: currentState.currency,
|
||||||
|
shortcut: currentState.currency,
|
||||||
|
value: currentState.amount,
|
||||||
|
}] : undefined,
|
||||||
|
|
||||||
|
blockHeight: 0,
|
||||||
|
blockHash: undefined,
|
||||||
|
timestamp: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(BlockchainActions.onNotification({
|
dispatch(BlockchainActions.onNotification({
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import TrezorConnect from 'trezor-connect';
|
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 PENDING from 'actions/constants/pendingTx';
|
||||||
import * as AccountsActions from 'actions/AccountsActions';
|
import * as AccountsActions from 'actions/AccountsActions';
|
||||||
import { toDecimalAmount } from 'utils/formatUtils';
|
import { toDecimalAmount } from 'utils/formatUtils';
|
||||||
|
import { observeChanges } from 'reducers/utils';
|
||||||
|
|
||||||
import type { BlockchainNotification } from 'trezor-connect';
|
import type { BlockchainNotification } from 'trezor-connect';
|
||||||
import type {
|
import type {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
GetState,
|
||||||
PromiseAction,
|
PromiseAction,
|
||||||
|
PayloadAction,
|
||||||
|
Network,
|
||||||
|
BlockchainFeeLevel,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
const DECIMALS: number = 6;
|
|
||||||
|
|
||||||
export const subscribe = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
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.descriptor);
|
const accounts: Array<string> = getState().accounts.filter(a => a.network === network).map(a => a.descriptor);
|
||||||
await TrezorConnect.blockchainSubscribe({
|
await TrezorConnect.blockchainSubscribe({
|
||||||
@ -23,22 +25,41 @@ export const subscribe = (network: string): PromiseAction<void> => async (dispat
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get current known fee
|
||||||
|
// Use default values from appConfig.json if it wasn't downloaded from blockchain yet
|
||||||
|
// update them later, after onBlockMined event
|
||||||
|
export const getFeeLevels = (network: Network): PayloadAction<Array<BlockchainFeeLevel>> => (dispatch: Dispatch, getState: GetState): Array<BlockchainFeeLevel> => {
|
||||||
|
const blockchain = getState().blockchain.find(b => b.shortcut === network.shortcut);
|
||||||
|
if (!blockchain || blockchain.feeLevels.length < 1) {
|
||||||
|
return network.fee.levels.map(level => ({
|
||||||
|
name: level.name,
|
||||||
|
value: level.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return blockchain.feeLevels;
|
||||||
|
};
|
||||||
|
|
||||||
export const onBlockMined = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onBlockMined = (network: string): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const blockchain = getState().blockchain.find(b => b.shortcut === network);
|
const blockchain = getState().blockchain.find(b => b.shortcut === network);
|
||||||
if (!blockchain) return;
|
if (!blockchain) return; // flowtype fallback
|
||||||
|
|
||||||
// const fee = await TrezorConnect.blockchainGetFee({
|
// if last update was more than 5 minutes ago
|
||||||
// coin: network,
|
const now = new Date().getTime();
|
||||||
// });
|
if (blockchain.feeTimestamp < now - 300000) {
|
||||||
// if (!fee.success) return;
|
const feeRequest = await TrezorConnect.blockchainEstimateFee({
|
||||||
|
coin: network,
|
||||||
|
});
|
||||||
|
if (feeRequest.success && observeChanges(blockchain.feeLevels, feeRequest.payload)) {
|
||||||
|
// check if downloaded fee levels are different
|
||||||
|
dispatch({
|
||||||
|
type: BLOCKCHAIN.UPDATE_FEE,
|
||||||
|
shortcut: network,
|
||||||
|
feeLevels: feeRequest.payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if (fee.payload !== blockchain.fee) {
|
// TODO: check for blockchain rollbacks here!
|
||||||
// dispatch({
|
|
||||||
// type: BLOCKCHAIN.UPDATE_FEE,
|
|
||||||
// shortcut: network,
|
|
||||||
// fee: fee.payload,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
const accounts: Array<any> = getState().accounts.filter(a => a.network === network);
|
const accounts: Array<any> = getState().accounts.filter(a => a.network === network);
|
||||||
// console.warn('ACCOUNTS', accounts);
|
// console.warn('ACCOUNTS', accounts);
|
||||||
@ -68,10 +89,12 @@ export const onBlockMined = (network: string): PromiseAction<void> => async (dis
|
|||||||
|
|
||||||
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const onNotification = (payload: $ElementType<BlockchainNotification, 'payload'>): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const { notification } = payload;
|
const { notification } = payload;
|
||||||
const account = getState().accounts.find(a => a.descriptor === notification.address);
|
const account = getState().accounts.find(a => a.descriptor === notification.descriptor);
|
||||||
if (!account) return;
|
if (!account) return;
|
||||||
|
const { network } = getState().selectedAccount;
|
||||||
|
if (!network) return; // flowtype fallback
|
||||||
|
|
||||||
if (notification.status === 'pending') {
|
if (!notification.blockHeight) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.ADD,
|
type: PENDING.ADD,
|
||||||
payload: {
|
payload: {
|
||||||
@ -79,14 +102,14 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
|
|||||||
deviceState: account.deviceState,
|
deviceState: account.deviceState,
|
||||||
network: account.network,
|
network: account.network,
|
||||||
|
|
||||||
amount: toDecimalAmount(notification.amount, DECIMALS),
|
amount: toDecimalAmount(notification.amount, network.decimals),
|
||||||
total: notification.type === 'send' ? toDecimalAmount(notification.total, DECIMALS) : toDecimalAmount(notification.amount, DECIMALS),
|
total: notification.type === 'send' ? toDecimalAmount(notification.total, network.decimals) : toDecimalAmount(notification.amount, network.decimals),
|
||||||
fee: toDecimalAmount(notification.fee, DECIMALS),
|
fee: toDecimalAmount(notification.fee, network.decimals),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// todo: replace "send success" notification with link to explorer
|
// todo: replace "send success" notification with link to explorer
|
||||||
} else if (notification.status === 'confirmed') {
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: PENDING.TX_RESOLVED,
|
type: PENDING.TX_RESOLVED,
|
||||||
hash: notification.hash,
|
hash: notification.hash,
|
||||||
@ -106,8 +129,8 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
|
|||||||
dispatch(AccountsActions.update({
|
dispatch(AccountsActions.update({
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
...account,
|
...account,
|
||||||
balance: toDecimalAmount(updatedAccount.payload.balance, DECIMALS),
|
balance: toDecimalAmount(updatedAccount.payload.balance, network.decimals),
|
||||||
availableBalance: toDecimalAmount(updatedAccount.payload.availableBalance, DECIMALS),
|
availableBalance: toDecimalAmount(updatedAccount.payload.availableBalance, network.decimals),
|
||||||
block: updatedAccount.payload.block,
|
block: updatedAccount.payload.block,
|
||||||
sequence: updatedAccount.payload.sequence,
|
sequence: updatedAccount.payload.sequence,
|
||||||
reserve: '0',
|
reserve: '0',
|
||||||
|
@ -66,7 +66,7 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover
|
|||||||
deviceID: device.features ? device.features.device_id : '0',
|
deviceID: device.features ? device.features.device_id : '0',
|
||||||
deviceState: device.state || '0',
|
deviceState: device.state || '0',
|
||||||
accountPath: account.path || [],
|
accountPath: account.path || [],
|
||||||
descriptor: account.address,
|
descriptor: account.descriptor,
|
||||||
|
|
||||||
balance: toDecimalAmount(account.balance, network.decimals),
|
balance: toDecimalAmount(account.balance, network.decimals),
|
||||||
availableBalance: toDecimalAmount(account.availableBalance, network.decimals),
|
availableBalance: toDecimalAmount(account.availableBalance, network.decimals),
|
||||||
|
@ -16,9 +16,10 @@ import type {
|
|||||||
AsyncAction,
|
AsyncAction,
|
||||||
TrezorDevice,
|
TrezorDevice,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import type { State } from 'reducers/SendFormRippleReducer';
|
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
||||||
import * as SessionStorageActions from '../SessionStorageActions';
|
import * as SessionStorageActions from '../SessionStorageActions';
|
||||||
|
|
||||||
|
import * as BlockchainActions from './BlockchainActions';
|
||||||
import * as ValidationActions from './SendFormValidationActions';
|
import * as ValidationActions from './SendFormValidationActions';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -43,14 +44,14 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
// handle gasPrice update from backend
|
// handle gasPrice update from backend
|
||||||
// recalculate fee levels if needed
|
// recalculate fee levels if needed
|
||||||
if (action.type === BLOCKCHAIN.UPDATE_FEE) {
|
if (action.type === BLOCKCHAIN.UPDATE_FEE) {
|
||||||
dispatch(ValidationActions.onFeeUpdated(action.shortcut, action.fee));
|
dispatch(ValidationActions.onFeeUpdated(action.shortcut, action.feeLevels));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let shouldUpdate: boolean = false;
|
let shouldUpdate: boolean = false;
|
||||||
// check if "selectedAccount" reducer changed
|
// check if "selectedAccount" reducer changed
|
||||||
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
|
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
|
||||||
account: ['balance', 'nonce'],
|
account: ['balance', 'sequence'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// check if "sendForm" reducer changed
|
// check if "sendForm" reducer changed
|
||||||
@ -79,7 +80,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
network,
|
network,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
|
|
||||||
if (!account || !network) return;
|
if (!account || account.networkType !== 'ripple' || !network) return;
|
||||||
|
|
||||||
const stateFromStorage = dispatch(SessionStorageActions.loadRippleDraftTransaction());
|
const stateFromStorage = dispatch(SessionStorageActions.loadRippleDraftTransaction());
|
||||||
if (stateFromStorage) {
|
if (stateFromStorage) {
|
||||||
@ -91,7 +92,8 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const feeLevels = dispatch(ValidationActions.getFeeLevels(network.symbol));
|
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
||||||
|
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
|
||||||
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -103,11 +105,20 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
networkSymbol: network.symbol,
|
networkSymbol: network.symbol,
|
||||||
feeLevels,
|
feeLevels,
|
||||||
selectedFeeLevel,
|
selectedFeeLevel,
|
||||||
|
fee: network.fee.defaultFee,
|
||||||
sequence: '1',
|
sequence: '1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from UI from "advanced" button
|
||||||
|
*/
|
||||||
|
export const toggleAdvanced = (): Action => ({
|
||||||
|
type: SEND.TOGGLE_ADVANCED,
|
||||||
|
networkType: 'ripple',
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "address" field change
|
* Called from UI on "address" field change
|
||||||
*/
|
*/
|
||||||
@ -160,6 +171,78 @@ export const onSetMax = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from UI on "fee" selection change
|
||||||
|
*/
|
||||||
|
export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const state = getState().sendFormRipple;
|
||||||
|
|
||||||
|
const isCustom = feeLevel.value === 'Custom';
|
||||||
|
const advanced = isCustom ? true : state.advanced;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SEND.CHANGE,
|
||||||
|
networkType: 'ripple',
|
||||||
|
state: {
|
||||||
|
...state,
|
||||||
|
advanced,
|
||||||
|
selectedFeeLevel: feeLevel,
|
||||||
|
fee: isCustom ? state.selectedFeeLevel.fee : feeLevel.fee,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from UI from "update recommended fees" button
|
||||||
|
*/
|
||||||
|
export const updateFeeLevels = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const {
|
||||||
|
account,
|
||||||
|
network,
|
||||||
|
} = getState().selectedAccount;
|
||||||
|
if (!account || !network) return;
|
||||||
|
|
||||||
|
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
||||||
|
const state: State = getState().sendFormRipple;
|
||||||
|
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels, state.selectedFeeLevel));
|
||||||
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SEND.CHANGE,
|
||||||
|
networkType: 'ripple',
|
||||||
|
state: {
|
||||||
|
...state,
|
||||||
|
feeLevels,
|
||||||
|
selectedFeeLevel,
|
||||||
|
feeNeedsUpdate: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from UI on "advanced / fee" field change
|
||||||
|
*/
|
||||||
|
export const onFeeChange = (fee: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const { network } = getState().selectedAccount;
|
||||||
|
if (!network) return;
|
||||||
|
const state: State = getState().sendFormRipple;
|
||||||
|
|
||||||
|
// switch to custom fee level
|
||||||
|
let newSelectedFeeLevel = state.selectedFeeLevel;
|
||||||
|
if (state.selectedFeeLevel.value !== 'Custom') newSelectedFeeLevel = state.feeLevels.find(f => f.value === 'Custom');
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SEND.CHANGE,
|
||||||
|
networkType: 'ripple',
|
||||||
|
state: {
|
||||||
|
...state,
|
||||||
|
untouched: false,
|
||||||
|
touched: { ...state.touched, fee: true },
|
||||||
|
selectedFeeLevel: newSelectedFeeLevel,
|
||||||
|
fee,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI from "send" button
|
* Called from UI from "send" button
|
||||||
@ -190,7 +273,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
useEmptyPassphrase: selected.useEmptyPassphrase,
|
useEmptyPassphrase: selected.useEmptyPassphrase,
|
||||||
path: account.accountPath,
|
path: account.accountPath,
|
||||||
transaction: {
|
transaction: {
|
||||||
fee: blockchain.fee, // Fee must be in the range of 10 to 10,000 drops
|
fee: currentState.selectedFeeLevel.fee, // Fee must be in the range of 10 to 10,000 drops
|
||||||
flags: 0x80000000,
|
flags: 0x80000000,
|
||||||
sequence: account.sequence,
|
sequence: account.sequence,
|
||||||
payment: {
|
payment: {
|
||||||
@ -256,8 +339,12 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
toggleAdvanced,
|
||||||
onAddressChange,
|
onAddressChange,
|
||||||
onAmountChange,
|
onAmountChange,
|
||||||
onSetMax,
|
onSetMax,
|
||||||
|
onFeeLevelChange,
|
||||||
|
updateFeeLevels,
|
||||||
|
onFeeChange,
|
||||||
onSend,
|
onSend,
|
||||||
};
|
};
|
@ -9,11 +9,13 @@ import type {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
GetState,
|
||||||
PayloadAction,
|
PayloadAction,
|
||||||
|
BlockchainFeeLevel,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
||||||
|
|
||||||
import AddressValidator from 'wallet-address-validator';
|
import AddressValidator from 'wallet-address-validator';
|
||||||
// general regular expressions
|
// general regular expressions
|
||||||
|
const ABS_RE = new RegExp('^[0-9]+$');
|
||||||
const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$');
|
const NUMBER_RE: RegExp = new RegExp('^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$');
|
||||||
const XRP_6_RE = new RegExp('^(0|0\\.([0-9]{0,6})?|[1-9][0-9]*\\.?([0-9]{0,6})?|\\.[0-9]{0,6})$');
|
const XRP_6_RE = new RegExp('^(0|0\\.([0-9]{0,6})?|[1-9][0-9]*\\.?([0-9]{0,6})?|\\.[0-9]{0,6})$');
|
||||||
|
|
||||||
@ -21,11 +23,10 @@ const XRP_6_RE = new RegExp('^(0|0\\.([0-9]{0,6})?|[1-9][0-9]*\\.?([0-9]{0,6})?|
|
|||||||
* Called from SendFormActions.observe
|
* Called from SendFormActions.observe
|
||||||
* Reaction for BLOCKCHAIN.FEE_UPDATED action
|
* Reaction for BLOCKCHAIN.FEE_UPDATED action
|
||||||
*/
|
*/
|
||||||
export const onFeeUpdated = (network: string, fee: string): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
|
export const onFeeUpdated = (network: string, feeLevels: Array<BlockchainFeeLevel>): PayloadAction<void> => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const state = getState().sendFormRipple;
|
const state = getState().sendFormRipple;
|
||||||
if (network === state.networkSymbol) return;
|
if (network === state.networkSymbol) return;
|
||||||
|
|
||||||
|
|
||||||
if (!state.untouched) {
|
if (!state.untouched) {
|
||||||
// if there is a transaction draft let the user know
|
// if there is a transaction draft let the user know
|
||||||
// and let him update manually
|
// and let him update manually
|
||||||
@ -35,24 +36,21 @@ export const onFeeUpdated = (network: string, fee: string): PayloadAction<void>
|
|||||||
state: {
|
state: {
|
||||||
...state,
|
...state,
|
||||||
feeNeedsUpdate: true,
|
feeNeedsUpdate: true,
|
||||||
recommendedFee: fee,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// automatically update feeLevels and gasPrice
|
// automatically update feeLevels
|
||||||
const feeLevels = dispatch(getFeeLevels(state.networkSymbol));
|
const newFeeLevels = dispatch(getFeeLevels(feeLevels));
|
||||||
const selectedFeeLevel = getSelectedFeeLevel(feeLevels, state.selectedFeeLevel);
|
const selectedFeeLevel = getSelectedFeeLevel(newFeeLevels, state.selectedFeeLevel);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.CHANGE,
|
type: SEND.CHANGE,
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
state: {
|
state: {
|
||||||
...state,
|
...state,
|
||||||
feeNeedsUpdate: false,
|
feeNeedsUpdate: false,
|
||||||
recommendedFee: fee,
|
feeLevels: newFeeLevels,
|
||||||
gasPrice: selectedFeeLevel.gasPrice,
|
|
||||||
feeLevels,
|
|
||||||
selectedFeeLevel,
|
selectedFeeLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -69,36 +67,51 @@ export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getSt
|
|||||||
state.errors = {};
|
state.errors = {};
|
||||||
state.warnings = {};
|
state.warnings = {};
|
||||||
state.infos = {};
|
state.infos = {};
|
||||||
|
state = dispatch(updateCustomFeeLabel(state));
|
||||||
state = dispatch(recalculateTotalAmount(state));
|
state = dispatch(recalculateTotalAmount(state));
|
||||||
state = dispatch(addressValidation(state));
|
state = dispatch(addressValidation(state));
|
||||||
state = dispatch(addressLabel(state));
|
state = dispatch(addressLabel(state));
|
||||||
state = dispatch(amountValidation(state));
|
state = dispatch(amountValidation(state));
|
||||||
|
state = dispatch(feeValidation(state));
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
|
network,
|
||||||
pending,
|
pending,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
if (!account) return $state;
|
if (!account || !network) return $state;
|
||||||
|
|
||||||
const blockchain = getState().blockchain.find(b => b.shortcut === account.network);
|
|
||||||
if (!blockchain) return $state;
|
|
||||||
const fee = toDecimalAmount(blockchain.fee, 6);
|
|
||||||
|
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
|
const fee = toDecimalAmount(state.selectedFeeLevel.fee, network.decimals);
|
||||||
|
|
||||||
if (state.setMax) {
|
if (state.setMax) {
|
||||||
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
||||||
const b = new BigNumber(account.balance).minus(pendingAmount);
|
const availableBalance = new BigNumber(account.balance).minus(pendingAmount);
|
||||||
state.amount = calculateMaxAmount(b, fee);
|
state.amount = calculateMaxAmount(availableBalance, fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.total = calculateTotal(state.amount, fee);
|
state.total = calculateTotal(state.amount, fee);
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateCustomFeeLabel = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
||||||
|
const { network } = getState().selectedAccount;
|
||||||
|
if (!network) return $state; // flowtype fallback
|
||||||
|
|
||||||
|
const state = { ...$state };
|
||||||
|
if ($state.selectedFeeLevel.value === 'Custom') {
|
||||||
|
state.selectedFeeLevel = {
|
||||||
|
...state.selectedFeeLevel,
|
||||||
|
fee: state.fee,
|
||||||
|
label: `${toDecimalAmount(state.fee, network.decimals)} ${state.networkSymbol}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Address value validation
|
* Address value validation
|
||||||
*/
|
*/
|
||||||
@ -189,6 +202,35 @@ const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dis
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fee value validation
|
||||||
|
*/
|
||||||
|
export const feeValidation = ($state: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
||||||
|
const state = { ...$state };
|
||||||
|
if (!state.touched.fee) return state;
|
||||||
|
|
||||||
|
const {
|
||||||
|
network,
|
||||||
|
} = getState().selectedAccount;
|
||||||
|
if (!network) return state;
|
||||||
|
|
||||||
|
const { fee } = state;
|
||||||
|
if (fee.length < 1) {
|
||||||
|
state.errors.fee = 'Fee is not set';
|
||||||
|
} else if (fee.length > 0 && !fee.match(ABS_RE)) {
|
||||||
|
state.errors.fee = 'Fee must be an absolute number';
|
||||||
|
} else {
|
||||||
|
const gl: BigNumber = new BigNumber(fee);
|
||||||
|
if (gl.lessThan(network.fee.minFee)) {
|
||||||
|
state.errors.fee = 'Fee is below recommended';
|
||||||
|
} else if (gl.greaterThan(network.fee.maxFee)) {
|
||||||
|
state.errors.fee = 'Fee is above recommended';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UTILITIES
|
* UTILITIES
|
||||||
*/
|
*/
|
||||||
@ -212,36 +254,32 @@ const calculateMaxAmount = (balance: BigNumber, fee: string): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFeeLevels = (symbol: string): PayloadAction<Array<FeeLevel>> => (dispatch: Dispatch, getState: GetState): Array<FeeLevel> => {
|
// Generate FeeLevel dataset for "fee" select
|
||||||
const blockchain = getState().blockchain.find(b => b.shortcut === symbol.toLowerCase());
|
export const getFeeLevels = (feeLevels: Array<BlockchainFeeLevel>, selected?: FeeLevel): PayloadAction<Array<FeeLevel>> => (dispatch: Dispatch, getState: GetState): Array<FeeLevel> => {
|
||||||
if (!blockchain) {
|
const { network } = getState().selectedAccount;
|
||||||
// return default fee levels (TODO: get them from config)
|
if (!network) return []; // flowtype fallback
|
||||||
return [{
|
|
||||||
value: 'Normal',
|
|
||||||
gasPrice: '0.000012',
|
|
||||||
label: `0.000012 ${symbol}`,
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
const xrpDrops = toDecimalAmount(blockchain.fee, 6);
|
// map BlockchainFeeLevel to SendFormReducer FeeLevel
|
||||||
|
const levels = feeLevels.map(level => ({
|
||||||
|
value: level.name,
|
||||||
|
fee: level.value,
|
||||||
|
label: `${toDecimalAmount(level.value, network.decimals)} ${network.symbol}`,
|
||||||
|
}));
|
||||||
|
|
||||||
// TODO: calc fee levels
|
// add "Custom" level
|
||||||
return [{
|
const customLevel = selected && selected.value === 'Custom' ? {
|
||||||
value: 'Normal',
|
value: 'Custom',
|
||||||
gasPrice: xrpDrops,
|
fee: selected.fee,
|
||||||
label: `${xrpDrops} ${symbol}`,
|
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
|
||||||
}];
|
} : {
|
||||||
|
value: 'Custom',
|
||||||
|
fee: '0',
|
||||||
|
label: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return levels.concat([customLevel]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const getFeeLevels = (shortcut: string): Array<FeeLevel> => ([
|
|
||||||
// {
|
|
||||||
// value: 'Normal',
|
|
||||||
// gasPrice: '1',
|
|
||||||
// label: `1 ${shortcut}`,
|
|
||||||
// },
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
|
|
||||||
export const getSelectedFeeLevel = (feeLevels: Array<FeeLevel>, selected: FeeLevel): FeeLevel => {
|
export const getSelectedFeeLevel = (feeLevels: Array<FeeLevel>, selected: FeeLevel): FeeLevel => {
|
||||||
const { value } = selected;
|
const { value } = selected;
|
||||||
let selectedFeeLevel: ?FeeLevel;
|
let selectedFeeLevel: ?FeeLevel;
|
||||||
|
@ -70,26 +70,24 @@ const TransactionItem = ({
|
|||||||
network,
|
network,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const url = `${network.explorer.tx}${tx.hash}`;
|
const url = `${network.explorer.tx}${tx.hash}`;
|
||||||
const date = typeof tx.timestamp === 'string' && tx.confirmations > 0 ? tx.timestamp : undefined; // TODO: format date
|
const date = typeof tx.timestamp === 'string' ? tx.timestamp : undefined; // TODO: format date
|
||||||
const addresses = (tx.type === 'send' ? tx.outputs : tx.inputs).reduce((arr, item) => arr.concat(item.addresses), []);
|
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' ? '-' : '+';
|
const operation = tx.type === 'send' ? '-' : '+';
|
||||||
|
const amount = tx.tokens ? tx.tokens.map(t => (<Amount key={t.value}>{operation}{t.value} {t.shortcut}</Amount>)) : <Amount>{operation}{tx.total} {network.symbol}</Amount>;
|
||||||
|
const fee = tx.tokens && tx.type === 'send' ? `${tx.fee} ${network.symbol}` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{ date && (<Date href={url} isGray>{ date }</Date>)}
|
{ date && (<Date href={url} isGray>{ date }</Date>)}
|
||||||
<Addresses>
|
<Addresses>
|
||||||
{ addresses.map(addr => (<Address key={addr}>{addr}</Address>)) }
|
{ addresses.map(addr => (<Address key={addr}>{addr}</Address>)) }
|
||||||
{ tx.confirmations <= 0 && (
|
{ !tx.blockHeight && (
|
||||||
<Date href={url} isGray>Transaction hash: {tx.hash}</Date>
|
<Date href={url} isGray>Transaction hash: {tx.hash}</Date>
|
||||||
)}
|
)}
|
||||||
</Addresses>
|
</Addresses>
|
||||||
<Value className={tx.type}>
|
<Value className={tx.type}>
|
||||||
<Amount>{operation}{amount}</Amount>
|
{amount}
|
||||||
{ fee && (<Fee>{operation}{fee}</Fee>) }
|
{ fee && (<Fee>{operation}{fee}</Fee>) }
|
||||||
</Value>
|
</Value>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
@ -15,7 +15,7 @@ const Img = styled.img`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TrezorImage = ({ model }: Props) => {
|
const TrezorImage = ({ model }: Props) => {
|
||||||
// $FlowIssue
|
// $FlowIssue: `require` must be a string literal.
|
||||||
const src = require(`./images/trezor-${model}.png`); // eslint-disable-line
|
const src = require(`./images/trezor-${model}.png`); // eslint-disable-line
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
|
@ -159,6 +159,7 @@ export type { Account } from 'reducers/AccountsReducer';
|
|||||||
export type { Discovery } from 'reducers/DiscoveryReducer';
|
export type { Discovery } from 'reducers/DiscoveryReducer';
|
||||||
export type { Token } from 'reducers/TokensReducer';
|
export type { Token } from 'reducers/TokensReducer';
|
||||||
export type { Web3Instance } from 'reducers/Web3Reducer';
|
export type { Web3Instance } from 'reducers/Web3Reducer';
|
||||||
|
export type { BlockchainFeeLevel } from 'reducers/BlockchainReducer';
|
||||||
|
|
||||||
export type Accounts = $ElementType<State, 'accounts'>;
|
export type Accounts = $ElementType<State, 'accounts'>;
|
||||||
export type LocalStorage = $ElementType<State, 'localStorage'>;
|
export type LocalStorage = $ElementType<State, 'localStorage'>;
|
||||||
|
@ -6,12 +6,17 @@ import * as BLOCKCHAIN_ACTION from 'actions/constants/blockchain';
|
|||||||
import type { Action } from 'flowtype';
|
import type { Action } from 'flowtype';
|
||||||
import type { BlockchainConnect, BlockchainError, BlockchainBlock } from 'trezor-connect';
|
import type { BlockchainConnect, BlockchainError, BlockchainBlock } from 'trezor-connect';
|
||||||
|
|
||||||
|
export type BlockchainFeeLevel = {
|
||||||
|
name: string,
|
||||||
|
value: string,
|
||||||
|
};
|
||||||
|
|
||||||
export type BlockchainNetwork = {
|
export type BlockchainNetwork = {
|
||||||
+shortcut: string,
|
+shortcut: string,
|
||||||
|
feeTimestamp: number,
|
||||||
|
feeLevels: Array<BlockchainFeeLevel>,
|
||||||
connected: boolean,
|
connected: boolean,
|
||||||
block: number,
|
block: number,
|
||||||
reserved: string, // xrp specific
|
|
||||||
fee: string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type State = Array<BlockchainNetwork>;
|
export type State = Array<BlockchainNetwork>;
|
||||||
@ -27,8 +32,6 @@ const onConnect = (state: State, action: BlockchainConnect): State => {
|
|||||||
return others.concat([{
|
return others.concat([{
|
||||||
...network,
|
...network,
|
||||||
connected: true,
|
connected: true,
|
||||||
fee: info.fee,
|
|
||||||
block: info.block,
|
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,8 +39,8 @@ const onConnect = (state: State, action: BlockchainConnect): State => {
|
|||||||
shortcut,
|
shortcut,
|
||||||
connected: true,
|
connected: true,
|
||||||
block: info.block,
|
block: info.block,
|
||||||
fee: info.fee,
|
feeTimestamp: 0,
|
||||||
reserved: info.reserved || '0',
|
feeLevels: [],
|
||||||
}]);
|
}]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,8 +59,8 @@ const onError = (state: State, action: BlockchainError): State => {
|
|||||||
shortcut,
|
shortcut,
|
||||||
connected: false,
|
connected: false,
|
||||||
block: 0,
|
block: 0,
|
||||||
fee: '0',
|
feeTimestamp: 0,
|
||||||
reserved: '0',
|
feeLevels: [],
|
||||||
}]);
|
}]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,14 +78,15 @@ const onBlock = (state: State, action: BlockchainBlock): State => {
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFee = (state: State, shortcut: string, fee: string): State => {
|
const updateFee = (state: State, shortcut: string, feeLevels: Array<BlockchainFeeLevel>): State => {
|
||||||
const network = state.find(b => b.shortcut === shortcut);
|
const network = state.find(b => b.shortcut === shortcut);
|
||||||
if (!network) return state;
|
if (!network) return state;
|
||||||
|
|
||||||
const others = state.filter(b => b !== network);
|
const others = state.filter(b => b !== network);
|
||||||
return others.concat([{
|
return others.concat([{
|
||||||
...network,
|
...network,
|
||||||
fee,
|
feeTimestamp: new Date().getTime(),
|
||||||
|
feeLevels,
|
||||||
}]);
|
}]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -96,7 +100,7 @@ export default (state: State = initialState, action: Action): State => {
|
|||||||
case BLOCKCHAIN_EVENT.BLOCK:
|
case BLOCKCHAIN_EVENT.BLOCK:
|
||||||
return onBlock(state, action);
|
return onBlock(state, action);
|
||||||
case BLOCKCHAIN_ACTION.UPDATE_FEE:
|
case BLOCKCHAIN_ACTION.UPDATE_FEE:
|
||||||
return updateFee(state, action.shortcut, action.fee);
|
return updateFee(state, action.shortcut, action.feeLevels);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
@ -5,6 +5,14 @@ import * as STORAGE from 'actions/constants/localStorage';
|
|||||||
|
|
||||||
import type { Action } from 'flowtype';
|
import type { Action } from 'flowtype';
|
||||||
|
|
||||||
|
type NetworkFeeLevel = {
|
||||||
|
name: string,
|
||||||
|
value: string, // ETH: gasPrice in gwei, XRP: fee in drops, BTC: sat/b
|
||||||
|
multiplier: number, // ETH specific
|
||||||
|
blocks: number, // BTC specific
|
||||||
|
recommended: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
export type Network = {
|
export type Network = {
|
||||||
type: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -15,13 +23,21 @@ export type Network = {
|
|||||||
defaultGasLimit: number;
|
defaultGasLimit: number;
|
||||||
defaultGasLimitTokens: number;
|
defaultGasLimitTokens: number;
|
||||||
defaultGasPrice: number;
|
defaultGasPrice: number;
|
||||||
chainId: number;
|
chainId: number; // ETH specific
|
||||||
explorer: {
|
explorer: {
|
||||||
tx: string;
|
tx: string;
|
||||||
address: string;
|
address: string;
|
||||||
};
|
};
|
||||||
tokens: string;
|
tokens: string;
|
||||||
decimals: number,
|
decimals: number;
|
||||||
|
fee: {
|
||||||
|
defaultFee: string;
|
||||||
|
minFee: string;
|
||||||
|
maxFee: string;
|
||||||
|
defaultGasLimit: string; // ETH specific
|
||||||
|
defaultGasLimitTokens: string; // ETH specific
|
||||||
|
levels: Array<NetworkFeeLevel>;
|
||||||
|
},
|
||||||
backends: Array<{
|
backends: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
urls: Array<string>;
|
urls: Array<string>;
|
||||||
|
@ -7,7 +7,7 @@ import type { Action } from 'flowtype';
|
|||||||
|
|
||||||
export type FeeLevel = {
|
export type FeeLevel = {
|
||||||
label: string;
|
label: string;
|
||||||
gasPrice: string;
|
fee: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export type State = {
|
|||||||
setMax: boolean;
|
setMax: boolean;
|
||||||
feeLevels: Array<FeeLevel>;
|
feeLevels: Array<FeeLevel>;
|
||||||
selectedFeeLevel: FeeLevel;
|
selectedFeeLevel: FeeLevel;
|
||||||
recommendedFee: string;
|
fee: string;
|
||||||
feeNeedsUpdate: boolean;
|
feeNeedsUpdate: boolean;
|
||||||
sequence: string;
|
sequence: string;
|
||||||
total: string;
|
total: string;
|
||||||
@ -49,11 +49,11 @@ export const initialState: State = {
|
|||||||
setMax: false,
|
setMax: false,
|
||||||
feeLevels: [],
|
feeLevels: [],
|
||||||
selectedFeeLevel: {
|
selectedFeeLevel: {
|
||||||
label: 'Normal',
|
|
||||||
gasPrice: '0',
|
|
||||||
value: 'Normal',
|
value: 'Normal',
|
||||||
|
label: '',
|
||||||
|
fee: '0',
|
||||||
},
|
},
|
||||||
recommendedFee: '0',
|
fee: '0',
|
||||||
feeNeedsUpdate: false,
|
feeNeedsUpdate: false,
|
||||||
sequence: '0',
|
sequence: '0',
|
||||||
total: '0',
|
total: '0',
|
||||||
|
@ -86,18 +86,27 @@ export const getDiscoveryProcess = (state: State): ?Discovery => {
|
|||||||
export const getAccountPendingTx = (pending: Array<Transaction>, account: ?Account): Array<Transaction> => {
|
export const getAccountPendingTx = (pending: Array<Transaction>, account: ?Account): Array<Transaction> => {
|
||||||
const a = account;
|
const a = account;
|
||||||
if (!a) return [];
|
if (!a) return [];
|
||||||
return pending.filter(p => p.network === a.network && p.address === a.descriptor);
|
return pending.filter(p => p.network === a.network && p.descriptor === a.descriptor);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPendingSequence = (pending: Array<Transaction>): number => pending.reduce((value: number, tx: Transaction) => {
|
export const getPendingSequence = (pending: Array<Transaction>): number => pending.reduce((value: number, tx: Transaction): number => {
|
||||||
if (tx.rejected) return value;
|
if (tx.rejected) return value;
|
||||||
return Math.max(value, tx.sequence + 1);
|
return Math.max(value, tx.sequence + 1);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
export const getPendingAmount = (pending: Array<Transaction>, currency: string, token: boolean = false): BigNumber => pending.reduce((value: BigNumber, tx: Transaction) => {
|
export const getPendingAmount = (pending: Array<Transaction>, currency: string, token: boolean = false): BigNumber => pending.reduce((value: BigNumber, tx: Transaction): BigNumber => {
|
||||||
if (tx.currency === currency && !tx.rejected) {
|
if (!token) {
|
||||||
return new BigNumber(value).plus(token ? tx.amount : tx.total);
|
// regular transactions
|
||||||
|
// add fees from token txs and amount from regular txs
|
||||||
|
return new BigNumber(value).plus(tx.tokens ? tx.fee : tx.total);
|
||||||
}
|
}
|
||||||
|
if (tx.tokens) {
|
||||||
|
// token transactions
|
||||||
|
const allTokens = tx.tokens.filter(t => t.shortcut === currency);
|
||||||
|
const tokensValue: BigNumber = allTokens.reduce((tv, t) => new BigNumber(value).plus(t.value), new BigNumber('0'));
|
||||||
|
return new BigNumber(value).plus(tokensValue);
|
||||||
|
}
|
||||||
|
// default
|
||||||
return value;
|
return value;
|
||||||
}, new BigNumber('0'));
|
}, new BigNumber('0'));
|
||||||
|
|
||||||
@ -115,7 +124,7 @@ export const getWeb3 = (state: State): ?Web3Instance => {
|
|||||||
return state.web3.find(w3 => w3.network === locationState.network);
|
return state.web3.find(w3 => w3.network === locationState.network);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const observeChanges = (prev: ?Object, current: ?Object, filter?: {[k: string]: Array<string>}): boolean => {
|
export const observeChanges = (prev: ?any, current: ?any, filter?: {[k: string]: Array<string>}): boolean => {
|
||||||
// 1. both objects are the same (solves simple types like string, boolean and number)
|
// 1. both objects are the same (solves simple types like string, boolean and number)
|
||||||
if (prev === current) return false;
|
if (prev === current) return false;
|
||||||
// 2. one of the objects is null/undefined
|
// 2. one of the objects is null/undefined
|
||||||
|
81
src/reducers/utils/pendingTxsExamples.js
Normal file
81
src/reducers/utils/pendingTxsExamples.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// useful data for tests
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
type: 'send',
|
||||||
|
network: 'trop',
|
||||||
|
deviceState: '4058d01c7c964787b7d06f0f32ce229088e123a042bf95aad658f1b1b99c73fc',
|
||||||
|
deviceID: 'A21DA462908B176CEE0402C1',
|
||||||
|
descriptor: '0x73d0385F4d8E00C5e6504C6030F47BF6212736A8',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
addresses: ['0x73d0385F4d8E00C5e6504C6030F47BF6212736A8'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
addresses: ['0x73d0385F4d8E00C5e6504C6030F47BF6212736A8', '2a', '3a'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hash: '0x100000',
|
||||||
|
amount: '0.01',
|
||||||
|
fee: '0.00001',
|
||||||
|
total: '0.01001',
|
||||||
|
|
||||||
|
sequence: 1,
|
||||||
|
// tokens: undefined,
|
||||||
|
tokens: [
|
||||||
|
{
|
||||||
|
name: 'Name',
|
||||||
|
shortcut: 'T01',
|
||||||
|
value: '200',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: 'Name2',
|
||||||
|
// shortcut: 'USD',
|
||||||
|
// value: '100',
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
blockHeight: 0,
|
||||||
|
blockHash: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'send',
|
||||||
|
network: 'txrp',
|
||||||
|
deviceState: '4058d01c7c964787b7d06f0f32ce229088e123a042bf95aad658f1b1b99c73fc',
|
||||||
|
deviceID: 'A21DA462908B176CEE0402C1',
|
||||||
|
descriptor: 'rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
addresses: ['rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
addresses: ['rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H', '2a', '3a'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hash: '0x100000',
|
||||||
|
amount: '0.01',
|
||||||
|
fee: '0.00001',
|
||||||
|
total: '0.01001',
|
||||||
|
|
||||||
|
sequence: 1,
|
||||||
|
tokens: undefined,
|
||||||
|
// tokens: [
|
||||||
|
// {
|
||||||
|
// name: 'Name',
|
||||||
|
// shortcut: 'T01',
|
||||||
|
// value: '200',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Name2',
|
||||||
|
// shortcut: 'USD',
|
||||||
|
// value: '100',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
|
||||||
|
blockHeight: 0,
|
||||||
|
blockHash: undefined,
|
||||||
|
},
|
||||||
|
];
|
@ -54,6 +54,18 @@ export const hexToString = (hex: string): string => {
|
|||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toDecimalAmount = (amount: string, decimals: number): string => new BigNumber(amount).div(10 ** decimals).toString(10);
|
export const toDecimalAmount = (amount: string | number, decimals: number): string => {
|
||||||
|
try {
|
||||||
|
return new BigNumber(amount).div(10 ** decimals).toString(10);
|
||||||
|
} catch (error) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fromDecimalAmount = (amount: string, decimals: number): string => new BigNumber(amount).times(10 ** decimals).toString(10);
|
export const fromDecimalAmount = (amount: string | number, decimals: number): string => {
|
||||||
|
try {
|
||||||
|
return new BigNumber(amount).times(10 ** decimals).toString(10);
|
||||||
|
} catch (error) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
export const getViewportHeight = (): number => (
|
export const getViewportHeight = (): number => (
|
||||||
// $FlowIssue
|
// $FlowIssue: "clientHeight" missing in null
|
||||||
document.documentElement.clientHeight || document.body.clientHeight // $FlowIssue
|
document.documentElement.clientHeight || document.body.clientHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getScrollX = (): number => {
|
export const getScrollX = (): number => {
|
||||||
@ -12,8 +12,8 @@ export const getScrollX = (): number => {
|
|||||||
} if (window.scrollLeft !== undefined) {
|
} if (window.scrollLeft !== undefined) {
|
||||||
return window.scrollLeft;
|
return window.scrollLeft;
|
||||||
}
|
}
|
||||||
// $FlowIssue
|
// $FlowIssue: parentNode || scrollLeft missing
|
||||||
return (document.documentElement || document.body.parentNode || document.body).scrollLeft; // $FlowIssue
|
return (document.documentElement || document.body.parentNode || document.body).scrollLeft;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getScrollY = (): number => {
|
export const getScrollY = (): number => {
|
||||||
@ -22,6 +22,6 @@ export const getScrollY = (): number => {
|
|||||||
} if (window.scrollTop !== undefined) {
|
} if (window.scrollTop !== undefined) {
|
||||||
return window.scrollTop;
|
return window.scrollTop;
|
||||||
}
|
}
|
||||||
// $FlowIssue
|
// $FlowIssue: parentNode || scrollTop missing
|
||||||
return (document.documentElement || document.body.parentNode || document.body).scrollTop; // $FlowIssue
|
return (document.documentElement || document.body.parentNode || document.body).scrollTop;
|
||||||
};
|
};
|
@ -13,9 +13,9 @@ const getInfoUrl = (networkShortcut: ?string) => {
|
|||||||
const urls = {
|
const urls = {
|
||||||
default: 'https://wiki.trezor.io',
|
default: 'https://wiki.trezor.io',
|
||||||
xrp: 'https://wiki.trezor.io/Ripple_(XRP)',
|
xrp: 'https://wiki.trezor.io/Ripple_(XRP)',
|
||||||
|
txrp: 'https://wiki.trezor.io/Ripple_(XRP)',
|
||||||
};
|
};
|
||||||
|
return networkShortcut && urls[networkShortcut] ? urls[networkShortcut] : urls.default;
|
||||||
return networkShortcut ? urls[networkShortcut] : urls.default;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -4,14 +4,15 @@ import styled from 'styled-components';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
|
import type { State } from 'flowtype';
|
||||||
import type { Location } from 'react-router';
|
|
||||||
|
|
||||||
import Indicator from './components/Indicator';
|
import Indicator from './components/Indicator';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
location: Location;
|
router: $ElementType<State, 'router'>,
|
||||||
|
selectedAccount: $ElementType<State, 'selectedAccount'>,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
@ -56,20 +57,28 @@ class TopNavigationAccount extends React.PureComponent<Props> {
|
|||||||
wrapper: ?HTMLElement;
|
wrapper: ?HTMLElement;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { state, pathname } = this.props.location;
|
const { state, pathname } = this.props.router.location;
|
||||||
if (!state) return null;
|
if (!state) return null;
|
||||||
|
const { network } = this.props.selectedAccount;
|
||||||
|
if (!network) return null;
|
||||||
|
|
||||||
const basePath = `/device/${state.device}/network/${state.network}/account/${state.account}`;
|
const basePath = `/device/${state.device}/network/${state.network}/account/${state.account}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper className="account-tabs" ref={this.wrapperRefCallback}>
|
<Wrapper className="account-tabs" ref={this.wrapperRefCallback}>
|
||||||
<StyledNavLink exact to={`${basePath}`}>Summary</StyledNavLink>
|
<StyledNavLink exact to={`${basePath}`}>Summary</StyledNavLink>
|
||||||
<StyledNavLink to={`${basePath}/receive`}>Receive</StyledNavLink>
|
<StyledNavLink to={`${basePath}/receive`}>Receive</StyledNavLink>
|
||||||
<StyledNavLink to={`${basePath}/send`}>Send</StyledNavLink>
|
<StyledNavLink to={`${basePath}/send`}>Send</StyledNavLink>
|
||||||
<StyledNavLink to={`${basePath}/signverify`}>Sign & Verify</StyledNavLink>
|
{network.type === 'ethereum'
|
||||||
|
&& <StyledNavLink to={`${basePath}/signverify`}>Sign & Verify</StyledNavLink>
|
||||||
|
}
|
||||||
<Indicator pathname={pathname} wrapper={() => this.wrapper} />
|
<Indicator pathname={pathname} wrapper={() => this.wrapper} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopNavigationAccount;
|
export default connect((state: State): Props => ({
|
||||||
|
router: state.router,
|
||||||
|
selectedAccount: state.selectedAccount,
|
||||||
|
}), null)(TopNavigationAccount);
|
@ -11,7 +11,7 @@ import Icon from 'components/Icon';
|
|||||||
import ICONS from 'config/icons';
|
import ICONS from 'config/icons';
|
||||||
import { FONT_SIZE } from 'config/variables';
|
import { FONT_SIZE } from 'config/variables';
|
||||||
|
|
||||||
import type { Props as BaseProps } from '../../ethereum/Container';
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
type Props = BaseProps & {
|
type Props = BaseProps & {
|
||||||
children: React.Node,
|
children: React.Node,
|
@ -1,6 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import { Select } from 'components/Select';
|
import { Select } from 'components/Select';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
@ -13,8 +14,9 @@ import colors from 'config/colors';
|
|||||||
import Title from 'views/Wallet/components/Title';
|
import Title from 'views/Wallet/components/Title';
|
||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
import Content from 'views/Wallet/components/Content';
|
import Content from 'views/Wallet/components/Content';
|
||||||
|
import * as stateUtils from 'reducers/utils';
|
||||||
import type { Token } from 'flowtype';
|
import type { Token } from 'flowtype';
|
||||||
import AdvancedForm from '../components/AdvancedForm';
|
import AdvancedForm from './components/AdvancedForm';
|
||||||
import PendingTransactions from '../components/PendingTransactions';
|
import PendingTransactions from '../components/PendingTransactions';
|
||||||
|
|
||||||
import type { Props } from './Container';
|
import type { Props } from './Container';
|
||||||
@ -221,10 +223,11 @@ const AccountSend = (props: Props) => {
|
|||||||
|
|
||||||
const isCurrentCurrencyToken = networkSymbol !== currency;
|
const isCurrentCurrencyToken = networkSymbol !== currency;
|
||||||
|
|
||||||
let selectedTokenBalance = 0;
|
let selectedTokenBalance = '0';
|
||||||
const selectedToken = tokens.find(t => t.symbol === currency);
|
const selectedToken = tokens.find(t => t.symbol === currency);
|
||||||
if (selectedToken) {
|
if (selectedToken) {
|
||||||
selectedTokenBalance = selectedToken.balance;
|
const pendingAmount: BigNumber = stateUtils.getPendingAmount(props.selectedAccount.pending, selectedToken.symbol, true);
|
||||||
|
selectedTokenBalance = new BigNumber(selectedToken.balance).minus(pendingAmount).toString(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
let isSendButtonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending;
|
let isSendButtonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending;
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import colors from 'config/colors';
|
||||||
|
|
||||||
|
import Input from 'components/inputs/Input';
|
||||||
|
import Tooltip from 'components/Tooltip';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import ICONS from 'config/icons';
|
||||||
|
|
||||||
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
|
type Props = BaseProps & {
|
||||||
|
children: React.Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Decide on a small screen width for the whole app
|
||||||
|
// and put it inside config/variables.js
|
||||||
|
// same variable also in "AccountSend/index.js"
|
||||||
|
const SmallScreenWidth = '850px';
|
||||||
|
|
||||||
|
const InputLabelWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AdvancedSettingsWrapper = styled.div`
|
||||||
|
padding: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
border-top: 1px solid ${colors.DIVIDER};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const GasInputRow = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SmallScreenWidth}) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const GasInput = styled(Input)`
|
||||||
|
/* min-height: 85px; */
|
||||||
|
padding-bottom: 28px;
|
||||||
|
&:first-child {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SmallScreenWidth}) {
|
||||||
|
&:first-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AdvancedSettingsSendButtonWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const getFeeInputState = (feeErrors: string, feeWarnings: string): string => {
|
||||||
|
let state = '';
|
||||||
|
if (feeWarnings && !feeErrors) {
|
||||||
|
state = 'warning';
|
||||||
|
}
|
||||||
|
if (feeErrors) {
|
||||||
|
state = 'error';
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Left = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// stateless component
|
||||||
|
const AdvancedForm = (props: Props) => {
|
||||||
|
const {
|
||||||
|
network,
|
||||||
|
} = props.selectedAccount;
|
||||||
|
if (!network) return null;
|
||||||
|
const {
|
||||||
|
errors,
|
||||||
|
warnings,
|
||||||
|
infos,
|
||||||
|
fee,
|
||||||
|
} = props.sendForm;
|
||||||
|
const {
|
||||||
|
onFeeChange,
|
||||||
|
} = props.sendFormActions;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdvancedSettingsWrapper>
|
||||||
|
<GasInputRow>
|
||||||
|
<GasInput
|
||||||
|
state={getFeeInputState(errors.fee, warnings.fee)}
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
topLabel={(
|
||||||
|
<InputLabelWrapper>
|
||||||
|
<Left>
|
||||||
|
Fee
|
||||||
|
<Tooltip
|
||||||
|
content={(
|
||||||
|
<React.Fragment>
|
||||||
|
Transfer cost in XRP drops
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
maxWidth={410}
|
||||||
|
readMoreLink="https://developers.ripple.com/transaction-cost.html"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={ICONS.HELP}
|
||||||
|
color={colors.TEXT_SECONDARY}
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Left>
|
||||||
|
</InputLabelWrapper>
|
||||||
|
)}
|
||||||
|
bottomText={errors.fee || warnings.fee || infos.fee}
|
||||||
|
value={fee}
|
||||||
|
onChange={event => onFeeChange(event.target.value)}
|
||||||
|
/>
|
||||||
|
</GasInputRow>
|
||||||
|
|
||||||
|
<AdvancedSettingsSendButtonWrapper>
|
||||||
|
{ props.children }
|
||||||
|
</AdvancedSettingsSendButtonWrapper>
|
||||||
|
</AdvancedSettingsWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdvancedForm;
|
@ -6,12 +6,15 @@ import { Select } from 'components/Select';
|
|||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
import Input from 'components/inputs/Input';
|
import Input from 'components/inputs/Input';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
|
import Link from 'components/Link';
|
||||||
import ICONS from 'config/icons';
|
import ICONS from 'config/icons';
|
||||||
import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables';
|
import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import Title from 'views/Wallet/components/Title';
|
import Title from 'views/Wallet/components/Title';
|
||||||
|
import P from 'components/Paragraph';
|
||||||
import Content from 'views/Wallet/components/Content';
|
import Content from 'views/Wallet/components/Content';
|
||||||
import PendingTransactions from '../components/PendingTransactions';
|
import PendingTransactions from '../components/PendingTransactions';
|
||||||
|
import AdvancedForm from './components/AdvancedForm';
|
||||||
|
|
||||||
import type { Props } from './Container';
|
import type { Props } from './Container';
|
||||||
|
|
||||||
@ -66,6 +69,34 @@ const CurrencySelect = styled(Select)`
|
|||||||
flex: 0.2;
|
flex: 0.2;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const FeeOptionWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeeLabelWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeeLabel = styled.span`
|
||||||
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UpdateFeeWrapper = styled.span`
|
||||||
|
margin-left: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
|
color: ${colors.WARNING_PRIMARY};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)`
|
||||||
|
margin-left: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
const ToggleAdvancedSettingsWrapper = styled.div`
|
const ToggleAdvancedSettingsWrapper = styled.div`
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -80,6 +111,14 @@ const ToggleAdvancedSettingsWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ToggleAdvancedSettingsButton = styled(Button)`
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: ${FONT_WEIGHT.SEMIBOLD};
|
||||||
|
`;
|
||||||
|
|
||||||
const SendButton = styled(Button)`
|
const SendButton = styled(Button)`
|
||||||
min-width: ${props => (props.isAdvancedSettingsHidden ? '50%' : '100%')};
|
min-width: ${props => (props.isAdvancedSettingsHidden ? '50%' : '100%')};
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
@ -89,6 +128,10 @@ const SendButton = styled(Button)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const AdvancedSettingsIcon = styled(Icon)`
|
||||||
|
margin-left: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
// render helpers
|
// render helpers
|
||||||
const getAddressInputState = (address: string, addressErrors: string, addressWarnings: string): string => {
|
const getAddressInputState = (address: string, addressErrors: string, addressWarnings: string): string => {
|
||||||
let state = '';
|
let state = '';
|
||||||
@ -128,6 +171,9 @@ const AccountSend = (props: Props) => {
|
|||||||
address,
|
address,
|
||||||
amount,
|
amount,
|
||||||
setMax,
|
setMax,
|
||||||
|
feeLevels,
|
||||||
|
selectedFeeLevel,
|
||||||
|
feeNeedsUpdate,
|
||||||
total,
|
total,
|
||||||
errors,
|
errors,
|
||||||
warnings,
|
warnings,
|
||||||
@ -137,9 +183,12 @@ const AccountSend = (props: Props) => {
|
|||||||
} = props.sendForm;
|
} = props.sendForm;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
toggleAdvanced,
|
||||||
onAddressChange,
|
onAddressChange,
|
||||||
onAmountChange,
|
onAmountChange,
|
||||||
onSetMax,
|
onSetMax,
|
||||||
|
onFeeLevelChange,
|
||||||
|
updateFeeLevels,
|
||||||
onSend,
|
onSend,
|
||||||
} = props.sendFormActions;
|
} = props.sendFormActions;
|
||||||
|
|
||||||
@ -231,18 +280,74 @@ const AccountSend = (props: Props) => {
|
|||||||
/>
|
/>
|
||||||
</InputRow>
|
</InputRow>
|
||||||
|
|
||||||
|
<InputRow>
|
||||||
|
<FeeLabelWrapper>
|
||||||
|
<FeeLabel>Fee</FeeLabel>
|
||||||
|
{feeNeedsUpdate && (
|
||||||
|
<UpdateFeeWrapper>
|
||||||
|
<Icon
|
||||||
|
icon={ICONS.WARNING}
|
||||||
|
color={colors.WARNING_PRIMARY}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
Recommended fees updated. <StyledLink onClick={updateFeeLevels} isGreen>Click here to use them</StyledLink>
|
||||||
|
</UpdateFeeWrapper>
|
||||||
|
)}
|
||||||
|
</FeeLabelWrapper>
|
||||||
|
<Select
|
||||||
|
isSearchable={false}
|
||||||
|
isClearable={false}
|
||||||
|
value={selectedFeeLevel}
|
||||||
|
onChange={onFeeLevelChange}
|
||||||
|
options={feeLevels}
|
||||||
|
formatOptionLabel={option => (
|
||||||
|
<FeeOptionWrapper>
|
||||||
|
<P>{option.value}</P>
|
||||||
|
<P>{option.label}</P>
|
||||||
|
</FeeOptionWrapper>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</InputRow>
|
||||||
|
|
||||||
<ToggleAdvancedSettingsWrapper
|
<ToggleAdvancedSettingsWrapper
|
||||||
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
||||||
>
|
>
|
||||||
<SendButton
|
<ToggleAdvancedSettingsButton
|
||||||
isDisabled={isSendButtonDisabled}
|
isTransparent
|
||||||
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
onClick={toggleAdvanced}
|
||||||
onClick={() => onSend()}
|
|
||||||
>
|
>
|
||||||
{sendButtonText}
|
Advanced settings
|
||||||
</SendButton>
|
<AdvancedSettingsIcon
|
||||||
|
icon={ICONS.ARROW_DOWN}
|
||||||
|
color={colors.TEXT_SECONDARY}
|
||||||
|
size={24}
|
||||||
|
isActive={advanced}
|
||||||
|
canAnimate
|
||||||
|
/>
|
||||||
|
</ToggleAdvancedSettingsButton>
|
||||||
|
|
||||||
|
{isAdvancedSettingsHidden && (
|
||||||
|
<SendButton
|
||||||
|
isDisabled={isSendButtonDisabled}
|
||||||
|
isAdvancedSettingsHidden={isAdvancedSettingsHidden}
|
||||||
|
onClick={() => onSend()}
|
||||||
|
>
|
||||||
|
{sendButtonText}
|
||||||
|
</SendButton>
|
||||||
|
)}
|
||||||
</ToggleAdvancedSettingsWrapper>
|
</ToggleAdvancedSettingsWrapper>
|
||||||
|
|
||||||
|
{advanced && (
|
||||||
|
<AdvancedForm {...props}>
|
||||||
|
<SendButton
|
||||||
|
isDisabled={isSendButtonDisabled}
|
||||||
|
onClick={() => onSend()}
|
||||||
|
>
|
||||||
|
{sendButtonText}
|
||||||
|
</SendButton>
|
||||||
|
</AdvancedForm>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
{props.selectedAccount.pending.length > 0 && (
|
{props.selectedAccount.pending.length > 0 && (
|
||||||
<PendingTransactions
|
<PendingTransactions
|
||||||
|
@ -78,6 +78,8 @@ const AccountSummary = (props: Props) => {
|
|||||||
const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol);
|
const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol);
|
||||||
const balance: string = new BigNumber(account.balance).minus(pendingAmount).toString(10);
|
const balance: string = new BigNumber(account.balance).minus(pendingAmount).toString(10);
|
||||||
|
|
||||||
|
const TMP_SHOW_HISTORY = false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Content>
|
<Content>
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -86,27 +88,29 @@ const AccountSummary = (props: Props) => {
|
|||||||
<StyledCoinLogo network={account.network} />
|
<StyledCoinLogo network={account.network} />
|
||||||
<AccountTitle>Account #{parseInt(account.index, 10) + 1}</AccountTitle>
|
<AccountTitle>Account #{parseInt(account.index, 10) + 1}</AccountTitle>
|
||||||
</AccountName>
|
</AccountName>
|
||||||
<Link href={explorerLink} isGray>See full transaction history</Link>
|
{ !account.empty && <Link href={explorerLink} isGray>See full transaction history</Link> }
|
||||||
</AccountHeading>
|
</AccountHeading>
|
||||||
<AccountBalance
|
<AccountBalance
|
||||||
network={network}
|
network={network}
|
||||||
balance={balance}
|
balance={balance}
|
||||||
fiat={props.fiat}
|
fiat={props.fiat}
|
||||||
/>
|
/>
|
||||||
<H2Wrapper>
|
{ TMP_SHOW_HISTORY && (
|
||||||
<H2>History</H2>
|
<H2Wrapper>
|
||||||
<StyledTooltip
|
<H2>History</H2>
|
||||||
maxWidth={200}
|
<StyledTooltip
|
||||||
placement="top"
|
maxWidth={200}
|
||||||
content="Insert token name, symbol or address to be able to send it."
|
placement="top"
|
||||||
>
|
content="Insert token name, symbol or address to be able to send it."
|
||||||
<StyledIcon
|
>
|
||||||
icon={ICONS.HELP}
|
<StyledIcon
|
||||||
color={colors.TEXT_SECONDARY}
|
icon={ICONS.HELP}
|
||||||
size={24}
|
color={colors.TEXT_SECONDARY}
|
||||||
/>
|
size={24}
|
||||||
</StyledTooltip>
|
/>
|
||||||
</H2Wrapper>
|
</StyledTooltip>
|
||||||
|
</H2Wrapper>)
|
||||||
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
|
18
yarn.lock
18
yarn.lock
@ -130,7 +130,6 @@
|
|||||||
"@babel/runtime@^7.2.0":
|
"@babel/runtime@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f"
|
||||||
integrity sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.12.0"
|
regenerator-runtime "^0.12.0"
|
||||||
|
|
||||||
@ -2621,7 +2620,6 @@ connect-history-api-fallback@^1.3.0:
|
|||||||
connected-react-router@^6.0.0:
|
connected-react-router@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.0.0.tgz#cb7ccbbc5ed353832ecd91d68289c916e8aba734"
|
resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.0.0.tgz#cb7ccbbc5ed353832ecd91d68289c916e8aba734"
|
||||||
integrity sha512-TarPqf2wY3cz993Mw3eBg2U12M5OmaGwKzJsinvRQh61nKb8WMUvimyhu6u2HeWS8625PHFXjNOU0OIAMWj/bQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
immutable "^3.8.1"
|
immutable "^3.8.1"
|
||||||
seamless-immutable "^7.1.3"
|
seamless-immutable "^7.1.3"
|
||||||
@ -4840,7 +4838,6 @@ hoist-non-react-statics@^2.5.0:
|
|||||||
hoist-non-react-statics@^3.2.1:
|
hoist-non-react-statics@^3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz#c09c0555c84b38a7ede6912b61efddafd6e75e1e"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz#c09c0555c84b38a7ede6912b61efddafd6e75e1e"
|
||||||
integrity sha512-TFsu3TV3YLY+zFTZDrN8L2DTFanObwmBLpWvJs1qfUuEQ5bTAdFcwfx2T/bsCXfM9QHSLvjfP+nihEl0yvozxw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
react-is "^16.3.2"
|
react-is "^16.3.2"
|
||||||
|
|
||||||
@ -5034,7 +5031,6 @@ ignore@^3.3.5:
|
|||||||
immutable@^3.8.1:
|
immutable@^3.8.1:
|
||||||
version "3.8.2"
|
version "3.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
||||||
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
|
|
||||||
|
|
||||||
import-local@^1.0.0:
|
import-local@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
@ -5147,7 +5143,6 @@ invariant@^2.2.0, invariant@^2.2.4:
|
|||||||
invariant@^2.2.1, invariant@^2.2.2:
|
invariant@^2.2.1, invariant@^2.2.2:
|
||||||
version "2.2.2"
|
version "2.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
|
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
|
||||||
integrity sha1-nh9WrArNtr8wMwbzOL47IErmA2A=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.0.0"
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
@ -5903,7 +5898,6 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
|
|||||||
"js-tokens@^3.0.0 || ^4.0.0":
|
"js-tokens@^3.0.0 || ^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
|
||||||
|
|
||||||
js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1:
|
js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1:
|
||||||
version "3.12.0"
|
version "3.12.0"
|
||||||
@ -6363,7 +6357,6 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
|
|||||||
loose-envify@^1.4.0:
|
loose-envify@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens "^3.0.0 || ^4.0.0"
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
@ -8049,7 +8042,6 @@ react-qr-svg@^2.1.0:
|
|||||||
react-redux@^6.0.0:
|
react-redux@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.0.tgz#09e86eeed5febb98e9442458ad2970c8f1a173ef"
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.0.tgz#09e86eeed5febb98e9442458ad2970c8f1a173ef"
|
||||||
integrity sha512-EmbC3uLl60pw2VqSSkj6HpZ6jTk12RMrwXMBdYtM6niq0MdEaRq9KYCwpJflkOZj349BLGQm1MI/JO1W96kLWQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.2.0"
|
"@babel/runtime" "^7.2.0"
|
||||||
hoist-non-react-statics "^3.2.1"
|
hoist-non-react-statics "^3.2.1"
|
||||||
@ -8084,7 +8076,6 @@ react-router@^4.2.0:
|
|||||||
react-router@^4.3.1:
|
react-router@^4.3.1:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e"
|
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e"
|
||||||
integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
history "^4.7.2"
|
history "^4.7.2"
|
||||||
hoist-non-react-statics "^2.5.0"
|
hoist-non-react-statics "^2.5.0"
|
||||||
@ -8365,7 +8356,6 @@ regenerator-runtime@^0.11.0:
|
|||||||
regenerator-runtime@^0.12.0:
|
regenerator-runtime@^0.12.0:
|
||||||
version "0.12.1"
|
version "0.12.1"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||||
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
|
||||||
|
|
||||||
regenerator-transform@^0.10.0:
|
regenerator-transform@^0.10.0:
|
||||||
version "0.10.1"
|
version "0.10.1"
|
||||||
@ -8831,7 +8821,6 @@ scryptsy@^1.2.1:
|
|||||||
seamless-immutable@^7.1.3:
|
seamless-immutable@^7.1.3:
|
||||||
version "7.1.4"
|
version "7.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/seamless-immutable/-/seamless-immutable-7.1.4.tgz#6e9536def083ddc4dea0207d722e0e80d0f372f8"
|
resolved "https://registry.yarnpkg.com/seamless-immutable/-/seamless-immutable-7.1.4.tgz#6e9536def083ddc4dea0207d722e0e80d0f372f8"
|
||||||
integrity sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A==
|
|
||||||
|
|
||||||
secp256k1@^3.0.1:
|
secp256k1@^3.0.1:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
@ -9871,9 +9860,9 @@ tr46@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
trezor-connect@6.0.3-beta.5:
|
trezor-connect@6.0.3-beta.10:
|
||||||
version "6.0.3-beta.5"
|
version "6.0.3-beta.10"
|
||||||
resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.3-beta.5.tgz#5ceea84ee4c990fd4df27c0e0b4feacbce30f894"
|
resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.3-beta.10.tgz#a703b06946bb98912cbe719f2bb3393ee41f03b1"
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-runtime "^6.26.0"
|
babel-runtime "^6.26.0"
|
||||||
events "^1.1.1"
|
events "^1.1.1"
|
||||||
@ -10359,7 +10348,6 @@ warning@^3.0.0:
|
|||||||
warning@^4.0.1:
|
warning@^4.0.1:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607"
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607"
|
||||||
integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.0.0"
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user