MyCrypto/common/features/selectors.ts
Connor Bryan e761b9d1fb Refactor redux pattern from top-level to ducks (#1936)
* Add a new route for AddressBook

* Further templating of the AddressBook view

* Add initial functionality to handle a table of existing address labels

* Make the linter happy

* Adjust paths

* Factor out TableRow and add common functionality

* Add initial Redux boilerplate for addressBook | fix minor linting issues

* Swap out terminology and types

* Connect up to Redux

* Connect data for AddressBookTable to Redux

* Use temporary fields for addition

* Remove alignment and index column

* Stopping point

* Adjust the sizing of rows to be consistent

* Initial implementation of a dropdown for the address field

* Minor styling to dropdown

* Stopping point

* Apply a focus concept onto the factory

* Add keyboard controls for the address field dropdown

* Adjust label of address field when it matches an addressBook entry

* Properly handle attempting to blur a non-existent component

* Minor styling changes on dropdown box

* Standardize address casing, add accessibility to dropdown

* Create an addressLabel component

* Pass refs correctly and fix some typings

* Exact version

* Add module name mapping for shared/keycodes

* addressBook reducer tests

* Add functionality to DeterministicModal

* Minor changes / Add test for addressBook selectors

* Move out AddressBookTable to a component

* Typing, translation and restructuring

* More typing and translation fixes

* More linting fixes

* More type changes

* Variable name for dropdown background

* Fix TS type errors, lint errors, remove unused props

* Used a different selector and removed method: AddressBookTable

* Linter was mad

* Linter mad again :(

* Add a translation and adjust styling of AddressBookTable

* Move the onBlur to a class method

* Prevent the default behavior of up/down/enter for dropdown

* Let's do it this way instead

* Adjust the styling on DeterministicWalletModal labels

* Change `AddressBookTable` into a pseudo-table using section and div

* Use readable keys vs. keycodes

* Put the dropdown in InputFactory and position it correctly

* Sanitation of label adding and changing

* Prevent duplicate labels in AddressBook and Row

* Add a box shadow and use `invalid` class insted of custom

* Use emphasis vs strong for address in dropdown

* Display the label undernearth the input vs. changing it

* Isolate AccountAddress into its own component

* Introduce interactivity to AccountAddress

* Fully incorporate with Redux

* Validation for AccountAddress

* Add validation notifications for address field on AddressBookTable

* Minor formatting

* Adjust wrappage for optimal flexboxxing

* Make AddressBookTable responsive

* Show an invalid input in "real time" instead of only on submit

* Real time input validation for AddressBookTableRow

* Responsive-ize the To address dropdown

* Hide identicons instead at small enough screen sizes

* Fix repsonsiveness of dropdown further

* Fix responsiveness of table rows and inputs

* Truncate account info and switch identicons to the right for consistency

* Use classnames instead of targetting element directly for DWM

* Display a notice if the entered query doesnt match a label and isnt an addr

* Don't show an error on the To address if its a label entry

* Display an error under AddressBookTableRow in real time

* Display errors in real time for AddressBookTable temp inputs

* Add realtime validation to AccountAddress

* Ensure toChecksumAddress is used when entering labels to address manager

* Show errors even after blurring.

* Create a ducks/ implementation for addressBook

* Duck-ize notifications

* Duck-ize customTokens

* Duck-ize deterministicWallets

* Only show errors on address/label entry if they have been blurred

* On certain inputs, show an invalid input immediately

* spec files in same directory

* Rename top-level redux directory

* Duck-ize gas

* Add displayed errors for labels with 0x and labels containing ens

* Move ENS checking validation out

* Add a saga for addLabelForAddress

* Completely revamp the redux side of Address Manager and test it all

* Adjust components to use new redux addressBook

* Incorporate new redux into AddressBookTableRow and clean up for linter

* Make linter and tests happy

* Another reduxy overhaul

* Still fixing it

* More redux updates

* Finalize redux stuff.

* Incorporate new reduxy way into AddressBookTable & Row

* Incorporate redux changes into Account Address

* Small tests fix

* Add and fix some selector tests

* Addressing Will's comments

* Shortened visibility class for line length reasons.

* Incorporate ducks pattern on updates addressBook

* Fix typeerror

* Migrate messages to ducks

* For Henry

* Duckify onboardStatus

* Duckify paritySigner

* Duckify rates

* Duckify transactions

* Duckerize wallet

* Reduckerate config

* Adjust exports and tests of every duck so far

* Duckify ENS

* Duckerificate schedule

* Duckificate swap

* Actually use the new sagas;  fix a circular dependency problem.

* Duckify transaction (phew)

* Add basics to redux/ directory

* Remove non-ducked redux stuff

* First sweep of redux/ directory

* Combine redundant imports

* Fix more linting stuff.

* A few more type fixes

* Welp... now I know not to use index.

* Sweep components/

* Sweep through containers/

* Im really starting to get frustrated

* The dawn of a new age.

* Linter fixes.

* De-flatten config/ reducers

* Do my thang on config selectors

* Adjust all references to config

* Split up ens reducers

* Wrap up de-nesting ENS

* Big boy refactor

* Split transaction into its reducers

* Fix reducers in transaction/

* Stopping point

* Adjust references to transaction from components

* Fix references to selectors

* Nest broadcast actions

* Nest field actions

* Nest meta actions

* Nest network actions

* Nest sign actions

* Nest broadcast types

* Nested fields types

* Nest meta types

* Nested network types

* Nested sign types

* Implement transaction saga changes

* Huh? No prepush problems?

* Update snappshot

* Reintroduce deleted tests

* A few missing tests found

* Found three missing transaction tests

* Found more tests

* Found the rest of the tests, woohoo.

* Renamed TypeKeys in TRANSACTION

* Specify TRANSACTION_BROADCAST

* Pretty up these imports

* Specify TRANSACTION_FIELDS

* Specify TRANSACTION_META

* Specify TRANSACTION_NETWORK

* Specify TRANSACTION_SIGN

* Adjust imports and add translations

* Update config snapshot

* Post-merge

* Temporary fix for DW/Config sagas so Daniel can continue smoke testing

* Remove first circulat dependency

* Fix more circular dependencies

* Properly structure config indices

* Further restructure config

* Prepare for idea

* Target directly from within features/

* Remove that circular dependency -- woohoo

* Remove the circular dependency from Web3Wallet, temporarily comment some tests pending assistance

* Un-comment the component-in-redux phenomenon

* Move onLoad to the store file

* Adjust addressBook imports/exports

* Adjusted imports/exports for customTokens

* Adjust imports/exports of deterministicWallets

* Adjust imports/exports of ens

* Restructure imports/exports of gas

* Restructure imports/exports for message

* Adjust imports/exports of notifications

* Restructure onboardStatus imports/exports

* Restructure paritySigner imports/exports

* Restructure rates imports/exports

* Restructure schedule imports/exports

* Fix broadcastweb3handler test

* Restructure swap imports/exports/

* Restructure transactionS imports/exports

* Restructured wallet imports and exports

* Hoist all necessary selectors aside from config/**/* and transaction/**/*

* Hoist all top-level selectors from transaction

* [Fix] Estimate Gas on Value Field Change (#1942) @ skubakdj

* Implement right-click context menu (#1780) @ bryanwb

* No Private Keys Online (#1466) @ wbobeirne

* Fix Stuck Node on Metamask Logout (#1951) @ wbobeirne

* [Fix] Make ENS Value Consistent (#1956) @ skubakdj

* Auto token add (#1808) @ HenryNguyen5

* Electron Ledger + Trezor Support (#1836) @ wbobeirne

* Fix Context Menu Popup Parameters (#1957)

* Add RSK network w/ network agnostic refactors (#1939) @ wbobeirne

* Change displayed notification back in helpers.tsx

* Remove newline on shell files

* Re-add newlines

* Remove newling on .travis.yml

* Prettier two files

* Re-add index.scss import in OnboardModal

* Restructure transaction subdirectories

* Everything in transaction/ except for sagas

* Restructure transaction imports/exports

* Nest broadcast sagas

* Nest fields

* Nest meta

* Nest network

* Nest sign

* Use generic names for reduxy stuff in the same directory to save space

* Do everything every in the whole wide world
2018-06-17 20:53:00 -05:00

661 lines
22 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import erc20 from 'libs/erc20';
import BN from 'bn.js';
import EthTx from 'ethereumjs-tx';
import { bufferToHex } from 'ethereumjs-util';
import { Address, Wei, TokenValue, Nonce, getDecimalFromEtherUnit } from 'libs/units';
import {
EAC_SCHEDULING_CONFIG,
EAC_ADDRESSES,
calcEACEndowment,
getSchedulerAddress,
getScheduleData,
getValidateRequestParamsData
} from 'libs/scheduling';
import { makeTransaction, getTransactionFields, IHexStrTransaction } from 'libs/transaction';
import { stripHexPrefixAndLower } from 'libs/formatters';
import { SecureWalletName, WalletName, getAddressMessage, AddressMessage } from 'config';
import { Token } from 'types/network';
import { ICurrentTo, ICurrentValue, IGetTransaction } from './types';
import { AppState } from './reducers';
import { addressBookSelectors } from './addressBook';
import { walletTypes, walletSelectors } from './wallet';
import { ratesSelectors } from './rates';
import { customTokensSelectors } from './customTokens';
import { scheduleSelectors, scheduleHelpers } from './schedule';
import { transactionsSelectors } from './transactions';
import { SavedTransaction } from 'types/transactions';
import * as configMetaSelectors from './config/meta/selectors';
import * as configSelectors from './config/selectors';
import * as transactionSelectors from './transaction/selectors';
import * as transactionFieldsSelectors from './transaction/fields/selectors';
import * as transactionMetaSelectors from './transaction/meta/selectors';
import * as transactionSignTypes from './transaction/sign/types';
import * as transactionSignSelectors from './transaction/sign/selectors';
import { reduceToValues, isFullTx } from './helpers';
export const isAnyOfflineWithWeb3 = (state: AppState): boolean => {
const { isWeb3Wallet } = walletSelectors.getWalletType(state);
const offline = configMetaSelectors.getOffline(state);
return offline && isWeb3Wallet;
};
// TODO: Convert to reselect selector (Issue #884)
export function getDisabledWallets(state: AppState): any {
const network = configSelectors.getNetworkConfig(state);
const isOffline = configMetaSelectors.getOffline(state);
const disabledWallets: any = {
wallets: [],
reasons: {}
};
const addReason = (wallets: WalletName[], reason: string) => {
if (!wallets.length) {
return;
}
disabledWallets.wallets = disabledWallets.wallets.concat(wallets);
wallets.forEach(wallet => {
disabledWallets.reasons[wallet] = reason;
});
};
// Some wallets don't support some networks
addReason(
configSelectors.unSupportedWalletFormatsOnNetwork(state),
`This wallet doesnt support the ${network.name} network`
);
// Some wallets are unavailable offline
if (isOffline) {
addReason(
[SecureWalletName.WEB3, SecureWalletName.TREZOR],
'This wallet cannot be accessed offline'
);
}
// Some wallets are disabled on certain platforms
if (process.env.BUILD_ELECTRON) {
addReason([SecureWalletName.WEB3], 'This wallet is not supported in the MyCrypto app');
}
// Dedupe and sort for consistency
disabledWallets.wallets = disabledWallets.wallets
.filter((name: string, idx: number) => disabledWallets.wallets.indexOf(name) === idx)
.sort();
return disabledWallets;
}
export function getTokens(state: AppState): walletTypes.MergedToken[] {
const network = configSelectors.getStaticNetworkConfig(state);
const tokens: Token[] = network ? network.tokens : [];
return tokens.concat(
state.customTokens.map((token: Token) => {
const mergedToken = { ...token, custom: true };
return mergedToken;
})
) as walletTypes.MergedToken[];
}
export function getWalletConfigTokens(state: AppState): walletTypes.MergedToken[] {
const tokens = getTokens(state);
const config = walletSelectors.getWalletConfig(state);
if (!config || !config.tokens) {
return [];
}
return config.tokens
.map(symbol => tokens.find(t => t.symbol === symbol))
.filter(token => token) as walletTypes.MergedToken[];
}
export const getToken = (state: AppState, unit: string): walletTypes.MergedToken | undefined => {
const tokens = getTokens(state);
const token = tokens.find(t => t.symbol === unit);
return token;
};
export function getTokenBalances(
state: AppState,
nonZeroOnly: boolean = false
): walletTypes.TokenBalance[] {
const tokens = getTokens(state);
if (!tokens) {
return [];
}
const ret = tokens.map(t => ({
symbol: t.symbol,
balance: state.wallet.tokens[t.symbol]
? state.wallet.tokens[t.symbol].balance
: TokenValue('0'),
error: state.wallet.tokens[t.symbol] ? state.wallet.tokens[t.symbol].error : null,
custom: t.custom,
decimal: t.decimal
}));
return nonZeroOnly ? ret.filter(t => !t.balance.isZero()) : ret;
}
export const getTokenWithBalance = (state: AppState, unit: string): walletTypes.TokenBalance => {
const tokens = getTokenBalances(state, false);
const currentToken = tokens.filter(t => t.symbol === unit);
//TODO: getting the first index is kinda hacky
return currentToken[0];
};
export const getTokenBalance = (state: AppState, unit: string): TokenValue | null => {
const token = getTokenWithBalance(state, unit);
if (!token) {
return token;
}
return token.balance;
};
export function getShownTokenBalances(
state: AppState,
nonZeroOnly: boolean = false
): walletTypes.TokenBalance[] {
const tokenBalances = getTokenBalances(state, nonZeroOnly);
const walletConfig = walletSelectors.getWalletConfig(state);
let walletTokens: string[] = [];
if (walletConfig) {
if (walletConfig.tokens) {
walletTokens = walletConfig.tokens;
}
}
return tokenBalances.filter(t => walletTokens.includes(t.symbol));
}
const getUSDConversionRate = (state: AppState, unit: string) => {
const { isTestnet } = configSelectors.getNetworkConfig(state);
const { rates } = ratesSelectors.getRates(state);
if (isTestnet) {
return null;
}
const conversionRate = rates[unit];
if (!conversionRate) {
return null;
}
return conversionRate.USD;
};
export const getValueInUSD = (state: AppState, value: TokenValue | Wei) => {
const unit = getUnit(state);
const conversionRate = getUSDConversionRate(state, unit);
if (!conversionRate) {
return null;
}
const sendValueUSD = value.muln(conversionRate);
return sendValueUSD;
};
export const getTransactionFeeInUSD = (state: AppState, fee: Wei) => {
const { unit } = configSelectors.getNetworkConfig(state);
const conversionRate = getUSDConversionRate(state, unit);
if (!conversionRate) {
return null;
}
const feeValueUSD = fee.muln(conversionRate);
return feeValueUSD;
};
export interface AllUSDValues {
valueUSD: BN | null;
feeUSD: BN | null;
totalUSD: BN | null;
}
export const getAllUSDValuesFromSerializedTx = (state: AppState): AllUSDValues => {
const fields = getParamsFromSerializedTx(state);
if (!fields) {
return {
feeUSD: null,
valueUSD: null,
totalUSD: null
};
}
const { currentValue, fee } = fields;
const valueUSD = getValueInUSD(state, currentValue);
const feeUSD = getTransactionFeeInUSD(state, fee);
return {
feeUSD,
valueUSD,
totalUSD: feeUSD && valueUSD ? valueUSD.add(feeUSD) : null
};
};
export function getRecentNetworkTransactions(state: AppState): SavedTransaction[] {
const txs = transactionsSelectors.getRecentTransactions(state);
const network = configSelectors.getNetworkConfig(state);
return txs.filter(tx => tx.chainId === network.chainId);
}
export function getRecentWalletTransactions(state: AppState): SavedTransaction[] {
const networkTxs = getRecentNetworkTransactions(state);
const wallet = walletSelectors.getWalletInst(state);
if (wallet) {
const addr = wallet.getAddressString().toLowerCase();
return networkTxs.filter(tx => tx.from.toLowerCase() === addr);
} else {
return [];
}
}
export const getSchedulingTransaction = (state: AppState): IGetTransaction => {
const { isFullTransaction } = getTransaction(state);
const currentTo = getCurrentTo(state);
const currentValue = getCurrentValue(state);
const nonce = transactionFieldsSelectors.getNonce(state);
const gasPrice = transactionFieldsSelectors.getGasPrice(state);
const timeBounty = scheduleSelectors.getTimeBounty(state);
const scheduleGasPrice = scheduleSelectors.getScheduleGasPrice(state);
const scheduleGasLimit = scheduleSelectors.getScheduleGasLimit(state);
const scheduleType = scheduleSelectors.getScheduleType(state);
const endowment = calcEACEndowment(
scheduleGasLimit.value,
currentValue.value,
scheduleGasPrice.value,
timeBounty.value
);
let transactionData = null;
const transactionFullAndValid = isFullTransaction && isSchedulingTransactionValid(state);
if (transactionFullAndValid) {
const deposit = scheduleSelectors.getScheduleDeposit(state);
const scheduleTimestamp = scheduleSelectors.getScheduleTimestamp(state);
const windowSize = scheduleSelectors.getWindowSize(state);
const callData = transactionFieldsSelectors.getData(state);
const scheduleTimezone = scheduleSelectors.getScheduleTimezone(state);
const windowStart = scheduleSelectors.getWindowStart(state);
transactionData = getScheduleData(
currentTo.raw,
callData.raw,
scheduleGasLimit.value,
currentValue.value,
scheduleHelpers.windowSizeBlockToMin(windowSize.value, scheduleType.value),
scheduleHelpers.calculateWindowStart(
scheduleType.value,
scheduleTimestamp,
scheduleTimezone.value,
windowStart.value
),
scheduleGasPrice.value,
timeBounty.value,
deposit.value
);
}
const transactionOptions = {
to: getSchedulerAddress(scheduleType.value),
data: transactionData,
gasLimit: EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT,
gasPrice: gasPrice.value,
nonce: Nonce('0'),
value: endowment
};
if (nonce) {
transactionOptions.nonce = Nonce(nonce.raw);
}
const schedulingTransaction: EthTx = makeTransaction(transactionOptions);
return {
transaction: schedulingTransaction,
isFullTransaction: transactionFullAndValid
};
};
const isSchedulingTransactionValid = (state: AppState): boolean => {
const schedulingState = scheduleSelectors.getScheduleState(state);
const windowSizeValid = scheduleSelectors.isWindowSizeValid(state);
const windowStartValid = scheduleHelpers.isWindowStartValid(
schedulingState,
configMetaSelectors.getLatestBlock(state)
);
const scheduleTimestampValid = scheduleHelpers.isScheduleTimestampValid(schedulingState);
const scheduleGasPriceValid = scheduleSelectors.isValidScheduleGasPrice(state);
const scheduleGasLimitValid = scheduleSelectors.isValidScheduleGasLimit(state);
const depositValid = scheduleSelectors.isValidScheduleDeposit(state);
const timeBountyValid = scheduleSelectors.isValidCurrentTimeBounty(state);
// return true if all fields are valid
return (
// either windowStart or scheduleTimestamp is used for scheduling
(windowStartValid || scheduleTimestampValid) &&
windowSizeValid &&
scheduleGasPriceValid &&
scheduleGasLimitValid &&
depositValid &&
timeBountyValid
);
};
export interface IGetValidateScheduleParamsCallPayload {
to: string;
data: string;
}
export const getValidateScheduleParamsCallPayload = (
state: AppState
): IGetValidateScheduleParamsCallPayload | undefined => {
const wallet = walletSelectors.getWalletInst(state);
const currentTo = getCurrentTo(state);
const currentValue = getCurrentValue(state);
const timeBounty = scheduleSelectors.getTimeBounty(state);
const scheduleGasPrice = scheduleSelectors.getScheduleGasPrice(state);
const scheduleGasLimit = scheduleSelectors.getScheduleGasLimit(state);
const scheduleType = scheduleSelectors.getScheduleType(state);
const deposit = scheduleSelectors.getScheduleDeposit(state);
const scheduleTimestamp = scheduleSelectors.getScheduleTimestamp(state);
const windowSize = scheduleSelectors.getWindowSize(state);
const scheduleTimezone = scheduleSelectors.getScheduleTimezone(state);
const windowStart = scheduleSelectors.getWindowStart(state);
/*
* Checks if any of these values are null or invalid
* due to an user input.
*/
if (
!currentValue.value ||
!currentTo.value ||
!scheduleGasPrice.value ||
!wallet ||
!windowSize.value ||
// we need either windowStart or scheduleTimestamp for scheduling
!(windowStart.value || scheduleTimestamp.value)
) {
return;
}
const callGasLimit = scheduleGasLimit.value || EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_LIMIT_FALLBACK;
const endowment = calcEACEndowment(
callGasLimit,
currentValue.value,
scheduleGasPrice.value,
timeBounty.value
);
const fromAddress = wallet.getAddressString();
const data = getValidateRequestParamsData(
bufferToHex(currentTo.value),
callGasLimit,
currentValue.value,
scheduleHelpers.windowSizeBlockToMin(windowSize.value, scheduleType.value),
scheduleHelpers.calculateWindowStart(
scheduleType.value,
scheduleTimestamp,
scheduleTimezone.value,
windowStart.value
),
scheduleGasPrice.value,
timeBounty.value,
deposit.value || Wei('0'),
scheduleType.value === 'time',
endowment,
fromAddress
);
return {
to: EAC_ADDRESSES.KOVAN.requestFactory,
data
};
};
export const isValidCurrentWindowStart = (state: AppState) => {
const currentWindowStart = scheduleSelectors.getWindowStart(state);
if (!currentWindowStart.value) {
return false;
}
return currentWindowStart.value > parseInt(configMetaSelectors.getLatestBlock(state), 10);
};
export const isEtherTransaction = (state: AppState) => {
const unit = getUnit(state);
const etherUnit = configSelectors.isNetworkUnit(state, unit);
return etherUnit;
};
export const getValidGasCost = (state: AppState) => {
const gasCost = transactionSelectors.getGasCost(state);
const etherBalance = walletSelectors.getEtherBalance(state);
const isOffline = configMetaSelectors.getOffline(state);
if (isOffline || !etherBalance) {
return true;
}
return gasCost.lte(etherBalance);
};
export const getDecimalFromUnit = (state: AppState, unit: string) => {
if (configSelectors.isNetworkUnit(state, unit)) {
return getDecimalFromEtherUnit('ether');
} else {
const token = getToken(state, unit);
if (!token) {
throw Error(`Token ${unit} not found`);
}
return token.decimal;
}
};
export const getCurrentTo = (state: AppState): ICurrentTo =>
isEtherTransaction(state)
? transactionFieldsSelectors.getTo(state)
: transactionMetaSelectors.getTokenTo(state);
export const getCurrentValue = (state: AppState): ICurrentValue =>
isEtherTransaction(state)
? transactionFieldsSelectors.getValue(state)
: transactionMetaSelectors.getTokenValue(state);
export const isValidCurrentTo = (state: AppState) => {
const currentTo = getCurrentTo(state);
const dataExists = transactionSelectors.getDataExists(state);
if (isEtherTransaction(state)) {
// if data exists the address can be 0x
return !!currentTo.value || dataExists;
} else {
return !!currentTo.value;
}
};
export const isCurrentToLabelEntry = (state: AppState): boolean => {
const currentTo = getCurrentTo(state);
return !currentTo.raw.startsWith('0x');
};
export function getCurrentToAddressMessage(state: AppState): AddressMessage | undefined {
const to = getCurrentTo(state);
return getAddressMessage(to.raw);
}
export const getUnit = (state: AppState) => {
const serializedTransaction = getSerializedTransaction(state);
const contractInteraction = transactionMetaSelectors.isContractInteraction(state);
// attempt to get the to address from the transaction
if (serializedTransaction && !contractInteraction) {
const transactionInstance = new EthTx(serializedTransaction);
const { to } = transactionInstance;
if (to) {
// see if any tokens match
let networkTokens: null | Token[] = null;
const customTokens = customTokensSelectors.getCustomTokens(state);
const networkConfig = configSelectors.getNetworkConfig(state);
if (!networkConfig.isCustom) {
networkTokens = networkConfig.tokens;
}
const mergedTokens = networkTokens ? [...networkTokens, ...customTokens] : customTokens;
const toChecksumAddress = configSelectors.getChecksumAddressFn(state);
const stringTo = toChecksumAddress(stripHexPrefixAndLower(to.toString('hex')));
const result = mergedTokens.find(t => t.address === stringTo);
if (result) {
return result.symbol;
}
}
}
return transactionMetaSelectors.getMetaState(state).unit;
};
export const signaturePending = (state: AppState) => {
const { isHardwareWallet } = walletSelectors.getWalletType(state);
const { pending } = state.transaction.sign;
return { isHardwareWallet, isSignaturePending: pending };
};
export const getSerializedTransaction = (state: AppState) =>
walletSelectors.getWalletType(state).isWeb3Wallet
? transactionSignSelectors.getWeb3Tx(state)
: transactionSignSelectors.getSignedTx(state);
export const getParamsFromSerializedTx = (
state: AppState
): transactionSignTypes.SerializedTxParams => {
const tx = getSerializedTransaction(state);
const isEther = isEtherTransaction(state);
const decimal = transactionMetaSelectors.getDecimal(state);
if (!tx) {
throw Error('Serialized transaction not found');
}
const fields = getTransactionFields(makeTransaction(tx));
const { value, data, gasLimit, gasPrice, to } = fields;
const currentValue = isEther ? Wei(value) : TokenValue(erc20.transfer.decodeInput(data)._value);
const currentTo = isEther ? Address(to) : Address(erc20.transfer.decodeInput(data)._to);
const unit = getUnit(state);
const fee = Wei(gasLimit).mul(Wei(gasPrice));
const total = fee.add(Wei(value));
return { ...fields, currentValue, currentTo, fee, total, unit, decimal, isToken: !isEther };
};
export const getTransaction = (state: AppState): IGetTransaction => {
const currentTo = getCurrentTo(state);
const currentValue = getCurrentValue(state);
const transactionFields = transactionFieldsSelectors.getFields(state);
const unit = getUnit(state);
const reducedValues = reduceToValues(transactionFields);
const transaction: EthTx = makeTransaction(reducedValues);
const dataExists = transactionSelectors.getDataExists(state);
const validGasCost = getValidGasCost(state);
const isFullTransaction = isFullTx(
state,
transactionFields,
currentTo,
currentValue,
dataExists,
validGasCost,
unit
);
return { transaction, isFullTransaction };
};
export const nonStandardTransaction = (state: AppState): boolean => {
const etherTransaction = isEtherTransaction(state);
const { isFullTransaction } = getTransaction(state);
const dataExists = transactionSelectors.getDataExists(state);
return isFullTransaction && dataExists && etherTransaction;
};
export const serializedAndTransactionFieldsMatch = (state: AppState, isLocallySigned: boolean) => {
const serialzedTransaction = getSerializedTransaction(state);
const { transaction, isFullTransaction } = getTransaction(state);
if (!isFullTransaction || !serialzedTransaction) {
return false;
}
const t1 = getTransactionFields(transaction);
// inject chainId into t1 as it wont have it from the fields
const networkConfig = configSelectors.getNetworkConfig(state);
if (!networkConfig) {
return false;
}
const { chainId } = networkConfig;
t1.chainId = chainId;
const t2 = getTransactionFields(makeTransaction(serialzedTransaction));
const checkValidity = (tx: IHexStrTransaction) =>
Object.keys(tx).reduce(
(match, currField: keyof IHexStrTransaction) => match && t1[currField] === t2[currField],
true
);
//reduce both ways to make sure both are exact same
const transactionsMatch = checkValidity(t1) && checkValidity(t2);
// if its signed then verify the signature too
return transactionsMatch && isLocallySigned
? makeTransaction(serialzedTransaction).verifySignature()
: true;
};
export const getFrom = (state: AppState) => {
const serializedTransaction = getSerializedTransaction(state);
// attempt to get the from address from the transaction
if (serializedTransaction) {
const transactionInstance = new EthTx(serializedTransaction);
try {
const from = transactionInstance.from;
if (from) {
const toChecksumAddress = configSelectors.getChecksumAddressFn(state);
return toChecksumAddress(from.toString('hex'));
}
} catch (e) {
console.warn(e);
}
}
return transactionMetaSelectors.getMetaState(state).from;
};
export const getCurrentBalance = (state: AppState): Wei | TokenValue | null => {
const etherTransaction = isEtherTransaction(state);
if (etherTransaction) {
return walletSelectors.getEtherBalance(state);
} else {
const unit = getUnit(state);
return getTokenBalance(state, unit);
}
};
export function getSelectedTokenContractAddress(state: AppState): string {
const allTokens = configSelectors.getAllTokens(state);
const currentUnit = getUnit(state);
if (configSelectors.isNetworkUnit(state, currentUnit)) {
return '';
}
return allTokens.reduce((tokenAddr, tokenInfo) => {
if (tokenAddr && tokenAddr.length) {
return tokenAddr;
}
if (tokenInfo.symbol === currentUnit) {
return tokenInfo.address;
}
return tokenAddr;
}, '');
}
export function getCurrentToLabel(state: AppState) {
const addresses = addressBookSelectors.getAddressLabels(state);
const currentTo = getCurrentTo(state);
return addresses[currentTo.raw.toLowerCase()] || null;
}