aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2018-02-28 03:25:29 +0800
committerGitHub <noreply@github.com>2018-02-28 03:25:29 +0800
commit3fefccd37219c9b4b513fc8d929723e07022b9c4 (patch)
tree22865ecd672570a6162ac3c9402ec9d63ad3f7ef /test
parent6a7ea00cd34f83b257f6b4280a5f4e20aa5d34ee (diff)
parentced62ac551a095c8f94f550f0c01a9d4fd04ce5b (diff)
downloadtangerine-wallet-browser-3fefccd37219c9b4b513fc8d929723e07022b9c4.tar.gz
tangerine-wallet-browser-3fefccd37219c9b4b513fc8d929723e07022b9c4.tar.zst
tangerine-wallet-browser-3fefccd37219c9b4b513fc8d929723e07022b9c4.zip
Merge branch 'master' into mascara-deploy
Diffstat (limited to 'test')
-rw-r--r--test/base.conf.js61
-rw-r--r--test/data/v17-long-history.json3053
-rw-r--r--test/flat.conf.js8
-rw-r--r--test/helper.js12
-rw-r--r--test/integration/helpers.js7
-rw-r--r--test/integration/index.js37
-rw-r--r--test/integration/lib/add-token.js153
-rw-r--r--test/integration/lib/confirm-sig-requests.js67
-rw-r--r--test/integration/lib/first-time.js196
-rw-r--r--test/integration/lib/mascara-first-time.js148
-rw-r--r--test/integration/lib/send-new-ui.js225
-rw-r--r--test/lib/migrations/001.json15
-rw-r--r--test/lib/migrations/002.json1
-rw-r--r--test/lib/mock-config-manager.js7
-rw-r--r--test/lib/mock-encryptor.js16
-rw-r--r--test/lib/mock-simple-keychain.js14
-rw-r--r--test/lib/mock-store.js18
-rw-r--r--test/lib/mock-tx-gen.js40
-rw-r--r--test/lib/shallow-with-store.js20
-rw-r--r--test/mascara.conf.js17
-rw-r--r--test/stub/provider.js31
-rw-r--r--test/unit/account-link-test.js18
-rw-r--r--test/unit/actions/config_test.js23
-rw-r--r--test/unit/actions/save_account_label_test.js21
-rw-r--r--test/unit/actions/set_selected_account_test.js23
-rw-r--r--test/unit/actions/tx_test.js142
-rw-r--r--test/unit/actions/view_info_test.js15
-rw-r--r--test/unit/actions/warning_test.js13
-rw-r--r--test/unit/address-book-controller.js16
-rw-r--r--test/unit/blacklist-controller-test.js41
-rw-r--r--test/unit/components/balance-component-test.js45
-rw-r--r--test/unit/components/binary-renderer-test.js10
-rw-r--r--test/unit/components/bn-as-decimal-input-test.js89
-rw-r--r--test/unit/components/pending-tx-test.js67
-rw-r--r--test/unit/config-manager-test.js42
-rw-r--r--test/unit/currency-controller-test.js59
-rw-r--r--test/unit/explorer-link-test.js16
-rw-r--r--test/unit/infura-controller-test.js62
-rw-r--r--test/unit/keyring-controller-test.js172
-rw-r--r--test/unit/linting_test.js9
-rw-r--r--test/unit/message-manager-test.js41
-rw-r--r--test/unit/metamask-controller-test.js114
-rw-r--r--test/unit/migrations-test.js10
-rw-r--r--test/unit/migrations/021-test.js16
-rw-r--r--test/unit/migrator-test.js41
-rw-r--r--test/unit/nameForAccount_test.js17
-rw-r--r--test/unit/network-contoller-test.js84
-rw-r--r--test/unit/nodeify-test.js18
-rw-r--r--test/unit/nonce-tracker-test.js203
-rw-r--r--test/unit/notice-controller-test.js71
-rw-r--r--test/unit/pending-balance-test.js93
-rw-r--r--test/unit/pending-tx-test.js402
-rw-r--r--test/unit/personal-message-manager-test.js47
-rw-r--r--test/unit/preferences-controller-test.js48
-rw-r--r--test/unit/reducers/unlock_vault_test.js27
-rw-r--r--test/unit/responsive/components/dropdown-test.js81
-rw-r--r--test/unit/tx-controller-test.js413
-rw-r--r--test/unit/tx-gas-util-test.js32
-rw-r--r--test/unit/tx-helper-test.js17
-rw-r--r--test/unit/tx-manager-test.js241
-rw-r--r--test/unit/tx-state-history-helper-test.js26
-rw-r--r--test/unit/tx-state-history-helper.js46
-rw-r--r--test/unit/tx-state-manager-test.js284
-rw-r--r--test/unit/tx-utils-test.js50
-rw-r--r--test/unit/ui/add-token.spec.js43
-rw-r--r--test/unit/util-test.js41
-rw-r--r--test/unit/util_test.js131
67 files changed, 6624 insertions, 1042 deletions
diff --git a/test/base.conf.js b/test/base.conf.js
new file mode 100644
index 000000000..82b9d8eec
--- /dev/null
+++ b/test/base.conf.js
@@ -0,0 +1,61 @@
+// Karma configuration
+// Generated on Mon Sep 11 2017 18:45:48 GMT-0700 (PDT)
+
+module.exports = function(config) {
+ return {
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: process.cwd(),
+
+ browserConsoleLogOptions: {
+ terminal: false,
+ },
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['qunit'],
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'test/integration/jquery-3.1.0.min.js',
+ { pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true },
+ { pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true },
+ ],
+
+ proxies: {
+ '/images/': '/base/dist/chrome/images/',
+ '/fonts/': '/base/dist/chrome/fonts/',
+ },
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+ // web server port
+ port: 9876,
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome', 'Firefox'],
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: true,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: 1,
+
+ nocache: true,
+ }
+}
diff --git a/test/data/v17-long-history.json b/test/data/v17-long-history.json
new file mode 100644
index 000000000..a33d425f8
--- /dev/null
+++ b/test/data/v17-long-history.json
@@ -0,0 +1,3053 @@
+{
+ "meta": {
+ "version": 17
+ },
+ "data": {
+ "config": {},
+ "NetworkController": {
+ "provider": {
+ "type": "ropsten",
+ "rpcTarget": "https://ropsten.infura.io/metamask"
+ },
+ "network": "3"
+ },
+ "NoticeController": {
+ "noticesList": [
+ {
+ "read": true,
+ "date": "Thu Feb 09 2017",
+ "title": "Terms of Use",
+ "body": "",
+ "id": 0
+ },
+ {
+ "read": true,
+ "date": "Mon May 08 2017",
+ "title": "Privacy Notice",
+ "body": "",
+ "id": 2
+ }
+ ]
+ },
+ "CurrencyController": {
+ "currentCurrency": "USD",
+ "conversionRate": 295.81988556,
+ "conversionDate": 1502734981
+ },
+ "KeyringController": {
+ "vault": "{\"data\":\"fFwVD3Msyq1o9NsDbjMlyJ1ZfoMcqfTgjR9cium0C5Vnpk9IM6f/RTVXfk0J4c4UkbgbKd++q8t1S+D22s7Ddz/BT/fe0GrbwPvAYQi1oJuOI9/Lf7I0JbESGv4PheijCIH4h/FiO+tIAuqM0Co3PULM4mOHdzXD8SWmzxbDGx+4wG84EQE9a1NEbqEjyqrX02h3NwZsjrSeuV5TibpGJB9vnKNpDu9wF0DVKLtLCG5n67uoTI/ve9Z7hIDa03vNi/71iE4avFb6ogE2SAkFDncEcU0xXVkBMapBXjrpe5sIq08Ddo0Hhi4fkd4yFW77sAH4TKzd6bWSn2AK8HL8Gpcrk4R6Cvv8EtyjUqsOJfE4AmYI6rWfFutLqEAp\",\"iv\":\"9fJ/OGDVwUnu3H0U71qOGA==\",\"salt\":\"5yGOu/+yrrb3DyP+cvMKIZqjhSjrEY+bnceHnz9n8gM=\"}",
+ "walletNicknames": {
+ "0x3ae39e89dc7e736cce53091057a45bf44b1a566c": "Account 1",
+ "0xa7a467edcb16a51976418ec6133f14f7939dc378": "Account 2",
+ "0x03ce38bd04b4ad7581a7070570381a530951ebbe": "Account 3"
+ }
+ },
+ "PreferencesController": {
+ "frequentRpcList": [],
+ "currentAccountTab": "history",
+ "tokens": [],
+ "selectedAddress": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c"
+ },
+ "seedWords": null,
+ "InfuraController": {
+ "infuraNetworkStatus": {
+ "mainnet": "degraded",
+ "ropsten": "ok",
+ "kovan": "ok",
+ "rinkeby": "ok"
+ }
+ },
+ "BlacklistController": {
+ "phishing": {
+ "version": 2,
+ "tolerance": 2,
+ "fuzzylist": [
+ "metamask.io",
+ "myetherwallet.com"
+ ],
+ "whitelist": [
+ "metamask.io",
+ "myetherwallet.com",
+ "ethereum.org",
+ "myetheroll.com",
+ "myetherapi.com",
+ "ledgerwallet.com",
+ "etherscan.io",
+ "etherid.org",
+ "ether.cards",
+ "etheroll.com",
+ "ethnews.com",
+ "ethex.market",
+ "ethereumdev.io",
+ "ethereumdev.kr",
+ "dether.io",
+ "ethermine.org",
+ "slaask.com",
+ "etherbtc.io",
+ "ethereal.capital",
+ "etherisc.com",
+ "m.famalk.net",
+ "etherecho.com",
+ "ethereum.os.tc",
+ "theethereum.wiki",
+ "metajack.im",
+ "etherhub.io",
+ "ethereum.network",
+ "ethereum.link",
+ "ethereum.com",
+ "prethereum.org",
+ "ethereumj.io",
+ "etheraus.com",
+ "ethereum.dev",
+ "1ethereum.ru",
+ "ethereum.nz",
+ "nethereum.com",
+ "metabank.com",
+ "metamas.com",
+ "metabase.com"
+ ],
+ "blacklist": [
+ "numerai.tech",
+ "decentraiand.org",
+ "blockcrein.info",
+ "blockchealn.info",
+ "bllookchain.info",
+ "blockcbhain.info",
+ "myetherwallet.com.ethpromonodes.com",
+ "mettamask.io",
+ "tokenswap.org",
+ "netherum.com",
+ "etherexx.org",
+ "etherume.io",
+ "ethereum.plus",
+ "ehtereum.org",
+ "etereurm.org",
+ "etheream.com",
+ "ethererum.org",
+ "ethereum.io",
+ "0xtoken.com",
+ "cryptoalliance.herokuapp.com",
+ "bitspark2.com",
+ "indorsetoken.com",
+ "bittreat.com",
+ "iconexus.tk",
+ "iconexus.ml",
+ "iconexus.ga",
+ "iconexus.cf",
+ "etherwallet.online",
+ "wallet-ethereum.net",
+ "bitsdigit.com",
+ "etherswap.org",
+ "eos.ac",
+ "uasfwallet.com",
+ "ziber.io",
+ "multiply-ethereum.info",
+ "bittrex.comze.com",
+ "karbon.vacau.com",
+ "etherdelta.gitlhub.io",
+ "etherdelta.glthub.io",
+ "digitaldevelopersfund.vacau.com",
+ "district-0x.io",
+ "coin-dash.com",
+ "coindash.ru",
+ "district0x.net",
+ "aragonproject.io",
+ "coin-wallet.info",
+ "coinswallet.info",
+ "contribute-status.im",
+ "ether-api.com",
+ "ether-wall.com",
+ "mycoinwallet.net",
+ "ethereumchamber.com",
+ "ethereumchamber.net",
+ "ethereumchest.com",
+ "myetherweb.com.de",
+ "myetherieumwallet.com",
+ "myetehrwallet.com",
+ "myeterwalet.com",
+ "myetherwaiiet.com",
+ "myetherwallet.info",
+ "myetherwallet.ch",
+ "myetherwallet.om",
+ "myethervallet.com",
+ "myetherwallet.com.cm",
+ "myetherwallet.com.co",
+ "myetherwallet.com.de",
+ "myetherwallet.com.gl",
+ "myetherwallet.com.im",
+ "myetherwallet.com.ua",
+ "secure-myetherwallet.com",
+ "update-myetherwallet.com",
+ "wwwmyetherwallet.com",
+ "myeatherwallet.com",
+ "myetharwallet.com",
+ "myelherwallel.com",
+ "myetherwaillet.com",
+ "myetherwaliet.com",
+ "myetherwallel.com",
+ "myetherwallet.cam",
+ "myetherwallet.cc",
+ "myetherwallet.co",
+ "myetherwallet.cm",
+ "myetherwallet.cz",
+ "myetherwallet.org",
+ "myetherwallet.tech",
+ "myetherwallet.top",
+ "myetherwallet.net",
+ "etherclassicwallet.com",
+ "omg-omise.co",
+ "omise-go.com",
+ "tenx-tech.com",
+ "tokensale-tenx.tech",
+ "ubiqcoin.org",
+ "metamask.com",
+ "ethtrade.io",
+ "myetcwallet.com",
+ "account-kigo.net",
+ "bitcoin-wallet.net",
+ "blocklichan.info",
+ "bloclkicihan.info",
+ "coindash.ml",
+ "eos-bonus.com",
+ "eos-io.info",
+ "ether-wallet.net",
+ "ethereum-wallet.info",
+ "ethereum-wallet.net",
+ "ethereumchest.net",
+ "reservations-kigo.net",
+ "reservations-lodgix.com",
+ "secure-liverez.com",
+ "secure-onerooftop.com",
+ "settings-liverez.com",
+ "software-liverez.com",
+ "software-lodgix.com",
+ "unhackableetherwallets.com",
+ "www-myetherwallet.com",
+ "etherwallet.co.za",
+ "etherwalletchain.com",
+ "etherwallets.net",
+ "etherwallets.nl",
+ "my-ethwallet.com",
+ "my.ether-wallet.co",
+ "myetherwallet.com.am",
+ "myetherwallet.com.ht",
+ "myetherwalletcom.com",
+ "xn--myetherwalle-xoc.com",
+ "xn--myetherwalle-44i.com",
+ "xn--myetherwalle-xhk.com",
+ "xn--myetherwallt-cfb.com",
+ "xn--myetherwallt-6tb.com",
+ "xn--myetherwallt-xub.com",
+ "xn--myetherwallt-ovb.com",
+ "xn--myetherwallt-fwb.com",
+ "xn--myetherwallt-5wb.com",
+ "xn--myetherwallt-jzi.com",
+ "xn--myetherwallt-2ck.com",
+ "xn--myetherwallt-lok.com",
+ "xn--myetherwallt-lsl.com",
+ "xn--myetherwallt-ce6f.com",
+ "xn--myetherwalet-mcc.com",
+ "xn--myetherwalet-xhf.com",
+ "xn--myetherwalet-lcc.com",
+ "xn--myetherwaet-15ba.com",
+ "xn--myetherwalet-whf.com",
+ "xn--myetherwaet-v2ea.com",
+ "xn--myetherwllet-59a.com",
+ "xn--myetherwllet-jbb.com",
+ "xn--myetherwllet-wbb.com",
+ "xn--myetherwllet-9bb.com",
+ "xn--myetherwllet-ncb.com",
+ "xn--myetherwllet-0cb.com",
+ "xn--myetherwllet-5nb.com",
+ "xn--myetherwllet-ktd.com",
+ "xn--myetherwllet-mre.com",
+ "xn--myetherwllet-76e.com",
+ "xn--myetherwllet-o0l.com",
+ "xn--myetherwllet-c45f.com",
+ "xn--myetherallet-ejn.com",
+ "xn--myethewallet-4nf.com",
+ "xn--myethewallet-iof.com",
+ "xn--myethewallet-mpf.com",
+ "xn--myethewallet-6bk.com",
+ "xn--myethewallet-i31f.com",
+ "xn--myethrwallet-feb.com",
+ "xn--myethrwallt-fbbf.com",
+ "xn--myethrwallet-seb.com",
+ "xn--myethrwallt-rbbf.com",
+ "xn--myethrwallet-5eb.com",
+ "xn--myethrwallt-3bbf.com",
+ "xn--myethrwallet-0tb.com",
+ "xn--myethrwallt-tpbf.com",
+ "xn--myethrwallet-rub.com",
+ "xn--myethrwallt-iqbf.com",
+ "xn--myethrwallet-ivb.com",
+ "xn--myethrwallt-6qbf.com",
+ "xn--myethrwallet-8vb.com",
+ "xn--myethrwallt-vrbf.com",
+ "xn--myethrwallet-zwb.com",
+ "xn--myethrwallt-ksbf.com",
+ "xn--myethrwallet-dzi.com",
+ "xn--myethrwallt-wbif.com",
+ "xn--myethrwallet-wck.com",
+ "xn--myethrwallt-skjf.com",
+ "xn--myethrwallet-fok.com",
+ "xn--myethrwallt-fvjf.com",
+ "xn--myethrwallet-fsl.com",
+ "xn--myethrwallt-fwkf.com",
+ "xn--myethrwallet-5d6f.com",
+ "xn--myethrwallt-319ef.com",
+ "xn--myeterwallet-ufk.com",
+ "xn--myeterwallet-nrl.com",
+ "xn--myeterwallet-von.com",
+ "xn--myeterwallet-jl6c.com",
+ "xn--myeherwallet-ooc.com",
+ "xn--myeherwalle-6hci.com",
+ "xn--myeherwallet-v4i.com",
+ "xn--myeherwalle-zgii.com",
+ "xn--myeherwallet-ohk.com",
+ "xn--myeherwalle-6oji.com",
+ "xn--mytherwallet-ceb.com",
+ "xn--mythrwallet-cbbc.com",
+ "xn--mythrwallt-c7acf.com",
+ "xn--mytherwallet-peb.com",
+ "xn--mythrwallet-obbc.com",
+ "xn--mythrwallt-n7acf.com",
+ "xn--mytherwallet-2eb.com",
+ "xn--mythrwallet-0bbc.com",
+ "xn--mythrwallt-y7acf.com",
+ "xn--mytherwallet-xtb.com",
+ "xn--mythrwallet-qpbc.com",
+ "xn--mythrwallt-jlbcf.com",
+ "xn--mytherwallet-oub.com",
+ "xn--mythrwallet-fqbc.com",
+ "xn--mythrwallt-5lbcf.com",
+ "xn--mythrwallet-3qbc.com",
+ "xn--mythrwallt-smbcf.com",
+ "xn--mytherwallet-5vb.com",
+ "xn--mythrwallet-srbc.com",
+ "xn--mythrwallt-fnbcf.com",
+ "xn--mytherwallet-wwb.com",
+ "xn--mythrwallet-hsbc.com",
+ "xn--mythrwallt-1nbcf.com",
+ "xn--mytherwallet-9yi.com",
+ "xn--mythrwallet-tbic.com",
+ "xn--mythrwallt-dnhcf.com",
+ "xn--mytherwallet-tck.com",
+ "xn--mythrwallet-pkjc.com",
+ "xn--mythrwallt-lsicf.com",
+ "xn--mytherwallet-cok.com",
+ "xn--mythrwallet-cvjc.com",
+ "xn--mythrwallt-c2icf.com",
+ "xn--mytherwallet-csl.com",
+ "xn--mythrwallet-cwkc.com",
+ "xn--mythrwallt-c0jcf.com",
+ "xn--mytherwallet-2d6f.com",
+ "xn--mythrwallet-019ec.com",
+ "xn--mythrwallt-yq3ecf.com",
+ "xn--metherwallet-qlb.com",
+ "xn--metherwallet-1uf.com",
+ "xn--metherwallet-iyi.com",
+ "xn--metherwallet-zhk.com",
+ "xn--metherwallet-3ml.com",
+ "xn--mytherwallet-fvb.com",
+ "xn--myetherwallt-7db.com",
+ "xn--myetherwallt-leb.com",
+ "xn--myetherwallt-yeb.com",
+ "xn--yetherwallet-vjf.com",
+ "xn--yetherwallet-dfk.com",
+ "xn--yetherwallet-1t1f.com",
+ "xn--yetherwallet-634f.com"
+ ]
+ }
+ },
+ "AddressBookController": {
+ "addressBook": []
+ },
+ "TransactionController": {
+ "transactions": [
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "history": [
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": 0
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": 0
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264",
+ "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264",
+ "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264",
+ "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264",
+ "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012"
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264",
+ "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012",
+ "retryCount": 1
+ },
+ {
+ "id": 6616756286038869,
+ "time": 1502438908445,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264",
+ "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012",
+ "retryCount": 1
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16643c",
+ "baseCount": 0,
+ "baseCountHex": "0x0",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264",
+ "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012",
+ "retryCount": 1
+ },
+ {
+ "id": 6616756286038870,
+ "time": 1502573153664,
+ "status": "rejected",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d"
+ },
+ "history": [
+ {
+ "id": 6616756286038870,
+ "time": 1502573153664,
+ "status": "rejected",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038870,
+ "time": 1502573153664,
+ "status": "rejected",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "history": [
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": 1
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": 1
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0",
+ "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0",
+ "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0",
+ "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0",
+ "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150"
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0",
+ "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150",
+ "retryCount": 1
+ },
+ {
+ "id": 6616756286038871,
+ "time": 1502573157128,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x28fa6ae00",
+ "gas": "0x7b0d",
+ "nonce": "0x01",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0",
+ "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150",
+ "retryCount": 1
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x168739",
+ "baseCount": 1,
+ "baseCountHex": "0x1",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0",
+ "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150",
+ "retryCount": 1
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "history": [
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 2
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 2
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ }
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc",
+ "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc",
+ "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc",
+ "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc",
+ "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270"
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc",
+ "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270",
+ "retryCount": 1
+ },
+ {
+ "id": 6616756286038872,
+ "time": 1502734903652,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x02",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc",
+ "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270",
+ "retryCount": 1
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b066",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 0
+ },
+ "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc",
+ "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270",
+ "retryCount": 1
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "history": [
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ }
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ }
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ }
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ }
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897",
+ "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897",
+ "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897",
+ "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897",
+ "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7"
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897",
+ "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7",
+ "retryCount": 2
+ },
+ {
+ "id": 6616756286038873,
+ "time": 1502734910224,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x3b9aca00",
+ "gas": "0x7b0d",
+ "nonce": "0x03",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897",
+ "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7",
+ "retryCount": 2
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 1
+ },
+ "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897",
+ "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7",
+ "retryCount": 2
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "history": [
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 4
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 4
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3",
+ "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3",
+ "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3",
+ "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3",
+ "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635"
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3",
+ "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635",
+ "retryCount": 4
+ },
+ {
+ "id": 6616756286038874,
+ "time": 1502734917414,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x04",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3",
+ "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635",
+ "retryCount": 4
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b067",
+ "baseCount": 2,
+ "baseCountHex": "0x2",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3",
+ "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635",
+ "retryCount": 4
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "history": [
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 5
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 5
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ }
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5",
+ "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5",
+ "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5",
+ "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5",
+ "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e"
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5",
+ "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e",
+ "retryCount": 3
+ },
+ {
+ "id": 6616756286038875,
+ "time": 1502734922745,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x05",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5",
+ "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e",
+ "retryCount": 3
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 2
+ },
+ "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5",
+ "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e",
+ "retryCount": 3
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "history": [
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "unapproved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d"
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 6
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ }
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "approved",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "4a817c800",
+ "gas": "0x7b0d",
+ "nonce": 6
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ }
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ }
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ }
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7",
+ "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "signed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7",
+ "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7",
+ "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "submitted",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7",
+ "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1"
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7",
+ "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1",
+ "retryCount": 5
+ },
+ {
+ "id": 6616756286038876,
+ "time": 1502734928623,
+ "status": "confirmed",
+ "metamaskNetworkId": "3",
+ "txParams": {
+ "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c",
+ "value": "0x0",
+ "gasPrice": "0x4a817c800",
+ "gas": "0x7b0d",
+ "nonce": "0x06",
+ "chainId": 3
+ },
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7",
+ "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1",
+ "retryCount": 5
+ }
+ ],
+ "gasLimitSpecified": false,
+ "estimatedGas": "5209",
+ "nonceDetails": {
+ "blockNumber": "0x16b068",
+ "baseCount": 3,
+ "baseCountHex": "0x3",
+ "pendingCount": 3
+ },
+ "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7",
+ "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1",
+ "retryCount": 5
+ }
+ ]
+ }
+ }
+} \ No newline at end of file
diff --git a/test/flat.conf.js b/test/flat.conf.js
new file mode 100644
index 000000000..cd2dbdcdc
--- /dev/null
+++ b/test/flat.conf.js
@@ -0,0 +1,8 @@
+const getBaseConfig = require('./base.conf.js')
+
+module.exports = function(config) {
+ const settings = getBaseConfig(config)
+ settings.files.push('development/bundle.js')
+ settings.files.push('test/integration/bundle.js')
+ config.set(settings)
+}
diff --git a/test/helper.js b/test/helper.js
index aaac7580e..a3abbebf2 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -1,3 +1,7 @@
+import Enzyme from 'enzyme'
+import Adapter from 'enzyme-adapter-react-15'
+
+Enzyme.configure({ adapter: new Adapter() })
// disallow promises from swallowing errors
enableFailureOnUnhandledPromiseRejection()
@@ -20,14 +24,12 @@ window.localStorage = {}
if (!window.crypto) window.crypto = {}
if (!window.crypto.getRandomValues) window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues')
-
-
-function enableFailureOnUnhandledPromiseRejection() {
+function enableFailureOnUnhandledPromiseRejection () {
// overwrite node's promise with the stricter Bluebird promise
global.Promise = require('bluebird')
// modified from https://github.com/mochajs/mocha/issues/1926#issuecomment-180842722
-
+
// rethrow unhandledRejections
if (typeof process !== 'undefined') {
process.on('unhandledRejection', function (reason) {
@@ -51,4 +53,4 @@ function enableFailureOnUnhandledPromiseRejection() {
typeof (console.error || console.log) === 'function') {
(console.error || console.log)('Unhandled rejections will be ignored!')
}
-} \ No newline at end of file
+}
diff --git a/test/integration/helpers.js b/test/integration/helpers.js
deleted file mode 100644
index eede103b4..000000000
--- a/test/integration/helpers.js
+++ /dev/null
@@ -1,7 +0,0 @@
-function wait(time) {
- return new Promise(function(resolve, reject) {
- setTimeout(function() {
- resolve()
- }, time * 3 || 1500)
- })
-}
diff --git a/test/integration/index.js b/test/integration/index.js
index ff6d1baf8..144303dbb 100644
--- a/test/integration/index.js
+++ b/test/integration/index.js
@@ -1,21 +1,26 @@
-var fs = require('fs')
-var path = require('path')
-var browserify = require('browserify');
-var tests = fs.readdirSync(path.join(__dirname, 'lib'))
-var bundlePath = path.join(__dirname, 'bundle.js')
+const fs = require('fs')
+const path = require('path')
+const pump = require('pump')
+const browserify = require('browserify')
+const tests = fs.readdirSync(path.join(__dirname, 'lib'))
+const bundlePath = path.join(__dirname, 'bundle.js')
-var b = browserify();
+const b = browserify()
-// Remove old bundle
-try {
- fs.unlinkSync(bundlePath)
-} catch (e) {}
+const writeStream = fs.createWriteStream(bundlePath)
-var writeStream = fs.createWriteStream(bundlePath)
-
-tests.forEach(function(fileName) {
- b.add(path.join(__dirname, 'lib', fileName))
+tests.forEach(function (fileName) {
+ const filePath = path.join(__dirname, 'lib', fileName)
+ console.log(`bundling test "${filePath}"`)
+ b.add(filePath)
})
-b.bundle().pipe(writeStream);
-
+pump(
+ b.bundle(),
+ writeStream,
+ (err) => {
+ if (err) throw err
+ console.log(`Integration test build completed: "${bundlePath}"`)
+ process.exit(0)
+ }
+) \ No newline at end of file
diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js
new file mode 100644
index 000000000..dd4251cc4
--- /dev/null
+++ b/test/integration/lib/add-token.js
@@ -0,0 +1,153 @@
+const reactTriggerChange = require('react-trigger-change')
+
+QUnit.module('Add token flow')
+
+QUnit.test('successful add token flow', (assert) => {
+ const done = assert.async()
+ runAddTokenFlowTest(assert)
+ .then(done)
+ .catch(err => {
+ assert.notOk(err, `Error was thrown: ${err.stack}`)
+ done()
+ })
+})
+
+async function runAddTokenFlowTest (assert, done) {
+ const selectState = $('select')
+ selectState.val('add token')
+ reactTriggerChange(selectState[0])
+
+ await timeout(2000)
+
+ // Check that no tokens have been added
+ assert.ok($('.token-list-item').length === 0, 'no tokens added')
+
+ // Go to Add Token screen
+ let addTokenButton = $('button.btn-clear.wallet-view__add-token-button')
+ assert.ok(addTokenButton[0], 'add token button present')
+ addTokenButton[0].click()
+
+ await timeout(1000)
+
+ // Verify Add Token screen
+ let addTokenWrapper = $('.add-token__wrapper')
+ assert.ok(addTokenWrapper[0], 'add token wrapper renders')
+
+ let addTokenTitle = $('.add-token__title')
+ assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct')
+
+ // Cancel Add Token
+ const cancelAddTokenButton = $('button.btn-cancel.add-token__button')
+ assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
+ cancelAddTokenButton.click()
+
+ await timeout(1000)
+
+ assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view')
+
+ // Return to Add Token Screen
+ addTokenButton = $('button.btn-clear.wallet-view__add-token-button')
+ assert.ok(addTokenButton[0], 'add token button present')
+ addTokenButton[0].click()
+
+ await timeout(1000)
+
+ // Verify Add Token Screen
+ addTokenWrapper = $('.add-token__wrapper')
+ addTokenTitle = $('.add-token__title')
+ assert.ok(addTokenWrapper[0], 'add token wrapper renders')
+ assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct')
+
+ // Search for token
+ const searchInput = $('input.add-token__input')
+ searchInput.val('a')
+ reactTriggerChange(searchInput[0])
+
+ await timeout()
+
+ // Click token to add
+ const tokenWrapper = $('div.add-token__token-wrapper')
+ assert.ok(tokenWrapper[0], 'token found')
+ const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image')
+ const tokenImageUrl = tokenImageProp.slice(5, -2)
+ tokenWrapper[0].click()
+
+ await timeout()
+
+ // Click Next button
+ let nextButton = $('button.btn-clear.add-token__button')
+ assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
+ nextButton[0].click()
+
+ await timeout()
+
+ // Confirm Add token
+ assert.equal(
+ $('.add-token__description')[0].textContent,
+ 'Would you like to add these tokens?',
+ 'confirm add token rendered'
+ )
+ assert.ok($('button.btn-clear.add-token__button')[0], 'confirm add token button found')
+ $('button.btn-clear.add-token__button')[0].click()
+
+ await timeout(2000)
+
+ // Verify added token image
+ let heroBalance = $('.hero-balance')
+ assert.ok(heroBalance, 'rendered hero balance')
+ assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
+
+ // Return to Add Token Screen
+ addTokenButton = $('button.btn-clear.wallet-view__add-token-button')
+ assert.ok(addTokenButton[0], 'add token button present')
+ addTokenButton[0].click()
+
+ await timeout(1000)
+
+ const addCustom = $('.add-token__add-custom')
+ assert.ok(addCustom[0], 'add custom token button present')
+ addCustom[0].click()
+
+ await timeout()
+
+ // Input token contract address
+ const customInput = $('input.add-token__add-custom-input')
+ customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
+ reactTriggerChange(customInput[0])
+
+ await timeout(1000)
+
+ // Click Next button
+ nextButton = $('button.btn-clear.add-token__button')
+ assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
+ nextButton[0].click()
+
+ await timeout(1000)
+
+ // Verify symbol length error since contract address won't return symbol
+ const errorMessage = $('.add-token__add-custom-error-message')
+ assert.ok(errorMessage[0], 'error rendered')
+ $('button.btn-cancel.add-token__button')[0].click()
+
+ await timeout(2000)
+
+ // // Confirm Add token
+ // assert.equal(
+ // $('.add-token__description')[0].textContent,
+ // 'Would you like to add these tokens?',
+ // 'confirm add token rendered'
+ // )
+ // assert.ok($('button.btn-clear.add-token__button')[0], 'confirm add token button found')
+ // $('button.btn-clear.add-token__button')[0].click()
+
+ // // Verify added token image
+ // heroBalance = $('.hero-balance')
+ // assert.ok(heroBalance, 'rendered hero balance')
+ // assert.ok(heroBalance.find('.identicon')[0], 'token added')
+}
+
+function timeout (time) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, time || 1500)
+ })
+}
diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js
new file mode 100644
index 000000000..e49424c37
--- /dev/null
+++ b/test/integration/lib/confirm-sig-requests.js
@@ -0,0 +1,67 @@
+const reactTriggerChange = require('react-trigger-change')
+
+const PASSWORD = 'password123'
+
+QUnit.module('confirm sig requests')
+
+QUnit.test('successful confirmation of sig requests', (assert) => {
+ const done = assert.async()
+ runConfirmSigRequestsTest(assert).then(done).catch((err) => {
+ assert.notOk(err, `Error was thrown: ${err.stack}`)
+ done()
+ })
+})
+
+async function runConfirmSigRequestsTest(assert, done) {
+ let selectState = $('select')
+ selectState.val('confirm sig requests')
+ reactTriggerChange(selectState[0])
+
+ await timeout(2000)
+
+ let confirmSigHeadline = $('.request-signature__headline')
+ assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
+
+ let confirmSigRowValue = $('.request-signature__row-value')
+ assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/))
+
+ let confirmSigSignButton = $('.request-signature__footer__sign-button')
+ confirmSigSignButton[0].click()
+
+ await timeout(2000)
+
+ confirmSigHeadline = $('.request-signature__headline')
+ assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
+
+ let confirmSigMessage = $('.request-signature__notice')
+ assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/))
+
+ confirmSigRowValue = $('.request-signature__row-value')
+ assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
+
+ confirmSigSignButton = $('.request-signature__footer__sign-button')
+ confirmSigSignButton[0].click()
+
+ await timeout(2000)
+
+ confirmSigHeadline = $('.request-signature__headline')
+ assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
+
+ confirmSigRowValue = $('.request-signature__row-value')
+ assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!')
+ assert.equal(confirmSigRowValue[1].textContent, '1337')
+
+ confirmSigSignButton = $('.request-signature__footer__sign-button')
+ confirmSigSignButton[0].click()
+
+ await timeout(2000)
+
+ const txView = $('.tx-view')
+ assert.ok(txView[0], 'Should return to the account details screen after confirming')
+}
+
+function timeout (time) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, time || 1500)
+ })
+} \ No newline at end of file
diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js
index dbb88a3da..764eae47c 100644
--- a/test/integration/lib/first-time.js
+++ b/test/integration/lib/first-time.js
@@ -1,120 +1,138 @@
+const reactTriggerChange = require('react-trigger-change')
const PASSWORD = 'password123'
+const runMascaraFirstTimeTest = require('./mascara-first-time')
QUnit.module('first time usage')
-QUnit.test('render init screen', function (assert) {
- var done = assert.async()
- let app
-
- wait().then(function() {
- app = $('iframe').contents().find('#app-content .mock-app-root')
-
- const recurseNotices = function () {
- let button = app.find('button')
- if (button.html() === 'Continue') {
- let termsPage = app.find('.markdown')[0]
- termsPage.scrollTop = termsPage.scrollHeight
- return wait().then(() => {
- button.click()
- return wait()
- }).then(() => {
- return recurseNotices()
- })
- } else {
- return wait()
- }
+QUnit.test('render init screen', (assert) => {
+ const done = assert.async()
+ runFirstTimeUsageTest(assert).then(done).catch((err) => {
+ assert.notOk(err, `Error was thrown: ${err.stack}`)
+ done()
+ })
+})
+
+async function runFirstTimeUsageTest(assert, done) {
+ if (window.METAMASK_PLATFORM_TYPE === 'mascara') {
+ return runMascaraFirstTimeTest(assert, done)
+ }
+
+ const selectState = $('select')
+ selectState.val('first time')
+ reactTriggerChange(selectState[0])
+
+ await timeout(2000)
+ const app = $('#app-content')
+
+ // recurse notices
+ while (true) {
+ const button = app.find('button')
+ if (button.html() === 'Accept') {
+ // still notices to accept
+ const termsPage = app.find('.markdown')[0]
+ termsPage.scrollTop = termsPage.scrollHeight
+ await timeout()
+ console.log('Clearing notice')
+ button.click()
+ await timeout()
+ } else {
+ // exit loop
+ console.log('No more notices...')
+ break
}
- return recurseNotices()
- }).then(function() {
- // Scroll through terms
- var title = app.find('h1').text()
- assert.equal(title, 'MetaMask', 'title screen')
+ }
- // enter password
- var pwBox = app.find('#password-box')[0]
- var confBox = app.find('#password-box-confirm')[0]
- pwBox.value = PASSWORD
- confBox.value = PASSWORD
+ await timeout()
- return wait()
- }).then(function() {
+ // Scroll through terms
+ const title = app.find('h1')[0]
+ assert.equal(title.textContent, 'MetaMask', 'title screen')
- // create vault
- var createButton = app.find('button.primary')[0]
- createButton.click()
+ // enter password
+ const pwBox = app.find('#password-box')[0]
+ const confBox = app.find('#password-box-confirm')[0]
+ pwBox.value = PASSWORD
+ confBox.value = PASSWORD
- return wait(1500)
- }).then(function() {
+ await timeout()
- var created = app.find('h3')[0]
- assert.equal(created.textContent, 'Vault Created', 'Vault created screen')
+ // create vault
+ const createButton = app.find('button.primary')[0]
+ createButton.click()
- // Agree button
- var button = app.find('button')[0]
- assert.ok(button, 'button present')
- button.click()
+ await timeout(3000)
- return wait(1000)
- }).then(function() {
+ const created = app.find('h3')[0]
+ assert.equal(created.textContent, 'Vault Created', 'Vault created screen')
- var detail = app.find('.account-detail-section')[0]
- assert.ok(detail, 'Account detail section loaded.')
+ // Agree button
+ const button = app.find('button')[0]
+ assert.ok(button, 'button present')
+ button.click()
- var sandwich = app.find('.sandwich-expando')[0]
- sandwich.click()
+ await timeout(1000)
- return wait()
- }).then(function() {
+ const detail = app.find('.account-detail-section')[0]
+ assert.ok(detail, 'Account detail section loaded.')
- var sandwich = app.find('.menu-droppo')[0]
- var children = sandwich.children
- var lock = children[children.length - 2]
- assert.ok(lock, 'Lock menu item found')
- lock.click()
+ const sandwich = app.find('.sandwich-expando')[0]
+ sandwich.click()
- return wait(1000)
- }).then(function() {
+ await timeout()
- var pwBox = app.find('#password-box')[0]
- pwBox.value = PASSWORD
+ const menu = app.find('.menu-droppo')[0]
+ const children = menu.children
+ const logout = children[2]
+ assert.ok(logout, 'Lock menu item found')
+ logout.click()
- var createButton = app.find('button.primary')[0]
- createButton.click()
+ await timeout(1000)
- return wait(1000)
- }).then(function() {
+ const pwBox2 = app.find('#password-box')[0]
+ pwBox2.value = PASSWORD
- var detail = app.find('.account-detail-section')[0]
- assert.ok(detail, 'Account detail section loaded again.')
+ const createButton2 = app.find('button.primary')[0]
+ createButton2.click()
- return wait()
- }).then(function (){
+ await timeout(1000)
- var qrButton = app.find('.fa.fa-qrcode')[0]
- qrButton.click()
+ const detail2 = app.find('.account-detail-section')[0]
+ assert.ok(detail2, 'Account detail section loaded again.')
- return wait(1000)
- }).then(function (){
+ await timeout()
- var qrHeader = app.find('.qr-header')[0]
- var qrContainer = app.find('#qr-container')[0]
- assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
- assert.ok(qrContainer, 'QR Container found')
+ // open account settings dropdown
+ const qrButton = app.find('.fa.fa-ellipsis-h')[0]
+ qrButton.click()
- return wait()
- }).then(function (){
+ await timeout(1000)
- var networkMenu = app.find('.network-indicator')[0]
- networkMenu.click()
+ // qr code item
+ const qrButton2 = app.find('.dropdown-menu-item')[1]
+ qrButton2.click()
- return wait()
- }).then(function (){
+ await timeout(1000)
- var networkMenu = app.find('.network-indicator')[0]
- var children = networkMenu.children
- children.length[3]
- assert.ok(children, 'All network options present')
+ const qrHeader = app.find('.qr-header')[0]
+ const qrContainer = app.find('#qr-container')[0]
+ assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
+ assert.ok(qrContainer, 'QR Container found')
- done()
+ await timeout()
+
+ const networkMenu = app.find('.network-indicator')[0]
+ networkMenu.click()
+
+ await timeout()
+
+ const networkMenu2 = app.find('.network-indicator')[0]
+ const children2 = networkMenu2.children
+ children2.length[3]
+ assert.ok(children2, 'All network options present')
+}
+
+function timeout (time) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, time || 1500)
})
-})
+} \ No newline at end of file
diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js
new file mode 100644
index 000000000..515c7f383
--- /dev/null
+++ b/test/integration/lib/mascara-first-time.js
@@ -0,0 +1,148 @@
+const PASSWORD = 'password123'
+const reactTriggerChange = require('react-trigger-change')
+
+async function runFirstTimeUsageTest (assert, done) {
+ await timeout(4000)
+
+ const app = $('#app-content')
+
+ await skipNotices(app)
+
+ await timeout()
+
+ // Scroll through terms
+ const title = app.find('.create-password__title').text()
+ assert.equal(title, 'Create Password', 'create password screen')
+
+ // enter password
+ const pwBox = app.find('.first-time-flow__input')[0]
+ const confBox = app.find('.first-time-flow__input')[1]
+ pwBox.value = PASSWORD
+ confBox.value = PASSWORD
+ reactTriggerChange(pwBox)
+ reactTriggerChange(confBox)
+
+
+ await timeout()
+
+ // Create Password
+ const createButton = app.find('button.first-time-flow__button')[0]
+ createButton.click()
+
+ await timeout(3000)
+
+ const created = app.find('.unique-image__title')[0]
+ assert.equal(created.textContent, 'Your unique account image', 'unique image screen')
+
+ // Agree button
+ let button = app.find('button')[0]
+ assert.ok(button, 'button present')
+ button.click()
+
+ await timeout(1000)
+
+ await skipNotices(app)
+
+ // secret backup phrase
+ const seedTitle = app.find('.backup-phrase__title')[0]
+ assert.equal(seedTitle.textContent, 'Secret Backup Phrase', 'seed phrase screen')
+ app.find('.backup-phrase__reveal-button').click()
+
+ await timeout(1000)
+ const seedPhrase = app.find('.backup-phrase__secret-words').text().split(' ')
+ app.find('.first-time-flow__button').click()
+
+ const selectPhrase = text => {
+ const option = $('.backup-phrase__confirm-seed-option')
+ .filter((i, d) => d.textContent === text)[0]
+
+ $(option).click()
+ }
+
+ await timeout(1000)
+
+ seedPhrase.forEach(sp => selectPhrase(sp))
+ app.find('.first-time-flow__button').click()
+ await timeout(1000)
+
+ // Deposit Ether Screen
+ const buyEthTitle = app.find('.buy-ether__title')[0]
+ assert.equal(buyEthTitle.textContent, 'Deposit Ether', 'deposit ether screen')
+ app.find('.buy-ether__do-it-later').click()
+ await timeout(1000)
+
+ const menu = app.find('.account-menu__icon')[0]
+ menu.click()
+
+ await timeout()
+
+ const lock = app.find('.account-menu__logout-button')[0]
+ assert.ok(lock, 'Lock menu item found')
+ lock.click()
+
+ await timeout(1000)
+
+ const pwBox2 = app.find('#password-box')[0]
+ pwBox2.value = PASSWORD
+
+ const createButton2 = app.find('button.primary')[0]
+ createButton2.click()
+
+ await timeout(1000)
+
+ const detail2 = app.find('.wallet-view')[0]
+ assert.ok(detail2, 'Account detail section loaded again.')
+
+ await timeout()
+
+ // open account settings dropdown
+ const qrButton = app.find('.wallet-view__details-button')[0]
+ qrButton.click()
+
+ await timeout(1000)
+
+ const qrHeader = app.find('.editable-label__value')[0]
+ const qrContainer = app.find('.qr-wrapper')[0]
+ assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
+ assert.ok(qrContainer, 'QR Container found')
+
+ await timeout()
+
+ const networkMenu = app.find('.network-component')[0]
+ networkMenu.click()
+
+ await timeout()
+
+ const networkMenu2 = app.find('.network-indicator')[0]
+ const children2 = networkMenu2.children
+ children2.length[3]
+ assert.ok(children2, 'All network options present')
+}
+
+module.exports = runFirstTimeUsageTest
+
+function timeout (time) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, time || 1500)
+ })
+}
+
+async function skipNotices (app) {
+ while (true) {
+ const button = app.find('button')
+ if (button && button.html() === 'Accept') {
+ // still notices to accept
+ const termsPage = app.find('.markdown')[0]
+ if (!termsPage) {
+ break
+ }
+ termsPage.scrollTop = termsPage.scrollHeight
+ await timeout()
+ button.click()
+ await timeout()
+ } else {
+ console.log('No more notices...')
+ break
+ }
+ }
+}
diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js
new file mode 100644
index 000000000..3456f2367
--- /dev/null
+++ b/test/integration/lib/send-new-ui.js
@@ -0,0 +1,225 @@
+const reactTriggerChange = require('react-trigger-change')
+
+const PASSWORD = 'password123'
+
+QUnit.module('new ui send flow')
+
+QUnit.test('successful send flow', (assert) => {
+ const done = assert.async()
+ runSendFlowTest(assert).then(done).catch((err) => {
+ assert.notOk(err, `Error was thrown: ${err.stack}`)
+ done()
+ })
+})
+
+global.ethQuery = {
+ sendTransaction: () => {},
+}
+
+async function runSendFlowTest(assert, done) {
+ console.log('*** start runSendFlowTest')
+ const selectState = $('select')
+ selectState.val('send new ui')
+ reactTriggerChange(selectState[0])
+
+ await timeout(2000)
+
+ const sendScreenButton = $('button.btn-clear.hero-balance-button')
+ assert.ok(sendScreenButton[1], 'send screen button present')
+ sendScreenButton[1].click()
+
+ await timeout(1000)
+
+ const sendTitle = $('.page-container__title')
+ assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct')
+
+ const sendCopy = $('.page-container__subtitle')
+ assert.equal(sendCopy[0].textContent, 'Only send ETH to an Ethereum address.', 'Send screen has copy')
+
+ const sendFromField = $('.send-v2__form-field')
+ assert.ok(sendFromField[0], 'send screen has a from field')
+
+ let sendFromFieldItemAddress = $('.account-list-item__account-name')
+ assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 4', 'send from field shows correct account name')
+
+ const sendFromFieldItem = $('.account-list-item')
+ sendFromFieldItem[0].click()
+
+ await timeout()
+
+ const sendFromDropdownList = $('.send-v2__from-dropdown__list')
+ assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts')
+ console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromDropdownList.children()[1]`, sendFromDropdownList.children()[1]);
+ sendFromDropdownList.children()[1].click()
+
+ await timeout()
+
+ sendFromFieldItemAddress = $('.account-list-item__account-name')
+ console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromFieldItemAddress[0]`, sendFromFieldItemAddress[0]);
+ assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 2', 'send from field dropdown changes account name')
+
+ let sendToFieldInput = $('.send-v2__to-autocomplete__input')
+ sendToFieldInput[0].focus()
+
+ await timeout()
+
+ const sendToDropdownList = $('.send-v2__from-dropdown__list')
+ assert.equal(sendToDropdownList.children().length, 5, 'send to dropdown shows all accounts and address book accounts')
+
+ sendToDropdownList.children()[2].click()
+
+ await timeout()
+
+ const sendToAccountAddress = sendToFieldInput.val()
+ assert.equal(sendToAccountAddress, '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', 'send to dropdown selects the correct address')
+
+ const sendAmountField = $('.send-v2__form-row:eq(2)')
+ sendAmountField.find('.currency-display')[0].click()
+
+ await timeout()
+
+ const sendAmountFieldInput = sendAmountField.find('input:text')
+ sendAmountFieldInput.val('5.1')
+ reactTriggerChange(sendAmountField.find('input')[0])
+
+ await timeout()
+
+ let errorMessage = $('.send-v2__error')
+ assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message')
+
+ sendAmountFieldInput.val('2.0')
+ reactTriggerChange(sendAmountFieldInput[0])
+
+ await timeout()
+ errorMessage = $('.send-v2__error')
+ assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
+
+ const sendGasField = $('.send-v2__gas-fee-display')
+ assert.equal(
+ sendGasField.find('.currency-display__input-wrapper > input').val(),
+ '0.000198',
+ 'send gas field should show estimated gas total'
+ )
+ assert.equal(
+ sendGasField.find('.currency-display__converted-value')[0].textContent,
+ '0.24 USD',
+ 'send gas field should show estimated gas total converted to USD'
+ )
+
+ const sendGasOpenCustomizeModalButton = $('.send-v2__sliders-icon-container'
+ )
+ sendGasOpenCustomizeModalButton[0].click()
+
+ await timeout(1000)
+
+ const customizeGasModal = $('.send-v2__customize-gas')
+ assert.ok(customizeGasModal[0], 'should render the customize gas modal')
+
+ const customizeGasPriceInput = $('.send-v2__gas-modal-card').first().find('input')
+ customizeGasPriceInput.val(50)
+ reactTriggerChange(customizeGasPriceInput[0])
+ const customizeGasLimitInput = $('.send-v2__gas-modal-card').last().find('input')
+ customizeGasLimitInput.val(60000)
+ reactTriggerChange(customizeGasLimitInput[0])
+
+ await timeout()
+
+ const customizeGasSaveButton = $('.send-v2__customize-gas__save')
+ customizeGasSaveButton[0].click()
+
+ await timeout()
+
+ assert.equal(
+ sendGasField.find('.currency-display__input-wrapper > input').val(),
+ '0.003',
+ 'send gas field should show customized gas total'
+ )
+ assert.equal(
+ sendGasField.find('.currency-display__converted-value')[0].textContent,
+ '3.60 USD',
+ 'send gas field should show customized gas total converted to USD'
+ )
+
+ const sendButton = $('button.btn-clear.page-container__footer-button')
+ assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
+ sendButton[0].click()
+
+ await timeout(2000)
+
+ selectState.val('send edit')
+ reactTriggerChange(selectState[0])
+
+ await timeout(2000)
+
+ const confirmFromName = $('.confirm-screen-account-name').first()
+ assert.equal(confirmFromName[0].textContent, 'Send Account 2', 'confirm screen should show correct from name')
+
+ const confirmToName = $('.confirm-screen-account-name').last()
+ assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
+
+ const confirmScreenRows = $('.confirm-screen-rows')
+ const confirmScreenGas = confirmScreenRows.find('.confirm-screen-row-info')[2]
+ assert.equal(confirmScreenGas.textContent, '3.6 USD', 'confirm screen should show correct gas')
+ const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[3]
+ assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total')
+
+ const confirmScreenBackButton = $('.confirm-screen-back-button')
+ confirmScreenBackButton[0].click()
+
+ await timeout(1000)
+
+ const sendFromFieldItemInEdit = $('.account-list-item')
+ sendFromFieldItemInEdit[0].click()
+
+ await timeout()
+
+ const sendFromDropdownListInEdit = $('.send-v2__from-dropdown__list')
+ sendFromDropdownListInEdit.children()[2].click()
+
+ await timeout()
+
+ const sendToFieldInputInEdit = $('.send-v2__to-autocomplete__input')
+ sendToFieldInputInEdit[0].focus()
+ sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
+
+ await timeout()
+
+ const sendAmountFieldInEdit = $('.send-v2__form-row:eq(2)')
+ sendAmountFieldInEdit.find('.currency-display')[0].click()
+
+ await timeout()
+
+ const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('input:text')
+ sendAmountFieldInputInEdit.val('1.0')
+ reactTriggerChange(sendAmountFieldInputInEdit[0])
+
+ await timeout()
+
+ const sendButtonInEdit = $('.btn-clear.page-container__footer-button')
+ assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered')
+ sendButtonInEdit[0].click()
+
+ await timeout()
+
+ // TODO: Need a way to mock background so that we can test correct transition from editing to confirm
+ selectState.val('confirm new ui')
+ reactTriggerChange(selectState[0])
+
+ await timeout(2000)
+ const confirmScreenConfirmButton = $('.confirm-screen-confirm-button')
+ console.log(`+++++++++++++++++++++++++++++++= confirmScreenConfirmButton[0]`, confirmScreenConfirmButton[0]);
+ confirmScreenConfirmButton[0].click()
+
+ await timeout(2000)
+
+ const txView = $('.tx-view')
+ console.log(`++++++++++++++++++++++++++++++++ txView[0]`, txView[0]);
+
+ assert.ok(txView[0], 'Should return to the account details screen after confirming')
+}
+
+function timeout (time) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, time || 1500)
+ })
+} \ No newline at end of file
diff --git a/test/lib/migrations/001.json b/test/lib/migrations/001.json
index 2fe6dd836..7bd55a50e 100644
--- a/test/lib/migrations/001.json
+++ b/test/lib/migrations/001.json
@@ -1 +1,14 @@
-{"version":0,"data":{"wallet":"{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}","config":{"provider":{"type":"etherscan"}}},"meta":{"version":0}} \ No newline at end of file
+{
+ "version": 0,
+ "data": {
+ "wallet": "{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}",
+ "config": {
+ "provider": {
+ "type": "etherscan"
+ }
+ }
+ },
+ "meta": {
+ "version": 0
+ }
+} \ No newline at end of file
diff --git a/test/lib/migrations/002.json b/test/lib/migrations/002.json
new file mode 100644
index 000000000..9ad3d4cfe
--- /dev/null
+++ b/test/lib/migrations/002.json
@@ -0,0 +1 @@
+{"meta":{"version":20},"data":{"config":{},"NetworkController":{"provider":{"type":"mainnet","rpcTarget":"https://mainnet.infura.io/metamask"},"network":"1"},"firstTimeInfo":{"version":"3.12.1","date":1517351427287},"NoticeController":{"noticesList":[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0},{"read":false,"date":"Mon May 08 2017","title":"Privacy Notice","body":"MetaMask is beta software. \n\nWhen you log in to MetaMask, your current account is visible to every new site you visit.\n\nFor your privacy, for now, please sign out of MetaMask when you're done using a site.\n\nAlso, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu.\n\n","id":2}]},"BlacklistController":{"phishing":{"version":2,"tolerance":2,"fuzzylist":["metamask.io","myetherwallet.com","cryptokitties.co"],"whitelist":["metahash.io","metahash.net","metahash.org","cryptotitties.com","cryptocities.net","cryptoshitties.co","cryptotitties.fun","cryptokitties.forsale","cryptokitties.care","metamate.cc","metamesh.tech","ico.nexus.social","metamesh.org","metatask.io","metmask.com","metarasa.com","metapack.com","metacase.com","metafas.nl","metamako.com","metamast.com","metamax.ru","metadesk.io","metadisk.com","metallsk.ru","metamag.fr","metamaks.ru","metamap.ru","metamaps.cc","metamats.com","metamax.by","metamax.com","metamax.io","metamuse.net","metarank.com","metaxas.com","megamas2.ru","metamask.io","myetherwallet.com","myethlerwallet.com","ethereum.org","myetheroll.com","myetherapi.com","ledgerwallet.com","databrokerdao.com","etherscan.io","etherid.org","ether.cards","etheroll.com","ethnews.com","ethex.market","ethereumdev.io","ethereumdev.kr","dether.io","ethermine.org","slaask.com","etherbtc.io","ethereal.capital","etherisc.com","m.famalk.net","etherecho.com","ethereum.os.tc","theethereum.wiki","metajack.im","etherhub.io","ethereum.network","ethereum.link","ethereum.com","prethereum.org","ethereumj.io","etheraus.com","ethereum.dev","1ethereum.ru","ethereum.nz","nethereum.com","metabank.com","metamas.com","aventus.io","metabase.com","etherdelta.com","metabase.one","cryptokitties.co"],"blacklist":["myetherwallet.uk.com","kodakone.cc","nyeihitervvallet.com","xn--myeterwalet-cm8eoi.com","nucleus.foundation","beetoken-ico.com","data-token.com","tron-labs.com","ocoin.tech","aionfoundation.com","ico-telegram.org","nyeihitervvallat.com","telegramcoin.us","daddi.cloud","daditoken.com","blockarray.org","dadi-cloud.net","wanchainfunding.org","ico-telegram.io","iconfoundation.site","iost.co","beetoken-ico.eu","cindicator.network","wanchainetwork.org","wamchain.org","wanchainltd.org","wanchainalliance.org","nucleus-vision.net","ledgerwallet.by","nucleuss.vision","myenhterswailct.com","cobin-hood.com","wanchainfoundation.org","xn--polniex-ex4c.com","xn--polniex-s1a.com","xn--polonex-ieb.com","xn--polonex-sza.com","xn--polonex-zw4c.com","xn--polonix-ws4c.com","xn--polonix-y8a.com","xn--pooniex-ojb.com","gramico.info","dimnsions.network","www-gemini.com","login-kucoin.net","venchain.foundation","grampreico.com","tgram.cc","ton-gramico.com","wwwpaywithink.com","coniomi.com","paywithnk.com","paywithlnk.com","iluminatto.com.br","pundix.eu","xn--bttrx-esay.com","xn--bttrex-w8a.com","xn--bnance-bwa.com","xn--shpeshift-11a.com","xn--shapeshif-ts6d.com","xn--shapshift-yf7d.com","wwwbluzelle.com","bluzelie.com","nucleus-vision.org","omisegonetwork.site","etlherzero.com","etlherdelta.com","xn--condesk-0ya.com","xn--condesk-sfb.com","xn--coindsk-vs4c.com","iexecplatform.com","tongramico.com","nucleus-vision.eu","intchain.network","wanchain.cloud","bluzelle-ico.com","ethzero-wallet.com","xn--metherwalle-jb9et7d.com","xn--coinesk-jo3c.com","venchainfoundation.com","myenhtersvvailot.com","ether-zero.net","ins.foundation","nastoken.org","telcointoken.com","ether0.org","eterzero.org","bluzelle-ico.eu","bleuzelle.com","appcoinstoken.org","xn--quanstamp-8s6d.com","myehntersvvailct.com","myeherwalllet.com","ico-bluzelle.com","bluzelle.im","bluzelle.one","bluzele.sale","bluzele.co","sether.ws","xn--myetherwalet-6gf.com","xn--rnyethewaliet-om1g.com","rnyethervailet.com","mvetherwaliet.com","rnyetherwailet.com","myethervaliet.com","rnyethervaliet.com","mvetherwalilet.com","xn--myethewalie-3ic0947g.com","xn--mthrwallet-z6ac3y.com","xn--myeherwalie-vici.com","xn--myethervvalie-8vc.com","xn--mythrwallt-06acf.com","xn--mtherwallet-y9a6y.com","myetherwallet.applytoken.tk","ethereum-zero.com","quanstamptoken.tk","bluzelle.network","ether-wallet.org","tron-wallet.info","appcoinsproject.com","vechain.foundation","tronlab.site","tronlabs.network","bluzelle.cc","ethblender.com","ethpaperwallet.net","waltontoken.org","icoselfkey.org","etherzeroclaim.com","etherzero.promo","bluzelle.pro","token-selfkey.org","xn--etherdlta-0f7d.com","sether.in","xn--ttrex-ysa9423c.com","bluzelle.eu","bluzelle.site","gifto.tech","xn--os-g7s.com","selfkey.co","xn--myeherwalet-ns8exy.com","xn--coinelegraph-wk5f.com","dai-stablecoin.com","eos-token.org","venchain.org","gatcoins.io","deepbrainchain.co","myetherwalililet.info","myehvterwallet.com","myehterumswallet.com","nucleusico.com","tronlab.tech","0x-project.com","gift-token-events.mywebcommunity.org","funfairtoken.org","breadtokenapp.com","cloudpetstore.com","myethwalilet.com","selfkeys.org","wallet-ethereum.com","xn--methrwallt-26ar0z.com","xn--mytherwllet-r8a0c.com","bluzelle.promo","tokensale.bluzelle.promo","cedarlake.org","marketingleads4u.com","cashaa.co","xn--inance-hrb.com","wanchain.tech","zenprolocol.com","ethscan.io","etherscan.in","props-project.com","zilliaq.com","reqestnetwork.com","etherdelta.pw","ethereum-giveaway.org","mysimpletoken.org","binancc.com","blnance.org","elherdelta.io","xn--hapeshit-ez9c2y.com","tenxwallet.co","singularitynet.info","mytlherwaliet.info","iconmainnet.ml","tokenselfkey.org","xn--myetewallet-cm8e5y.com","envione.org","myetherwalletet.com","claimbcd.com","ripiocreditnetwork.in","xn--yeterwallet-ml8euo.com","ethclassicwallet.info","myltherwallet.ru.com","etherdella.com","xn--yeterwallet-bm8ewn.com","singularty.net","cloudkitties.co","iconfoundation.io","kittystat.com","gatscoin.io","singularitynet.in","sale.canay.io","canay.io","wabicoin.co","envion.top","sirinslabs.com","tronlab.co","paxful.com.ng","changellyli.com","ethereum-code.com","xn--plonex-6va6c.com","envion.co","envion.cc","envion.site","ethereumchain.info","xn--envon-1sa.org","xn--btstamp-rfb.net","envlon.org","envion-ico.org","spectivvr.org","sirinlbs.com","ethereumdoubler.life","xn--myetherwllet-fnb.com","sirin-labs.com","sirin-labs.org","envion.one","envion.live","propsproject.org","propsprojects.com","decentralland.org","xn--metherwalet-ns8ep4b.com","redpulsetoken.co","propsproject.tech","xn--myeterwalet-nl8emj.com","powrerledger.com","cryptokitties.com","sirinlabs.pro","sirinlabs.co","sirnlabs.com","superbitcoin-blockchain.info","hellobloom.me","mobus.network","powrrledger.com","xn--myeherwalet-ms8eyy.com","qlink-ico.com","gatcoin.in","tokensale.gamefllp.com","gamefllp.com","xn--myeherwalle-vici.com","xn--myetherwalet-39b.com","xn--polonex-ffb.com","xn--birex-leba.com","raiden-network.org","sirintabs.com","xn--metherwallt-79a30a.com","xn--myethrwllet-2kb3p.com","myethlerwallet.eu","xn--btrex-b4a.com","powerrledger.com","xn--cointeegraph-wz4f.com","myerherwalet.com","qauntstanp.com","myetherermwallet.com","xn--myethewalet-ns8eqq.com","xn--nvion-hza.org","nnyetherwallelt.ru.com","ico-wacoin.com","xn--myeterwalet-nl8enj.com","bitcoinsilver.io","t0zero.com","tokensale.gizer.in","gizer.in","wabitoken.com","gladius.ws","xn--metherwallt-8bb4w.com","quanttstamp.com","gladius.im","ethereumstorage.net","powerledgerr.com","xn--myeherwallet-4j5f.com","quamtstamp.com","quntstamp.com","xn--changely-j59c.com","shapeshlft.com","coinbasenews.co.uk","xn--metherwallet-hmb.com","envoin.org","powerledger.com","bitstannp.net","xn--myetherallet-4k5fwn.com","xn--coinbas-pya.com","requestt.network","oracls.network","sirinlabs.website","powrledger.io","slackconfirm.com","shape-shift.io","oracles-network.org","xn--myeherwalle-zb9eia.com","blockstack.one","urtust.io","bittrex.one","t0-ico.com","xn--cinbase-90a.com","xn--metherwalet-ns8ez1g.com","tzero-ico.com","tzero.su","tzero.website","blockstack.network","ico-tzero.com","spectre.site","tzero.pw","spectre-ai.net","xn--waxtokn-y8a.com","dmarket.pro","bittrex.com11648724328774.cf","bittrex.com1987465798.ga","autcus.org","t-zero.org","xn--zero-zxb.com","myetherwalletfork.com","blokclbain.info","datum.sale","spectre-ai.org","powerledgr.com","simpletoken.live","sale.simpletoken.live","qauntstamp.com","raiden-network.com","metalpayme.com","quantstamp-ico.com","myetherwailetclient.com","biockchain.biz","wallets-blockchain.com","golemairdrop.com","omisegoairdrop.net","blodkchainwallet.info","walton-chain.org","elite888-ico.com","bitflyerjp.com","chainlinksmartcontract.com","stormtoken.eu","omise-go.tech","saltending.com","stormltoken.com","xn--quanttamp-42b.com","stormtoken.co","storntoken.com","stromtoken.com","storm-token.com","stormtokens.io","ether-delta.com","ethconnect.live","ethconnect.trade","xn--bttrex-3va.net","quantstamp.com.co","wancha.in","augur-network.com","quantstamp.com.ua","myetherwalletmew.com","myetherumwalletts.com","xn--quanstamp-tmd.com","quantsstamps.com","changellyl.net","xn--myetherwalet-1fb.com","myethereumwallets.com","xn--myetherwalet-e9b.com","quantslamp.com","metelpay.com","xn--eterdelta-m75d.com","linksmartcontract.com","myetherwalletaccess.com","myetherwalletcheck.com","myetherwalletcheck.info","myetherwalletconf.com","myetherwalleteal.com","myetherwalletec.com","myetherwalletgeth.com","myetherwalletmetamask.com","myetherwalletmm.com","myetherwalletmy.com","myetherwalletnh.com","myetherwalletnod.com","myetherwalletrr.com","myetherwalletrty.com","myetherwalletsec.com","myetherwalletsecure.com","myetherwalletutc.com","myetherwalletver.info","myetherwalletview.com","myetherwalletview.info","myetherwalletvrf.com","myetherwalletmist.com","myetherwalletext.com","myetherwalletjson.com","mettalpay.com","bricklblock.io","bittrexy.com","utrust.so","myethierwallet.org","metallpay.com","kraken-wallet.com","dmarkt.io","etherdeltla.com","unlversa.io","universa.sale","mercuryprotocol.live","ripiocredlt.network","myetlherwa11et.com","dentacoin.in","rdrtg.com","myetherwallet.com.rdrgh.com","rdrgh.com","ripiocreditnetwork.co","riaden.network","hydrominer.biz","rdrblock.com","reqest.network","senstoken.com","myetherwallat.services","ripiocredit.net","xn--metherwallet-c06f.com","ico.ripiocredits.com","ripiocredits.com","raidens.network","artoken.co","myetherwalletlgn.com","etherblog.click","stormtoken.site","httpmyetherwallet.com","myetherwalletverify.com","byzantiumfork.com","myetherwallet.com.byzantiumfork.com","www-myethervvallet.com","ether24.info","block-v.io","bittrex.cash","shapishift.io","ripiocerdit.network","rnyetherwa11et.com","claimether.com","enigmatokensale.com","ethereum-org.com","mvetnerwallet.com","myctherwallet.com","myetherwaltet.com","myetherwatlet.com","privatix.me","myetherwalletcnf.com","myetherwalletver.com","privatix.top","privatix.pro","privatex.io","stormtoken.cc","raiden.online","stormstoken.com","myetereumwallet.com","stormtokens.net","myetherwalletconf.info","storrntoken.com","worldofbattles.io","ico.worldofbattles.io","privatix.live","riden.network","raidan.network","ralden.network","mymyetherwallet.com","myetherwallets.net","myetherwalletverify.info","stormxtoken.com","myethereum-wallet.com","myetherwallet-forkprep.pagedemo.co","myetnerwailet.com","www-mvetherwallet.com","etheirdelta.com","myetherwalletiu.com","myetherwaiiett.com","xn--mytherwalet-cbb87i.com","xn--myethrwallet-ivb.co","xn--myeterwallet-f1b.com","myehterwaliet.com","omegaone.co","myetherwaiietw.com","slack.com.ru","polkodot.network","request-network.net","requestnetwork.live","binancie.com","first-eth.info","myewerthwalliet.com","enjincoin.pw","xn--bitrex-k17b.com","alrswap.io","www-request.network","myetnenwallet.com","www-enigma.co","cryptoinsidenews.com","air-swap.tech","launch.airswap.cc","airswap.cc","airswaptoken.com","launch.airswap.in","airswap.in","security-steemit.com.mx","blockchalnwallet.com","blodkchainwallet.com","blodkchaln.com","myethereumwaiiet.com","myethereumwaliet.com","myethereumwalilet.com","myetherswailet.com","myetherswaliet.com","myetherswalilet.com","myetherwalilett.com","myetherwalletl.com","myetherwalletww.com","myethereunwallet.com","myethereumwallct.com","myetherwaiieti.com","myetherwaiiete.com","upfirng.com","paypie.net","paypie.tech","soam.co","myetherwaiict.com","numerai-token.com","www-bankera.com","vvanchain.org","omisegoairdrop.com","xn--enjncoin-41a.io","suncontract.su","myetherwaiietr.com","shapeshiff.io","warchain.org","myethwallett.com","myethervvaliet.com","wanchains.org","etherparty.in","enjincoin.me","etiam.io","invest.smartlands.tech","smartlands.tech","enijncoin.io","wanchain.network","nimiq.su","enjincoin.sale","tenxwallet.io","golem-network.net","myyethwallet.ml","mywetherwailiet.com","omg-omise.com","district0x.tech","centra-token.com","etherdetla.com","etnerparty.io","etherdelta.su","myetherwallett.neocities.org","myetherwallet-secure.com","myethereumwalletntw.info","real-markets.io","wallet-ethereum.org","request-network.com","shapeshifth.io","shiapeshift.in","coin.red-puise.com","ibittreix.com","coinkbase.com","cindicator.pro","myetherwallet.com.ailogin.me","eventchain.co","kinkik.in","myetherumwalletview.com","protostokenhub.com","coinrbase.com","myetherwalletlogin.com","omisegotoken.com","myethereumwalletntw.com","reall.markets","cobinhood.org","cobinhood.io","happy-coin.org","bitfinex.com.co","bitfienex.com","iconn.foundation","centra.vip","smartcontract.live","icon.community","air-token.com","centra.credit","myetherwallet-singin.com","smartcontractlink.com","shapesshift.io","0xtoken.io","augurproject.co","ethereumus.one","myetherumwalet.com","myetherwalletsignin.com","change-bank.org","charge-bank.com","myetherwalletsingin.com","myetherwalletcontract.com","change-bank.io","chainlink.tech","myetherwallet-confirm.com","tokensale.kybernet.network","kybernet.network","kyberr.network","kybernetwork.io","myetherwalletconfirm.com","kvnuke.github.io","kin.kikpro.co","myethereumwallet.co.uk","tokensale-kyber.network","kyber-network.co","tokensale.kyber-network.co","pyro0.github.io","tokensale.kyber.digital","kyber.digital","omise-go.me","my.etherwallet.com.de","bepartof.change-bank.co","change-bank.co","enigma-tokens.co","coinbase.com.eslogin.co","xn--bittrx-mva.com","ethrdelta.github.io","etherdellta.com","ico-nexus.social","red-pulse.tech","bitj0b.io","xn--bttrex-bwa.com","kin-klk.com","kin-crowdsale.com","ethedelta.com","coindash.su","myethwallet.co.uk","swarm.credit","myethereumwallet.uk","iconexu.social","wanchain.co","enigrna.co","linknetwork.co","qtum-token.com","omisego.com.co","rivetzintl.org","etherdelta.one","the-ether.pro","etherdelta.gitnub.io","kirkik.com","monetha.ltd","vlberate.io","ethereumwallet-kr.info","omise-go.org","iconexus.social","bittirrex.com","aventus.pro","atlant.solutions","aventus.group","metamak.io","omise.com.co","herotokens.io","starbase.pro","etherdelta.githulb.io","herotoken.co","kinico.net","dmarket.ltd","etherdelta.gilthub.io","golem-network.com","etnerscan.io","bllttriex.com","monetha.me","monetha.co","monetha-crowdsale.com","starbase.tech","aventus-crowdsale.com","shapeshift.pro","bllttrex.com","kickico.co","statustoken.im","bilttrex.com","tenxpay.io","bittrex.ltd","metalpay.im","aragon.im","coindash.tech","decentraland.tech","decentraland.pro","status-token.com","bittrex.cam","enigmatoken.com","unocoin.company","unocoin.fund","0xproject.io","0xtoken.com","numerai.tech","decentraiand.org","blockcrein.info","blockchealn.info","bllookchain.info","blockcbhain.info","myetherwallet.com.ethpromonodes.com","mettamask.io","tokenswap.org","netherum.com","etherexx.org","etherume.io","ethereum.plus","ehtereum.org","etereurm.org","etheream.com","ethererum.org","ethereum.io","etherdelta-glthub.com","cryptoalliance.herokuapp.com","bitspark2.com","indorsetoken.com","iconexus.tk","iconexus.ml","iconexus.ga","iconexus.cf","etherwallet.online","wallet-ethereum.net","bitsdigit.com","etherswap.org","eos.ac","uasfwallet.com","ziber.io","multiply-ethereum.info","bittrex.comze.com","karbon.vacau.com","etherdelta.gitlhub.io","etherdelta.glthub.io","digitaldevelopersfund.vacau.com","district-0x.io","coin-dash.com","coindash.ru","district0x.net","aragonproject.io","coin-wallet.info","coinswallet.info","contribute-status.im","ether-api.com","ether-wall.com","mycoinwallet.net","ethereumchamber.com","ethereumchamber.net","ethereumchest.com","ethewallet.com","myetherwallet.com.vc","myetherwallet.com.pe","myetherwallet.us.com","myetherwallet.com.u0387831.cp.regruhosting.ru","myethereumwallet.su","myetherweb.com.de","myetherieumwallet.com","myetehrwallet.com","myeterwalet.com","myetherwaiiet.com","myetherwallet.info","myetherwallet.ch","myetherwallet.om","myethervallet.com","myetherwallet.com.cm","myetherwallet.com.co","myetherwallet.com.de","myetherwallet.com.gl","myetherwallet.com.im","myetherwallet.com.ua","secure-myetherwallet.com","update-myetherwallet.com","wwwmyetherwallet.com","myeatherwallet.com","myetharwallet.com","myelherwallel.com","myetherwaillet.com","myetherwaliet.com","myetherwallel.com","myetherwallet.cam","myetherwallet.cc","myetherwallet.co","myetherwallet.cm","myetherwallet.cz","myetherwallet.org","myetherwallet.tech","myetherwallet.top","myetherwallet.net","myetherwallet.ru.com","myetherwallet.com.ru","metherwallet.com","myetrerwallet.com","myetlerwallet.com","myethterwallet.com","myethwallet.io","myethterwallet.co","myehterwallet.co","myaetherwallet.com","myetthterwallet.com","myetherwallet.one","myelterwallet.com","myetherwallet.gdn","myetherwallt.com","myeterwallet.com","myeteherwallet.com","myethearwailet.com","myetherwallelt.com","myetherwallett.com","etherwallet.org","myetherewallet.com","myeherwallet.com","myethcrwallet.com","myetherwallet.link","myetherwallets.com","myethearwaillet.com","myethearwallet.com","myetherawllet.com","myethereallet.com","myetherswallet.com","myetherwalet.com","myetherwaller.com","myetherwalliet.com","myetherwllet.com","etherwallet.io","myetherwallet.ca","myetherwallet.me","myetherwallet.ru","myetherwallet.xyz","myetherwallte.com","myethirwallet.com","myethrewallet.com","etherwallet.net","maetherwallet.com","meyetherwallet.com","my.ether-wallet.pw","myehterwallet.com","myeitherwallet.com","myelherwallet.com","myeltherwallet.com","myerherwallet.com","myethearwalet.com","myetherewalle.com","myethervvallet.com","myetherwallent.com","myetherwallet.fm","myetherwalllet.com","myetherwalltet.com","myetherwollet.com","myetlherwalet.com","myetlherwallet.com","rnyetherwallet.com","etherclassicwallet.com","omg-omise.co","omise-go.com","omise-go.net","omise-omg.com","omise-go.io","tenx-tech.com","bitclaive.com","tokensale-tenx.tech","ubiqcoin.org","metamask.com","ethtrade.io","myetcwallet.com","account-kigo.net","bitcoin-wallet.net","blocklichan.info","bloclkicihan.info","coindash.ml","eos-bonus.com","eos-io.info","ether-wallet.net","ethereum-wallet.info","ethereum-wallet.net","ethereumchest.net","reservations-kigo.net","reservations-lodgix.com","secure-liverez.com","secure-onerooftop.com","settings-liverez.com","software-liverez.com","software-lodgix.com","unhackableetherwallets.com","www-myetherwallet.com","etherwallet.co.za","etherwalletchain.com","etherwallets.net","etherwallets.nl","my-ethwallet.com","my.ether-wallet.co","myetherwallet.com.am","myetherwallet.com.ht","myetherwalletcom.com","myehterwailet.com","xn--myetherwalle-xoc.com","xn--myetherwalle-44i.com","xn--myetherwalle-xhk.com","xn--myetherwallt-cfb.com","xn--myetherwallt-6tb.com","xn--myetherwallt-xub.com","xn--myetherwallt-ovb.com","xn--myetherwallt-fwb.com","xn--myetherwallt-5wb.com","xn--myetherwallt-jzi.com","xn--myetherwallt-2ck.com","xn--myetherwallt-lok.com","xn--myetherwallt-lsl.com","xn--myetherwallt-ce6f.com","xn--myetherwalet-mcc.com","xn--myetherwalet-xhf.com","xn--myetherwalet-lcc.com","xn--myetherwaet-15ba.com","xn--myetherwalet-whf.com","xn--myetherwaet-v2ea.com","xn--myetherwllet-59a.com","xn--myetherwllet-jbb.com","xn--myetherwllet-wbb.com","xn--myetherwllet-9bb.com","xn--myetherwllet-ncb.com","xn--myetherwllet-0cb.com","xn--myetherwllet-5nb.com","xn--myetherwllet-ktd.com","xn--myetherwllet-mre.com","xn--myetherwllet-76e.com","xn--myetherwllet-o0l.com","xn--myetherwllet-c45f.com","xn--myetherallet-ejn.com","xn--myethewallet-4nf.com","xn--myethewallet-iof.com","xn--myethewallet-mpf.com","xn--myethewallet-6bk.com","xn--myethewallet-i31f.com","xn--myethrwallet-feb.com","xn--myethrwallt-fbbf.com","xn--myethrwallet-seb.com","xn--myethrwallt-rbbf.com","xn--myethrwallet-5eb.com","xn--myethrwallt-3bbf.com","xn--myethrwallet-0tb.com","xn--myethrwallt-tpbf.com","xn--myethrwallet-rub.com","xn--myethrwallt-iqbf.com","xn--myethrwallet-ivb.com","xn--myethrwallt-6qbf.com","xn--myethrwallet-8vb.com","xn--myethrwallt-vrbf.com","xn--myethrwallet-zwb.com","xn--myethrwallt-ksbf.com","xn--myethrwallet-dzi.com","xn--myethrwallt-wbif.com","xn--myethrwallet-wck.com","xn--myethrwallt-skjf.com","xn--myethrwallet-fok.com","xn--myethrwallt-fvjf.com","xn--myethrwallet-fsl.com","xn--myethrwallt-fwkf.com","xn--myethrwallet-5d6f.com","xn--myethrwallt-319ef.com","xn--myeterwallet-ufk.com","xn--myeterwallet-nrl.com","xn--myeterwallet-von.com","xn--myeterwallet-jl6c.com","xn--myeherwallet-ooc.com","xn--myeherwalle-6hci.com","xn--myeherwallet-v4i.com","xn--myeherwalle-zgii.com","xn--myeherwallet-ohk.com","xn--myeherwalle-6oji.com","xn--mytherwallet-ceb.com","xn--mythrwallet-cbbc.com","xn--mythrwallt-c7acf.com","xn--mytherwallet-peb.com","xn--mythrwallet-obbc.com","xn--mythrwallt-n7acf.com","xn--mytherwallet-2eb.com","xn--mythrwallet-0bbc.com","xn--mythrwallt-y7acf.com","xn--mytherwallet-xtb.com","xn--mythrwallet-qpbc.com","xn--mythrwallt-jlbcf.com","xn--mytherwallet-oub.com","xn--mythrwallet-fqbc.com","xn--mythrwallt-5lbcf.com","xn--mythrwallet-3qbc.com","xn--mythrwallt-smbcf.com","xn--mytherwallet-5vb.com","xn--mythrwallet-srbc.com","xn--mythrwallt-fnbcf.com","xn--mytherwallet-wwb.com","xn--mythrwallet-hsbc.com","xn--mythrwallt-1nbcf.com","xn--mytherwallet-9yi.com","xn--mythrwallet-tbic.com","xn--mythrwallt-dnhcf.com","xn--mytherwallet-tck.com","xn--mythrwallet-pkjc.com","xn--mythrwallt-lsicf.com","xn--mytherwallet-cok.com","xn--mythrwallet-cvjc.com","xn--mythrwallt-c2icf.com","xn--mytherwallet-csl.com","xn--mythrwallet-cwkc.com","xn--mythrwallt-c0jcf.com","xn--mytherwallet-2d6f.com","xn--mythrwallet-019ec.com","xn--mythrwallt-yq3ecf.com","xn--metherwallet-qlb.com","xn--metherwallet-1uf.com","xn--metherwallet-iyi.com","xn--metherwallet-zhk.com","xn--metherwallet-3ml.com","xn--mytherwallet-fvb.com","xn--myetherwallt-7db.com","xn--myetherwallt-leb.com","xn--myetherwallt-yeb.com","xn--yetherwallet-vjf.com","xn--yetherwallet-dfk.com","xn--yetherwallet-1t1f.com","xn--yetherwallet-634f.com","xn--myeherwallet-fpc.com","xn--myethewallt-crb.com","xn--metherwallet-1vc.com","xn--myeherwallt-kbb8039g.com","xn--myeherwallet-vk5f.com","xn--yethewallet-iw8ejl.com","xn--bittrx-th8b.com","xn--polniex-n0a.com","thekey.vin","thekey-vip.com","digitexftures.com","ethzero-wallet.org","zeepln.io","wepowers.network","wepower.vision"]}},"CurrencyController":{"currentCurrency":"usd","conversionRate":1112,"conversionDate":1517351401}}} \ No newline at end of file
diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js
index 72be86ed1..0cc6953bb 100644
--- a/test/lib/mock-config-manager.js
+++ b/test/lib/mock-config-manager.js
@@ -2,9 +2,8 @@ const ObservableStore = require('obs-store')
const clone = require('clone')
const ConfigManager = require('../../app/scripts/lib/config-manager')
const firstTimeState = require('../../app/scripts/first-time-state')
-const STORAGE_KEY = 'metamask-config'
-module.exports = function() {
- let store = new ObservableStore(clone(firstTimeState))
+module.exports = function () {
+ const store = new ObservableStore(clone(firstTimeState))
return new ConfigManager({ store })
-} \ No newline at end of file
+}
diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js
index 09bbf7ad5..ef229a82f 100644
--- a/test/lib/mock-encryptor.js
+++ b/test/lib/mock-encryptor.js
@@ -4,29 +4,33 @@ let cacheVal
module.exports = {
- encrypt(password, dataObj) {
+ encrypt (password, dataObj) {
cacheVal = dataObj
return Promise.resolve(mockHex)
},
- decrypt(password, text) {
+ decrypt (password, text) {
return Promise.resolve(cacheVal || {})
},
- encryptWithKey(key, dataObj) {
+ encryptWithKey (key, dataObj) {
return this.encrypt(key, dataObj)
},
- decryptWithKey(key, text) {
+ decryptWithKey (key, text) {
return this.decrypt(key, text)
},
- keyFromPassword(password) {
+ keyFromPassword (password) {
return Promise.resolve(mockKey)
},
- generateSalt() {
+ generateSalt () {
return 'WHADDASALT!'
},
+ getRandomValues () {
+ return 'SOO RANDO!!!1'
+ }
+
}
diff --git a/test/lib/mock-simple-keychain.js b/test/lib/mock-simple-keychain.js
index 615b3e537..d3addc3e8 100644
--- a/test/lib/mock-simple-keychain.js
+++ b/test/lib/mock-simple-keychain.js
@@ -6,32 +6,32 @@ const type = 'Simple Key Pair'
module.exports = class MockSimpleKeychain {
- static type() { return type }
+ static type () { return type }
- constructor(opts) {
+ constructor (opts) {
this.type = type
this.opts = opts || {}
this.wallets = []
}
- serialize() {
+ serialize () {
return [ fakeWallet.privKey ]
}
- deserialize(data) {
+ deserialize (data) {
if (!Array.isArray(data)) {
throw new Error('Simple keychain deserialize requires a privKey array.')
}
this.wallets = [ fakeWallet ]
}
- addAccounts(n = 1) {
- for(var i = 0; i < n; i++) {
+ addAccounts (n = 1) {
+ for (var i = 0; i < n; i++) {
this.wallets.push(fakeWallet)
}
}
- getAccounts() {
+ getAccounts () {
return this.wallets.map(w => w.address)
}
diff --git a/test/lib/mock-store.js b/test/lib/mock-store.js
new file mode 100644
index 000000000..8af8f6d23
--- /dev/null
+++ b/test/lib/mock-store.js
@@ -0,0 +1,18 @@
+const createStore = require('redux').createStore
+const applyMiddleware = require('redux').applyMiddleware
+const thunkMiddleware = require('redux-thunk').default
+const createLogger = require('redux-logger').createLogger
+const rootReducer = function () {}
+
+module.exports = configureStore
+
+const loggerMiddleware = createLogger()
+
+const createStoreWithMiddleware = applyMiddleware(
+ thunkMiddleware,
+ loggerMiddleware
+)(createStore)
+
+function configureStore (initialState) {
+ return createStoreWithMiddleware(rootReducer, initialState)
+}
diff --git a/test/lib/mock-tx-gen.js b/test/lib/mock-tx-gen.js
new file mode 100644
index 000000000..7aea09c59
--- /dev/null
+++ b/test/lib/mock-tx-gen.js
@@ -0,0 +1,40 @@
+const extend = require('xtend')
+const BN = require('ethereumjs-util').BN
+const template = {
+ 'status': 'submitted',
+ 'txParams': {
+ 'from': '0x7d3517b0d011698406d6e0aed8453f0be2697926',
+ 'gas': '0x30d40',
+ 'value': '0x0',
+ 'nonce': '0x3',
+ },
+}
+
+class TxGenerator {
+
+ constructor () {
+ this.txs = []
+ }
+
+ generate (tx = {}, opts = {}) {
+ let { count, fromNonce } = opts
+ let nonce = fromNonce || this.txs.length
+ let txs = []
+ for (let i = 0; i < count; i++) {
+ txs.push(extend(template, {
+ txParams: {
+ nonce: hexify(nonce++),
+ }
+ }, tx))
+ }
+ this.txs = this.txs.concat(txs)
+ return txs
+ }
+
+}
+
+function hexify (number) {
+ return '0x' + (new BN(number)).toString(16)
+}
+
+module.exports = TxGenerator
diff --git a/test/lib/shallow-with-store.js b/test/lib/shallow-with-store.js
new file mode 100644
index 000000000..9df10a3c5
--- /dev/null
+++ b/test/lib/shallow-with-store.js
@@ -0,0 +1,20 @@
+const { shallow, mount } = require('enzyme')
+
+module.exports = {
+ shallowWithStore,
+ mountWithStore,
+}
+
+function shallowWithStore (component, store) {
+ const context = {
+ store,
+ }
+ return shallow(component, {context})
+}
+
+function mountWithStore (component, store) {
+ const context = {
+ store,
+ }
+ return mount(component, {context})
+}
diff --git a/test/mascara.conf.js b/test/mascara.conf.js
new file mode 100644
index 000000000..97e53fc2b
--- /dev/null
+++ b/test/mascara.conf.js
@@ -0,0 +1,17 @@
+const getBaseConfig = require('./base.conf.js')
+
+module.exports = function(config) {
+ const settings = getBaseConfig(config)
+
+ // ui and tests
+ settings.files.push('dist/mascara/ui.js')
+ settings.files.push('dist/mascara/tests.js')
+ // service worker background
+ settings.files.push({ pattern: 'dist/mascara/background.js', watched: false, included: false, served: true }),
+ settings.proxies['/background.js'] = '/base/dist/mascara/background.js'
+
+ // use this to keep the browser open for debugging
+ settings.browserNoActivityTimeout = 10000000
+
+ config.set(settings)
+}
diff --git a/test/stub/provider.js b/test/stub/provider.js
new file mode 100644
index 000000000..e77db4e28
--- /dev/null
+++ b/test/stub/provider.js
@@ -0,0 +1,31 @@
+const JsonRpcEngine = require('json-rpc-engine')
+const scaffoldMiddleware = require('eth-json-rpc-middleware/scaffold')
+const TestBlockchain = require('eth-block-tracker/test/util/testBlockMiddleware')
+
+module.exports = {
+ createEngineForTestData,
+ providerFromEngine,
+ scaffoldMiddleware,
+ createTestProviderTools,
+}
+
+
+function createEngineForTestData () {
+ return new JsonRpcEngine()
+}
+
+function providerFromEngine (engine) {
+ const provider = { sendAsync: engine.handle.bind(engine) }
+ return provider
+}
+
+function createTestProviderTools (opts = {}) {
+ const engine = createEngineForTestData()
+ const testBlockchain = new TestBlockchain()
+ // handle provided hooks
+ engine.push(scaffoldMiddleware(opts.scaffold || {}))
+ // handle block tracker methods
+ engine.push(testBlockchain.createMiddleware())
+ const provider = providerFromEngine(engine)
+ return { provider, engine, testBlockchain }
+}
diff --git a/test/unit/account-link-test.js b/test/unit/account-link-test.js
deleted file mode 100644
index 803a70f37..000000000
--- a/test/unit/account-link-test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-var assert = require('assert')
-var linkGen = require('../../ui/lib/account-link')
-
-describe('account-link', function() {
-
- it('adds ropsten prefix to ropsten test network', function() {
- var result = linkGen('account', '3')
- assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included')
- assert.notEqual(result.indexOf('account'), -1, 'account included')
- })
-
- it('adds kovan prefix to kovan test network', function() {
- var result = linkGen('account', '42')
- assert.notEqual(result.indexOf('kovan'), -1, 'kovan included')
- assert.notEqual(result.indexOf('account'), -1, 'account included')
- })
-
-})
diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js
index 14198fa8a..648f456fb 100644
--- a/test/unit/actions/config_test.js
+++ b/test/unit/actions/config_test.js
@@ -1,36 +1,34 @@
-var jsdom = require('mocha-jsdom')
+// var jsdom = require('mocha-jsdom')
var assert = require('assert')
var freeze = require('deep-freeze-strict')
var path = require('path')
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
-var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
-
-describe ('config view actions', function() {
+var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
+describe('config view actions', function () {
var initialState = {
metamask: {
rpcTarget: 'foo',
- frequentRpcList: []
+ frequentRpcList: [],
},
appState: {
currentView: {
name: 'accounts',
- }
- }
+ },
+ },
}
freeze(initialState)
- describe('SHOW_CONFIG_PAGE', function() {
- it('should set appState.currentView.name to config', function() {
+ describe('SHOW_CONFIG_PAGE', function () {
+ it('should set appState.currentView.name to config', function () {
var result = reducers(initialState, actions.showConfigPage())
assert.equal(result.appState.currentView.name, 'config')
})
})
- describe('SET_RPC_TARGET', function() {
-
- it('sets the state.metamask.rpcTarget property of the state to the action.value', function() {
+ describe('SET_RPC_TARGET', function () {
+ it('sets the state.metamask.rpcTarget property of the state to the action.value', function () {
const action = {
type: actions.SET_RPC_TARGET,
value: 'foo',
@@ -41,5 +39,4 @@ describe ('config view actions', function() {
assert.equal(result.metamask.provider.rpcTarget, 'foo')
})
})
-
})
diff --git a/test/unit/actions/save_account_label_test.js b/test/unit/actions/save_account_label_test.js
index 1df428b1d..c5ffd6cbf 100644
--- a/test/unit/actions/save_account_label_test.js
+++ b/test/unit/actions/save_account_label_test.js
@@ -1,22 +1,21 @@
-var jsdom = require('mocha-jsdom')
+// var jsdom = require('mocha-jsdom')
var assert = require('assert')
var freeze = require('deep-freeze-strict')
var path = require('path')
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
-var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
+var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
-describe('SAVE_ACCOUNT_LABEL', function() {
-
- it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function() {
+describe('SAVE_ACCOUNT_LABEL', function () {
+ it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () {
var initialState = {
metamask: {
identities: {
foo: {
- name: 'bar'
- }
+ name: 'bar',
+ },
},
- }
+ },
}
freeze(initialState)
@@ -24,13 +23,13 @@ describe('SAVE_ACCOUNT_LABEL', function() {
type: actions.SAVE_ACCOUNT_LABEL,
value: {
account: 'foo',
- label: 'baz'
+ label: 'baz',
},
}
freeze(action)
var resultingState = reducers(initialState, action)
assert.equal(resultingState.metamask.identities.foo.name, action.value.label)
- });
-});
+ })
+})
diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js
index 2dc42d2ec..28b47d09d 100644
--- a/test/unit/actions/set_selected_account_test.js
+++ b/test/unit/actions/set_selected_account_test.js
@@ -1,18 +1,17 @@
-var jsdom = require('mocha-jsdom')
+// var jsdom = require('mocha-jsdom')
var assert = require('assert')
var freeze = require('deep-freeze-strict')
var path = require('path')
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
-var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
+var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
-describe('SET_SELECTED_ACCOUNT', function() {
-
- it('sets the state.appState.activeAddress property of the state to the action.value', function() {
+describe('SET_SELECTED_ACCOUNT', function () {
+ it('sets the state.appState.activeAddress property of the state to the action.value', function () {
var initialState = {
appState: {
activeAddress: 'foo',
- }
+ },
}
freeze(initialState)
@@ -24,15 +23,15 @@ describe('SET_SELECTED_ACCOUNT', function() {
var resultingState = reducers(initialState, action)
assert.equal(resultingState.appState.activeAddress, action.value)
- });
-});
+ })
+})
-describe('SHOW_ACCOUNT_DETAIL', function() {
- it('updates metamask state', function() {
+describe('SHOW_ACCOUNT_DETAIL', function () {
+ it('updates metamask state', function () {
var initialState = {
metamask: {
- selectedAddress: 'foo'
- }
+ selectedAddress: 'foo',
+ },
}
freeze(initialState)
diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js
index bd72a666e..b6a691860 100644
--- a/test/unit/actions/tx_test.js
+++ b/test/unit/actions/tx_test.js
@@ -1,29 +1,27 @@
-var jsdom = require('mocha-jsdom')
+// var jsdom = require('mocha-jsdom')
var assert = require('assert')
var freeze = require('deep-freeze-strict')
var path = require('path')
var sinon = require('sinon')
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
-var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
+var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
-describe('tx confirmation screen', function() {
-
- beforeEach(function() {
- this.sinon = sinon.sandbox.create();
- });
+describe('tx confirmation screen', function () {
+ beforeEach(function () {
+ this.sinon = sinon.sandbox.create()
+ })
- afterEach(function(){
- this.sinon.restore();
- });
+ afterEach(function () {
+ this.sinon.restore()
+ })
var initialState, result
- describe('when there is only one tx', function() {
+ describe('when there is only one tx', function () {
var firstTxId = 1457634084250832
- beforeEach(function() {
-
+ beforeEach(function () {
initialState = {
appState: {
currentView: {
@@ -34,130 +32,42 @@ describe('tx confirmation screen', function() {
unapprovedTxs: {
'1457634084250832': {
id: 1457634084250832,
- status: "unconfirmed",
+ status: 'unconfirmed',
time: 1457634084250,
- }
+ },
},
- }
+ },
}
freeze(initialState)
})
- describe('cancelTx', function() {
-
- before(function(done) {
+ describe('cancelTx', function () {
+ before(function (done) {
actions._setBackgroundConnection({
- approveTransaction(txId, cb) { cb('An error!') },
- cancelTransaction(txId) { /* noop */ },
- clearSeedWordCache(cb) { cb() },
+ approveTransaction (txId, cb) { cb('An error!') },
+ cancelTransaction (txId, cb) { cb() },
+ clearSeedWordCache (cb) { cb() },
})
- let action = actions.cancelTx({value: firstTxId})
- result = reducers(initialState, action)
+ actions.cancelTx({value: firstTxId})((action) => {
+ result = reducers(initialState, action)
+ })
done()
})
- it('should transition to the account detail view', function() {
+ it('should transition to the account detail view', function () {
assert.equal(result.appState.currentView.name, 'accountDetail')
})
- it('should have no unconfirmed txs remaining', function() {
+ it('should have no unconfirmed txs remaining', function () {
var count = getUnconfirmedTxCount(result)
assert.equal(count, 0)
})
})
-
- describe('sendTx', function() {
- var result
-
- describe('when there is an error', function() {
-
- before(function(done) {
- alert = () => {/* noop */}
-
- actions._setBackgroundConnection({
- approveTransaction(txId, cb) { cb({message: 'An error!'}) },
- })
-
- actions.sendTx({id: firstTxId})(function(action) {
- result = reducers(initialState, action)
- done()
- })
- })
-
- it('should stay on the page', function() {
- assert.equal(result.appState.currentView.name, 'confTx')
- })
-
- it('should set errorMessage on the currentView', function() {
- assert(result.appState.currentView.errorMessage)
- })
- })
-
- describe('when there is success', function() {
- it('should complete tx and go home', function() {
- actions._setBackgroundConnection({
- approveTransaction(txId, cb) { cb() },
- })
-
- var dispatchExpect = sinon.mock()
- dispatchExpect.twice()
-
- actions.sendTx({id: firstTxId})(dispatchExpect)
- })
- })
- })
-
- describe('when there are two pending txs', function() {
- var firstTxId = 1457634084250832
- var result, initialState
- before(function(done) {
- initialState = {
- appState: {
- currentView: {
- name: 'confTx',
- },
- },
- metamask: {
- unapprovedTxs: {
- '1457634084250832': {
- id: firstTxId,
- status: "unconfirmed",
- time: 1457634084250,
- },
- '1457634084250833': {
- id: 1457634084250833,
- status: "unconfirmed",
- time: 1457634084255,
- },
- },
- }
- }
- freeze(initialState)
-
- // Mocking a background connection:
- actions._setBackgroundConnection({
- approveTransaction(firstTxId, cb) { cb() },
- })
-
- let action = actions.sendTx({id: firstTxId})(function(action) {
- result = reducers(initialState, action)
- })
- done()
- })
-
- it('should stay on the confTx view', function() {
- assert.equal(result.appState.currentView.name, 'confTx')
- })
-
- it('should transition to the first tx', function() {
- assert.equal(result.appState.currentView.context, 0)
- })
- })
})
-});
+})
-function getUnconfirmedTxCount(state) {
+function getUnconfirmedTxCount (state) {
var txs = state.metamask.unapprovedTxs
var count = Object.keys(txs).length
return count
diff --git a/test/unit/actions/view_info_test.js b/test/unit/actions/view_info_test.js
index 0558c6e42..69895d801 100644
--- a/test/unit/actions/view_info_test.js
+++ b/test/unit/actions/view_info_test.js
@@ -1,23 +1,22 @@
-var jsdom = require('mocha-jsdom')
+// var jsdom = require('mocha-jsdom')
var assert = require('assert')
var freeze = require('deep-freeze-strict')
var path = require('path')
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
-var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
+var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
-describe('SHOW_INFO_PAGE', function() {
-
- it('sets the state.appState.currentView.name property to info', function() {
+describe('SHOW_INFO_PAGE', function () {
+ it('sets the state.appState.currentView.name property to info', function () {
var initialState = {
appState: {
activeAddress: 'foo',
- }
+ },
}
freeze(initialState)
const action = actions.showInfoPage()
var resultingState = reducers(initialState, action)
assert.equal(resultingState.appState.currentView.name, 'info')
- });
-});
+ })
+})
diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js
index 37be9ee85..28b565499 100644
--- a/test/unit/actions/warning_test.js
+++ b/test/unit/actions/warning_test.js
@@ -1,14 +1,13 @@
-var jsdom = require('mocha-jsdom')
+// var jsdom = require('mocha-jsdom')
var assert = require('assert')
var freeze = require('deep-freeze-strict')
var path = require('path')
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
-var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
+var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
-describe('action DISPLAY_WARNING', function() {
-
- it('sets appState.warning to provided value', function() {
+describe('action DISPLAY_WARNING', function () {
+ it('sets appState.warning to provided value', function () {
var initialState = {
appState: {},
}
@@ -20,5 +19,5 @@ describe('action DISPLAY_WARNING', function() {
const resultingState = reducers(initialState, action)
assert.equal(resultingState.appState.warning, warningText, 'warning text set')
- });
-});
+ })
+})
diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js
index f345b0328..655c9022c 100644
--- a/test/unit/address-book-controller.js
+++ b/test/unit/address-book-controller.js
@@ -1,5 +1,4 @@
const assert = require('assert')
-const extend = require('xtend')
const AddressBookController = require('../../app/scripts/controllers/address-book')
const mockKeyringController = {
@@ -7,21 +6,20 @@ const mockKeyringController = {
getState: function () {
return {
identities: {
- '0x0aaa' : {
+ '0x0aaa': {
address: '0x0aaa',
name: 'owned',
- }
- }
+ },
+ },
}
- }
- }
+ },
+ },
}
-
-describe('address-book-controller', function() {
+describe('address-book-controller', function () {
var addressBookController
- beforeEach(function() {
+ beforeEach(function () {
addressBookController = new AddressBookController({}, mockKeyringController)
})
diff --git a/test/unit/blacklist-controller-test.js b/test/unit/blacklist-controller-test.js
new file mode 100644
index 000000000..a9260466f
--- /dev/null
+++ b/test/unit/blacklist-controller-test.js
@@ -0,0 +1,41 @@
+const assert = require('assert')
+const BlacklistController = require('../../app/scripts/controllers/blacklist')
+
+describe('blacklist controller', function () {
+ let blacklistController
+
+ before(() => {
+ blacklistController = new BlacklistController()
+ })
+
+ describe('checkForPhishing', function () {
+ it('should not flag whitelisted values', function () {
+ const result = blacklistController.checkForPhishing('www.metamask.io')
+ assert.equal(result, false)
+ })
+ it('should flag explicit values', function () {
+ const result = blacklistController.checkForPhishing('metamask.com')
+ assert.equal(result, true)
+ })
+ it('should flag levenshtein values', function () {
+ const result = blacklistController.checkForPhishing('metmask.io')
+ assert.equal(result, true)
+ })
+ it('should not flag not-even-close values', function () {
+ const result = blacklistController.checkForPhishing('example.com')
+ assert.equal(result, false)
+ })
+ it('should not flag the ropsten faucet domains', function () {
+ const result = blacklistController.checkForPhishing('faucet.metamask.io')
+ assert.equal(result, false)
+ })
+ it('should not flag the mascara domain', function () {
+ const result = blacklistController.checkForPhishing('zero.metamask.io')
+ assert.equal(result, false)
+ })
+ it('should not flag the mascara-faucet domain', function () {
+ const result = blacklistController.checkForPhishing('zero-faucet.metamask.io')
+ assert.equal(result, false)
+ })
+ })
+}) \ No newline at end of file
diff --git a/test/unit/components/balance-component-test.js b/test/unit/components/balance-component-test.js
new file mode 100644
index 000000000..9b1e82acf
--- /dev/null
+++ b/test/unit/components/balance-component-test.js
@@ -0,0 +1,45 @@
+const assert = require('assert')
+const h = require('react-hyperscript')
+const { createMockStore } = require('redux-test-utils')
+const { shallowWithStore } = require('../../lib/shallow-with-store')
+const BalanceComponent = require('../../../ui/app/components/balance-component')
+const mockState = {
+ metamask: {
+ accounts: { abc: {} },
+ network: 1,
+ selectedAddress: 'abc',
+ }
+}
+
+describe('BalanceComponent', function () {
+ let balanceComponent
+ let store
+ let component
+ beforeEach(function () {
+ store = createMockStore(mockState)
+ component = shallowWithStore(h(BalanceComponent), store)
+ balanceComponent = component.dive()
+ })
+
+ it('shows token balance and convert to fiat value based on conversion rate', function () {
+ const formattedBalance = '1.23 ETH'
+
+ const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
+ const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 2)
+
+ assert.equal('1.23 ETH', tokenBalance)
+ assert.equal(2.46, fiatDisplayNumber)
+ })
+
+ it('shows only the token balance when conversion rate is not available', function () {
+ const formattedBalance = '1.23 ETH'
+
+ const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
+ const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 0)
+
+ assert.equal('1.23 ETH', tokenBalance)
+ assert.equal('N/A', fiatDisplayNumber)
+ })
+
+})
+
diff --git a/test/unit/components/binary-renderer-test.js b/test/unit/components/binary-renderer-test.js
index 3264faddc..ee2fa8b60 100644
--- a/test/unit/components/binary-renderer-test.js
+++ b/test/unit/components/binary-renderer-test.js
@@ -1,24 +1,22 @@
var assert = require('assert')
var BinaryRenderer = require('../../../ui/app/components/binary-renderer')
-describe('BinaryRenderer', function() {
-
+describe('BinaryRenderer', function () {
let binaryRenderer
const message = 'Hello, world!'
const buffer = new Buffer(message, 'utf8')
const hex = buffer.toString('hex')
- beforeEach(function() {
+ beforeEach(function () {
binaryRenderer = new BinaryRenderer()
})
- it('recovers message', function() {
+ it('recovers message', function () {
const result = binaryRenderer.hexToText(hex)
assert.equal(result, message)
})
-
- it('recovers message with hex prefix', function() {
+ it('recovers message with hex prefix', function () {
const result = binaryRenderer.hexToText('0x' + hex)
assert.equal(result, message)
})
diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js
new file mode 100644
index 000000000..58ecc9c89
--- /dev/null
+++ b/test/unit/components/bn-as-decimal-input-test.js
@@ -0,0 +1,89 @@
+var assert = require('assert')
+
+const additions = require('react-testutils-additions')
+const h = require('react-hyperscript')
+const ReactTestUtils = require('react-addons-test-utils')
+const ethUtil = require('ethereumjs-util')
+const BN = ethUtil.BN
+
+var BnInput = require('../../../ui/app/components/bn-as-decimal-input')
+
+describe('BnInput', function () {
+ it('can tolerate a gas decimal number at a high precision', function (done) {
+ const renderer = ReactTestUtils.createRenderer()
+
+ let valueStr = '20'
+ while (valueStr.length < 20) {
+ valueStr += '0'
+ }
+ const value = new BN(valueStr, 10)
+
+ const inputStr = '2.3'
+
+ let targetStr = '23'
+ while (targetStr.length < 19) {
+ targetStr += '0'
+ }
+ const target = new BN(targetStr, 10)
+
+ const precision = 18 // ether precision
+ const scale = 18
+
+ const props = {
+ value,
+ scale,
+ precision,
+ onChange: (newBn) => {
+ assert.equal(newBn.toString(), target.toString(), 'should tolerate increase')
+ done()
+ },
+ }
+
+ const inputComponent = h(BnInput, props)
+ const component = additions.renderIntoDocument(inputComponent)
+ renderer.render(inputComponent)
+ const input = additions.find(component, 'input.hex-input')[0]
+ ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: {
+ value: inputStr,
+ checkValidity () { return true } },
+ })
+ })
+
+ it('can tolerate wei precision', function (done) {
+ const renderer = ReactTestUtils.createRenderer()
+
+ let valueStr = '1000000000'
+
+ const value = new BN(valueStr, 10)
+ const inputStr = '1.000000001'
+
+
+ let targetStr = '1000000001'
+
+ const target = new BN(targetStr, 10)
+
+ const precision = 9 // gwei precision
+ const scale = 9
+
+ const props = {
+ value,
+ scale,
+ precision,
+ onChange: (newBn) => {
+ assert.equal(newBn.toString(), target.toString(), 'should tolerate increase')
+ const reInput = BnInput.prototype.downsize(newBn.toString(), 9, 9)
+ assert.equal(reInput.toString(), inputStr, 'should tolerate increase')
+ done()
+ },
+ }
+
+ const inputComponent = h(BnInput, props)
+ const component = additions.renderIntoDocument(inputComponent)
+ renderer.render(inputComponent)
+ const input = additions.find(component, 'input.hex-input')[0]
+ ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: {
+ value: inputStr,
+ checkValidity () { return true } },
+ })
+ })
+})
diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js
new file mode 100644
index 000000000..c6c588e1c
--- /dev/null
+++ b/test/unit/components/pending-tx-test.js
@@ -0,0 +1,67 @@
+const assert = require('assert')
+const h = require('react-hyperscript')
+const PendingTx = require('../../../ui/app/components/pending-tx')
+const ethUtil = require('ethereumjs-util')
+
+const { createMockStore } = require('redux-test-utils')
+const { shallowWithStore } = require('../../lib/shallow-with-store')
+
+const identities = { abc: {}, def: {} }
+const mockState = {
+ metamask: {
+ accounts: { abc: {} },
+ identities,
+ conversionRate: 10,
+ selectedAddress: 'abc',
+ }
+}
+
+describe('PendingTx', function () {
+ const gasPrice = '0x4A817C800' // 20 Gwei
+ const txData = {
+ 'id': 5021615666270214,
+ 'time': 1494458763011,
+ 'status': 'unapproved',
+ 'metamaskNetworkId': '1494442339676',
+ 'txParams': {
+ 'from': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826',
+ 'to': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
+ 'value': '0xde0b6b3a7640000',
+ gasPrice,
+ 'gas': '0x7b0c',
+ },
+ 'gasLimitSpecified': false,
+ 'estimatedGas': '0x5208',
+ }
+ const newGasPrice = '0x77359400'
+
+ const computedBalances = {}
+ computedBalances[Object.keys(identities)[0]] = {
+ ethBalance: '0x00000000000000056bc75e2d63100000',
+ }
+ const props = {
+ txData,
+ computedBalances,
+ sendTransaction: (txMeta, event) => {
+ // Assert changes:
+ const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice)
+ assert.notEqual(result, gasPrice, 'gas price should change')
+ assert.equal(result, newGasPrice, 'gas price assigned.')
+ },
+ }
+
+ let pendingTxComponent
+ let store
+ let component
+ beforeEach(function () {
+ store = createMockStore(mockState)
+ component = shallowWithStore(h(PendingTx, props), store)
+ pendingTxComponent = component
+ })
+
+ it('should render correctly', function (done) {
+ assert.equal(pendingTxComponent.props().identities, identities)
+ done()
+ })
+})
+
diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js
index 05324e741..b710e2dfb 100644
--- a/test/unit/config-manager-test.js
+++ b/test/unit/config-manager-test.js
@@ -2,26 +2,22 @@
global.fetch = global.fetch || require('isomorphic-fetch')
const assert = require('assert')
-const extend = require('xtend')
-const rp = require('request-promise')
-const nock = require('nock')
const configManagerGen = require('../lib/mock-config-manager')
-describe('config-manager', function() {
+describe('config-manager', function () {
var configManager
- beforeEach(function() {
+ beforeEach(function () {
configManager = configManagerGen()
})
- describe('#setConfig', function() {
-
+ describe('#setConfig', function () {
it('should set the config key', function () {
var testConfig = {
provider: {
type: 'rpc',
- rpcTarget: 'foobar'
- }
+ rpcTarget: 'foobar',
+ },
}
configManager.setConfig(testConfig)
var result = configManager.getData()
@@ -30,17 +26,17 @@ describe('config-manager', function() {
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget)
})
- it('setting wallet should not overwrite config', function() {
+ it('setting wallet should not overwrite config', function () {
var testConfig = {
provider: {
type: 'rpc',
- rpcTarget: 'foobar'
+ rpcTarget: 'foobar',
},
}
configManager.setConfig(testConfig)
var testWallet = {
- name: 'this is my fake wallet'
+ name: 'this is my fake wallet',
}
configManager.setWallet(testWallet)
@@ -58,13 +54,13 @@ describe('config-manager', function() {
})
})
- describe('wallet nicknames', function() {
- it('should return null when no nicknames are saved', function() {
+ describe('wallet nicknames', function () {
+ it('should return null when no nicknames are saved', function () {
var nick = configManager.nicknameForWallet('0x0')
assert.equal(nick, null, 'no nickname returned')
})
- it('should persist nicknames', function() {
+ it('should persist nicknames', function () {
var account = '0x0'
var nick1 = 'foo'
var nick2 = 'bar'
@@ -79,8 +75,8 @@ describe('config-manager', function() {
})
})
- describe('rpc manipulations', function() {
- it('changing rpc should return a different rpc', function() {
+ describe('rpc manipulations', function () {
+ it('changing rpc should return a different rpc', function () {
var firstRpc = 'first'
var secondRpc = 'second'
@@ -94,21 +90,21 @@ describe('config-manager', function() {
})
})
- describe('transactions', function() {
- beforeEach(function() {
+ describe('transactions', function () {
+ beforeEach(function () {
configManager.setTxList([])
})
- describe('#getTxList', function() {
- it('when new should return empty array', function() {
+ describe('#getTxList', function () {
+ it('when new should return empty array', function () {
var result = configManager.getTxList()
assert.ok(Array.isArray(result))
assert.equal(result.length, 0)
})
})
- describe('#setTxList', function() {
- it('saves the submitted data to the tx list', function() {
+ describe('#setTxList', function () {
+ it('saves the submitted data to the tx list', function () {
var target = [{ foo: 'bar' }]
configManager.setTxList(target)
var result = configManager.getTxList()
diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js
index 079f8b488..63ab60f9e 100644
--- a/test/unit/currency-controller-test.js
+++ b/test/unit/currency-controller-test.js
@@ -2,86 +2,81 @@
global.fetch = global.fetch || require('isomorphic-fetch')
const assert = require('assert')
-const extend = require('xtend')
-const rp = require('request-promise')
const nock = require('nock')
const CurrencyController = require('../../app/scripts/controllers/currency')
-describe('currency-controller', function() {
+describe('currency-controller', function () {
var currencyController
- beforeEach(function() {
+ beforeEach(function () {
currencyController = new CurrencyController()
})
- describe('currency conversions', function() {
-
- describe('#setCurrentCurrency', function() {
- it('should return USD as default', function() {
- assert.equal(currencyController.getCurrentCurrency(), 'USD')
+ describe('currency conversions', function () {
+ describe('#setCurrentCurrency', function () {
+ it('should return USD as default', function () {
+ assert.equal(currencyController.getCurrentCurrency(), 'usd')
})
- it('should be able to set to other currency', function() {
- assert.equal(currencyController.getCurrentCurrency(), 'USD')
+ it('should be able to set to other currency', function () {
+ assert.equal(currencyController.getCurrentCurrency(), 'usd')
currencyController.setCurrentCurrency('JPY')
var result = currencyController.getCurrentCurrency()
assert.equal(result, 'JPY')
})
})
- describe('#getConversionRate', function() {
- it('should return undefined if non-existent', function() {
+ describe('#getConversionRate', function () {
+ it('should return undefined if non-existent', function () {
var result = currencyController.getConversionRate()
assert.ok(!result)
})
})
- describe('#updateConversionRate', function() {
- it('should retrieve an update for ETH to USD and set it in memory', function(done) {
+ describe('#updateConversionRate', function () {
+ it('should retrieve an update for ETH to USD and set it in memory', function (done) {
this.timeout(15000)
- var usdMock = nock('https://www.cryptonator.com')
- .get('/api/ticker/eth-USD')
- .reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
+ nock('https://api.infura.io')
+ .get('/v1/ticker/ethusd')
+ .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}')
assert.equal(currencyController.getConversionRate(), 0)
- currencyController.setCurrentCurrency('USD')
+ currencyController.setCurrentCurrency('usd')
currencyController.updateConversionRate()
- .then(function() {
+ .then(function () {
var result = currencyController.getConversionRate()
console.log('currencyController.getConversionRate:', result)
assert.equal(typeof result, 'number')
done()
- }).catch(function(err) {
+ }).catch(function (err) {
done(err)
})
-
})
- it('should work for JPY as well.', function() {
+ it('should work for JPY as well.', function () {
this.timeout(15000)
assert.equal(currencyController.getConversionRate(), 0)
- var jpyMock = nock('https://www.cryptonator.com')
- .get('/api/ticker/eth-JPY')
- .reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
+ nock('https://api.infura.io')
+ .get('/v1/ticker/ethjpy')
+ .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}')
var promise = new Promise(
function (resolve, reject) {
- currencyController.setCurrentCurrency('JPY')
- currencyController.updateConversionRate().then(function() {
+ currencyController.setCurrentCurrency('jpy')
+ currencyController.updateConversionRate().then(function () {
resolve()
})
- })
+ })
- promise.then(function() {
+ promise.then(function () {
var result = currencyController.getConversionRate()
assert.equal(typeof result, 'number')
- }).catch(function(err) {
+ }).catch(function (done, err) {
done(err)
})
})
})
})
-
})
diff --git a/test/unit/explorer-link-test.js b/test/unit/explorer-link-test.js
deleted file mode 100644
index 4f0230c2c..000000000
--- a/test/unit/explorer-link-test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-var assert = require('assert')
-var linkGen = require('../../ui/lib/explorer-link')
-
-describe('explorer-link', function() {
-
- it('adds ropsten prefix to ropsten test network', function() {
- var result = linkGen('hash', '3')
- assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten injected')
- })
-
- it('adds kovan prefix to kovan test network', function() {
- var result = linkGen('hash', '42')
- assert.notEqual(result.indexOf('kovan'), -1, 'kovan injected')
- })
-
-})
diff --git a/test/unit/infura-controller-test.js b/test/unit/infura-controller-test.js
new file mode 100644
index 000000000..605305efa
--- /dev/null
+++ b/test/unit/infura-controller-test.js
@@ -0,0 +1,62 @@
+const assert = require('assert')
+const sinon = require('sinon')
+const InfuraController = require('../../app/scripts/controllers/infura')
+
+describe('infura-controller', function () {
+ let infuraController, sandbox, networkStatus
+ const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down'}
+
+ before(async function () {
+ infuraController = new InfuraController()
+ sandbox = sinon.sandbox.create()
+ sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response)
+ networkStatus = await infuraController.checkInfuraNetworkStatus()
+ })
+
+ after(function () {
+ sandbox.restore()
+ })
+
+ describe('Network status queries', function () {
+
+ describe('Mainnet', function () {
+ it('should have Mainnet', function () {
+ assert.equal(Object.keys(networkStatus)[0], 'mainnet')
+ })
+
+ it('should have a value for Mainnet status', function () {
+ assert.equal(networkStatus.mainnet, 'degraded')
+ })
+ })
+
+ describe('Ropsten', function () {
+ it('should have Ropsten', function () {
+ assert.equal(Object.keys(networkStatus)[1], 'ropsten')
+ })
+
+ it('should have a value for Ropsten status', function () {
+ assert.equal(networkStatus.ropsten, 'ok')
+ })
+ })
+
+ describe('Kovan', function () {
+ it('should have Kovan', function () {
+ assert.equal(Object.keys(networkStatus)[2], 'kovan')
+ })
+
+ it('should have a value for Kovan status', function () {
+ assert.equal(networkStatus.kovan, 'ok')
+ })
+ })
+
+ describe('Rinkeby', function () {
+ it('should have Rinkeby', function () {
+ assert.equal(Object.keys(networkStatus)[3], 'rinkeby')
+ })
+
+ it('should have a value for Rinkeby status', function () {
+ assert.equal(networkStatus.rinkeby, 'down')
+ })
+ })
+ })
+})
diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js
deleted file mode 100644
index efd0a3546..000000000
--- a/test/unit/keyring-controller-test.js
+++ /dev/null
@@ -1,172 +0,0 @@
-const assert = require('assert')
-const KeyringController = require('../../app/scripts/keyring-controller')
-const configManagerGen = require('../lib/mock-config-manager')
-const ethUtil = require('ethereumjs-util')
-const BN = ethUtil.BN
-const async = require('async')
-const mockEncryptor = require('../lib/mock-encryptor')
-const MockSimpleKeychain = require('../lib/mock-simple-keychain')
-const sinon = require('sinon')
-
-describe('KeyringController', function() {
-
- let keyringController, state
- let password = 'password123'
- let seedWords = 'puzzle seed penalty soldier say clay field arctic metal hen cage runway'
- let addresses = ['eF35cA8EbB9669A35c31b5F6f249A9941a812AC1'.toLowerCase()]
- let accounts = []
- let originalKeystore
-
- beforeEach(function(done) {
- this.sinon = sinon.sandbox.create()
- window.localStorage = {} // Hacking localStorage support into JSDom
-
- keyringController = new KeyringController({
- configManager: configManagerGen(),
- txManager: {
- getTxList: () => [],
- getUnapprovedTxList: () => []
- },
- ethStore: {
- addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
- },
- })
-
- // Stub out the browser crypto for a mock encryptor.
- // Browser crypto is tested in the integration test suite.
- keyringController.encryptor = mockEncryptor
-
- keyringController.createNewVaultAndKeychain(password)
- .then(function (newState) {
- state = newState
- done()
- })
- .catch((err) => {
- done(err)
- })
- })
-
- afterEach(function() {
- // Cleanup mocks
- this.sinon.restore()
- })
-
- describe('#createNewVaultAndKeychain', function () {
- this.timeout(10000)
-
- it('should set a vault on the configManager', function(done) {
- keyringController.store.updateState({ vault: null })
- assert(!keyringController.store.getState().vault, 'no previous vault')
- keyringController.createNewVaultAndKeychain(password)
- .then(() => {
- const vault = keyringController.store.getState().vault
- assert(vault, 'vault created')
- done()
- })
- .catch((reason) => {
- done(reason)
- })
- })
- })
-
- describe('#restoreKeyring', function() {
-
- it(`should pass a keyring's serialized data back to the correct type.`, function(done) {
- const mockSerialized = {
- type: 'HD Key Tree',
- data: {
- mnemonic: seedWords,
- numberOfAccounts: 1,
- }
- }
- const mock = this.sinon.mock(keyringController)
-
- mock.expects('getBalanceAndNickname')
- .exactly(1)
-
- keyringController.restoreKeyring(mockSerialized)
- .then((keyring) => {
- assert.equal(keyring.wallets.length, 1, 'one wallet restored')
- return keyring.getAccounts()
- })
- .then((accounts) => {
- assert.equal(accounts[0], addresses[0])
- mock.verify()
- done()
- })
- .catch((reason) => {
- done(reason)
- })
- })
- })
-
- describe('#createNickname', function() {
- it('should add the address to the identities hash', function() {
- const fakeAddress = '0x12345678'
- keyringController.createNickname(fakeAddress)
- const identities = keyringController.memStore.getState().identities
- const identity = identities[fakeAddress]
- assert.equal(identity.address, fakeAddress)
- })
- })
-
- describe('#saveAccountLabel', function() {
- it ('sets the nickname', function(done) {
- const account = addresses[0]
- var nick = 'Test nickname'
- const identities = keyringController.memStore.getState().identities
- identities[ethUtil.addHexPrefix(account)] = {}
- keyringController.memStore.updateState({ identities })
- keyringController.saveAccountLabel(account, nick)
- .then((label) => {
- try {
- assert.equal(label, nick)
- const persisted = keyringController.store.getState().walletNicknames[account]
- assert.equal(persisted, nick)
- done()
- } catch (err) {
- done()
- }
- })
- .catch((reason) => {
- done(reason)
- })
- })
- })
-
- describe('#getAccounts', function() {
- it('returns the result of getAccounts for each keyring', function(done) {
- keyringController.keyrings = [
- { getAccounts() { return Promise.resolve([1,2,3]) } },
- { getAccounts() { return Promise.resolve([4,5,6]) } },
- ]
-
- keyringController.getAccounts()
- .then((result) => {
- assert.deepEqual(result, [1,2,3,4,5,6])
- done()
- })
- })
- })
-
- describe('#addGasBuffer', function() {
- it('adds 100k gas buffer to estimates', function() {
-
- const gas = '0x04ee59' // Actual estimated gas example
- const tooBigOutput = '0x80674f9' // Actual bad output
- const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
- const correctBuffer = new BN('100000', 10)
- const correct = bnGas.add(correctBuffer)
-
- const tooBig = new BN(tooBigOutput, 16)
- const result = keyringController.addGasBuffer(gas)
- const bnResult = new BN(ethUtil.stripHexPrefix(result), 16)
-
- assert.equal(result.indexOf('0x'), 0, 'included hex prefix')
- assert(bnResult.gt(bnGas), 'Estimate increased in value.')
- assert.equal(bnResult.sub(bnGas).toString(10), '100000', 'added 100k gas')
- assert.equal(result, '0x' + correct.toString(16), 'Added the right amount')
- assert.notEqual(result, tooBigOutput, 'not that bad estimate')
- })
- })
-})
diff --git a/test/unit/linting_test.js b/test/unit/linting_test.js
deleted file mode 100644
index 75d90652d..000000000
--- a/test/unit/linting_test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// LINTING:
-const lint = require('mocha-eslint');
-const lintPaths = ['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/**', '!docs/**', '!app/scripts/chromereload.js']
-
-const lintOptions = {
- strict: false,
-}
-
-lint(lintPaths, lintOptions) \ No newline at end of file
diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js
index faf7429d4..9b76241ed 100644
--- a/test/unit/message-manager-test.js
+++ b/test/unit/message-manager-test.js
@@ -1,29 +1,26 @@
const assert = require('assert')
-const extend = require('xtend')
-const EventEmitter = require('events')
-
const MessageManger = require('../../app/scripts/lib/message-manager')
-describe('Transaction Manager', function() {
+describe('Message Manager', function () {
let messageManager
- beforeEach(function() {
- messageManager = new MessageManger ()
+ beforeEach(function () {
+ messageManager = new MessageManger()
})
- describe('#getMsgList', function() {
- it('when new should return empty array', function() {
+ describe('#getMsgList', function () {
+ it('when new should return empty array', function () {
var result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 0)
})
- it('should also return transactions from local storage if any', function() {
+ it('should also return transactions from local storage if any', function () {
})
})
- describe('#addMsg', function() {
- it('adds a Msg returned in getMsgList', function() {
+ describe('#addMsg', function () {
+ it('adds a Msg returned in getMsgList', function () {
var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
var result = messageManager.messages
@@ -33,8 +30,8 @@ describe('Transaction Manager', function() {
})
})
- describe('#setMsgStatusApproved', function() {
- it('sets the Msg status to approved', function() {
+ describe('#setMsgStatusApproved', function () {
+ it('sets the Msg status to approved', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.setMsgStatusApproved(1)
@@ -45,8 +42,8 @@ describe('Transaction Manager', function() {
})
})
- describe('#rejectMsg', function() {
- it('sets the Msg status to rejected', function() {
+ describe('#rejectMsg', function () {
+ it('sets the Msg status to rejected', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.rejectMsg(1)
@@ -57,8 +54,8 @@ describe('Transaction Manager', function() {
})
})
- describe('#_updateMsg', function() {
- it('replaces the Msg with the same id', function() {
+ describe('#_updateMsg', function () {
+ it('replaces the Msg with the same id', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
@@ -67,19 +64,19 @@ describe('Transaction Manager', function() {
})
})
- describe('#getUnapprovedMsgs', function() {
- it('returns unapproved Msgs in a hash', function() {
+ describe('#getUnapprovedMsgs', function () {
+ it('returns unapproved Msgs in a hash', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
- let result = messageManager.getUnapprovedMsgs()
+ const result = messageManager.getUnapprovedMsgs()
assert.equal(typeof result, 'object')
assert.equal(result['1'].status, 'unapproved')
assert.equal(result['2'], undefined)
})
})
- describe('#getMsg', function() {
- it('returns a Msg with the requested id', function() {
+ describe('#getMsg', function () {
+ it('returns a Msg with the requested id', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
assert.equal(messageManager.getMsg('1').status, 'unapproved')
diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js
index 78b9e9df7..3fc7f9a98 100644
--- a/test/unit/metamask-controller-test.js
+++ b/test/unit/metamask-controller-test.js
@@ -3,27 +3,127 @@ const sinon = require('sinon')
const clone = require('clone')
const MetaMaskController = require('../../app/scripts/metamask-controller')
const firstTimeState = require('../../app/scripts/first-time-state')
+const BN = require('ethereumjs-util').BN
+const GWEI_BN = new BN('1000000000')
-const STORAGE_KEY = 'metamask-config'
-
-describe('MetaMaskController', function() {
+describe('MetaMaskController', function () {
const noop = () => {}
- let controller = new MetaMaskController({
+ const metamaskController = new MetaMaskController({
showUnconfirmedMessage: noop,
unlockAccountMessage: noop,
showUnapprovedTx: noop,
+ platform: {},
+ encryptor: {
+ encrypt: function(password, object) {
+ this.object = object
+ return Promise.resolve()
+ },
+ decrypt: function () {
+ return Promise.resolve(this.object)
+ }
+ },
// initial state
initState: clone(firstTimeState),
})
- beforeEach(function() {
+ beforeEach(function () {
// sinon allows stubbing methods that are easily verified
this.sinon = sinon.sandbox.create()
})
- afterEach(function() {
+ afterEach(function () {
// sinon requires cleanup otherwise it will overwrite context
this.sinon.restore()
})
-}) \ No newline at end of file
+ describe('Metamask Controller', function () {
+ assert(metamaskController)
+
+ beforeEach(function () {
+ sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
+ sinon.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
+ })
+
+ afterEach(function () {
+ metamaskController.keyringController.createNewVaultAndKeychain.restore()
+ metamaskController.keyringController.createNewVaultAndRestore.restore()
+ })
+
+ describe('#getGasPrice', function () {
+ it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
+ const realRecentBlocksController = metamaskController.recentBlocksController
+ metamaskController.recentBlocksController = {
+ store: {
+ getState: () => {
+ return {
+ recentBlocks: [
+ { gasPrices: [ '0x3b9aca00', '0x174876e800'] },
+ { gasPrices: [ '0x3b9aca00', '0x174876e800'] },
+ { gasPrices: [ '0x174876e800', '0x174876e800' ]},
+ { gasPrices: [ '0x174876e800', '0x174876e800' ]},
+ ]
+ }
+ }
+ }
+ }
+
+ const gasPrice = metamaskController.getGasPrice()
+ assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price')
+
+ metamaskController.recentBlocksController = realRecentBlocksController
+ })
+
+ it('gives the 1 gwei price if no blocks have been seen.', async function () {
+ const realRecentBlocksController = metamaskController.recentBlocksController
+ metamaskController.recentBlocksController = {
+ store: {
+ getState: () => {
+ return {
+ recentBlocks: []
+ }
+ }
+ }
+ }
+
+ const gasPrice = metamaskController.getGasPrice()
+ assert.equal(gasPrice, '0x' + GWEI_BN.toString(16), 'defaults to 1 gwei')
+
+ metamaskController.recentBlocksController = realRecentBlocksController
+ })
+
+ })
+
+ describe('#createNewVaultAndKeychain', function () {
+ it('can only create new vault on keyringController once', async function () {
+ const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
+
+
+ const password = 'a-fake-password'
+
+ const first = await metamaskController.createNewVaultAndKeychain(password)
+ const second = await metamaskController.createNewVaultAndKeychain(password)
+
+ assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce)
+
+ selectStub.reset()
+ })
+ })
+
+ describe('#createNewVaultAndRestore', function () {
+ it('should be able to call newVaultAndRestore despite a mistake.', async function () {
+ // const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
+
+ const password = 'what-what-what'
+ const wrongSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadiu'
+ const rightSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
+ const first = await metamaskController.createNewVaultAndRestore(password, wrongSeed)
+ .catch((e) => {
+ return
+ })
+ const second = await metamaskController.createNewVaultAndRestore(password, rightSeed)
+
+ assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
+ })
+ })
+ })
+})
diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js
index ccd1477b0..5bad25a45 100644
--- a/test/unit/migrations-test.js
+++ b/test/unit/migrations-test.js
@@ -3,7 +3,7 @@ const path = require('path')
const wallet1 = require(path.join('..', 'lib', 'migrations', '001.json'))
const vault4 = require(path.join('..', 'lib', 'migrations', '004.json'))
-let vault5, vault6, vault7, vault8, vault9, vault10, vault11
+let vault5, vault6, vault7, vault8, vault9 // vault10, vault11
const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002'))
const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003'))
@@ -16,6 +16,7 @@ const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations',
const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010'))
const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011'))
const migration12 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '012'))
+const migration13 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '013'))
const oldTestRpc = 'https://rawtestrpc.metamask.io/'
@@ -23,7 +24,6 @@ const newTestRpc = 'https://testrpc.metamask.io/'
describe('wallet1 is migrated successfully', () => {
it('should convert providers', () => {
-
wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null }
return migration2.migrate(wallet1)
@@ -98,7 +98,11 @@ describe('wallet1 is migrated successfully', () => {
}).then((twelfthResult) => {
assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.')
assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.')
- })
+ assert.equal(twelfthResult.data.config.provider.type, 'testnet', 'network is originally testnet.')
+ return migration13.migrate(twelfthResult)
+ }).then((thirteenthResult) => {
+ assert.equal(thirteenthResult.data.config.provider.type, 'ropsten', 'network has been changed to ropsten.')
+ })
})
})
diff --git a/test/unit/migrations/021-test.js b/test/unit/migrations/021-test.js
new file mode 100644
index 000000000..458e9b4b5
--- /dev/null
+++ b/test/unit/migrations/021-test.js
@@ -0,0 +1,16 @@
+const assert = require('assert')
+
+const wallet2 = require('../../lib/migrations/002.json')
+const migration21 = require('../../../app/scripts/migrations/021')
+
+describe('wallet2 is migrated successfully with out the BlacklistController', () => {
+ it('should delete BlacklistController key', (done) => {
+ migration21.migrate(wallet2)
+ .then((migratedData) => {
+ assert.equal(migratedData.meta.version, 21)
+ assert(!migratedData.data.BlacklistController)
+ assert(!migratedData.data.RecentBlocks)
+ done()
+ }).catch(done)
+ })
+})
diff --git a/test/unit/migrator-test.js b/test/unit/migrator-test.js
new file mode 100644
index 000000000..16066fefe
--- /dev/null
+++ b/test/unit/migrator-test.js
@@ -0,0 +1,41 @@
+const assert = require('assert')
+const clone = require('clone')
+const Migrator = require('../../app/scripts/lib/migrator/')
+const migrations = [
+ {
+ version: 1,
+ migrate: (data) => {
+ // clone the data just like we do in migrations
+ const clonedData = clone(data)
+ clonedData.meta.version = 1
+ return Promise.resolve(clonedData)
+ },
+ },
+ {
+ version: 2,
+ migrate: (data) => {
+ const clonedData = clone(data)
+ clonedData.meta.version = 2
+ return Promise.resolve(clonedData)
+ },
+ },
+ {
+ version: 3,
+ migrate: (data) => {
+ const clonedData = clone(data)
+ clonedData.meta.version = 3
+ return Promise.resolve(clonedData)
+ },
+ },
+]
+const versionedData = {meta: {version: 0}, data: {hello: 'world'}}
+describe('Migrator', () => {
+ const migrator = new Migrator({ migrations })
+ it('migratedData version should be version 3', (done) => {
+ migrator.migrateData(versionedData)
+ .then((migratedData) => {
+ assert.equal(migratedData.meta.version, migrations[2].version)
+ done()
+ }).catch(done)
+ })
+})
diff --git a/test/unit/nameForAccount_test.js b/test/unit/nameForAccount_test.js
index 6839d40f8..e7c0b18b4 100644
--- a/test/unit/nameForAccount_test.js
+++ b/test/unit/nameForAccount_test.js
@@ -4,25 +4,23 @@ var sinon = require('sinon')
var path = require('path')
var contractNamer = require(path.join(__dirname, '..', '..', 'ui', 'lib', 'contract-namer.js'))
-describe('contractNamer', function() {
-
- beforeEach(function() {
+describe('contractNamer', function () {
+ beforeEach(function () {
this.sinon = sinon.sandbox.create()
})
- afterEach(function() {
+ afterEach(function () {
this.sinon.restore()
})
- describe('naming a contract', function() {
-
- it('should return nothing for an unknown random account', function() {
+ describe('naming a contract', function () {
+ it('should return nothing for an unknown random account', function () {
const input = '0x2386F26FC10000'
const output = contractNamer(input)
assert.deepEqual(output, null)
})
- it('should accept identities as an optional second parameter', function() {
+ it('should accept identities as an optional second parameter', function () {
const input = '0x2386F26FC10000'.toLowerCase()
const expected = 'bar'
const identities = {}
@@ -31,7 +29,7 @@ describe('contractNamer', function() {
assert.deepEqual(output, expected)
})
- it('should check for identities case insensitively', function() {
+ it('should check for identities case insensitively', function () {
const input = '0x2386F26FC10000'.toLowerCase()
const expected = 'bar'
const identities = {}
@@ -39,6 +37,5 @@ describe('contractNamer', function() {
const output = contractNamer(input.toUpperCase(), identities)
assert.deepEqual(output, expected)
})
-
})
})
diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js
new file mode 100644
index 000000000..0b3b5adeb
--- /dev/null
+++ b/test/unit/network-contoller-test.js
@@ -0,0 +1,84 @@
+const assert = require('assert')
+const NetworkController = require('../../app/scripts/controllers/network')
+
+describe('# Network Controller', function () {
+ let networkController
+ const networkControllerProviderInit = {
+ getAccounts: () => {},
+ }
+
+ beforeEach(function () {
+ networkController = new NetworkController({
+ provider: {
+ type: 'rinkeby',
+ },
+ })
+
+ networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor)
+ })
+ describe('network', function () {
+ describe('#provider', function () {
+ it('provider should be updatable without reassignment', function () {
+ networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor)
+ const proxy = networkController._proxy
+ proxy.setTarget({ test: true, on: () => {} })
+ assert.ok(proxy.test)
+ })
+ })
+ describe('#getNetworkState', function () {
+ it('should return loading when new', function () {
+ const networkState = networkController.getNetworkState()
+ assert.equal(networkState, 'loading', 'network is loading')
+ })
+ })
+
+ describe('#setNetworkState', function () {
+ it('should update the network', function () {
+ networkController.setNetworkState(1)
+ const networkState = networkController.getNetworkState()
+ assert.equal(networkState, 1, 'network is 1')
+ })
+ })
+
+ describe('#getRpcAddressForType', function () {
+ it('should return the right rpc address', function () {
+ const rpcTarget = networkController.getRpcAddressForType('mainnet')
+ assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
+ })
+ })
+ describe('#setProviderType', function () {
+ it('should update provider.type', function () {
+ networkController.setProviderType('mainnet')
+ const type = networkController.getProviderConfig().type
+ assert.equal(type, 'mainnet', 'provider type is updated')
+ })
+ it('should set the network to loading', function () {
+ networkController.setProviderType('mainnet')
+ const loading = networkController.isNetworkLoading()
+ assert.ok(loading, 'network is loading')
+ })
+ it('should set the right rpcTarget', function () {
+ networkController.setProviderType('mainnet')
+ const rpcTarget = networkController.getProviderConfig().rpcTarget
+ assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
+ })
+ })
+ })
+})
+
+function dummyProviderConstructor() {
+ return {
+ // provider
+ sendAsync: noop,
+ // block tracker
+ _blockTracker: {},
+ start: noop,
+ stop: noop,
+ on: noop,
+ addListener: noop,
+ once: noop,
+ removeAllListeners: noop,
+ }
+}
+
+function noop() {} \ No newline at end of file
diff --git a/test/unit/nodeify-test.js b/test/unit/nodeify-test.js
index a14d34338..c7b127889 100644
--- a/test/unit/nodeify-test.js
+++ b/test/unit/nodeify-test.js
@@ -1,22 +1,30 @@
const assert = require('assert')
const nodeify = require('../../app/scripts/lib/nodeify')
-describe('nodeify', function() {
-
+describe('nodeify', function () {
var obj = {
foo: 'bar',
promiseFunc: function (a) {
var solution = this.foo + a
return Promise.resolve(solution)
- }
+ },
}
- it('should retain original context', function(done) {
- var nodified = nodeify(obj.promiseFunc).bind(obj)
+ it('should retain original context', function (done) {
+ var nodified = nodeify(obj.promiseFunc, obj)
nodified('baz', function (err, res) {
assert.equal(res, 'barbaz')
done()
})
})
+ it('should allow the last argument to not be a function', function (done) {
+ const nodified = nodeify(obj.promiseFunc, obj)
+ try {
+ nodified('baz')
+ done()
+ } catch (err) {
+ done(new Error('should not have thrown if the last argument is not a function'))
+ }
+ })
})
diff --git a/test/unit/nonce-tracker-test.js b/test/unit/nonce-tracker-test.js
new file mode 100644
index 000000000..8970cf84d
--- /dev/null
+++ b/test/unit/nonce-tracker-test.js
@@ -0,0 +1,203 @@
+const assert = require('assert')
+const NonceTracker = require('../../app/scripts/lib/nonce-tracker')
+const MockTxGen = require('../lib/mock-tx-gen')
+let providerResultStub = {}
+
+describe('Nonce Tracker', function () {
+ let nonceTracker, provider
+ let getPendingTransactions, pendingTxs
+ let getConfirmedTransactions, confirmedTxs
+
+ describe('#getNonceLock', function () {
+
+ describe('with 3 confirmed and 1 pending', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 })
+ pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 })
+ nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1')
+ })
+
+ it('should return 4', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '4', `nonce should be 4 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+
+ it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '4', 'nonce should be 4')
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('with no previous txs', function () {
+ beforeEach(function () {
+ nonceTracker = generateNonceTrackerWith([], [])
+ })
+
+ it('should return 0', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 returned ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('with multiple previous txs with same nonce', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 1 })
+ pendingTxs = txGen.generate({
+ status: 'submitted',
+ txParams: { nonce: '0x01' },
+ }, { count: 5 })
+
+ nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x0')
+ })
+
+ it('should return nonce after those', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('when local confirmed count is higher than network nonce', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 })
+ nonceTracker = generateNonceTrackerWith([], confirmedTxs, '0x1')
+ })
+
+ it('should return nonce after those', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '3', `nonce should be 3 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('when local pending count is higher than other metrics', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 })
+ nonceTracker = generateNonceTrackerWith(pendingTxs, [])
+ })
+
+ it('should return nonce after those', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('when provider nonce is higher than other metrics', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 })
+ nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x05')
+ })
+
+ it('should return nonce after those', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('when there are some pending nonces below the remote one and some over.', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 })
+ nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x03')
+ })
+
+ it('should return nonce after those', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('when there are pending nonces non sequentially over the network nonce.', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ txGen.generate({ status: 'submitted' }, { count: 5 })
+ // 5 over that number
+ pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 })
+ nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x00')
+ })
+
+ it('should return nonce after network nonce', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('When all three return different values', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 })
+ const pendingTxs = txGen.generate({
+ status: 'submitted',
+ nonce: 100,
+ }, { count: 1 })
+ // 0x32 is 50 in hex:
+ nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x32')
+ })
+
+ it('should return nonce after network nonce', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '50', `nonce should be 50 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+
+ describe('Faq issue 67', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 })
+ const pendingTxs = txGen.generate({
+ status: 'submitted',
+ }, { count: 10 })
+ // 0x40 is 64 in hex:
+ nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40')
+ })
+
+ it('should return nonce after network nonce', async function () {
+ this.timeout(15000)
+ const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
+ assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`)
+ await nonceLock.releaseLock()
+ })
+ })
+ })
+})
+
+function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
+ const getPendingTransactions = () => pending
+ const getConfirmedTransactions = () => confirmed
+ providerResultStub.result = providerStub
+ const provider = {
+ sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
+ _blockTracker: {
+ getCurrentBlock: () => '0x11b568',
+ },
+ }
+ return new NonceTracker({
+ provider,
+ getPendingTransactions,
+ getConfirmedTransactions,
+ })
+}
+
diff --git a/test/unit/notice-controller-test.js b/test/unit/notice-controller-test.js
index ea37108bb..09eeda15c 100644
--- a/test/unit/notice-controller-test.js
+++ b/test/unit/notice-controller-test.js
@@ -1,42 +1,38 @@
const assert = require('assert')
-const extend = require('xtend')
-const rp = require('request-promise')
-const nock = require('nock')
const configManagerGen = require('../lib/mock-config-manager')
const NoticeController = require('../../app/scripts/notice-controller')
-const STORAGE_KEY = 'metamask-persistence-key'
-describe('notice-controller', function() {
+describe('notice-controller', function () {
var noticeController
- beforeEach(function() {
+ beforeEach(function () {
// simple localStorage polyfill
- let configManager = configManagerGen()
+ const configManager = configManagerGen()
noticeController = new NoticeController({
configManager: configManager,
})
})
- describe('notices', function() {
- describe('#getNoticesList', function() {
- it('should return an empty array when new', function(done) {
- var testList = [{
- id:0,
- read:false,
- title:"Futuristic Notice"
- }]
+ describe('notices', function () {
+ describe('#getNoticesList', function () {
+ it('should return an empty array when new', function (done) {
+ // const testList = [{
+ // id: 0,
+ // read: false,
+ // title: 'Futuristic Notice',
+ // }]
var result = noticeController.getNoticesList()
assert.equal(result.length, 0)
done()
})
})
- describe('#setNoticesList', function() {
+ describe('#setNoticesList', function () {
it('should set data appropriately', function (done) {
var testList = [{
- id:0,
- read:false,
- title:"Futuristic Notice"
+ id: 0,
+ read: false,
+ title: 'Futuristic Notice',
}]
noticeController.setNoticesList(testList)
var testListId = noticeController.getNoticesList()[0].id
@@ -45,12 +41,12 @@ describe('notice-controller', function() {
})
})
- describe('#updateNoticeslist', function() {
- it('should integrate the latest changes from the source', function(done) {
+ describe('#updateNoticeslist', function () {
+ it('should integrate the latest changes from the source', function (done) {
var testList = [{
- id:55,
- read:false,
- title:"Futuristic Notice"
+ id: 55,
+ read: false,
+ title: 'Futuristic Notice',
}]
noticeController.setNoticesList(testList)
noticeController.updateNoticesList().then(() => {
@@ -62,14 +58,14 @@ describe('notice-controller', function() {
})
it('should not overwrite any existing fields', function (done) {
var testList = [{
- id:0,
- read:false,
- title:"Futuristic Notice"
+ id: 0,
+ read: false,
+ title: 'Futuristic Notice',
}]
noticeController.setNoticesList(testList)
var newList = noticeController.getNoticesList()
assert.equal(newList[0].id, 0)
- assert.equal(newList[0].title, "Futuristic Notice")
+ assert.equal(newList[0].title, 'Futuristic Notice')
assert.equal(newList.length, 1)
done()
})
@@ -78,9 +74,9 @@ describe('notice-controller', function() {
describe('#markNoticeRead', function () {
it('should mark a notice as read', function (done) {
var testList = [{
- id:0,
- read:false,
- title:"Futuristic Notice"
+ id: 0,
+ read: false,
+ title: 'Futuristic Notice',
}]
noticeController.setNoticesList(testList)
noticeController.markNoticeRead(testList[0])
@@ -93,9 +89,9 @@ describe('notice-controller', function() {
describe('#getLatestUnreadNotice', function () {
it('should retrieve the latest unread notice', function (done) {
var testList = [
- {id:0,read:true,title:"Past Notice"},
- {id:1,read:false,title:"Current Notice"},
- {id:2,read:false,title:"Future Notice"},
+ {id: 0, read: true, title: 'Past Notice'},
+ {id: 1, read: false, title: 'Current Notice'},
+ {id: 2, read: false, title: 'Future Notice'},
]
noticeController.setNoticesList(testList)
var latestUnread = noticeController.getLatestUnreadNotice()
@@ -104,9 +100,9 @@ describe('notice-controller', function() {
})
it('should return undefined if no unread notices exist.', function (done) {
var testList = [
- {id:0,read:true,title:"Past Notice"},
- {id:1,read:true,title:"Current Notice"},
- {id:2,read:true,title:"Future Notice"},
+ {id: 0, read: true, title: 'Past Notice'},
+ {id: 1, read: true, title: 'Current Notice'},
+ {id: 2, read: true, title: 'Future Notice'},
]
noticeController.setNoticesList(testList)
var latestUnread = noticeController.getLatestUnreadNotice()
@@ -115,5 +111,4 @@ describe('notice-controller', function() {
})
})
})
-
})
diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js
new file mode 100644
index 000000000..dc4c1c3e4
--- /dev/null
+++ b/test/unit/pending-balance-test.js
@@ -0,0 +1,93 @@
+const assert = require('assert')
+const PendingBalanceCalculator = require('../../app/scripts/lib/pending-balance-calculator')
+const MockTxGen = require('../lib/mock-tx-gen')
+const BN = require('ethereumjs-util').BN
+let providerResultStub = {}
+
+const zeroBn = new BN(0)
+const etherBn = new BN(String(1e18))
+const ether = '0x' + etherBn.toString(16)
+
+describe('PendingBalanceCalculator', function () {
+ let balanceCalculator, pendingTxs
+
+ describe('#calculateMaxCost(tx)', function () {
+ it('returns a BN for a given tx value', function () {
+ const txGen = new MockTxGen()
+ pendingTxs = txGen.generate({
+ status: 'submitted',
+ txParams: {
+ value: ether,
+ gasPrice: '0x0',
+ gas: '0x0',
+ }
+ }, { count: 1 })
+
+ const balanceCalculator = generateBalanceCalcWith([], zeroBn)
+ const result = balanceCalculator.calculateMaxCost(pendingTxs[0])
+ assert.equal(result.toString(), etherBn.toString(), 'computes one ether')
+ })
+
+ it('calculates gas costs as well', function () {
+ const txGen = new MockTxGen()
+ pendingTxs = txGen.generate({
+ status: 'submitted',
+ txParams: {
+ value: '0x0',
+ gasPrice: '0x2',
+ gas: '0x3',
+ }
+ }, { count: 1 })
+
+ const balanceCalculator = generateBalanceCalcWith([], zeroBn)
+ const result = balanceCalculator.calculateMaxCost(pendingTxs[0])
+ assert.equal(result.toString(), '6', 'computes 6 wei of gas')
+ })
+ })
+
+ describe('if you have no pending txs and one ether', function () {
+
+ beforeEach(function () {
+ balanceCalculator = generateBalanceCalcWith([], etherBn)
+ })
+
+ it('returns the network balance', async function () {
+ const result = await balanceCalculator.getBalance()
+ assert.equal(result, ether, `gave ${result} needed ${ether}`)
+ })
+ })
+
+ describe('if you have a one ether pending tx and one ether', function () {
+ beforeEach(function () {
+ const txGen = new MockTxGen()
+ pendingTxs = txGen.generate({
+ status: 'submitted',
+ txParams: {
+ value: ether,
+ gasPrice: '0x0',
+ gas: '0x0',
+ }
+ }, { count: 1 })
+
+ balanceCalculator = generateBalanceCalcWith(pendingTxs, etherBn)
+ })
+
+ it('returns the subtracted result', async function () {
+ const result = await balanceCalculator.getBalance()
+ assert.equal(result, '0x0', `gave ${result} needed '0x0'`)
+ return true
+ })
+
+ })
+})
+
+function generateBalanceCalcWith (transactions, providerStub = zeroBn) {
+ const getPendingTransactions = async () => transactions
+ const getBalance = async () => providerStub
+
+ return new PendingBalanceCalculator({
+ getBalance,
+ getPendingTransactions,
+ })
+}
+
diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js
new file mode 100644
index 000000000..f0b4e3bfc
--- /dev/null
+++ b/test/unit/pending-tx-test.js
@@ -0,0 +1,402 @@
+const assert = require('assert')
+const ethUtil = require('ethereumjs-util')
+const EthTx = require('ethereumjs-tx')
+const ObservableStore = require('obs-store')
+const clone = require('clone')
+const { createTestProviderTools } = require('../stub/provider')
+const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker')
+const MockTxGen = require('../lib/mock-tx-gen')
+const sinon = require('sinon')
+const noop = () => true
+const currentNetworkId = 42
+const otherNetworkId = 36
+const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
+
+
+describe('PendingTransactionTracker', function () {
+ let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub,
+ provider, txMeta3, txList, knownErrors
+ this.timeout(10000)
+ beforeEach(function () {
+ txMeta = {
+ id: 1,
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ status: 'signed',
+ txParams: {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ nonce: '0x1',
+ value: '0xfffff',
+ },
+ rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
+ }
+ txMetaNoHash = {
+ id: 2,
+ status: 'signed',
+ txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'},
+ }
+ txMetaNoRawTx = {
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ status: 'signed',
+ txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'},
+ }
+ providerResultStub = {}
+ provider = createTestProviderTools({ scaffold: providerResultStub }).provider
+
+ pendingTxTracker = new PendingTransactionTracker({
+ provider,
+ nonceTracker: {
+ getGlobalLock: async () => {
+ return { releaseLock: () => {} }
+ }
+ },
+ getPendingTransactions: () => {return []},
+ getCompletedTransactions: () => {return []},
+ publishTransaction: () => {},
+ })
+ })
+
+ describe('_checkPendingTx state management', function () {
+ let stub
+
+ afterEach(function () {
+ if (stub) {
+ stub.restore()
+ }
+ })
+
+ it('should become failed if another tx with the same nonce succeeds', async function () {
+
+ // SETUP
+ const txGen = new MockTxGen()
+
+ txGen.generate({
+ id: '456',
+ value: '0x01',
+ hash: '0xbad',
+ status: 'confirmed',
+ nonce: '0x01',
+ }, { count: 1 })
+
+ const pending = txGen.generate({
+ id: '123',
+ value: '0x02',
+ hash: '0xfad',
+ status: 'submitted',
+ nonce: '0x01',
+ }, { count: 1 })[0]
+
+ stub = sinon.stub(pendingTxTracker, 'getCompletedTransactions')
+ .returns(txGen.txs)
+
+ // THE EXPECTATION
+ const spy = sinon.spy()
+ pendingTxTracker.on('tx:failed', (txId, err) => {
+ assert.equal(txId, pending.id, 'should fail the pending tx')
+ assert.equal(err.name, 'NonceTakenErr', 'should emit a nonce taken error.')
+ spy(txId, err)
+ })
+
+ // THE METHOD
+ await pendingTxTracker._checkPendingTx(pending)
+
+ // THE ASSERTION
+ assert.ok(spy.calledWith(pending.id), 'tx failed should be emitted')
+ })
+ })
+
+ describe('#checkForTxInBlock', function () {
+ it('should return if no pending transactions', function () {
+ // throw a type error if it trys to do anything on the block
+ // thus failing the test
+ const block = Proxy.revocable({}, {}).revoke()
+ pendingTxTracker.checkForTxInBlock(block)
+ })
+ it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
+ const block = Proxy.revocable({}, {}).revoke()
+ pendingTxTracker.getPendingTransactions = () => [txMetaNoHash]
+ pendingTxTracker.once('tx:failed', (txId, err) => {
+ assert(txId, txMetaNoHash.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker.checkForTxInBlock(block)
+ })
+ it('should emit \'txConfirmed\' if the tx is in the block', function (done) {
+ const block = { transactions: [txMeta]}
+ pendingTxTracker.getPendingTransactions = () => [txMeta]
+ pendingTxTracker.once('tx:confirmed', (txId) => {
+ assert(txId, txMeta.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
+ pendingTxTracker.checkForTxInBlock(block)
+ })
+ })
+ describe('#queryPendingTxs', function () {
+ it('should call #_checkPendingTxs if their is no oldBlock', function (done) {
+ let newBlock, oldBlock
+ newBlock = { number: '0x01' }
+ pendingTxTracker._checkPendingTxs = done
+ pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
+ })
+ it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) {
+ let newBlock, oldBlock
+ oldBlock = { number: '0x01' }
+ newBlock = { number: '0x03' }
+ pendingTxTracker._checkPendingTxs = done
+ pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
+ })
+ it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) {
+ let newBlock, oldBlock
+ oldBlock = { number: '0x1' }
+ newBlock = { number: '0x2' }
+ pendingTxTracker._checkPendingTxs = () => {
+ const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less')
+ done(err)
+ }
+ pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
+ done()
+ })
+ })
+
+ describe('#_checkPendingTx', function () {
+ it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
+ pendingTxTracker.once('tx:failed', (txId, err) => {
+ assert(txId, txMetaNoHash.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker._checkPendingTx(txMetaNoHash)
+ })
+
+ it('should should return if query does not return txParams', function () {
+ providerResultStub.eth_getTransactionByHash = null
+ pendingTxTracker._checkPendingTx(txMeta)
+ })
+
+ it('should emit \'txConfirmed\'', function (done) {
+ providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'}
+ pendingTxTracker.once('tx:confirmed', (txId) => {
+ assert(txId, txMeta.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
+ pendingTxTracker._checkPendingTx(txMeta)
+ })
+ })
+
+ describe('#_checkPendingTxs', function () {
+ beforeEach(function () {
+ const txMeta2 = txMeta3 = txMeta
+ txMeta2.id = 2
+ txMeta3.id = 3
+ txList = [txMeta, txMeta2, txMeta3].map((tx) => {
+ tx.processed = new Promise ((resolve) => { tx.resolve = resolve })
+ return tx
+ })
+ })
+
+ it('should warp all txMeta\'s in #_checkPendingTx', function (done) {
+ pendingTxTracker.getPendingTransactions = () => txList
+ pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
+ const list = txList.map
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+
+ pendingTxTracker._checkPendingTxs()
+ })
+ })
+
+ describe('#resubmitPendingTxs', function () {
+ const blockStub = { number: '0x0' };
+ beforeEach(function () {
+ const txMeta2 = txMeta3 = txMeta
+ txList = [txMeta, txMeta2, txMeta3].map((tx) => {
+ tx.processed = new Promise ((resolve) => { tx.resolve = resolve })
+ return tx
+ })
+ })
+
+ it('should return if no pending transactions', function () {
+ pendingTxTracker.resubmitPendingTxs()
+ })
+ it('should call #_resubmitTx for all pending tx\'s', function (done) {
+ pendingTxTracker.getPendingTransactions = () => txList
+ pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) }
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+ pendingTxTracker.resubmitPendingTxs(blockStub)
+ })
+ it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) {
+ knownErrors =[
+ // geth
+ ' Replacement transaction Underpriced ',
+ ' known transaction',
+ // parity
+ 'Gas price too low to replace ',
+ ' transaction with the sAme hash was already imported',
+ // other
+ ' gateway timeout',
+ ' noncE too low ',
+ ]
+ const enoughForAllErrors = txList.concat(txList)
+
+ pendingTxTracker.on('tx:failed', (_, err) => done(err))
+
+ pendingTxTracker.getPendingTransactions = () => enoughForAllErrors
+ pendingTxTracker._resubmitTx = async (tx) => {
+ tx.resolve()
+ throw new Error(knownErrors.pop())
+ }
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+
+ pendingTxTracker.resubmitPendingTxs(blockStub)
+ })
+ it('should emit \'tx:warning\' if it encountered a real error', function (done) {
+ pendingTxTracker.once('tx:warning', (txMeta, err) => {
+ if (err.message === 'im some real error') {
+ const matchingTx = txList.find(tx => tx.id === txMeta.id)
+ matchingTx.resolve()
+ } else {
+ done(err)
+ }
+ })
+
+ pendingTxTracker.getPendingTransactions = () => txList
+ pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') }
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+
+ pendingTxTracker.resubmitPendingTxs(blockStub)
+ })
+ })
+ describe('#_resubmitTx', function () {
+ const mockFirstRetryBlockNumber = '0x1'
+ let txMetaToTestExponentialBackoff
+
+ beforeEach(() => {
+ pendingTxTracker.getBalance = (address) => {
+ assert.equal(address, txMeta.txParams.from, 'Should pass the address')
+ return enoughBalance
+ }
+ pendingTxTracker.publishTransaction = async (rawTx) => {
+ assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx')
+ }
+ sinon.spy(pendingTxTracker, 'publishTransaction')
+
+ txMetaToTestExponentialBackoff = Object.assign({}, txMeta, {
+ retryCount: 4,
+ firstRetryBlockNumber: mockFirstRetryBlockNumber,
+ })
+ })
+
+ afterEach(() => {
+ pendingTxTracker.publishTransaction.reset()
+ })
+
+ it('should publish the transaction', function (done) {
+ const enoughBalance = '0x100000'
+
+ // Stubbing out current account state:
+ // Adding the fake tx:
+ pendingTxTracker._resubmitTx(txMeta)
+ .then(() => done())
+ .catch((err) => {
+ assert.ifError(err, 'should not throw an error')
+ done(err)
+ })
+
+ assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction')
+ })
+
+ it('should not publish the transaction if the limit of retries has been exceeded', function (done) {
+ const enoughBalance = '0x100000'
+ const mockLatestBlockNumber = '0x5'
+
+ pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber)
+ .then(() => done())
+ .catch((err) => {
+ assert.ifError(err, 'should not throw an error')
+ done(err)
+ })
+
+ assert.equal(pendingTxTracker.publishTransaction.callCount, 0, 'Should NOT call publish transaction')
+ })
+
+ it('should publish the transaction if the number of blocks since last retry exceeds the last set limit', function (done) {
+ const enoughBalance = '0x100000'
+ const mockLatestBlockNumber = '0x11'
+
+ pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber)
+ .then(() => done())
+ .catch((err) => {
+ assert.ifError(err, 'should not throw an error')
+ done(err)
+ })
+
+ assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction')
+ })
+ })
+
+ describe('#_checkIfNonceIsTaken', function () {
+ beforeEach ( function () {
+ let confirmedTxList = [{
+ id: 1,
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ status: 'confirmed',
+ txParams: {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ nonce: '0x1',
+ value: '0xfffff',
+ },
+ rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
+ }, {
+ id: 2,
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ status: 'confirmed',
+ txParams: {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ nonce: '0x2',
+ value: '0xfffff',
+ },
+ rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
+ }]
+ pendingTxTracker.getCompletedTransactions = (address) => {
+ if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken')
+ return confirmedTxList
+ }
+ })
+
+ it('should return false if nonce has not been taken', function (done) {
+ pendingTxTracker._checkIfNonceIsTaken({
+ txParams: {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ nonce: '0x3',
+ value: '0xfffff',
+ },
+ })
+ .then((taken) => {
+ assert.ok(!taken)
+ done()
+ })
+ .catch(done)
+ })
+
+ it('should return true if nonce has been taken', function (done) {
+ pendingTxTracker._checkIfNonceIsTaken({
+ txParams: {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ nonce: '0x2',
+ value: '0xfffff',
+ },
+ }).then((taken) => {
+ assert.ok(taken)
+ done()
+ })
+ .catch(done)
+ })
+ })
+})
diff --git a/test/unit/personal-message-manager-test.js b/test/unit/personal-message-manager-test.js
index f2c01392c..ec2f9a4d1 100644
--- a/test/unit/personal-message-manager-test.js
+++ b/test/unit/personal-message-manager-test.js
@@ -1,29 +1,27 @@
const assert = require('assert')
-const extend = require('xtend')
-const EventEmitter = require('events')
const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager')
-describe('Personal Message Manager', function() {
+describe('Personal Message Manager', function () {
let messageManager
- beforeEach(function() {
+ beforeEach(function () {
messageManager = new PersonalMessageManager()
})
- describe('#getMsgList', function() {
- it('when new should return empty array', function() {
+ describe('#getMsgList', function () {
+ it('when new should return empty array', function () {
var result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 0)
})
- it('should also return transactions from local storage if any', function() {
+ it('should also return transactions from local storage if any', function () {
})
})
- describe('#addMsg', function() {
- it('adds a Msg returned in getMsgList', function() {
+ describe('#addMsg', function () {
+ it('adds a Msg returned in getMsgList', function () {
var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
var result = messageManager.messages
@@ -33,8 +31,8 @@ describe('Personal Message Manager', function() {
})
})
- describe('#setMsgStatusApproved', function() {
- it('sets the Msg status to approved', function() {
+ describe('#setMsgStatusApproved', function () {
+ it('sets the Msg status to approved', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.setMsgStatusApproved(1)
@@ -45,8 +43,8 @@ describe('Personal Message Manager', function() {
})
})
- describe('#rejectMsg', function() {
- it('sets the Msg status to rejected', function() {
+ describe('#rejectMsg', function () {
+ it('sets the Msg status to rejected', function () {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.rejectMsg(1)
@@ -57,8 +55,8 @@ describe('Personal Message Manager', function() {
})
})
- describe('#_updateMsg', function() {
- it('replaces the Msg with the same id', function() {
+ describe('#_updateMsg', function () {
+ it('replaces the Msg with the same id', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
@@ -67,19 +65,19 @@ describe('Personal Message Manager', function() {
})
})
- describe('#getUnapprovedMsgs', function() {
- it('returns unapproved Msgs in a hash', function() {
+ describe('#getUnapprovedMsgs', function () {
+ it('returns unapproved Msgs in a hash', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
- let result = messageManager.getUnapprovedMsgs()
+ const result = messageManager.getUnapprovedMsgs()
assert.equal(typeof result, 'object')
assert.equal(result['1'].status, 'unapproved')
assert.equal(result['2'], undefined)
})
})
- describe('#getMsg', function() {
- it('returns a Msg with the requested id', function() {
+ describe('#getMsg', function () {
+ it('returns a Msg with the requested id', function () {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
assert.equal(messageManager.getMsg('1').status, 'unapproved')
@@ -87,24 +85,23 @@ describe('Personal Message Manager', function() {
})
})
- describe('#normalizeMsgData', function() {
- it('converts text to a utf8 hex string', function() {
+ describe('#normalizeMsgData', function () {
+ it('converts text to a utf8 hex string', function () {
var input = 'hello'
var output = messageManager.normalizeMsgData(input)
assert.equal(output, '0x68656c6c6f', 'predictably hex encoded')
})
- it('tolerates a hex prefix', function() {
+ it('tolerates a hex prefix', function () {
var input = '0x12'
var output = messageManager.normalizeMsgData(input)
assert.equal(output, '0x12', 'un modified')
})
- it('tolerates normal hex', function() {
+ it('tolerates normal hex', function () {
var input = '12'
var output = messageManager.normalizeMsgData(input)
assert.equal(output, '0x12', 'adds prefix')
})
})
-
})
diff --git a/test/unit/preferences-controller-test.js b/test/unit/preferences-controller-test.js
new file mode 100644
index 000000000..9fb5e4251
--- /dev/null
+++ b/test/unit/preferences-controller-test.js
@@ -0,0 +1,48 @@
+const assert = require('assert')
+const PreferencesController = require('../../app/scripts/controllers/preferences')
+
+describe('preferences controller', function () {
+ let preferencesController
+
+ before(() => {
+ preferencesController = new PreferencesController()
+ })
+
+ describe('addToken', function () {
+ it('should add that token to its state', async function () {
+ const address = '0xabcdef1234567'
+ const symbol = 'ABBR'
+ const decimals = 5
+
+ await preferencesController.addToken(address, symbol, decimals)
+
+ const tokens = preferencesController.getTokens()
+ assert.equal(tokens.length, 1, 'one token added')
+
+ const added = tokens[0]
+ assert.equal(added.address, address, 'set address correctly')
+ assert.equal(added.symbol, symbol, 'set symbol correctly')
+ assert.equal(added.decimals, decimals, 'set decimals correctly')
+ })
+
+ it('should allow updating a token value', async function () {
+ const address = '0xabcdef1234567'
+ const symbol = 'ABBR'
+ const decimals = 5
+
+ await preferencesController.addToken(address, symbol, decimals)
+
+ const newDecimals = 6
+ await preferencesController.addToken(address, symbol, newDecimals)
+
+ const tokens = preferencesController.getTokens()
+ assert.equal(tokens.length, 1, 'one token added')
+
+ const added = tokens[0]
+ assert.equal(added.address, address, 'set address correctly')
+ assert.equal(added.symbol, symbol, 'set symbol correctly')
+ assert.equal(added.decimals, newDecimals, 'updated decimals correctly')
+ })
+ })
+})
+
diff --git a/test/unit/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js
index b7540af08..2b7d70b2c 100644
--- a/test/unit/reducers/unlock_vault_test.js
+++ b/test/unit/reducers/unlock_vault_test.js
@@ -1,32 +1,31 @@
-var jsdom = require('mocha-jsdom')
+// var jsdom = require('mocha-jsdom')
var assert = require('assert')
-var freeze = require('deep-freeze-strict')
+// var freeze = require('deep-freeze-strict')
var path = require('path')
var sinon = require('sinon')
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
-var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
+var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
-describe('#unlockMetamask(selectedAccount)', function() {
-
- beforeEach(function() {
+describe('#unlockMetamask(selectedAccount)', function () {
+ beforeEach(function () {
// sinon allows stubbing methods that are easily verified
this.sinon = sinon.sandbox.create()
})
- afterEach(function() {
+ afterEach(function () {
// sinon requires cleanup otherwise it will overwrite context
this.sinon.restore()
})
- describe('after an error', function() {
- it('clears warning', function() {
+ describe('after an error', function () {
+ it('clears warning', function () {
const warning = 'this is the wrong warning'
const account = 'foo_account'
const initialState = {
appState: {
warning: warning,
- }
+ },
}
const resultState = reducers(initialState, actions.unlockMetamask(account))
@@ -34,14 +33,14 @@ describe('#unlockMetamask(selectedAccount)', function() {
})
})
- describe('going home after an error', function() {
- it('clears warning', function() {
+ describe('going home after an error', function () {
+ it('clears warning', function () {
const warning = 'this is the wrong warning'
- const account = 'foo_account'
+ // const account = 'foo_account'
const initialState = {
appState: {
warning: warning,
- }
+ },
}
const resultState = reducers(initialState, actions.goHome())
diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js
new file mode 100644
index 000000000..982d8c6ec
--- /dev/null
+++ b/test/unit/responsive/components/dropdown-test.js
@@ -0,0 +1,81 @@
+const assert = require('assert');
+
+const h = require('react-hyperscript');
+const sinon = require('sinon');
+const path = require('path');
+const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'dropdowns', 'index.js')).Dropdown;
+
+const { createMockStore } = require('redux-test-utils')
+const { mountWithStore } = require('../../../lib/shallow-with-store')
+
+const mockState = {
+ metamask: {
+ }
+}
+
+describe('Dropdown components', function () {
+ let onClickOutside;
+ let closeMenu;
+ let onClick;
+
+ let dropdownComponentProps = {
+ isOpen: true,
+ zIndex: 11,
+ onClickOutside,
+ style: {
+ position: 'absolute',
+ right: 0,
+ top: '36px',
+ },
+ innerStyle: {},
+ }
+
+ let dropdownComponent
+ let store
+ let component
+ beforeEach(function () {
+ onClickOutside = sinon.spy();
+ closeMenu = sinon.spy();
+ onClick = sinon.spy();
+
+ store = createMockStore(mockState)
+ component = mountWithStore(h(
+ Dropdown,
+ dropdownComponentProps,
+ [
+ h('style', `
+ .drop-menu-item:hover { background:rgb(235, 235, 235); }
+ .drop-menu-item i { margin: 11px; }
+ `),
+ h('li', {
+ closeMenu,
+ onClick,
+ }, 'Item 1'),
+ h('li', {
+ closeMenu,
+ onClick,
+ }, 'Item 2'),
+ ]
+ ), store)
+ dropdownComponent = component
+ })
+
+ it('can render two items', function () {
+ const items = dropdownComponent.find('li');
+ assert.equal(items.length, 2);
+ });
+
+ it('closes when item clicked', function() {
+ const items = dropdownComponent.find('li');
+ const node = items.at(0);
+ node.simulate('click');
+ assert.equal(node.props().closeMenu, closeMenu);
+ });
+
+ it('invokes click handler when item clicked', function() {
+ const items = dropdownComponent.find('li');
+ const node = items.at(0);
+ node.simulate('click');
+ assert.equal(onClick.calledOnce, true);
+ });
+});
diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js
new file mode 100644
index 000000000..cc99afee4
--- /dev/null
+++ b/test/unit/tx-controller-test.js
@@ -0,0 +1,413 @@
+const assert = require('assert')
+const ethUtil = require('ethereumjs-util')
+const EthTx = require('ethereumjs-tx')
+const EthjsQuery = require('ethjs-query')
+const ObservableStore = require('obs-store')
+const sinon = require('sinon')
+const TransactionController = require('../../app/scripts/controllers/transactions')
+const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
+const { createTestProviderTools } = require('../stub/provider')
+
+const noop = () => true
+const currentNetworkId = 42
+const otherNetworkId = 36
+const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
+
+
+describe('Transaction Controller', function () {
+ let txController, provider, providerResultStub, testBlockchain
+
+ beforeEach(function () {
+ providerResultStub = {
+ // 1 gwei
+ eth_gasPrice: '0x0de0b6b3a7640000',
+ // by default, all accounts are external accounts (not contracts)
+ eth_getCode: '0x',
+ }
+ const providerTools = createTestProviderTools({ scaffold: providerResultStub })
+ provider = providerTools.provider
+ testBlockchain = providerTools.testBlockchain
+
+ txController = new TransactionController({
+ provider,
+ networkStore: new ObservableStore(currentNetworkId),
+ txHistoryLimit: 10,
+ blockTracker: { getCurrentBlock: noop, on: noop, once: noop },
+ signTransaction: (ethTx) => new Promise((resolve) => {
+ ethTx.sign(privKey)
+ resolve()
+ }),
+ })
+ txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
+ })
+
+ describe('#getState', function () {
+ it('should return a state object with the right keys and datat types', function () {
+ const exposedState = txController.getState()
+ assert('unapprovedTxs' in exposedState, 'state should have the key unapprovedTxs')
+ assert('selectedAddressTxList' in exposedState, 'state should have the key selectedAddressTxList')
+ assert(typeof exposedState.unapprovedTxs === 'object', 'should be an object')
+ assert(Array.isArray(exposedState.selectedAddressTxList), 'should be an array')
+ })
+ })
+
+ describe('#getUnapprovedTxCount', function () {
+ it('should return the number of unapproved txs', function () {
+ txController.txStateManager._saveTxList([
+ { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ ])
+ const unapprovedTxCount = txController.getUnapprovedTxCount()
+ assert.equal(unapprovedTxCount, 3, 'should be 3')
+ })
+ })
+
+ describe('#getPendingTxCount', function () {
+ it('should return the number of pending txs', function () {
+ txController.txStateManager._saveTxList([
+ { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ ])
+ const pendingTxCount = txController.getPendingTxCount()
+ assert.equal(pendingTxCount, 3, 'should be 3')
+ })
+ })
+
+ describe('#getConfirmedTransactions', function () {
+ let address
+ beforeEach(function () {
+ address = '0xc684832530fcbddae4b4230a47e991ddcec2831d'
+ const txParams = {
+ 'from': address,
+ 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ }
+ txController.txStateManager._saveTxList([
+ {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams},
+ ])
+ })
+
+ it('should return the number of confirmed txs', function () {
+ assert.equal(txController.nonceTracker.getConfirmedTransactions(address).length, 3)
+ })
+ })
+
+
+ describe('#newUnapprovedTransaction', function () {
+ let stub, txMeta, txParams
+ beforeEach(function () {
+ txParams = {
+ 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ }
+ txMeta = {
+ status: 'unapproved',
+ id: 1,
+ metamaskNetworkId: currentNetworkId,
+ txParams,
+ history: [],
+ }
+ txController.txStateManager._saveTxList([txMeta])
+ stub = sinon.stub(txController, 'addUnapprovedTransaction').callsFake(() => {
+ txController.emit('newUnapprovedTx', txMeta)
+ return Promise.resolve(txController.txStateManager.addTx(txMeta))
+ })
+
+ afterEach(function () {
+ txController.txStateManager._saveTxList([])
+ stub.restore()
+ })
+ })
+
+ it('should resolve when finished and status is submitted and resolve with the hash', function (done) {
+ txController.once('newUnapprovedTx', (txMetaFromEmit) => {
+ setTimeout(() => {
+ txController.setTxHash(txMetaFromEmit.id, '0x0')
+ txController.txStateManager.setTxStatusSubmitted(txMetaFromEmit.id)
+ }, 10)
+ })
+
+ txController.newUnapprovedTransaction(txParams)
+ .then((hash) => {
+ assert(hash, 'newUnapprovedTransaction needs to return the hash')
+ done()
+ })
+ .catch(done)
+ })
+
+ it('should reject when finished and status is rejected', function (done) {
+ txController.once('newUnapprovedTx', (txMetaFromEmit) => {
+ setTimeout(() => {
+ txController.txStateManager.setTxStatusRejected(txMetaFromEmit.id)
+ }, 10)
+ })
+
+ txController.newUnapprovedTransaction(txParams)
+ .catch((err) => {
+ if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') done()
+ else done(err)
+ })
+ })
+ })
+
+ describe('#addUnapprovedTransaction', function () {
+
+ it('should add an unapproved transaction and return a valid txMeta', function (done) {
+ txController.addUnapprovedTransaction({})
+ .then((txMeta) => {
+ assert(('id' in txMeta), 'should have a id')
+ assert(('time' in txMeta), 'should have a time stamp')
+ assert(('metamaskNetworkId' in txMeta), 'should have a metamaskNetworkId')
+ assert(('txParams' in txMeta), 'should have a txParams')
+ assert(('history' in txMeta), 'should have a history')
+
+ const memTxMeta = txController.txStateManager.getTx(txMeta.id)
+ assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`)
+ done()
+ }).catch(done)
+ })
+
+ it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) {
+ providerResultStub.eth_gasPrice = '4a817c800'
+ txController.once('newUnapprovedTx', (txMetaFromEmit) => {
+ assert(txMetaFromEmit, 'txMeta is falsey')
+ done()
+ })
+ txController.addUnapprovedTransaction({})
+ .catch(done)
+ })
+
+ })
+
+ describe('#addTxDefaults', function () {
+ it('should add the tx defaults if their are none', function (done) {
+ const txMeta = {
+ 'txParams': {
+ 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ },
+ }
+ providerResultStub.eth_gasPrice = '4a817c800'
+ providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }
+ providerResultStub.eth_estimateGas = '5209'
+ txController.addTxDefaults(txMeta)
+ .then((txMetaWithDefaults) => {
+ assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value')
+ assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price')
+ assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field')
+ done()
+ })
+ .catch(done)
+ })
+ })
+
+ describe('#validateTxParams', function () {
+ it('does not throw for positive values', function (done) {
+ var sample = {
+ value: '0x01',
+ }
+ txController.txGasUtil.validateTxParams(sample).then(() => {
+ done()
+ }).catch(done)
+ })
+
+ it('returns error for negative values', function (done) {
+ var sample = {
+ value: '-0x01',
+ }
+ txController.txGasUtil.validateTxParams(sample)
+ .then(() => done('expected to thrown on negativity values but didn\'t'))
+ .catch((err) => {
+ assert.ok(err, 'error')
+ done()
+ })
+ })
+ })
+
+ describe('#addTx', function () {
+ it('should emit updates', function (done) {
+ const txMeta = {
+ id: '1',
+ status: 'unapproved',
+ metamaskNetworkId: currentNetworkId,
+ txParams: {},
+ }
+
+ const eventNames = ['update:badge', '1:unapproved']
+ const listeners = []
+ eventNames.forEach((eventName) => {
+ listeners.push(new Promise((resolve) => {
+ txController.once(eventName, (arg) => {
+ resolve(arg)
+ })
+ }))
+ })
+ Promise.all(listeners)
+ .then((returnValues) => {
+ assert.deepEqual(returnValues.pop(), txMeta, 'last event 1:unapproved should return txMeta')
+ done()
+ })
+ .catch(done)
+ txController.addTx(txMeta)
+ })
+ })
+
+ describe('#approveTransaction', function () {
+ let txMeta, originalValue
+
+ beforeEach(function () {
+ originalValue = '0x01'
+ txMeta = {
+ id: '1',
+ status: 'unapproved',
+ metamaskNetworkId: currentNetworkId,
+ txParams: {
+ nonce: originalValue,
+ gas: originalValue,
+ gasPrice: originalValue,
+ },
+ }
+ })
+
+
+ it('does not overwrite set values', function (done) {
+ this.timeout(15000)
+ const wrongValue = '0x05'
+
+ txController.addTx(txMeta)
+ providerResultStub.eth_gasPrice = wrongValue
+ providerResultStub.eth_estimateGas = '0x5209'
+
+ const signStub = sinon.stub(txController, 'signTransaction').callsFake(() => Promise.resolve())
+
+ const pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => {
+ txController.setTxHash('1', originalValue)
+ txController.txStateManager.setTxStatusSubmitted('1')
+ })
+
+ txController.approveTransaction(txMeta.id).then(() => {
+ const result = txController.txStateManager.getTx(txMeta.id)
+ const params = result.txParams
+
+ assert.equal(params.gas, originalValue, 'gas unmodified')
+ assert.equal(params.gasPrice, originalValue, 'gas price unmodified')
+ assert.equal(result.hash, originalValue, `hash was set \n got: ${result.hash} \n expected: ${originalValue}`)
+ signStub.restore()
+ pubStub.restore()
+ done()
+ }).catch(done)
+ })
+ })
+
+ describe('#sign replay-protected tx', function () {
+ it('prepares a tx with the chainId set', function (done) {
+ txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ txController.signTransaction('1').then((rawTx) => {
+ const ethTx = new EthTx(ethUtil.toBuffer(rawTx))
+ assert.equal(ethTx.getChainId(), currentNetworkId)
+ done()
+ }).catch(done)
+ })
+ })
+
+ describe('#updateAndApproveTransaction', function () {
+ let txMeta
+ beforeEach(function () {
+ txMeta = {
+ id: 1,
+ status: 'unapproved',
+ txParams: {
+ from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ gasPrice: '0x77359400',
+ gas: '0x7b0d',
+ nonce: '0x4b',
+ },
+ metamaskNetworkId: currentNetworkId,
+ }
+ })
+ it('should update and approve transactions', function () {
+ txController.txStateManager.addTx(txMeta)
+ txController.updateAndApproveTransaction(txMeta)
+ const tx = txController.txStateManager.getTx(1)
+ assert.equal(tx.status, 'approved')
+ })
+ })
+
+ describe('#getChainId', function () {
+ it('returns 0 when the chainId is NaN', function () {
+ txController.networkStore = new ObservableStore(NaN)
+ assert.equal(txController.getChainId(), 0)
+ })
+ })
+
+ describe('#cancelTransaction', function () {
+ beforeEach(function () {
+ txController.txStateManager._saveTxList([
+ { id: 0, status: 'unapproved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 1, status: 'rejected', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 2, status: 'approved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 3, status: 'signed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 4, status: 'submitted', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 5, status: 'confirmed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 6, status: 'failed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ ])
+ })
+
+ it('should set the transaction to rejected from unapproved', async function () {
+ await txController.cancelTransaction(0)
+ assert.equal(txController.txStateManager.getTx(0).status, 'rejected')
+ })
+
+ })
+
+ describe('#publishTransaction', function () {
+ let hash, txMeta
+ beforeEach(function () {
+ hash = '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8'
+ txMeta = {
+ id: 1,
+ status: 'unapproved',
+ txParams: {},
+ metamaskNetworkId: currentNetworkId,
+ }
+ providerResultStub.eth_sendRawTransaction = hash
+ })
+
+ it('should publish a tx, updates the rawTx when provided a one', async function () {
+ txController.txStateManager.addTx(txMeta)
+ await txController.publishTransaction(txMeta.id)
+ const publishedTx = txController.txStateManager.getTx(1)
+ assert.equal(publishedTx.hash, hash)
+ assert.equal(publishedTx.status, 'submitted')
+ })
+ })
+
+
+ describe('#getPendingTransactions', function () {
+ beforeEach(function () {
+ txController.txStateManager._saveTxList([
+ { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 2, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 6, status: 'confimed', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {} },
+ ])
+ })
+ it('should show only submitted transactions as pending transasction', function () {
+ assert(txController.pendingTxTracker.getPendingTransactions().length, 1)
+ assert(txController.pendingTxTracker.getPendingTransactions()[0].status, 'submitted')
+ })
+ })
+})
diff --git a/test/unit/tx-gas-util-test.js b/test/unit/tx-gas-util-test.js
new file mode 100644
index 000000000..d9a12d1c3
--- /dev/null
+++ b/test/unit/tx-gas-util-test.js
@@ -0,0 +1,32 @@
+const assert = require('assert')
+const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
+const { createTestProviderTools } = require('../stub/provider')
+
+describe('Tx Gas Util', function () {
+ let txGasUtil, provider, providerResultStub
+ beforeEach(function () {
+ providerResultStub = {}
+ provider = createTestProviderTools({ scaffold: providerResultStub }).provider
+ txGasUtil = new TxGasUtils({
+ provider,
+ })
+ })
+
+ it('removes recipient for txParams with 0x when contract data is provided', function () {
+ const zeroRecipientandDataTxParams = {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ to: '0x',
+ data: 'bytecode',
+ }
+ const sanitizedTxParams = txGasUtil.validateRecipient(zeroRecipientandDataTxParams)
+ assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
+ })
+
+ it('should error when recipient is 0x', function () {
+ const zeroRecipientTxParams = {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ to: '0x',
+ }
+ assert.throws(() => { txGasUtil.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
+ })
+})
diff --git a/test/unit/tx-helper-test.js b/test/unit/tx-helper-test.js
new file mode 100644
index 000000000..cc6543c30
--- /dev/null
+++ b/test/unit/tx-helper-test.js
@@ -0,0 +1,17 @@
+const assert = require('assert')
+const txHelper = require('../../ui/lib/tx-helper')
+
+describe('txHelper', function () {
+ it('always shows the oldest tx first', function () {
+ const metamaskNetworkId = 1
+ const txs = {
+ a: { metamaskNetworkId, time: 3 },
+ b: { metamaskNetworkId, time: 1 },
+ c: { metamaskNetworkId, time: 2 },
+ }
+
+ const sorted = txHelper(txs, null, null, metamaskNetworkId)
+ assert.equal(sorted[0].time, 1, 'oldest tx first')
+ assert.equal(sorted[2].time, 3, 'newest tx last')
+ })
+})
diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js
deleted file mode 100644
index 21e94357b..000000000
--- a/test/unit/tx-manager-test.js
+++ /dev/null
@@ -1,241 +0,0 @@
-const assert = require('assert')
-const extend = require('xtend')
-const EventEmitter = require('events')
-const ethUtil = require('ethereumjs-util')
-const EthTx = require('ethereumjs-tx')
-const ObservableStore = require('obs-store')
-const STORAGE_KEY = 'metamask-persistance-key'
-const TransactionManager = require('../../app/scripts/transaction-manager')
-const noop = () => true
-const currentNetworkId = 42
-const otherNetworkId = 36
-const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
-
-describe('Transaction Manager', function() {
- let txManager
-
- beforeEach(function() {
- txManager = new TransactionManager({
- networkStore: new ObservableStore({ network: currentNetworkId }),
- txHistoryLimit: 10,
- blockTracker: new EventEmitter(),
- signTransaction: (ethTx) => new Promise((resolve) => {
- ethTx.sign(privKey)
- resolve()
- })
- })
- })
-
- describe('#validateTxParams', function () {
- it('returns null for positive values', function() {
- var sample = {
- value: '0x01'
- }
- var res = txManager.txProviderUtils.validateTxParams(sample, (err) => {
- assert.equal(err, null, 'no error')
- })
- })
-
- it('returns error for negative values', function() {
- var sample = {
- value: '-0x01'
- }
- var res = txManager.txProviderUtils.validateTxParams(sample, (err) => {
- assert.ok(err, 'error')
- })
- })
- })
-
- describe('#getTxList', function() {
- it('when new should return empty array', function() {
- var result = txManager.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 0)
- })
- it('should also return transactions from local storage if any', function() {
-
- })
- })
-
- describe('#addTx', function() {
- it('adds a tx returned in getTxList', function() {
- var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(tx, noop)
- var result = txManager.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 1)
- assert.equal(result[0].id, 1)
- })
-
- it('does not override txs from other networks', function() {
- var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} }
- txManager.addTx(tx, noop)
- txManager.addTx(tx2, noop)
- var result = txManager.getFullTxList()
- var result2 = txManager.getTxList()
- assert.equal(result.length, 2, 'txs were deleted')
- assert.equal(result2.length, 1, 'incorrect number of txs on network.')
- })
-
- it('cuts off early txs beyond a limit', function() {
- const limit = txManager.txHistoryLimit
- for (let i = 0; i < limit + 1; i++) {
- let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(tx, noop)
- }
- var result = txManager.getTxList()
- assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
- assert.equal(result[0].id, 1, 'early txs truncted')
- })
-
- it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function() {
- const limit = txManager.txHistoryLimit
- for (let i = 0; i < limit + 1; i++) {
- let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(tx, noop)
- }
- var result = txManager.getTxList()
- assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
- assert.equal(result[0].id, 1, 'early txs truncted')
- })
-
- it('cuts off early txs beyond a limit but does not cut unapproved txs', function() {
- var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(unconfirmedTx, noop)
- const limit = txManager.txHistoryLimit
- for (let i = 1; i < limit + 1; i++) {
- let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(tx, noop)
- }
- var result = txManager.getTxList()
- assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
- assert.equal(result[0].id, 0, 'first tx should still be there')
- assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved')
- assert.equal(result[1].id, 2, 'early txs truncted')
- })
- })
-
- describe('#setTxStatusSigned', function() {
- it('sets the tx status to signed', function() {
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(tx, noop)
- txManager.setTxStatusSigned(1)
- var result = txManager.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 1)
- assert.equal(result[0].status, 'signed')
- })
-
- it('should emit a signed event to signal the exciton of callback', (done) => {
- this.timeout(10000)
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- let noop = function () {
- assert(true, 'event listener has been triggered and noop executed')
- done()
- }
- txManager.addTx(tx)
- txManager.on('1:signed', noop)
- txManager.setTxStatusSigned(1)
- })
- })
-
- describe('#setTxStatusRejected', function() {
- it('sets the tx status to rejected', function() {
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(tx)
- txManager.setTxStatusRejected(1)
- var result = txManager.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 1)
- assert.equal(result[0].status, 'rejected')
- })
-
- it('should emit a rejected event to signal the exciton of callback', (done) => {
- this.timeout(10000)
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txManager.addTx(tx)
- let noop = function (err, txId) {
- assert(true, 'event listener has been triggered and noop executed')
- done()
- }
- txManager.on('1:rejected', noop)
- txManager.setTxStatusRejected(1)
- })
-
- })
-
- describe('#updateTx', function() {
- it('replaces the tx with the same id', function() {
- txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} })
- var result = txManager.getTx('1')
- assert.equal(result.hash, 'foo')
- })
- })
-
- describe('#getUnapprovedTxList', function() {
- it('returns unapproved txs in a hash', function() {
- txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- let result = txManager.getUnapprovedTxList()
- assert.equal(typeof result, 'object')
- assert.equal(result['1'].status, 'unapproved')
- assert.equal(result['2'], undefined)
- })
- })
-
- describe('#getTx', function() {
- it('returns a tx with the requested id', function() {
- txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- assert.equal(txManager.getTx('1').status, 'unapproved')
- assert.equal(txManager.getTx('2').status, 'confirmed')
- })
- })
-
- describe('#getFilteredTxList', function() {
- it('returns a tx with the requested data', function() {
- let txMetas = [
- { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- ]
- txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop))
- let filterParams
-
- filterParams = { status: 'unapproved', from: '0xaa' }
- assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { status: 'unapproved', to: '0xaa' }
- assert.equal(txManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { status: 'confirmed', from: '0xbb' }
- assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { status: 'confirmed' }
- assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { from: '0xaa' }
- assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { to: '0xaa' }
- assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- })
- })
-
- describe('#sign replay-protected tx', function() {
- it('prepares a tx with the chainId set', function() {
- txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- txManager.signTransaction('1', (err, rawTx) => {
- if (err) return assert.fail('it should not fail')
- const ethTx = new EthTx(ethUtil.toBuffer(rawTx))
- assert.equal(ethTx.getChainId(), currentNetworkId)
- })
- })
- })
-
-})
diff --git a/test/unit/tx-state-history-helper-test.js b/test/unit/tx-state-history-helper-test.js
new file mode 100644
index 000000000..90cb10713
--- /dev/null
+++ b/test/unit/tx-state-history-helper-test.js
@@ -0,0 +1,26 @@
+const assert = require('assert')
+const clone = require('clone')
+const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
+
+describe('deepCloneFromTxMeta', function () {
+ it('should clone deep', function () {
+ const input = {
+ foo: {
+ bar: {
+ bam: 'baz'
+ }
+ }
+ }
+ const output = txStateHistoryHelper.snapshotFromTxMeta(input)
+ assert('foo' in output, 'has a foo key')
+ assert('bar' in output.foo, 'has a bar key')
+ assert('bam' in output.foo.bar, 'has a bar key')
+ assert.equal(output.foo.bar.bam, 'baz', 'has a baz value')
+ })
+
+ it('should remove the history key', function () {
+ const input = { foo: 'bar', history: 'remembered' }
+ const output = txStateHistoryHelper.snapshotFromTxMeta(input)
+ assert(typeof output.history, 'undefined', 'should remove history')
+ })
+})
diff --git a/test/unit/tx-state-history-helper.js b/test/unit/tx-state-history-helper.js
new file mode 100644
index 000000000..79ee26d6e
--- /dev/null
+++ b/test/unit/tx-state-history-helper.js
@@ -0,0 +1,46 @@
+const assert = require('assert')
+const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
+const testVault = require('../data/v17-long-history.json')
+
+
+describe('tx-state-history-helper', function () {
+ it('migrates history to diffs and can recover original values', function () {
+ testVault.data.TransactionController.transactions.forEach((tx, index) => {
+ const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
+ newHistory.forEach((newEntry, index) => {
+ if (index === 0) {
+ assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
+ } else {
+ assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
+ }
+ const oldEntry = tx.history[index]
+ const historySubset = newHistory.slice(0, index + 1)
+ const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
+ assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
+ })
+ })
+ })
+
+ it('replaying history does not mutate the original obj', function () {
+ const initialState = { test: true, message: 'hello', value: 1 }
+ const diff1 = [{
+ "op": "replace",
+ "path": "/message",
+ "value": "haay",
+ }]
+ const diff2 = [{
+ "op": "replace",
+ "path": "/value",
+ "value": 2,
+ }]
+ const history = [initialState, diff1, diff2]
+
+ const beforeStateSnapshot = JSON.stringify(initialState)
+ const latestState = txStateHistoryHelper.replayHistory(history)
+ const afterStateSnapshot = JSON.stringify(initialState)
+
+ assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
+ assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
+ })
+
+})
diff --git a/test/unit/tx-state-manager-test.js b/test/unit/tx-state-manager-test.js
new file mode 100644
index 000000000..02dc52967
--- /dev/null
+++ b/test/unit/tx-state-manager-test.js
@@ -0,0 +1,284 @@
+const assert = require('assert')
+const clone = require('clone')
+const ObservableStore = require('obs-store')
+const TxStateManager = require('../../app/scripts/lib/tx-state-manager')
+const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
+const noop = () => true
+
+describe('TransactionStateManger', function () {
+ let txStateManager
+ const currentNetworkId = 42
+ const otherNetworkId = 2
+
+ beforeEach(function () {
+ txStateManager = new TxStateManager({
+ initState: {
+ transactions: [],
+ },
+ txHistoryLimit: 10,
+ getNetwork: () => currentNetworkId
+ })
+ })
+
+ describe('#setTxStatusSigned', function () {
+ it('sets the tx status to signed', function () {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ txStateManager.setTxStatusSigned(1)
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 1)
+ assert.equal(result[0].status, 'signed')
+ })
+
+ it('should emit a signed event to signal the exciton of callback', (done) => {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ const noop = function () {
+ assert(true, 'event listener has been triggered and noop executed')
+ done()
+ }
+ txStateManager.addTx(tx)
+ txStateManager.on('1:signed', noop)
+ txStateManager.setTxStatusSigned(1)
+
+ })
+ })
+
+ describe('#setTxStatusRejected', function () {
+ it('sets the tx status to rejected', function () {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx)
+ txStateManager.setTxStatusRejected(1)
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 1)
+ assert.equal(result[0].status, 'rejected')
+ })
+
+ it('should emit a rejected event to signal the exciton of callback', (done) => {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx)
+ const noop = function (err, txId) {
+ assert(true, 'event listener has been triggered and noop executed')
+ done()
+ }
+ txStateManager.on('1:rejected', noop)
+ txStateManager.setTxStatusRejected(1)
+ })
+ })
+
+ describe('#getFullTxList', function () {
+ it('when new should return empty array', function () {
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 0)
+ })
+ })
+
+ describe('#getTxList', function () {
+ it('when new should return empty array', function () {
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 0)
+ })
+ })
+
+ describe('#addTx', function () {
+ it('adds a tx returned in getTxList', function () {
+ let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 1)
+ assert.equal(result[0].id, 1)
+ })
+
+ it('does not override txs from other networks', function () {
+ let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ let tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ txStateManager.addTx(tx2, noop)
+ let result = txStateManager.getFullTxList()
+ let result2 = txStateManager.getTxList()
+ assert.equal(result.length, 2, 'txs were deleted')
+ assert.equal(result2.length, 1, 'incorrect number of txs on network.')
+ })
+
+ it('cuts off early txs beyond a limit', function () {
+ const limit = txStateManager.txHistoryLimit
+ for (let i = 0; i < limit + 1; i++) {
+ const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ }
+ let result = txStateManager.getTxList()
+ assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
+ assert.equal(result[0].id, 1, 'early txs truncted')
+ })
+
+ it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () {
+ const limit = txStateManager.txHistoryLimit
+ for (let i = 0; i < limit + 1; i++) {
+ const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ }
+ let result = txStateManager.getTxList()
+ assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
+ assert.equal(result[0].id, 1, 'early txs truncted')
+ })
+
+ it('cuts off early txs beyond a limit but does not cut unapproved txs', function () {
+ let unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(unconfirmedTx, noop)
+ const limit = txStateManager.txHistoryLimit
+ for (let i = 1; i < limit + 1; i++) {
+ const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ }
+ let result = txStateManager.getTxList()
+ assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
+ assert.equal(result[0].id, 0, 'first tx should still be there')
+ assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved')
+ assert.equal(result[1].id, 2, 'early txs truncted')
+ })
+ })
+
+ describe('#updateTx', function () {
+ it('replaces the tx with the same id', function () {
+ txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ const txMeta = txStateManager.getTx('1')
+ txMeta.hash = 'foo'
+ txStateManager.updateTx(txMeta)
+ let result = txStateManager.getTx('1')
+ assert.equal(result.hash, 'foo')
+ })
+
+ it('updates gas price and adds history items', function () {
+ const originalGasPrice = '0x01'
+ const desiredGasPrice = '0x02'
+
+ const txMeta = {
+ id: '1',
+ status: 'unapproved',
+ metamaskNetworkId: currentNetworkId,
+ txParams: {
+ gasPrice: originalGasPrice,
+ },
+ }
+
+ const updatedMeta = clone(txMeta)
+
+ txStateManager.addTx(txMeta)
+ const updatedTx = txStateManager.getTx('1')
+ // verify tx was initialized correctly
+ assert.equal(updatedTx.history.length, 1, 'one history item (initial)')
+ assert.equal(Array.isArray(updatedTx.history[0]), false, 'first history item is initial state')
+ assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state')
+ // modify value and updateTx
+ updatedTx.txParams.gasPrice = desiredGasPrice
+ txStateManager.updateTx(updatedTx)
+ // check updated value
+ const result = txStateManager.getTx('1')
+ assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated')
+ // validate history was updated
+ assert.equal(result.history.length, 2, 'two history items (initial + diff)')
+ const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice }
+ assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)')
+ })
+ })
+
+ describe('#getUnapprovedTxList', function () {
+ it('returns unapproved txs in a hash', function () {
+ txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ const result = txStateManager.getUnapprovedTxList()
+ assert.equal(typeof result, 'object')
+ assert.equal(result['1'].status, 'unapproved')
+ assert.equal(result['2'], undefined)
+ })
+ })
+
+ describe('#getTx', function () {
+ it('returns a tx with the requested id', function () {
+ txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ assert.equal(txStateManager.getTx('1').status, 'unapproved')
+ assert.equal(txStateManager.getTx('2').status, 'confirmed')
+ })
+ })
+
+ describe('#getFilteredTxList', function () {
+ it('returns a tx with the requested data', function () {
+ const txMetas = [
+ { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ ]
+ txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop))
+ let filterParams
+
+ filterParams = { status: 'unapproved', from: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { status: 'unapproved', to: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { status: 'confirmed', from: '0xbb' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { status: 'confirmed' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { from: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { to: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ })
+ })
+
+ describe('#wipeTransactions', function () {
+
+ const specificAddress = '0xaa'
+ const otherAddress = '0xbb'
+
+ it('should remove only the transactions from a specific address', function () {
+
+ const txMetas = [
+ { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId },
+ { id: 1, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId },
+ { id: 2, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId },
+ ]
+ txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop))
+
+ txStateManager.wipeTransactions(specificAddress)
+
+ const transactionsFromCurrentAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress)
+ const transactionsFromOtherAddresses = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from !== specificAddress)
+
+ assert.equal(transactionsFromCurrentAddress.length, 0)
+ assert.equal(transactionsFromOtherAddresses.length, 2)
+ })
+
+ it('should not remove the transactions from other networks', function () {
+ const txMetas = [
+ { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId },
+ { id: 1, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId },
+ { id: 2, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId },
+ ]
+
+ txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop))
+
+ txStateManager.wipeTransactions(specificAddress)
+
+ const txsFromCurrentNetworkAndAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress)
+ const txFromOtherNetworks = txStateManager.getFullTxList().filter((txMeta) => txMeta.metamaskNetworkId === otherNetworkId)
+
+ assert.equal(txsFromCurrentNetworkAndAddress.length, 0)
+ assert.equal(txFromOtherNetworks.length, 2)
+
+ })
+ })
+}) \ No newline at end of file
diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js
index 93e9e4134..8ca13412e 100644
--- a/test/unit/tx-utils-test.js
+++ b/test/unit/tx-utils-test.js
@@ -1,19 +1,25 @@
const assert = require('assert')
-const ethUtil = require('ethereumjs-util')
-const BN = ethUtil.BN
+const Transaction = require('ethereumjs-tx')
+const BN = require('bn.js')
-const TxUtils = require('../../app/scripts/lib/tx-utils')
+const { hexToBn, bnToHex } = require('../../app/scripts/lib/util')
+const TxUtils = require('../../app/scripts/lib/tx-gas-utils')
-describe('txUtils', function() {
+
+describe('txUtils', function () {
let txUtils
- before(function() {
- txUtils = new TxUtils()
+ before(function () {
+ txUtils = new TxUtils(new Proxy({}, {
+ get: (obj, name) => {
+ return () => {}
+ },
+ }))
})
- describe('chain Id', function() {
- it('prepares a transaction with the provided chainId', function() {
+ describe('chain Id', function () {
+ it('prepares a transaction with the provided chainId', function () {
const txParams = {
to: '0x70ad465e0bab6504002ad58c744ed89c7da38524',
from: '0x69ad465e0bab6504002ad58c744ed89c7da38525',
@@ -24,13 +30,13 @@ describe('txUtils', function() {
nonce: '0x3',
chainId: 42,
}
- const ethTx = txUtils.buildEthTxFromParams(txParams)
+ const ethTx = new Transaction(txParams)
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params')
})
})
- describe('addGasBuffer', function() {
- it('multiplies by 1.5, when within block gas limit', function() {
+ describe('addGasBuffer', function () {
+ it('multiplies by 1.5, when within block gas limit', function () {
// naive estimatedGas: 0x16e360 (1.5 mil)
const inputHex = '0x16e360'
// dummy gas limit: 0x3d4c52 (4 mil)
@@ -41,20 +47,20 @@ describe('txUtils', function() {
const expectedBn = inputBn.muln(1.5)
assert(outputBn.eq(expectedBn), 'returns 1.5 the input value')
})
-
- it('uses original estimatedGas, when above block gas limit', function() {
+
+ it('uses original estimatedGas, when above block gas limit', function () {
// naive estimatedGas: 0x16e360 (1.5 mil)
const inputHex = '0x16e360'
// dummy gas limit: 0x0f4240 (1 mil)
const blockGasLimitHex = '0x0f4240'
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex)
- const inputBn = hexToBn(inputHex)
+ // const inputBn = hexToBn(inputHex)
const outputBn = hexToBn(output)
const expectedBn = hexToBn(inputHex)
assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value')
})
- it('buffers up to reccomend gas limit reccomended ceiling', function() {
+ it('buffers up to recommend gas limit recommended ceiling', function () {
// naive estimatedGas: 0x16e360 (1.5 mil)
const inputHex = '0x16e360'
// dummy gas limit: 0x1e8480 (2 mil)
@@ -65,17 +71,7 @@ describe('txUtils', function() {
// const inputBn = hexToBn(inputHex)
// const outputBn = hexToBn(output)
const expectedHex = bnToHex(ceilGasLimitBn)
- assert.equal(output, expectedHex, 'returns the gas limit reccomended ceiling value')
+ assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value')
})
})
-})
-
-// util
-
-function hexToBn(inputHex) {
- return new BN(ethUtil.stripHexPrefix(inputHex), 16)
-}
-
-function bnToHex(inputBn) {
- return ethUtil.addHexPrefix(inputBn.toString(16))
-} \ No newline at end of file
+}) \ No newline at end of file
diff --git a/test/unit/ui/add-token.spec.js b/test/unit/ui/add-token.spec.js
new file mode 100644
index 000000000..69b7fb620
--- /dev/null
+++ b/test/unit/ui/add-token.spec.js
@@ -0,0 +1,43 @@
+const assert = require('assert')
+const { createMockStore } = require('redux-test-utils')
+const h = require('react-hyperscript')
+const { shallowWithStore } = require('../../lib/shallow-with-store')
+const AddTokenScreen = require('../../../old-ui/app/add-token')
+
+describe('Add Token Screen', function () {
+ let addTokenComponent, store, component
+ const mockState = {
+ metamask: {
+ identities: {
+ '0x7d3517b0d011698406d6e0aed8453f0be2697926': {
+ 'address': '0x7d3517b0d011698406d6e0aed8453f0be2697926',
+ 'name': 'Add Token Name',
+ },
+ },
+ },
+ }
+ beforeEach(function () {
+ store = createMockStore(mockState)
+ component = shallowWithStore(h(AddTokenScreen), store)
+ addTokenComponent = component.dive()
+ })
+
+ describe('#ValidateInputs', function () {
+
+ it('Default State', function () {
+ addTokenComponent.instance().validateInputs()
+ const state = addTokenComponent.state()
+ assert.equal(state.warning, 'Address is invalid.')
+ })
+
+ it('Address is a Metamask Identity', function () {
+ addTokenComponent.setState({
+ address: '0x7d3517b0d011698406d6e0aed8453f0be2697926',
+ })
+ addTokenComponent.instance().validateInputs()
+ const state = addTokenComponent.state()
+ assert.equal(state.warning, 'Personal address detected. Input the token contract address.')
+ })
+
+ })
+})
diff --git a/test/unit/util-test.js b/test/unit/util-test.js
new file mode 100644
index 000000000..6da185b2c
--- /dev/null
+++ b/test/unit/util-test.js
@@ -0,0 +1,41 @@
+const assert = require('assert')
+const { sufficientBalance } = require('../../app/scripts/lib/util')
+
+
+describe('SufficientBalance', function () {
+ it('returns true if max tx cost is equal to balance.', function () {
+ const tx = {
+ 'value': '0x1',
+ 'gas': '0x2',
+ 'gasPrice': '0x3',
+ }
+ const balance = '0x8'
+
+ const result = sufficientBalance(tx, balance)
+ assert.ok(result, 'sufficient balance found.')
+ })
+
+ it('returns true if max tx cost is less than balance.', function () {
+ const tx = {
+ 'value': '0x1',
+ 'gas': '0x2',
+ 'gasPrice': '0x3',
+ }
+ const balance = '0x9'
+
+ const result = sufficientBalance(tx, balance)
+ assert.ok(result, 'sufficient balance found.')
+ })
+
+ it('returns false if max tx cost is more than balance.', function () {
+ const tx = {
+ 'value': '0x1',
+ 'gas': '0x2',
+ 'gasPrice': '0x3',
+ }
+ const balance = '0x6'
+
+ const result = sufficientBalance(tx, balance)
+ assert.ok(!result, 'insufficient balance found.')
+ })
+}) \ No newline at end of file
diff --git a/test/unit/util_test.js b/test/unit/util_test.js
index 00528b905..59048975a 100644
--- a/test/unit/util_test.js
+++ b/test/unit/util_test.js
@@ -5,96 +5,96 @@ const ethUtil = require('ethereumjs-util')
var path = require('path')
var util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'util.js'))
-describe('util', function() {
+describe('util', function () {
var ethInWei = '1'
- for (var i = 0; i < 18; i++ ) { ethInWei += '0' }
+ for (var i = 0; i < 18; i++) { ethInWei += '0' }
- beforeEach(function() {
+ beforeEach(function () {
this.sinon = sinon.sandbox.create()
})
- afterEach(function() {
+ afterEach(function () {
this.sinon.restore()
})
- describe('#parseBalance', function() {
- it('should render 0.01 eth correctly', function() {
+ describe('#parseBalance', function () {
+ it('should render 0.01 eth correctly', function () {
const input = '0x2386F26FC10000'
const output = util.parseBalance(input)
assert.deepEqual(output, ['0', '01'])
})
- it('should render 12.023 eth correctly', function() {
+ it('should render 12.023 eth correctly', function () {
const input = 'A6DA46CCA6858000'
const output = util.parseBalance(input)
assert.deepEqual(output, ['12', '023'])
})
- it('should render 0.0000000342422 eth correctly', function() {
+ it('should render 0.0000000342422 eth correctly', function () {
const input = '0x7F8FE81C0'
const output = util.parseBalance(input)
assert.deepEqual(output, ['0', '0000000342422'])
})
- it('should render 0 eth correctly', function() {
+ it('should render 0 eth correctly', function () {
const input = '0x0'
const output = util.parseBalance(input)
assert.deepEqual(output, ['0', '0'])
})
})
- describe('#addressSummary', function() {
- it('should add case-sensitive checksum', function() {
+ describe('#addressSummary', function () {
+ it('should add case-sensitive checksum', function () {
var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'
var result = util.addressSummary(address)
assert.equal(result, '0xFDEa65C8...b825')
})
- it('should accept arguments for firstseg, lastseg, and keepPrefix', function() {
+ it('should accept arguments for firstseg, lastseg, and keepPrefix', function () {
var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'
var result = util.addressSummary(address, 4, 4, false)
assert.equal(result, 'FDEa...b825')
})
})
- describe('#isValidAddress', function() {
- it('should allow 40-char non-prefixed hex', function() {
+ describe('#isValidAddress', function () {
+ it('should allow 40-char non-prefixed hex', function () {
var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825'
var result = util.isValidAddress(address)
assert.ok(result)
})
- it('should allow 42-char non-prefixed hex', function() {
+ it('should allow 42-char non-prefixed hex', function () {
var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'
var result = util.isValidAddress(address)
assert.ok(result)
})
- it('should not allow less non hex-prefixed', function() {
+ it('should not allow less non hex-prefixed', function () {
var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85'
var result = util.isValidAddress(address)
assert.ok(!result)
})
- it('should not allow less hex-prefixed', function() {
+ it('should not allow less hex-prefixed', function () {
var address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85'
var result = util.isValidAddress(address)
assert.ok(!result)
})
- it('should recognize correct capitalized checksum', function() {
+ it('should recognize correct capitalized checksum', function () {
var address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825'
var result = util.isValidAddress(address)
assert.ok(result)
})
- it('should recognize incorrect capitalized checksum', function() {
+ it('should recognize incorrect capitalized checksum', function () {
var address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825'
var result = util.isValidAddress(address)
assert.ok(!result)
})
- it('should recognize this sample hashed address', function() {
+ it('should recognize this sample hashed address', function () {
const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0'
const result = util.isValidAddress(address)
const hashed = ethUtil.toChecksumAddress(address.toLowerCase())
@@ -103,60 +103,57 @@ describe('util', function() {
})
})
- describe('#numericBalance', function() {
-
- it('should return a BN 0 if given nothing', function() {
+ describe('#numericBalance', function () {
+ it('should return a BN 0 if given nothing', function () {
var result = util.numericBalance()
assert.equal(result.toString(10), 0)
})
- it('should work with hex prefix', function() {
+ it('should work with hex prefix', function () {
var result = util.numericBalance('0x012')
assert.equal(result.toString(10), '18')
})
- it('should work with no hex prefix', function() {
+ it('should work with no hex prefix', function () {
var result = util.numericBalance('012')
assert.equal(result.toString(10), '18')
})
-
})
- describe('#formatBalance', function() {
-
- it('when given nothing', function() {
+ describe('#formatBalance', function () {
+ it('when given nothing', function () {
var result = util.formatBalance()
assert.equal(result, 'None', 'should return "None"')
})
- it('should return eth as string followed by ETH', function() {
+ it('should return eth as string followed by ETH', function () {
var input = new ethUtil.BN(ethInWei, 10).toJSON()
var result = util.formatBalance(input, 4)
assert.equal(result, '1.0000 ETH')
})
- it('should return eth as string followed by ETH', function() {
+ it('should return eth as string followed by ETH', function () {
var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON()
var result = util.formatBalance(input, 3)
assert.equal(result, '0.500 ETH')
})
- it('should display specified decimal points', function() {
- var input = "0x128dfa6a90b28000"
+ it('should display specified decimal points', function () {
+ var input = '0x128dfa6a90b28000'
var result = util.formatBalance(input, 2)
assert.equal(result, '1.33 ETH')
})
- it('should default to 3 decimal points', function() {
- var input = "0x128dfa6a90b28000"
+ it('should default to 3 decimal points', function () {
+ var input = '0x128dfa6a90b28000'
var result = util.formatBalance(input)
assert.equal(result, '1.337 ETH')
})
- it('should show 2 significant digits for tiny balances', function() {
- var input = "0x1230fa6a90b28"
+ it('should show 2 significant digits for tiny balances', function () {
+ var input = '0x1230fa6a90b28'
var result = util.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() {
+ it('should not parse the balance and return value with 2 decimal points with ETH at the end', function () {
var value = '1.2456789'
var needsParse = false
var result = util.formatBalance(value, 2, needsParse)
@@ -164,17 +161,16 @@ describe('util', function() {
})
})
- describe('normalizing values', function() {
-
- describe('#normalizeToWei', function() {
- it('should convert an eth to the appropriate equivalent values', function() {
+ 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',
+ wei: '1000000000000000000',
+ kwei: '1000000000000000',
+ mwei: '1000000000000',
+ gwei: '1000000000',
szabo: '1000000',
- finney:'1000',
+ finney: '1000',
ether: '1',
// kether:'0.001',
// mether:'0.000001',
@@ -185,8 +181,7 @@ describe('util', function() {
}
var oneEthBn = new ethUtil.BN(ethInWei, 10)
- for(var currency in valueTable) {
-
+ for (var currency in valueTable) {
var value = new ethUtil.BN(valueTable[currency], 10)
var output = util.normalizeToWei(value, currency)
assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`)
@@ -194,60 +189,70 @@ describe('util', function() {
})
})
- describe('#normalizeEthStringToWei', function() {
- it('should convert decimal eth to pure wei BN', function() {
+ describe('#normalizeEthStringToWei', function () {
+ it('should convert decimal eth to pure wei BN', function () {
var input = '1.23456789'
var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), '1234567890000000000')
})
- it('should convert 1 to expected wei', function() {
+ it('should convert 1 to expected wei', function () {
var input = '1'
var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), ethInWei)
})
- })
- describe('#normalizeNumberToWei', function() {
+ it('should account for overflow numbers gracefully by dropping extra precision.', function () {
+ var input = '1.11111111111111111111'
+ var output = util.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 = util.normalizeEthStringToWei(input)
+ assert.equal(output.toString(10), '1100000000000000001')
+ })
+ })
- it('should handle a simple use case', function() {
+ describe('#normalizeNumberToWei', function () {
+ it('should handle a simple use case', function () {
var input = 0.0002
var output = util.normalizeNumberToWei(input, 'ether')
var str = output.toString(10)
assert.equal(str, '200000000000000')
})
- it('should convert a kwei number to the appropriate equivalent wei', function() {
+ it('should convert a kwei number to the appropriate equivalent wei', function () {
var result = util.normalizeNumberToWei(1.111, 'kwei')
assert.equal(result.toString(10), '1111', 'accepts decimals')
})
- it('should convert a ether number to the appropriate equivalent wei', function() {
+ it('should convert a ether number to the appropriate equivalent wei', function () {
var result = util.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() {
+ describe('#isHex', function () {
+ it('should return true when given a hex string', function () {
var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2')
assert(result)
})
- it('should return false when given a non-hex string', function() {
+ it('should return false when given a non-hex string', function () {
var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal')
assert(!result)
})
- it('should return false when given a string containing a non letter/number character', function() {
+ it('should return false when given a string containing a non letter/number character', function () {
var result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal')
assert(!result)
})
- it('should return true when given a hex string with hex-prefix', function() {
+ it('should return true when given a hex string with hex-prefix', function () {
var result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2')
assert(result)
})
-
})
})
})