commit
7397335f27
|
@ -24,10 +24,10 @@ workflows:
|
|||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-build
|
||||
- test-e2e-firefox:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-build
|
||||
# - test-e2e-firefox:
|
||||
# requires:
|
||||
# - prep-deps-npm
|
||||
# - prep-build
|
||||
- test-unit:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
|
@ -52,7 +52,7 @@ workflows:
|
|||
- test-lint
|
||||
- test-unit
|
||||
- test-e2e-chrome
|
||||
- test-e2e-firefox
|
||||
# - test-e2e-firefox
|
||||
- test-integration-mascara-chrome
|
||||
- test-integration-mascara-firefox
|
||||
- test-integration-flat-chrome
|
||||
|
@ -179,7 +179,7 @@ jobs:
|
|||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Test
|
||||
command: sudo npm install -g npm@6 && npm audit
|
||||
command: sudo npm install -g npm@6.4.1 && npm audit
|
||||
|
||||
test-e2e-chrome:
|
||||
docker:
|
||||
|
|
|
@ -11,6 +11,7 @@ development/publish-release.js
|
|||
|
||||
app/scripts/lib/extension-instance.js
|
||||
app/scripts/chromereload.js
|
||||
app/vendor/**
|
||||
|
||||
ui/lib/blockies.js
|
||||
|
||||
|
|
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -2,6 +2,33 @@
|
|||
|
||||
## Current Master
|
||||
|
||||
## 4.11.0 Fri Feb 08 2019
|
||||
|
||||
### Features
|
||||
|
||||
- [#262](https://github.com/poanetwork/nifty-wallet/pull/262): (Feature) Add native support of Görli testnet
|
||||
- [#254](https://github.com/poanetwork/nifty-wallet/pull/254): (Feature) HitBTC exchange for core POA network
|
||||
- [#251](https://github.com/poanetwork/nifty-wallet/pull/251): (Feature) Delegate Proxy contract type (EIP-897)
|
||||
- [#252](https://github.com/poanetwork/nifty-wallet/pull/252): (Feature) Simultaneous support of Trezor and Ledger HD wallets
|
||||
- [#250](https://github.com/poanetwork/nifty-wallet/pull/250): (Feature) Support of multiple accounts from Trezor HD wallet for single session
|
||||
- [#237](https://github.com/poanetwork/nifty-wallet/pull/237): (Feature) Multiple Ledger accounts for one session
|
||||
- [#249](https://github.com/poanetwork/nifty-wallet/pull/249): (Feature) Textarea instead of input for array type outputs in contract calls
|
||||
- [#247](https://github.com/poanetwork/nifty-wallet/pull/247): (Update) Change exchange rate API endpoint
|
||||
|
||||
### Fixes
|
||||
|
||||
- [#261](https://github.com/poanetwork/nifty-wallet/pull/261): (Fix) Clear timeout on componentWillUnmount in connect hardware screen
|
||||
- [#260](https://github.com/poanetwork/nifty-wallet/pull/260): (Fix) Remove unit && integration tests for unused components
|
||||
- [#258](https://github.com/poanetwork/nifty-wallet/pull/258): (Fix) ENS validation fix for Send transaction screen
|
||||
- [#257](https://github.com/poanetwork/nifty-wallet/pull/257): (Fix) Replace poa.infura.io with core.poa.network in e2e
|
||||
- [#248](https://github.com/poanetwork/nifty-wallet/pull/248): (Fix) validation for calling data from contract: Default `0x` value for _bytes_ field type should be set only for input fields
|
||||
|
||||
### Refactoring
|
||||
|
||||
- [#259](https://github.com/poanetwork/nifty-wallet/pull/259): (Refactoring) Refactor copy component
|
||||
- [#256](https://github.com/poanetwork/nifty-wallet/pull/256): (Refactoring) Send-token component
|
||||
- [#253](https://github.com/poanetwork/nifty-wallet/pull/253): (Refactoring) Refactor network props enums
|
||||
|
||||
## 4.10.1 Sat Dec 29 2018
|
||||
|
||||
- [#219](https://github.com/poanetwork/nifty-wallet/pull/219): (Feature) Multiple output fields support for contract call
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "4.10.1",
|
||||
"version": "4.11.0",
|
||||
"manifest_version": 2,
|
||||
"author": "POA Network",
|
||||
"description": "__MSG_appDescription__",
|
||||
|
@ -52,6 +52,14 @@
|
|||
],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"*://connect.trezor.io/*/popup.html"
|
||||
],
|
||||
"js": [
|
||||
"vendor/trezor/content-script.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
const ObservableStore = require('obs-store')
|
||||
const extend = require('xtend')
|
||||
|
||||
/**
|
||||
* @typedef {Object} CachedBalancesOptions
|
||||
* @property {Object} accountTracker An {@code AccountTracker} reference
|
||||
* @property {Function} getNetwork A function to get the current network
|
||||
* @property {Object} initState The initial controller state
|
||||
*/
|
||||
|
||||
/**
|
||||
* Background controller responsible for maintaining
|
||||
* a cache of account balances in local storage
|
||||
*/
|
||||
class CachedBalancesController {
|
||||
/**
|
||||
* Creates a new controller instance
|
||||
*
|
||||
* @param {CachedBalancesOptions} [opts] Controller configuration parameters
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
const { accountTracker, getNetwork } = opts
|
||||
|
||||
this.accountTracker = accountTracker
|
||||
this.getNetwork = getNetwork
|
||||
|
||||
const initState = extend({
|
||||
cachedBalances: {},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this._registerUpdates()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cachedBalances property for the current network. Cached balances will be updated to those in the passed accounts
|
||||
* if balances in the passed accounts are truthy.
|
||||
*
|
||||
* @param {Object} obj The the recently updated accounts object for the current network
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateCachedBalances ({ accounts }) {
|
||||
const network = await this.getNetwork()
|
||||
const balancesToCache = await this._generateBalancesToCache(accounts, network)
|
||||
this.store.updateState({
|
||||
cachedBalances: balancesToCache,
|
||||
})
|
||||
}
|
||||
|
||||
_generateBalancesToCache (newAccounts, currentNetwork) {
|
||||
const { cachedBalances } = this.store.getState()
|
||||
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] }
|
||||
|
||||
Object.keys(newAccounts).forEach(accountID => {
|
||||
const account = newAccounts[accountID]
|
||||
|
||||
if (account.balance) {
|
||||
currentNetworkBalancesToCache[accountID] = account.balance
|
||||
}
|
||||
})
|
||||
const balancesToCache = {
|
||||
...cachedBalances,
|
||||
[currentNetwork]: currentNetworkBalancesToCache,
|
||||
}
|
||||
|
||||
return balancesToCache
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up listeners and subscriptions which should trigger an update of cached balances. These updates will
|
||||
* happen when the current account changes. Which happens on block updates, as well as on network and account
|
||||
* selections.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
*/
|
||||
_registerUpdates () {
|
||||
const update = this.updateCachedBalances.bind(this)
|
||||
this.accountTracker.store.subscribe(update)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CachedBalancesController
|
|
@ -131,11 +131,11 @@ class CurrencyController {
|
|||
currentCoin = this.getCurrentCoin()
|
||||
let conversionRate, conversionDate
|
||||
if (currentCoin === 'poa') {
|
||||
const coinId = await this.getCoinMarketCapId(currentCoin)
|
||||
const response = await fetch(`https://api.coinmarketcap.com/v2/ticker/${coinId}/?convert=${currentCurrency.toLowerCase()}`)
|
||||
const apiLink = `https://min-api.cryptocompare.com/data/price?fsym=${currentCoin.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}`
|
||||
const response = await fetch(apiLink)
|
||||
const parsedResponse = await response.json()
|
||||
conversionRate = Number(parsedResponse.data.quotes[currentCurrency.toUpperCase()].price)
|
||||
conversionDate = Number(parsedResponse.metadata.timestamp)
|
||||
conversionRate = Number(parsedResponse[currentCurrency.toUpperCase()])
|
||||
conversionDate = parseInt((new Date()).getTime() / 1000)
|
||||
} else {
|
||||
const response = await fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
|
||||
const parsedResponse = await response.json()
|
||||
|
@ -166,16 +166,6 @@ class CurrencyController {
|
|||
}, POLLING_INTERVAL)
|
||||
}
|
||||
|
||||
async getCoinMarketCapId (symbol) {
|
||||
const response = await fetch(`https://api.coinmarketcap.com/v2/listings/`)
|
||||
const parsedResponse = await response.json()
|
||||
const results = parsedResponse.data.filter(coin => coin.symbol === symbol.toUpperCase())
|
||||
if (!results.length) {
|
||||
throw new Error(`Nifty Wallet - Failed to fetch ${symbol} from coinmarketcap listings`)
|
||||
}
|
||||
return results[0].id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CurrencyController
|
||||
|
|
|
@ -1,64 +1,72 @@
|
|||
const POA = 'poa'
|
||||
const DAI = 'dai'
|
||||
const POA_SOKOL = 'sokol'
|
||||
const MAINNET = 'mainnet'
|
||||
const ROPSTEN = 'ropsten'
|
||||
const RINKEBY = 'rinkeby'
|
||||
const KOVAN = 'kovan'
|
||||
const MAINNET = 'mainnet'
|
||||
const POA_SOKOL = 'sokol'
|
||||
const POA = 'poa'
|
||||
const DAI = 'dai'
|
||||
const GOERLI_TESTNET = 'goerli_testnet'
|
||||
const LOCALHOST = 'localhost'
|
||||
|
||||
const MAINNET_CODE = 1
|
||||
const ROPSTEN_CODE = 3
|
||||
const RINKEYBY_CODE = 4
|
||||
const KOVAN_CODE = 42
|
||||
const POA_SOKOL_CODE = 77
|
||||
const POA_CODE = 99
|
||||
const DAI_CODE = 100
|
||||
const POA_SOKOL_CODE = 77
|
||||
const MAINNET_CODE = 1
|
||||
const ROPSTEN_CODE = 3
|
||||
const RINKEBY_CODE = 4
|
||||
const KOVAN_CODE = 42
|
||||
const GOERLI_TESTNET_CODE = 5
|
||||
|
||||
const POA_DISPLAY_NAME = 'POA Network'
|
||||
const DAI_DISPLAY_NAME = 'xDai Chain'
|
||||
const POA_SOKOL_DISPLAY_NAME = 'Sokol'
|
||||
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
||||
const ROPSTEN_DISPLAY_NAME = 'Ropsten'
|
||||
const RINKEBY_DISPLAY_NAME = 'Rinkeby'
|
||||
const KOVAN_DISPLAY_NAME = 'Kovan'
|
||||
const POA_SOKOL_DISPLAY_NAME = 'Sokol'
|
||||
const POA_DISPLAY_NAME = 'POA Network'
|
||||
const DAI_DISPLAY_NAME = 'xDai Chain'
|
||||
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
||||
const GOERLI_TESTNET_DISPLAY_NAME = 'Görli Testnet'
|
||||
|
||||
const DROPDOWN_POA_DISPLAY_NAME = POA_DISPLAY_NAME
|
||||
const DROPDOWN_DAI_DISPLAY_NAME = DAI_DISPLAY_NAME
|
||||
const DROPDOWN_POA_SOKOL_DISPLAY_NAME = 'Sokol Network'
|
||||
const DROPDOWN_MAINNET_DISPLAY_NAME = 'Main Network'
|
||||
const DROPDOWN_ROPSTEN_DISPLAY_NAME = 'Ropsten Test Net'
|
||||
const DROPDOWN_RINKEBY_DISPLAY_NAME = 'Rinkeby Test Net'
|
||||
const DROPDOWN_KOVAN_DISPLAY_NAME = 'Kovan Test Net'
|
||||
const DROPDOWN_POA_SOKOL_DISPLAY_NAME = 'Sokol Network'
|
||||
const DROPDOWN_POA_DISPLAY_NAME = POA_DISPLAY_NAME
|
||||
const DROPDOWN_DAI_DISPLAY_NAME = DAI_DISPLAY_NAME
|
||||
const DROPDOWN_MAINNET_DISPLAY_NAME = 'Main Network'
|
||||
const DROPDOWN_GOERLI_TESTNET_DISPLAY_NAME = 'Görli Test Net'
|
||||
|
||||
module.exports = {
|
||||
POA,
|
||||
DAI,
|
||||
POA_SOKOL,
|
||||
MAINNET,
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
POA_SOKOL,
|
||||
POA,
|
||||
DAI,
|
||||
GOERLI_TESTNET,
|
||||
LOCALHOST,
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEYBY_CODE,
|
||||
KOVAN_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEBY_CODE,
|
||||
KOVAN_CODE,
|
||||
GOERLI_TESTNET_CODE,
|
||||
POA_DISPLAY_NAME,
|
||||
DAI_DISPLAY_NAME,
|
||||
POA_SOKOL_DISPLAY_NAME,
|
||||
MAINNET_DISPLAY_NAME,
|
||||
ROPSTEN_DISPLAY_NAME,
|
||||
RINKEBY_DISPLAY_NAME,
|
||||
KOVAN_DISPLAY_NAME,
|
||||
MAINNET_DISPLAY_NAME,
|
||||
POA_SOKOL_DISPLAY_NAME,
|
||||
POA_DISPLAY_NAME,
|
||||
DAI_DISPLAY_NAME,
|
||||
GOERLI_TESTNET_DISPLAY_NAME,
|
||||
DROPDOWN_POA_DISPLAY_NAME,
|
||||
DROPDOWN_DAI_DISPLAY_NAME,
|
||||
DROPDOWN_POA_SOKOL_DISPLAY_NAME,
|
||||
DROPDOWN_MAINNET_DISPLAY_NAME,
|
||||
DROPDOWN_ROPSTEN_DISPLAY_NAME,
|
||||
DROPDOWN_RINKEBY_DISPLAY_NAME,
|
||||
DROPDOWN_KOVAN_DISPLAY_NAME,
|
||||
DROPDOWN_POA_SOKOL_DISPLAY_NAME,
|
||||
DROPDOWN_POA_DISPLAY_NAME,
|
||||
DROPDOWN_DAI_DISPLAY_NAME,
|
||||
DROPDOWN_MAINNET_DISPLAY_NAME,
|
||||
DROPDOWN_GOERLI_TESTNET_DISPLAY_NAME,
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ const createInfuraClient = require('./createInfuraClient')
|
|||
const createJsonRpcClient = require('./createJsonRpcClient')
|
||||
const createLocalhostClient = require('./createLocalhostClient')
|
||||
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
|
||||
const ethNetProps = require('eth-net-props')
|
||||
|
||||
const {
|
||||
ROPSTEN,
|
||||
|
@ -21,10 +22,12 @@ const {
|
|||
POA_SOKOL,
|
||||
POA,
|
||||
DAI,
|
||||
GOERLI_TESTNET,
|
||||
POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
GOERLI_TESTNET_CODE,
|
||||
} = require('./enums')
|
||||
const POA_RPC_URL = 'https://core.poa.network'
|
||||
const DAI_RPC_URL = 'https://dai.poa.network'
|
||||
const SOKOL_RPC_URL = 'https://sokol.poa.network'
|
||||
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
||||
|
||||
const env = process.env.METAMASK_ENV
|
||||
|
@ -109,7 +112,13 @@ module.exports = class NetworkController extends EventEmitter {
|
|||
|
||||
async setProviderType (type) {
|
||||
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
|
||||
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST || type === POA_SOKOL || type === POA || type === DAI, `NetworkController - Unknown rpc type "${type}"`)
|
||||
assert(INFURA_PROVIDER_TYPES.includes(type) ||
|
||||
type === LOCALHOST ||
|
||||
type === POA_SOKOL ||
|
||||
type === POA ||
|
||||
type === DAI ||
|
||||
type === GOERLI_TESTNET
|
||||
, `NetworkController - Unknown rpc type "${type}"`)
|
||||
const providerConfig = { type }
|
||||
this.providerConfig = providerConfig
|
||||
}
|
||||
|
@ -145,11 +154,13 @@ module.exports = class NetworkController extends EventEmitter {
|
|||
this._configureInfuraProvider(opts)
|
||||
// other type-based rpc endpoints
|
||||
} else if (type === POA) {
|
||||
this._configureStandardProvider({ rpcUrl: POA_RPC_URL })
|
||||
this._configureStandardProvider({ rpcUrl: ethNetProps.RPCEndpoints(POA_CODE)[0] })
|
||||
} else if (type === DAI) {
|
||||
this._configureStandardProvider({ rpcUrl: DAI_RPC_URL })
|
||||
this._configureStandardProvider({ rpcUrl: ethNetProps.RPCEndpoints(DAI_CODE)[0] })
|
||||
} else if (type === POA_SOKOL) {
|
||||
this._configureStandardProvider({ rpcUrl: SOKOL_RPC_URL })
|
||||
this._configureStandardProvider({ rpcUrl: ethNetProps.RPCEndpoints(POA_SOKOL_CODE)[0] })
|
||||
} else if (type === GOERLI_TESTNET) {
|
||||
this._configureStandardProvider({ rpcUrl: ethNetProps.RPCEndpoints(GOERLI_TESTNET_CODE)[0] })
|
||||
} else if (type === LOCALHOST) {
|
||||
this._configureLocalhostProvider()
|
||||
// url-based rpc endpoints
|
||||
|
|
|
@ -1,39 +1,123 @@
|
|||
const {
|
||||
POA_SOKOL,
|
||||
POA,
|
||||
DAI,
|
||||
POA_SOKOL,
|
||||
MAINNET,
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
POA_SOKOL_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEYBY_CODE,
|
||||
KOVAN_CODE,
|
||||
GOERLI_TESTNET,
|
||||
POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEBY_CODE,
|
||||
KOVAN_CODE,
|
||||
GOERLI_TESTNET_CODE,
|
||||
POA_DISPLAY_NAME,
|
||||
DAI_DISPLAY_NAME,
|
||||
POA_SOKOL_DISPLAY_NAME,
|
||||
MAINNET_DISPLAY_NAME,
|
||||
ROPSTEN_DISPLAY_NAME,
|
||||
RINKEBY_DISPLAY_NAME,
|
||||
KOVAN_DISPLAY_NAME,
|
||||
MAINNET_DISPLAY_NAME,
|
||||
POA_DISPLAY_NAME,
|
||||
GOERLI_TESTNET_DISPLAY_NAME,
|
||||
DROPDOWN_POA_DISPLAY_NAME,
|
||||
DROPDOWN_DAI_DISPLAY_NAME,
|
||||
DROPDOWN_POA_SOKOL_DISPLAY_NAME,
|
||||
DROPDOWN_MAINNET_DISPLAY_NAME,
|
||||
DROPDOWN_ROPSTEN_DISPLAY_NAME,
|
||||
DROPDOWN_RINKEBY_DISPLAY_NAME,
|
||||
DROPDOWN_KOVAN_DISPLAY_NAME,
|
||||
DROPDOWN_GOERLI_TESTNET_DISPLAY_NAME,
|
||||
} = require('./enums')
|
||||
|
||||
const networkToNameMap = {
|
||||
[POA_SOKOL]: POA_SOKOL_DISPLAY_NAME,
|
||||
[POA]: POA_SOKOL_DISPLAY_NAME,
|
||||
[ROPSTEN]: ROPSTEN_DISPLAY_NAME,
|
||||
[RINKEBY]: RINKEBY_DISPLAY_NAME,
|
||||
[KOVAN]: KOVAN_DISPLAY_NAME,
|
||||
[MAINNET]: MAINNET_DISPLAY_NAME,
|
||||
[POA_SOKOL_CODE]: POA_SOKOL_DISPLAY_NAME,
|
||||
[ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME,
|
||||
[RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME,
|
||||
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
|
||||
[POA_CODE]: POA_DISPLAY_NAME,
|
||||
}
|
||||
const networks = {}
|
||||
|
||||
const getNetworkDisplayName = key => networkToNameMap[key]
|
||||
const POA_OBJ = {
|
||||
order: 1,
|
||||
providerName: POA,
|
||||
networkID: POA_CODE,
|
||||
displayName: POA_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_POA_DISPLAY_NAME,
|
||||
}
|
||||
networks[POA_CODE] = POA_OBJ
|
||||
networks[POA] = POA_OBJ
|
||||
|
||||
const DAI_OBJ = {
|
||||
order: 2,
|
||||
providerName: DAI,
|
||||
networkID: DAI_CODE,
|
||||
displayName: DAI_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_DAI_DISPLAY_NAME,
|
||||
}
|
||||
networks[DAI_CODE] = DAI_OBJ
|
||||
networks[DAI] = DAI_OBJ
|
||||
|
||||
const POA_SOKOL_OBJ = {
|
||||
order: 3,
|
||||
providerName: POA_SOKOL,
|
||||
networkID: POA_SOKOL_CODE,
|
||||
displayName: POA_SOKOL_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_POA_SOKOL_DISPLAY_NAME,
|
||||
}
|
||||
networks[POA_SOKOL_CODE] = POA_SOKOL_OBJ
|
||||
networks[POA_SOKOL] = POA_SOKOL_OBJ
|
||||
|
||||
const MAINNET_OBJ = {
|
||||
order: 4,
|
||||
providerName: MAINNET,
|
||||
networkID: MAINNET_CODE,
|
||||
displayName: MAINNET_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_MAINNET_DISPLAY_NAME,
|
||||
}
|
||||
networks[MAINNET_CODE] = MAINNET_OBJ
|
||||
networks[MAINNET] = MAINNET_OBJ
|
||||
|
||||
const ROPSTEN_OBJ = {
|
||||
order: 5,
|
||||
providerName: ROPSTEN,
|
||||
networkID: ROPSTEN_CODE,
|
||||
displayName: ROPSTEN_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_ROPSTEN_DISPLAY_NAME,
|
||||
}
|
||||
networks[ROPSTEN_CODE] = ROPSTEN_OBJ
|
||||
networks[ROPSTEN] = ROPSTEN_OBJ
|
||||
|
||||
const KOVAN_OBJ = {
|
||||
order: 6,
|
||||
providerName: KOVAN,
|
||||
networkID: KOVAN_CODE,
|
||||
displayName: KOVAN_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_KOVAN_DISPLAY_NAME,
|
||||
}
|
||||
networks[KOVAN_CODE] = KOVAN_OBJ
|
||||
networks[KOVAN] = KOVAN_OBJ
|
||||
|
||||
const RINKEBY_OBJ = {
|
||||
order: 7,
|
||||
providerName: RINKEBY,
|
||||
networkID: RINKEBY_CODE,
|
||||
displayName: RINKEBY_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_RINKEBY_DISPLAY_NAME,
|
||||
}
|
||||
networks[RINKEBY_CODE] = RINKEBY_OBJ
|
||||
networks[RINKEBY] = RINKEBY_OBJ
|
||||
|
||||
const GOERLI_TESTNET_OBJ = {
|
||||
order: 7,
|
||||
providerName: GOERLI_TESTNET,
|
||||
networkID: GOERLI_TESTNET_CODE,
|
||||
displayName: GOERLI_TESTNET_DISPLAY_NAME,
|
||||
displayNameDropdown: DROPDOWN_GOERLI_TESTNET_DISPLAY_NAME,
|
||||
}
|
||||
networks[GOERLI_TESTNET_CODE] = GOERLI_TESTNET_OBJ
|
||||
networks[GOERLI_TESTNET] = GOERLI_TESTNET_OBJ
|
||||
|
||||
const getNetworkDisplayName = key => networks[key].displayName
|
||||
|
||||
module.exports = {
|
||||
networks,
|
||||
getNetworkDisplayName,
|
||||
}
|
||||
|
|
|
@ -5,6 +5,15 @@ module.exports = {
|
|||
}
|
||||
const ethNetProps = require('eth-net-props')
|
||||
|
||||
const { POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEBY_CODE,
|
||||
KOVAN_CODE,
|
||||
GOERLI_TESTNET_CODE } = require('../controllers/network/enums')
|
||||
|
||||
/**
|
||||
* Gives the caller a url at which the user can acquire coin, depending on the network they are in
|
||||
*
|
||||
|
@ -19,16 +28,17 @@ const ethNetProps = require('eth-net-props')
|
|||
*/
|
||||
function getBuyEthUrl ({ network, amount, address, ind }) {
|
||||
let url
|
||||
switch (network) {
|
||||
case '1':
|
||||
case '99':
|
||||
case '100':
|
||||
switch (Number(network)) {
|
||||
case MAINNET_CODE:
|
||||
case POA_CODE:
|
||||
case DAI_CODE:
|
||||
url = getExchanges({network, amount, address})[ind].link
|
||||
break
|
||||
case '3':
|
||||
case '4':
|
||||
case '42':
|
||||
case '77':
|
||||
case ROPSTEN_CODE:
|
||||
case RINKEBY_CODE:
|
||||
case KOVAN_CODE:
|
||||
case POA_SOKOL_CODE:
|
||||
case GOERLI_TESTNET_CODE:
|
||||
url = getFaucets(network)[ind]
|
||||
break
|
||||
}
|
||||
|
@ -77,6 +87,10 @@ function getExchanges ({network, amount, address}) {
|
|||
name: 'CEX Plus',
|
||||
link: 'http://cex.plus/market/poa_eth',
|
||||
},
|
||||
{
|
||||
name: 'HitBTC',
|
||||
link: 'https://hitbtc.com/POA-to-ETH',
|
||||
},
|
||||
]
|
||||
case 100:
|
||||
return [
|
||||
|
@ -85,5 +99,7 @@ function getExchanges ({network, amount, address}) {
|
|||
link: 'https://dai-bridge.poa.network/',
|
||||
},
|
||||
]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
|
|||
const AddressBookController = require('./controllers/address-book')
|
||||
const InfuraController = require('./controllers/infura')
|
||||
const BlacklistController = require('./controllers/blacklist')
|
||||
const CachedBalancesController = require('./controllers/cached-balances')
|
||||
const RecentBlocksController = require('./controllers/recent-blocks')
|
||||
const MessageManager = require('./lib/message-manager')
|
||||
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||
|
@ -51,6 +52,14 @@ const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
|
|||
const EthQuery = require('eth-query')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const sigUtil = require('eth-sig-util')
|
||||
const { importTypes } = require('../../old-ui/app/accounts/import/enums')
|
||||
const { LEDGER, TREZOR } = require('../../old-ui/app/components/connect-hardware/enum')
|
||||
|
||||
const {
|
||||
POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE } = require('./controllers/network/enums')
|
||||
const accountsPerPage = 5
|
||||
|
||||
module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
|
@ -138,6 +147,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
}
|
||||
})
|
||||
|
||||
this.cachedBalancesController = new CachedBalancesController({
|
||||
accountTracker: this.accountTracker,
|
||||
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
|
||||
initState: initState.CachedBalancesController,
|
||||
})
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
this.networkController.on('networkDidChange', () => {
|
||||
this.accountTracker._updateAccounts()
|
||||
|
@ -227,6 +242,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
ShapeShiftController: this.shapeshiftController.store,
|
||||
NetworkController: this.networkController.store,
|
||||
InfuraController: this.infuraController.store,
|
||||
CachedBalancesController: this.cachedBalancesController.store,
|
||||
})
|
||||
|
||||
this.memStore = new ComposableObservableStore(null, {
|
||||
|
@ -234,6 +250,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
AccountTracker: this.accountTracker.store,
|
||||
TxController: this.txController.memStore,
|
||||
BalancesController: this.balancesController.store,
|
||||
CachedBalancesController: this.cachedBalancesController.store,
|
||||
TokenRatesController: this.tokenRatesController.store,
|
||||
MessageManager: this.messageManager.memStore,
|
||||
PersonalMessageManager: this.personalMessageManager.memStore,
|
||||
|
@ -369,11 +386,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
resetAccount: nodeify(this.resetAccount, this),
|
||||
changePassword: nodeify(this.changePassword, this),
|
||||
removeAccount: nodeify(this.removeAccount, this),
|
||||
updateABI: nodeify(this.updateABI, this),
|
||||
getContract: nodeify(this.getContract, this),
|
||||
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
|
||||
|
||||
// hardware wallets
|
||||
connectHardware: nodeify(this.connectHardware, this),
|
||||
connectHardwareAndUnlockAddress: nodeify(this.connectHardwareAndUnlockAddress, this),
|
||||
forgetDevice: nodeify(this.forgetDevice, this),
|
||||
checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
|
||||
unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this),
|
||||
|
@ -595,10 +614,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
async getKeyringForDevice (deviceName, hdPath = null) {
|
||||
let keyringName = null
|
||||
switch (deviceName) {
|
||||
case 'trezor':
|
||||
case TREZOR:
|
||||
keyringName = TrezorKeyring.type
|
||||
break
|
||||
case 'ledger':
|
||||
case LEDGER:
|
||||
keyringName = LedgerBridgeKeyring.type
|
||||
break
|
||||
default:
|
||||
|
@ -645,6 +664,72 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
return accounts
|
||||
}
|
||||
|
||||
connectHardwareAndUnlockAddress (deviceName, hdPath, addressToUnlock) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||
|
||||
const accountsFromFirstPage = await keyring.getFirstPage()
|
||||
const initialPage = 0
|
||||
let accounts = await this.findAccountInLedger({
|
||||
accounts: accountsFromFirstPage,
|
||||
keyring,
|
||||
page: initialPage,
|
||||
addressToUnlock,
|
||||
hdPath,
|
||||
})
|
||||
accounts = accounts || accountsFromFirstPage
|
||||
|
||||
// Merge with existing accounts
|
||||
// and make sure addresses are not repeated
|
||||
const oldAccounts = await this.keyringController.getAccounts()
|
||||
const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
|
||||
this.accountTracker.syncWithAddresses(accountsToTrack)
|
||||
|
||||
resolve(accountsFromFirstPage)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async findAccountInLedger ({accounts, keyring, page, addressToUnlock, hdPath}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// to do: store pages depth in dropdown
|
||||
const pagesDepth = 10
|
||||
if (page >= pagesDepth) {
|
||||
reject({
|
||||
message: `Requested account ${addressToUnlock} is not found in ${pagesDepth} pages of ${hdPath} path of Ledger. Try to unlock this account from Ledger.`,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (accounts.length) {
|
||||
const accountIsFound = accounts.some((account, ind) => {
|
||||
const normalizedAddress = account.address.toLowerCase()
|
||||
if (normalizedAddress === addressToUnlock) {
|
||||
const indToUnlock = page * accountsPerPage + ind
|
||||
keyring.setAccountToUnlock(indToUnlock)
|
||||
}
|
||||
return normalizedAddress === addressToUnlock
|
||||
})
|
||||
|
||||
if (!accountIsFound) {
|
||||
accounts = await keyring.getNextPage()
|
||||
page++
|
||||
this.findAccountInLedger({accounts, keyring, page, addressToUnlock, hdPath})
|
||||
.then(accounts => {
|
||||
resolve(accounts)
|
||||
})
|
||||
.catch(e => {
|
||||
reject(e)
|
||||
})
|
||||
} else {
|
||||
resolve(accounts)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device is unlocked
|
||||
*
|
||||
|
@ -660,11 +745,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async forgetDevice (deviceName) {
|
||||
|
||||
async forgetDevice (deviceName, clearAccounts) {
|
||||
const keyring = await this.getKeyringForDevice(deviceName)
|
||||
keyring.forgetDevice()
|
||||
return true
|
||||
const accountsToForget = await keyring.forgetDevice(clearAccounts)
|
||||
for (const acc of accountsToForget) {
|
||||
const accToLower = acc.toLowerCase()
|
||||
await this.preferencesController.removeAddress(accToLower)
|
||||
await this.accountTracker.removeAccount([accToLower])
|
||||
}
|
||||
return accountsToForget
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -674,21 +763,39 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
*/
|
||||
async unlockHardwareWalletAccount (index, deviceName, hdPath) {
|
||||
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||
let hdAccounts = await keyring.getFirstPage()
|
||||
const accountPosition = Number(index) + 1
|
||||
const pages = Math.ceil(accountPosition / accountsPerPage)
|
||||
const indexInPage = index % accountsPerPage
|
||||
if (pages > 1) {
|
||||
for (let iterator = 0; iterator < pages; iterator++) {
|
||||
hdAccounts = await keyring.getNextPage()
|
||||
iterator++
|
||||
}
|
||||
}
|
||||
|
||||
keyring.setAccountToUnlock(index)
|
||||
const oldAccounts = await this.keyringController.getAccounts()
|
||||
const keyState = await this.keyringController.addNewAccount(keyring)
|
||||
const newAccounts = await this.keyringController.getAccounts()
|
||||
this.preferencesController.setAddresses(newAccounts)
|
||||
|
||||
let selectedAddressChanged = false
|
||||
newAccounts.forEach(address => {
|
||||
if (!oldAccounts.includes(address)) {
|
||||
// Set the account label to Trezor 1 / Ledger 1, etc
|
||||
this.preferencesController.setAccountLabel(address, `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${parseInt(index, 10) + 1}`)
|
||||
// Select the account
|
||||
this.preferencesController.setSelectedAddress(address)
|
||||
selectedAddressChanged = true
|
||||
}
|
||||
})
|
||||
|
||||
if (!selectedAddressChanged) {
|
||||
// Select the account
|
||||
this.preferencesController.setSelectedAddress(hdAccounts[indexInPage].address)
|
||||
}
|
||||
|
||||
const { identities } = this.preferencesController.store.getState()
|
||||
return { ...keyState, identities }
|
||||
}
|
||||
|
@ -822,6 +929,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
* Removes an account from state / storage.
|
||||
*
|
||||
* @param {string[]} address A hex address
|
||||
* @param {int} network ID
|
||||
*
|
||||
*/
|
||||
async removeAccount (address, network) {
|
||||
|
@ -839,6 +947,23 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
return address
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates implementation ABI for proxy account type.
|
||||
*
|
||||
* @param {string[]} address A hex address
|
||||
* @param {int} network ID
|
||||
*
|
||||
*/
|
||||
async updateABI (address, network, newABI) {
|
||||
// Sets new ABI for implementation contract
|
||||
try {
|
||||
await this.keyringController.updateABI(address, network, newABI)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Imports an account with the specified import strategy.
|
||||
|
@ -851,7 +976,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
*/
|
||||
async importAccountWithStrategy (strategy, args) {
|
||||
let keyring
|
||||
if (strategy === 'Contract') {
|
||||
if (strategy === importTypes.CONTRACT.DEFAULT || strategy === importTypes.CONTRACT.PROXY) {
|
||||
args.contractType = strategy
|
||||
keyring = await this.keyringController.addNewKeyring('Simple Address', args)
|
||||
} else {
|
||||
const privateKey = await accountImporter.importAccount(strategy, args)
|
||||
|
@ -1390,7 +1516,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
|
||||
const networkIdStr = networkController.store.getState().network
|
||||
const networkId = parseInt(networkIdStr)
|
||||
const isPOA = networkId === 77 || networkId === 99
|
||||
const isPOA = networkId === POA_SOKOL_CODE || networkId === POA_CODE || networkId === DAI_CODE
|
||||
|
||||
// Return 1 gwei if using a POA network of if there are no blocks have been observed:
|
||||
if (isPOA || recentBlocks.length === 0) {
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
const extension = require('extensionizer')
|
||||
const explorerLinks = require('eth-net-props').explorerLinks
|
||||
const { capitalizeFirstLetter } = require('../lib/util')
|
||||
const {
|
||||
POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
GOERLI_TESTNET_CODE } = require('../controllers/network/enums')
|
||||
|
||||
class ExtensionPlatform {
|
||||
|
||||
|
@ -125,7 +130,11 @@ class ExtensionPlatform {
|
|||
|
||||
_getExplorer (hash, networkId) {
|
||||
let explorerName
|
||||
if (networkId === 99 || networkId === 100 || networkId === 77) {
|
||||
if (networkId === POA_CODE ||
|
||||
networkId === DAI_CODE ||
|
||||
networkId === POA_SOKOL_CODE ||
|
||||
networkId === GOERLI_TESTNET_CODE
|
||||
) {
|
||||
explorerName = 'BlockScout'
|
||||
} else {
|
||||
explorerName = 'Etherscan'
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
|
||||
<title>TrezorConnect | Trezor</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="keywords" content="" />
|
||||
<meta name="author" content="Trezor info@trezor.io" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="title" content="Trezor Connect" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="-1" />
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html, body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
min-width: 328px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="trezor-usb-permissions" src="https://connect.trezor.io/6/extension-permissions.html" allow="usb" frameborder="0" width="100%" height="100%"></iframe>
|
||||
<script type="text/javascript" src="./vendor/trezor/usb-permissions.js"></script>
|
||||
</body>
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
Passing messages from background script to popup
|
||||
*/
|
||||
|
||||
let port = chrome.runtime.connect({ name: 'trezor-connect' });
|
||||
port.onMessage.addListener(message => {
|
||||
window.postMessage(message, window.location.origin);
|
||||
});
|
||||
port.onDisconnect.addListener(d => {
|
||||
port = null;
|
||||
});
|
||||
|
||||
/*
|
||||
Passing messages from popup to background script
|
||||
*/
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
if (port && event.source === window && event.data) {
|
||||
port.postMessage(event.data);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
Handling messages from usb permissions iframe
|
||||
*/
|
||||
|
||||
const switchToPopupTab = (event) => {
|
||||
|
||||
window.removeEventListener('beforeunload', switchToPopupTab);
|
||||
|
||||
if (!event) {
|
||||
// triggered from 'usb-permissions-close' message
|
||||
// switch tab to previous index and close current
|
||||
chrome.tabs.query({
|
||||
currentWindow: true,
|
||||
active: true,
|
||||
}, (current) => {
|
||||
if (current.length < 0) return;
|
||||
chrome.tabs.query({
|
||||
index: current[0].index - 1
|
||||
}, popup => {
|
||||
if (popup.length < 0) return;
|
||||
chrome.tabs.update(popup[0].id, { active: true });
|
||||
})
|
||||
chrome.tabs.remove(current[0].id);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// triggered from 'beforeunload' event
|
||||
// find tab by popup pattern and switch to it
|
||||
chrome.tabs.query({
|
||||
url: "*://connect.trezor.io/*/popup.html"
|
||||
}, (tabs) => {
|
||||
if (tabs.length < 0) return;
|
||||
chrome.tabs.update(tabs[0].id, { active: true });
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data === 'usb-permissions-init') {
|
||||
const iframe = document.getElementById('trezor-usb-permissions');
|
||||
iframe.contentWindow.postMessage({
|
||||
type: 'usb-permissions-init',
|
||||
extension: chrome.runtime.id,
|
||||
}, '*');
|
||||
} else if (event.data === 'usb-permissions-close') {
|
||||
switchToPopupTab();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', switchToPopupTab);
|
|
@ -78,6 +78,10 @@ createCopyTasks('fonts', {
|
|||
source: './app/fonts/',
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/fonts`),
|
||||
})
|
||||
createCopyTasks('vendor', {
|
||||
source: './app/vendor/',
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/vendor`),
|
||||
})
|
||||
createCopyTasks('reload', {
|
||||
devOnly: true,
|
||||
source: './app/scripts/',
|
||||
|
|
|
@ -14,16 +14,20 @@ const EditableLabel = require('./components/editable-label')
|
|||
const TabBar = require('./components/tab-bar')
|
||||
const TokenList = require('./components/token-list')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
const CopyButton = require('./components/copyButton')
|
||||
const CopyButton = require('./components/copy/copy-button')
|
||||
const ToastComponent = require('./components/toast')
|
||||
import { getMetaMaskAccounts } from '../../ui/app/selectors'
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountDetailScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
return {
|
||||
metamask: state.metamask,
|
||||
identities: state.metamask.identities,
|
||||
keyrings: state.metamask.keyrings,
|
||||
accounts: state.metamask.accounts,
|
||||
warning: state.appState.warning,
|
||||
accounts,
|
||||
address: state.metamask.selectedAddress,
|
||||
accountDetail: state.appState.accountDetail,
|
||||
network: state.metamask.network,
|
||||
|
@ -62,6 +66,10 @@ AccountDetailScreen.prototype.render = function () {
|
|||
|
||||
h('.account-detail-section.full-flex-height', [
|
||||
|
||||
h(ToastComponent, {
|
||||
isSuccess: false,
|
||||
}),
|
||||
|
||||
// identicon, label, balance, etc
|
||||
h('.account-data-subsection', {
|
||||
style: {
|
||||
|
|
|
@ -4,7 +4,7 @@ const h = require('react-hyperscript')
|
|||
const {qrcode: qrCode} = require('qrcode-npm')
|
||||
const {connect} = require('react-redux')
|
||||
const {isHexPrefixed} = require('ethereumjs-util')
|
||||
const CopyButton = require('./components/copyButton')
|
||||
const CopyButton = require('./components/copy/copy-button')
|
||||
|
||||
class AccountQrScreen extends PureComponent {
|
||||
static defaultProps = {
|
||||
|
|
|
@ -4,7 +4,18 @@ import { connect } from 'react-redux'
|
|||
import actions from '../../../../ui/app/actions'
|
||||
import Web3 from 'web3'
|
||||
import log from 'loglevel'
|
||||
import copyToClipboard from 'copy-to-clipboard'
|
||||
import CopyButton from '../../components/copy/copy-button'
|
||||
import ErrorComponent from '../../components/error'
|
||||
import { getFullABI } from './helpers'
|
||||
|
||||
import { POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEBY_CODE,
|
||||
KOVAN_CODE,
|
||||
GOERLI_TESTNET_CODE } from '../../../../app/scripts/controllers/network/enums'
|
||||
|
||||
class ContractImportView extends Component {
|
||||
constructor (props) {
|
||||
|
@ -23,6 +34,7 @@ class ContractImportView extends Component {
|
|||
static propTypes = {
|
||||
error: PropTypes.string,
|
||||
network: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
displayWarning: PropTypes.func,
|
||||
importNewAccount: PropTypes.func,
|
||||
hideWarning: PropTypes.func,
|
||||
|
@ -41,9 +53,9 @@ class ContractImportView extends Component {
|
|||
abiOnChange (abi) {
|
||||
this.props.hideWarning()
|
||||
try {
|
||||
if (abi && JSON.parse(abi)) {
|
||||
if (abi) {
|
||||
this.setState({
|
||||
abi,
|
||||
abi: JSON.stringify(abi),
|
||||
abiInputDisabled: true,
|
||||
importDisabled: false,
|
||||
})
|
||||
|
@ -54,6 +66,12 @@ class ContractImportView extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.type !== prevProps.type) {
|
||||
this.clearInputs()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error } = this.props
|
||||
|
||||
|
@ -64,6 +82,7 @@ class ContractImportView extends Component {
|
|||
alignItems: 'center',
|
||||
padding: '5px 0px 0px 0px',
|
||||
}}>
|
||||
<ErrorComponent error={error} />
|
||||
<span>Paste address of contract here</span>
|
||||
<input
|
||||
className="large-input"
|
||||
|
@ -76,10 +95,12 @@ class ContractImportView extends Component {
|
|||
}}
|
||||
/>
|
||||
<span style={{ marginTop: '20px' }}>Paste ABI of contract here
|
||||
<i
|
||||
className="clipboard cursor-pointer"
|
||||
style={{ marginLeft: '10px' }}
|
||||
onClick={(e) => { copyToClipboard(this.state.abi) }}
|
||||
<CopyButton
|
||||
value={this.state.abi}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
}}
|
||||
tooltipPosition="right"
|
||||
/>
|
||||
</span>
|
||||
<textarea
|
||||
|
@ -106,26 +127,17 @@ class ContractImportView extends Component {
|
|||
|
||||
autodetectContractAbi = () => {
|
||||
const { contractAddr, web3 } = this.state
|
||||
const { type, network } = this.props
|
||||
if (!contractAddr || !web3.isAddress(contractAddr)) {
|
||||
this.clearAbi()
|
||||
return
|
||||
}
|
||||
|
||||
const networkName = this.getBlockscoutApiNetworkSuffix()
|
||||
const bloscoutApiLink = `https://blockscout.com/poa/${networkName}/api`
|
||||
const bloscoutApiContractPath = '?module=contract'
|
||||
const blockscoutApiGetAbiPath = `&action=getabi&address=${this.state.contractAddr}`
|
||||
const apiLink = `${bloscoutApiLink}${bloscoutApiContractPath}${blockscoutApiGetAbiPath}`
|
||||
fetch(apiLink)
|
||||
.then(response => {
|
||||
return response.json()
|
||||
})
|
||||
.then(responseJson => {
|
||||
this.abiOnChange(responseJson && responseJson.result)
|
||||
})
|
||||
.catch((e) => {
|
||||
getFullABI(web3.eth, contractAddr, network, type)
|
||||
.then(finalABI => this.abiOnChange(finalABI))
|
||||
.catch(e => {
|
||||
this.clearAbi()
|
||||
log.debug(e)
|
||||
this.props.displayWarning(e.message)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -178,7 +190,7 @@ class ContractImportView extends Component {
|
|||
return this.props.displayWarning('Invalid contract ABI')
|
||||
}
|
||||
|
||||
this.props.importNewAccount('Contract', { addr: contractAddr, network: this.props.network, abi })
|
||||
this.props.importNewAccount(this.props.type, { addr: contractAddr, network: this.props.network, abi })
|
||||
// JS runtime requires caught rejections but failures are handled by Redux
|
||||
.catch()
|
||||
}
|
||||
|
@ -186,25 +198,36 @@ class ContractImportView extends Component {
|
|||
getBlockscoutApiNetworkSuffix () {
|
||||
const { network } = this.props
|
||||
switch (Number(network)) {
|
||||
case 1:
|
||||
case MAINNET_CODE:
|
||||
return 'mainnet'
|
||||
case 99:
|
||||
case POA_CODE:
|
||||
return 'core'
|
||||
case 77:
|
||||
case POA_SOKOL_CODE:
|
||||
return 'sokol'
|
||||
case 100:
|
||||
case DAI_CODE:
|
||||
return 'dai'
|
||||
case 42:
|
||||
case KOVAN_CODE:
|
||||
return 'kovan'
|
||||
case 3:
|
||||
case ROPSTEN_CODE:
|
||||
return 'ropsten'
|
||||
case 4:
|
||||
case RINKEBY_CODE:
|
||||
return 'rinkeby'
|
||||
case GOERLI_TESTNET_CODE:
|
||||
return 'goerli'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
clearInputs () {
|
||||
this.setState({
|
||||
contractAddr: '',
|
||||
abi: '',
|
||||
abiInputDisabled: false,
|
||||
importDisabled: true,
|
||||
})
|
||||
}
|
||||
|
||||
clearAbi () {
|
||||
this.setState({
|
||||
abi: '',
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
const importTypes = {
|
||||
PRIVATE_KEY: 'Private Key',
|
||||
JSON_FILE: 'JSON File',
|
||||
CONTRACT: {
|
||||
DEFAULT: 'Contract',
|
||||
PROXY: 'Proxy',
|
||||
},
|
||||
}
|
||||
|
||||
const labels = {
|
||||
CONTRACT: 'CONTRACT',
|
||||
PROXY: 'PROXY',
|
||||
HARDWARE: 'HARDWARE',
|
||||
IMPORTED: 'IMPORTED',
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
importTypes,
|
||||
labels,
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
import log from 'loglevel'
|
||||
import { importTypes } from './enums'
|
||||
|
||||
const nestedJsonObjToArray = (jsonObj) => {
|
||||
return jsonObjToArray(jsonObj)
|
||||
}
|
||||
|
||||
const jsonObjToArray = (jsonObj) => {
|
||||
return Object.keys(jsonObj).reduce((arr, key) => {
|
||||
if (jsonObj[key].constructor === Object || jsonObj[key].constructor === Array) {
|
||||
arr = arr.concat(jsonObjToArray(jsonObj[key]))
|
||||
} else if (jsonObj[key].constructor === String) {
|
||||
arr.push(jsonObj[key])
|
||||
}
|
||||
return arr
|
||||
}, [])
|
||||
}
|
||||
|
||||
const getBlockscoutApiNetworkSuffix = (network) => {
|
||||
switch (Number(network)) {
|
||||
case 1:
|
||||
return 'mainnet'
|
||||
case 99:
|
||||
return 'core'
|
||||
case 77:
|
||||
return 'sokol'
|
||||
case 100:
|
||||
return 'dai'
|
||||
case 42:
|
||||
return 'kovan'
|
||||
case 3:
|
||||
return 'ropsten'
|
||||
case 4:
|
||||
return 'rinkeby'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const fetchABI = (addr, network) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const networkName = getBlockscoutApiNetworkSuffix(network)
|
||||
const bloscoutApiLink = `https://blockscout.com/poa/${networkName}/api`
|
||||
const bloscoutApiContractPath = '?module=contract'
|
||||
const blockscoutApiGetAbiPath = `&action=getabi&address=${addr}`
|
||||
const apiLink = `${bloscoutApiLink}${bloscoutApiContractPath}${blockscoutApiGetAbiPath}`
|
||||
fetch(apiLink)
|
||||
.then(response => {
|
||||
return response.json()
|
||||
})
|
||||
.then(responseJson => {
|
||||
resolve(responseJson && responseJson.result)
|
||||
})
|
||||
.catch((e) => {
|
||||
log.debug(e)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getFullABI = (eth, contractAddr, network, type) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchABI(contractAddr, network)
|
||||
.then((targetABI) => {
|
||||
targetABI = targetABI && JSON.parse(targetABI)
|
||||
let finalABI = targetABI
|
||||
if (type === importTypes.CONTRACT.PROXY) {
|
||||
if (!eth.contract(targetABI).at(contractAddr).implementation) {
|
||||
const e = {
|
||||
message: 'This is not a valid Delegate Proxy contract',
|
||||
}
|
||||
reject(e)
|
||||
}
|
||||
try {
|
||||
eth.contract(targetABI).at(contractAddr).implementation.call((err, implAddr) => {
|
||||
fetchABI(implAddr, network)
|
||||
.then((implABI) => {
|
||||
implABI = implABI && JSON.parse(implABI)
|
||||
finalABI = implABI ? targetABI.concat(implABI) : targetABI
|
||||
resolve(finalABI)
|
||||
})
|
||||
.catch(e => reject(e))
|
||||
})
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
} else {
|
||||
resolve(finalABI)
|
||||
}
|
||||
})
|
||||
.catch(e => { reject(e) })
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
nestedJsonObjToArray,
|
||||
getFullABI,
|
||||
}
|
|
@ -1,137 +1,154 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import actions from '../../../../ui/app/actions'
|
||||
import Select from 'react-select'
|
||||
import { importTypes } from './enums'
|
||||
import { nestedJsonObjToArray } from './helpers'
|
||||
|
||||
// Subviews
|
||||
const JsonImportView = require('./json.js')
|
||||
const PrivateKeyImportView = require('./private-key.js')
|
||||
const ContractImportView = require('./contract.js')
|
||||
import JsonImportView from './json.js'
|
||||
import PrivateKeyImportView from './private-key.js'
|
||||
import ContractImportView from './contract.js'
|
||||
|
||||
const menuItems = [
|
||||
'Private Key',
|
||||
'JSON File',
|
||||
'Contract',
|
||||
]
|
||||
const menuItems = nestedJsonObjToArray(importTypes)
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountImportSubview)
|
||||
class AccountImportSubview extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
description: '',
|
||||
type: importTypes.PRIVATE_KEY,
|
||||
}
|
||||
}
|
||||
static propTypes = {
|
||||
menuItems: PropTypes.array,
|
||||
warning: PropTypes.node,
|
||||
goHome: PropTypes.func,
|
||||
displayWarning: PropTypes.func,
|
||||
showImportPage: PropTypes.func,
|
||||
}
|
||||
render () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { menuItems } = props
|
||||
const { type } = state
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
}}>
|
||||
<div className="section-title" style={{
|
||||
height: '1px',
|
||||
width: '100%',
|
||||
}} />
|
||||
<div style={{
|
||||
width: '100%',
|
||||
padding: '0 30px',
|
||||
}}>
|
||||
<div className="flex-row flex-center">
|
||||
<div
|
||||
className="i fa fa-arrow-left fa-lg cursor-pointer"
|
||||
onClick={(event) => { props.goHome() }}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '30px',
|
||||
}}
|
||||
/>
|
||||
<h2 className="page-subtitle" style={{
|
||||
fontFamily: 'Nunito SemiBold',
|
||||
}}
|
||||
>Import Accounts</h2>
|
||||
</div>
|
||||
<div
|
||||
className="error"
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<span>Imported accounts will not be associated with your originally created Nifty Wallet account seedphrase.</span>
|
||||
</div>
|
||||
<div style={{ padding: '10px 0' }}>
|
||||
<h3 style={{ padding: '3px' }}>Select Type</h3>
|
||||
<Select {...{
|
||||
name: 'import-type-select',
|
||||
clearable: false,
|
||||
value: type || menuItems[0],
|
||||
options: menuItems.map((type) => {
|
||||
return {
|
||||
value: type,
|
||||
label: type,
|
||||
}
|
||||
}),
|
||||
onChange: (opt) => { this.onChange(opt) },
|
||||
}}/>
|
||||
<p className="hw-connect__header__msg" dangerouslySetInnerHTML={{__html: this.state.description}} />
|
||||
</div>
|
||||
{this.renderImportView()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
onChange (opt) {
|
||||
const props = this.props
|
||||
props.showImportPage()
|
||||
const type = opt.value
|
||||
let description
|
||||
switch (type) {
|
||||
case importTypes.PRIVATE_KEY:
|
||||
case importTypes.JSON_FILE:
|
||||
description = ''
|
||||
break
|
||||
case importTypes.CONTRACT.DEFAULT:
|
||||
description = `Contract type will automatically retrieve its ABI, if it was verified in <a href='https://blockscout.com' target='_blank'>Blockscout</a>`
|
||||
break
|
||||
case importTypes.CONTRACT.PROXY:
|
||||
description = `Proxy contract type will automatically contain ABI of implementation, if proxy and implementation were both verified in <a href='https://blockscout.com' target='_blank'>Blockscout</a>`
|
||||
break
|
||||
default:
|
||||
description = ''
|
||||
break
|
||||
}
|
||||
this.setState({ type, description })
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.props.displayWarning('')
|
||||
}
|
||||
renderImportView () {
|
||||
const { menuItems } = this.props
|
||||
const state = this.state || {}
|
||||
const { type } = state
|
||||
const current = type || menuItems[0]
|
||||
|
||||
switch (current) {
|
||||
case importTypes.PRIVATE_KEY:
|
||||
return <PrivateKeyImportView/>
|
||||
case importTypes.JSON_FILE:
|
||||
return <JsonImportView/>
|
||||
case importTypes.CONTRACT.DEFAULT:
|
||||
return <ContractImportView type={importTypes.CONTRACT.DEFAULT}/>
|
||||
case importTypes.CONTRACT.PROXY:
|
||||
return <ContractImportView type={importTypes.CONTRACT.PROXY}/>
|
||||
default:
|
||||
return <JsonImportView/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
menuItems,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountImportSubview, Component)
|
||||
function AccountImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { menuItems } = props
|
||||
const { type } = state
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h('.section-title', { style: {
|
||||
height: '1px',
|
||||
width: '100%',
|
||||
}}),
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
paddingLeft: '30px',
|
||||
paddingRight: '30px',
|
||||
},
|
||||
}, [
|
||||
h('.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
props.dispatch(actions.goHome())
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '30px',
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', {
|
||||
style: {
|
||||
fontFamily: 'Nunito SemiBold',
|
||||
},
|
||||
}, 'Import Accounts'),
|
||||
]),
|
||||
h('.error', {
|
||||
style: {
|
||||
display: 'inline-block',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h('span', 'Imported accounts will not be associated with your originally created Nifty Wallet account seedphrase.'),
|
||||
]),
|
||||
h('div', {
|
||||
style: {
|
||||
padding: '10px 0',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('h3', { style: { padding: '3px' } }, 'Select Type'),
|
||||
|
||||
h('style', `
|
||||
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
|
||||
color: rgb(174,174,174);
|
||||
}
|
||||
`),
|
||||
|
||||
h(Select, {
|
||||
name: 'import-type-select',
|
||||
clearable: false,
|
||||
value: type || menuItems[0],
|
||||
options: menuItems.map((type) => {
|
||||
return {
|
||||
value: type,
|
||||
label: type,
|
||||
}
|
||||
}),
|
||||
onChange: (opt) => {
|
||||
props.dispatch(actions.showImportPage())
|
||||
this.setState({ type: opt.value })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
this.renderImportView(),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.componentWillUnmount = function () {
|
||||
this.props.dispatch(actions.displayWarning(''))
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.renderImportView = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { type } = state
|
||||
const { menuItems } = props
|
||||
const current = type || menuItems[0]
|
||||
|
||||
switch (current) {
|
||||
case 'Private Key':
|
||||
return h(PrivateKeyImportView)
|
||||
case 'JSON File':
|
||||
return h(JsonImportView)
|
||||
case 'Contract':
|
||||
return h(ContractImportView)
|
||||
default:
|
||||
return h(JsonImportView)
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
goHome: () => dispatch(actions.goHome()),
|
||||
displayWarning: warning => dispatch(actions.displayWarning(warning)),
|
||||
showImportPage: options => dispatch(actions.showImportPage()),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountImportSubview)
|
||||
|
|
|
@ -5,7 +5,7 @@ const connect = require('react-redux').connect
|
|||
const actions = require('../../ui/app/actions')
|
||||
const Tooltip = require('./components/tooltip.js')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const Copyable = require('./components/copyable')
|
||||
const Copyable = require('./components/copy/copyable')
|
||||
const addressSummary = require('./util').addressSummary
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ const ConfirmAddTokenScreen = require('./components/confirm-add-token')
|
|||
const RemoveTokenScreen = require('./remove-token')
|
||||
const AddSuggestedTokenScreen = require('./add-suggested-token')
|
||||
const Import = require('./accounts/import')
|
||||
import ConnectHardwareForm from './components/connect-hardware/index.js'
|
||||
const ForgetDeviceScreen = require('./components/connect-hardware/forget-screen')
|
||||
import ConnectHardwareForm from './components/connect-hardware/index'
|
||||
const InfoScreen = require('./info')
|
||||
const AppBar = require('./components/app-bar')
|
||||
const Loading = require('./components/loading')
|
||||
|
@ -44,6 +45,7 @@ const DeleteRpc = require('./components/delete-rpc')
|
|||
const DeleteImportedAccount = require('./components/delete-imported-account')
|
||||
const ConfirmChangePassword = require('./components/confirm-change-password')
|
||||
const ethNetProps = require('eth-net-props')
|
||||
const { getMetaMaskAccounts } = require('../../ui/app/selectors')
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
|
@ -54,9 +56,11 @@ inherits(App, Component)
|
|||
function App () { Component.call(this) }
|
||||
|
||||
function mapStateToProps (state) {
|
||||
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
|
||||
const {
|
||||
identities,
|
||||
accounts,
|
||||
address,
|
||||
keyrings,
|
||||
isInitialized,
|
||||
|
@ -281,6 +285,10 @@ App.prototype.renderPrimary = function () {
|
|||
log.debug('rendering import screen')
|
||||
return h(Import, {key: 'import-menu'})
|
||||
|
||||
case 'forget-device':
|
||||
log.debug('rendering forget device screen')
|
||||
return h(ForgetDeviceScreen, {key: 'forget-device'})
|
||||
|
||||
case 'hardware-wallets-menu':
|
||||
log.debug('rendering hardware wallet menu screen')
|
||||
return h(ConnectHardwareForm, {key: 'hardware-wallets-menu'})
|
||||
|
|
|
@ -1,22 +1,48 @@
|
|||
const Component = require('react').Component
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const connect = require('react-redux').connect
|
||||
const Dropdown = require('./dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||
const Identicon = require('./identicon')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const ethNetProps = require('eth-net-props')
|
||||
const { getCurrentKeyring, ifLooseAcc, ifContractAcc } = require('../util')
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import actions from '../../../ui/app/actions'
|
||||
import { connect } from 'react-redux'
|
||||
import { Dropdown, DropdownMenuItem } from './dropdown'
|
||||
import Identicon from './identicon'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import copyToClipboard from 'copy-to-clipboard'
|
||||
import ethNetProps from 'eth-net-props'
|
||||
import { getCurrentKeyring, ifLooseAcc, ifContractAcc, ifHardwareAcc } from '../util'
|
||||
import { getHdPaths, isLedger } from './connect-hardware/util'
|
||||
import { LEDGER } from './connect-hardware/enum'
|
||||
import { importTypes, labels } from '../accounts/import/enums'
|
||||
import { getFullABI } from '../accounts/import/helpers'
|
||||
import log from 'loglevel'
|
||||
import Web3 from 'web3'
|
||||
|
||||
class AccountsDropdownMenuItemWrapper extends DropdownMenuItem {
|
||||
render () {
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
style={{
|
||||
padding: '8px 0px',
|
||||
}}
|
||||
closeMenu={() => {}}
|
||||
onClick={() => this.props.onClick()}
|
||||
>
|
||||
<span className="acc-dd-menu-item-text">{this.props.label}</span>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class AccountDropdowns extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
const web3 = new Web3(global.ethereumProvider)
|
||||
this.state = {
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: false,
|
||||
web3,
|
||||
labels: {},
|
||||
isProxy: false,
|
||||
contractProps: null,
|
||||
preventToast: false,
|
||||
}
|
||||
this.accountSelectorToggleClassName = 'accounts-selector'
|
||||
this.optionsMenuToggleClassName = 'account-dropdown'
|
||||
|
@ -24,16 +50,9 @@ class AccountDropdowns extends Component {
|
|||
|
||||
renderAccounts () {
|
||||
const { identities, selected, keyrings, network } = this.props
|
||||
const accountOrder = keyrings.reduce((list, keyring) => {
|
||||
if (ifContractAcc(keyring) && keyring.network === network) {
|
||||
list = list.concat(keyring.accounts)
|
||||
} else if (!ifContractAcc(keyring)) {
|
||||
list = list.concat(keyring.accounts)
|
||||
}
|
||||
return list
|
||||
}, [])
|
||||
const accountOrder = this.getAccounts()
|
||||
|
||||
return accountOrder.map((address, index) => {
|
||||
const accountsViews = accountOrder.map((address, index) => {
|
||||
const identity = identities[address]
|
||||
if (!identity) {
|
||||
return null
|
||||
|
@ -53,325 +72,370 @@ class AccountDropdowns extends Component {
|
|||
return this.accountsDropdownItemView(index, isSelected, keyring, identity)
|
||||
}
|
||||
})
|
||||
return accountsViews
|
||||
}
|
||||
|
||||
accountsDropdownItemView (index, isSelected, keyring, identity) {
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
this.props.actions.showAccountDetail(identity.address)
|
||||
},
|
||||
style: {
|
||||
marginTop: index === 0 ? '5px' : '',
|
||||
fontSize: '16px',
|
||||
padding: '8px 0px',
|
||||
},
|
||||
},
|
||||
[
|
||||
isSelected ? h('div', {
|
||||
style: {
|
||||
width: '4px',
|
||||
height: '26px',
|
||||
background: '#60db97',
|
||||
position: 'absolute',
|
||||
left: '-25px',
|
||||
},
|
||||
}) : null,
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
overflow: 'none',
|
||||
address: identity.address,
|
||||
diameter: 24,
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
},
|
||||
),
|
||||
h('span', {
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
fontSize: '16px',
|
||||
maxWidth: '95px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
color: isSelected ? 'white' : '',
|
||||
},
|
||||
}, identity.name || ''),
|
||||
this.indicateIfLoose(keyring),
|
||||
ifLooseAcc(keyring) ? h('.remove', {
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.props.actions.showDeleteImportedAccount(identity)
|
||||
this.setState({
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: false,
|
||||
})
|
||||
},
|
||||
}) : null,
|
||||
]
|
||||
)
|
||||
const { labels } = this.state
|
||||
const { address, name } = identity
|
||||
const leftBorder = isSelected ? <div className="accs-dd-menu-item-selected" /> : null
|
||||
const accountIcon = (
|
||||
<Identicon
|
||||
overflow="none"
|
||||
address={identity.address}
|
||||
diameter={24}
|
||||
style={{ marginLeft: '10px' }}
|
||||
/>
|
||||
)
|
||||
const accountName = (
|
||||
<span
|
||||
className="accs-dd-menu-item-account-name"
|
||||
style={{ color: isSelected ? 'white' : '' }}
|
||||
>{name || ''}
|
||||
</span>
|
||||
)
|
||||
const accountLabel = labels[address] ? <div className="keyring-label">{labels[address]}</div> : null
|
||||
const removeIcon = ifLooseAcc(keyring) ? (
|
||||
<div
|
||||
className="remove"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.props.actions.showDeleteImportedAccount(identity)
|
||||
this.setState({
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: false,
|
||||
})
|
||||
}}
|
||||
/>) : null
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={`account_${index}`}
|
||||
closeMenu={() => {}}
|
||||
onClick={() => this.accountOnClick(keyring, address)}
|
||||
style={{
|
||||
marginTop: index === 0 ? '5px' : '',
|
||||
fontSize: '16px',
|
||||
padding: '8px 0px',
|
||||
}}
|
||||
>
|
||||
{leftBorder}
|
||||
{accountIcon}
|
||||
{accountName}
|
||||
{accountLabel}
|
||||
{removeIcon}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
ifHardwareAcc (keyring) {
|
||||
if (keyring && keyring.type.search('Hardware') !== -1) {
|
||||
return true
|
||||
accountOnClick (keyring, address) {
|
||||
this.props.actions.showAccountDetail(address)
|
||||
if (ifHardwareAcc(keyring)) {
|
||||
if (isLedger(keyring.type)) {
|
||||
const hdPaths = getHdPaths()
|
||||
return new Promise((resolve, reject) => {
|
||||
this.props.actions.connectHardwareAndUnlockAddress(LEDGER, hdPaths[1].value, address)
|
||||
.then(_ => resolve())
|
||||
.catch(e => {
|
||||
this.props.actions.connectHardwareAndUnlockAddress(LEDGER, hdPaths[0].value, address)
|
||||
.then(_ => resolve())
|
||||
.catch(e => reject(e))
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
if (!this.state.preventToast) {
|
||||
this.props.actions.displayToast(e)
|
||||
} else {
|
||||
this.allowToast()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.preventToast()
|
||||
}
|
||||
} else {
|
||||
this.preventToast()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
indicateIfLoose (keyring) {
|
||||
allowToast () {
|
||||
this.setState({preventToast: false})
|
||||
}
|
||||
|
||||
preventToast () {
|
||||
this.setState({preventToast: true})
|
||||
}
|
||||
|
||||
ifProxyAcc (address, setProxy) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.props.actions.getContract(address)
|
||||
.then(contractProps => {
|
||||
if (setProxy) {
|
||||
this.setState({contractProps})
|
||||
}
|
||||
resolve(contractProps && contractProps.contractType === importTypes.CONTRACT.PROXY)
|
||||
})
|
||||
.catch(e => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
setLabel (keyring, address) {
|
||||
if (ifLooseAcc(keyring)) {
|
||||
let label
|
||||
if (ifContractAcc(keyring)) {
|
||||
label = 'CONTRACT'
|
||||
} else if (this.ifHardwareAcc(keyring)) {
|
||||
label = 'HARDWARE'
|
||||
const setProxy = false
|
||||
this.ifProxyAcc(address, setProxy)
|
||||
.then(isProxy => {
|
||||
label = isProxy ? labels.PROXY : labels.CONTRACT
|
||||
this.setLabelToState(label, address)
|
||||
})
|
||||
} else if (ifHardwareAcc(keyring)) {
|
||||
label = labels.HARDWARE
|
||||
this.setLabelToState(label, address)
|
||||
} else {
|
||||
label = 'IMPORTED'
|
||||
label = labels.IMPORTED
|
||||
this.setLabelToState(label, address)
|
||||
}
|
||||
return h('.keyring-label', label)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
setLabelToState (label, address) {
|
||||
const labelsArr = this.state.labels
|
||||
labelsArr[address] = label
|
||||
this.setState({labelsArr})
|
||||
}
|
||||
|
||||
renderAccountSelector () {
|
||||
const { actions } = this.props
|
||||
const { accountSelectorActive } = this.state
|
||||
let menuItems = []
|
||||
menuItems = Object.assign(menuItems, this.renderAccounts())
|
||||
const bottomMenuItems = [
|
||||
<AccountsDropdownMenuItemWrapper key="AccountsDropdownMenuItemAdd" onClick={() => actions.addNewAccount()} label="Create Account" />,
|
||||
<AccountsDropdownMenuItemWrapper key="AccountsDropdownMenuItemImport" onClick={() => actions.showImportPage()} label="Import Account" />,
|
||||
<AccountsDropdownMenuItemWrapper key="AccountsDropdownMenuItemConnectHD" onClick={() => actions.showConnectHWWalletPage()} label="Connect hardware wallet" />,
|
||||
]
|
||||
menuItems = menuItems.concat(bottomMenuItems)
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
useCssTransition: true, // Hardcoded because account selector is temporarily in app-header
|
||||
style: {
|
||||
return (
|
||||
<Dropdown
|
||||
useCssTransition={true} // Hardcoded because account selector is temporarily in app-header
|
||||
style={{
|
||||
position: 'absolute',
|
||||
marginLeft: '-213px',
|
||||
top: '38px',
|
||||
minWidth: '180px',
|
||||
maxHeight: accountSelectorActive ? '300px' : '0px',
|
||||
width: '265px',
|
||||
},
|
||||
innerStyle: {
|
||||
}}
|
||||
innerStyle={{
|
||||
padding: '8px 25px',
|
||||
},
|
||||
isOpen: accountSelectorActive,
|
||||
onClickOutside: (event) => {
|
||||
}}
|
||||
isOpen={accountSelectorActive}
|
||||
onClickOutside={(event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName)
|
||||
if (accountSelectorActive && isNotToggleElement) {
|
||||
this.setState({ accountSelectorActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
...this.renderAccounts(),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
style: {
|
||||
padding: '8px 0px',
|
||||
},
|
||||
closeMenu: () => {},
|
||||
onClick: () => actions.addNewAccount(),
|
||||
},
|
||||
[
|
||||
h('span', { style: { fontSize: '16px', color: '#60db97' } }, 'Create Account'),
|
||||
],
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
style: {
|
||||
padding: '8px 0px',
|
||||
},
|
||||
closeMenu: () => {},
|
||||
onClick: () => actions.showImportPage(),
|
||||
},
|
||||
[
|
||||
h('span', {
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
marginBottom: '5px',
|
||||
color: '#60db97',
|
||||
},
|
||||
}, 'Import Account'),
|
||||
]
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
style: {
|
||||
padding: '8px 0px',
|
||||
},
|
||||
closeMenu: () => {},
|
||||
onClick: () => actions.showConnectHWWalletPage(),
|
||||
},
|
||||
[
|
||||
h('span', {
|
||||
style: {
|
||||
fontSize: '16px',
|
||||
marginBottom: '5px',
|
||||
color: '#60db97',
|
||||
},
|
||||
}, 'Connect hardware wallet'),
|
||||
]
|
||||
),
|
||||
]
|
||||
}}
|
||||
>
|
||||
{menuItems}
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
renderAccountOptions () {
|
||||
const { actions, selected, network, keyrings, identities } = this.props
|
||||
const { optionsMenuActive } = this.state
|
||||
const { optionsMenuActive, isProxy } = this.state
|
||||
|
||||
const keyring = getCurrentKeyring(selected, network, keyrings, identities)
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
style: {
|
||||
return (
|
||||
<Dropdown
|
||||
style={{
|
||||
position: 'relative',
|
||||
marginLeft: '-234px',
|
||||
minWidth: '180px',
|
||||
// marginTop: '30px',
|
||||
top: '30px',
|
||||
width: '280px',
|
||||
},
|
||||
isOpen: optionsMenuActive,
|
||||
onClickOutside: (event) => {
|
||||
}}
|
||||
isOpen={optionsMenuActive}
|
||||
onClickOutside={(event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName)
|
||||
if (optionsMenuActive && isNotToggleElement) {
|
||||
this.setState({ optionsMenuActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected, network } = this.props
|
||||
const url = ethNetProps.explorerLinks.getExplorerAccountLinkFor(selected, network)
|
||||
global.platform.openWindow({ url })
|
||||
},
|
||||
},
|
||||
`View on block explorer`,
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected, identities } = this.props
|
||||
var identity = identities[selected]
|
||||
actions.showQrView(selected, identity ? identity.name : '')
|
||||
},
|
||||
},
|
||||
'Show QR Code',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected } = this.props
|
||||
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
copyToClipboard(checkSumAddress)
|
||||
},
|
||||
},
|
||||
'Copy address to clipboard',
|
||||
),
|
||||
ifContractAcc(keyring) ? h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: async () => {
|
||||
const { selected } = this.props
|
||||
const contractProps = await this.props.actions.getContract(selected)
|
||||
const abi = contractProps && contractProps.abi
|
||||
copyToClipboard(JSON.stringify(abi))
|
||||
},
|
||||
},
|
||||
'Copy ABI to clipboard',
|
||||
) : null,
|
||||
(!this.ifHardwareAcc(keyring) && !(ifContractAcc(keyring))) ? h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
actions.requestAccountExport()
|
||||
},
|
||||
},
|
||||
'Export Private Key',
|
||||
) : null,
|
||||
]
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
closeMenu={() => {}}
|
||||
onClick={() => this.viewOnBlockExplorer()}
|
||||
>View on block explorer</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
closeMenu={() => {}}
|
||||
onClick={() => this.showQRCode()}
|
||||
>Show QR Code</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
closeMenu={() => {}}
|
||||
onClick={() => this.copyAddress()}
|
||||
>Copy address to clipboard</DropdownMenuItem>
|
||||
{ifContractAcc(keyring) ? <DropdownMenuItem
|
||||
closeMenu={() => {}}
|
||||
onClick={() => this.copyABI()}
|
||||
>Copy ABI to clipboard</DropdownMenuItem> : null}
|
||||
{isProxy ? <DropdownMenuItem
|
||||
closeMenu={() => {}}
|
||||
onClick={() => this.updateABI()}
|
||||
>Update implementation ABI</DropdownMenuItem> : null}
|
||||
{(!ifHardwareAcc(keyring) && !(ifContractAcc(keyring))) ? <DropdownMenuItem
|
||||
closeMenu={() => {}}
|
||||
onClick={() => actions.requestAccountExport()}
|
||||
>Export Private Key</DropdownMenuItem> : null}
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
viewOnBlockExplorer = () => {
|
||||
const { selected, network } = this.props
|
||||
const url = ethNetProps.explorerLinks.getExplorerAccountLinkFor(selected, network)
|
||||
global.platform.openWindow({ url })
|
||||
}
|
||||
|
||||
showQRCode = () => {
|
||||
const { selected, identities, actions } = this.props
|
||||
const identity = identities[selected]
|
||||
actions.showQrView(selected, identity ? identity.name : '')
|
||||
}
|
||||
|
||||
copyAddress = () => {
|
||||
const { selected } = this.props
|
||||
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
copyToClipboard(checkSumAddress)
|
||||
}
|
||||
|
||||
copyABI = async () => {
|
||||
const { contractProps } = this.state
|
||||
const abi = contractProps && contractProps.abi
|
||||
copyToClipboard(JSON.stringify(abi))
|
||||
}
|
||||
|
||||
updateABI = async () => {
|
||||
const { actions, selected, network } = this.props
|
||||
const { web3 } = this.state
|
||||
actions.showLoadingIndication()
|
||||
getFullABI(web3.eth, selected, network, importTypes.CONTRACT.PROXY)
|
||||
.then(finalABI => {
|
||||
actions.updateABI(selected, network, finalABI)
|
||||
.then()
|
||||
.catch(e => {
|
||||
log.debug(e)
|
||||
})
|
||||
.finally(() => actions.hideLoadingIndication())
|
||||
})
|
||||
.catch(e => {
|
||||
log.debug(e)
|
||||
actions.hideLoadingIndication()
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { style, enableAccountsSelector, enableAccountOptions } = this.props
|
||||
const { optionsMenuActive, accountSelectorActive } = this.state
|
||||
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
style: style,
|
||||
},
|
||||
[
|
||||
enableAccountsSelector && h(
|
||||
'div.accounts-selector',
|
||||
{
|
||||
style: {
|
||||
background: 'url(images/switch_acc.svg) white center center no-repeat',
|
||||
height: '25px',
|
||||
width: '25px',
|
||||
marginRight: '3px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: !accountSelectorActive,
|
||||
optionsMenuActive: false,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountSelector(),
|
||||
),
|
||||
enableAccountOptions && h(
|
||||
'div.address-dropdown.account-dropdown',
|
||||
{
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: !optionsMenuActive,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountOptions()
|
||||
),
|
||||
]
|
||||
const accountSelector = enableAccountsSelector && (
|
||||
<div
|
||||
className="accounts-selector accounts-selector-additional-style"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: !accountSelectorActive,
|
||||
optionsMenuActive: false,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{this.renderAccountSelector()}
|
||||
</div>
|
||||
)
|
||||
const accountOptions = enableAccountOptions && (
|
||||
<div
|
||||
className="address-dropdown account-dropdown"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: !optionsMenuActive,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{this.renderAccountOptions()}
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<span style={style}>
|
||||
{accountSelector}
|
||||
{accountOptions}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
getAccounts () {
|
||||
const { keyrings, network } = this.props
|
||||
const accountOrder = keyrings.reduce((list, keyring) => {
|
||||
if (ifContractAcc(keyring) && keyring.network === network) {
|
||||
list = list.concat(keyring.accounts)
|
||||
} else if (!ifContractAcc(keyring)) {
|
||||
list = list.concat(keyring.accounts)
|
||||
}
|
||||
return list
|
||||
}, [])
|
||||
return accountOrder
|
||||
}
|
||||
|
||||
checkIfProxy () {
|
||||
const { selected } = this.props
|
||||
const setProxy = true
|
||||
this.ifProxyAcc(selected, setProxy)
|
||||
.then(isProxy => {
|
||||
this.setState({isProxy})
|
||||
})
|
||||
}
|
||||
|
||||
setAllLabels () {
|
||||
const { identities, keyrings, network } = this.props
|
||||
const accountOrder = this.getAccounts()
|
||||
|
||||
accountOrder.forEach((address) => {
|
||||
const keyring = getCurrentKeyring(address, network, keyrings, identities)
|
||||
this.setLabel(keyring, address)
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.setAllLabels()
|
||||
this.checkIfProxy()
|
||||
}
|
||||
|
||||
// switch to the first account in the list on network switch, if unlocked account was contract before change
|
||||
componentDidUpdate (prevProps) {
|
||||
const { selected, keyrings } = this.props
|
||||
if (prevProps.selected !== selected) {
|
||||
this.checkIfProxy()
|
||||
this.setAllLabels()
|
||||
} else if (prevProps.keyrings.length !== keyrings.length) {
|
||||
this.setAllLabels()
|
||||
}
|
||||
if (!isNaN(this.props.network)) {
|
||||
const { selected, network, keyrings, identities } = this.props
|
||||
const { network } = this.props
|
||||
if (network !== prevProps.network) {
|
||||
const { keyrings, identities } = this.props
|
||||
const keyring = getCurrentKeyring(selected, this.props.network, keyrings, identities)
|
||||
const firstKeyring = keyrings && keyrings[0]
|
||||
const firstKeyRingAcc = firstKeyring && firstKeyring.accounts && firstKeyring.accounts[0]
|
||||
if (!keyring || (ifContractAcc(keyring) && firstKeyRingAcc)) {
|
||||
return this.props.actions.showAccountDetail(firstKeyRingAcc)
|
||||
}
|
||||
this.setAllLabels()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -396,6 +460,8 @@ AccountDropdowns.propTypes = {
|
|||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
actions: {
|
||||
showLoadingIndication: () => dispatch(actions.showLoadingIndication()),
|
||||
hideLoadingIndication: () => dispatch(actions.hideLoadingIndication()),
|
||||
showConfigPage: () => dispatch(actions.showConfigPage()),
|
||||
requestAccountExport: () => dispatch(actions.requestExportAccount()),
|
||||
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
|
||||
|
@ -404,7 +470,17 @@ const mapDispatchToProps = (dispatch) => {
|
|||
showConnectHWWalletPage: () => dispatch(actions.showConnectHWWalletPage()),
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
showDeleteImportedAccount: (identity) => dispatch(actions.showDeleteImportedAccount(identity)),
|
||||
displayWarning: (msg) => dispatch(actions.displayWarning(msg)),
|
||||
getContract: (addr) => dispatch(actions.getContract(addr)),
|
||||
setHardwareWalletDefaultHdPath: ({device, path}) => {
|
||||
return dispatch(actions.setHardwareWalletDefaultHdPath({device, path}))
|
||||
},
|
||||
connectHardwareAndUnlockAddress: (deviceName, hdPath, address) => {
|
||||
return dispatch(actions.connectHardwareAndUnlockAddress(deviceName, hdPath, address))
|
||||
},
|
||||
displayToast: (msg) => dispatch(actions.displayToast(msg)),
|
||||
hideToast: () => dispatch(actions.hideToast()),
|
||||
updateABI: (address, network, abi) => dispatch(actions.updateABI(address, network, abi)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ const exportAsFile = require('../util').exportAsFile
|
|||
const actions = require('../../../ui/app/actions')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const connect = require('react-redux').connect
|
||||
const CopyButton = require('./copyButton')
|
||||
const CopyButton = require('./copy/copy-button')
|
||||
|
||||
module.exports = connect(mapStateToProps)(ExportAccountView)
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ const emptyAddr = '0x0000000000000000000000000000000000000000'
|
|||
const SEARCH_TAB = 'SEARCH'
|
||||
const CUSTOM_TOKEN_TAB = 'CUSTOM_TOKEN'
|
||||
|
||||
const { POA_CODE, MAINNET_CODE } = require('../../../../app/scripts/controllers/network/enums')
|
||||
|
||||
class AddTokenScreen extends Component {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -93,8 +95,9 @@ class AddTokenScreen extends Component {
|
|||
const { network } = this.props
|
||||
const networkID = parseInt(network)
|
||||
let views = []
|
||||
const isProdNetwork = networkID === 1 || networkID === 99
|
||||
isProdNetwork ? views = [h(TabBar, {
|
||||
const isProdNetworkWithKnownTokens = networkID === MAINNET_CODE ||
|
||||
networkID === POA_CODE
|
||||
isProdNetworkWithKnownTokens ? views = [h(TabBar, {
|
||||
style: {
|
||||
paddingTop: '0px',
|
||||
},
|
||||
|
@ -450,7 +453,11 @@ class AddTokenScreen extends Component {
|
|||
const { symbol = '', decimals = '' } = await this.tokenInfoGetter(address)
|
||||
|
||||
const autoFilled = Boolean(symbol && decimals)
|
||||
this.setState({ autoFilled })
|
||||
this.setState({
|
||||
autoFilled,
|
||||
warning: '',
|
||||
customAddressError: null,
|
||||
})
|
||||
this.handleCustomSymbolChange(symbol || '')
|
||||
this.handleCustomDecimalsChange(decimals || '')
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@ const NetworkIndicator = require('./network')
|
|||
const {AccountDropdowns} = require('./account-dropdowns')
|
||||
const ethNetProps = require('eth-net-props')
|
||||
|
||||
const { LOCALHOST } = require('../../../app/scripts/controllers/network/enums')
|
||||
const { networks } = require('../../../app/scripts/controllers/network/util')
|
||||
|
||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
||||
|
||||
module.exports = class AppBar extends Component {
|
||||
|
@ -243,6 +246,35 @@ module.exports = class AppBar extends Component {
|
|||
const state = this.state || {}
|
||||
const isOpen = state.isNetworkMenuOpen
|
||||
|
||||
const networkDropdownItems = Object.keys(networks)
|
||||
.filter((networkID) => {
|
||||
return !isNaN(networkID)
|
||||
})
|
||||
.sort((networkID1, networkID2) => {
|
||||
const networkObj1 = networks[networkID1]
|
||||
const networkObj2 = networks[networkID2]
|
||||
return networkObj1.order - networkObj2.order
|
||||
})
|
||||
.map((networkID) => {
|
||||
const networkObj = networks[networkID]
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: networkObj.providerName,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType(networkObj.providerName)),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === networkObj.providerName ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === networkObj.providerName ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(networkID),
|
||||
]
|
||||
)
|
||||
})
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
|
@ -272,124 +304,7 @@ module.exports = class AppBar extends Component {
|
|||
},
|
||||
}, [
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'poa',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('poa')),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'poa' ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'poa' ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(99),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'dai',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('dai')),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'dai' ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'dai' ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(100),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'sokol',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('sokol')),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'sokol' ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'sokol' ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(77),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'main',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'mainnet' ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'mainnet' ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(1),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'ropsten' ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'ropsten' ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(3),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'kovan' ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'kovan' ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(42),
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'rinkeby' ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'rinkeby' ? 'div.selected-network' : ''),
|
||||
ethNetProps.props.getNetworkDisplayName(4),
|
||||
]
|
||||
),
|
||||
...networkDropdownItems,
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
|
@ -398,15 +313,15 @@ module.exports = class AppBar extends Component {
|
|||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => {
|
||||
props.dispatch(actions.setRpcTarget('http://localhost:8545'))
|
||||
props.dispatch(actions.setProviderType('localhost'))
|
||||
props.dispatch(actions.setProviderType(LOCALHOST))
|
||||
},
|
||||
style: {
|
||||
paddingLeft: '20px',
|
||||
fontSize: '16px',
|
||||
color: providerType === 'localhost' ? 'white' : '',
|
||||
color: providerType === LOCALHOST ? 'white' : '',
|
||||
},
|
||||
},
|
||||
[h(providerType === 'localhost' ? 'div.selected-network' : ''),
|
||||
[h(providerType === LOCALHOST ? 'div.selected-network' : ''),
|
||||
'Localhost 8545',
|
||||
]
|
||||
),
|
||||
|
|
|
@ -8,8 +8,10 @@ import AccountPanel from './account-panel'
|
|||
import RadioList from './custom-radio-list'
|
||||
import { getNetworkDisplayName } from '../../../app/scripts/controllers/network/util'
|
||||
import { getFaucets, getExchanges } from '../../../app/scripts/lib/buy-eth-url'
|
||||
import { MAINNET_CODE } from '../../../app/scripts/controllers/network/enums'
|
||||
import ethNetProps from 'eth-net-props'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getMetaMaskAccounts } from '../../../ui/app/selectors'
|
||||
|
||||
class BuyButtonSubview extends Component {
|
||||
render () {
|
||||
|
@ -78,7 +80,7 @@ class BuyButtonSubview extends Component {
|
|||
|
||||
default:
|
||||
return (
|
||||
<div className="flex-column" style={{ margin: '0px 0px 20px 30px' }}>
|
||||
<div className="flex-column" style={{ margin: '0px 30px 20px 30px' }}>
|
||||
{ this._getBuyOptionsView(network) }
|
||||
</div>
|
||||
)
|
||||
|
@ -157,8 +159,8 @@ class BuyButtonSubview extends Component {
|
|||
}
|
||||
|
||||
formVersionSubview () {
|
||||
const network = this.props.network
|
||||
if (network === '1') {
|
||||
const { network } = this.props
|
||||
if (Number(network) === MAINNET_CODE) {
|
||||
if (this.props.buyView.formView.coinbase) {
|
||||
return <CoinbaseForm { ...this.props } />
|
||||
} else if (this.props.buyView.formView.shapeshift) {
|
||||
|
@ -197,9 +199,10 @@ BuyButtonSubview.propTypes = {
|
|||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
return {
|
||||
identity: state.appState.identity,
|
||||
account: state.metamask.accounts[state.appState.buyView.buyAddress],
|
||||
account: accounts[state.appState.buyView.buyAddress],
|
||||
warning: state.appState.warning,
|
||||
buyView: state.appState.buyView,
|
||||
network: state.metamask.network,
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
class ConfirmScreen extends Component {
|
||||
static propTypes = {
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
renderAdditionalData: PropTypes.func,
|
||||
question: PropTypes.string.isRequired,
|
||||
withDescription: PropTypes.bool,
|
||||
description: PropTypes.string,
|
||||
onCancelClick: PropTypes.func.isRequired,
|
||||
onNoClick: PropTypes.func.isRequired,
|
||||
onYesClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div
|
||||
className="flex-column flex-grow"
|
||||
style={{
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div className="section-title flex-row flex-center">
|
||||
<i className="fa fa-arrow-left fa-lg cursor-pointer"
|
||||
onClick={() => this.props.onCancelClick()}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '30px',
|
||||
}}
|
||||
/>
|
||||
<h2 className="page-subtitle">{this.props.subtitle}</h2>
|
||||
</div>
|
||||
{this.props.withDescription ? (
|
||||
<div style={{
|
||||
margin: '0px 30px 20px',
|
||||
}}>
|
||||
<div className="error">{this.props.description}</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.props.renderAdditionalData ? this.props.renderAdditionalData() : null}
|
||||
<p className="confirm-label"
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
margin: '0px 30px 20px ',
|
||||
}}
|
||||
>{this.props.question}
|
||||
</p>
|
||||
<div className="flex-row flex-right"
|
||||
style={{
|
||||
marginRight: '30px',
|
||||
}}
|
||||
>
|
||||
<button className="btn-violet"
|
||||
onClick={() => this.props.onNoClick()}>
|
||||
No
|
||||
</button>
|
||||
<button
|
||||
onClick={() => this.props.onYesClick()}
|
||||
>
|
||||
Yes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
metamask: state.metamask,
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps)(ConfirmScreen)
|
|
@ -4,25 +4,14 @@ import ethNetProps from 'eth-net-props'
|
|||
import { default as Select } from 'react-select'
|
||||
import Button from '../../../../ui/app/components/button'
|
||||
import { capitalizeFirstLetter } from '../../../../app/scripts/lib/util'
|
||||
import { getHdPaths } from './util'
|
||||
import { LEDGER } from './enum'
|
||||
|
||||
class AccountList extends Component {
|
||||
constructor (props, context) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
getHdPaths = () => {
|
||||
return [
|
||||
{
|
||||
label: `Ledger Live`,
|
||||
value: `m/44'/60'/0'/0/0`,
|
||||
},
|
||||
{
|
||||
label: `Legacy (MEW / MyCrypto)`,
|
||||
value: `m/44'/60'/0'`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
goToNextPage = () => {
|
||||
// If we have < 5 accounts, it's restricted by BIP-44
|
||||
if (this.props.accounts.length === 5) {
|
||||
|
@ -39,7 +28,7 @@ class AccountList extends Component {
|
|||
renderHdPathSelector = () => {
|
||||
const { onPathChange, selectedPath } = this.props
|
||||
|
||||
const options = this.getHdPaths()
|
||||
const options = getHdPaths()
|
||||
return (
|
||||
<div>
|
||||
<h3 className="hw-connect__hdPath__title">Select HD Path</h3>
|
||||
|
@ -66,27 +55,34 @@ class AccountList extends Component {
|
|||
<div className="hw-connect">
|
||||
<h3 className="hw-connect">
|
||||
<h3 className="hw-connect__unlock-title">{`Unlock ${capitalizeFirstLetter(device)}`}</h3>
|
||||
{device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null}
|
||||
<p className="hw-connect__msg">Select the account to view in Nifty Wallet</p>
|
||||
{device.toLowerCase() === LEDGER ? this.renderHdPathSelector() : null}
|
||||
<p className="hw-connect__msg">Select the accounts to view in Nifty Wallet</p>
|
||||
</h3>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderInput = (a, i) => {
|
||||
const { selectedAccounts } = this.props
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
name={`selectedAccount-${i}`}
|
||||
id={`address-${i}`}
|
||||
value={a.index}
|
||||
onChange={(e) => this.props.onAccountChange(e.target.value)}
|
||||
checked={selectedAccounts.includes(a.index.toString())}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderAccounts = () => {
|
||||
const rows = []
|
||||
this.props.accounts.forEach((a, i) => {
|
||||
rows.push(
|
||||
<div className="hw-account-list__item" key={a.address}>
|
||||
<div className="hw-account-list__item__radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedAccount"
|
||||
id={`address-${i}`}
|
||||
value={a.index}
|
||||
onChange={(e) => this.props.onAccountChange(e.target.value)}
|
||||
checked={this.props.selectedAccount === a.index.toString()}
|
||||
/>
|
||||
{this.renderInput(a, i)}
|
||||
<label className="hw-account-list__item__label" htmlFor={`address-${i}`}>
|
||||
{`${a.address.slice(0, 4)}...${a.address.slice(-4)}`}
|
||||
<span
|
||||
|
@ -125,7 +121,7 @@ class AccountList extends Component {
|
|||
}
|
||||
|
||||
renderButtons = () => {
|
||||
const disabled = this.props.selectedAccount === null
|
||||
const disabled = !this.props.selectedAccount && this.props.selectedAccounts.length === 0
|
||||
const buttonProps = {}
|
||||
if (disabled) {
|
||||
buttonProps.disabled = true
|
||||
|
@ -182,6 +178,7 @@ AccountList.propTypes = {
|
|||
getPage: PropTypes.func.isRequired,
|
||||
network: PropTypes.string,
|
||||
selectedAccount: PropTypes.string,
|
||||
selectedAccounts: PropTypes.array,
|
||||
history: PropTypes.object,
|
||||
onUnlockAccount: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Button from '../../../../ui/app/components/button'
|
||||
import { LEDGER, TREZOR } from './enum'
|
||||
import { capitalizeFirstLetter } from '../../../../app/scripts/lib/util'
|
||||
|
||||
const trezorCap = capitalizeFirstLetter(TREZOR)
|
||||
const ledgerCap = capitalizeFirstLetter(LEDGER)
|
||||
|
||||
class ConnectScreen extends Component {
|
||||
constructor (props, context) {
|
||||
|
@ -10,6 +15,11 @@ class ConnectScreen extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||
browserSupported: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
connect = () => {
|
||||
if (this.state.selectedDevice) {
|
||||
this.props.connectToHardwareWallet(this.state.selectedDevice)
|
||||
|
@ -20,8 +30,8 @@ class ConnectScreen extends Component {
|
|||
renderConnectToTrezorButton () {
|
||||
return (
|
||||
<button
|
||||
className={`hw-connect__btn${this.state.selectedDevice === 'trezor' ? ' selected' : ''}`}
|
||||
onClick={_ => this.setState({selectedDevice: 'trezor'})}
|
||||
className={`hw-connect__btn${this.state.selectedDevice === TREZOR ? ' selected' : ''}`}
|
||||
onClick={_ => this.setState({selectedDevice: TREZOR})}
|
||||
>
|
||||
<img className="hw-connect__btn__img" src="images/trezor-logo.svg"/>
|
||||
</button>
|
||||
|
@ -31,8 +41,8 @@ class ConnectScreen extends Component {
|
|||
renderConnectToLedgerButton () {
|
||||
return (
|
||||
<button
|
||||
className={`hw-connect__btn${this.state.selectedDevice === 'ledger' ? ' selected' : ''}`}
|
||||
onClick={_ => this.setState({selectedDevice: 'ledger'})}
|
||||
className={`hw-connect__btn${this.state.selectedDevice === LEDGER ? ' selected' : ''}`}
|
||||
onClick={_ => this.setState({selectedDevice: LEDGER})}
|
||||
>
|
||||
<img className="hw-connect__btn__img" src="images/ledger-logo.svg"/>
|
||||
</button>
|
||||
|
@ -82,12 +92,12 @@ class ConnectScreen extends Component {
|
|||
|
||||
getAffiliateLinks () {
|
||||
const links = {
|
||||
trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=niftywallet' target='_blank'>Trezor</a>`,
|
||||
ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s' target='_blank'>Ledger</a>`,
|
||||
trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=niftywallet' target='_blank'>${trezorCap}</a>`,
|
||||
ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s' target='_blank'>${ledgerCap}</a>`,
|
||||
}
|
||||
|
||||
const text = 'Order a Trezor or Ledger and keep your funds in cold storage'
|
||||
const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger)
|
||||
const text = `Order a ${trezorCap} or ${ledgerCap} and keep your funds in cold storage`
|
||||
const response = text.replace(trezorCap, links.trezor).replace(ledgerCap, links.ledger)
|
||||
|
||||
return (
|
||||
<div className="hw-connect__get-hw__msg" dangerouslySetInnerHTML={{ __html: response }} />
|
||||
|
@ -126,10 +136,4 @@ class ConnectScreen extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
ConnectScreen.propTypes = {
|
||||
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||
browserSupported: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
module.exports = ConnectScreen
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
const LEDGER = 'ledger'
|
||||
const TREZOR = 'trezor'
|
||||
|
||||
module.exports = {
|
||||
LEDGER,
|
||||
TREZOR,
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import ConfirmScreen from '../confirm'
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../../../ui/app/actions'
|
||||
import { capitalizeFirstLetter } from '../../../../app/scripts/lib/util'
|
||||
|
||||
class ForgetDeviceScreen extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
clearAccounts: false,
|
||||
}
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
device: PropTypes.string,
|
||||
showConnectHWWalletPage: PropTypes.func,
|
||||
forgetDevice: PropTypes.func,
|
||||
goHome: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<ConfirmScreen
|
||||
subtitle="Forget device"
|
||||
question={`Are you sure you want to forget this ${capitalizeFirstLetter(this.props.device)} ?`}
|
||||
onCancelClick={() => this.props.showConnectHWWalletPage()}
|
||||
renderAdditionalData={() => {
|
||||
return (
|
||||
<div style={{
|
||||
margin: '0px 30px 20px',
|
||||
}}>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={this.state.clearAccounts}
|
||||
onChange={(e) => {
|
||||
const clearAccountsPrev = this.state.clearAccounts
|
||||
const clearAccountsNew = !clearAccountsPrev
|
||||
this.setState({clearAccounts: clearAccountsNew})
|
||||
}}
|
||||
/>
|
||||
<span style={{'paddingLeft': '5px'}}>Remove associated accounts from the list of imported accounts in the wallet</span>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
onNoClick={() => this.props.showConnectHWWalletPage()}
|
||||
onYesClick={() => {
|
||||
this.props.forgetDevice(this.props.device, this.state.clearAccounts)
|
||||
.then(_ => {
|
||||
this.setState({
|
||||
error: null,
|
||||
selectedAccount: null,
|
||||
selectedAccounts: [],
|
||||
accounts: [],
|
||||
unlocked: false,
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.setState({ error: (e.message || e.toString()) })
|
||||
})
|
||||
.finally(() => {
|
||||
this.props.goHome()
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
device: state.appState.currentView.device,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
showConnectHWWalletPage: () => dispatch(actions.showConnectHWWalletPage()),
|
||||
forgetDevice: (device, clearAccounts) => dispatch(actions.forgetDevice(device, clearAccounts)),
|
||||
goHome: () => dispatch(actions.goHome()),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(ForgetDeviceScreen)
|
|
@ -7,13 +7,17 @@ import AccountList from './account-list'
|
|||
import { formatBalance } from '../../util'
|
||||
import { getPlatform } from '../../../../app/scripts/lib/util'
|
||||
import { PLATFORM_FIREFOX } from '../../../../app/scripts/lib/enums'
|
||||
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
|
||||
import { LEDGER, TREZOR } from './enum'
|
||||
|
||||
class ConnectHardwareForm extends Component {
|
||||
constructor (props, context) {
|
||||
super(props)
|
||||
this.timerID = null
|
||||
this.state = {
|
||||
error: null,
|
||||
selectedAccount: null,
|
||||
selectedAccounts: [],
|
||||
accounts: [],
|
||||
browserSupported: true,
|
||||
unlocked: false,
|
||||
|
@ -32,13 +36,16 @@ class ConnectHardwareForm extends Component {
|
|||
this.setState({accounts: newAccounts})
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearTimeout(this.timerID)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.checkIfUnlocked()
|
||||
}
|
||||
|
||||
async checkIfUnlocked () {
|
||||
['trezor', 'ledger'].forEach(async device => {
|
||||
[TREZOR, LEDGER].forEach(async device => {
|
||||
const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device])
|
||||
if (unlocked) {
|
||||
this.setState({unlocked: true})
|
||||
|
@ -69,7 +76,21 @@ class ConnectHardwareForm extends Component {
|
|||
}
|
||||
|
||||
onAccountChange = (account) => {
|
||||
this.setState({selectedAccount: account.toString(), error: null})
|
||||
let selectedAcc = account.toString()
|
||||
const selectedAccounts = this.state.selectedAccounts
|
||||
if (!selectedAccounts.includes(selectedAcc)) {
|
||||
selectedAccounts.push(selectedAcc)
|
||||
} else {
|
||||
const indToRemove = selectedAccounts.indexOf(selectedAcc)
|
||||
selectedAccounts.splice(indToRemove, 1)
|
||||
selectedAcc = selectedAccounts[selectedAccounts.length - 1]
|
||||
}
|
||||
const newState = {
|
||||
selectedAccounts,
|
||||
selectedAccount: selectedAcc,
|
||||
error: null,
|
||||
}
|
||||
this.setState(newState)
|
||||
}
|
||||
|
||||
onAccountRestriction = () => {
|
||||
|
@ -79,7 +100,7 @@ class ConnectHardwareForm extends Component {
|
|||
showTemporaryAlert () {
|
||||
this.props.showAlert('Hardware wallet connected')
|
||||
// Autohide the alert after 5 seconds
|
||||
setTimeout(_ => {
|
||||
this.timerID = setTimeout(_ => {
|
||||
this.props.hideAlert()
|
||||
}, 5000)
|
||||
}
|
||||
|
@ -109,7 +130,6 @@ class ConnectHardwareForm extends Component {
|
|||
newState.selectedAccount = null
|
||||
}
|
||||
|
||||
|
||||
// Map accounts with balances
|
||||
newState.accounts = accounts.map(account => {
|
||||
const normalizedAddress = account.address.toLowerCase()
|
||||
|
@ -131,33 +151,33 @@ class ConnectHardwareForm extends Component {
|
|||
}
|
||||
|
||||
onForgetDevice = (device) => {
|
||||
this.props.forgetDevice(device)
|
||||
.then(_ => {
|
||||
this.setState({
|
||||
error: null,
|
||||
selectedAccount: null,
|
||||
accounts: [],
|
||||
unlocked: false,
|
||||
})
|
||||
}).catch(e => {
|
||||
this.setState({ error: (e.message || e.toString()) })
|
||||
})
|
||||
this.props.showForgetDevicePage(device)
|
||||
}
|
||||
|
||||
onUnlockAccount = (device) => {
|
||||
|
||||
if (this.state.selectedAccount === null) {
|
||||
this.setState({ error: 'You need to select an account!' })
|
||||
if (this.state.selectedAccounts.length === 0) {
|
||||
return this.setState({ error: 'You need to select an account!' })
|
||||
}
|
||||
|
||||
this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device)
|
||||
this.unlockHardwareWalletAccounts(this.state.selectedAccounts, device)
|
||||
.then(_ => {
|
||||
this.props.goHome()
|
||||
}).catch(e => {
|
||||
this.setState({ error: (e.message || e.toString()) })
|
||||
})
|
||||
}
|
||||
|
||||
unlockHardwareWalletAccounts = (accounts, device) => {
|
||||
return accounts.reduce((promise, account) => {
|
||||
return promise
|
||||
.then((result) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(this.props.unlockHardwareWalletAccount(account, device))
|
||||
})
|
||||
})
|
||||
.catch(e => this.setState({ error: (e.message || e.toString()) }))
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
onCancel = () => {
|
||||
this.props.goHome()
|
||||
}
|
||||
|
@ -185,6 +205,7 @@ class ConnectHardwareForm extends Component {
|
|||
device={this.state.device}
|
||||
accounts={this.state.accounts}
|
||||
selectedAccount={this.state.selectedAccount}
|
||||
selectedAccounts={this.state.selectedAccounts}
|
||||
onAccountChange={this.onAccountChange}
|
||||
network={this.props.network}
|
||||
getPage={this.getPage}
|
||||
|
@ -220,6 +241,7 @@ class ConnectHardwareForm extends Component {
|
|||
}
|
||||
|
||||
ConnectHardwareForm.propTypes = {
|
||||
showForgetDevicePage: PropTypes.func,
|
||||
showImportPage: PropTypes.func,
|
||||
showConnectPage: PropTypes.func,
|
||||
connectHardware: PropTypes.func,
|
||||
|
@ -239,8 +261,9 @@ ConnectHardwareForm.propTypes = {
|
|||
|
||||
const mapStateToProps = state => {
|
||||
const {
|
||||
metamask: { network, selectedAddress, identities = {}, accounts = [] },
|
||||
metamask: { network, selectedAddress, identities = {} },
|
||||
} = state
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
const numberOfExistingAccounts = Object.keys(identities).length
|
||||
const {
|
||||
appState: { defaultHdPaths },
|
||||
|
@ -269,14 +292,15 @@ const mapDispatchToProps = dispatch => {
|
|||
checkHardwareStatus: (deviceName, hdPath) => {
|
||||
return dispatch(actions.checkHardwareStatus(deviceName, hdPath))
|
||||
},
|
||||
forgetDevice: (deviceName) => {
|
||||
return dispatch(actions.forgetDevice(deviceName))
|
||||
forgetDevice: (deviceName, clearAccounts) => {
|
||||
return dispatch(actions.forgetDevice(deviceName, clearAccounts))
|
||||
},
|
||||
unlockHardwareWalletAccount: (index, deviceName, hdPath) => {
|
||||
return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath))
|
||||
},
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||
showForgetDevicePage: (device) => dispatch(actions.showForgetDevicePage(device)),
|
||||
showAlert: (msg) => dispatch(actions.showAlert(msg)),
|
||||
hideAlert: () => dispatch(actions.hideAlert()),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { LEDGER, TREZOR } from './enum'
|
||||
|
||||
function isLedger (device) {
|
||||
return device && device.toLowerCase().includes(LEDGER)
|
||||
}
|
||||
|
||||
function isTrezor (device) {
|
||||
return device && device.toLowerCase().includes(TREZOR)
|
||||
}
|
||||
|
||||
function getHdPaths () {
|
||||
return [
|
||||
{
|
||||
label: `Ledger Live`,
|
||||
value: `m/44'/60'/0'/0/0`,
|
||||
},
|
||||
{
|
||||
label: `Legacy (MEW / MyCrypto)`,
|
||||
value: `m/44'/60'/0'`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isLedger,
|
||||
isTrezor,
|
||||
getHdPaths,
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import CopyComponent from './copy-component'
|
||||
|
||||
class CopyButton extends CopyComponent {
|
||||
|
||||
// As parameters, accepts:
|
||||
// "value", which is the value to copy (mandatory)
|
||||
// "title", which is the text to show on hover (optional, defaults to 'Copy')
|
||||
render () {
|
||||
const { value, display, title, style, isWhite, tooltipPosition } = this.props
|
||||
const { copied } = this.state
|
||||
|
||||
const message = copied ? 'Copied' : title || ' Copy '
|
||||
const defaultCopyStyles = ['clipboard', 'cursor-pointer']
|
||||
const originalStyle = {
|
||||
display: display || 'flex',
|
||||
alignItems: 'center',
|
||||
}
|
||||
const fullStyle = Object.assign(originalStyle, style)
|
||||
|
||||
const tooltipChild = (
|
||||
<i
|
||||
style={{
|
||||
marginLeft: '5px',
|
||||
}}
|
||||
className={classNames(defaultCopyStyles, {white: isWhite})}
|
||||
onClick={(event) => this.onClick(event, value)}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="copy-button"
|
||||
style={fullStyle}
|
||||
>
|
||||
{this.renderTooltip(message, tooltipPosition, tooltipChild)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CopyButton
|
|
@ -0,0 +1,51 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import copyToClipboard from 'copy-to-clipboard'
|
||||
import Tooltip from '../tooltip'
|
||||
|
||||
class CopyComponent extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.timerID = null
|
||||
this.state = {
|
||||
copied: false,
|
||||
}
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
style: PropTypes.object,
|
||||
tooltipPosition: PropTypes.oneOf(['left', 'right', 'top', 'bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight']),
|
||||
}
|
||||
|
||||
onClick (event, value) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearTimeout(this.timerID)
|
||||
}
|
||||
|
||||
renderTooltip (message, position, children) {
|
||||
return (
|
||||
<Tooltip
|
||||
title={message}
|
||||
position={position}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
debounceRestore () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timerID)
|
||||
this.timerID = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CopyComponent
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react'
|
||||
import CopyComponent from './copy-component'
|
||||
|
||||
class Copyable extends CopyComponent {
|
||||
|
||||
render () {
|
||||
const { value, children } = this.props
|
||||
const { copied } = this.state
|
||||
|
||||
const message = copied ? 'Copied!' : 'Copy'
|
||||
const position = 'bottom'
|
||||
const tooltipChild = (
|
||||
<span
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={(event) => this.onClick(event, value)}
|
||||
>{children}
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
this.renderTooltip(message, position, tooltipChild)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Copyable
|
|
@ -1,62 +0,0 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const classNames = require('classnames')
|
||||
const inherits = require('util').inherits
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
|
||||
module.exports = CopyButton
|
||||
|
||||
inherits(CopyButton, Component)
|
||||
function CopyButton () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
// As parameters, accepts:
|
||||
// "value", which is the value to copy (mandatory)
|
||||
// "title", which is the text to show on hover (optional, defaults to 'Copy')
|
||||
CopyButton.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
|
||||
const value = props.value
|
||||
const display = props.display
|
||||
const copied = state.copied
|
||||
|
||||
const message = copied ? 'Copied' : props.title || ' Copy '
|
||||
const defaultCopyStyles = ['clipboard', 'cursor-pointer']
|
||||
|
||||
return h('.copy-button', {
|
||||
style: {
|
||||
display: display || 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h(Tooltip, {
|
||||
title: message,
|
||||
}, [
|
||||
h('i', {
|
||||
style: {
|
||||
marginLeft: '5px',
|
||||
},
|
||||
className: classNames(defaultCopyStyles, {white: props.isWhite}),
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
CopyButton.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
module.exports = Copyable
|
||||
|
||||
inherits(Copyable, Component)
|
||||
function Copyable () {
|
||||
Component.call(this)
|
||||
this.state = {
|
||||
copied: false,
|
||||
}
|
||||
}
|
||||
|
||||
Copyable.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
const { value, children } = props
|
||||
const { copied } = state
|
||||
|
||||
return h(Tooltip, {
|
||||
title: copied ? 'Copied!' : 'Copy',
|
||||
position: 'bottom',
|
||||
}, h('span', {
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}, children))
|
||||
}
|
||||
|
||||
Copyable.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
|
@ -1,10 +1,28 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
import ConfirmScreen from './confirm'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../../ui/app/actions'
|
||||
|
||||
module.exports = connect(mapStateToProps)(DeleteImportedAccount)
|
||||
class DeleteImportedAccount extends ConfirmScreen {
|
||||
render () {
|
||||
return (
|
||||
<ConfirmScreen
|
||||
subtitle="Delete Imported Account"
|
||||
withDescription={true}
|
||||
description="Be sure, that you saved a private key or JSON keystore file of this account in a safe place. Otherwise, you will not be able to restore this account."
|
||||
question={`Are you sure to delete imported ${this.props.identity.name} (${this.props.identity.address})?`}
|
||||
onCancelClick={() => this.props.dispatch(actions.showConfigPage())}
|
||||
onNoClick={() => this.props.dispatch(actions.showConfigPage())}
|
||||
onYesClick={() => {
|
||||
this.props.dispatch(actions.removeAccount(this.props.identity.address, this.props.metamask.network))
|
||||
.then(() => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
|
@ -14,67 +32,4 @@ function mapStateToProps (state) {
|
|||
}
|
||||
}
|
||||
|
||||
inherits(DeleteImportedAccount, Component)
|
||||
function DeleteImportedAccount () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
DeleteImportedAccount.prototype.render = function () {
|
||||
return h('.flex-column.flex-grow', {
|
||||
style: {
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
},
|
||||
}, [
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '30px',
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Delete Imported Account'),
|
||||
]),
|
||||
h('div', {
|
||||
style: {
|
||||
margin: '0px 30px 20px',
|
||||
},
|
||||
},
|
||||
h('.error', 'Be sure, that you saved a private key or JSON keystore file of this account in a safe place. Otherwise, you will not be able to restore this account.'),
|
||||
),
|
||||
h('p.confirm-label', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
margin: '0px 30px 20px ',
|
||||
},
|
||||
},
|
||||
`Are you sure to delete imported ${this.props.identity.name} (${this.props.identity.address})?`),
|
||||
h('.flex-row.flex-right', {
|
||||
style: {
|
||||
marginRight: '30px',
|
||||
},
|
||||
}, [
|
||||
h('button.btn-violet',
|
||||
{
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
},
|
||||
},
|
||||
'No'),
|
||||
h('button',
|
||||
{
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.removeAccount(this.props.identity.address, this.props.metamask.network))
|
||||
.then(() => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
})
|
||||
},
|
||||
},
|
||||
'Yes'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
module.exports = connect(mapStateToProps)(DeleteImportedAccount)
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
import ConfirmScreen from './confirm'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../../ui/app/actions'
|
||||
|
||||
module.exports = connect(mapStateToProps)(DeleteRpc)
|
||||
class DeleteRpc extends ConfirmScreen {
|
||||
render () {
|
||||
return (
|
||||
<ConfirmScreen
|
||||
subtitle="Delete Custom RPC"
|
||||
question={`Are you sure to delete ${this.props.url} ?`}
|
||||
onCancelClick={() => this.props.dispatch(actions.showConfigPage())}
|
||||
onNoClick={() => this.props.dispatch(actions.showConfigPage())}
|
||||
onYesClick={() => {
|
||||
this.props.dispatch(actions.removeCustomRPC(this.props.url, this.props.provider))
|
||||
.then(() => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
|
@ -14,60 +30,4 @@ function mapStateToProps (state) {
|
|||
}
|
||||
}
|
||||
|
||||
inherits(DeleteRpc, Component)
|
||||
function DeleteRpc () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
DeleteRpc.prototype.render = function () {
|
||||
return h('.flex-column.flex-grow', {
|
||||
style: {
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
},
|
||||
}, [
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '30px',
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Delete Custom RPC'),
|
||||
]),
|
||||
h('p.confirm-label', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
margin: '0px 30px 20px ',
|
||||
},
|
||||
},
|
||||
`Are you sure to delete ${this.props.url} ?`),
|
||||
h('.flex-row.flex-right', {
|
||||
style: {
|
||||
marginRight: '30px',
|
||||
},
|
||||
}, [
|
||||
h('button.btn-violet',
|
||||
{
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
},
|
||||
},
|
||||
'No'),
|
||||
h('button',
|
||||
{
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.removeCustomRPC(this.props.url, this.props.provider))
|
||||
.then(() => {
|
||||
this.props.dispatch(actions.showConfigPage())
|
||||
})
|
||||
},
|
||||
},
|
||||
'Yes'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
module.exports = connect(mapStateToProps)(DeleteRpc)
|
||||
|
|
|
@ -94,7 +94,7 @@ class DropdownMenuItem extends Component {
|
|||
}
|
||||
|
||||
DropdownMenuItem.propTypes = {
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
closeMenu: PropTypes.func,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
|
|
|
@ -8,6 +8,7 @@ const networkMap = require('ethjs-ens/lib/network-map.json')
|
|||
const ensRE = /.+\..+$/
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
const log = require('loglevel')
|
||||
const { isValidENSAddress } = require('../util')
|
||||
|
||||
|
||||
module.exports = EnsInput
|
||||
|
@ -31,6 +32,7 @@ EnsInput.prototype.render = function () {
|
|||
loadingEns: false,
|
||||
ensResolution: null,
|
||||
ensFailure: null,
|
||||
toError: null,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -103,17 +105,27 @@ EnsInput.prototype.lookupEnsName = function () {
|
|||
nickname: recipient.trim(),
|
||||
hoverText: address + '\nClick to Copy',
|
||||
ensFailure: false,
|
||||
toError: null,
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error(reason)
|
||||
return this.setState({
|
||||
const setStateObj = {
|
||||
loadingEns: false,
|
||||
ensResolution: ZERO_ADDRESS,
|
||||
ensResolution: recipient,
|
||||
ensFailure: true,
|
||||
hoverText: reason.message,
|
||||
})
|
||||
toError: null,
|
||||
}
|
||||
if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') {
|
||||
setStateObj.hoverText = 'ENS name not found'
|
||||
setStateObj.toError = 'ensNameNotFound'
|
||||
setStateObj.ensFailure = false
|
||||
} else {
|
||||
log.error(reason)
|
||||
setStateObj.hoverText = reason.message
|
||||
}
|
||||
|
||||
return this.setState(setStateObj)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -125,7 +137,7 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
|
|||
const nickname = state.nickname || ' '
|
||||
if (prevState && ensResolution && this.props.onChange &&
|
||||
ensResolution !== prevState.ensResolution) {
|
||||
this.props.onChange(ensResolution, nickname)
|
||||
this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,14 +147,17 @@ EnsInput.prototype.ensIcon = function (recipient) {
|
|||
title: hoverText,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
padding: '9px',
|
||||
padding: '6px 0px',
|
||||
right: '0px',
|
||||
transform: 'translatex(-40px)',
|
||||
},
|
||||
}, this.ensIconContents(recipient))
|
||||
}
|
||||
|
||||
EnsInput.prototype.ensIconContents = function (recipient) {
|
||||
const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS}
|
||||
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS}
|
||||
|
||||
if (toError) return
|
||||
|
||||
if (loadingEns) {
|
||||
return h('img', {
|
||||
|
@ -151,17 +166,26 @@ EnsInput.prototype.ensIconContents = function (recipient) {
|
|||
width: '30px',
|
||||
height: '30px',
|
||||
transform: 'translateY(-6px)',
|
||||
marginRight: '-5px',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (ensFailure) {
|
||||
return h('i.fa.fa-warning.fa-lg.warning')
|
||||
return h('i.fa.fa-warning.fa-lg.warning', {
|
||||
style: {
|
||||
color: '#df2265',
|
||||
background: 'white',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (ensResolution && (ensResolution !== ZERO_ADDRESS)) {
|
||||
return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', {
|
||||
style: { color: 'green' },
|
||||
style: {
|
||||
color: '#60db97',
|
||||
background: 'white',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../../ui/app/actions'
|
||||
|
||||
class SendError extends Component {
|
||||
class ErrorComponent extends Component {
|
||||
static propTypes = {
|
||||
error: PropTypes.string,
|
||||
onClose: PropTypes.func,
|
||||
hideWarning: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
|
@ -29,7 +31,7 @@ class SendError extends Component {
|
|||
height: '16px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={(e) => this.props.onClose(e)}
|
||||
onClick={(e) => this.props.hideWarning()}
|
||||
/>
|
||||
<div style={{
|
||||
marginLeft: '30px',
|
||||
|
@ -47,4 +49,10 @@ class SendError extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = SendError
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideWarning: () => dispatch(actions.hideWarning()),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(null, mapDispatchToProps)(ErrorComponent)
|
|
@ -1,15 +1,16 @@
|
|||
import React, { Component } from 'react'
|
||||
import { formatBalance, countSignificantDecimals } from '../util'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DAI_CODE, POA_SOKOL_CODE, GOERLI_TESTNET_CODE } from '../../../app/scripts/controllers/network/enums'
|
||||
|
||||
class FiatValue extends Component {
|
||||
render = () => {
|
||||
const props = this.props
|
||||
let { conversionRate } = props
|
||||
const { currentCurrency, network } = props
|
||||
const isSokol = parseInt(network) === 77
|
||||
const isDai = parseInt(network) === 100
|
||||
if (isSokol) {
|
||||
const isTestnet = parseInt(network) === POA_SOKOL_CODE || parseInt(network) === GOERLI_TESTNET_CODE
|
||||
const isDai = parseInt(network) === DAI_CODE
|
||||
if (isTestnet) {
|
||||
conversionRate = 0
|
||||
} else if (isDai) {
|
||||
conversionRate = 1
|
||||
|
|
|
@ -2,15 +2,7 @@ const Component = require('react').Component
|
|||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethNetProps = require('eth-net-props')
|
||||
const {
|
||||
DROPDOWN_ROPSTEN_DISPLAY_NAME,
|
||||
DROPDOWN_RINKEBY_DISPLAY_NAME,
|
||||
DROPDOWN_KOVAN_DISPLAY_NAME,
|
||||
DROPDOWN_POA_SOKOL_DISPLAY_NAME,
|
||||
DROPDOWN_POA_DISPLAY_NAME,
|
||||
DROPDOWN_DAI_DISPLAY_NAME,
|
||||
DROPDOWN_MAINNET_DISPLAY_NAME,
|
||||
} = require('../../../app/scripts/controllers/network/enums')
|
||||
const { networks } = require('../../../app/scripts/controllers/network/util')
|
||||
|
||||
module.exports = Network
|
||||
|
||||
|
@ -23,12 +15,6 @@ function Network () {
|
|||
Network.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { provider, network: networkNumber } = props
|
||||
let providerName
|
||||
try {
|
||||
providerName = provider.type
|
||||
} catch (e) {
|
||||
providerName = null
|
||||
}
|
||||
let displayName, hoverText
|
||||
|
||||
if (networkNumber === 'loading') {
|
||||
|
@ -51,26 +37,8 @@ Network.prototype.render = function () {
|
|||
h('i.fa.fa-caret-down'),
|
||||
])
|
||||
} else {
|
||||
if (providerName === 'mainnet' || parseInt(networkNumber) === 1) {
|
||||
displayName = DROPDOWN_MAINNET_DISPLAY_NAME
|
||||
hoverText = ethNetProps.props.getNetworkDisplayName(networkNumber)
|
||||
} else if (providerName === 'ropsten' || parseInt(networkNumber) === 3) {
|
||||
displayName = DROPDOWN_ROPSTEN_DISPLAY_NAME
|
||||
hoverText = ethNetProps.props.getNetworkDisplayName(networkNumber)
|
||||
} else if (providerName === 'sokol' || parseInt(networkNumber) === 77) {
|
||||
displayName = DROPDOWN_POA_SOKOL_DISPLAY_NAME
|
||||
hoverText = ethNetProps.props.getNetworkDisplayName(networkNumber)
|
||||
} else if (providerName === 'kovan' || parseInt(networkNumber) === 42) {
|
||||
displayName = DROPDOWN_KOVAN_DISPLAY_NAME
|
||||
hoverText = ethNetProps.props.getNetworkDisplayName(networkNumber)
|
||||
} else if (providerName === 'rinkeby' || parseInt(networkNumber) === 4) {
|
||||
displayName = DROPDOWN_RINKEBY_DISPLAY_NAME
|
||||
hoverText = ethNetProps.props.getNetworkDisplayName(networkNumber)
|
||||
} else if (providerName === 'poa' || parseInt(networkNumber) === 99) {
|
||||
displayName = DROPDOWN_POA_DISPLAY_NAME
|
||||
hoverText = ethNetProps.props.getNetworkDisplayName(networkNumber)
|
||||
} else if (providerName === 'dai' || parseInt(networkNumber) === 100) {
|
||||
displayName = DROPDOWN_DAI_DISPLAY_NAME
|
||||
if (networkNumber && networks[networkNumber]) {
|
||||
displayName = networks[networkNumber].displayNameDropdown
|
||||
hoverText = ethNetProps.props.getNetworkDisplayName(networkNumber)
|
||||
} else {
|
||||
displayName = 'Private Network'
|
||||
|
|
|
@ -10,7 +10,7 @@ const BN = ethUtil.BN
|
|||
const hexToBn = require('../../../app/scripts/lib/hex-to-bn')
|
||||
const util = require('../util')
|
||||
const MiniAccountPanel = require('./mini-account-panel')
|
||||
const Copyable = require('./copyable')
|
||||
const Copyable = require('./copy/copyable')
|
||||
const EthBalance = require('./eth-balance')
|
||||
const TokenBalance = require('./token-balance')
|
||||
const addressSummary = util.addressSummary
|
||||
|
@ -24,13 +24,16 @@ const connect = require('react-redux').connect
|
|||
const abiDecoder = require('abi-decoder')
|
||||
const { tokenInfoGetter, calcTokenAmount } = require('../../../ui/app/token-util')
|
||||
const BigNumber = require('bignumber.js')
|
||||
const ethNetProps = require('eth-net-props')
|
||||
import { getMetaMaskAccounts } from '../../../ui/app/selectors'
|
||||
import ToastComponent from './toast'
|
||||
|
||||
const MIN_GAS_PRICE_BN = new BN('0')
|
||||
const MIN_GAS_LIMIT_BN = new BN('21000')
|
||||
|
||||
module.exports = connect(mapStateToProps)(PendingTx)
|
||||
inherits(PendingTx, Component)
|
||||
function PendingTx () {
|
||||
function PendingTx (props) {
|
||||
Component.call(this)
|
||||
this.state = {
|
||||
valid: true,
|
||||
|
@ -39,14 +42,16 @@ function PendingTx () {
|
|||
tokenSymbol: '',
|
||||
tokenDecimals: 0,
|
||||
tokenDataRetrieved: false,
|
||||
coinName: ethNetProps.props.getNetworkCoinName(props.network),
|
||||
}
|
||||
this.tokenInfoGetter = tokenInfoGetter()
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
return {
|
||||
identities: state.metamask.identities,
|
||||
accounts: state.metamask.accounts,
|
||||
accounts,
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
||||
|
@ -164,6 +169,9 @@ PendingTx.prototype.render = function () {
|
|||
h('div', {
|
||||
key: txMeta.id,
|
||||
}, [
|
||||
h(ToastComponent, {
|
||||
isSuccess: false,
|
||||
}),
|
||||
|
||||
h('form#pending-tx-form', {
|
||||
onSubmit: this.onSubmit.bind(this),
|
||||
|
@ -504,7 +512,7 @@ PendingTx.prototype.render = function () {
|
|||
}, 'Reset'),
|
||||
|
||||
// Accept Button or Buy Button
|
||||
insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
|
||||
insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, `Buy ${this.state.coinName}`) :
|
||||
h('input.confirm', {
|
||||
type: 'submit',
|
||||
value: 'Submit',
|
||||
|
|
|
@ -4,9 +4,10 @@ import { connect } from 'react-redux'
|
|||
import SendProfile from './send-profile'
|
||||
import ExecutorCell from './executor-cell'
|
||||
import SendHeader from './send-header'
|
||||
import SendError from './send-error'
|
||||
import ErrorComponent from '../error'
|
||||
import actions from '../../../../ui/app/actions'
|
||||
import { ifContractAcc } from '../../util'
|
||||
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
|
||||
import Web3 from 'web3'
|
||||
|
||||
const ownerABI = [{
|
||||
|
@ -73,12 +74,7 @@ class ChooseContractExecutor extends Component {
|
|||
<div className="send-screen flex-column flex-grow">
|
||||
<SendProfile />
|
||||
<SendHeader title="Choose contract executor" back={() => this.back()} />
|
||||
<SendError
|
||||
error={error}
|
||||
onClose={() => {
|
||||
this.props.hideWarning()
|
||||
}}
|
||||
/>
|
||||
<ErrorComponent error={error} />
|
||||
<div style={{ padding: '0 30px' }}>
|
||||
<span className="hw-connect__header__msg">Contract transaction will be executed from selected account</span>
|
||||
</div>
|
||||
|
@ -237,9 +233,10 @@ class ChooseContractExecutor extends Component {
|
|||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
const result = {
|
||||
selected: state.metamask.selectedAddress,
|
||||
accounts: state.metamask.accounts,
|
||||
accounts,
|
||||
keyrings: state.metamask.keyrings,
|
||||
identities: state.metamask.identities,
|
||||
warning: state.appState.warning,
|
||||
|
|
|
@ -4,12 +4,14 @@ import { connect } from 'react-redux'
|
|||
import PersistentForm from '../../../lib/persistent-form'
|
||||
import SendProfile from './send-profile'
|
||||
import SendHeader from './send-header'
|
||||
import SendError from './send-error'
|
||||
import ErrorComponent from '../error'
|
||||
import ToastComponent from '../toast'
|
||||
import Select from 'react-select'
|
||||
import actions from '../../../../ui/app/actions'
|
||||
import abiEncoder from 'web3-eth-abi'
|
||||
import Web3 from 'web3'
|
||||
import copyToClipboard from 'copy-to-clipboard'
|
||||
import CopyButton from '../copy/copy-button'
|
||||
|
||||
class SendTransactionField extends Component {
|
||||
constructor (props) {
|
||||
|
@ -31,26 +33,49 @@ class SendTransactionField extends Component {
|
|||
onChange: PropTypes.func,
|
||||
}
|
||||
|
||||
generateAttributes () {
|
||||
return {
|
||||
placeholder: this.props.placeholder,
|
||||
value: this.state.val,
|
||||
disabled: this.props.disabled,
|
||||
onChange: (e) => {
|
||||
this.setState({
|
||||
val: e.target.value,
|
||||
})
|
||||
this.props.onChange(e.target.value)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SendTransactionTextField extends SendTransactionField {
|
||||
render () {
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
<input type="text"
|
||||
{...this.generateAttributes()}
|
||||
className="input large-input output"
|
||||
placeholder={this.props.placeholder}
|
||||
value={this.state.val}
|
||||
disabled={this.props.disabled}
|
||||
onChange={(e) => {
|
||||
this.setState({
|
||||
val: e.target.value,
|
||||
})
|
||||
this.props.onChange(e.target.value)
|
||||
}}
|
||||
style={{ marginTop: '5px' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SendTransactionTextArea extends SendTransactionField {
|
||||
render () {
|
||||
return (
|
||||
<textarea
|
||||
{...this.generateAttributes()}
|
||||
style={{
|
||||
marginTop: '5px',
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
padding: '10px',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SendTransactionInputSelect extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
@ -109,15 +134,9 @@ class SendTransactionScreen extends PersistentForm {
|
|||
copyDisabled: true,
|
||||
}
|
||||
|
||||
this.timerID = null
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.props.hideToast()
|
||||
clearTimeout(this.timerID)
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.getContractMethods()
|
||||
}
|
||||
|
@ -132,11 +151,8 @@ class SendTransactionScreen extends PersistentForm {
|
|||
<div className="send-screen flex-column flex-grow">
|
||||
<SendProfile />
|
||||
<SendHeader title="Execute Method" />
|
||||
<SendError
|
||||
error={error}
|
||||
onClose={() => { this.props.hideWarning() }}
|
||||
/>
|
||||
{this.props.toastMsg ? <div className="toast">{this.props.toastMsg}</div> : null}
|
||||
<ErrorComponent error={error} />
|
||||
<ToastComponent isSuccess={true} />
|
||||
<div style={{ padding: '0 30px' }}>
|
||||
<Select
|
||||
clearable={false}
|
||||
|
@ -206,15 +222,17 @@ class SendTransactionScreen extends PersistentForm {
|
|||
style={{ marginTop: '10px' }}
|
||||
>
|
||||
{params.name || `${paramName} ${ind + 1}`}
|
||||
{!isInput ? <i
|
||||
className="clipboard cursor-pointer"
|
||||
style={{ marginLeft: '10px' }}
|
||||
onClick={(e) => { copyToClipboard(defaultValue) }}
|
||||
{!isInput ? <CopyButton
|
||||
value={defaultValue}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginLeft: '5px',
|
||||
}}
|
||||
/> : null}
|
||||
</h3>
|
||||
)
|
||||
// bytes field is not mandatory to fill: 0x is by default
|
||||
if (params.type.startsWith('bytes') && !Array.isArray(params.type)) {
|
||||
if (params.type.startsWith('bytes') && !Array.isArray(params.type) && isInput) {
|
||||
const inputValues = this.props.inputValues || {}
|
||||
if (!inputValues[ind]) {
|
||||
inputValues[ind] = '0x'
|
||||
|
@ -224,24 +242,27 @@ class SendTransactionScreen extends PersistentForm {
|
|||
}
|
||||
}
|
||||
let field
|
||||
const allTypesProps = {
|
||||
ind,
|
||||
defaultValue,
|
||||
disabled: !isInput,
|
||||
onChange: val => isInput ? this.handleInputChange(val, params.type, ind) : null,
|
||||
}
|
||||
const textTypeProps = {
|
||||
key: Math.random(),
|
||||
placeholder: params.type,
|
||||
}
|
||||
if (params.type === 'bool' && isInput) {
|
||||
field = (
|
||||
<SendTransactionInputSelect
|
||||
ind={ind}
|
||||
defaultValue={defaultValue}
|
||||
onChange={val => this.handleInputChange(val, params.type, ind)}
|
||||
/>
|
||||
<SendTransactionInputSelect {...allTypesProps} />
|
||||
)
|
||||
} else if (params.type.includes('[]') && !isInput) {
|
||||
field = (
|
||||
<SendTransactionTextArea {...allTypesProps} {...textTypeProps} />
|
||||
)
|
||||
} else {
|
||||
field = (
|
||||
<SendTransactionField
|
||||
key={Math.random()}
|
||||
ind={ind}
|
||||
disabled={!isInput}
|
||||
placeholder={params.type}
|
||||
defaultValue={defaultValue}
|
||||
onChange={val => isInput ? this.handleInputChange(val, params.type, ind) : null}
|
||||
/>
|
||||
<SendTransactionTextField {...allTypesProps} {...textTypeProps} />
|
||||
)
|
||||
}
|
||||
const fieldObj = (
|
||||
|
@ -387,7 +408,6 @@ class SendTransactionScreen extends PersistentForm {
|
|||
}
|
||||
|
||||
setOutputValue = (val, type) => {
|
||||
console.log(val)
|
||||
if (!type) {
|
||||
return val || ''
|
||||
}
|
||||
|
@ -423,9 +443,6 @@ class SendTransactionScreen extends PersistentForm {
|
|||
if (txData) {
|
||||
copyToClipboard(txData)
|
||||
this.props.displayToast('Contract ABI encoded method call has been successfully copied to clipboard')
|
||||
this.timerID = setTimeout(() => {
|
||||
this.props.hideToast()
|
||||
}, 4000)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,7 +470,6 @@ function mapStateToProps (state) {
|
|||
const result = {
|
||||
address: state.metamask.selectedAddress,
|
||||
warning: state.appState.warning,
|
||||
toastMsg: state.appState.toastMsg,
|
||||
methodSelected: contractAcc && contractAcc.methodSelected,
|
||||
methodABI: contractAcc && contractAcc.methodABI,
|
||||
inputValues: contractAcc && contractAcc.inputValues,
|
||||
|
|
|
@ -4,6 +4,7 @@ import Identicon from '../identicon'
|
|||
import { addressSummary } from '../../util'
|
||||
import EthBalance from '../eth-balance'
|
||||
import TokenBalance from '../token-balance'
|
||||
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
|
||||
|
||||
class SendProfile extends Component {
|
||||
render () {
|
||||
|
@ -77,9 +78,10 @@ class SendProfile extends Component {
|
|||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
var result = {
|
||||
address: state.metamask.selectedAddress,
|
||||
accounts: state.metamask.accounts,
|
||||
accounts,
|
||||
identities: state.metamask.identities,
|
||||
network: state.metamask.network,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
|
|
|
@ -1,31 +1,284 @@
|
|||
const inherits = require('util').inherits
|
||||
const PersistentForm = require('../../../lib/persistent-form')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
const {
|
||||
import React from 'react'
|
||||
import PersistentForm from '../../../lib/persistent-form'
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../../../ui/app/actions'
|
||||
import {
|
||||
numericBalance,
|
||||
isInvalidChecksumAddress,
|
||||
isValidAddress,
|
||||
} = require('../../util')
|
||||
const EnsInput = require('../ens-input')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const { tokenInfoGetter, calcTokenAmountWithDec } = require('../../../../ui/app/token-util')
|
||||
const TokenTracker = require('eth-token-watcher')
|
||||
const Loading = require('../loading')
|
||||
const BigNumber = require('bignumber.js')
|
||||
} from '../../util'
|
||||
import EnsInput from '../ens-input'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { tokenInfoGetter, calcTokenAmountWithDec } from '../../../../ui/app/token-util'
|
||||
import TokenTracker from 'eth-token-watcher'
|
||||
import Loading from '../loading'
|
||||
import BigNumber from 'bignumber.js'
|
||||
BigNumber.config({ ERRORS: false })
|
||||
const log = require('loglevel')
|
||||
import log from 'loglevel'
|
||||
import SendProfile from './send-profile'
|
||||
import SendHeader from './send-header'
|
||||
import SendError from './send-error'
|
||||
import ErrorComponent from '../error'
|
||||
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
|
||||
|
||||
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
||||
class SendTransactionScreen extends PersistentForm {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
token: {
|
||||
address: '',
|
||||
symbol: '',
|
||||
balance: 0,
|
||||
decimals: 0,
|
||||
},
|
||||
amount: '',
|
||||
isLoading: true,
|
||||
}
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
render () {
|
||||
const { isLoading, token, amount } = this.state
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Loading isLoading={isLoading} loadingMessage="Loading..." />
|
||||
)
|
||||
}
|
||||
this.persistentFormParentId = 'send-tx-form'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
var result = {
|
||||
const props = this.props
|
||||
const {
|
||||
network,
|
||||
identities,
|
||||
addressBook,
|
||||
error,
|
||||
} = props
|
||||
const nextDisabled = token.balance <= 0
|
||||
|
||||
return (
|
||||
|
||||
<div className="send-screen flex-column flex-grow">
|
||||
<SendProfile isToken={true} token={token} />
|
||||
<SendHeader title={`Send ${this.state.token.symbol} Tokens`} />
|
||||
<ErrorComponent error={error} />
|
||||
<section className="flex-row flex-center">
|
||||
<EnsInput
|
||||
name="address"
|
||||
placeholder="Recipient Address"
|
||||
onChange={() => this.recipientDidChange.bind(this)}
|
||||
network={network}
|
||||
identities={identities}
|
||||
addressBook={addressBook}
|
||||
/>
|
||||
</section>
|
||||
<section className="flex-row flex-center">
|
||||
<input className="large-input"
|
||||
name="amount"
|
||||
value={amount}
|
||||
onChange={(e) => this.amountDidChange(e.target.value)}
|
||||
placeholder="Amount"
|
||||
type="number"
|
||||
style={{
|
||||
marginRight: '6px',
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => this.onSubmit()}
|
||||
disabled={nextDisabled}
|
||||
>Next
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.getTokensMetadata()
|
||||
.then(() => {
|
||||
this.createFreshTokenTracker()
|
||||
})
|
||||
}
|
||||
|
||||
async getTokensMetadata () {
|
||||
this.setState({isLoading: true})
|
||||
this.tokenInfoGetter = tokenInfoGetter()
|
||||
const { tokenAddress, network } = this.props
|
||||
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(tokenAddress)
|
||||
this.setState({
|
||||
token: {
|
||||
address: tokenAddress,
|
||||
network,
|
||||
symbol,
|
||||
decimals,
|
||||
},
|
||||
})
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.props.displayWarning()
|
||||
if (!this.tracker) return
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
createFreshTokenTracker () {
|
||||
this.setState({isLoading: true})
|
||||
const { address, tokenAddress } = this.props
|
||||
if (!isValidAddress(tokenAddress)) return
|
||||
if (this.tracker) {
|
||||
// Clean up old trackers when refreshing:
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
if (!global.ethereumProvider) return
|
||||
|
||||
this.tracker = new TokenTracker({
|
||||
userAddress: address,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: [this.state.token],
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
|
||||
// Set up listener instances for cleaning up
|
||||
this.balanceUpdater = this.updateBalances.bind(this)
|
||||
this.showError = (error) => {
|
||||
this.setState({ error, isLoading: false })
|
||||
}
|
||||
this.tracker.on('update', this.balanceUpdater)
|
||||
this.tracker.on('error', this.showError)
|
||||
|
||||
this.tracker.updateBalances()
|
||||
.then(() => {
|
||||
this.updateBalances(this.tracker.serialize())
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error(`Problem updating balances`, reason)
|
||||
this.setState({ isLoading: false })
|
||||
})
|
||||
}
|
||||
|
||||
updateBalances (tokens) {
|
||||
if (!this.tracker.running) {
|
||||
return
|
||||
}
|
||||
this.setState({ token: (tokens && tokens[0]), isLoading: false })
|
||||
}
|
||||
|
||||
recipientDidChange (recipient, nickname) {
|
||||
this.setState({
|
||||
recipient,
|
||||
nickname,
|
||||
})
|
||||
}
|
||||
|
||||
amountDidChange (amount) {
|
||||
this.setState({
|
||||
amount,
|
||||
})
|
||||
}
|
||||
|
||||
async onSubmit () {
|
||||
const state = this.state || {}
|
||||
const { token, amount } = state
|
||||
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||
let nickname = state.nickname || ' '
|
||||
if (typeof recipient === 'object') {
|
||||
if (recipient.toAddress) {
|
||||
recipient = recipient.toAddress
|
||||
}
|
||||
if (recipient.nickname) {
|
||||
nickname = recipient.nickname
|
||||
}
|
||||
}
|
||||
const parts = amount.split('.')
|
||||
|
||||
let message
|
||||
|
||||
if (isNaN(amount) || amount === '') {
|
||||
message = 'Invalid token\'s amount.'
|
||||
return this.props.displayWarning(message)
|
||||
}
|
||||
|
||||
if (parts[1]) {
|
||||
const decimal = parts[1]
|
||||
if (decimal.length > 18) {
|
||||
message = 'Token\'s amount is too precise.'
|
||||
return this.props.displayWarning(message)
|
||||
}
|
||||
}
|
||||
|
||||
const tokenAddress = ethUtil.addHexPrefix(token.address)
|
||||
const tokensValueWithoutDec = new BigNumber(amount)
|
||||
const tokensValueWithDec = new BigNumber(calcTokenAmountWithDec(amount, token.decimals))
|
||||
|
||||
if (tokensValueWithDec.gt(token.balance)) {
|
||||
message = 'Insufficient token\'s balance.'
|
||||
return this.props.displayWarning(message)
|
||||
}
|
||||
|
||||
if (amount < 0) {
|
||||
message = 'Can not send negative amounts of ETH.'
|
||||
return this.props.displayWarning(message)
|
||||
}
|
||||
|
||||
if ((isInvalidChecksumAddress(recipient))) {
|
||||
message = 'Recipient address checksum is invalid.'
|
||||
return this.props.displayWarning(message)
|
||||
}
|
||||
|
||||
if (!isValidAddress(recipient) || (!recipient)) {
|
||||
message = 'Recipient address is invalid.'
|
||||
return this.props.displayWarning(message)
|
||||
}
|
||||
|
||||
this.props.hideWarning()
|
||||
|
||||
this.props.addToAddressBook(recipient, nickname)
|
||||
|
||||
const txParams = {
|
||||
from: this.props.address,
|
||||
value: '0x',
|
||||
}
|
||||
|
||||
const toAddress = ethUtil.addHexPrefix(recipient)
|
||||
|
||||
txParams.to = tokenAddress
|
||||
|
||||
const tokensAmount = `0x${amount.toString(16)}`
|
||||
const encoded = this.generateTokenTransferData({toAddress, amount: tokensAmount})
|
||||
txParams.data = encoded
|
||||
|
||||
const confTxScreenParams = {
|
||||
isToken: true,
|
||||
tokenSymbol: token.symbol,
|
||||
tokensToSend: tokensValueWithoutDec,
|
||||
tokensTransferTo: toAddress,
|
||||
}
|
||||
|
||||
this.props.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams)
|
||||
}
|
||||
|
||||
generateTokenTransferData ({ toAddress = '0x0', amount = '0x0' }) {
|
||||
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
|
||||
const abi = require('ethereumjs-abi')
|
||||
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||
abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]),
|
||||
x => ('00' + x.toString(16)).slice(-2)
|
||||
).join('')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
const result = {
|
||||
address: state.metamask.selectedAddress,
|
||||
accounts: state.metamask.accounts,
|
||||
accounts,
|
||||
identities: state.metamask.identities,
|
||||
warning: state.appState.warning,
|
||||
network: state.metamask.network,
|
||||
|
@ -41,269 +294,20 @@ function mapStateToProps (state) {
|
|||
return result
|
||||
}
|
||||
|
||||
inherits(SendTransactionScreen, PersistentForm)
|
||||
function SendTransactionScreen () {
|
||||
this.state = {
|
||||
token: {
|
||||
address: '',
|
||||
symbol: '',
|
||||
balance: 0,
|
||||
decimals: 0,
|
||||
},
|
||||
isLoading: true,
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
showAccountsPage: () => dispatch(actions.showAccountsPage()),
|
||||
displayWarning: warning => dispatch(actions.displayWarning(warning)),
|
||||
hideWarning: () => dispatch(actions.hideWarning()),
|
||||
addToAddressBook: (recipient, nickname) => dispatch(actions.addToAddressBook(recipient, nickname)),
|
||||
signTokenTx: (
|
||||
tokenAddress,
|
||||
toAddress,
|
||||
tokensValueWithDec,
|
||||
txParams,
|
||||
confTxScreenParams
|
||||
) => dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams)),
|
||||
}
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.render = function () {
|
||||
const { isLoading, token } = this.state
|
||||
if (isLoading) {
|
||||
return h(Loading, {
|
||||
isLoading: isLoading,
|
||||
loadingMessage: 'Loading...',
|
||||
})
|
||||
}
|
||||
this.persistentFormParentId = 'send-tx-form'
|
||||
|
||||
const props = this.props
|
||||
const {
|
||||
network,
|
||||
identities,
|
||||
addressBook,
|
||||
error,
|
||||
} = props
|
||||
|
||||
return (
|
||||
|
||||
h('.send-screen.flex-column.flex-grow', [
|
||||
|
||||
//
|
||||
// Sender Profile
|
||||
//
|
||||
|
||||
h(SendProfile, {
|
||||
isToken: true,
|
||||
token,
|
||||
}),
|
||||
|
||||
//
|
||||
// Send Header
|
||||
//
|
||||
|
||||
h(SendHeader, {
|
||||
title: `Send ${this.state.token.symbol} Tokens`,
|
||||
}),
|
||||
|
||||
// error message
|
||||
h(SendError, {
|
||||
error,
|
||||
}),
|
||||
|
||||
// 'to' field
|
||||
h('section.flex-row.flex-center', [
|
||||
h(EnsInput, {
|
||||
name: 'address',
|
||||
placeholder: 'Recipient Address',
|
||||
onChange: this.recipientDidChange.bind(this),
|
||||
network,
|
||||
identities,
|
||||
addressBook,
|
||||
}),
|
||||
]),
|
||||
|
||||
// 'amount' and send button
|
||||
h('section.flex-row.flex-center', [
|
||||
|
||||
h('input.large-input', {
|
||||
name: 'amount',
|
||||
placeholder: 'Amount',
|
||||
type: 'number',
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
dataset: {
|
||||
persistentFormId: 'tx-amount',
|
||||
},
|
||||
}),
|
||||
|
||||
h('button', {
|
||||
onClick: this.onSubmit.bind(this),
|
||||
}, 'Next'),
|
||||
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.componentDidMount = function () {
|
||||
this.getTokensMetadata()
|
||||
.then(() => {
|
||||
this.createFreshTokenTracker()
|
||||
})
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.getTokensMetadata = async function () {
|
||||
this.setState({isLoading: true})
|
||||
this.tokenInfoGetter = tokenInfoGetter()
|
||||
const { tokenAddress, network } = this.props
|
||||
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(tokenAddress)
|
||||
this.setState({
|
||||
token: {
|
||||
address: tokenAddress,
|
||||
network,
|
||||
symbol,
|
||||
decimals,
|
||||
},
|
||||
})
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.componentWillUnmount = function () {
|
||||
this.props.dispatch(actions.displayWarning(''))
|
||||
if (!this.tracker) return
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.createFreshTokenTracker = function () {
|
||||
this.setState({isLoading: true})
|
||||
const { address, tokenAddress } = this.props
|
||||
if (!isValidAddress(tokenAddress)) return
|
||||
if (this.tracker) {
|
||||
// Clean up old trackers when refreshing:
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
if (!global.ethereumProvider) return
|
||||
|
||||
this.tracker = new TokenTracker({
|
||||
userAddress: address,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: [this.state.token],
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
|
||||
// Set up listener instances for cleaning up
|
||||
this.balanceUpdater = this.updateBalances.bind(this)
|
||||
this.showError = (error) => {
|
||||
this.setState({ error, isLoading: false })
|
||||
}
|
||||
this.tracker.on('update', this.balanceUpdater)
|
||||
this.tracker.on('error', this.showError)
|
||||
|
||||
this.tracker.updateBalances()
|
||||
.then(() => {
|
||||
this.updateBalances(this.tracker.serialize())
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error(`Problem updating balances`, reason)
|
||||
this.setState({ isLoading: false })
|
||||
})
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.updateBalances = function (tokens) {
|
||||
if (!this.tracker.running) {
|
||||
return
|
||||
}
|
||||
this.setState({ token: (tokens && tokens[0]), isLoading: false })
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.navigateToAccounts = function (event) {
|
||||
event.stopPropagation()
|
||||
this.props.dispatch(actions.showAccountsPage())
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) {
|
||||
this.setState({
|
||||
recipient: recipient,
|
||||
nickname: nickname,
|
||||
})
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.onSubmit = async function () {
|
||||
const state = this.state || {}
|
||||
const { token } = state
|
||||
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||
const nickname = state.nickname || ' '
|
||||
const input = document.querySelector('input[name="amount"]').value
|
||||
const parts = input.split('.')
|
||||
|
||||
let message
|
||||
|
||||
if (isNaN(input) || input === '') {
|
||||
message = 'Invalid token\'s amount.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if (parts[1]) {
|
||||
var decimal = parts[1]
|
||||
if (decimal.length > 18) {
|
||||
message = 'Token\'s amount is too precise.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
}
|
||||
|
||||
const tokenAddress = ethUtil.addHexPrefix(token.address)
|
||||
const tokensValueWithoutDec = new BigNumber(input)
|
||||
const tokensValueWithDec = new BigNumber(calcTokenAmountWithDec(input, token.decimals))
|
||||
|
||||
if (tokensValueWithDec.gt(token.balance)) {
|
||||
message = 'Insufficient token\'s balance.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if (input < 0) {
|
||||
message = 'Can not send negative amounts of ETH.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if ((isInvalidChecksumAddress(recipient))) {
|
||||
message = 'Recipient address checksum is invalid.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if (!isValidAddress(recipient) || (!recipient)) {
|
||||
message = 'Recipient address is invalid.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.hideWarning())
|
||||
|
||||
this.props.dispatch(actions.addToAddressBook(recipient, nickname))
|
||||
|
||||
var txParams = {
|
||||
from: this.props.address,
|
||||
value: '0x',
|
||||
}
|
||||
|
||||
const toAddress = ethUtil.addHexPrefix(recipient)
|
||||
|
||||
txParams.to = tokenAddress
|
||||
|
||||
const tokensAmount = `0x${input.toString(16)}`
|
||||
const encoded = this.generateTokenTransferData({toAddress, amount: tokensAmount})
|
||||
txParams.data = encoded
|
||||
|
||||
const confTxScreenParams = {
|
||||
isToken: true,
|
||||
tokenSymbol: token.symbol,
|
||||
tokensToSend: tokensValueWithoutDec,
|
||||
tokensTransferTo: toAddress,
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.signTokenTx(tokenAddress, toAddress, tokensValueWithDec, txParams, confTxScreenParams))
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.generateTokenTransferData = function ({ toAddress = '0x0', amount = '0x0' }) {
|
||||
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
|
||||
const abi = require('ethereumjs-abi')
|
||||
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||
abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]),
|
||||
x => ('00' + x.toString(16)).slice(-2)
|
||||
).join('')
|
||||
}
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendTransactionScreen)
|
||||
|
|
|
@ -14,13 +14,16 @@ const EnsInput = require('../ens-input')
|
|||
const ethUtil = require('ethereumjs-util')
|
||||
import SendProfile from './send-profile'
|
||||
import SendHeader from './send-header'
|
||||
import SendError from './send-error'
|
||||
import ErrorComponent from '../error'
|
||||
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
|
||||
import ToastComponent from '../toast'
|
||||
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
var result = {
|
||||
address: state.metamask.selectedAddress,
|
||||
accounts: state.metamask.accounts,
|
||||
accounts,
|
||||
identities: state.metamask.identities,
|
||||
warning: state.appState.warning,
|
||||
network: state.metamask.network,
|
||||
|
@ -54,6 +57,10 @@ SendTransactionScreen.prototype.render = function () {
|
|||
|
||||
h('.send-screen.flex-column.flex-grow', [
|
||||
|
||||
h(ToastComponent, {
|
||||
isSuccess: false,
|
||||
}),
|
||||
|
||||
//
|
||||
// Sender Profile
|
||||
//
|
||||
|
@ -69,11 +76,8 @@ SendTransactionScreen.prototype.render = function () {
|
|||
}),
|
||||
|
||||
// error message
|
||||
h(SendError, {
|
||||
h(ErrorComponent, {
|
||||
error,
|
||||
onClose: () => {
|
||||
this.props.dispatch(actions.hideWarning())
|
||||
},
|
||||
}),
|
||||
|
||||
// 'to' field
|
||||
|
@ -159,8 +163,16 @@ SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickna
|
|||
|
||||
SendTransactionScreen.prototype.onSubmit = function () {
|
||||
const state = this.state || {}
|
||||
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||
const nickname = state.nickname || ' '
|
||||
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||
let nickname = state.nickname || ' '
|
||||
if (typeof recipient === 'object') {
|
||||
if (recipient.toAddress) {
|
||||
recipient = recipient.toAddress
|
||||
}
|
||||
if (recipient.nickname) {
|
||||
nickname = recipient.nickname
|
||||
}
|
||||
}
|
||||
const input = document.querySelector('input[name="amount"]').value
|
||||
const parts = input.split('.')
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ const ethNetProps = require('eth-net-props')
|
|||
const actions = require('../../../ui/app/actions')
|
||||
const addressSummary = require('../util').addressSummary
|
||||
|
||||
const CopyButton = require('./copyButton')
|
||||
const CopyButton = require('./copy/copy-button')
|
||||
const EthBalance = require('./eth-balance')
|
||||
const Tooltip = require('./tooltip')
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import classnames from 'classnames'
|
||||
import actions from '../../../ui/app/actions'
|
||||
|
||||
class ToastComponent extends Component {
|
||||
static propTypes = {
|
||||
msg: PropTypes.string,
|
||||
toastMsg: PropTypes.string,
|
||||
isSuccess: PropTypes.bool,
|
||||
hideToast: PropTypes.func,
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.timerID = null
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if ((!prevProps.msg && this.props.msg) || (!prevProps.toastMsg && this.props.toastMsg)) {
|
||||
this.timerID = setTimeout(() => {
|
||||
this.props.hideToast()
|
||||
clearTimeout(this.timerID)
|
||||
}, 4000)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.props.hideToast()
|
||||
clearTimeout(this.timerID)
|
||||
}
|
||||
|
||||
render () {
|
||||
let toastMsg = this.props.msg || this.props.toastMsg
|
||||
toastMsg = (toastMsg && toastMsg.message) || toastMsg
|
||||
return toastMsg ? (
|
||||
<div
|
||||
className={classnames('toast', {
|
||||
'green': this.props.isSuccess,
|
||||
'red': !this.props.isSuccess,
|
||||
})}
|
||||
onClick={(e) => this.props.hideToast()}
|
||||
>{toastMsg}</div>
|
||||
) : null
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
toastMsg: state.appState.toastMsg,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
hideToast: () => dispatch(actions.hideToast()),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(ToastComponent)
|
|
@ -9,6 +9,7 @@ const ethUtil = require('ethereumjs-util')
|
|||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const connect = require('react-redux').connect
|
||||
const { MAINNET_CODE } = require('../../../app/scripts/controllers/network/enums')
|
||||
import { countSignificantDecimals } from '../util'
|
||||
|
||||
const tokenCellDropDownPrefix = 'token-cell_dropdown_'
|
||||
|
@ -33,7 +34,7 @@ TokenCell.prototype.render = function () {
|
|||
return (
|
||||
h(`li#token-cell_${ind}.token-cell`, {
|
||||
style: {
|
||||
cursor: network === '1' ? 'pointer' : 'default',
|
||||
cursor: Number(network) === MAINNET_CODE ? 'pointer' : 'default',
|
||||
borderBottom: isLastTokenCell ? 'none' : '1px solid #e2e2e2',
|
||||
padding: '20px 0',
|
||||
margin: '0 30px',
|
||||
|
|
|
@ -134,38 +134,47 @@ TokenList.prototype.renderTokenStatusBar = function () {
|
|||
const tokensFromCurrentNetwork = tokens.filter(token => (parseInt(token.network) === parseInt(network) || !token.network))
|
||||
|
||||
let msg
|
||||
let noTokens = false
|
||||
if (tokensFromCurrentNetwork.length === 1) {
|
||||
msg = `You own 1 token`
|
||||
} else if (tokensFromCurrentNetwork.length > 1) {
|
||||
msg = `You own ${tokensFromCurrentNetwork.length} tokens`
|
||||
} else {
|
||||
msg = `No tokens found`
|
||||
noTokens = true
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
minHeight: '70px',
|
||||
padding: '30px 30px 10px',
|
||||
},
|
||||
}, [
|
||||
h('span', msg),
|
||||
h('button.btn-primary.wallet-view__add-token-button', {
|
||||
key: 'reveal-account-bar',
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
this.props.addToken()
|
||||
},
|
||||
return h('div', [
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
minHeight: '70px',
|
||||
padding: '30px 30px 10px',
|
||||
},
|
||||
}, [
|
||||
'Add Token',
|
||||
h('span', msg),
|
||||
h('button.btn-primary.wallet-view__add-token-button', {
|
||||
key: 'reveal-account-bar',
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
this.props.addToken()
|
||||
},
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
'Add Token',
|
||||
]),
|
||||
]),
|
||||
noTokens ? h('div', {
|
||||
style: {
|
||||
height: '70px',
|
||||
},
|
||||
}) : null,
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ const connect = require('react-redux').connect
|
|||
|
||||
const EthBalance = require('./eth-balance')
|
||||
const addressSummary = require('../util').addressSummary
|
||||
const CopyButton = require('./copyButton')
|
||||
const CopyButton = require('./copy/copy-button')
|
||||
const vreme = new (require('vreme'))()
|
||||
const Tooltip = require('./tooltip')
|
||||
const numberToBN = require('number-to-bn')
|
||||
|
@ -15,6 +15,16 @@ const ethNetProps = require('eth-net-props')
|
|||
const TransactionIcon = require('./transaction-list-item-icon')
|
||||
const ShiftListItem = require('./shift-list-item')
|
||||
|
||||
const { POA_CODE,
|
||||
DAI_CODE,
|
||||
POA_SOKOL_CODE,
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEBY_CODE,
|
||||
KOVAN_CODE,
|
||||
GOERLI_TESTNET_CODE,
|
||||
} = require('../../../app/scripts/controllers/network/enums')
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
|
||||
|
@ -59,13 +69,20 @@ TransactionListItem.prototype.render = function () {
|
|||
const { transaction, network, conversionRate, currentCurrency } = this.props
|
||||
const { status } = transaction
|
||||
if (transaction.key === 'shapeshift') {
|
||||
if (network === '1') return h(ShiftListItem, transaction)
|
||||
if (Number(network) === MAINNET_CODE) return h(ShiftListItem, transaction)
|
||||
}
|
||||
var date = formatDate(transaction.time)
|
||||
|
||||
let isLinkable = false
|
||||
const numericNet = parseInt(network)
|
||||
isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 4 || numericNet === 42 || numericNet === 77 || numericNet === 99 || numericNet === 100
|
||||
isLinkable = numericNet === MAINNET_CODE ||
|
||||
numericNet === ROPSTEN_CODE ||
|
||||
numericNet === RINKEBY_CODE ||
|
||||
numericNet === KOVAN_CODE ||
|
||||
numericNet === POA_SOKOL_CODE ||
|
||||
numericNet === POA_CODE ||
|
||||
numericNet === DAI_CODE ||
|
||||
numericNet === GOERLI_TESTNET_CODE
|
||||
|
||||
var isMsg = ('msgParams' in transaction)
|
||||
var isTx = ('txParams' in transaction)
|
||||
|
|
|
@ -3,6 +3,7 @@ const h = require('react-hyperscript')
|
|||
const inherits = require('util').inherits
|
||||
|
||||
const TransactionListItem = require('./transaction-list-item')
|
||||
const { MAINNET_CODE } = require('../../../app/scripts/controllers/network/enums')
|
||||
|
||||
module.exports = TransactionList
|
||||
|
||||
|
@ -16,7 +17,7 @@ TransactionList.prototype.render = function () {
|
|||
const { transactions, network, unapprovedMsgs, conversionRate } = this.props
|
||||
|
||||
var shapeShiftTxList
|
||||
if (network === '1') {
|
||||
if (Number(network) === MAINNET_CODE) {
|
||||
shapeShiftTxList = this.props.shapeShiftTxList
|
||||
}
|
||||
const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
|
||||
|
@ -76,7 +77,7 @@ TransactionList.prototype.render = function () {
|
|||
}, [
|
||||
h('p', {
|
||||
style: {
|
||||
marginTop: '50px',
|
||||
margin: '50px 0',
|
||||
},
|
||||
}, 'No transaction history.'),
|
||||
]),
|
||||
|
|
|
@ -13,6 +13,8 @@ import PendingMsg from './components/pending-msg'
|
|||
import PendingPersonalMsg from './components/pending-personal-msg'
|
||||
import PendingTypedMsg from './components/pending-typed-msg'
|
||||
const Loading = require('./components/loading')
|
||||
const { DAI_CODE, POA_SOKOL_CODE, GOERLI_TESTNET_CODE } = require('../../app/scripts/controllers/network/enums')
|
||||
const { getMetaMaskAccounts } = require('../../ui/app/selectors')
|
||||
|
||||
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
|
||||
|
||||
|
@ -21,8 +23,8 @@ function mapStateToProps (state) {
|
|||
const { screenParams, pendingTxIndex } = appState.currentView
|
||||
return {
|
||||
identities: metamask.identities,
|
||||
accounts: getMetaMaskAccounts(state),
|
||||
keyrings: metamask.keyrings,
|
||||
accounts: metamask.accounts,
|
||||
selectedAddress: metamask.selectedAddress,
|
||||
unapprovedTxs: metamask.unapprovedTxs,
|
||||
unapprovedMsgs: metamask.unapprovedMsgs,
|
||||
|
@ -55,9 +57,9 @@ ConfirmTxScreen.prototype.render = function () {
|
|||
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, blockGasLimit } = props
|
||||
let { conversionRate } = props
|
||||
|
||||
const isSokol = parseInt(network) === 77
|
||||
const isDai = parseInt(network) === 100
|
||||
if (isSokol) {
|
||||
const isTestnet = parseInt(network) === POA_SOKOL_CODE || parseInt(network) === GOERLI_TESTNET_CODE
|
||||
const isDai = parseInt(network) === DAI_CODE
|
||||
if (isTestnet) {
|
||||
conversionRate = 0
|
||||
} else if (isDai) {
|
||||
conversionRate = 1
|
||||
|
|
|
@ -12,6 +12,7 @@ const validUrl = require('valid-url')
|
|||
const exportAsFile = require('./util').exportAsFile
|
||||
const Modal = require('../../ui/app/components/modals/index').Modal
|
||||
const ethNetProps = require('eth-net-props')
|
||||
const { networks } = require('../../app/scripts/controllers/network/util')
|
||||
|
||||
module.exports = connect(mapStateToProps)(ConfigScreen)
|
||||
|
||||
|
@ -290,46 +291,12 @@ function currentProviderDisplay (metamaskState, state) {
|
|||
const provider = metamaskState.provider
|
||||
let title, value
|
||||
|
||||
switch (provider.type) {
|
||||
|
||||
case 'mainnet':
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(1)
|
||||
break
|
||||
|
||||
case 'sokol':
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(77)
|
||||
break
|
||||
|
||||
case 'ropsten':
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(3)
|
||||
break
|
||||
|
||||
case 'kovan':
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(42)
|
||||
break
|
||||
|
||||
case 'rinkeby':
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(4)
|
||||
break
|
||||
|
||||
case 'poa':
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(99)
|
||||
break
|
||||
|
||||
case 'dai':
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(100)
|
||||
break
|
||||
|
||||
default:
|
||||
title = 'Current RPC'
|
||||
value = metamaskState.provider.rpcTarget
|
||||
if (networks[provider.type]) {
|
||||
title = 'Current Network'
|
||||
value = ethNetProps.props.getNetworkDisplayName(networks[provider.type].networkID)
|
||||
} else {
|
||||
title = 'Current RPC'
|
||||
value = metamaskState.provider.rpcTarget
|
||||
}
|
||||
|
||||
return h('div', [
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
.accs-dd-menu-item-selected {
|
||||
width: 4px;
|
||||
height: 26px;
|
||||
background: #60db97;
|
||||
position: absolute;
|
||||
left: -25px;
|
||||
}
|
||||
.accs-dd-menu-item-account-name {
|
||||
margin-left: 10px;
|
||||
font-size: 16px;
|
||||
max-width: 95px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.acc-dd-menu-item-text {
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
color: #60db97;
|
||||
}
|
||||
.accounts-selector-additional-style {
|
||||
background: url(images/switch_acc.svg) white center center no-repeat;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-right: 3px;
|
||||
}
|
|
@ -280,10 +280,13 @@ app sections
|
|||
|
||||
/* unlock */
|
||||
.toast {
|
||||
border: 1px solid #60db97 !important;
|
||||
background-image: url('../images/remove.svg');
|
||||
background-size: 12px 12px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 5px 5px;
|
||||
border: 1px solid !important;
|
||||
color: #ffffff !important;
|
||||
font-size: 12px;
|
||||
background: #60db97;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
width: 357px;
|
||||
|
@ -294,6 +297,17 @@ app sections
|
|||
right: 0px;
|
||||
z-index: 100;
|
||||
animation: 500ms ease-out 0s move;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toast.green {
|
||||
background-color: #60db97;
|
||||
border-color: #60db97 !important;
|
||||
}
|
||||
|
||||
.toast.red {
|
||||
background-color: #ff1345;
|
||||
border-color: #ff1345 !important;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
|
|
|
@ -1,79 +1,25 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
import ConfirmScreen from './components/confirm'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import actions from '../../ui/app/actions'
|
||||
|
||||
module.exports = connect(mapStateToProps)(RemoveTokenScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(RemoveTokenScreen, Component)
|
||||
function RemoveTokenScreen () {
|
||||
this.state = {}
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
RemoveTokenScreen.prototype.render = function () {
|
||||
const props = this.props
|
||||
|
||||
const warning = `Are you sure you want to remove token "${props.symbol}"?`
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-grow', [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
props.dispatch(actions.goHome())
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '30px',
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Remove Token'),
|
||||
]),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'inline-block',
|
||||
textAlign: 'center',
|
||||
padding: '0 30px',
|
||||
},
|
||||
}, [
|
||||
h('p.confirm-label', warning),
|
||||
]),
|
||||
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
h('.flex-space-around.flex-right', {
|
||||
style: {
|
||||
padding: '20px 30px',
|
||||
},
|
||||
}, [
|
||||
h('button.btn-violet',
|
||||
{
|
||||
onClick: () => {
|
||||
class RemoveTokenScreen extends ConfirmScreen {
|
||||
render () {
|
||||
return (
|
||||
<ConfirmScreen
|
||||
subtitle="Remove Token"
|
||||
question={`Are you sure you want to remove token "${this.props.symbol}"?`}
|
||||
onCancelClick={() => this.props.dispatch(actions.goHome())}
|
||||
onNoClick={() => this.props.dispatch(actions.goHome())}
|
||||
onYesClick={() => {
|
||||
this.props.dispatch(actions.removeToken(this.props.address))
|
||||
.then(() => {
|
||||
this.props.dispatch(actions.goHome())
|
||||
},
|
||||
},
|
||||
'No'),
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick: (event) => {
|
||||
this.props.dispatch(actions.removeToken(props.address))
|
||||
.then(() => {
|
||||
this.props.dispatch(actions.goHome())
|
||||
})
|
||||
},
|
||||
}, 'Yes'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect()(RemoveTokenScreen)
|
||||
|
|
|
@ -26,6 +26,7 @@ module.exports = {
|
|||
miniAddressSummary: miniAddressSummary,
|
||||
isAllOneCase: isAllOneCase,
|
||||
isValidAddress: isValidAddress,
|
||||
isValidENSAddress,
|
||||
numericBalance: numericBalance,
|
||||
parseBalance: parseBalance,
|
||||
formatBalance: formatBalance,
|
||||
|
@ -44,6 +45,7 @@ module.exports = {
|
|||
getCurrentKeyring,
|
||||
ifLooseAcc,
|
||||
ifContractAcc,
|
||||
ifHardwareAcc,
|
||||
}
|
||||
|
||||
function valuesFor (obj) {
|
||||
|
@ -81,6 +83,10 @@ function isValidAddress (address) {
|
|||
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
|
||||
}
|
||||
|
||||
function isValidENSAddress (address) {
|
||||
return address.match(/^.{7,}\.(eth|test)$/)
|
||||
}
|
||||
|
||||
function isInvalidChecksumAddress (address) {
|
||||
var prefixed = ethUtil.addHexPrefix(address)
|
||||
if (address === '0x0000000000000000000000000000000000000000') return false
|
||||
|
@ -209,6 +215,9 @@ function normalizeEthStringToWei (str) {
|
|||
while (decimal.length < 18) {
|
||||
decimal += '0'
|
||||
}
|
||||
if (decimal.length > 18) {
|
||||
decimal = decimal.slice(0, 18)
|
||||
}
|
||||
const decimalBN = new ethUtil.BN(decimal, 10)
|
||||
eth = eth.add(decimalBN)
|
||||
}
|
||||
|
@ -323,6 +332,7 @@ function ifLooseAcc (keyring) {
|
|||
} catch (e) { return }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* checks, if keyring is contract
|
||||
*
|
||||
|
@ -337,3 +347,17 @@ function ifContractAcc (keyring) {
|
|||
return isContract
|
||||
} catch (e) { return }
|
||||
}
|
||||
|
||||
/**
|
||||
* checks, if keyring is of hardware type
|
||||
*
|
||||
* @param {object} keyring
|
||||
*
|
||||
* returns {boolean} true, if keyring is of hardware type and false, if it is not
|
||||
**/
|
||||
function ifHardwareAcc (keyring) {
|
||||
if (keyring && keyring.type.search('Hardware') !== -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ var cssFiles = {
|
|||
'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.css'), 'utf8'),
|
||||
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
|
||||
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
|
||||
'dropdowns.css': fs.readFileSync(path.join(__dirname, '/app/css/dropdowns.css'), 'utf8'),
|
||||
}
|
||||
|
||||
function bundleCss () {
|
||||
|
|
|
@ -570,13 +570,41 @@
|
|||
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==",
|
||||
"dev": true
|
||||
},
|
||||
"@sinonjs/formatio": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz",
|
||||
"integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==",
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz",
|
||||
"integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"samsam": "1.3.0"
|
||||
"type-detect": "4.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sinonjs/formatio": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz",
|
||||
"integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/samsam": "^2 || ^3"
|
||||
}
|
||||
},
|
||||
"@sinonjs/samsam": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.1.0.tgz",
|
||||
"integrity": "sha512-IXio+GWY+Q8XUjHUOgK7wx8fpvr7IFffgyXb1bnJFfX3001KmHt35Zq4tp7MXZyjJPCLPuadesDYNk41LYtVjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.0.2",
|
||||
"array-from": "^2.1.1",
|
||||
"lodash.get": "^4.4.2"
|
||||
}
|
||||
},
|
||||
"@storybook/addon-actions": {
|
||||
|
@ -10104,7 +10132,7 @@
|
|||
}
|
||||
},
|
||||
"eth-contract-metadata": {
|
||||
"version": "github:MetaMask/eth-contract-metadata#889defeaaa18e5488f2695a462d22a29936229e2",
|
||||
"version": "github:MetaMask/eth-contract-metadata#588b2c56edbbf68303069ae4fda4ae905fa20918",
|
||||
"from": "github:MetaMask/eth-contract-metadata#master"
|
||||
},
|
||||
"eth-ens-namehash": {
|
||||
|
@ -10124,8 +10152,8 @@
|
|||
}
|
||||
},
|
||||
"eth-hd-keyring": {
|
||||
"version": "https://registry.npmjs.org/eth-hd-keyring/-/eth-hd-keyring-2.0.0.tgz",
|
||||
"from": "eth-hd-keyring@2.0.0",
|
||||
"version": "github:vbaranov/eth-hd-keyring#64d0fa741af88d5f232f9518fd150190c421b3e7",
|
||||
"from": "github:vbaranov/eth-hd-keyring#2.0.1",
|
||||
"requires": {
|
||||
"bip39": "^2.2.0",
|
||||
"eth-sig-util": "^2.0.1",
|
||||
|
@ -10444,13 +10472,13 @@
|
|||
}
|
||||
},
|
||||
"eth-keychain-controller": {
|
||||
"version": "github:vbaranov/KeyringController#52961c8fac58177c9b39ec7754de3ecda90e8a08",
|
||||
"version": "github:vbaranov/KeyringController#b4527590d38a9421962343f7ea56518df775456d",
|
||||
"from": "github:vbaranov/KeyringController#simple-address",
|
||||
"requires": {
|
||||
"bip39": "^2.4.0",
|
||||
"bluebird": "^3.5.0",
|
||||
"browser-passworder": "^2.0.3",
|
||||
"eth-hd-keyring": "https://registry.npmjs.org/eth-hd-keyring/-/eth-hd-keyring-2.0.0.tgz",
|
||||
"eth-hd-keyring": "github:vbaranov/eth-hd-keyring#64d0fa741af88d5f232f9518fd150190c421b3e7",
|
||||
"eth-sig-util": "^1.4.0",
|
||||
"eth-simple-keyring": "^2.0.0",
|
||||
"ethereumjs-util": "^5.1.2",
|
||||
|
@ -10461,7 +10489,7 @@
|
|||
"dependencies": {
|
||||
"babelify": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
|
||||
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
|
||||
"requires": {
|
||||
"babel-core": "^6.0.14",
|
||||
|
@ -10473,22 +10501,12 @@
|
|||
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
|
||||
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
|
||||
"requires": {
|
||||
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
|
||||
"ethereumjs-util": "^5.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"requires": {
|
||||
"bn.js": "^4.10.0",
|
||||
"ethereumjs-util": "^5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"requires": {
|
||||
"bn.js": "^4.10.0",
|
||||
|
@ -10524,9 +10542,8 @@
|
|||
}
|
||||
},
|
||||
"eth-ledger-bridge-keyring": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.1.1.tgz",
|
||||
"integrity": "sha512-EhClGSy5ixcd55yHGXoA3C7I8iFFi6kgSqvKOSj+5URtg5PYpHP8kv+KemFPOT1Px6se/IFHI9OIelUS8kN3lw==",
|
||||
"version": "github:vbaranov/eth-ledger-bridge-keyring#c36b34d131a585274e8c7bb69ff985bba4816624",
|
||||
"from": "github:vbaranov/eth-ledger-bridge-keyring#0.1.0-clear-accounts-flag",
|
||||
"requires": {
|
||||
"eth-sig-util": "^1.4.2",
|
||||
"ethereumjs-tx": "^1.3.4",
|
||||
|
@ -10540,6 +10557,7 @@
|
|||
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
|
||||
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
|
||||
"requires": {
|
||||
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
|
||||
"ethereumjs-util": "^5.1.1"
|
||||
}
|
||||
},
|
||||
|
@ -10549,7 +10567,7 @@
|
|||
"integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8="
|
||||
},
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"requires": {
|
||||
"bn.js": "^4.10.0",
|
||||
|
@ -10713,9 +10731,9 @@
|
|||
}
|
||||
},
|
||||
"eth-net-props": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.10.tgz",
|
||||
"integrity": "sha512-sCKttmHUFficRcyx95CWUW2AuvQ6rEboy4XcoR49HU7UNMxOdRti3rFyGrkVepRozn1bLLjet/doPKnj8rDG7g==",
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.13.tgz",
|
||||
"integrity": "sha512-jQ+6WD0vRAQyrYwdfGmfV3URE/U+7hNrwutWYFbJ5C4gONSmIOeoJS1lsRU+DjJ2N8TdwsY86Ov6+li3lmfWoQ==",
|
||||
"requires": {
|
||||
"chai": "^4.1.2"
|
||||
}
|
||||
|
@ -10945,15 +10963,15 @@
|
|||
}
|
||||
},
|
||||
"eth-trezor-keyring": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eth-trezor-keyring/-/eth-trezor-keyring-0.1.0.tgz",
|
||||
"integrity": "sha512-7ynDXiXGQOh9CslksJSmGGK726lV9fTnIp2QQnjbZJgR4zJIoSUYQYKvT2wXcxLhVrTUl2hLjwKN9QGqDCMVwA==",
|
||||
"version": "github:vbaranov/eth-trezor-keyring#ec3df8a71ff8733e525d5609faeb5b711678dd11",
|
||||
"from": "github:vbaranov/eth-trezor-keyring#0.2.0--clear-accounts-flag",
|
||||
"requires": {
|
||||
"eth-sig-util": "^1.4.2",
|
||||
"ethereumjs-tx": "^1.3.4",
|
||||
"ethereumjs-util": "^5.1.5",
|
||||
"events": "^2.0.0",
|
||||
"hdkey": "0.8.0"
|
||||
"hdkey": "0.8.0",
|
||||
"trezor-connect": "^6.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"eth-sig-util": {
|
||||
|
@ -10961,7 +10979,7 @@
|
|||
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
|
||||
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
|
||||
"requires": {
|
||||
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
|
||||
"ethereumjs-util": "^5.1.1"
|
||||
}
|
||||
},
|
||||
|
@ -10971,7 +10989,7 @@
|
|||
"integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8="
|
||||
},
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"requires": {
|
||||
"bn.js": "^4.10.0",
|
||||
|
@ -14994,7 +15012,8 @@
|
|||
"bindings": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz",
|
||||
"integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw=="
|
||||
"integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==",
|
||||
"dev": true
|
||||
},
|
||||
"bip39": {
|
||||
"version": "2.5.0",
|
||||
|
@ -15013,6 +15032,7 @@
|
|||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
|
||||
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
|
@ -15047,7 +15067,8 @@
|
|||
"bn.js": {
|
||||
"version": "4.11.8",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
|
||||
"dev": true
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
|
@ -15090,12 +15111,14 @@
|
|||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
|
||||
"dev": true
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-xor": "^1.0.3",
|
||||
"cipher-base": "^1.0.0",
|
||||
|
@ -15252,7 +15275,8 @@
|
|||
"buffer-xor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
|
||||
"dev": true
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "1.1.1",
|
||||
|
@ -15350,6 +15374,7 @@
|
|||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
||||
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
|
@ -15509,6 +15534,7 @@
|
|||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cipher-base": "^1.0.1",
|
||||
"inherits": "^2.0.1",
|
||||
|
@ -15521,6 +15547,7 @@
|
|||
"version": "1.1.7",
|
||||
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cipher-base": "^1.0.3",
|
||||
"create-hash": "^1.1.0",
|
||||
|
@ -15820,6 +15847,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
|
||||
"integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browserify-aes": "^1.0.6",
|
||||
"create-hash": "^1.1.2",
|
||||
|
@ -15858,6 +15886,7 @@
|
|||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
|
||||
"integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
|
@ -16169,14 +16198,6 @@
|
|||
"integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==",
|
||||
"dev": true
|
||||
},
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"requires": {
|
||||
"bn.js": "^4.10.0",
|
||||
"ethereumjs-util": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"ethereumjs-block": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "http://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz",
|
||||
|
@ -16366,6 +16387,7 @@
|
|||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
|
||||
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.11.0",
|
||||
"create-hash": "^1.1.2",
|
||||
|
@ -16455,6 +16477,7 @@
|
|||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
|
||||
"integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-hex-prefixed": "1.0.0",
|
||||
"strip-hex-prefix": "1.0.0"
|
||||
|
@ -16476,6 +16499,7 @@
|
|||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
||||
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"md5.js": "^1.3.4",
|
||||
"safe-buffer": "^5.1.1"
|
||||
|
@ -16886,6 +16910,7 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
|
||||
"integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
|
@ -16895,6 +16920,7 @@
|
|||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
|
||||
"integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
|
@ -16922,6 +16948,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
|
@ -17089,7 +17116,8 @@
|
|||
"is-hex-prefixed": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz",
|
||||
"integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ="
|
||||
"integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=",
|
||||
"dev": true
|
||||
},
|
||||
"is-natural-number": {
|
||||
"version": "4.0.1",
|
||||
|
@ -17290,6 +17318,7 @@
|
|||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz",
|
||||
"integrity": "sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bindings": "^1.2.1",
|
||||
"inherits": "^2.0.3",
|
||||
|
@ -17589,6 +17618,7 @@
|
|||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
|
@ -17748,12 +17778,14 @@
|
|||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
||||
"dev": true
|
||||
},
|
||||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
|
@ -17823,7 +17855,8 @@
|
|||
"nan": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
|
||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
|
||||
"dev": true
|
||||
},
|
||||
"nano-json-stream-parser": {
|
||||
"version": "0.1.2",
|
||||
|
@ -18490,6 +18523,7 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1"
|
||||
|
@ -18499,6 +18533,7 @@
|
|||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rlp/-/rlp-2.1.0.tgz",
|
||||
"integrity": "sha512-93U7IKH5j7nmXFVg19MeNBGzQW5uXW1pmCuKY8veeKIhYTE32C2d0mOegfiIAfXcHOKJjjPlJisn8iHDF5AezA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
|
@ -18512,7 +18547,8 @@
|
|||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"safe-event-emitter": {
|
||||
"version": "1.0.1",
|
||||
|
@ -18564,6 +18600,7 @@
|
|||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.5.2.tgz",
|
||||
"integrity": "sha512-iin3kojdybY6NArd+UFsoTuapOF7bnJNf2UbcWXaY3z+E1sJDipl60vtzB5hbO/uquBu7z0fd4VC4Irp+xoFVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bindings": "^1.2.1",
|
||||
"bip66": "^1.1.3",
|
||||
|
@ -18695,6 +18732,7 @@
|
|||
"version": "2.4.11",
|
||||
"resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
|
@ -18905,6 +18943,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz",
|
||||
"integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-hex-prefixed": "1.0.0"
|
||||
}
|
||||
|
@ -19593,15 +19632,17 @@
|
|||
"requires": {
|
||||
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"ethereumjs-util": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.10.0",
|
||||
"ethereumjs-util": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.10.0",
|
||||
"ethereumjs-util": "^5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
|
@ -25044,9 +25085,9 @@
|
|||
"integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo="
|
||||
},
|
||||
"just-extend": {
|
||||
"version": "1.1.27",
|
||||
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz",
|
||||
"integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz",
|
||||
"integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==",
|
||||
"dev": true
|
||||
},
|
||||
"karma": {
|
||||
|
@ -27974,13 +28015,13 @@
|
|||
"dev": true
|
||||
},
|
||||
"nise": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/nise/-/nise-1.3.2.tgz",
|
||||
"integrity": "sha512-KPKb+wvETBiwb4eTwtR/OsA2+iijXP+VnlSFYJo3EHjm2yjek1NWxHOUQat3i7xNLm1Bm18UA5j5Wor0yO2GtA==",
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/nise/-/nise-1.4.8.tgz",
|
||||
"integrity": "sha512-kGASVhuL4tlAV0tvA34yJYZIVihrUt/5bDwpp4tTluigxUr2bBlJeDXmivb6NuEdFkqvdv/Ybb9dm16PSKUhtw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/formatio": "^2.0.0",
|
||||
"just-extend": "^1.1.27",
|
||||
"@sinonjs/formatio": "^3.1.0",
|
||||
"just-extend": "^4.0.2",
|
||||
"lolex": "^2.3.2",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"text-encoding": "^0.6.4"
|
||||
|
@ -38119,6 +38160,23 @@
|
|||
"resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
|
||||
"integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A=="
|
||||
},
|
||||
"trezor-connect": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/trezor-connect/-/trezor-connect-6.0.3.tgz",
|
||||
"integrity": "sha512-QqgnulN9uQstVjxYTF9+gcNk01kBUQA4xXy2abyPOgxh8Wio9iiSGftQa12KkFfWLZKkZD1sL03KvQ2ievL05g==",
|
||||
"requires": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"events": "^1.1.1",
|
||||
"whatwg-fetch": "^2.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"trim": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
||||
|
|
14
package.json
14
package.json
|
@ -11,17 +11,17 @@
|
|||
"publish-docs": "gh-pages -d docs/jsdocs",
|
||||
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
||||
"watch:test:unit": "nodemon --exec \"npm run test:unit\" ./test ./app ./ui",
|
||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"",
|
||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\"",
|
||||
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
||||
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
||||
"test:integration": "npm run test:flat",
|
||||
"test:integration:build": "gulp build:scss",
|
||||
"test:e2e:chrome": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:chrome'",
|
||||
"test:e2e:drizzle:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-drizzle.sh",
|
||||
"test:e2e:chrome:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh",
|
||||
"test:e2e:firefox": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:firefox'",
|
||||
"test:e2e:firefox:beta": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh",
|
||||
"test:e2e:run:chrome": "SELENIUM_BROWSER=chrome mocha test/e2e/metamask.spec --bail --recursive",
|
||||
"test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/metamask.spec --bail --recursive",
|
||||
"test:e2e:run:chrome": "SELENIUM_BROWSER=chrome mocha test/e2e/nw.spec --bail --recursive",
|
||||
"test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/nw.spec --bail --recursive",
|
||||
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
|
||||
"test:screens:run": "node test/screens/new-ui.js",
|
||||
"test:coverage": "nyc --reporter=text --reporter=html npm run test:unit && npm run test:coveralls-upload",
|
||||
|
@ -116,14 +116,14 @@
|
|||
"eth-json-rpc-filters": "github:poanetwork/eth-json-rpc-filters#3.0.2",
|
||||
"eth-json-rpc-infura": "^3.0.0",
|
||||
"eth-keychain-controller": "github:vbaranov/KeyringController#simple-address",
|
||||
"eth-ledger-bridge-keyring": "^0.1.0",
|
||||
"eth-ledger-bridge-keyring": "github:vbaranov/eth-ledger-bridge-keyring#0.1.0-clear-accounts-flag",
|
||||
"eth-method-registry": "^1.0.0",
|
||||
"eth-net-props": "^1.0.10",
|
||||
"eth-net-props": "^1.0.13",
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
"eth-query": "^2.1.2",
|
||||
"eth-sig-util": "^2.0.2",
|
||||
"eth-token-watcher": "^1.1.6",
|
||||
"eth-trezor-keyring": "^0.1.0",
|
||||
"eth-trezor-keyring": "github:vbaranov/eth-trezor-keyring#0.2.0--clear-accounts-flag",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-tx": "^1.3.0",
|
||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
|
|
|
@ -30,6 +30,7 @@ module.exports = {
|
|||
info: By.css('li.dropdown-menu-item:nth-child(4)'),
|
||||
},
|
||||
account: {
|
||||
item: By.className('dropdown-menu-item'),
|
||||
account1: By.css('#app-content > div > div.full-width > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(2) > span'),
|
||||
account2: By.css('#app-content > div > div.full-width > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span'),
|
||||
account3: By.css('#app-content > div > div.full-width > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(4) > span'),
|
||||
|
@ -54,6 +55,17 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
screens: {
|
||||
hdWallet: {
|
||||
buttonArrow: By.className('fa fa-arrow-left fa-lg cursor-pointer'),
|
||||
error: By.className('error'),
|
||||
title: By.className('section-title flex-row flex-center'),
|
||||
buttonConnect: {
|
||||
enabled: By.className('hw-connect__connect-btn'),
|
||||
disabled: By.className('hw-connect__connect-btn disabled'),
|
||||
},
|
||||
image: By.className('hw-connect__btn__img'),
|
||||
imageSelected: By.className('hw-connect__btn selected'),
|
||||
},
|
||||
chooseContractExecutor: {
|
||||
title: By.className('flex-center send-header'),
|
||||
titleText: 'Choose contract executor',
|
||||
|
@ -240,7 +252,8 @@ module.exports = {
|
|||
error: By.css('span.error'),
|
||||
selectArrow: By.className('Select-arrow-zone'),
|
||||
selectType: By.name('import-type-select'),
|
||||
itemContract: By.id('react-select-3--option-2'),
|
||||
itemContract: By.id('react-select-4--option-2'),
|
||||
itemProxyContract: By.id('react-select-3--option-3'),
|
||||
contractAddress: By.id('address-box'),
|
||||
contractABI: By.id('abi-box'),
|
||||
title: By.css('#app-content > div > div.app-primary.from-right > div > div:nth-child(2) > div.flex-row.flex-center > h2'),
|
||||
|
@ -292,22 +305,25 @@ module.exports = {
|
|||
},
|
||||
// balance: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'),
|
||||
balance: By.xpath('//*[@id="app-content"]/div/div[2]/div/div/div[2]/div[1]/div/div/div[1]/div[1]'),
|
||||
balanceUSD: By.xpath('//*[@id="app-content"]/div/div[2]/div/div/div[2]/div[1]/div/div/div[2]/div[1]'),
|
||||
address: By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div'),
|
||||
tokens: {
|
||||
menu: By.id('wallet-view__tab-tokens'),
|
||||
token: By.className('token-cell'),
|
||||
balance: By.css('#token-cell_0 > h3'),
|
||||
amount: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > div > span'),
|
||||
amount: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > div > div:nth-child(1) > span'),
|
||||
textNoTokens: 'No tokens found',
|
||||
textYouOwn1token: 'You own 1 token',
|
||||
buttonAdd: By.css('div.full-flex-height:nth-child(2) > div:nth-child(1) > button:nth-child(2)'),
|
||||
buttonAdd: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > div > div:nth-child(1) > button'),
|
||||
buttonAdd2: By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > div:nth-child(1) > button'),
|
||||
buttonAddText: 'Add Token',
|
||||
counter: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > div > span'),
|
||||
counterFF: By.css('div.full-flex-height:nth-child(2) > div:nth-child(1) > span:nth-child(1)'),
|
||||
counter: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > div > div:nth-child(1) > span'),
|
||||
counterFF: By.css('div.full-flex-height:nth-child(2) > div:nth-child(1) > div:nth-child(1) > span:nth-child(1)'),
|
||||
},
|
||||
},
|
||||
buyEther: {
|
||||
title: By.className('flex-center buy-title'),
|
||||
buttonArrow: By.className('fa fa-arrow-left fa-lg cursor-pointer'),
|
||||
},
|
||||
info: {
|
||||
title: By.className('section-title flex-row flex-center'),
|
||||
|
@ -323,7 +339,7 @@ module.exports = {
|
|||
buttons: {
|
||||
back: By.className('fa fa-arrow-left fa-lg cursor-pointer'),
|
||||
no: By.className('btn-violet'),
|
||||
yes: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button:nth-child(2)'),
|
||||
yes: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-row.flex-right > button:nth-child(2)'),
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -382,6 +398,7 @@ module.exports = {
|
|||
ROPSTEN: 'ropsten',
|
||||
KOVAN: 'kovan',
|
||||
RINKEBY: 'rinkeby',
|
||||
GOERLI: 'goerli',
|
||||
LOCALHOST: 'localhost',
|
||||
CUSTOM: 'http://test.com',
|
||||
},
|
||||
|
|
769
test/e2e/func.js
769
test/e2e/func.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,139 @@
|
|||
const path = require('path')
|
||||
const Func = require('./func').Functions
|
||||
const account1 = '0x2E428ABd9313D256d64D1f69fe3929C3BE18fD1f'
|
||||
const account2 = '0xd7b7AFeCa35e32594e29504771aC847E2a803742'
|
||||
const testsFolder = './test-cases'
|
||||
const setup = require(`${testsFolder}/setup.spec`)
|
||||
const login = require(`${testsFolder}/login.spec`)
|
||||
const { accountCreation, getCreatedAccounts } = require(`${testsFolder}/account-creation.spec`)
|
||||
const connectHDWallet = require(`${testsFolder}/connect-hd-wallet.spec`)
|
||||
const importAccount = require(`${testsFolder}/import-account.spec`)
|
||||
const importContractAccount = require(`${testsFolder}/import-contract-account.spec`)
|
||||
const deleteImportedAccount = require(`${testsFolder}/delete-imported-account.spec`)
|
||||
const signData = require(`${testsFolder}/sign-data.spec`)
|
||||
const exportPrivateKey = require(`${testsFolder}/export-private-key.spec`)
|
||||
const importGanacheSeedPhrase = require(`${testsFolder}/import-ganache-seed-phrase.spec`)
|
||||
const checkEmittedEvents = require(`${testsFolder}/check-emitted-events.spec`)
|
||||
const addCustomToken = require(`${testsFolder}/add-token-custom.spec`)
|
||||
const changePassword = require(`${testsFolder}/change-password.spec`)
|
||||
const addTokeFromSearch = require(`${testsFolder}/add-token-search.spec`)
|
||||
const customRPC = require(`${testsFolder}/custom-rpc.spec`)
|
||||
|
||||
describe('Metamask popup page', async function () {
|
||||
|
||||
this.timeout(15 * 60 * 1000)
|
||||
const f = new Func()
|
||||
let driver, extensionId
|
||||
const password = '123456789'
|
||||
const newPassword = {
|
||||
correct: 'abcDEF123!@#',
|
||||
short: '123',
|
||||
incorrect: '1234567890',
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
const extPath = path.resolve('dist/chrome')
|
||||
driver = await Func.buildChromeWebDriver(extPath)
|
||||
f.driver = driver
|
||||
extensionId = await f.getExtensionIdChrome()
|
||||
f.extensionId = extensionId
|
||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||
|
||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
const extPath = path.resolve('dist/firefox')
|
||||
driver = await Func.buildFirefoxWebdriver()
|
||||
f.driver = driver
|
||||
await f.installWebExt(extPath)
|
||||
await f.delay(700)
|
||||
extensionId = await f.getExtensionIdFirefox()
|
||||
f.extensionId = extensionId
|
||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
// logs command not supported in firefox
|
||||
// https://github.com/SeleniumHQ/selenium/issues/2910
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
// check for console errors
|
||||
const errors = await f.checkBrowserForConsoleErrors(driver)
|
||||
if (errors.length) {
|
||||
const errorReports = errors.map(err => err.message)
|
||||
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
||||
console.log(errorMessage)
|
||||
}
|
||||
}
|
||||
// gather extra data if test failed
|
||||
if (this.currentTest.state === 'failed') {
|
||||
await f.verboseReportOnFailure(this.currentTest)
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await driver.quit()
|
||||
})
|
||||
|
||||
describe('Setup', async () => {
|
||||
await setup(f)
|
||||
})
|
||||
|
||||
describe('Log In', async () => {
|
||||
await login(f, password)
|
||||
})
|
||||
|
||||
describe('Account Creation', async () => {
|
||||
await accountCreation(f, password)
|
||||
})
|
||||
|
||||
describe('Connect Hardware Wallet', async () => {
|
||||
await connectHDWallet(f)
|
||||
})
|
||||
|
||||
describe('Import Account', async () => {
|
||||
await importAccount(f)
|
||||
})
|
||||
|
||||
describe('Import Contract account', async () => {
|
||||
await importContractAccount(f, account1, getCreatedAccounts)
|
||||
})
|
||||
|
||||
describe('Delete Imported Account', async () => {
|
||||
await deleteImportedAccount(f)
|
||||
})
|
||||
|
||||
describe('Sign Data', async () => {
|
||||
await signData(f)
|
||||
})
|
||||
|
||||
describe('Export private key', async () => {
|
||||
await exportPrivateKey(f, password)
|
||||
})
|
||||
|
||||
describe('Import Ganache seed phrase', async () => {
|
||||
await importGanacheSeedPhrase(f, account2, password)
|
||||
})
|
||||
|
||||
describe('Check the filter of emitted events', async () => {
|
||||
await checkEmittedEvents(f, account1, account2)
|
||||
})
|
||||
|
||||
describe('Add Token: Custom', async () => {
|
||||
await addCustomToken(f, account1, account2)
|
||||
})
|
||||
|
||||
describe('Change password', async () => {
|
||||
await changePassword(f, password, newPassword)
|
||||
})
|
||||
|
||||
describe('Add Token:Search', async () => {
|
||||
await addTokeFromSearch(f)
|
||||
})
|
||||
|
||||
describe('Custom Rpc', async () => {
|
||||
await customRPC(f)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
const assert = require('assert')
|
||||
const { menus, screens, NETWORKS } = require('../elements')
|
||||
const clipboardy = require('clipboardy')
|
||||
const createdAccounts = []
|
||||
|
||||
const accountCreation = async (f, password) => {
|
||||
const newAccountName = 'new name'
|
||||
|
||||
it('sets provider type to localhost', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
await f.delay(2000)
|
||||
})
|
||||
|
||||
it('copy icon is displayed and clickable', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.main.iconCopy)
|
||||
await field.click()
|
||||
assert.notEqual(field, false, 'copy icon doesn\'t present')
|
||||
})
|
||||
|
||||
it("Account's address is displayed and has length 20 symbols", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.main.address)
|
||||
createdAccounts.push((await field.getText()).toUpperCase())
|
||||
console.log(createdAccounts[0])
|
||||
assert.notEqual(createdAccounts[0].length, 20, "address isn't displayed")
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.length, 42, "address account wasn't copied to clipboard")
|
||||
})
|
||||
|
||||
it('open \'Account name\' change dialog', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.dot.menu)
|
||||
await menu.click()
|
||||
const field = await f.waitUntilShowUp(screens.main.edit)
|
||||
await field.click()
|
||||
const accountName = await f.waitUntilShowUp(screens.main.fieldAccountName)
|
||||
assert.notEqual(accountName, false, '\'Account name\' change dialog isn\'t opened')
|
||||
assert.equal(await accountName.getAttribute('value'), 'Account 1', 'incorrect account name')
|
||||
})
|
||||
|
||||
it('fill out new account\'s name', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.main.fieldAccountName)
|
||||
await field.clear()
|
||||
await field.sendKeys(newAccountName)
|
||||
})
|
||||
|
||||
it('dialog \'Account name\' is disappeared if click button \'Save\'', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.main.buttons.save)
|
||||
assert.equal(await button.getText(), 'Save', 'button has incorrect name')
|
||||
assert.notEqual(button, true, 'button \'Save\' does not present')
|
||||
await f.click(button)
|
||||
const accountName = await f.waitUntilShowUp(screens.main.fieldAccountName, 10)
|
||||
assert.equal(accountName, false, '\'Account name\' change dialog isn\'t opened')
|
||||
})
|
||||
|
||||
it('account has new name', async () => {
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const account1 = await f.waitUntilShowUp(menus.account.account1)
|
||||
assert.equal(await account1.getText(), newAccountName, 'account\'s name didn\'t changed')
|
||||
await accountMenu.click()
|
||||
})
|
||||
|
||||
it('adds a second account', async () => {
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.createAccount)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
it("Account's address is displayed and has length 20 symbols", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.main.address)
|
||||
createdAccounts.push((await field.getText()).toUpperCase())
|
||||
console.log(createdAccounts[1])
|
||||
assert.notEqual(createdAccounts[1].length, 20, "address isn't displayed")
|
||||
})
|
||||
|
||||
it('logs out of the vault', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.sandwich.menu)
|
||||
await menu.click()
|
||||
await f.delay(500)
|
||||
const button = await f.waitUntilShowUp(menus.sandwich.logOut)
|
||||
assert.equal(await button.getText(), 'Log Out', 'button has incorrect name')
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('accepts account password after lock', async () => {
|
||||
const box = await f.waitUntilShowUp(screens.lock.fieldPassword)
|
||||
await box.sendKeys(password)
|
||||
const button = await f.waitUntilShowUp(screens.lock.buttonLogin)
|
||||
assert.equal(await button.getText(), 'Log In', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
})
|
||||
|
||||
it('shows QR code option', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.dot.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.dot.showQRcode)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
it('checks QR code address is the same as account details address', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.QRcode.address)
|
||||
const text = await field.getText()
|
||||
assert.equal(text.toUpperCase(), createdAccounts[1], 'QR address doesn\'t match')
|
||||
})
|
||||
|
||||
it('copy icon is displayed and clickable', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.QRcode.iconCopy)
|
||||
await field.click()
|
||||
assert.notEqual(field, false, 'copy icon doesn\'t present')
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.length, 42, "address account wasn't copied to clipboard")
|
||||
})
|
||||
|
||||
it('close QR code screen by clicking button arrow', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.QRcode.buttonArrow)
|
||||
await f.click(button)
|
||||
})
|
||||
|
||||
it('user is able to open \'Info\' screen', async () => {
|
||||
const accountMenu = await f.waitUntilShowUp(menus.sandwich.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.sandwich.info)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
it('screen \'Info\' has correct title', async () => {
|
||||
const title = await f.waitUntilShowUp(screens.info.title)
|
||||
assert.equal(await title.getText(), screens.info.titleText, 'title is incorrect')
|
||||
})
|
||||
|
||||
it('close \'Info\' screen by clicking button arrow', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.info.buttonArrow)
|
||||
await button.click()
|
||||
})
|
||||
}
|
||||
|
||||
const getCreatedAccounts = () => {
|
||||
return createdAccounts
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
accountCreation,
|
||||
getCreatedAccounts,
|
||||
}
|
|
@ -0,0 +1,504 @@
|
|||
const assert = require('assert')
|
||||
const { screens, menus, NETWORKS } = require('../elements')
|
||||
const token = { supply: 101, name: 'Test', decimals: 0, ticker: 'ABC' }
|
||||
let tokenAddress
|
||||
|
||||
const addCustomToken = async (f, account1, account2) => {
|
||||
describe('Add token to LOCALHOST', function () {
|
||||
|
||||
it('Create custom token in LOCALHOST', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
tokenAddress = await f.createToken(account1, token, true)
|
||||
console.log('Token contract address: ' + tokenAddress)
|
||||
assert.equal(tokenAddress.length, 42, 'failed to create token')
|
||||
})
|
||||
|
||||
it('navigates to the add token screen', async () => {
|
||||
await f.waitUntilShowUp(screens.main.identicon)
|
||||
const tab = await f.waitUntilShowUp(screens.main.tokens.menu)
|
||||
await tab.click()
|
||||
const addTokenButton = await f.waitUntilShowUp(screens.main.tokens.buttonAdd2)
|
||||
assert.equal(await addTokenButton.getText(), screens.main.tokens.buttonAddText)
|
||||
await f.click(addTokenButton)
|
||||
await f.delay(2000)
|
||||
})
|
||||
|
||||
it('checks add token screen has correct title', async () => {
|
||||
const addTokenScreen = await f.waitUntilShowUp(screens.addToken.title)
|
||||
assert.equal(await addTokenScreen.getText(), screens.addToken.titleText)
|
||||
})
|
||||
|
||||
it('adds token parameters', async () => {
|
||||
const tab = await f.waitUntilShowUp(screens.addToken.tab.custom, 30)
|
||||
if (!await f.waitUntilShowUp(screens.addToken.custom.fields.contractAddress)) await tab.click()
|
||||
})
|
||||
|
||||
it('address input is displayed and has correct placeholder', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.custom.fields.contractAddress)
|
||||
assert.equal(await field.getAttribute('placeholder'), 'Token Contract Address', 'incorrect placeholder')
|
||||
})
|
||||
|
||||
it('fill out address input', async () => {
|
||||
const tokenContractAddress = await f.waitUntilShowUp(screens.addToken.custom.fields.contractAddress)
|
||||
await tokenContractAddress.sendKeys(tokenAddress)
|
||||
await f.delay(2000)
|
||||
})
|
||||
|
||||
it('field \'Symbol\' enabled and has correct value', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.custom.fields.tokenSymbol)
|
||||
assert.equal(await field.isEnabled(), true, 'field disabled')
|
||||
assert.equal(await field.getAttribute('placeholder'), 'Like "ETH"', 'incorrect placeholder')
|
||||
assert.equal(await field.getAttribute('value'), token.ticker, 'incorrect value')
|
||||
})
|
||||
|
||||
it('field \'Decimals\' enabled and has correct value', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.custom.fields.decimals)
|
||||
assert.equal(await field.isEnabled(), false, 'field disabled')
|
||||
assert.equal(await field.getAttribute('value'), token.decimals, 'incorrect value')
|
||||
})
|
||||
|
||||
it('checks the token balance', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.custom.buttons.add)
|
||||
await f.click(button)
|
||||
await f.delay(2000)
|
||||
const tokenBalance = await f.waitUntilShowUp(screens.main.tokens.balance)
|
||||
assert.equal(await tokenBalance.getText(), token.supply + ' ' + token.ticker, 'balance is incorrect or not displayed')
|
||||
})
|
||||
|
||||
it('click to token opens the etherscan', async () => {
|
||||
const link = await f.waitUntilShowUp(screens.main.tokens.token)
|
||||
await link.click()
|
||||
await f.delay(2000)
|
||||
const allHandles = await f.driver.getAllWindowHandles()
|
||||
console.log('allHandles.length ' + allHandles.length)
|
||||
assert.equal(allHandles.length, 2, 'etherscan wasn\'t opened')
|
||||
await f.switchToLastPage()
|
||||
await f.delay(2000)
|
||||
const title = await f.waitUntilCurrentUrl()
|
||||
console.log(title)
|
||||
assert.equal(title.includes('https://etherscan.io/token/'), true, 'etherscan wasn\'t opened')
|
||||
await f.switchToFirstPage()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Token menu', function () {
|
||||
|
||||
it('token menu is displayed and clickable ', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
})
|
||||
|
||||
it('link \'View on blockexplorer...\' leads to correct page ', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.token.view)
|
||||
assert.notEqual(menu, false, 'item isn\'t displayed')
|
||||
assert.equal(await menu.getText(), menus.token.viewText, 'incorrect name')
|
||||
await menu.click()
|
||||
await f.delay(2000)
|
||||
const allHandles = await f.driver.getAllWindowHandles()
|
||||
console.log('allHandles.length ' + allHandles.length)
|
||||
assert.equal(allHandles.length, 3, 'etherscan wasn\'t opened')
|
||||
await f.switchToLastPage()
|
||||
const title = await f.waitUntilCurrentUrl()
|
||||
|
||||
console.log(title)
|
||||
assert.equal(title.includes('https://etherscan.io/token/'), true, 'etherscan wasn\'t opened')
|
||||
await f.switchToFirstPage()
|
||||
})
|
||||
|
||||
it('item \'Copy\' is displayed and clickable ', async () => {
|
||||
let menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.token.copy)
|
||||
assert.notEqual(item, false, 'item isn\'t displayed')
|
||||
assert.equal(await item.getText(), menus.token.copyText, 'incorrect name')
|
||||
await item.click()
|
||||
menu = await f.waitUntilShowUp(menus.token.menu, 10)
|
||||
assert.notEqual(menu, false, 'menu wasn\'t closed')
|
||||
})
|
||||
|
||||
it('item \'Remove\' is displayed', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.token.remove)
|
||||
assert.notEqual(item, false, 'item isn\'t displayed')
|
||||
assert.equal(await item.getText(), menus.token.removeText, 'incorrect name')
|
||||
})
|
||||
|
||||
it('item \'Send \' is displayed', async () => {
|
||||
const item = await f.waitUntilShowUp(menus.token.send)
|
||||
assert.notEqual(item, false, 'item isn\'t displayed')
|
||||
assert.equal(await item.getText(), menus.token.sendText, 'incorrect name')
|
||||
await f.waitUntilShowUp(menus.token.menu)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check support of token per network basis ', async () => {
|
||||
const inexistentToken = '0xB8c77482e45F1F44dE1745F52C74426C631bDD51'
|
||||
describe('Token should be displayed only for network, where it was added ', async () => {
|
||||
|
||||
it('token should not be displayed in POA network', async () => {
|
||||
await f.setProvider(NETWORKS.POA)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in SOKOL testnet', async () => {
|
||||
await f.setProvider(NETWORKS.SOKOL)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in MAINNET', async () => {
|
||||
await f.setProvider(NETWORKS.MAINNET)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in ROPSTEN testnet', async () => {
|
||||
await f.setProvider(NETWORKS.ROPSTEN)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in KOVAN testnet', async () => {
|
||||
await f.setProvider(NETWORKS.KOVAN)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in RINKEBY testnet', async () => {
|
||||
await f.setProvider(NETWORKS.RINKEBY)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('Custom tokens validation ', async () => {
|
||||
|
||||
it('can not add inexistent token to POA network', async () => {
|
||||
await f.setProvider(NETWORKS.POA)
|
||||
console.log(tokenAddress)
|
||||
assert(await f.isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in POA network')
|
||||
})
|
||||
|
||||
it('can not add inexistent token to SOKOL testnet', async () => {
|
||||
await f.setProvider(NETWORKS.SOKOL)
|
||||
assert(await f.isDisabledAddInexistentToken(inexistentToken), true, 'can add inexistent token in SOKOL testnet')
|
||||
})
|
||||
|
||||
it('can not add inexistent token to ROPSTEN testnet', async () => {
|
||||
await f.setProvider(NETWORKS.ROPSTEN)
|
||||
assert(await f.isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in ROPSTEN testnet')
|
||||
})
|
||||
|
||||
it('can not add inexistent token to KOVAN testnet', async () => {
|
||||
await f.setProvider(NETWORKS.KOVAN)
|
||||
assert(await f.isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in KOVAN testnet')
|
||||
})
|
||||
|
||||
it('can not add inexistent token to RINKEBY testnet', async () => {
|
||||
await f.setProvider(NETWORKS.RINKEBY)
|
||||
assert(await f.isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in RINKEBY testnet')
|
||||
})
|
||||
|
||||
it('can not add inexistent token to MAINNET', async () => {
|
||||
await f.setProvider(NETWORKS.MAINNET)
|
||||
assert(await f.isDisabledAddInexistentToken(tokenAddress), true, 'can add inexistent token in MAINNET')
|
||||
})
|
||||
|
||||
it('can not add inexistent token to LOCALHOST network', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
assert(await f.isDisabledAddInexistentToken(tokenAddress.slice(0, tokenAddress.length - 2) + '0'), true, 'can add inexistent token in LOCALHOST network')
|
||||
})
|
||||
|
||||
it('token still should be displayed in LOCALHOST network', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
await f.waitUntilDisappear(screens.main.tokens.amount)
|
||||
assert.notEqual(await f.waitUntilShowUp(screens.main.tokens.amount), false, 'App is frozen')
|
||||
const tokens = await f.driver.findElements(screens.main.tokens.amount)
|
||||
assert.equal(tokens.length, 1, '\'Tokens\' section doesn\'t contain field with amount of tokens')
|
||||
assert.equal(await tokens[0].getText(), screens.main.tokens.textYouOwn1token, 'Token isn\'t displayed')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Transfer tokens', function () {
|
||||
|
||||
const invalidAddress = '0xkqjefwblknnecwe'
|
||||
const invalidAmount = 'eeeee'
|
||||
const largeAmount = '123'
|
||||
const preciseAmount = '0.123456789123456789123'
|
||||
const negativeAmount = '-1'
|
||||
|
||||
it('switch to account 1 ', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.account1)
|
||||
await item.click()
|
||||
await f.delay(2000)
|
||||
const accountName = await f.waitUntilShowUp(screens.main.accountName)
|
||||
assert.equal(await accountName.getText(), 'Account 1', 'account name incorrect')
|
||||
})
|
||||
|
||||
it('open screen \'Transfer tokens\' ', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.token.send)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
it('field \'Amount\' is displayed and has correct placeholder ', async () => {
|
||||
const item = await f.waitUntilShowUp(screens.sendTokens.field.amount)
|
||||
assert.equal(await item.getAttribute('placeholder'), screens.sendTokens.field.amountPlaceholder, 'placeholder is incorrect')
|
||||
})
|
||||
|
||||
it('field \'Address\' is displayed and has correct placeholder ', async () => {
|
||||
const item = await f.waitUntilShowUp(screens.sendTokens.field.address)
|
||||
assert.equal(await item.getAttribute('placeholder'), screens.sendTokens.field.addressPlaceholder, 'placeholder is incorrect')
|
||||
})
|
||||
|
||||
it('token\'s balance is correct ', async () => {
|
||||
const item = await f.waitUntilShowUp(screens.sendTokens.balance)
|
||||
assert.equal(await item.getText(), token.supply, 'token\'s balance is incorrect')
|
||||
})
|
||||
|
||||
it('token\'s symbol is correct ', async () => {
|
||||
const item = await f.waitUntilShowUp(screens.sendTokens.symbol)
|
||||
assert.equal(await item.getText(), token.ticker, 'token\'s symbol is incorrect')
|
||||
})
|
||||
|
||||
it('error message if invalid token\'s amount', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.sendTokens.button.next)
|
||||
assert.equal(await button.getText(), 'Next', 'button \'Next\' has incorrect name')
|
||||
await f.click(button)
|
||||
const error = await f.waitUntilShowUp(screens.sendTokens.error)
|
||||
assert.equal(await error.getText(), screens.sendTokens.errorText.invalidAmount, ' error message is incorrect')
|
||||
})
|
||||
|
||||
it('error message if invalid address', async () => {
|
||||
const amount = await f.waitUntilShowUp(screens.sendTokens.field.amount)
|
||||
await amount.sendKeys('1')
|
||||
const address = await f.waitUntilShowUp(screens.sendTokens.field.address)
|
||||
await address.sendKeys(invalidAddress)
|
||||
const button = await f.waitUntilShowUp(screens.sendTokens.button.next)
|
||||
await f.click(button)
|
||||
await f.click(button)
|
||||
await f.delay(2000)
|
||||
const error = await f.waitUntilShowUp(screens.sendTokens.error)
|
||||
assert.equal(await error.getText(), screens.sendTokens.errorText.address, ' error message is incorrect')
|
||||
})
|
||||
|
||||
it('error message if amount is large', async () => {
|
||||
const amount = await f.waitUntilShowUp(screens.sendTokens.field.amount)
|
||||
await amount.sendKeys(largeAmount)
|
||||
const address = await f.waitUntilShowUp(screens.sendTokens.field.address)
|
||||
await f.clearField(address)
|
||||
await address.sendKeys(account2)
|
||||
const button = await f.waitUntilShowUp(screens.sendTokens.button.next)
|
||||
await f.click(button)
|
||||
await f.click(button)
|
||||
await f.delay(2000)
|
||||
const error = await f.waitUntilShowUp(screens.sendTokens.error)
|
||||
assert.equal(await error.getText(), screens.sendTokens.errorText.largeAmount, ' error message is incorrect')
|
||||
})
|
||||
|
||||
it('error message if amount is invalid', async () => {
|
||||
const amount = await f.waitUntilShowUp(screens.sendTokens.field.amount)
|
||||
await f.clearField(amount)
|
||||
await amount.sendKeys(invalidAmount)
|
||||
const button = await f.waitUntilShowUp(screens.sendTokens.button.next)
|
||||
await f.click(button)
|
||||
await f.click(button)
|
||||
await f.delay(2000)
|
||||
const error = await f.waitUntilShowUp(screens.sendTokens.error)
|
||||
assert.equal(await error.getText(), screens.sendTokens.errorText.invalidAmount, ' error message is incorrect')
|
||||
})
|
||||
|
||||
it.skip('error message if amount is too precise', async () => {
|
||||
const amount = await f.waitUntilShowUp(screens.sendTokens.field.amount)
|
||||
await f.clearField(amount)
|
||||
await amount.sendKeys(preciseAmount)
|
||||
const button = await f.waitUntilShowUp(screens.sendTokens.button.next)
|
||||
await f.click(button)
|
||||
await f.click(button)
|
||||
await f.delay(2000)
|
||||
const error = await f.waitUntilShowUp(screens.sendTokens.error)
|
||||
assert.equal(await error.getText(), screens.sendTokens.errorText.tooPrecise, ' error message is incorrect')
|
||||
})
|
||||
|
||||
it('error message if amount is negative', async () => {
|
||||
const amount = await f.waitUntilShowUp(screens.sendTokens.field.amount)
|
||||
await f.clearField(amount)
|
||||
await amount.sendKeys(negativeAmount)
|
||||
const button = await f.waitUntilShowUp(screens.sendTokens.button.next)
|
||||
await f.click(button)
|
||||
await f.click(button)
|
||||
await f.delay(2000)
|
||||
const error = await f.waitUntilShowUp(screens.sendTokens.error)
|
||||
assert.equal(await error.getText(), screens.sendTokens.errorText.negativeAmount, ' error message is incorrect')
|
||||
})
|
||||
|
||||
it('\'Confirm transaction\' screen is opened if address and amount are correct', async () => {
|
||||
const amount = await f.waitUntilShowUp(screens.sendTokens.field.amount)
|
||||
await f.clearField(amount)
|
||||
await amount.sendKeys('5')
|
||||
const button = await f.waitUntilShowUp(screens.sendTokens.button.next)
|
||||
await f.click(button)
|
||||
|
||||
const buttonSubmit = await f.waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||
assert.notEqual(buttonSubmit, false, 'incorrect screen was opened')
|
||||
})
|
||||
|
||||
it('\'Confirm transaction\' screen: token\'s amount is correct', async () => {
|
||||
const amount = await f.waitUntilShowUp(screens.confirmTransaction.amount)
|
||||
assert.equal(await amount.getText(), '5.000', ' amount is incorrect')
|
||||
})
|
||||
|
||||
it('\'Confirm transaction\' screen: token\'s symbol is correct', async () => {
|
||||
const symbol = await f.waitUntilShowUp(screens.confirmTransaction.symbol)
|
||||
assert.equal(await symbol.getText(), token.ticker, ' symbol is incorrect')
|
||||
})
|
||||
|
||||
it('submit transaction', async () => {
|
||||
await f.driver.navigate().refresh()
|
||||
const button = await f.waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||
await f.click(button)
|
||||
const list = await f.waitUntilShowUp(screens.main.transactionList)
|
||||
assert.notEqual(list, false, ' main screen isn\'t opened')
|
||||
})
|
||||
|
||||
it('correct amount substracted from sender\'s tokens balance', async () => {
|
||||
const tab = await f.waitUntilShowUp(screens.main.tokens.menu)
|
||||
await tab.click()
|
||||
await f.driver.navigate().refresh()
|
||||
await f.delay(5000)
|
||||
await f.driver.navigate().refresh()
|
||||
await f.delay(5000)
|
||||
await f.driver.navigate().refresh()
|
||||
await f.delay(5000)
|
||||
const balance = await f.waitUntilShowUp(screens.main.tokens.balance)
|
||||
assert.equal(await balance.getText(), (token.supply - 5) + ' ' + token.ticker, 'balance is incorrect')
|
||||
})
|
||||
|
||||
it('switch to account 2 ', async () => {
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.account2)
|
||||
await item.click()
|
||||
await f.delay(2000)
|
||||
const accountName = await f.waitUntilShowUp(screens.main.accountName)
|
||||
assert.equal(await accountName.getText(), 'Account 2', 'account name incorrect')
|
||||
})
|
||||
|
||||
it('added token isn\'t displayed for another account in the same network', async () => {
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.createAccount)
|
||||
await item.click()
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
await f.delay(2000)
|
||||
})
|
||||
|
||||
it('add token to another account in the same network', async () => {
|
||||
const addTokenButton = await f.waitUntilShowUp(screens.main.tokens.buttonAdd)
|
||||
assert.equal(await addTokenButton.getText(), screens.main.tokens.buttonAddText)
|
||||
await f.click(addTokenButton)
|
||||
|
||||
const tokenContractAddress = await f.waitUntilShowUp(screens.addToken.custom.fields.contractAddress)
|
||||
await tokenContractAddress.sendKeys(tokenAddress)
|
||||
|
||||
const buttonAdd = await f.waitUntilShowUp(screens.addToken.custom.buttons.add)
|
||||
await f.click(buttonAdd)
|
||||
})
|
||||
|
||||
it('tokens were transfered, balance is updated', async () => {
|
||||
const balance = await f.waitUntilShowUp(screens.main.tokens.balance)
|
||||
assert.equal(await balance.getText(), '5 ' + token.ticker, 'balance is incorrect')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Remove token, provider is localhost', function () {
|
||||
|
||||
it('switch to account 1 ', async () => {
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.account1)
|
||||
await item.click()
|
||||
await f.delay(2000)
|
||||
const accountName = await f.waitUntilShowUp(screens.main.accountName)
|
||||
assert.equal(await accountName.getText(), 'Account 1', 'account name incorrect')
|
||||
})
|
||||
|
||||
it('remove option opens \'Remove token\' screen ', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
const menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
const remove = await f.waitUntilShowUp(menus.token.remove)
|
||||
await remove.click()
|
||||
})
|
||||
|
||||
it('screen \'Remove token\' has correct title', async () => {
|
||||
const title = await f.waitUntilShowUp(screens.removeToken.title)
|
||||
assert.equal(await title.getText(), screens.removeToken.titleText, 'title is incorrect')
|
||||
})
|
||||
|
||||
it('screen \'Remove token\' has correct label', async () => {
|
||||
const title = await f.waitUntilShowUp(screens.removeToken.label)
|
||||
assert.equal((await title.getText()).includes(screens.removeToken.labelText + token.ticker), true, 'label is incorrect')
|
||||
})
|
||||
|
||||
it('button "No" bring back to "Main" screen', async () => {
|
||||
const title = await f.waitUntilShowUp(screens.removeToken.title)
|
||||
assert.equal(await title.getText(), screens.removeToken.titleText, 'title is incorrect')
|
||||
const button = await f.waitUntilShowUp(screens.removeToken.buttons.no)
|
||||
assert.notEqual(button, false, 'button \'No\' isn\'t displayed ')
|
||||
assert.equal(await button.getText(), 'No', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const token = await f.waitUntilShowUp(screens.main.tokens.balance)
|
||||
assert.notEqual(await token.getText(), '', 'token is disapeared after return from remove token screen ')
|
||||
})
|
||||
|
||||
it('button "Yes" delete token', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
const remove = await f.waitUntilShowUp(menus.token.remove)
|
||||
await remove.click()
|
||||
|
||||
const title = await f.waitUntilShowUp(screens.removeToken.title)
|
||||
assert.equal(await title.getText(), screens.removeToken.titleText, 'title is incorrect')
|
||||
|
||||
const button = await f.waitUntilShowUp(screens.removeToken.buttons.yes)
|
||||
assert.notEqual(button, false, 'button \'Yes\' isn\'t displayed ')
|
||||
assert.equal(await button.getText(), 'Yes', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('check if token was removed from SOKOL network', async () => {
|
||||
await f.setProvider(NETWORKS.SOKOL)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('check if token was removed from KOVAN network', async () => {
|
||||
await f.setProvider(NETWORKS.KOVAN)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('check if token was removed from ROPSTEN network', async () => {
|
||||
await f.setProvider(NETWORKS.ROPSTEN)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('check if token was removed from MAINNET network', async () => {
|
||||
await f.setProvider(NETWORKS.MAINNET)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('check if token was removed from POA network', async () => {
|
||||
await f.setProvider(NETWORKS.POA)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('check if token was removed from RINKEBY network', async () => {
|
||||
await f.setProvider(NETWORKS.RINKEBY)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = addCustomToken
|
|
@ -0,0 +1,291 @@
|
|||
const assert = require('assert')
|
||||
const { screens, menus, elements, NETWORKS } = require('../elements')
|
||||
|
||||
const addTokeFromSearch = async (f) => {
|
||||
const request = {
|
||||
valid: 'cry',
|
||||
invalid: 'zzz',
|
||||
notExistingAddress: '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC',
|
||||
}
|
||||
const Qtum = {
|
||||
name: 'Qtum (QTUM)',
|
||||
address: '0x9a642d6b3368ddc662CA244bAdf32cDA716005BC',
|
||||
}
|
||||
|
||||
describe('add Mainnet\'s tokens', function () {
|
||||
|
||||
it(' field \'Search\' is displayed', async () => {
|
||||
await f.setProvider(NETWORKS.MAINNET)
|
||||
await f.delay(2000)
|
||||
const tab = await f.waitUntilShowUp(screens.main.tokens.menu)
|
||||
await tab.click()
|
||||
const button = await f.waitUntilShowUp(screens.main.tokens.buttonAdd2, 300)
|
||||
await f.click(button)
|
||||
const field = await f.waitUntilShowUp(screens.addToken.search.fieldSearch)
|
||||
assert.notEqual(field, false, 'field \'Search\' isn\'t displayed')
|
||||
})
|
||||
|
||||
it('button \'Next\' is disabled if no tokens found', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
assert.equal(await button.isEnabled(), false, 'button is enabled')
|
||||
assert.equal(await button.getText(), 'Next', 'button has incorrect name')
|
||||
})
|
||||
|
||||
it('button \'Cancel\' is enabled and lead to main screen ', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.cancel)
|
||||
assert.equal(await button.isEnabled(), true, 'button isn\'t enabled')
|
||||
assert.equal(await button.getText(), 'Cancel', 'button has incorrect name')
|
||||
})
|
||||
|
||||
it('Search by name: searching result list is empty if request invalid', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.search.fieldSearch)
|
||||
await field.sendKeys(request.invalid)
|
||||
const list = await f.waitUntilShowUp(screens.addToken.search.token.unselected, 20)
|
||||
assert.equal(list, false, 'unexpected tokens are displayed')
|
||||
})
|
||||
|
||||
it('Search by name: searching result list isn\'t empty ', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.search.fieldSearch)
|
||||
await f.clearField(field)
|
||||
await field.sendKeys(request.valid)
|
||||
await f.waitUntilShowUp(screens.addToken.search.token.unselected)
|
||||
const list = await f.driver.findElements(screens.addToken.search.token.unselected)
|
||||
assert.notEqual(list, 0, 'tokens aren\'t displayed')
|
||||
})
|
||||
|
||||
it('Token\'s info contains name, symbol and picture ', async () => {
|
||||
const tokens = await f.driver.findElements(screens.addToken.search.token.unselected)
|
||||
const names = await f.driver.findElements(screens.addToken.search.token.name)
|
||||
const icons = await f.driver.findElements(screens.addToken.search.token.icon)
|
||||
assert.equal(tokens.length, names.length, 'some names are missed')
|
||||
assert.equal(tokens.length, icons.length, 'some icons are missed')
|
||||
})
|
||||
|
||||
it('button \'Next\' is disabled if no one token is selected', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
assert.equal(await button.isEnabled(), false, 'button is enabled')
|
||||
})
|
||||
|
||||
it('user can select one token', async () => {
|
||||
const token = await f.waitUntilShowUp(screens.addToken.search.token.unselected)
|
||||
await token.click()
|
||||
})
|
||||
|
||||
it('button \'Next\' is enabled if token is selected', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
assert.equal(await button.isEnabled(), true, 'button is disabled')
|
||||
})
|
||||
|
||||
it('user can unselected token', async () => {
|
||||
const token = await f.waitUntilShowUp(screens.addToken.search.token.selected)
|
||||
await token.click()
|
||||
})
|
||||
|
||||
it('button \'Next\' is disabled after token was unselected', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
assert.equal(await button.isEnabled(), false, 'button is enabled')
|
||||
})
|
||||
|
||||
it('user can select two tokens', async () => {
|
||||
await f.waitUntilShowUp(screens.addToken.search.token.unselected)
|
||||
const tokensUnselected = await f.driver.findElements(screens.addToken.search.token.unselected)
|
||||
await tokensUnselected[0].click()
|
||||
await tokensUnselected[2].click()
|
||||
const tokensSelected = await f.driver.findElements(screens.addToken.search.token.selected)
|
||||
assert.equal(tokensSelected.length, 2, 'user can\'t select 2 tokens')
|
||||
})
|
||||
|
||||
it('click button \'Next\' opens confirm screen ', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
await f.click(button)
|
||||
const buttonAdd = await f.waitUntilShowUp(screens.addToken.search.confirm.button.add)
|
||||
assert.notEqual(buttonAdd, false, 'failed to open screen confirmation')
|
||||
})
|
||||
|
||||
it('confirm screen: two selected tokens are displayed and have correct parameters', async () => {
|
||||
const tokens = await f.driver.findElements(screens.addToken.search.confirm.token.item)
|
||||
assert.equal(tokens.length, 2, 'incorrect number of tokens are presented')
|
||||
|
||||
const names = await f.driver.findElements(screens.addToken.search.confirm.token.name)
|
||||
const name0 = await names[0].getText()
|
||||
const name1 = await names[1].getText()
|
||||
assert.equal(name0.length > 10, true, 'empty token name')
|
||||
assert.equal(name1.length > 10, true, 'empty token name')
|
||||
await f.delay(2000)
|
||||
const balances = await f.driver.findElements(screens.addToken.search.confirm.token.balance)
|
||||
const balance0 = await balances[1].getText()
|
||||
const balance1 = await balances[2].getText()
|
||||
assert.equal(balance0, '0', 'balance isn\'t 0')
|
||||
assert.equal(balance1, '0', 'balance isn\'t 0')
|
||||
})
|
||||
|
||||
it('button \'Back\' is enabled and leads to previous screen ', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.confirm.button.back)
|
||||
assert.equal(await button.isEnabled(), true, 'button isn\'t enabled')
|
||||
await f.click(button)
|
||||
const fieldSearch = await f.waitUntilShowUp(screens.addToken.search.fieldSearch)
|
||||
assert.notEqual(fieldSearch, false, 'add token screen didn\'t opened')
|
||||
})
|
||||
|
||||
it('button \'Next\' is enabled if confirmation list isn\'t empty', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
assert.equal(await button.isEnabled(), true, 'button is disabled')
|
||||
})
|
||||
|
||||
it('previous selected tokens remain selected after new search', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.search.fieldSearch)
|
||||
await f.clearField(field)
|
||||
await field.sendKeys(request.valid)
|
||||
await f.waitUntilShowUp(screens.addToken.search.token.selected)
|
||||
const listSelected = await f.driver.findElements(screens.addToken.search.token.selected)
|
||||
assert.equal(listSelected.length, 2, 'tokens are unselected')
|
||||
})
|
||||
|
||||
it('user can unselect token', async () => {
|
||||
const tokensUnselected = await f.driver.findElements(screens.addToken.search.token.unselected)
|
||||
assert.notEqual(tokensUnselected.length, 0, 'all tokens are selected')
|
||||
|
||||
let tokensSelected = await f.driver.findElements(screens.addToken.search.token.selected)
|
||||
await tokensSelected[0].click()
|
||||
const old = tokensSelected.length
|
||||
|
||||
tokensSelected = await f.driver.findElements(screens.addToken.search.token.selected)
|
||||
assert.equal(tokensSelected.length, old - 1, 'can\'t unselect token')
|
||||
})
|
||||
|
||||
it('confirm screen: unselected token aren\'t displayed', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
await f.click(button)
|
||||
await f.waitUntilShowUp(screens.addToken.search.confirm.token.item)
|
||||
const tokens = await f.driver.findElements(screens.addToken.search.confirm.token.item)
|
||||
assert.equal(tokens.length, 1, 'incorrect number of tokens are presented')
|
||||
const back = await f.waitUntilShowUp(screens.addToken.search.confirm.button.back)
|
||||
await f.click(back)
|
||||
})
|
||||
|
||||
it('Search by contract address: searching result list is empty if address invalid ', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.search.fieldSearch)
|
||||
await field.sendKeys(request.notExistingAddress)
|
||||
const list = await f.waitUntilShowUp(screens.addToken.search.token.unselected, 20)
|
||||
assert.equal(list, false, 'unexpected tokens are displayed')
|
||||
})
|
||||
|
||||
it('Search by valid contract address: searching result list contains one token ', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.addToken.search.fieldSearch)
|
||||
await f.clearField(field)
|
||||
await f.clearField(field)
|
||||
await field.sendKeys(Qtum.address)
|
||||
const token = await f.waitUntilShowUp(screens.addToken.search.token.unselected)
|
||||
const list = await f.driver.findElements(screens.addToken.search.token.unselected)
|
||||
assert.notEqual(list, 0, 'tokens aren\'t displayed')
|
||||
await token.click()
|
||||
})
|
||||
|
||||
it('Token\'s info contains correct name ', async () => {
|
||||
const name = await f.waitUntilShowUp(screens.addToken.search.token.name)
|
||||
assert.equal(await name.getText(), Qtum.name, 'incorrect token\'s name')
|
||||
})
|
||||
|
||||
it('one more token added to confirmation list', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.button.next)
|
||||
await f.click(button)
|
||||
await f.waitUntilShowUp(screens.addToken.search.confirm.token.item)
|
||||
const list = await f.driver.findElements(screens.addToken.search.confirm.token.item)
|
||||
assert.equal(list.length, 2, 'token wasn\'t added')
|
||||
})
|
||||
|
||||
it('button \'Add tokens\' is enabled and clickable', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.addToken.search.confirm.button.add)
|
||||
assert.equal(await button.isEnabled(), true, 'button isn\'t enabled')
|
||||
await f.click(button)
|
||||
const identicon = await f.waitUntilShowUp(screens.main.identicon)
|
||||
assert.notEqual(identicon, false, 'main screen didn\'t opened')
|
||||
})
|
||||
|
||||
it('all selected tokens are displayed on main screen', async () => {
|
||||
await f.waitUntilShowUp(screens.main.tokens.token)
|
||||
const tokens = await f.driver.findElements(screens.main.tokens.token)
|
||||
assert.equal(tokens.length, 2, 'tokens weren\'t added')
|
||||
})
|
||||
|
||||
it('correct value of counter of owned tokens', async () => {
|
||||
const counter = await f.waitUntilShowUp(screens.main.tokens.counter)
|
||||
assert.equal(await counter.getText(), 'You own 2 tokens', 'incorrect value of counter')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Token should be displayed only for network, where it was added ', async () => {
|
||||
|
||||
it('token should not be displayed in POA network', async () => {
|
||||
await f.setProvider(NETWORKS.POA)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in DAI network', async () => {
|
||||
await f.setProvider(NETWORKS.DAI)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in SOKOL testnet', async () => {
|
||||
await f.setProvider(NETWORKS.SOKOL)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in LOCALHOST network', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in ROPSTEN testnet', async () => {
|
||||
await f.setProvider(NETWORKS.ROPSTEN)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in KOVAN testnet', async () => {
|
||||
await f.setProvider(NETWORKS.KOVAN)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
|
||||
it('token should not be displayed in RINKEBY testnet', async () => {
|
||||
await f.setProvider(NETWORKS.RINKEBY)
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
})
|
||||
describe('remove Mainnet\'s tokens', function () {
|
||||
|
||||
it('remove tokens', async () => {
|
||||
|
||||
let menu
|
||||
let button
|
||||
let counter
|
||||
let buttonYes
|
||||
|
||||
await f.setProvider(NETWORKS.MAINNET)
|
||||
await f.waitUntilShowUp(elements.loader, 25)
|
||||
await f.waitUntilDisappear(elements.loader, 50)
|
||||
menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
button = await f.waitUntilShowUp(menus.token.remove)
|
||||
await button.click()
|
||||
buttonYes = await f.waitUntilShowUp(screens.removeToken.buttons.yes)
|
||||
await buttonYes.click()
|
||||
counter = await f.waitUntilShowUp(screens.main.tokens.counter)
|
||||
assert.equal(await counter.getText(), 'You own 1 token', 'incorrect value of counter')
|
||||
const tokensNumber = await f.driver.findElements(screens.main.tokens.token)
|
||||
assert.equal(tokensNumber.length, 1, 'incorrect amount of token\'s is displayed')
|
||||
|
||||
menu = await f.waitUntilShowUp(menus.token.menu)
|
||||
await menu.click()
|
||||
button = await f.waitUntilShowUp(menus.token.remove)
|
||||
await button.click()
|
||||
buttonYes = await f.waitUntilShowUp(screens.removeToken.buttons.yes)
|
||||
await buttonYes.click()
|
||||
counter = await f.waitUntilShowUp(screens.main.tokens.counter)
|
||||
assert.equal(await counter.getText(), 'No tokens found', 'incorrect value of counter')
|
||||
|
||||
assert.equal(await f.assertTokensNotDisplayed(), true, 'tokens are displayed')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = addTokeFromSearch
|
|
@ -0,0 +1,174 @@
|
|||
const assert = require('assert')
|
||||
const { screens, menus, NETWORKS } = require('../elements')
|
||||
|
||||
const changePassword = async (f, password, newPassword) => {
|
||||
let fieldNewPassword
|
||||
let fieldConfirmNewPassword
|
||||
let fieldOldPassword
|
||||
let buttonYes
|
||||
|
||||
describe('Check screen "Settings" -> "Change password" ', async () => {
|
||||
|
||||
it('checks if current network name (localhost) is correct', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
const menu = await f.waitUntilShowUp(menus.sandwich.menu, 300)
|
||||
await menu.click()
|
||||
const settings = await f.waitUntilShowUp(menus.sandwich.settings)
|
||||
await settings.click()
|
||||
const field = await f.waitUntilShowUp(screens.settings.currentNetwork)
|
||||
assert.equal(await field.getText(), 'http://localhost:8545', 'current network is incorrect')
|
||||
})
|
||||
|
||||
it('error should not be displayed', async () => {
|
||||
const error = await f.waitUntilShowUp(screens.settings.error, 10)
|
||||
assert.equal(error, false, 'improper error is displayed')
|
||||
})
|
||||
|
||||
it('checks if "Change password" button is present and enabled', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.sandwich.menu, 300)
|
||||
await menu.click()
|
||||
const settings = await f.waitUntilShowUp(menus.sandwich.settings)
|
||||
await settings.click()
|
||||
await f.waitUntilShowUp(screens.settings.fieldNewRPC)
|
||||
const buttons = await f.driver.findElements(screens.settings.buttons.changePassword)
|
||||
await f.scrollTo(buttons[0])
|
||||
assert.equal(buttons.length, 1, 'Button "Change password" is not present')
|
||||
assert.equal(await buttons[0].getText(), 'Change password', 'button has incorrect name')
|
||||
assert.equal(await buttons[0].isEnabled(), true, 'Button "Change password" is disabled')
|
||||
await f.click(buttons[0])
|
||||
})
|
||||
|
||||
it('screen has correct title', async () => {
|
||||
const title = await f.waitUntilShowUp(screens.changePassword.title)
|
||||
assert.equal(await title.getText(), screens.changePassword.titleText, '"Change password" screen contains incorrect title')
|
||||
})
|
||||
|
||||
it('screen contains correct label', async () => {
|
||||
await f.waitUntilShowUp(screens.changePassword.label)
|
||||
const labels = await f.driver.findElements(screens.changePassword.label)
|
||||
assert.equal(labels.length, 1, 'screen "Change password" doesn\'t contain label')
|
||||
assert.equal(await labels[0].getText(), screens.changePassword.labelText, 'label contains incorrect title')
|
||||
})
|
||||
|
||||
it('clicking the button "No" bring back to "Setting" screen ', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.changePassword.buttonNo)
|
||||
assert.equal(await button.getText(), 'No', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const title = await f.waitUntilShowUp(screens.settings.title)
|
||||
assert.equal(await title.getText(), screens.settings.titleText, 'button "No" doesnt open settings screen')
|
||||
const buttonChangePass = await f.driver.findElement(screens.settings.buttons.changePassword)
|
||||
await f.scrollTo(buttonChangePass)
|
||||
await f.click(buttonChangePass)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Validation of errors ', async () => {
|
||||
|
||||
before(async () => {
|
||||
fieldOldPassword = await f.waitUntilShowUp(screens.changePassword.fieldOldPassword)
|
||||
await fieldOldPassword.sendKeys(password)
|
||||
fieldNewPassword = await f.waitUntilShowUp(screens.changePassword.fieldNewPassword)
|
||||
fieldConfirmNewPassword = await f.waitUntilShowUp(screens.changePassword.fieldConfirmNewPassword)
|
||||
buttonYes = await f.waitUntilShowUp(screens.changePassword.buttonYes)
|
||||
})
|
||||
|
||||
it('error if new password shorter than 8 digits', async () => {
|
||||
await fieldNewPassword.sendKeys(newPassword.short)
|
||||
await fieldConfirmNewPassword.sendKeys(newPassword.short)
|
||||
assert.equal(await buttonYes.getText(), 'Yes', 'button has incorrect name')
|
||||
await f.click(buttonYes)
|
||||
await f.delay(2000)
|
||||
const errors = await f.driver.findElements(screens.changePassword.error)
|
||||
assert.equal(errors.length > 0, true, 'error isn\'t displayed')
|
||||
assert.equal(await errors[0].getText(), screens.changePassword.errorText.notLong, 'Error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('error if new password doesn\'t match confirmation', async () => {
|
||||
await f.clearField(fieldNewPassword)
|
||||
await f.clearField(fieldConfirmNewPassword)
|
||||
await fieldNewPassword.sendKeys(newPassword.correct)
|
||||
await fieldConfirmNewPassword.sendKeys(newPassword.incorrect)
|
||||
await f.click(buttonYes)
|
||||
await f.delay(2000)
|
||||
const errors = await f.driver.findElements(screens.changePassword.error)
|
||||
assert.equal(errors.length > 0, true, 'error isn\'t displayed')
|
||||
assert.equal(await errors[0].getText(), screens.changePassword.errorText.dontMatch, 'Error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('error if new password match old password', async () => {
|
||||
await f.clearField(fieldNewPassword)
|
||||
await f.clearField(fieldConfirmNewPassword)
|
||||
await fieldNewPassword.sendKeys(password)
|
||||
await fieldConfirmNewPassword.sendKeys(password)
|
||||
await f.click(buttonYes)
|
||||
await f.delay(2000)
|
||||
const errors = await f.driver.findElements(screens.changePassword.error)
|
||||
assert.equal(errors.length > 0, true, 'error isn\'t displayed')
|
||||
assert.equal(await errors[0].getText(), screens.changePassword.errorText.differ, 'Error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('error if old password incorrect', async () => {
|
||||
await f.clearField(fieldOldPassword)
|
||||
await fieldOldPassword.sendKeys(newPassword.incorrect)
|
||||
await f.click(buttonYes)
|
||||
await f.click(buttonYes)
|
||||
await f.delay(2000)
|
||||
const errors = await f.driver.findElements(screens.changePassword.error)
|
||||
assert.equal(errors.length > 0, true, 'error isn\'t displayed')
|
||||
assert.equal(await errors[0].getText(), screens.changePassword.errorText.incorrectPassword, 'Error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('no errors if old, new, confirm new passwords are correct; user can change password', async () => {
|
||||
await f.clearField(fieldNewPassword)
|
||||
await f.clearField(fieldOldPassword)
|
||||
await f.clearField(fieldConfirmNewPassword)
|
||||
|
||||
await fieldOldPassword.sendKeys(password)
|
||||
await fieldNewPassword.sendKeys(newPassword.correct)
|
||||
await fieldConfirmNewPassword.sendKeys(newPassword.correct)
|
||||
await f.click(buttonYes)
|
||||
await f.waitUntilShowUp(screens.settings.buttons.changePassword, 25)
|
||||
const buttons = await f.driver.findElements(screens.settings.buttons.changePassword)
|
||||
assert.equal(buttons.length, 1, 'Button "Change password" is not present')
|
||||
assert.equal(await buttons[0].isEnabled(), true, 'Button "Change password" is disabled')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check if new password is accepted', async () => {
|
||||
|
||||
it('user can log out', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.sandwich.menu)
|
||||
await menu.click()
|
||||
const itemLogOut = await f.waitUntilShowUp(menus.sandwich.logOut)
|
||||
await itemLogOut.click()
|
||||
const field = await f.waitUntilShowUp(screens.lock.fieldPassword)
|
||||
assert.notEqual(field, false, 'password box isn\'t present after logout')
|
||||
})
|
||||
|
||||
it('can\'t login with old password', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.lock.fieldPassword)
|
||||
await field.sendKeys(password)
|
||||
const button = await f.waitUntilShowUp(screens.lock.buttonLogin)
|
||||
await f.click(button)
|
||||
const error = await f.waitUntilShowUp(screens.lock.error)
|
||||
assert.notEqual(error, false, 'error isn\'t displayed if password incorrect')
|
||||
assert.equal(await error.getText(), screens.lock.errorText, 'error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('accepts new password after lock', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.lock.fieldPassword)
|
||||
await f.clearField(field)
|
||||
await field.sendKeys(newPassword.correct)
|
||||
const button = await f.waitUntilShowUp(screens.lock.buttonLogin)
|
||||
await f.click(button)
|
||||
|
||||
await f.waitUntilShowUp(screens.main.buttons.buy)
|
||||
const buttons = await f.driver.findElements(screens.main.buttons.buy)
|
||||
assert.equal(buttons.length, 1, 'main screen isn\'t displayed')
|
||||
assert.equal(await buttons[0].getText(), 'Buy', 'button has incorrect name')
|
||||
password = newPassword.correct
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = changePassword
|
|
@ -0,0 +1,68 @@
|
|||
const assert = require('assert')
|
||||
const { screens, menus, NETWORKS } = require('../elements')
|
||||
const eventsEmitter = 'https://vbaranov.github.io/event-listener-dapp/'
|
||||
|
||||
const checkEmittedEvents = async (f, account1, account2) => {
|
||||
it('emit event', async () => {
|
||||
await f.setProvider(NETWORKS.SOKOL)
|
||||
let account
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
account = account1
|
||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
account = account2
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.account2)
|
||||
await item.click()
|
||||
}
|
||||
|
||||
const balanceField = await f.waitUntilShowUp(screens.main.balance)
|
||||
await f.delay(2000)
|
||||
const balance = await balanceField.getText()
|
||||
console.log('Account = ' + account)
|
||||
console.log('Balance = ' + balance)
|
||||
assert.equal(parseFloat(balance) > 0.001, true, 'Balance of account ' + account + ' TOO LOW !!! Please refill with Sokol eth!!!!')
|
||||
await f.driver.get(eventsEmitter)
|
||||
const button = await f.waitUntilShowUp(screens.eventsEmitter.button)
|
||||
await button.click()
|
||||
await f.delay(1000)
|
||||
})
|
||||
|
||||
it('confirms transaction in MetaMask popup', async () => {
|
||||
const windowHandles = await f.driver.getAllWindowHandles()
|
||||
await f.driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
||||
await f.delay(5000)
|
||||
const gasPrice = await f.waitUntilShowUp(screens.confirmTransaction.fields.gasPrice)
|
||||
await gasPrice.sendKeys('10')
|
||||
const button = await f.waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||
await f.click(button)
|
||||
})
|
||||
|
||||
it('check number of events', async () => {
|
||||
const windowHandles = await f.driver.getAllWindowHandles()
|
||||
await f.driver.switchTo().window(windowHandles[0])
|
||||
await f.delay(5000)
|
||||
const event = await f.waitUntilShowUp(screens.eventsEmitter.event, 600)
|
||||
const events = await f.driver.findElements(screens.eventsEmitter.event)
|
||||
console.log('number of events = ' + events.length)
|
||||
if (!event) console.log("event wasn't created or transaction failed".toUpperCase())
|
||||
else {
|
||||
const events = await f.driver.findElements(screens.eventsEmitter.event)
|
||||
assert.equal(events.length, 1, 'More than 1 event was fired: ' + events.length + ' events')
|
||||
}
|
||||
})
|
||||
|
||||
it('open app', async () => {
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
await f.driver.get(`chrome-extension://${f.extensionId}/popup.html`)
|
||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
await f.driver.get(`moz-extension://${f.extensionId}/popup.html`)
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.account1)
|
||||
await item.click()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = checkEmittedEvents
|
|
@ -0,0 +1,101 @@
|
|||
const assert = require('assert')
|
||||
const { menus, screens } = require('../elements')
|
||||
|
||||
const connectHDWallet = async (f) => {
|
||||
it("Account menu contais item 'Connect HD wallet'", async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(menus.account.item)
|
||||
const items = await f.driver.findElements(menus.account.item)
|
||||
await f.delay(500)
|
||||
assert.equal(await items[4].getText(), 'Connect hardware wallet', "item's text incorrect")
|
||||
await items[4].click()
|
||||
})
|
||||
|
||||
|
||||
it("Opens screen 'Connect HD wallet',title is correct", async () => {
|
||||
const title = await f.waitUntilShowUp(screens.hdWallet.title)
|
||||
assert.equal(await title.getText(), 'Connect to hardware wallet', "item's text incorrect")
|
||||
})
|
||||
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
it("Button 'Connect' disabled by default", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.hdWallet.buttonConnect.disabled)
|
||||
assert.notEqual(button, false, "button isn't displayed")
|
||||
assert.equal(await button.getText(), 'CONNECT', 'button has incorrect text')
|
||||
})
|
||||
|
||||
it('Ledger image is displayed', async () => {
|
||||
const image = await f.waitUntilShowUp(screens.hdWallet.image)
|
||||
assert.notEqual(image, false, "ledger's image isn't displayed")
|
||||
const src = await image.getAttribute('src')
|
||||
assert.equal(src.includes('images/ledger-logo.svg'), true, 'Ledger has incorrect image')
|
||||
})
|
||||
|
||||
it('Trezor image is displayed', async () => {
|
||||
const images = await f.driver.findElements(screens.hdWallet.image)
|
||||
assert.notEqual(images[1], false, "trezor's image isn't displayed")
|
||||
const src = await images[1].getAttribute('src')
|
||||
assert.equal(src.includes('images/trezor-logo.svg'), true, 'Trezor has incorrect image')
|
||||
})
|
||||
|
||||
it("Button 'Connect' enabled if Trezor selected", async () => {
|
||||
const images = await f.driver.findElements(screens.hdWallet.image)
|
||||
await images[1].click()
|
||||
const button = await f.waitUntilShowUp(screens.hdWallet.buttonConnect.enabled)
|
||||
assert.equal(await button.isEnabled(), true, 'button is disabled')
|
||||
})
|
||||
|
||||
it("Button 'Connect' enabled if Ledger selected", async () => {
|
||||
const images = await f.driver.findElements(screens.hdWallet.image)
|
||||
await images[0].click()
|
||||
const button = await f.waitUntilShowUp(screens.hdWallet.buttonConnect.enabled)
|
||||
assert.equal(await button.isEnabled(), true, 'button is disabled')
|
||||
})
|
||||
|
||||
it('Only one device can be selected', async () => {
|
||||
const selected = await f.driver.findElements(screens.hdWallet.imageSelected)
|
||||
assert.equal(await selected.length, 1, 'more than one device is selected')
|
||||
})
|
||||
|
||||
it('Error message if connect Ledger', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.hdWallet.buttonConnect.enabled)
|
||||
await button.click()
|
||||
const error = await f.waitUntilShowUp(screens.hdWallet.error)
|
||||
const shouldBe = "TransportError: U2F browser support is needed for Ledger. Please use Chrome, Opera or Firefox with a U2F extension. Also make sure you're on an HTTPS connection"
|
||||
assert.equal(await error.getText(), shouldBe, 'error has incorrect text')
|
||||
})
|
||||
|
||||
it('Popup opens if connect Trezor', async () => {
|
||||
const images = await f.driver.findElements(screens.hdWallet.image)
|
||||
await images[1].click()
|
||||
const button = await f.waitUntilShowUp(screens.hdWallet.buttonConnect.enabled)
|
||||
await button.click()
|
||||
await f.delay(2000)
|
||||
const allHandles = await f.driver.getAllWindowHandles()
|
||||
assert.equal(allHandles.length, 2, "popup isn't opened")
|
||||
f.driver.switchTo().window(allHandles[1])
|
||||
await f.delay(2000)
|
||||
f.driver.close()
|
||||
f.driver.switchTo().window(allHandles[0])
|
||||
await f.delay(2000)
|
||||
assert.equal(allHandles.length, 2, "popup isn't opened")
|
||||
await f.switchToFirstPage()
|
||||
await f.driver.navigate().refresh()
|
||||
})
|
||||
}
|
||||
it('Button arrow leads to main screen', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(menus.account.item)
|
||||
const items = await f.driver.findElements(menus.account.item)
|
||||
await f.delay(500)
|
||||
await items[4].click()
|
||||
const arrow = await f.waitUntilShowUp(screens.hdWallet.buttonArrow)
|
||||
await arrow.click()
|
||||
const ident = await f.waitUntilShowUp(screens.main.identicon, 20)
|
||||
assert.notEqual(ident, false, "main screen isn't opened")
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = connectHDWallet
|
|
@ -0,0 +1,150 @@
|
|||
const assert = require('assert')
|
||||
const { screens, menus, NETWORKS } = require('../elements')
|
||||
|
||||
const customRPC = async (f) => {
|
||||
const invalidStringUrl = 'http://lwkdfowi**&#v er'
|
||||
const urlWithoutHttp = 'infura.com'
|
||||
const invalidEndpoint = 'http://abrakadabrawdjkwjeciwkasuhlvflwe.com'
|
||||
const correctRpcUrl = 'https://core.poa.network/test'
|
||||
|
||||
it('switches to settings screen through menu \'Network -> Custom RPC\'', async function () {
|
||||
await f.setProvider(NETWORKS.CUSTOM)
|
||||
const settings = await f.waitUntilShowUp(screens.settings.title)
|
||||
assert.equal(await settings.getText(), screens.settings.titleText, 'inappropriate screen is opened')
|
||||
})
|
||||
|
||||
it('error message if new Rpc url is invalid', async function () {
|
||||
const field = await f.waitUntilShowUp(screens.settings.fieldNewRPC)
|
||||
await field.sendKeys(invalidStringUrl)
|
||||
const button = await f.waitUntilShowUp(screens.settings.buttonSave)
|
||||
assert.equal(await button.getText(), 'Save', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
await f.delay(1000)
|
||||
assert.equal(await f.waitUntilShowUp(screens.settings.buttons.delete, 5), false, 'invalid Rpc was added')
|
||||
const errors = await f.driver.findElements(screens.settings.error)
|
||||
assert.equal(errors.length, 1, 'error isn\'t displayed if Rpc url incorrect')
|
||||
assert.equal(await errors[0].getText(), screens.settings.errors.invalidRpcUrl, 'error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('error message if new Rpc url has no HTTP/HTTPS prefix', async function () {
|
||||
const fieldRpc = await f.driver.findElement(screens.settings.fieldNewRPC)
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await fieldRpc.sendKeys(urlWithoutHttp)
|
||||
const button = await f.waitUntilShowUp(screens.settings.buttonSave)
|
||||
await f.click(button)
|
||||
await f.delay(1000)
|
||||
assert.equal(await f.waitUntilShowUp(screens.settings.buttons.delete, 5), false, 'invalid Rpc was added')
|
||||
const errors = await f.driver.findElements(screens.settings.error)
|
||||
assert.equal(errors.length, 1, 'error isn\'t displayed if Rpc url incorrect')
|
||||
assert.equal(await errors[0].getText(), screens.settings.errors.invalidHTTP, 'error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('error message if Rpc doesn\'t exist', async function () {
|
||||
const fieldRpc = await f.driver.findElement(screens.settings.fieldNewRPC)
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await fieldRpc.sendKeys(invalidEndpoint)
|
||||
const button = await f.waitUntilShowUp(screens.settings.buttonSave)
|
||||
await f.click(button)
|
||||
await f.delay(1000)
|
||||
assert.equal(await f.waitUntilShowUp(screens.settings.buttons.delete, 5), false, 'invalid Rpc was added')
|
||||
await f.waitUntilShowUp(screens.settings.error)
|
||||
const errors = await f.driver.findElements(screens.settings.error)
|
||||
assert.equal(errors.length, 1, 'error isn\'t displayed if Rpc url incorrect')
|
||||
assert.equal(await errors[0].getText(), screens.settings.errors.invalidRpcEndpoint, 'error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('user can add valid custom rpc', async function () {
|
||||
const fieldRpc = await f.driver.findElement(screens.settings.fieldNewRPC)
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await fieldRpc.sendKeys(correctRpcUrl + 0)
|
||||
await f.driver.findElement(screens.settings.buttonSave).click()
|
||||
await f.delay(20000)
|
||||
const customUrlElement = await f.waitUntilShowUp(screens.settings.currentNetwork)
|
||||
assert.equal(await customUrlElement.getText(), correctRpcUrl + 0, 'Added Url doesn\'t match')
|
||||
})
|
||||
|
||||
it('new added Rpc displayed in network dropdown menu', async function () {
|
||||
let menu = await f.waitUntilShowUp(screens.main.network)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.networks.addedCustomRpc)
|
||||
assert.equal(await item.getText(), correctRpcUrl + 0, 'Added custom Url isn\'t displayed ')
|
||||
menu = await f.waitUntilShowUp(screens.main.network)
|
||||
await menu.click()
|
||||
})
|
||||
|
||||
it('user can add four more valid custom rpc', async function () {
|
||||
const fieldRpc = await f.waitUntilShowUp(screens.settings.fieldNewRPC)
|
||||
const customUrlElement = await f.waitUntilShowUp(screens.settings.currentNetwork)
|
||||
for (let i = 1; i < 5; i++) {
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await f.clearField(fieldRpc)
|
||||
await fieldRpc.sendKeys(correctRpcUrl + i)
|
||||
await f.driver.findElement(screens.settings.buttonSave).click()
|
||||
await f.delay(5000)
|
||||
assert.equal(await customUrlElement.getText(), correctRpcUrl + i, '#' + i + ': Current RPC field contains incorrect URL')
|
||||
}
|
||||
})
|
||||
|
||||
it('new added Rpc displayed in network dropdown menu', async function () {
|
||||
let menu = await f.waitUntilShowUp(screens.main.network)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(menus.networks.addedCustomRpc)
|
||||
const items = await f.driver.findElements(menus.networks.addedCustomRpc)
|
||||
assert.equal(items.length, 5, 'Incorrect number of added RPC')
|
||||
|
||||
menu = await f.waitUntilShowUp(screens.main.network)
|
||||
await menu.click()
|
||||
})
|
||||
|
||||
it('click button \'Delete\' opens screen \'Delete Custom RPC\'', async function () {
|
||||
await f.delay(1000)
|
||||
const button = await f.waitUntilShowUp(screens.settings.buttons.delete, 10)
|
||||
assert.equal(await button.getText(), 'Delete', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const title = await f.waitUntilShowUp(screens.settings.title)
|
||||
assert.equal(await title.getText(), screens.deleteCustomRPC.titleText, 'inappropriate screen is opened')
|
||||
})
|
||||
|
||||
it('click button \'No\' opens screen \'Settings\'', async function () {
|
||||
const button = await f.waitUntilShowUp(screens.deleteCustomRPC.buttons.no)
|
||||
assert.equal(await button.getText(), 'No', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const title = await f.waitUntilShowUp(screens.settings.title)
|
||||
assert.equal(await title.getText(), screens.settings.titleText, 'inappropriate screen is opened')
|
||||
})
|
||||
|
||||
it('user able to delete custom rpc', async function () {
|
||||
const buttonDelete = await f.waitUntilShowUp(screens.settings.buttons.delete, 25)
|
||||
await f.click(buttonDelete)
|
||||
const yesButton = await f.waitUntilShowUp(screens.deleteCustomRPC.buttons.yes)
|
||||
assert.equal(await yesButton.getText(), 'Yes')
|
||||
await f.click(yesButton)
|
||||
const title = await f.waitUntilShowUp(screens.settings.title)
|
||||
assert.equal(await title.getText(), screens.settings.titleText, 'inappropriate screen is opened')
|
||||
})
|
||||
|
||||
it('deleted custom rpc isn\'t displayed in \'Settings\' screen', async function () {
|
||||
const currentNetwork = await f.waitUntilShowUp(screens.settings.currentNetwork)
|
||||
assert.equal(await currentNetwork.getText(), 'POA Network', 'custom Rpc is displayed after deletion')
|
||||
})
|
||||
|
||||
it('deleted custom rpc isn\'t displayed in network dropdown menu', async function () {
|
||||
await f.delay(2000)
|
||||
let menu = await f.waitUntilShowUp(screens.main.network)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(menus.networks.addedCustomRpc, 20)
|
||||
const items = await f.driver.findElements(menus.networks.addedCustomRpc)
|
||||
assert.equal(items.length, 4, 'deleted custom rpc is displayed in network dropdown menu')
|
||||
menu = await f.waitUntilShowUp(screens.main.network)
|
||||
await menu.click()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = customRPC
|
|
@ -0,0 +1,57 @@
|
|||
const assert = require('assert')
|
||||
const { menus, screens } = require('../elements')
|
||||
const { account } = menus
|
||||
const { deleteImportedAccount: deleteImportedAccountScr, settings } = screens
|
||||
|
||||
const deleteImportedAccount = async (f) => {
|
||||
it('Open delete imported account screen', async function () {
|
||||
const menu = await f.waitUntilShowUp(account.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(account.delete)
|
||||
await item.click()
|
||||
const deleteImportedAccountTitle = await f.waitUntilShowUp(deleteImportedAccountScr.title)
|
||||
assert.equal(await deleteImportedAccountTitle.getText(), deleteImportedAccountScr.titleText)
|
||||
})
|
||||
|
||||
it("Can't remove imported account with 'No' button", async function () {
|
||||
const button = await f.waitUntilShowUp(deleteImportedAccountScr.buttons.no)
|
||||
assert.equal(await button.getText(), 'No', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const settingsTitle = await f.waitUntilShowUp(settings.title)
|
||||
assert.equal(await settingsTitle.getText(), 'Settings')
|
||||
// check, that imported account still exists
|
||||
const menu = await f.waitUntilShowUp(account.menu)
|
||||
await menu.click()
|
||||
await f.delay(2000)
|
||||
const label = await f.waitUntilShowUp(account.label)
|
||||
assert.equal(await label.getText(), 'IMPORTED')
|
||||
})
|
||||
|
||||
it('Open delete imported account screen again', async function () {
|
||||
const menu = await f.waitUntilShowUp(account.menu)
|
||||
await menu.click()
|
||||
await f.delay(2000)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(account.delete)
|
||||
const buttons = await f.driver.findElements(account.delete)
|
||||
assert.notEqual(buttons[0], false, "icon 'remove' isn't displayed")
|
||||
await buttons[0].click()
|
||||
})
|
||||
|
||||
it("Remove imported account with 'Yes' button", async function () {
|
||||
const button = await f.waitUntilShowUp(deleteImportedAccountScr.buttons.yes)
|
||||
assert.equal(await button.getText(), 'Yes', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const settingsTitle = await f.waitUntilShowUp(settings.title)
|
||||
assert.equal(await settingsTitle.getText(), 'Settings', "screen 'Settings' has incorrect title")
|
||||
// check, that imported account is removed
|
||||
const menu = await f.waitUntilShowUp(account.menu)
|
||||
await menu.click()
|
||||
await f.delay(3000)
|
||||
const label = await f.waitUntilShowUp(account.label, 25)
|
||||
assert.equal(label, false, "account isn't deleted")
|
||||
await menu.click()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = deleteImportedAccount
|
|
@ -0,0 +1,84 @@
|
|||
const assert = require('assert')
|
||||
const clipboardy = require('clipboardy')
|
||||
const { screens, menus } = require('../elements')
|
||||
|
||||
const exportPrivateKey = async (f, password) => {
|
||||
it('open dialog', async () => {
|
||||
await f.driver.navigate().refresh()
|
||||
const menu = await f.waitUntilShowUp(menus.dot.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.dot.exportPR)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
it('warning is displayed', async () => {
|
||||
await f.waitUntilShowUp(screens.exportPR.error)
|
||||
const error = await f.driver.findElements(screens.exportPR.error)
|
||||
assert.equal(error.length, 1, 'warning isn\'t present')
|
||||
assert.equal(await error[0].getText(), screens.exportPR.warningText, 'warning\'s text incorrect')
|
||||
})
|
||||
|
||||
it('button \'Cancel\' leads back to main screen', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.exportPR.button.cancel)
|
||||
assert.equal(await button.getText(), 'Cancel', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const field = await f.waitUntilShowUp(screens.exportPR.fieldPassword, 20)
|
||||
assert.equal(field, false, 'field \'password\' is displayed after closing')
|
||||
})
|
||||
|
||||
it('error message if password incorrect', async () => {
|
||||
await f.driver.navigate().refresh()
|
||||
const menu = await f.waitUntilShowUp(menus.dot.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.dot.exportPR)
|
||||
await item.click()
|
||||
const field = await f.waitUntilShowUp(screens.exportPR.fieldPassword)
|
||||
await field.sendKeys('abrakadabr')
|
||||
const button = await f.waitUntilShowUp(screens.exportPR.button.submit)
|
||||
assert.equal(await button.getText(), 'Submit', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
await f.delay(500)
|
||||
const error = await f.driver.findElements(screens.exportPR.error)
|
||||
assert.equal(error.length, 2, 'warning isn\'t present')
|
||||
assert.equal(await error[1].getText(), screens.exportPR.errorText, 'error\'s text incorrect')
|
||||
})
|
||||
|
||||
it('private key is shown if password correct', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.exportPR.fieldPassword)
|
||||
await f.clearField(field)
|
||||
await field.sendKeys(password)
|
||||
const button = await f.waitUntilShowUp(screens.exportPR.button.submit)
|
||||
await f.click(button)
|
||||
const key = await f.waitUntilShowUp(screens.yourPR.key)
|
||||
const pr = await key.getText()
|
||||
assert.equal(pr.length, 32 * 2, 'private key isn\'t displayed')
|
||||
})
|
||||
|
||||
it('icon copy cliboard is displayed and clickable', async () => {
|
||||
await f.waitUntilShowUp(screens.yourPR.copy)
|
||||
const icons = await f.driver.findElements(screens.yourPR.copy)
|
||||
assert.notEqual(icons[1], false, 'icon copy isn\'t displayed')
|
||||
await icons[1].click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.length, 64, "private key wasn't copied to clipboard")
|
||||
})
|
||||
|
||||
it('file loaded if click button \'Save\' ', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.yourPR.button.save)
|
||||
assert.equal(await button.getText(), 'Save as File', 'button has incorrect name')
|
||||
assert.notEqual(button, false, 'button \'Save\' isn\'t displayed')
|
||||
})
|
||||
|
||||
it('button \'Done\' leads back to main screen', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.yourPR.button.done)
|
||||
await f.click(button)
|
||||
const field = await f.waitUntilShowUp(screens.yourPR.key, 20)
|
||||
assert.equal(field, false, 'screen \'Your PR\' is displayed after closing')
|
||||
await f.driver.navigate().refresh()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = exportPrivateKey
|
|
@ -0,0 +1,69 @@
|
|||
const assert = require('assert')
|
||||
const { menus, screens, elements, NETWORKS } = require('../elements')
|
||||
const { account } = menus
|
||||
const { main: {
|
||||
tokens: tokensEl,
|
||||
balance: balanceEl,
|
||||
},
|
||||
importAccounts,
|
||||
} = screens
|
||||
const addr = '0xf4702CbA917260b2D6731Aea6385215073e8551b'
|
||||
const addrPrivKey = '76bd0ced0a47055bb5d060e1ae4a8cb3ece658d668823e250dae6e79d3ab4435'
|
||||
|
||||
const importAccount = async (f) => {
|
||||
it('Open import account menu', async () => {
|
||||
await f.setProvider(NETWORKS.POA)
|
||||
await f.delay(2000)
|
||||
const menu = await f.waitUntilShowUp(account.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(account.import)
|
||||
await item.click()
|
||||
const importAccountTitle = await f.waitUntilShowUp(importAccounts.title)
|
||||
assert.equal(await importAccountTitle.getText(), importAccounts.textTitle)
|
||||
})
|
||||
|
||||
it('Imports account', async () => {
|
||||
const privateKeyBox = await f.waitUntilShowUp(importAccounts.fieldPrivateKey)
|
||||
await privateKeyBox.sendKeys(addrPrivKey)
|
||||
const button = await f.waitUntilShowUp(importAccounts.buttonImport)
|
||||
assert.equal(await button.getText(), 'Import', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const menu = await f.waitUntilShowUp(account.menu)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(account.label)
|
||||
const labels = await f.driver.findElements(account.label)
|
||||
const label = labels[0]
|
||||
assert.equal(await label.getText(), 'IMPORTED')
|
||||
await menu.click()
|
||||
})
|
||||
|
||||
it('Auto-detect tokens for POA core network ', async () => {
|
||||
// await setProvider(NETWORKS.POA)
|
||||
const tab = await f.waitUntilShowUp(tokensEl.menu)
|
||||
await tab.click()
|
||||
const balance = await f.waitUntilShowUp(tokensEl.balance)
|
||||
console.log(await balance.getText())
|
||||
assert.equal(await balance.getText(), '1 DOPR', 'token isnt\' auto-detected')
|
||||
})
|
||||
|
||||
it.skip('Auto-detect tokens for MAIN core network ', async () => {
|
||||
await f.setProvider(NETWORKS.MAINNET)
|
||||
await f.waitUntilShowUp(elements.loader, 25)
|
||||
await f.waitUntilDisappear(elements.loader, 25)
|
||||
const balance = await f.waitUntilShowUp(tokensEl.balance)
|
||||
console.log(await balance.getText())
|
||||
assert.equal(await balance.getText(), '0.001 WETH', 'token isnt\' auto-detected')
|
||||
})
|
||||
|
||||
it('Check Sokol balance', async () => {
|
||||
await f.setProvider(NETWORKS.POA)
|
||||
await f.delay(2000)
|
||||
const balanceField = await f.waitUntilShowUp(balanceEl)
|
||||
const balance = await balanceField.getText()
|
||||
console.log(`Account = ${addr}`)
|
||||
console.log('Balance = ' + balance)
|
||||
assert.equal(parseFloat(balance) > 0.001, true, `Balance of account ${addr} TOO LOW !!! Please refill with Sokol eth!!!!`)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = importAccount
|
|
@ -0,0 +1,862 @@
|
|||
const assert = require('assert')
|
||||
const clipboardy = require('clipboardy')
|
||||
const { menus, screens, elements, NETWORKS } = require('../elements')
|
||||
let abiClipboard
|
||||
|
||||
const importContractAccount = async (f, account1, getCreatedAccounts) => {
|
||||
describe('Proxy contract', async () => {
|
||||
const proxyContract = '0x0518ac3db78eb326f42dbcfb4b2978e8059989a5'
|
||||
const proxyABI = [{'constant': true, 'inputs': [], 'name': 'proxyOwner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'version', 'type': 'string'}, {'name': 'implementation', 'type': 'address'}], 'name': 'upgradeTo', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'implementation', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'upgradeabilityOwner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'version', 'type': 'string'}, {'name': 'implementation', 'type': 'address'}, {'name': 'data', 'type': 'bytes'}], 'name': 'upgradeToAndCall', 'outputs': [], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'newOwner', 'type': 'address'}], 'name': 'transferProxyOwnership', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': true, 'stateMutability': 'payable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': false, 'name': 'previousOwner', 'type': 'address'}, {'indexed': false, 'name': 'newOwner', 'type': 'address'}], 'name': 'ProxyOwnershipTransferred', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': false, 'name': 'version', 'type': 'string'}, {'indexed': true, 'name': 'implementation', 'type': 'address'}], 'name': 'Upgraded', 'type': 'event'}] // eslint-disable-line no-unused-vars
|
||||
const joinedABI = [{'constant': true, 'inputs': [], 'name': 'proxyOwner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'version', 'type': 'string'}, {'name': 'implementation', 'type': 'address'}], 'name': 'upgradeTo', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'implementation', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'upgradeabilityOwner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'version', 'type': 'string'}, {'name': 'implementation', 'type': 'address'}, {'name': 'data', 'type': 'bytes'}], 'name': 'upgradeToAndCall', 'outputs': [], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'newOwner', 'type': 'address'}], 'name': 'transferProxyOwnership', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': true, 'stateMutability': 'payable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': false, 'name': 'previousOwner', 'type': 'address'}, {'indexed': false, 'name': 'newOwner', 'type': 'address'}], 'name': 'ProxyOwnershipTransferred', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': false, 'name': 'version', 'type': 'string'}, {'indexed': true, 'name': 'implementation', 'type': 'address'}], 'name': 'Upgraded', 'type': 'event'}, {'constant': true, 'inputs': [], 'name': 'desc', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'methodFromImplementation', 'outputs': [{'name': 'yep', 'type': 'bool'}], 'payable': false, 'stateMutability': 'pure', 'type': 'function'}]
|
||||
|
||||
describe('imports ABI of proxy and implementation together', async () => {
|
||||
it('opens import account menu', async () => {
|
||||
await f.setProvider(NETWORKS.SOKOL)
|
||||
const menu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.import2)
|
||||
await item.click()
|
||||
const importAccountTitle = await f.waitUntilShowUp(screens.importAccounts.title)
|
||||
assert.equal(await importAccountTitle.getText(), screens.importAccounts.textTitle)
|
||||
})
|
||||
|
||||
it("Select type 'Proxy'", async () => {
|
||||
await f.delay(1000)
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.selectArrow)
|
||||
await field.click()
|
||||
const item = await f.waitUntilShowUp(screens.importAccounts.itemProxyContract)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
it("Fill 'Address' with valid proxy contract , SOKOL", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractAddress)
|
||||
await f.clearField(field, 100)
|
||||
await field.sendKeys(proxyContract)
|
||||
})
|
||||
|
||||
it('ABI of Proxy + Implementation is fetched and matches the pattern', async () => {
|
||||
await f.delay(5000)
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractABI)
|
||||
abiClipboard = await field.getText()
|
||||
console.log(abiClipboard)
|
||||
assert.deepEqual(JSON.parse(abiClipboard), joinedABI, "ABI isn't fetched")
|
||||
})
|
||||
|
||||
it("Click button 'Import', main screen opens", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.importAccounts.buttonImport)
|
||||
await f.click(button)
|
||||
const ident = await f.waitUntilShowUp(screens.main.identicon, 20)
|
||||
assert.notEqual(ident, false, "main screen isn't opened")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Check 3dots menu for 'Proxy' account", () => {
|
||||
|
||||
it('open 3dots menu', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.dot.menu)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(menus.dot.item)
|
||||
const items = await f.driver.findElements(menus.dot.item)
|
||||
assert.equal(items.length, 5, '3dot menu has incorrect number of items')
|
||||
})
|
||||
|
||||
it('Check text of items', async () => {
|
||||
const items = await f.driver.findElements(menus.dot.item)
|
||||
assert.equal(await items[0].getText(), 'View on block explorer', '1st item has incorrect text')
|
||||
assert.equal(await items[1].getText(), 'Show QR Code', '2nd item has incorrect text')
|
||||
assert.equal(await items[2].getText(), 'Copy address to clipboard', '3d item has incorrect text')
|
||||
assert.equal(await items[3].getText(), 'Copy ABI to clipboard', '4th item has incorrect text')
|
||||
assert.equal(await items[4].getText(), 'Update implementation ABI', '5th item has incorrect text')
|
||||
})
|
||||
|
||||
it("Click 'Update implementation ABI'", async () => {
|
||||
const items = await f.driver.findElements(menus.dot.item)
|
||||
await items[4].click()
|
||||
const menu = await f.waitUntilShowUp(menus.dot.item, 20)
|
||||
assert.equal(menu, false, "3dot menu wasn't closed")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Remove imported 'Proxy' account", async () => {
|
||||
it("Label 'PROXY' present", async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await menu.click()
|
||||
await f.delay(2000)
|
||||
await f.waitUntilShowUp(menus.account.label)
|
||||
const labels = await f.driver.findElements(menus.account.label)
|
||||
const label = labels[1]
|
||||
assert.equal(await label.getText(), 'PROXY', 'label incorrect')
|
||||
})
|
||||
it('Delete imported account', async () => {
|
||||
await f.waitUntilShowUp(menus.account.delete)
|
||||
const items = await f.driver.findElements(menus.account.delete)
|
||||
await items[1].click()
|
||||
const button = await f.waitUntilShowUp(screens.deleteImportedAccount.buttons.yes)
|
||||
await button.click()
|
||||
const buttonArrow = await f.waitUntilShowUp(screens.settings.buttons.arrow)
|
||||
await buttonArrow.click()
|
||||
const identicon = await f.waitUntilShowUp(screens.main.identicon)
|
||||
assert.notEqual(identicon, false, 'main screen didn\'t opened')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Simple contract', async () => {
|
||||
const contractSokol = '0x215b2ab35749e5a9f3efe890de602fb9844e842f'
|
||||
console.log('Contract ' + contractSokol + ' , Sokol')
|
||||
const wrongAddress = '0xB87b6077D59B01Ab9fa8cd5A1A21D02a4d60D35'
|
||||
const notContractAddress = '0x56B2e3C3cFf7f3921Dc2e0F8B8e20d1eEc29216b'
|
||||
describe('Import Contract', async () => {
|
||||
|
||||
it('opens import account menu', async () => {
|
||||
await f.setProvider(NETWORKS.ROPSTEN)
|
||||
const menu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await menu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.import2)
|
||||
await item.click()
|
||||
const importAccountTitle = await f.waitUntilShowUp(screens.importAccounts.title)
|
||||
assert.equal(await importAccountTitle.getText(), screens.importAccounts.textTitle)
|
||||
})
|
||||
|
||||
it("Warning's text is correct", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.warning)
|
||||
assert.equal(await field.getText(), 'Imported accounts will not be associated with your originally created Nifty Wallet account seedphrase.', "incorrect warning's text")
|
||||
})
|
||||
|
||||
it("Select type 'Contract'", async () => {
|
||||
await f.delay(1000)
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.selectArrow)
|
||||
await field.click()
|
||||
await f.delay(2000)
|
||||
const item = await f.waitUntilShowUp(screens.importAccounts.itemContract)
|
||||
await item.click()
|
||||
})
|
||||
|
||||
it("Field 'Address' is displayed", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractAddress)
|
||||
assert.notEqual(field, false, "field 'Address' isn't displayed")
|
||||
await field.sendKeys(wrongAddress)
|
||||
})
|
||||
|
||||
it("Button 'Import' is displayed", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.importAccounts.buttonImport)
|
||||
assert.notEqual(button, false, "button 'Import' isn't displayed")
|
||||
assert.equal(await button.getText(), 'Import', 'wrong name of button')
|
||||
})
|
||||
|
||||
it("Button 'Import' is disabled if incorrect address", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.importAccounts.buttonImport)
|
||||
assert.equal(await button.isEnabled(), false, 'button enabled')
|
||||
})
|
||||
|
||||
it("Field 'ABI' is displayed", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractABI)
|
||||
assert.notEqual(field, false, "field 'ABI' isn't displayed")
|
||||
})
|
||||
|
||||
it("Field 'ABI' is empty if contract isn't verified in current network", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractABI)
|
||||
assert.equal(await field.getText(), '', "field 'ABI' isn't displayed")
|
||||
})
|
||||
|
||||
it("Fill 'Address' with not contract address , SOKOL", async () => {
|
||||
await f.setProvider(NETWORKS.SOKOL)
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractAddress)
|
||||
await f.clearField(field, 100)
|
||||
await field.sendKeys(notContractAddress)
|
||||
})
|
||||
|
||||
it("Button 'Import' is disabled if not contract address", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.importAccounts.buttonImport)
|
||||
assert.equal(await button.isEnabled(), false, 'button enabled')
|
||||
})
|
||||
|
||||
it("Fill 'Address' with valid contract , SOKOL", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractAddress)
|
||||
await f.clearField(field, 100)
|
||||
await field.sendKeys(contractSokol)
|
||||
})
|
||||
|
||||
it("Button 'Import' is enabled if contract address is correct", async () => {
|
||||
await f.delay(5000)
|
||||
const button = await f.waitUntilShowUp(screens.importAccounts.buttonImport)
|
||||
assert.equal(await button.isEnabled(), true, 'button enabled')
|
||||
})
|
||||
|
||||
it('ABI is fetched ', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.contractABI)
|
||||
abiClipboard = await field.getText()
|
||||
assert.equal(abiClipboard.length, 4457, "ABI isn't fetched")
|
||||
})
|
||||
|
||||
it('icon copy is displayed for ABI ', async () => {
|
||||
const field = await f.waitUntilShowUp(screens.importAccounts.iconCopy)
|
||||
assert.notEqual(field, false, "icon copy isn't displayed")
|
||||
await field.click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text, abiClipboard, "address account wasn't copied to clipboard")
|
||||
})
|
||||
|
||||
it("Click button 'Import', main screen opens", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.importAccounts.buttonImport)
|
||||
await f.click(button)
|
||||
const ident = await f.waitUntilShowUp(screens.main.identicon, 20)
|
||||
assert.notEqual(ident, false, "main screen isn't opened")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Check 3dots menu for 'Contract' account", () => {
|
||||
|
||||
it('open 3dots menu', async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.dot.menu)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(menus.dot.item)
|
||||
const items = await f.driver.findElements(menus.dot.item)
|
||||
assert.equal(items.length, 4, '3dot menu has incorrect number of items')
|
||||
})
|
||||
|
||||
it('Check text of items', async () => {
|
||||
const items = await f.driver.findElements(menus.dot.item)
|
||||
assert.equal(await items[0].getText(), 'View on block explorer', '1st item has incorrect text')
|
||||
assert.equal(await items[1].getText(), 'Show QR Code', '2nd item has incorrect text')
|
||||
assert.equal(await items[2].getText(), 'Copy address to clipboard', '3d item has incorrect text')
|
||||
assert.equal(await items[3].getText(), 'Copy ABI to clipboard', '4th item has incorrect text')
|
||||
})
|
||||
|
||||
it("Click 'Copy ABI'", async () => {
|
||||
const items = await f.driver.findElements(menus.dot.item)
|
||||
await items[3].click()
|
||||
const menu = await f.waitUntilShowUp(menus.dot.item, 20)
|
||||
assert.equal(menu, false, "3dot menu wasn't closed")
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text, abiClipboard, "ABI wasn't copied to clipboard")
|
||||
})
|
||||
})
|
||||
|
||||
describe('Execute Method screen', () => {
|
||||
const notContractAddress = '0x56B2e3C3cFf7f3921Dc2e0F8B8e20d1eEc29216b'
|
||||
describe("Check UI and button's functionality", () => {
|
||||
|
||||
it("Click button 'Execute method'", async () => {
|
||||
await f.driver.navigate().refresh()
|
||||
await f.delay(2000)
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonExecuteMethod)
|
||||
assert.notEqual(button, false, "button doesn't displayed")
|
||||
assert.equal(await button.getText(), 'Execute methods', 'button has incorrect name')
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('title is displayed and correct', async () => {
|
||||
const title = await f.waitUntilShowUp(screens.executeMethod.title)
|
||||
assert.notEqual(title, false, 'title isn\'t displayed')
|
||||
assert.equal(await title.getText(), screens.executeMethod.titleText, 'incorrect text')
|
||||
})
|
||||
|
||||
it('Click arrow button leads to main screen', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonArrow)
|
||||
await f.click(button)
|
||||
const identicon = await f.waitUntilShowUp(screens.main.identicon, 40)
|
||||
assert.notEqual(identicon, false, "main screen isn't opened")
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check output for data type : ADDRESS', () => {
|
||||
|
||||
const address = '0x56B2e3C3cFf7f3921Dc2e0F8B8e20d1eEc29216b'
|
||||
|
||||
it("Click button 'Execute method'", async () => {
|
||||
await f.driver.navigate().refresh()
|
||||
await f.delay(2000)
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonExecuteMethod)
|
||||
assert.notEqual(button, false, "button doesn't displayed")
|
||||
assert.equal(await button.getText(), 'Execute methods', 'button has incorrect name')
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it("Select method 'returnAddress'", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.selectArrow)
|
||||
await field.click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
await list[3].click()
|
||||
assert.equal(list.length, 22, "drop down menu isn't displayed")
|
||||
})
|
||||
|
||||
it("Button 'Call data' is displayed and disabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), false, "Button 'Call data' is enabled")
|
||||
})
|
||||
|
||||
it("Fill out input field 'Address'", async () => {
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldParameter)
|
||||
assert.notEqual(fields[0], false, "field parameter#1 isn't displayed")
|
||||
await fields[0].sendKeys(address)
|
||||
})
|
||||
|
||||
it("Button 'Call data' is displayed and enabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), true, "Button 'Call data' is disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('method returns correct value', async () => {
|
||||
await f.delay(3000)
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(fields[1], false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(fields[1])
|
||||
assert.equal(text.toLowerCase(), address.toLowerCase(), 'incorrect value was returned')
|
||||
})
|
||||
|
||||
it('icon copy cliboard is displayed and clickable', async () => {
|
||||
const icon = await f.waitUntilShowUp(screens.executeMethod.copy)
|
||||
assert.notEqual(icon, false, 'icon copy isn\'t displayed')
|
||||
await icon.click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.toLowerCase(), address.toLowerCase(), "output wasn't copied to clipboard")
|
||||
})
|
||||
|
||||
it("2nd call doesn't throw the error", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
await button.click()
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(field, false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(field)
|
||||
assert.equal(text.toLowerCase(), address.toLowerCase(), 'incorrect value was returned')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check output for data type : STRING', () => {
|
||||
const stringValue = 'POA network'
|
||||
|
||||
it("Select method 'returnString'", async () => {
|
||||
await f.delay(3000)
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.selectArrow)
|
||||
await field.click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
await list[14].click()
|
||||
assert.equal(list.length, 22, "drop down menu isn't displayed")
|
||||
})
|
||||
|
||||
it('Fill out input parameter field ', async () => {
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldParameter)
|
||||
assert.notEqual(fields[0], false, "field parameter#1 isn't displayed")
|
||||
await fields[0].sendKeys(stringValue)
|
||||
})
|
||||
|
||||
it("Click button 'Call data' ", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), true, "Button 'Call data' is disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('method returns correct value', async () => {
|
||||
await f.delay(3000)
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(fields[1], false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(fields[1])
|
||||
assert.equal(text, stringValue, 'incorrect value was returned')
|
||||
})
|
||||
|
||||
it('icon copy cliboard is displayed and clickable', async () => {
|
||||
const icon = await f.waitUntilShowUp(screens.executeMethod.copy)
|
||||
assert.notEqual(icon, false, 'icon copy isn\'t displayed')
|
||||
await icon.click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.toLowerCase(), stringValue.toLowerCase(), "output wasn't copied to clipboard")
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check output for data type : BOOLEAN', () => {
|
||||
|
||||
it("Select method 'returnBoolean'", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.selectArrow)
|
||||
await field.click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
await list[5].click()
|
||||
assert.equal(list.length, 22, "drop down menu isn't displayed")
|
||||
})
|
||||
|
||||
it('Select value TRUE from dropdown menu', async () => {
|
||||
const arrows = await f.driver.findElements(screens.executeMethod.selectArrow)
|
||||
await arrows[1].click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
assert.equal(await list[1].getText(), 'true', 'TRUE menu item: incorrect text')
|
||||
assert.equal(list.length, 2, "drop down menu isn't displayed")
|
||||
await list[1].click()
|
||||
})
|
||||
|
||||
it("Click button 'Call data' ", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), true, "Button 'Call data' is disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('method returns correct value: TRUE', async () => {
|
||||
await f.delay(3000)
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(fields[0], false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(fields[0])
|
||||
assert.equal(text, 'true', 'incorrect value was returned')
|
||||
})
|
||||
|
||||
it('Select value FALSE from dropdown menu', async () => {
|
||||
const arrows = await f.driver.findElements(screens.executeMethod.selectArrow)
|
||||
await arrows[1].click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
assert.equal(await list[0].getText(), 'false', 'FALSE menu item: incorrect text')
|
||||
assert.equal(list.length, 2, "drop down menu isn't displayed")
|
||||
await list[0].click()
|
||||
})
|
||||
|
||||
it("Click button 'Call data' ", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), true, "Button 'Call data' is disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('method returns correct value, FALSE', async () => {
|
||||
await f.delay(3000)
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(fields[0], false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(fields[0])
|
||||
assert.equal(text, 'false', 'incorrect value was returned')
|
||||
})
|
||||
|
||||
it('icon copy cliboard is displayed and clickable', async () => {
|
||||
const icon = await f.waitUntilShowUp(screens.executeMethod.copy)
|
||||
assert.notEqual(icon, false, 'icon copy isn\'t displayed')
|
||||
await icon.click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.toLowerCase(), 'false', "output wasn't copied to clipboard")
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('Check output for data type : BYTES', () => {
|
||||
|
||||
const bytesValue = '0x010203'
|
||||
|
||||
it("Select method 'returnBytes1'", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.selectArrow)
|
||||
await field.click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
await list[7].click()
|
||||
assert.equal(list.length, 22, "drop down menu isn't displayed")
|
||||
})
|
||||
|
||||
it('Fill out input parameter field ', async () => {
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldParameter)
|
||||
assert.notEqual(fields[0], false, "field parameter#1 isn't displayed")
|
||||
await fields[0].sendKeys(bytesValue)
|
||||
})
|
||||
|
||||
it("Click button 'Call data' ", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), true, "Button 'Call data' is disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('method returns correct value', async () => {
|
||||
await f.delay(3000)
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(fields[1], false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(fields[1])
|
||||
assert.equal(text, bytesValue, 'incorrect value was returned')
|
||||
})
|
||||
|
||||
it('icon copy cliboard is displayed and clickable', async () => {
|
||||
const icon = await f.waitUntilShowUp(screens.executeMethod.copy)
|
||||
assert.notEqual(icon, false, 'icon copy isn\'t displayed')
|
||||
await icon.click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.toLowerCase(), bytesValue.toLowerCase(), "output wasn't copied to clipboard")
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check output for data type : UINT256', () => {
|
||||
|
||||
const uint256Value = '1122334455667788991122334455667788'
|
||||
|
||||
it("Select method 'returnUint256'", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.selectArrow)
|
||||
await field.click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
await list[17].click()
|
||||
assert.equal(list.length, 22, "drop down menu isn't displayed")
|
||||
})
|
||||
|
||||
it('Fill out input parameter field ', async () => {
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldParameter)
|
||||
assert.notEqual(fields[0], false, "field parameter#1 isn't displayed")
|
||||
await fields[0].sendKeys(uint256Value)
|
||||
})
|
||||
|
||||
it("Click button 'Call data' ", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), true, "Button 'Call data' is disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('method returns correct value', async () => {
|
||||
await f.delay(3000)
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(fields[1], false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(fields[1])
|
||||
assert.equal(text, uint256Value, 'incorrect value was returned')
|
||||
})
|
||||
|
||||
it('icon copy cliboard is displayed and clickable', async () => {
|
||||
const icon = await f.waitUntilShowUp(screens.executeMethod.copy)
|
||||
assert.notEqual(icon, false, 'icon copy isn\'t displayed')
|
||||
await icon.click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.toLowerCase(), uint256Value.toLowerCase(), "output wasn't copied to clipboard")
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check output for data type : INT256', () => {
|
||||
|
||||
const int256Value = '-1122334455667788991122334455667788'
|
||||
|
||||
it("Select method 'returnInt256'", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.selectArrow)
|
||||
await field.click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
await list[10].click()
|
||||
assert.equal(list.length, 22, "drop down menu isn't displayed")
|
||||
})
|
||||
|
||||
it('Fill out input parameter field ', async () => {
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldParameter)
|
||||
assert.notEqual(fields[0], false, "field parameter#1 isn't displayed")
|
||||
await fields[0].sendKeys(int256Value)
|
||||
})
|
||||
|
||||
it("Click button 'Call data' ", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCall)
|
||||
assert.notEqual(button, false, "button 'Call data' isn't displayed")
|
||||
assert.equal(await button.isEnabled(), true, "Button 'Call data' is disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('method returns correct value', async () => {
|
||||
await f.delay(3000)
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldOutput)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldOutput)
|
||||
assert.notEqual(fields[1], false, "field 'Output' isn't displayed")
|
||||
const text = await f.waitUntilHasValue(fields[1])
|
||||
assert.equal(text, int256Value, 'incorrect value was returned')
|
||||
})
|
||||
|
||||
it('icon copy cliboard is displayed and clickable', async () => {
|
||||
const icon = await f.waitUntilShowUp(screens.executeMethod.copy)
|
||||
assert.notEqual(icon, false, 'icon copy isn\'t displayed')
|
||||
await icon.click()
|
||||
})
|
||||
|
||||
it('Check clipboard buffer', async () => {
|
||||
const text = clipboardy.readSync()
|
||||
assert.equal(text.toLowerCase(), int256Value.toLowerCase(), "output wasn't copied to clipboard")
|
||||
})
|
||||
})
|
||||
|
||||
describe('Check executed method', () => {
|
||||
|
||||
it("Select method 'transfer'", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.selectArrow)
|
||||
await field.click()
|
||||
await f.waitUntilShowUp(screens.executeMethod.items)
|
||||
const list = await f.driver.findElements(screens.executeMethod.items)
|
||||
await list[21].click()
|
||||
assert.equal(list.length, 22, "drop down menu isn't displayed")
|
||||
})
|
||||
|
||||
it("Button 'Copy ABI encoded' is displayed", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCopyABI)
|
||||
assert.notEqual(button, false, "button 'Copy ABI encoded' isn't displayed")
|
||||
})
|
||||
|
||||
it("Button 'Copy ABI encoded' is disabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCopyABI)
|
||||
assert.equal(await button.isEnabled(), false, "button 'Copy ABI encoded' enabled")
|
||||
})
|
||||
|
||||
it("Button 'Next' is disabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonNext)
|
||||
assert.equal(await button.isEnabled(), false, "button 'Next' enabled")
|
||||
})
|
||||
|
||||
it("Fill out parameter '_value' with valid data", async () => {
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldParameter)
|
||||
assert.notEqual(fields[1], false, "field address isn't displayed")
|
||||
await fields[1].sendKeys('1')
|
||||
})
|
||||
|
||||
it("Button 'Copy ABI encoded' is disabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCopyABI)
|
||||
assert.equal(await button.isEnabled(), false, "button 'Copy ABI encoded' enabled")
|
||||
})
|
||||
|
||||
it("Button 'Next' is disabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonNext)
|
||||
assert.equal(await button.isEnabled(), false, "button 'Next' enabled")
|
||||
})
|
||||
it("Fill out parameter '_to' with wrong data", async () => {
|
||||
await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
const fields = await f.driver.findElements(screens.executeMethod.fieldParameter)
|
||||
assert.notEqual(fields[0], false, "field address isn't displayed")
|
||||
await fields[0].sendKeys(wrongAddress)
|
||||
})
|
||||
|
||||
it("Error message if click 'Copy ABI encoded' with wrong address", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCopyABI)
|
||||
await button.click()
|
||||
const error = await f.waitUntilShowUp(elements.error)
|
||||
assert.notEqual(error, false, 'no error message')
|
||||
})
|
||||
|
||||
it('Close error message', async () => {
|
||||
const button = await f.waitUntilShowUp(elements.errorClose)
|
||||
await button.click()
|
||||
const title = await f.waitUntilShowUp(screens.executeMethod.title)
|
||||
assert.notEqual(title, false, "error message isn't closed")
|
||||
})
|
||||
|
||||
it.skip("Error message if click 'Next' with wrong address", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonNext)
|
||||
await button.click()
|
||||
const error = await f.waitUntilShowUp(elements.error)
|
||||
assert.notEqual(error, false, 'no error message')
|
||||
})
|
||||
|
||||
it.skip('Close error message', async () => {
|
||||
const button = await f.waitUntilShowUp(elements.errorClose)
|
||||
await button.click()
|
||||
const title = await f.waitUntilShowUp(screens.executeMethod.title)
|
||||
assert.notEqual(title, false, "error message isn't closed")
|
||||
})
|
||||
|
||||
it("Fill out parameter '_to' with valid data", async () => {
|
||||
const field = await f.waitUntilShowUp(screens.executeMethod.fieldParameter)
|
||||
await f.clearField(field, 100)
|
||||
await field.sendKeys(notContractAddress)
|
||||
assert.notEqual(field, false, "field address isn't displayed")
|
||||
})
|
||||
|
||||
it("Button 'Next' is enabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonNext)
|
||||
assert.equal(await button.isEnabled(), true, "button 'Next' disabled")
|
||||
})
|
||||
|
||||
it("Button 'Copy ABI encoded' is enabled", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonCopyABI)
|
||||
assert.equal(await button.isEnabled(), true, "button 'Copy ABI encoded' disabled")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it("Click button 'Next'", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonNext)
|
||||
assert.notEqual(button, false, "button 'Next' isn't displayed")
|
||||
await button.click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Choose Contract Executor', () => {
|
||||
|
||||
it('Title is displayed and correct', async () => {
|
||||
await f.delay(5000)
|
||||
const title = await f.waitUntilShowUp(screens.chooseContractExecutor.title)
|
||||
assert.notEqual(title, false, 'title isn\'t displayed')
|
||||
assert.equal(await title.getText(), screens.chooseContractExecutor.titleText, 'incorrect text')
|
||||
})
|
||||
|
||||
it('Two accounts displayed', async () => {
|
||||
const accs = await f.waitUntilShowUp(screens.chooseContractExecutor.account)
|
||||
assert.notEqual(accs, false, 'accounts aren\'t displayed')
|
||||
const accounts = await f.driver.findElements(screens.chooseContractExecutor.account)
|
||||
assert.equal(accounts.length, 4, "number of accounts isn't 2")
|
||||
})
|
||||
|
||||
it("Click arrow button leads to 'Execute Method' screen ", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.chooseContractExecutor.buttonArrow)
|
||||
assert.notEqual(button, false, 'button isn\'t displayed')
|
||||
await button.click()
|
||||
await f.delay(2000)
|
||||
|
||||
const title = await f.waitUntilShowUp(screens.executeMethod.title)
|
||||
assert.notEqual(title, false, 'title isn\'t displayed')
|
||||
assert.equal(await title.getText(), screens.executeMethod.titleText, "'Execute Method' screen isn't opened")
|
||||
})
|
||||
|
||||
it("Return back to 'Choose Contract Executor' screen", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.executeMethod.buttonNext)
|
||||
assert.notEqual(button, false, "button 'Next' isn't displayed")
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it("Button 'Next' is disabled by default", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.chooseContractExecutor.buttonNext)
|
||||
assert.notEqual(button, false, 'button isn\'t displayed')
|
||||
assert.equal(await button.isEnabled(), false, 'button enabled by default')
|
||||
})
|
||||
|
||||
it('User is able to select account', async () => {
|
||||
await f.waitUntilShowUp(screens.chooseContractExecutor.account)
|
||||
const accounts = await f.driver.findElements(screens.chooseContractExecutor.account)
|
||||
const account = accounts[1]
|
||||
await account.click()
|
||||
const selected = await f.driver.findElements(screens.chooseContractExecutor.selectedAccount)
|
||||
assert.equal(selected.length, 1, "account isn't selected")
|
||||
})
|
||||
|
||||
it('User is able to select only one account', async () => {
|
||||
const account = (await f.driver.findElements(screens.chooseContractExecutor.account))[2]
|
||||
await account.click()
|
||||
const selected = await f.driver.findElements(screens.chooseContractExecutor.selectedAccount)
|
||||
assert.equal(selected.length, 1, 'more than one accounts are selected')
|
||||
})
|
||||
|
||||
it("Click button 'Next' open 'Confirm transaction' screen", async () => {
|
||||
const button = await f.waitUntilShowUp(screens.chooseContractExecutor.buttonNext)
|
||||
await button.click()
|
||||
await f.delay(3000)
|
||||
const reject = await f.waitUntilShowUp(screens.confirmTransaction.button.reject)
|
||||
assert.notEqual(reject, false, "button reject isn't displayed")
|
||||
})
|
||||
|
||||
it("Button 'Buy POA' is displayed", async function () {
|
||||
const button = await f.waitUntilShowUp(screens.confirmTransaction.button.buyEther)
|
||||
assert.equal(await button.getText(), 'Buy POA', 'button has incorrect name')
|
||||
assert.equal(await button.isEnabled(), true, 'button is disabled')
|
||||
})
|
||||
|
||||
it("Open screen 'Buy'", async function () {
|
||||
const button = await f.waitUntilShowUp(screens.confirmTransaction.button.buyEther)
|
||||
await button.click()
|
||||
const title = await f.waitUntilShowUp(screens.buyEther.title)
|
||||
assert.equal(await title.getText(), 'Buy POA', "screen 'Buy POA' has incorrect title text")
|
||||
const arrow = await f.waitUntilShowUp(elements.buttonArrow)
|
||||
await arrow.click()
|
||||
})
|
||||
|
||||
it("Click button 'Reject' open contract's account screen", async () => {
|
||||
const reject = await f.waitUntilShowUp(screens.confirmTransaction.button.reject)
|
||||
assert.equal(await reject.getText(), 'Reject', 'button has incorrect name')
|
||||
await reject.click()
|
||||
const buttonExecute = await f.waitUntilShowUp(screens.executeMethod.buttonExecuteMethod)
|
||||
assert.notEqual(buttonExecute, false, "contract's account hasn't opened")
|
||||
})
|
||||
|
||||
it("Button arrow leads to executor's account screen", async () => {
|
||||
assert.equal(await f.executeTransferMethod(0, account1), true, "can't execute the method 'transfer'")
|
||||
await f.delay(2000)
|
||||
const arrow = await f.waitUntilShowUp(elements.buttonArrow)
|
||||
await arrow.click()
|
||||
await f.delay(2000)
|
||||
const address = await f.waitUntilShowUp(screens.main.address)
|
||||
assert.equal((await address.getText()).toUpperCase(), getCreatedAccounts()[0], "executors account isn't opened")
|
||||
})
|
||||
|
||||
it('Switch to contract account ', async () => {
|
||||
const accountMenu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await accountMenu.click()
|
||||
const item = await f.waitUntilShowUp(menus.account.account4)
|
||||
await item.click()
|
||||
await f.delay(2000)
|
||||
const address = await f.waitUntilShowUp(screens.main.address)
|
||||
assert.equal((await address.getText()).toUpperCase(), contractSokol.toUpperCase(), "contract's account isn't opened")
|
||||
})
|
||||
|
||||
it("Confirm transaction: button 'Reject All' leads to contract's account screen", async () => {
|
||||
assert.equal(await f.executeTransferMethod(0, account1), true, "can't execute the method 'transfer'")
|
||||
const rejectAll = await f.waitUntilShowUp(screens.confirmTransaction.button.rejectAll)
|
||||
assert.equal(await rejectAll.getText(), 'Reject All', 'button has incorrect name')
|
||||
await rejectAll.click()
|
||||
await f.delay(2000)
|
||||
const address = await f.waitUntilShowUp(screens.main.address)
|
||||
assert.equal((await address.getText()).toUpperCase(), contractSokol.toUpperCase(), "contract account isn't opened")
|
||||
})
|
||||
|
||||
it("Confirm transaction: button 'Submit' leads to contract's account screen", async () => {
|
||||
assert.equal(await f.executeTransferMethod(2, account1), true, "can't execute the method 'transfer'")
|
||||
await f.delay(2000)
|
||||
const button = await f.waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||
assert.equal(await button.getAttribute('value'), 'Submit', 'button has incorrect name')
|
||||
await button.click()
|
||||
await f.delay(2000)
|
||||
const address = await f.waitUntilShowUp(screens.main.address)
|
||||
assert.equal((await address.getText()).toUpperCase(), contractSokol.toUpperCase(), "contract account isn't opened")
|
||||
})
|
||||
|
||||
it("Label 'CONTRACT' present", async () => {
|
||||
const menu = await f.waitUntilShowUp(menus.account.menu)
|
||||
await menu.click()
|
||||
await f.waitUntilShowUp(menus.account.label)
|
||||
const label = (await f.driver.findElements(menus.account.label))[1]
|
||||
assert.equal(await label.getText(), 'CONTRACT', 'label incorrect')
|
||||
})
|
||||
it('Delete imported account', async () => {
|
||||
await f.waitUntilShowUp(menus.account.delete)
|
||||
const items = await f.driver.findElements(menus.account.delete)
|
||||
await items[1].click()
|
||||
const button = await f.waitUntilShowUp(screens.deleteImportedAccount.buttons.yes)
|
||||
await button.click()
|
||||
const buttonArrow = await f.waitUntilShowUp(screens.settings.buttons.arrow)
|
||||
await buttonArrow.click()
|
||||
const identicon = await f.waitUntilShowUp(screens.main.identicon)
|
||||
assert.notEqual(identicon, false, 'main screen didn\'t opened')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = importContractAccount
|
|
@ -0,0 +1,68 @@
|
|||
const assert = require('assert')
|
||||
const { screens, menus, NETWORKS } = require('../elements')
|
||||
const testSeedPhrase = 'horn among position unable audit puzzle cannon apology gun autumn plug parrot'
|
||||
|
||||
const importGanacheSeedPhrase = async (f, account2, password) => {
|
||||
it('logs out', async () => {
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
const menu = await f.waitUntilShowUp(menus.sandwich.menu)
|
||||
await menu.click()
|
||||
const logOut = await f.waitUntilShowUp(menus.sandwich.logOut)
|
||||
assert.equal(await logOut.getText(), menus.sandwich.textLogOut)
|
||||
await logOut.click()
|
||||
})
|
||||
|
||||
it('restores from seed phrase', async () => {
|
||||
const restoreSeedLink = await f.waitUntilShowUp(screens.lock.linkRestore)
|
||||
assert.equal(await restoreSeedLink.getText(), screens.lock.linkRestoreText)
|
||||
await restoreSeedLink.click()
|
||||
})
|
||||
|
||||
it('adds seed phrase', async () => {
|
||||
const seedTextArea = await f.waitUntilShowUp(screens.restoreVault.textArea)
|
||||
await seedTextArea.sendKeys(testSeedPhrase)
|
||||
|
||||
let field = await f.driver.findElement(screens.restoreVault.fieldPassword)
|
||||
await field.sendKeys(password)
|
||||
field = await f.driver.findElement(screens.restoreVault.fieldPasswordConfirm)
|
||||
await field.sendKeys(password)
|
||||
field = await f.waitUntilShowUp(screens.restoreVault.buttos.ok)
|
||||
await f.click(field)
|
||||
})
|
||||
|
||||
it('balance renders', async () => {
|
||||
const balance = await f.waitUntilShowUp(screens.main.balance)
|
||||
assert.equal(await balance.getText(), '100.000', "balance isn't correct")
|
||||
})
|
||||
|
||||
it('sends transaction', async () => {
|
||||
const sendButton = await f.waitUntilShowUp(screens.main.buttons.send)
|
||||
assert.equal(await sendButton.getText(), screens.main.buttons.sendText)
|
||||
await f.click(sendButton)
|
||||
})
|
||||
|
||||
it('adds recipient address and amount', async () => {
|
||||
const sendTranscationScreen = await f.waitUntilShowUp(screens.sendTransaction.title)
|
||||
assert.equal(await sendTranscationScreen.getText(), screens.sendTransaction.titleText, 'Transaction screen has incorrect titlr')
|
||||
const inputAddress = await f.waitUntilShowUp(screens.sendTransaction.field.address)
|
||||
const inputAmmount = await f.waitUntilShowUp(screens.sendTransaction.field.amount)
|
||||
await inputAddress.sendKeys(account2)
|
||||
await inputAmmount.sendKeys('10')
|
||||
const button = await f.waitUntilShowUp(screens.sendTransaction.buttonNext)
|
||||
assert.equal(await button.getText(), 'Next', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
})
|
||||
|
||||
it('confirms transaction', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.confirmTransaction.button.submit)
|
||||
assert.equal(await button.getAttribute('value'), 'Submit', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
})
|
||||
|
||||
it('finds the transaction in the transactions list', async () => {
|
||||
const transactionAmount = await f.waitUntilShowUp(screens.main.transactionList)
|
||||
assert.equal(await transactionAmount.getText(), '10.0')
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = importGanacheSeedPhrase
|
|
@ -0,0 +1,58 @@
|
|||
const assert = require('assert')
|
||||
const { screens } = require('../elements')
|
||||
|
||||
const login = async (f, password) => {
|
||||
it('title is \'Nifty Wallet\'', async () => {
|
||||
const title = await f.driver.getTitle()
|
||||
assert.equal(title, 'Nifty Wallet', 'title is incorrect')
|
||||
})
|
||||
|
||||
it('screen \'Terms of Use\' has not empty agreement', async () => {
|
||||
await f.delay(5000)
|
||||
const terms = await f.waitUntilShowUp(screens.TOU.agreement, 900)
|
||||
const text = await terms.getText()
|
||||
assert.equal(text.length > 400, true, 'agreement is too short')
|
||||
})
|
||||
|
||||
it('screen \'Terms of Use\' has correct title', async () => {
|
||||
const terms = await f.waitUntilShowUp(screens.TOU.title)
|
||||
assert.equal(await terms.getText(), screens.TOU.titleText, 'title is incorrect')
|
||||
})
|
||||
|
||||
it('checks if the TOU contains link \'Terms of service\'', async () => {
|
||||
const element = await f.waitUntilShowUp(screens.TOU.linkTerms)
|
||||
await f.scrollTo(screens.TOU.linkTerms)
|
||||
assert.notEqual(element, null, ' link \'Terms of service\' isn\'t present')
|
||||
assert.equal(await element.getText(), screens.TOU.linkTermsText, 'incorrect name of link \'Terms of service\'')
|
||||
})
|
||||
|
||||
it('checks if the button \'Accept\' is present and enabled', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.TOU.button)
|
||||
assert.notEqual(button, false, 'button isn\'t present')
|
||||
assert.equal(await button.isEnabled(), true, 'button isn\'t enabled')
|
||||
assert.equal(await button.getText(), 'Accept', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
})
|
||||
|
||||
it('accepts password with length of eight', async () => {
|
||||
const passwordBox = await f.waitUntilShowUp(screens.create.fieldPassword)
|
||||
const passwordBoxConfirm = await f.waitUntilShowUp(screens.create.fieldPasswordConfirm)
|
||||
const button = await f.waitUntilShowUp(screens.create.button)
|
||||
assert.equal(await button.getText(), 'Create', 'button has incorrect name')
|
||||
await passwordBox.sendKeys(password)
|
||||
await passwordBoxConfirm.sendKeys(password)
|
||||
await f.click(button)
|
||||
})
|
||||
|
||||
it('shows vault was created and seed phrase', async () => {
|
||||
await f.delay(300)
|
||||
const element = await f.waitUntilShowUp(screens.seedPhrase.fieldPhrase)
|
||||
const seedPhrase = await element.getText()
|
||||
assert.equal(seedPhrase.split(' ').length, 12)
|
||||
const continueAfterSeedPhrase = await f.waitUntilShowUp(screens.seedPhrase.buttonIveCopied)
|
||||
assert.equal(await continueAfterSeedPhrase.getText(), screens.seedPhrase.textButtonIveCopied)
|
||||
await f.click(continueAfterSeedPhrase)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = login
|
|
@ -0,0 +1,9 @@
|
|||
const setup = async (f) => {
|
||||
it('switches to extensions list', async () => {
|
||||
await f.delay(300)
|
||||
await f.switchToFirstPage()
|
||||
await f.delay(5000)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = setup
|
|
@ -0,0 +1,67 @@
|
|||
const assert = require('assert')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const { By } = webdriver
|
||||
const { screens, NETWORKS } = require('../elements')
|
||||
|
||||
const signData = async (f) => {
|
||||
it('Simulate sign request ', async () => {
|
||||
await f.delay(5000)
|
||||
await f.setProvider(NETWORKS.LOCALHOST)
|
||||
await f.driver.get('https://danfinlay.github.io/js-eth-personal-sign-examples/')
|
||||
const button = await f.waitUntilShowUp(By.id('ethSignButton'))
|
||||
assert.notEqual(button, false, "resource isn't responding")
|
||||
await button.click()
|
||||
await f.delay(5000)
|
||||
})
|
||||
|
||||
it('navigates back to MetaMask popup in the tab', async () => {
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
await f.driver.get(`chrome-extension://${f.extensionId}/popup.html`)
|
||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
await f.driver.get(`moz-extension://${f.extensionId}/popup.html`)
|
||||
}
|
||||
await f.delay(700)
|
||||
})
|
||||
|
||||
it('error message is displayed and contains text', async () => {
|
||||
const error = await f.waitUntilShowUp(screens.signMessage.error)
|
||||
assert.notEqual(error, false, 'error message isn\'t displayed')
|
||||
const text = await error.getText()
|
||||
assert.equal(text.length > 183, true, 'error message hasn\'t text')
|
||||
})
|
||||
|
||||
it('account name is displayed and correct', async () => {
|
||||
const name = await f.waitUntilShowUp(screens.signMessage.accountName)
|
||||
assert.notEqual(name, false, 'account name isn\'t displayed')
|
||||
assert.equal(await name.getText(), 'new name', 'account name is incorrect')
|
||||
})
|
||||
|
||||
it('title is displayed and correct', async () => {
|
||||
const title = await f.waitUntilShowUp(screens.signMessage.title)
|
||||
assert.notEqual(title, false, 'title isn\'t displayed')
|
||||
assert.equal(await title.getText(), 'Sign message', 'title is incorrect')
|
||||
})
|
||||
|
||||
it('message is displayed and correct', async () => {
|
||||
const message = await f.waitUntilShowUp(screens.signMessage.message)
|
||||
assert.notEqual(message, false, 'message isn\'t displayed')
|
||||
assert.equal((await message.getText()).length > 32, true, 'message is incorrect')
|
||||
})
|
||||
|
||||
it('button \'Cancel\' is enabled and lead to main screen ', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.signMessage.buttons.cancel)
|
||||
assert.equal(await button.isEnabled(), true, 'button isn\'t enabled')
|
||||
assert.equal(await button.getText(), 'Cancel', 'button has incorrect name')
|
||||
})
|
||||
|
||||
it('button \'Sign\' is enabled and lead to main screen ', async () => {
|
||||
const button = await f.waitUntilShowUp(screens.signMessage.buttons.sign)
|
||||
assert.equal(await button.isEnabled(), true, 'button isn\'t enabled')
|
||||
assert.equal(await button.getText(), 'Sign', 'button has incorrect name')
|
||||
await f.click(button)
|
||||
const identicon = await f.waitUntilShowUp(screens.main.identicon)
|
||||
assert.notEqual(identicon, false, 'main screen didn\'t opened')
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = signData
|
|
@ -81,6 +81,10 @@ describe('', function () {
|
|||
name: 'CEX Plus',
|
||||
link: 'http://cex.plus/market/poa_eth',
|
||||
},
|
||||
{
|
||||
name: 'HitBTC',
|
||||
link: 'https://hitbtc.com/POA-to-ETH',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
const assert = require('assert')
|
||||
const sinon = require('sinon')
|
||||
const CachedBalancesController = require('../../../../app/scripts/controllers/cached-balances')
|
||||
|
||||
describe('CachedBalancesController', () => {
|
||||
describe('updateCachedBalances', () => {
|
||||
it('should update the cached balances', async () => {
|
||||
const controller = new CachedBalancesController({
|
||||
getNetwork: () => Promise.resolve(17),
|
||||
accountTracker: {
|
||||
store: {
|
||||
subscribe: () => {},
|
||||
},
|
||||
},
|
||||
initState: {
|
||||
cachedBalances: 'mockCachedBalances',
|
||||
},
|
||||
})
|
||||
|
||||
controller._generateBalancesToCache = sinon.stub().callsFake(() => Promise.resolve('mockNewCachedBalances'))
|
||||
|
||||
await controller.updateCachedBalances({ accounts: 'mockAccounts' })
|
||||
|
||||
assert.equal(controller._generateBalancesToCache.callCount, 1)
|
||||
assert.deepEqual(controller._generateBalancesToCache.args[0], ['mockAccounts', 17])
|
||||
assert.equal(controller.store.getState().cachedBalances, 'mockNewCachedBalances')
|
||||
})
|
||||
})
|
||||
|
||||
describe('_generateBalancesToCache', () => {
|
||||
it('should generate updated account balances where the current network was updated', () => {
|
||||
const controller = new CachedBalancesController({
|
||||
accountTracker: {
|
||||
store: {
|
||||
subscribe: () => {},
|
||||
},
|
||||
},
|
||||
initState: {
|
||||
cachedBalances: {
|
||||
17: {
|
||||
a: '0x1',
|
||||
b: '0x2',
|
||||
c: '0x3',
|
||||
},
|
||||
16: {
|
||||
a: '0xa',
|
||||
b: '0xb',
|
||||
c: '0xc',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const result = controller._generateBalancesToCache({
|
||||
a: { balance: '0x4' },
|
||||
b: { balance: null },
|
||||
c: { balance: '0x5' },
|
||||
}, 17)
|
||||
|
||||
assert.deepEqual(result, {
|
||||
17: {
|
||||
a: '0x4',
|
||||
b: '0x2',
|
||||
c: '0x5',
|
||||
},
|
||||
16: {
|
||||
a: '0xa',
|
||||
b: '0xb',
|
||||
c: '0xc',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should generate updated account balances where the a new network was selected', () => {
|
||||
const controller = new CachedBalancesController({
|
||||
accountTracker: {
|
||||
store: {
|
||||
subscribe: () => {},
|
||||
},
|
||||
},
|
||||
initState: {
|
||||
cachedBalances: {
|
||||
17: {
|
||||
a: '0x1',
|
||||
b: '0x2',
|
||||
c: '0x3',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const result = controller._generateBalancesToCache({
|
||||
a: { balance: '0x4' },
|
||||
b: { balance: null },
|
||||
c: { balance: '0x5' },
|
||||
}, 16)
|
||||
|
||||
assert.deepEqual(result, {
|
||||
17: {
|
||||
a: '0x1',
|
||||
b: '0x2',
|
||||
c: '0x3',
|
||||
},
|
||||
16: {
|
||||
a: '0x4',
|
||||
c: '0x5',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_registerUpdates', () => {
|
||||
it('should subscribe to the account tracker with the updateCachedBalances method', async () => {
|
||||
const subscribeSpy = sinon.spy()
|
||||
const controller = new CachedBalancesController({
|
||||
getNetwork: () => Promise.resolve(17),
|
||||
accountTracker: {
|
||||
store: {
|
||||
subscribe: subscribeSpy,
|
||||
},
|
||||
},
|
||||
})
|
||||
subscribeSpy.resetHistory()
|
||||
|
||||
const updateCachedBalancesSpy = sinon.spy()
|
||||
controller.updateCachedBalances = updateCachedBalancesSpy
|
||||
controller._registerUpdates({ accounts: 'mockAccounts' })
|
||||
|
||||
assert.equal(subscribeSpy.callCount, 1)
|
||||
|
||||
subscribeSpy.args[0][0]()
|
||||
|
||||
assert.equal(updateCachedBalancesSpy.callCount, 1)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -352,13 +352,14 @@ describe('MetaMaskController', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('unlockHardwareWalletAccount', function () {
|
||||
describe.skip('unlockHardwareWalletAccount', function () {
|
||||
let accountToUnlock
|
||||
let windowOpenStub
|
||||
let addNewAccountStub
|
||||
let getAccountsStub
|
||||
beforeEach(async function () {
|
||||
accountToUnlock = 10
|
||||
this.timeout(10000)
|
||||
accountToUnlock = 4
|
||||
windowOpenStub = sinon.stub(window, 'open')
|
||||
windowOpenStub.returns(noop)
|
||||
|
||||
|
@ -376,6 +377,7 @@ describe('MetaMaskController', function () {
|
|||
sinon.spy(metamaskController.preferencesController, 'setSelectedAddress')
|
||||
sinon.spy(metamaskController.preferencesController, 'setAccountLabel')
|
||||
await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch((e) => null)
|
||||
|
||||
await metamaskController.unlockHardwareWalletAccount(accountToUnlock, 'trezor', `m/44/0'/0'`)
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
const assert = require('assert')
|
||||
const {
|
||||
nestedJsonObjToArray,
|
||||
} = require('../../../../../../old-ui/app/accounts/import/helpers')
|
||||
|
||||
describe('#nestedJsonObjToArray', () => {
|
||||
const JsonPattern1 = {
|
||||
key1: 'val1',
|
||||
key2: 'val2',
|
||||
}
|
||||
const JsonPattern2 = {
|
||||
key1: 'val1',
|
||||
key2: {
|
||||
key3_1: 'val2',
|
||||
key3_2: 'val3',
|
||||
},
|
||||
}
|
||||
const JsonPattern3 = {
|
||||
key1: 'val1',
|
||||
key2: ['val2', 'val3'],
|
||||
key3: {
|
||||
key3_1: 'val4',
|
||||
key3_2: 'val5',
|
||||
},
|
||||
}
|
||||
const JsonPattern4 = {
|
||||
key1: 'val1',
|
||||
key2: {
|
||||
key3_1: 'val2',
|
||||
key3_2: {
|
||||
key3_2_1: 'val3',
|
||||
key3_2_3: 'val4',
|
||||
},
|
||||
},
|
||||
}
|
||||
const JsonPattern5 = {
|
||||
key1: 'val1',
|
||||
key2: {
|
||||
key3_1: 'val2',
|
||||
key3_2: {
|
||||
key3_2_1: 'val3',
|
||||
key3_2_3: ['val4', 'val5', {key3_2_3_1: 'val6'}],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
it('converts nested json objects to arrays correctly', () => {
|
||||
assert.deepEqual(['val1', 'val2'], nestedJsonObjToArray(JsonPattern1))
|
||||
assert.deepEqual(['val1', 'val2', 'val3'], nestedJsonObjToArray(JsonPattern2))
|
||||
assert.deepEqual(['val1', 'val2', 'val3', 'val4', 'val5'], nestedJsonObjToArray(JsonPattern3))
|
||||
assert.deepEqual(['val1', 'val2', 'val3', 'val4'], nestedJsonObjToArray(JsonPattern4))
|
||||
assert.deepEqual(['val1', 'val2', 'val3', 'val4', 'val5', 'val6'], nestedJsonObjToArray(JsonPattern5))
|
||||
})
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import SendContractError from '../../../../../../old-ui/app/components/send/send-error'
|
||||
import ErrorComponent from '../../../../../old-ui/app/components/error'
|
||||
import { mount } from 'enzyme'
|
||||
import thunk from 'redux-thunk'
|
||||
import configureMockStore from 'redux-mock-store'
|
||||
|
@ -16,12 +16,12 @@ const mockStore = configureMockStore(middlewares)
|
|||
const store = mockStore(state)
|
||||
let wrapper
|
||||
|
||||
describe('SendContractError component', () => {
|
||||
describe('renders SendContractError component', () => {
|
||||
describe('ErrorComponent', () => {
|
||||
describe('renders ErrorComponent', () => {
|
||||
beforeEach(function () {
|
||||
wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<SendContractError error="Error!"/>
|
||||
<ErrorComponent error="Error!"/>
|
||||
</Provider>
|
||||
)
|
||||
})
|
||||
|
@ -30,11 +30,11 @@ describe('SendContractError component', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('doesn\'t render SendContractError component', () => {
|
||||
describe('doesn\'t render ErrorComponent component', () => {
|
||||
beforeEach(function () {
|
||||
wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<SendContractError/>
|
||||
<ErrorComponent/>
|
||||
</Provider>
|
||||
)
|
||||
})
|
|
@ -12,6 +12,7 @@ describe('ChooseContractExecutor component', () => {
|
|||
metamask: {
|
||||
selectedAddress: '0x99a22ce737b6a48f44cad6331432ce98693cad07',
|
||||
accounts: ['0x99a22ce737b6a48f44cad6331432ce98693cad07'],
|
||||
cachedBalances: {'0x99a22ce737b6a48f44cad6331432ce98693cad07': 1},
|
||||
keyrings: [
|
||||
{
|
||||
'type': 'HD Key Tree',
|
||||
|
|
|
@ -11,6 +11,7 @@ const state = {
|
|||
metamask: {
|
||||
selectedAddress: '0x99a22ce737b6a48f44cad6331432ce98693cad07',
|
||||
accounts: ['0x99a22ce737b6a48f44cad6331432ce98693cad07'],
|
||||
cachedBalances: {'0x99a22ce737b6a48f44cad6331432ce98693cad07': 1},
|
||||
identities: {
|
||||
'0x99a22ce737b6a48f44cad6331432ce98693cad07': {
|
||||
name: 'Account 1',
|
||||
|
|
|
@ -4,7 +4,19 @@ const {
|
|||
getCurrentKeyring,
|
||||
ifLooseAcc,
|
||||
ifContractAcc,
|
||||
addressSummary,
|
||||
isValidAddress,
|
||||
numericBalance,
|
||||
parseBalance,
|
||||
formatBalance,
|
||||
normalizeToWei,
|
||||
normalizeEthStringToWei,
|
||||
normalizeNumberToWei,
|
||||
isHex,
|
||||
} = require('../../../../old-ui/app/util')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
let ethInWei = '1'
|
||||
for (let i = 0; i < 18; i++) { ethInWei += '0' }
|
||||
|
||||
describe('countSignificantDecimals(val, len) function', () => {
|
||||
it('returns correct significant decimals', () => {
|
||||
|
@ -74,3 +86,247 @@ describe('ifContractAcc(keyring) function', () => {
|
|||
assert.equal(false, ifContractAcc({}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addressSummary', function () {
|
||||
it('should add case-sensitive checksum', function () {
|
||||
const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'
|
||||
const result = addressSummary(address)
|
||||
assert.equal(result, '0xFDEa65C8...b825')
|
||||
})
|
||||
|
||||
it('should accept arguments for firstseg, lastseg, and keepPrefix', function () {
|
||||
const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'
|
||||
const result = addressSummary(address, 4, 4, false)
|
||||
assert.equal(result, 'FDEa...b825')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#isValidAddress', function () {
|
||||
it('should allow 40-char non-prefixed hex', function () {
|
||||
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825'
|
||||
const result = isValidAddress(address)
|
||||
assert.ok(result)
|
||||
})
|
||||
|
||||
it('should allow 42-char non-prefixed hex', function () {
|
||||
const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'
|
||||
const result = isValidAddress(address)
|
||||
assert.ok(result)
|
||||
})
|
||||
|
||||
it('should not allow less non hex-prefixed', function () {
|
||||
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85'
|
||||
const result = isValidAddress(address)
|
||||
assert.ok(!result)
|
||||
})
|
||||
|
||||
it('should not allow less hex-prefixed', function () {
|
||||
const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85'
|
||||
const result = isValidAddress(address)
|
||||
assert.ok(!result)
|
||||
})
|
||||
|
||||
it('should recognize correct capitalized checksum', function () {
|
||||
const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825'
|
||||
const result = isValidAddress(address)
|
||||
assert.ok(result)
|
||||
})
|
||||
|
||||
it('should recognize incorrect capitalized checksum', function () {
|
||||
const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825'
|
||||
const result = isValidAddress(address)
|
||||
assert.ok(!result)
|
||||
})
|
||||
|
||||
it('should recognize this sample hashed address', function () {
|
||||
const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0'
|
||||
const result = isValidAddress(address)
|
||||
const hashed = ethUtil.toChecksumAddress(address.toLowerCase())
|
||||
assert.equal(hashed, address, 'example is hashed correctly')
|
||||
assert.ok(result, 'is valid by our check')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#numericBalance', function () {
|
||||
it('should return a BN 0 if given nothing', function () {
|
||||
const result = numericBalance()
|
||||
assert.equal(result.toString(10), 0)
|
||||
})
|
||||
|
||||
it('should work with hex prefix', function () {
|
||||
const result = numericBalance('0x012')
|
||||
assert.equal(result.toString(10), '18')
|
||||
})
|
||||
|
||||
it('should work with no hex prefix', function () {
|
||||
const result = numericBalance('012')
|
||||
assert.equal(result.toString(10), '18')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#parseBalance', function () {
|
||||
it('should render 0.01 eth correctly', function () {
|
||||
const input = '0x2386F26FC10000'
|
||||
const output = parseBalance(input)
|
||||
assert.deepEqual(output, ['0', '01'])
|
||||
})
|
||||
|
||||
it('should render 12.023 eth correctly', function () {
|
||||
const input = 'A6DA46CCA6858000'
|
||||
const output = parseBalance(input)
|
||||
assert.deepEqual(output, ['12', '023'])
|
||||
})
|
||||
|
||||
it('should render 0.0000000342422 eth correctly', function () {
|
||||
const input = '0x7F8FE81C0'
|
||||
const output = parseBalance(input)
|
||||
assert.deepEqual(output, ['0', '0000000342422'])
|
||||
})
|
||||
|
||||
it('should render 0 eth correctly', function () {
|
||||
const input = '0x0'
|
||||
const output = parseBalance(input)
|
||||
assert.deepEqual(output, ['0', '0'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatBalance function', function () {
|
||||
it('when given nothing', function () {
|
||||
const result = formatBalance()
|
||||
assert.equal(result, '0', 'should return "None"')
|
||||
})
|
||||
|
||||
it('should return eth as string followed by ETH', function () {
|
||||
const input = new ethUtil.BN(ethInWei, 10).toJSON()
|
||||
console.log('input = ', input)
|
||||
const result = formatBalance(input, 4)
|
||||
assert.equal(result, '1.0000 ETH')
|
||||
})
|
||||
|
||||
it('should return eth as string followed by ETH', function () {
|
||||
const input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON()
|
||||
console.log('input = ', input)
|
||||
const result = formatBalance(input, 3)
|
||||
assert.equal(result, '0.500 ETH')
|
||||
})
|
||||
|
||||
it('should display specified decimal points', function () {
|
||||
const input = '0x128dfa6a90b28000'
|
||||
const result = formatBalance(input, 2)
|
||||
assert.equal(result, '1.33 ETH')
|
||||
})
|
||||
|
||||
it('should default to 3 decimal points', function () {
|
||||
const input = '0x128dfa6a90b28000'
|
||||
const result = formatBalance(input)
|
||||
assert.equal(result, '1.337 ETH')
|
||||
})
|
||||
|
||||
it('should show 2 significant digits for tiny balances', function () {
|
||||
const input = '0x1230fa6a90b28'
|
||||
const result = formatBalance(input)
|
||||
assert.equal(result, '0.00032 ETH')
|
||||
})
|
||||
|
||||
it('should not parse the balance and return value with 2 decimal points with ETH at the end', function () {
|
||||
const value = '1.2456789'
|
||||
const needsParse = false
|
||||
const result = formatBalance(value, 2, needsParse)
|
||||
assert.equal(result, '1.24 ETH')
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizing values', function () {
|
||||
describe('#normalizeToWei', function () {
|
||||
it('should convert an eth to the appropriate equivalent values', function () {
|
||||
var valueTable = {
|
||||
wei: '1000000000000000000',
|
||||
kwei: '1000000000000000',
|
||||
mwei: '1000000000000',
|
||||
gwei: '1000000000',
|
||||
szabo: '1000000',
|
||||
finney: '1000',
|
||||
ether: '1',
|
||||
// kether:'0.001',
|
||||
// mether:'0.000001',
|
||||
// AUDIT: We're getting BN numbers on these ones.
|
||||
// I think they're big enough to ignore for now.
|
||||
// gether:'0.000000001',
|
||||
// tether:'0.000000000001',
|
||||
}
|
||||
var oneEthBn = new ethUtil.BN(ethInWei, 10)
|
||||
|
||||
for (var currency in valueTable) {
|
||||
var value = new ethUtil.BN(valueTable[currency], 10)
|
||||
var output = normalizeToWei(value, currency)
|
||||
assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#normalizeEthStringToWei', function () {
|
||||
it('should convert decimal eth to pure wei BN', function () {
|
||||
var input = '1.23456789'
|
||||
var output = normalizeEthStringToWei(input)
|
||||
assert.equal(output.toString(10), '1234567890000000000')
|
||||
})
|
||||
|
||||
it('should convert 1 to expected wei', function () {
|
||||
var input = '1'
|
||||
var output = normalizeEthStringToWei(input)
|
||||
assert.equal(output.toString(10), ethInWei)
|
||||
})
|
||||
|
||||
it('should account for overflow numbers gracefully by dropping extra precision.', function () {
|
||||
var input = '1.11111111111111111111'
|
||||
var output = normalizeEthStringToWei(input)
|
||||
assert.equal(output.toString(10), '1111111111111111111')
|
||||
})
|
||||
|
||||
it('should not truncate very exact wei values that do not have extra precision.', function () {
|
||||
var input = '1.100000000000000001'
|
||||
var output = normalizeEthStringToWei(input)
|
||||
assert.equal(output.toString(10), '1100000000000000001')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#normalizeNumberToWei', function () {
|
||||
it('should handle a simple use case', function () {
|
||||
var input = 0.0002
|
||||
var output = normalizeNumberToWei(input, 'ether')
|
||||
var str = output.toString(10)
|
||||
assert.equal(str, '200000000000000')
|
||||
})
|
||||
|
||||
it('should convert a kwei number to the appropriate equivalent wei', function () {
|
||||
var result = normalizeNumberToWei(1.111, 'kwei')
|
||||
assert.equal(result.toString(10), '1111', 'accepts decimals')
|
||||
})
|
||||
|
||||
it('should convert a ether number to the appropriate equivalent wei', function () {
|
||||
var result = normalizeNumberToWei(1.111, 'ether')
|
||||
assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals')
|
||||
})
|
||||
})
|
||||
describe('#isHex', function () {
|
||||
it('should return true when given a hex string', function () {
|
||||
var result = isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2')
|
||||
assert(result)
|
||||
})
|
||||
|
||||
it('should return false when given a non-hex string', function () {
|
||||
var result = isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal')
|
||||
assert(!result)
|
||||
})
|
||||
|
||||
it('should return false when given a string containing a non letter/number character', function () {
|
||||
var result = isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal')
|
||||
assert(!result)
|
||||
})
|
||||
|
||||
it('should return true when given a hex string with hex-prefix', function () {
|
||||
var result = isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2')
|
||||
assert(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -128,12 +128,14 @@ describe('util', function () {
|
|||
|
||||
it('should return eth as string followed by ETH', function () {
|
||||
var input = new ethUtil.BN(ethInWei, 10).toJSON()
|
||||
console.log('input = ', input)
|
||||
var result = util.formatBalance(input, 4)
|
||||
assert.equal(result, '1.0000 ETH')
|
||||
})
|
||||
|
||||
it('should return eth as string followed by ETH', function () {
|
||||
var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON()
|
||||
console.log('input = ', input)
|
||||
var result = util.formatBalance(input, 3)
|
||||
assert.equal(result, '0.500 ETH')
|
||||
})
|
||||
|
|
|
@ -11,6 +11,9 @@ const ethUtil = require('ethereumjs-util')
|
|||
const { fetchLocale } = require('../i18n-helper')
|
||||
const log = require('loglevel')
|
||||
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums')
|
||||
const { POA,
|
||||
DAI,
|
||||
POA_SOKOL } = require('../../app/scripts/controllers/network/enums')
|
||||
const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util')
|
||||
const WebcamUtils = require('../lib/webcam-utils')
|
||||
|
||||
|
@ -71,6 +74,7 @@ var actions = {
|
|||
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
|
||||
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
|
||||
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
|
||||
SHOW_FORGET_DEVICE_PAGE: 'SHOW_FORGET_DEVICE_PAGE',
|
||||
SHOW_HARDWARE_WALLET_PAGE: 'SHOW_HARDWARE_WALLET_PAGE',
|
||||
SHOW_NEW_ACCOUNT_PAGE: 'SHOW_NEW_ACCOUNT_PAGE',
|
||||
SET_NEW_ACCOUNT_FORM: 'SET_NEW_ACCOUNT_FORM',
|
||||
|
@ -81,6 +85,7 @@ var actions = {
|
|||
showRestoreVault: showRestoreVault,
|
||||
showInitializeMenu: showInitializeMenu,
|
||||
showImportPage,
|
||||
showForgetDevicePage,
|
||||
showConnectHWWalletPage: showConnectHWWalletPage,
|
||||
showNewAccountPage,
|
||||
setNewAccountForm,
|
||||
|
@ -91,6 +96,7 @@ var actions = {
|
|||
importNewAccount,
|
||||
addNewAccount,
|
||||
connectHardware,
|
||||
connectHardwareAndUnlockAddress,
|
||||
checkHardwareStatus,
|
||||
forgetDevice,
|
||||
unlockHardwareWalletAccount,
|
||||
|
@ -100,6 +106,7 @@ var actions = {
|
|||
changePassword,
|
||||
getContract,
|
||||
removeAccount,
|
||||
updateABI,
|
||||
showNewVaultSeed: showNewVaultSeed,
|
||||
showInfoPage: showInfoPage,
|
||||
CLOSE_WELCOME_SCREEN: 'CLOSE_WELCOME_SCREEN',
|
||||
|
@ -659,6 +666,26 @@ function removeAccount (address, network) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateABI (address, network, newABI) {
|
||||
return dispatch => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
background.updateABI(address, network, newABI, (err, account) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
log.info('Implementation ABI for proxy updated: ' + account)
|
||||
dispatch(actions.showAccountsPage())
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function addNewKeyring (type, opts) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
@ -746,12 +773,12 @@ function checkHardwareStatus (deviceName, hdPath) {
|
|||
}
|
||||
}
|
||||
|
||||
function forgetDevice (deviceName) {
|
||||
function forgetDevice (deviceName, clearAccounts) {
|
||||
log.debug(`background.forgetDevice`, deviceName)
|
||||
return (dispatch, getState) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
background.forgetDevice(deviceName, (err, response) => {
|
||||
background.forgetDevice(deviceName, clearAccounts, (err, accountsToForget) => {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
|
@ -761,7 +788,7 @@ function forgetDevice (deviceName) {
|
|||
dispatch(actions.hideLoadingIndication())
|
||||
|
||||
forceUpdateMetamaskState(dispatch)
|
||||
return resolve()
|
||||
return resolve(accountsToForget)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -788,6 +815,23 @@ function connectHardware (deviceName, page, hdPath) {
|
|||
}
|
||||
}
|
||||
|
||||
function connectHardwareAndUnlockAddress (deviceName, hdPath, addressToUnlock) {
|
||||
log.debug(`background.connectHardwareAndUnlockAddress`, deviceName, hdPath, addressToUnlock)
|
||||
return (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
background.connectHardwareAndUnlockAddress(deviceName, hdPath, addressToUnlock, (err, accounts) => {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
forceUpdateMetamaskState(dispatch)
|
||||
return resolve(accounts)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function unlockHardwareWalletAccount (index, deviceName, hdPath) {
|
||||
log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
|
||||
return (dispatch, getState) => {
|
||||
|
@ -1490,6 +1534,13 @@ function showImportPage () {
|
|||
}
|
||||
}
|
||||
|
||||
function showForgetDevicePage (device) {
|
||||
return {
|
||||
type: actions.SHOW_FORGET_DEVICE_PAGE,
|
||||
value: device,
|
||||
}
|
||||
}
|
||||
|
||||
function showConnectHWWalletPage () {
|
||||
return {
|
||||
type: actions.SHOW_HARDWARE_WALLET_PAGE,
|
||||
|
@ -1959,7 +2010,9 @@ function setProviderType (type) {
|
|||
dispatch(actions.setSelectedToken())
|
||||
})
|
||||
|
||||
const newCoin = type === 'poa' || type === 'sokol' ? 'poa' : type === 'dai' ? 'dai' : 'eth'
|
||||
const newCoin = type === POA || type === POA_SOKOL ?
|
||||
'poa' : type === DAI ?
|
||||
'dai' : 'eth'
|
||||
background.setCurrentCoin(newCoin, (err, data) => {
|
||||
if (err) {
|
||||
log.error(err.stack)
|
||||
|
|
|
@ -7,6 +7,7 @@ const h = require('react-hyperscript')
|
|||
const actions = require('./actions')
|
||||
const classnames = require('classnames')
|
||||
const log = require('loglevel')
|
||||
const { getMetaMaskAccounts } = require('./selectors')
|
||||
|
||||
// init
|
||||
const InitializeScreen = require('../../mascara/src/app/first-time').default
|
||||
|
@ -279,9 +280,10 @@ function mapStateToProps (state) {
|
|||
loadingMessage,
|
||||
} = appState
|
||||
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
|
||||
const {
|
||||
identities,
|
||||
accounts,
|
||||
address,
|
||||
keyrings,
|
||||
isInitialized,
|
||||
|
|
|
@ -13,6 +13,7 @@ const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
|
|||
const Tooltip = require('../tooltip')
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import { PRIMARY } from '../../constants/common'
|
||||
import { getMetaMaskAccounts } from '../../selectors'
|
||||
|
||||
const {
|
||||
SETTINGS_ROUTE,
|
||||
|
@ -41,7 +42,7 @@ function mapStateToProps (state) {
|
|||
isAccountMenuOpen: state.metamask.isAccountMenuOpen,
|
||||
keyrings: state.metamask.keyrings,
|
||||
identities: state.metamask.identities,
|
||||
accounts: state.metamask.accounts,
|
||||
accounts: getMetaMaskAccounts(state),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue