@ -17,7 +17,7 @@ import BigNumber from 'bignumber.js';
import { initialState } from '../reducers/SendFormReducer' ;
import { initialState } from '../reducers/SendFormReducer' ;
import { findAccount } from '../reducers/AccountsReducer' ;
import { findAccount } from '../reducers/AccountsReducer' ;
import { findToken } from '../reducers/TokensReducer' ;
import { findToken } from '../reducers/TokensReducer' ;
import { findDevice } from '../reducers/ DevicesReducer ';
import { findDevice } from '../reducers/ utils ';
import type {
import type {
Dispatch ,
Dispatch ,
@ -34,6 +34,7 @@ import type { Config, Coin } from '../reducers/LocalStorageReducer';
import type { Token } from '../reducers/TokensReducer' ;
import type { Token } from '../reducers/TokensReducer' ;
import type { State , FeeLevel } from '../reducers/SendFormReducer' ;
import type { State , FeeLevel } from '../reducers/SendFormReducer' ;
import type { Account } from '../reducers/AccountsReducer' ;
import type { Account } from '../reducers/AccountsReducer' ;
import type { Props } from '../components/wallet/account/send' ;
export type SendTxAction = {
export type SendTxAction = {
type : typeof SEND . TX _COMPLETE ,
type : typeof SEND . TX _COMPLETE ,
@ -56,6 +57,9 @@ export type SendFormAction = SendTxAction | {
errors : { [ k : string ] : string } ,
errors : { [ k : string ] : string } ,
warnings : { [ k : string ] : string } ,
warnings : { [ k : string ] : string } ,
infos : { [ k : string ] : string }
infos : { [ k : string ] : string }
} | {
type : typeof SEND . ADDRESS _VALIDATION ,
state : State
} | {
} | {
type : typeof SEND . ADDRESS _CHANGE ,
type : typeof SEND . ADDRESS _CHANGE ,
state : State
state : State
@ -111,21 +115,28 @@ export type SendFormAction = SendTxAction | {
//const numberRegExp = new RegExp('^([0-9]{0,10}\\.)?[0-9]{1,18}$');
//const numberRegExp = new RegExp('^([0-9]{0,10}\\.)?[0-9]{1,18}$');
const numberRegExp : RegExp = new RegExp ( '^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$' ) ;
const numberRegExp : RegExp = new RegExp ( '^(0|0\\.([0-9]+)?|[1-9][0-9]*\\.?([0-9]+)?|\\.[0-9]+)$' ) ;
const calculateFee = ( gasPrice : string , gasLimit : string ) : string => {
export const calculateFee = ( gasPrice : string , gasLimit : string ) : string => {
try {
return EthereumjsUnits . convert ( new BigNumber ( gasPrice ) . times ( gasLimit ) , 'gwei' , 'ether' ) ;
return EthereumjsUnits . convert ( new BigNumber ( gasPrice ) . times ( gasLimit ) , 'gwei' , 'ether' ) ;
} catch ( error ) {
return '0' ;
}
}
}
const calculateTotal = ( amount : string , gasPrice : string , gasLimit : string ) : string => {
export const calculateTotal = ( amount : string , gasPrice : string , gasLimit : string ) : string => {
try {
try {
return new BigNumber ( amount ) . plus ( calculateFee ( gasPrice , gasLimit ) ) . toString ( ) ;
// return new BigNumber(amount).plus( calculateFee(gasPrice, gasLimit) ).toString();
const fee = calculateFee ( gasPrice , gasLimit ) ;
return new BigNumber ( amount ) . plus ( calculateFee ( gasPrice , gasLimit ) ) . toFixed ( 16 ) ;
} catch ( error ) {
} catch ( error ) {
return '0' ;
return '0' ;
}
}
}
}
const calculateMaxAmount = ( balance : string , gasPrice : string , gasLimit : string ) : string => {
export const calculateMaxAmount = ( balance : string , gasPrice : string , gasLimit : string ) : string => {
try {
try {
const fee = EthereumjsUnits . convert ( new BigNumber ( gasPrice ) . times ( gasLimit ) , 'gwei' , 'ether' ) ;
// TODO - minus pendings
const fee = calculateFee ( gasPrice , gasLimit ) ;
const b = new BigNumber ( balance ) ;
const b = new BigNumber ( balance ) ;
const max = b . minus ( fee ) ;
const max = b . minus ( fee ) ;
if ( max . lessThan ( 0 ) ) return '0' ;
if ( max . lessThan ( 0 ) ) return '0' ;
@ -136,25 +147,56 @@ const calculateMaxAmount = (balance: string, gasPrice: string, gasLimit: string)
}
}
const setAmountAndTotal = ( state : State ) : State => {
export const calculate = ( prevProps : Props , props : Props ) => {
const {
account ,
} = props . selectedAccount ;
if ( ! account ) return ;
// if (state.setMax) {
console . warn ( "CALCULATE!" , props )
// const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
// if (!account) return;
// if (isToken) {
const prevState = prevProps . sendForm ;
// const token: ?Token = findToken(getState().tokens, account.address, state.selectedCurrency, account.deviceState);
const state = props . sendForm ;
// if (!token) return;
const isToken : boolean = state . currency !== state . networkSymbol ;
// state.amount = token.balance;
// } else {
// state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
// }
// }
// state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
return state ;
// account balance
// token balance
// gasLimit, gasPrice changed
// const shouldRecalculateAmount =
// (prevProps.selectedAccount.account !== account)
// || (prevProps.)
if ( state . setMax ) {
const pendingAmount = props . pending . reduce ( ( value , p ) => {
//if (p.tx.amount)
console . warn ( "PENDING AMOUNT!" , p , value ) ;
} , 0 ) ;
if ( isToken ) {
const token : ? Token = findToken ( props . tokens , account . address , state . currency , account . deviceState ) ;
if ( token ) {
state . amount = token . balance ;
}
} else {
state . amount = calculateMaxAmount ( account . balance , state . gasPrice , state . gasLimit ) ;
}
}
// amount changed
// fee changed
state . total = calculateTotal ( isToken ? '0' : state . amount , state . gasPrice , state . gasLimit ) ;
if ( state . selectedFeeLevel . value === 'Custom' ) {
state . selectedFeeLevel . label = ` ${ calculateFee ( state . gasPrice , state . gasLimit ) } ${ state . networkSymbol } ` ;
state . selectedFeeLevel . gasPrice = state . gasPrice ;
}
}
}
export const getFeeLevels = ( symbol : string , gasPrice : BigNumber | string , gasLimit : string , selected ? : FeeLevel ) : Array < FeeLevel > => {
export const getFeeLevels = ( symbol : string , gasPrice : BigNumber | string , gasLimit : string , selected ? : FeeLevel ) : Array < FeeLevel > => {
const price : BigNumber = typeof gasPrice === 'string' ? new BigNumber ( gasPrice ) : gasPrice
const price : BigNumber = typeof gasPrice === 'string' ? new BigNumber ( gasPrice ) : gasPrice
const quarter : BigNumber = price . dividedBy ( 4 ) ;
const quarter : BigNumber = price . dividedBy ( 4 ) ;
@ -194,45 +236,44 @@ export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLi
// initialize component
// initialize component
export const init = ( ) : ThunkAction => {
export const init = ( stateFromStorage : ? State ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const {
if ( ! accountState ) return ;
account ,
network ,
web3
} = getState ( ) . selectedAccount ;
const { location } = getState ( ) . router ;
if ( ! account || ! network || ! web3 ) return ;
const urlParams : RouterLocationState = location . state ;
const selected : ? TrezorDevice = getState ( ) . wallet . selectedDevice ;
if ( stateFromStorage ) {
if ( ! selected ) return ;
dispatch ( {
type : SEND . INIT ,
const web3instance : ? Web3Instance = getState ( ) . web3 . find ( w3 => w3 . network === urlParams . network ) ;
state : stateFromStorage
if ( ! web3instance ) return ;
} ) ;
return ;
const account = getState ( ) . accounts . find ( a => a . deviceState === accountState . deviceState && a . index === accountState . index && a . network === accountState . network ) ;
}
if ( ! account ) return ;
// TODO: check if there are some unfinished tx in localStorage
// TODO: check if there are some unfinished tx in localStorage
const coin : Coin = accountState . coin ;
const gasPrice : BigNumber = new BigNumber ( EthereumjsUnits . convert ( web3 . gasPrice , 'wei' , 'gwei' ) ) || new BigNumber ( network . defaultGasPrice ) ;
const gasLimit : string = network . defaultGasLimit . toString ( ) ;
const gasPrice : BigNumber = new BigNumber ( EthereumjsUnits . convert ( web3instance . gasPrice , 'wei' , 'gwei' ) ) || new BigNumber ( coin . defaultGasPrice ) ;
const feeLevels : Array < FeeLevel > = getFeeLevels ( network . symbol , gasPrice , gasLimit ) ;
const gasLimit : string = coin . defaultGasLimit . toString ( ) ;
const feeLevels : Array < FeeLevel > = getFeeLevels ( coin . symbol , gasPrice , gasLimit ) ;
// TODO: get nonce
// TODO: get nonce
// TODO: LOAD DATA FROM SESSION STORAGE
const state : State = {
const state : State = {
... initialState ,
... initialState ,
network : coin . network ,
network Name: network . network ,
coinSymbol: coin . symbol ,
networkSymbol: network . symbol ,
selectedCurrency: coin . symbol ,
currency: network . symbol ,
feeLevels ,
feeLevels ,
selectedFeeLevel : feeLevels . find ( f => f . value === 'Normal' ) ,
selectedFeeLevel : feeLevels . find ( f => f . value === 'Normal' ) ,
recommendedGasPrice : gasPrice . toString ( ) ,
recommendedGasPrice : gasPrice . toString ( ) ,
gasLimit ,
gasLimit ,
gasPrice : gasPrice . toString ( ) ,
gasPrice : gasPrice . toString ( ) ,
nonce : account . nonce . toString ( ) , // TODO!!!
} ;
} ;
dispatch ( {
dispatch ( {
@ -242,25 +283,76 @@ export const init = (): ThunkAction => {
}
}
}
}
export const dispose = ( ) : Action => {
return {
type : SEND . DISPOSE
}
}
export const toggleAdvanced = ( address : string ) : Action => {
export const toggleAdvanced = ( address : string ) : Action => {
return {
return {
type : SEND . TOGGLE _ADVANCED
type : SEND . TOGGLE _ADVANCED
}
}
}
}
export const validation = ( ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const addressValidation = ( ) : ThunkAction => {
if ( ! accountState ) return ;
return ( dispatch : Dispatch , getState : GetState ) : void => {
const {
account ,
network ,
tokens ,
} = getState ( ) . selectedAccount ;
if ( ! account || ! network ) return ;
const state : State = getState ( ) . sendForm ;
const state : State = getState ( ) . sendForm ;
const infos = { ... state . infos } ;
const warnings = { ... state . warnings } ;
if ( state . untouched || ! state . touched . address ) return ;
const savedAccounts = getState ( ) . accounts . filter ( a => a . address . toLowerCase ( ) === state . address . toLowerCase ( ) ) ;
if ( savedAccounts . length > 0 ) {
// check if found account belongs to this network
// corner-case: when same derivation path is used on different networks
const currentNetworkAccount = savedAccounts . find ( a => a . network === network . network ) ;
if ( currentNetworkAccount ) {
const device : ? TrezorDevice = findDevice ( getState ( ) . devices , currentNetworkAccount . deviceID , currentNetworkAccount . deviceState ) ;
if ( device ) {
infos . address = ` ${ device . instanceLabel } Account # ${ ( currentNetworkAccount . index + 1 ) } ` ;
}
} else {
const otherNetworkAccount = savedAccounts [ 0 ] ;
const device : ? TrezorDevice = findDevice ( getState ( ) . devices , otherNetworkAccount . deviceID , otherNetworkAccount . deviceState ) ;
const coins = getState ( ) . localStorage . config . coins ;
const otherNetwork : ? Coin = coins . find ( c => c . network === otherNetworkAccount . network )
if ( device && otherNetwork ) {
warnings . address = ` Looks like it's ${ device . instanceLabel } Account # ${ ( otherNetworkAccount . index + 1 ) } address of ${ otherNetwork . name } network ` ;
}
}
} else {
delete warnings . address ;
delete infos . address ;
}
dispatch ( {
type : SEND . ADDRESS _VALIDATION ,
state : {
... state ,
infos ,
warnings
}
} )
}
}
export const validation = ( props : Props ) : void => {
const {
account ,
network ,
tokens ,
} = props . selectedAccount ;
if ( ! account || ! network ) return ;
const state : State = props . sendForm ;
const errors : { [ k : string ] : string } = { } ;
const errors : { [ k : string ] : string } = { } ;
const warnings : { [ k : string ] : string } = { } ;
const warnings : { [ k : string ] : string } = { } ;
@ -269,26 +361,17 @@ export const validation = (): ThunkAction => {
if ( state . untouched ) return ;
if ( state . untouched ) return ;
// valid address
// valid address
if ( state . touched . address ) {
if ( state . touched . address ) {
const accounts = getState ( ) . accounts ;
const savedAccounts = accounts . filter ( a => a . address . toLowerCase ( ) === state . address . toLowerCase ( ) ) ;
if ( state . address . length < 1 ) {
if ( state . address . length < 1 ) {
errors . address = 'Address is not set' ;
errors . address = 'Address is not set' ;
} else if ( ! EthereumjsUtil . isValidAddress ( state . address ) ) {
} else if ( ! EthereumjsUtil . isValidAddress ( state . address ) ) {
errors . address = 'Address is not valid' ;
errors . address = 'Address is not valid' ;
} else if ( savedAccounts . length > 0 ) {
// check if founded account belongs to this network
// corner-case: when same derivation path is used on different networks
const currentNetworkAccount = savedAccounts . find ( a => a . network === accountState . network ) ;
if ( currentNetworkAccount ) {
const device : ? TrezorDevice = findDevice ( getState ( ) . devices , currentNetworkAccount . deviceID , currentNetworkAccount . deviceState ) ;
if ( ! device ) return ;
infos . address = ` ${ device . instanceLabel } Account # ${ ( currentNetworkAccount . index + 1 ) } ` ;
} else {
} else {
const device : ? TrezorDevice = findDevice ( getState ( ) . devices , savedAccounts [ 0 ] . deviceID , savedAccounts [ 0 ] . deviceState ) ;
// address warning or info are set in addressValidation ThunkAction
if ( ! device ) return ;
// do not override this
warnings . address = ` Looks like it's ${ device . instanceLabel } Account # ${ ( savedAccounts [ 0 ] . index + 1 ) } address of ${ savedAccounts [ 0 ] . network . toUpperCase ( ) } network ` ;
if ( state . warnings . address ) {
warnings . address = state . warnings . address ;
} else if ( state . infos . address ) {
infos . address = state . infos . address ;
}
}
}
}
}
}
@ -303,13 +386,10 @@ export const validation = (): ThunkAction => {
errors . amount = 'Amount is not a number' ;
errors . amount = 'Amount is not a number' ;
} else {
} else {
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account ) return ;
let decimalRegExp : RegExp ;
let decimalRegExp : RegExp ;
if ( state . sele ctedC urrency !== state . coin Symbol) {
if ( state . currency !== state . networkSymbol ) {
const token = findToken ( getState( ) . tokens, account . address , state . sele ctedC urrency, account . deviceState ) ;
const token = findToken ( tokens, account . address , state . currency, account . deviceState ) ;
if ( token ) {
if ( token ) {
if ( parseInt ( token . decimals ) > 0 ) {
if ( parseInt ( token . decimals ) > 0 ) {
//decimalRegExp = new RegExp('^(0|0\\.([0-9]{0,' + token.decimals + '})?|[1-9]+\\.?([0-9]{0,' + token.decimals + '})?|\\.[0-9]{1,' + token.decimals + '})$');
//decimalRegExp = new RegExp('^(0|0\\.([0-9]{0,' + token.decimals + '})?|[1-9]+\\.?([0-9]{0,' + token.decimals + '})?|\\.[0-9]{1,' + token.decimals + '})$');
@ -322,7 +402,7 @@ export const validation = (): ThunkAction => {
if ( ! state . amount . match ( decimalRegExp ) ) {
if ( ! state . amount . match ( decimalRegExp ) ) {
errors . amount = ` Maximum ${ token . decimals } decimals allowed ` ;
errors . amount = ` Maximum ${ token . decimals } decimals allowed ` ;
} else if ( new BigNumber ( state . total ) . greaterThan ( account . balance ) ) {
} else if ( new BigNumber ( state . total ) . greaterThan ( account . balance ) ) {
errors . amount = ` Not enough ${ state . coin Symbol } to cover transaction fee ` ;
errors . amount = ` Not enough ${ state . network Symbol } to cover transaction fee ` ;
} else if ( new BigNumber ( state . amount ) . greaterThan ( token . balance ) ) {
} else if ( new BigNumber ( state . amount ) . greaterThan ( token . balance ) ) {
errors . amount = 'Not enough funds' ;
errors . amount = 'Not enough funds' ;
} else if ( new BigNumber ( state . amount ) . lessThanOrEqualTo ( '0' ) ) {
} else if ( new BigNumber ( state . amount ) . lessThanOrEqualTo ( '0' ) ) {
@ -351,7 +431,7 @@ export const validation = (): ThunkAction => {
const gl : BigNumber = new BigNumber ( state . gasLimit ) ;
const gl : BigNumber = new BigNumber ( state . gasLimit ) ;
if ( gl . lessThan ( 1 ) ) {
if ( gl . lessThan ( 1 ) ) {
errors . gasLimit = 'Gas limit is too low' ;
errors . gasLimit = 'Gas limit is too low' ;
} else if ( gl . lessThan ( state . sele ctedC urrency !== state . coinSymbol ? accountState . coin . defaultGasLimitTokens : accountState . coin . defaultGasLimit ) ) {
} else if ( gl . lessThan ( state . currency !== state . networkSymbol ? network . defaultGasLimitTokens : network . defaultGasLimit ) ) {
warnings . gasLimit = 'Gas limit is below recommended' ;
warnings . gasLimit = 'Gas limit is below recommended' ;
}
}
}
}
@ -381,9 +461,6 @@ export const validation = (): ThunkAction => {
} else if ( ! state . nonce . match ( re ) ) {
} else if ( ! state . nonce . match ( re ) ) {
errors . nonce = 'Nonce is not a valid number' ;
errors . nonce = 'Nonce is not a valid number' ;
} else {
} else {
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account ) return ;
const n : BigNumber = new BigNumber ( state . nonce ) ;
const n : BigNumber = new BigNumber ( state . nonce ) ;
if ( n . lessThan ( account . nonce ) ) {
if ( n . lessThan ( account . nonce ) ) {
warnings . nonce = 'Nonce is lower than recommended' ;
warnings . nonce = 'Nonce is lower than recommended' ;
@ -403,108 +480,75 @@ export const validation = (): ThunkAction => {
// valid nonce?
// valid nonce?
dispatch ( {
state . errors = errors ;
type : SEND . VALIDATION ,
state . warnings = warnings ;
errors ,
state . infos = infos ;
warnings ,
infos
} ) ;
}
}
}
export const onAddressChange = ( address : string ) : ThunkAction => {
export const onAddressChange = ( address : string ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const state : State = getState ( ) . sendForm ;
const currentState : State = getState ( ) . sendForm ;
const touched = { ... state . touched } ;
const touched = { ... currentState . touched } ;
touched . address = true ;
touched . address = true ;
const state : State = {
dispatch ( {
... currentState ,
type : SEND . ADDRESS _CHANGE ,
state : {
... state ,
untouched : false ,
untouched : false ,
touched ,
touched ,
address
address
} ;
}
dispatch ( {
type : SEND . ADDRESS _CHANGE ,
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
dispatch ( addressValidation ( ) ) ;
}
}
}
}
export const onAmountChange = ( amount : string ) : ThunkAction => {
export const onAmountChange = ( amount : string ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const state = getState ( ) . sendForm ;
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const touched = { ... state . touched } ;
const currentState : State = getState ( ) . sendForm ;
const isToken : boolean = currentState . selectedCurrency !== currentState . coinSymbol ;
const touched = { ... currentState . touched } ;
touched . amount = true ;
touched . amount = true ;
const total : string = calculateTotal ( isToken ? '0' : amount , currentState . gasPrice , currentState . gasLimit ) ;
const state : State = {
dispatch ( {
... currentState ,
type : SEND . AMOUNT _CHANGE ,
state : {
... state ,
untouched : false ,
untouched : false ,
touched ,
touched ,
setMax : false ,
setMax : false ,
amount ,
amount ,
total
}
} ;
dispatch ( {
type : SEND . AMOUNT _CHANGE ,
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
export const onCurrencyChange = ( currency : { value : string , label : string } ) : ThunkAction => {
export const onCurrencyChange = ( currency : { value : string , label : string } ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const {
if ( ! accountState ) return ;
account ,
const currentState : State = getState ( ) . sendForm ;
network
const isToken : boolean = currency . value !== currentState . coinSymbol ;
} = getState ( ) . selectedAccount ;
if ( ! account || ! network ) return ;
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
const currentState : State = getState ( ) . sendForm ;
if ( ! account ) {
const isToken : boolean = currency . value !== currentState . networkSymbol ;
// account not found
const gasLimit : string = isToken ? network . defaultGasLimitTokens . toString ( ) : network . defaultGasLimit . toString ( ) ;
return ;
}
const coin : Coin = accountState . coin ;
let gasLimit : string = '' ;
let amount : string = currentState . amount ;
let total : string ;
if ( isToken ) {
gasLimit = coin . defaultGasLimitTokens . toString ( ) ;
if ( currentState . setMax ) {
const token : ? Token = findToken ( getState ( ) . tokens , account . address , currency . value , accountState . deviceState ) ;
if ( ! token ) return ;
amount = token . balance ;
}
total = calculateTotal ( '0' , currentState . gasPrice , currentState . gasLimit ) ;
} else {
gasLimit = coin . defaultGasLimit . toString ( ) ;
if ( currentState . setMax ) {
amount = calculateMaxAmount ( account . balance , currentState . gasPrice , currentState . gasLimit ) ;
}
total = calculateTotal ( amount , currentState . gasPrice , currentState . gasLimit ) ;
}
const feeLevels : Array < FeeLevel > = getFeeLevels ( currentState. coinSymbol , currentState . g asPrice, gasLimit , currentState . selectedFeeLevel ) ;
const feeLevels : Array < FeeLevel > = getFeeLevels ( network . symbol , currentState . recommendedGasPrice , gasLimit , currentState . selectedFeeLevel ) ;
const selectedFeeLevel : ? FeeLevel = feeLevels . find ( f => f . value === currentState . selectedFeeLevel . value ) ;
const selectedFeeLevel : ? FeeLevel = feeLevels . find ( f => f . value === currentState . selectedFeeLevel . value ) ;
if ( ! selectedFeeLevel ) return ;
if ( ! selectedFeeLevel ) return ;
const state : State = {
const state : State = {
... currentState ,
... currentState ,
sele ctedC urrency: currency . value ,
currency : currency . value ,
amount ,
// amount,
total ,
// total,
feeLevels ,
feeLevels ,
selectedFeeLevel ,
selectedFeeLevel ,
gasLimit ,
gasLimit ,
@ -514,66 +558,36 @@ export const onCurrencyChange = (currency: { value: string, label: string }): Th
type : SEND . CURRENCY _CHANGE ,
type : SEND . CURRENCY _CHANGE ,
state
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
export const onSetMax = ( ) : ThunkAction => {
export const onSetMax = ( ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const state = getState ( ) . sendForm ;
if ( ! accountState ) return ;
const touched = { ... state . touched } ;
const currentState : State = getState ( ) . sendForm ;
const isToken : boolean = currentState . selectedCurrency !== currentState . coinSymbol ;
const touched = { ... currentState . touched } ;
touched . amount = true ;
touched . amount = true ;
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account ) {
// account not found
return ;
}
let amount : string = currentState . amount ;
let total : string = currentState . total ;
if ( ! currentState . setMax ) {
if ( isToken ) {
const token : ? Token = findToken ( getState ( ) . tokens , account . address , currentState . selectedCurrency , accountState . deviceState ) ;
if ( ! token ) return ;
amount = token . balance ;
total = calculateTotal ( '0' , currentState . gasPrice , currentState . gasLimit ) ;
} else {
amount = calculateMaxAmount ( account . balance , currentState . gasPrice , currentState . gasLimit ) ;
total = calculateTotal ( amount , currentState . gasPrice , currentState . gasLimit ) ;
}
}
const state : State = {
... currentState ,
untouched : false ,
touched ,
setMax : ! currentState . setMax ,
amount ,
total
} ;
dispatch ( {
dispatch ( {
type : SEND . SET _MAX ,
type : SEND . SET _MAX ,
state
state : {
... state ,
untouched : false ,
touched ,
setMax : ! state . setMax ,
}
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
export const onFeeLevelChange = ( feeLevel : FeeLevel ) : ThunkAction => {
export const onFeeLevelChange = ( feeLevel : FeeLevel ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const {
if ( ! accountState ) return ;
network
const currentState : State = getState ( ) . sendForm ;
} = getState ( ) . selectedAccount ;
const isToken : boolean = currentState . selectedCurrency !== currentState . coinSymbol ;
if ( ! network ) return ;
const coin : Coin = accountState . coin ;
const currentState : State = getState ( ) . sendForm ;
const isToken : boolean = currentState . currency !== currentState . networkSymbol ;
const state : State = {
const state : State = {
... currentState ,
... currentState ,
@ -584,38 +598,23 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
if ( feeLevel . value === 'Custom' ) {
if ( feeLevel . value === 'Custom' ) {
state . advanced = true ;
state . advanced = true ;
feeLevel . gasPrice = state . gasPrice ;
feeLevel . gasPrice = state . gasPrice ;
feeLevel . label = ` ${ calculateFee ( state . gasPrice , state . gasLimit ) } ${ state . coin Symbol } ` ;
feeLevel . label = ` ${ calculateFee ( state . gasPrice , state . gasLimit ) } ${ state . network Symbol } ` ;
} else {
} else {
const customLevel : ? FeeLevel = state . feeLevels . find ( f => f . value === 'Custom' ) ;
const customLevel : ? FeeLevel = state . feeLevels . find ( f => f . value === 'Custom' ) ;
if ( customLevel )
if ( customLevel )
customLevel . label = '' ;
customLevel . label = '' ;
state . gasPrice = feeLevel . gasPrice ;
state . gasPrice = feeLevel . gasPrice ;
if ( isToken ) {
if ( isToken ) {
state . gasLimit = coin . defaultGasLimitTokens . toString ( )
state . gasLimit = network . defaultGasLimitTokens . toString ( )
} else {
state . gasLimit = state . data . length > 0 ? state . gasLimit : coin . defaultGasLimit . toString ( ) ;
}
}
if ( currentState . setMax ) {
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account ) return ;
if ( isToken ) {
const token : ? Token = findToken ( getState ( ) . tokens , account . address , currentState . selectedCurrency , accountState . deviceState ) ;
if ( ! token ) return ;
state . amount = token . balance ;
} else {
} else {
state . amount = calculateMaxAmount ( account . balance , state . gasPrice , state . gasLimit ) ;
state . gasLimit = state . data . length > 0 ? state . gasLimit : network . defaultGasLimit . toString ( ) ;
}
}
}
}
state . total = calculateTotal ( isToken ? '0' : state . amount , state . gasPrice , state . gasLimit ) ;
dispatch ( {
dispatch ( {
type : SEND . FEE _LEVEL _CHANGE ,
type : SEND . FEE _LEVEL _CHANGE ,
state
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
@ -624,12 +623,25 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
export const updateFeeLevels = ( ) : ThunkAction => {
export const updateFeeLevels = ( ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const {
if ( ! accountState ) return ;
account ,
network
} = getState ( ) . selectedAccount ;
if ( ! account || ! network ) return ;
const currentState : State = getState ( ) . sendForm ;
const currentState : State = getState ( ) . sendForm ;
const isToken : boolean = currentState . selectedCurrency !== currentState . coinSymbol ;
const isToken : boolean = currentState . currency !== currentState . networkSymbol ;
let gasLimit : string = isToken ? network . defaultGasLimitTokens . toString ( ) : network . defaultGasLimit . toString ( ) ;
// override custom settings
if ( currentState . selectedFeeLevel . value === 'Custom' ) {
// update only gasPrice
currentState . selectedFeeLevel . gasPrice = currentState . recommendedGasPrice ;
// leave gas limit as it was
gasLimit = currentState . gasLimit ;
}
const feeLevels : Array < FeeLevel > = getFeeLevels ( currentState . coinSymbol , currentState . recommendedGasPrice , currentState . gasLimit , currentState . selectedFeeLevel ) ;
const feeLevels : Array < FeeLevel > = getFeeLevels ( network. s ymbol, currentState . recommendedGasPrice , gasLimit , currentState . selectedFeeLevel ) ;
const selectedFeeLevel : ? FeeLevel = feeLevels . find ( f => f . value === currentState . selectedFeeLevel . value ) ;
const selectedFeeLevel : ? FeeLevel = feeLevels . find ( f => f . value === currentState . selectedFeeLevel . value ) ;
if ( ! selectedFeeLevel ) return ;
if ( ! selectedFeeLevel ) return ;
@ -641,34 +653,18 @@ export const updateFeeLevels = (): ThunkAction => {
gasPriceNeedsUpdate : false ,
gasPriceNeedsUpdate : false ,
} ;
} ;
if ( currentState . setMax ) {
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account ) return ;
if ( isToken ) {
const token : ? Token = findToken ( getState ( ) . tokens , account . address , state . selectedCurrency , accountState . deviceState ) ;
if ( ! token ) return ;
const tokenBalance : string = token . balance ;
state . amount = tokenBalance ;
} else {
state . amount = calculateMaxAmount ( account . balance , state . gasPrice , state . gasLimit ) ;
}
}
state . total = calculateTotal ( isToken ? '0' : state . amount , state . gasPrice , state . gasLimit ) ;
dispatch ( {
dispatch ( {
type : SEND . UPDATE _FEE _LEVELS ,
type : SEND . UPDATE _FEE _LEVELS ,
state
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
export const onGasPriceChange = ( gasPrice : string ) : ThunkAction => {
export const onGasPriceChange = ( gasPrice : string ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
if ( ! accountState ) return ;
const currentState : State = getState ( ) . sendForm ;
const currentState : State = getState ( ) . sendForm ;
const isToken : boolean = currentState . sele ctedC urrency !== currentState . coin Symbol;
const isToken : boolean = currentState . currency !== currentState . networkSymbol ;
const touched = { ... currentState . touched } ;
const touched = { ... currentState . touched } ;
touched . gasPrice = true ;
touched . gasPrice = true ;
@ -680,99 +676,46 @@ export const onGasPriceChange = (gasPrice: string): ThunkAction => {
gasPrice : gasPrice ,
gasPrice : gasPrice ,
} ;
} ;
if ( gasPrice. match ( numberRegExp ) && state . gasLimit . match ( numberRegExp ) ) {
if ( currentState. selectedFeeLevel . value !== 'Custom' ) {
const customLevel = currentState . feeLevels . find ( f => f . value === 'Custom' ) ;
const customLevel = currentState . feeLevels . find ( f => f . value === 'Custom' ) ;
if ( ! customLevel ) return ;
if ( ! customLevel ) return ;
customLevel . gasPrice = gasPrice ;
customLevel . label = ` ${ calculateFee ( gasPrice , state . gasLimit ) } ${ state . coinSymbol } ` ;
state . selectedFeeLevel = customLevel ;
state . selectedFeeLevel = customLevel ;
if ( currentState . setMax ) {
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account ) return ;
if ( isToken ) {
const token : ? Token = findToken ( getState ( ) . tokens , account . address , state . selectedCurrency , accountState . deviceState ) ;
if ( ! token ) return ;
const tokenBalance : string = token . balance ;
state . amount = tokenBalance ;
} else {
state . amount = calculateMaxAmount ( account . balance , state . gasPrice , state . gasLimit ) ;
}
}
}
state . total = calculateTotal ( isToken ? '0' : state . amount , state . gasPrice , state . gasLimit ) ;
} else {
// state.gasPrice = currentState.gasPrice;
}
dispatch ( {
dispatch ( {
type : SEND . GAS _PRICE _CHANGE ,
type : SEND . GAS _PRICE _CHANGE ,
state
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
export const onGasLimitChange = ( gasLimit : string , updateFeeLevels : boolean = false ) : ThunkAction => {
export const onGasLimitChange = ( gasLimit : string , updateFeeLevels : boolean = false ) : ThunkAction => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
return ( dispatch : Dispatch , getState : GetState ) : void => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
if ( ! accountState ) return ;
const currentState : State = getState ( ) . sendForm ;
const currentState : State = getState ( ) . sendForm ;
const isToken : boolean = currentState . sele ctedC urrency !== currentState . coin Symbol;
const isToken : boolean = currentState . currency !== currentState . network Symbol;
const touched = { ... currentState . touched } ;
const touched = { ... currentState . touched } ;
touched . gasLimit = true ;
touched . gasLimit = true ;
const state : State = {
const state : State = {
... currentState ,
... currentState ,
calculatingGasLimit : false ,
untouched : false ,
untouched : false ,
touched ,
touched ,
gasLimit ,
gasLimit ,
} ;
} ;
if ( gasLimit . match ( numberRegExp ) && state . gasPrice . match ( numberRegExp ) ) {
if ( currentState . selectedFeeLevel . value !== 'Custom' ) {
const customLevel = currentState . feeLevels . find ( f => f . value === 'Custom' ) ;
if ( updateFeeLevels ) {
const feeLevels : Array < FeeLevel > = getFeeLevels ( state . coinSymbol , state . gasPrice , gasLimit , state . selectedFeeLevel ) ;
const selectedFeeLevel : ? FeeLevel = feeLevels . find ( f => f . value === state . selectedFeeLevel . value ) ;
if ( ! selectedFeeLevel ) return ;
state . selectedFeeLevel = selectedFeeLevel ;
state . feeLevels = feeLevels ;
} else {
const customLevel : ? FeeLevel = state . feeLevels . find ( f => f . value === 'Custom' ) ;
if ( ! customLevel ) return ;
if ( ! customLevel ) return ;
customLevel . label = ` ${ calculateFee ( currentState . gasPrice , gasLimit ) } ${ state . coinSymbol } ` ;
state . selectedFeeLevel = customLevel ;
state . selectedFeeLevel = customLevel ;
}
}
if ( state . setMax ) {
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account ) return ;
if ( isToken ) {
const token : ? Token = findToken ( getState ( ) . tokens , account . address , state . selectedCurrency , accountState . deviceState ) ;
if ( ! token ) return ;
const tokenBalance : string = token . balance ;
state . amount = tokenBalance ;
} else {
state . amount = calculateMaxAmount ( account . balance , state . gasPrice , state . gasLimit ) ;
}
}
}
state . total = calculateTotal ( isToken ? '0' : state . amount , state . gasPrice , state . gasLimit ) ;
dispatch ( {
dispatch ( {
type : SEND . GAS _LIMIT _CHANGE ,
type : SEND . GAS _LIMIT _CHANGE ,
state
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
@ -793,7 +736,6 @@ export const onNonceChange = (nonce: string): AsyncAction => {
type : SEND . NONCE _CHANGE ,
type : SEND . NONCE _CHANGE ,
state
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
}
}
}
}
@ -805,16 +747,22 @@ export const onDataChange = (data: string): AsyncAction => {
const state : State = {
const state : State = {
... currentState ,
... currentState ,
calculatingGasLimit : true ,
untouched : false ,
untouched : false ,
touched ,
touched ,
data ,
data ,
} ;
} ;
if ( currentState . selectedFeeLevel . value !== 'Custom' ) {
const customLevel = currentState . feeLevels . find ( f => f . value === 'Custom' ) ;
if ( ! customLevel ) return ;
state . selectedFeeLevel = customLevel ;
}
dispatch ( {
dispatch ( {
type : SEND . DATA _CHANGE ,
type : SEND . DATA _CHANGE ,
state
state
} ) ;
} ) ;
dispatch ( validation ( ) ) ;
dispatch ( estimateGasPrice ( ) ) ;
dispatch ( estimateGasPrice ( ) ) ;
}
}
@ -824,58 +772,71 @@ export const onDataChange = (data: string): AsyncAction => {
const estimateGasPrice = ( ) : AsyncAction => {
const estimateGasPrice = ( ) : AsyncAction => {
return async ( dispatch : Dispatch , getState : GetState ) : Promise < void > => {
return async ( dispatch : Dispatch , getState : GetState ) : Promise < void > => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const {
if ( ! accountState ) return ;
web3 ,
const web3instance : ? Web3Instance = getState( ) . web3 . filter ( w3 => w3 . network === accountState . network ) [ 0 ] ;
network
if ( ! web3instance ) return ;
} = getState ( ) . selectedAccount ;
const web3 = web3instance . web3 ;
if ( ! web3 || ! network ) return ;
const currentState : State = getState ( ) . sendForm ;
const requestedData = currentState . data ;
const w3 = web3 . web3 ;
if ( currentState . errors . data ) {
const state : State = getState ( ) . sendForm ;
const requestedData = state . data ;
const re = /^[0-9A-Fa-f]+$/g ;
if ( ! re . test ( requestedData ) ) {
// to stop calculating
dispatch ( onGasLimitChange ( state . gasLimit ) ) ;
return ;
return ;
}
}
const data : string = '0x' + ( currentState . data . length % 2 === 0 ? currentState . data : '0' + currentState . data ) ;
if ( state . data . length < 1 ) {
const gasLimit = await estimateGas ( web3instance . web3 , {
// set default
dispatch ( onGasLimitChange ( network . defaultGasLimit . toString ( ) ) ) ;
return ;
}
// TODO: allow data starting with 0x ...
const data : string = '0x' + ( state . data . length % 2 === 0 ? state . data : '0' + state . data ) ;
const gasLimit = await estimateGas ( w3 , {
to : '0x0000000000000000000000000000000000000000' ,
to : '0x0000000000000000000000000000000000000000' ,
data ,
data ,
value : web3 . toHex ( web3 . toWei ( currentState . amount , 'ether' ) ) ,
value : w 3. toHex ( w3 . toWei ( s tate. amount , 'ether' ) ) ,
gasPrice : web3 . toHex ( EthereumjsUnits . convert ( currentState . gasPrice , 'gwei' , 'wei' ) ) ,
gasPrice : w 3. toHex ( EthereumjsUnits . convert ( s tate. gasPrice , 'gwei' , 'wei' ) ) ,
} ) ;
} ) ;
if ( getState ( ) . sendForm . data === requestedData ) {
if ( getState ( ) . sendForm . data === requestedData ) {
dispatch ( onGasLimitChange ( gasLimit . toString ( ) , true ) ) ;
dispatch ( onGasLimitChange ( gasLimit . toString ( ) ) ) ;
}
}
}
}
}
}
export const onSend = ( ) : AsyncAction => {
export const onSend = ( ) : AsyncAction => {
//return onSendERC20();
return async ( dispatch : Dispatch , getState : GetState ) : Promise < void > => {
return async ( dispatch : Dispatch , getState : GetState ) : Promise < void > => {
const accountState : ? AccountState = getState ( ) . selectedAccount ;
const {
if ( ! accountState ) return ;
account ,
network ,
web3
} = getState ( ) . selectedAccount ;
if ( ! account || ! web3 || ! network ) return ;
const currentState : State = getState ( ) . sendForm ;
const currentState : State = getState ( ) . sendForm ;
const web3instance : ? Web3Instance = getState ( ) . web3 . filter ( w3 => w3 . network === accountState . network ) [ 0 ] ;
const account : ? Account = findAccount ( getState ( ) . accounts , accountState . index , accountState . deviceState , accountState . network ) ;
if ( ! account || ! web3instance ) return ;
const isToken : boolean = currentState . sele ctedC urrency !== currentState . coin Symbol;
const isToken : boolean = currentState . currency !== currentState . networkSymbol ;
const w eb 3 = web3 instance . web3 ;
const w3 = web3 . web3 ;
const address _n = account . addressPath ;
const address _n = account . addressPath ;
let data : string = '0x' + currentState . data ;
let data : string = '0x' + currentState . data ;
let txAmount : string = w eb 3. toHex ( w eb 3. toWei ( currentState . amount , 'ether' ) ) ;
let txAmount : string = w 3. toHex ( w 3. toWei ( currentState . amount , 'ether' ) ) ;
let txAddress : string = currentState . address ;
let txAddress : string = currentState . address ;
if ( isToken ) {
if ( isToken ) {
const token : ? Token = findToken ( getState ( ) . tokens , account . address , currentState . sele ctedC urrency, account State . deviceState ) ;
const token : ? Token = findToken ( getState ( ) . tokens , account . address , currentState . currency, account . deviceState ) ;
if ( ! token ) return ;
if ( ! token ) return ;
const contract = web3 instance . erc20 . at ( token . address ) ;
const contract = web3 . erc20 . at ( token . address ) ;
const amountValue : string = new BigNumber ( currentState . amount ) . times ( Math . pow ( 10 , token . decimals ) ) . toString ( ) ;
const amountValue : string = new BigNumber ( currentState . amount ) . times ( Math . pow ( 10 , token . decimals ) ) . toString ( ) ;
data = contract . transfer . getData ( currentState . address , amountValue , {
data = contract . transfer . getData ( currentState . address , amountValue , {
@ -893,11 +854,10 @@ export const onSend = (): AsyncAction => {
to : txAddress ,
to : txAddress ,
value : txAmount ,
value : txAmount ,
data ,
data ,
//chainId: 3 // ropsten
chainId : web3 . chainId ,
chainId : web3instance . chainId ,
nonce : w3 . toHex ( account . nonce ) ,
nonce : web3 . toHex ( account . nonce ) ,
gasLimit : w3 . toHex ( currentState . gasLimit ) ,
gasLimit : web3 . toHex ( currentState . gasLimit ) ,
gasPrice : w3 . toHex ( EthereumjsUnits . convert ( currentState . gasPrice , 'gwei' , 'wei' ) ) ,
gasPrice : web3 . toHex ( EthereumjsUnits . convert ( currentState . gasPrice , 'gwei' , 'wei' ) ) ,
r : '' ,
r : '' ,
s : '' ,
s : '' ,
v : ''
v : ''
@ -940,20 +900,19 @@ export const onSend = (): AsyncAction => {
txData . r = '0x' + signedTransaction . payload . r ;
txData . r = '0x' + signedTransaction . payload . r ;
txData . s = '0x' + signedTransaction . payload . s ;
txData . s = '0x' + signedTransaction . payload . s ;
txData . v = w eb 3. toHex ( signedTransaction . payload . v ) ;
txData . v = w 3. toHex ( signedTransaction . payload . v ) ;
try {
try {
const tx = new EthereumjsTx ( txData ) ;
const tx = new EthereumjsTx ( txData ) ;
const serializedTx = '0x' + tx . serialize ( ) . toString ( 'hex' ) ;
const serializedTx = '0x' + tx . serialize ( ) . toString ( 'hex' ) ;
const txid : string = await pushTx ( web3 , serializedTx ) ;
const txid : string = await pushTx ( w3 , serializedTx ) ;
const coin : Coin = accountState . coin ;
dispatch ( {
dispatch ( {
type : SEND . TX _COMPLETE ,
type : SEND . TX _COMPLETE ,
account : account ,
account : account ,
selectedCurrency : currentState . sele ctedC urrency,
selectedCurrency : currentState . currency,
amount : currentState . amount ,
amount : currentState . amount ,
txid ,
txid ,
txData ,
txData ,
@ -964,7 +923,7 @@ export const onSend = (): AsyncAction => {
payload : {
payload : {
type : 'success' ,
type : 'success' ,
title : 'Transaction success' ,
title : 'Transaction success' ,
message : ` <a href=" ${ coin . explorer . tx } ${ txid } " class="green" target="_blank" rel="noreferrer noopener">See transaction detail</a> ` ,
message : ` <a href=" ${ network . explorer . tx } ${ txid } " class="green" target="_blank" rel="noreferrer noopener">See transaction detail</a> ` ,
cancelable : true ,
cancelable : true ,
actions : [ ]
actions : [ ]
}
}
@ -987,9 +946,6 @@ export const onSend = (): AsyncAction => {
}
}
export default {
export default {
init ,
dispose ,
toggleAdvanced ,
toggleAdvanced ,
onAddressChange ,
onAddressChange ,
onAmountChange ,
onAmountChange ,