Merge pull request #264 from poanetwork/develop

NW release 4.11.0
This commit is contained in:
Victor Baranov 2019-02-08 20:47:16 +03:00 committed by GitHub
commit 7397335f27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
111 changed files with 6696 additions and 4815 deletions

View File

@ -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:

View File

@ -11,6 +11,7 @@ development/publish-release.js
app/scripts/lib/extension-instance.js
app/scripts/chromereload.js
app/vendor/**
ui/lib/blockies.js

View File

@ -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

View File

@ -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": [

View File

@ -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

View File

@ -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

View File

@ -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,
}

View File

@ -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

View File

@ -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,
}

View File

@ -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 []
}
}

View File

@ -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) {

View File

@ -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'

View File

@ -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>

21
app/vendor/trezor/content-script.js vendored Normal file
View File

@ -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);
}
});

50
app/vendor/trezor/usb-permissions.js vendored Normal file
View File

@ -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);

View File

@ -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/',

View File

@ -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: {

View File

@ -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 = {

View File

@ -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: '',

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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)

View File

@ -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

View File

@ -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'})

View File

@ -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)),
},
}
}

View File

@ -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)

View File

@ -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 || '')
}

View File

@ -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',
]
),

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -0,0 +1,7 @@
const LEDGER = 'ledger'
const TREZOR = 'trezor'
module.exports = {
LEDGER,
TREZOR,
}

View File

@ -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)

View File

@ -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()),
}

View File

@ -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,
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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'

View File

@ -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',

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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)

View File

@ -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('.')

View File

@ -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')

View File

@ -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)

View File

@ -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',

View File

@ -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,
])
}

View File

@ -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)

View File

@ -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.'),
]),

View File

@ -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

View File

@ -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', [

View File

@ -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;
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -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 () {

198
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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',
},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

139
test/e2e/nw.spec.js Normal file
View File

@ -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)
})
})

View File

@ -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,
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',
},
])
})

View File

@ -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)
})
})
})

View File

@ -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'`)
})

View File

@ -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))
})
})

View File

@ -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>
)
})

View File

@ -12,6 +12,7 @@ describe('ChooseContractExecutor component', () => {
metamask: {
selectedAddress: '0x99a22ce737b6a48f44cad6331432ce98693cad07',
accounts: ['0x99a22ce737b6a48f44cad6331432ce98693cad07'],
cachedBalances: {'0x99a22ce737b6a48f44cad6331432ce98693cad07': 1},
keyrings: [
{
'type': 'HD Key Tree',

View File

@ -11,6 +11,7 @@ const state = {
metamask: {
selectedAddress: '0x99a22ce737b6a48f44cad6331432ce98693cad07',
accounts: ['0x99a22ce737b6a48f44cad6331432ce98693cad07'],
cachedBalances: {'0x99a22ce737b6a48f44cad6331432ce98693cad07': 1},
identities: {
'0x99a22ce737b6a48f44cad6331432ce98693cad07': {
name: 'Account 1',

View File

@ -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)
})
})
})

View File

@ -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')
})

View File

@ -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)

View File

@ -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,

View File

@ -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