diff --git a/.flowconfig b/.flowconfig
new file mode 100644
index 00000000..ab91d13d
--- /dev/null
+++ b/.flowconfig
@@ -0,0 +1,20 @@
+[ignore]
+.*/node_modules/bitcoinjs-lib-zcash/.*
+.*/node_modules/bitcoinjs-lib/.*
+.*/node_modules/hd-wallet/.*
+.*/node_modules/protobufjs-old-fixed-webpack/src/bower.json
+.*/node_modules/trezor-link/lib/.*
+.*/_old/.*
+
+[libs]
+
+
+
+[options]
+esproposal.class_static_fields=enable
+esproposal.class_instance_fields=enable
+esproposal.export_star_as=enable
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
+esproposal.decorators=ignore
+module.name_mapper='.*\(.less\)' -> 'CSSModule'
+module.system=haste
diff --git a/.gitignore b/.gitignore
index 4fb43b65..1ecda50d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,6 @@ logs
.yarnclean
# Local config file
-webpack/constants.js
\ No newline at end of file
+webpack/constants.js
+
+_old
\ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100644
index 00000000..e071864d
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,13 @@
+
+
+printf "\n-- DEPLOY START -----------------------\n"
+
+yarn run build
+
+printf "\n-- COPYING FILES ----------------------\n"
+
+cd build
+rsync -avz --delete -e ssh . admin@dev.sldev.cz:~/experiments/www
+cd ../
+
+printf "\n-- COMPLETE ---------------------------\n"
diff --git a/images/bch-logo.png b/images/bch-logo.png
new file mode 100644
index 00000000..9590c12e
Binary files /dev/null and b/images/bch-logo.png differ
diff --git a/images/btc-logo.png b/images/btc-logo.png
new file mode 100644
index 00000000..5b77226c
Binary files /dev/null and b/images/btc-logo.png differ
diff --git a/images/btg-logo.png b/images/btg-logo.png
new file mode 100644
index 00000000..5eda4570
Binary files /dev/null and b/images/btg-logo.png differ
diff --git a/images/case.png b/images/case.png
new file mode 100644
index 00000000..eee16f1d
Binary files /dev/null and b/images/case.png differ
diff --git a/images/dash-logo.png b/images/dash-logo.png
new file mode 100644
index 00000000..5c3a113d
Binary files /dev/null and b/images/dash-logo.png differ
diff --git a/images/etc-logo.png b/images/etc-logo.png
new file mode 100644
index 00000000..e868d6ea
Binary files /dev/null and b/images/etc-logo.png differ
diff --git a/images/eth-logo.png b/images/eth-logo.png
new file mode 100644
index 00000000..5b7c1a8a
Binary files /dev/null and b/images/eth-logo.png differ
diff --git a/images/icontrezor.png b/images/icontrezor.png
new file mode 100644
index 00000000..8c35659b
Binary files /dev/null and b/images/icontrezor.png differ
diff --git a/images/ltc-logo.png b/images/ltc-logo.png
new file mode 100644
index 00000000..201f2350
Binary files /dev/null and b/images/ltc-logo.png differ
diff --git a/images/zec-logo.png b/images/zec-logo.png
new file mode 100644
index 00000000..134ba792
Binary files /dev/null and b/images/zec-logo.png differ
diff --git a/package.json b/package.json
index bfaf3057..55bc64da 100644
--- a/package.json
+++ b/package.json
@@ -21,16 +21,26 @@
},
"dependencies": {
"babel-preset-react": "^6.24.1",
+ "color-hash": "^1.0.3",
"ethereumjs-tx": "^1.3.3",
+ "ethereumjs-units": "^0.2.0",
"ethereumjs-util": "^5.1.2",
"hdkey": "0.7.1",
+ "path-to-regexp": "^2.1.0",
+ "raf": "^3.4.0",
+ "rc-tooltip": "^3.7.0",
"react": "^16.1.1",
+ "react-blockies": "^1.2.2",
+ "react-css-transition": "^0.7.4",
"react-dom": "^16.1.1",
+ "react-ellipsis-text": "^1.0.0",
"react-hot-loader": "^3.1.3",
"react-qr-svg": "^2.1.0",
"react-redux": "^5.0.6",
"react-router-dom": "^4.2.2",
"react-router-redux": "next",
+ "react-scale-text": "^1.2.2",
+ "react-select": "^1.1.0",
"react-transition-group": "^2.2.1",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
diff --git a/src/assets/tos.pdf b/src/assets/tos.pdf
new file mode 100644
index 00000000..501795a6
Binary files /dev/null and b/src/assets/tos.pdf differ
diff --git a/src/data/appConfig.json b/src/data/appConfig.json
new file mode 100644
index 00000000..2c9ce85b
--- /dev/null
+++ b/src/data/appConfig.json
@@ -0,0 +1,81 @@
+{
+ "coins1": [
+ {
+ "name": "Ethereum Ropsten",
+ "symbol": "eth",
+ "network": "ropsten-eth",
+ "shortcut": "eth",
+ "bip44": "m/44'/60'/0'/0",
+ "defaultGasPrice": 64,
+ "defaultGasLimit": 21000,
+ "defaultGasLimitTokens": 200000,
+ "backends": [
+ {
+ "name": "TREZOR Wallet - Ethereum",
+ "urls": [
+ "https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4",
+ "http://10.34.2.5:8545"
+ ],
+ "explorer": "https://blockexplorer.com"
+ }
+ ]
+ }
+ ],
+
+ "coins": [
+ {
+ "name": "Ethereum Ropsten",
+ "symbol": "eth",
+ "network": "ropsten-eth",
+ "shortcut": "eth",
+ "bip44": "m/44'/60'/0'/0",
+ "defaultGasPrice": 64,
+ "defaultGasLimit": 21000,
+ "defaultGasLimitTokens": 200000,
+ "backends": [
+ {
+ "name": "TREZOR Wallet - Ethereum",
+ "urls": [
+ "https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4",
+ "http://10.34.2.5:8545"
+ ],
+ "explorer": "https://blockexplorer.com"
+ }
+ ]
+ },
+ {
+ "name": "Ethereum Rinkeby",
+ "symbol": "etc",
+ "network": "ropsten-eth",
+ "shortcut": "etc",
+ "bip44": "m/44'/61'/0'/0",
+ "defaultGasPrice": 64,
+ "defaultGasLimit": 21000,
+ "defaultGasLimitTokens": 200000,
+ "backends": [
+ {
+ "name": "TREZOR Wallet - Ethereum",
+ "urls": [
+ "https://rinkeby.infura.io/QGyVKozSUEh2YhL4s2G4",
+ "http://10.34.2.5:8545"
+ ],
+ "explorer": "https://blockexplorer.com"
+ }
+ ]
+ }
+ ],
+
+ "fiatValueTickers": [
+
+ ],
+
+ "bridge": {
+ "url": "https://localback.net:21324",
+ "configUrl": "data/config_signed.bin",
+ "latestUrl": "data/bridge/latest.txt"
+ },
+ "extensionId": "jcjjhjgimijdkoamemaghajlhegmoclj",
+ "storageVersion": "1.1.0",
+ "metadataVersion": "1.0.0"
+
+}
\ No newline at end of file
diff --git a/src/data/ethERC20.json b/src/data/ethERC20.json
new file mode 100644
index 00000000..68cd8d8e
--- /dev/null
+++ b/src/data/ethERC20.json
@@ -0,0 +1,152 @@
+[
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {
+ "name":"",
+ "type":"string"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {"name":"_spender", "type":"address"},
+ {"name":"_value","type":"uint256"}
+ ],
+ "name":"approve",
+ "outputs":[
+ {"name":"success","type":"bool"}
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[],
+ "name":"totalSupply",
+ "outputs":[
+ {"name":"","type":"uint256"}
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {"name":"_from","type":"address"},
+ {"name":"_to","type":"address"},
+ {"name":"_value","type":"uint256"}
+ ],
+ "name":"transferFrom",
+ "outputs":[
+ {"name":"success","type":"bool"}
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[],
+ "name":"decimals",
+ "outputs":[
+ {"name":"","type":"uint8"}
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[],
+ "name":"version",
+ "outputs":[
+ {"name":"","type":"string"}
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
+ {"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},
+ {
+ "constant":false,
+ "inputs":[
+ {"name":"_to","type":"address"},
+ {"name":"_value","type":"uint256"}
+ ],
+ "name":"transfer",
+ "outputs":[
+ {"name":"success","type":"bool"}
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {"name":"_spender","type":"address"},
+ {"name":"_value","type":"uint256"},
+ {"name":"_extraData","type":"bytes"}
+ ],
+ "name":"approveAndCall",
+ "outputs":[
+ {"name":"success","type":"bool"}
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+ {"name":"_owner","type":"address"},
+ {"name":"_spender","type":"address"}
+ ],
+ "name":"allowance",
+ "outputs":[
+ {"name":"remaining","type":"uint256"}
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "inputs":[],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"constructor"
+ },
+ {
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"fallback"
+ },{
+ "anonymous":false,
+ "inputs":[
+ {"indexed":true,"name":"_from","type":"address"},
+ {"indexed":true,"name":"_to","type":"address"},
+ {"indexed":false,"name":"_value","type":"uint256"}
+ ],
+ "name":"Transfer",
+ "type":"event"
+ },{
+ "anonymous":false,
+ "inputs":[
+ {"indexed":true,"name":"_owner","type":"address"},
+ {"indexed":true,"name":"_spender","type":"address"},
+ {"indexed":false,"name":"_value","type":"uint256"}
+ ],
+ "name":"Approval",
+ "type":"event"
+ }
+]
\ No newline at end of file
diff --git a/src/data/ethTokens.json b/src/data/ethTokens.json
new file mode 100644
index 00000000..4b95fd74
--- /dev/null
+++ b/src/data/ethTokens.json
@@ -0,0 +1,2504 @@
+[
+ {
+ "address": "0xfdbc1adc26f0f8f8606a5d63b7d3a3cd21c22b23",
+ "name": "1World",
+ "symbol": "1WO",
+ "decimals": 8
+ },
+ {
+ "address": "0xAf30D2a7E90d7DC361c8C4585e9BB7D2F6f15bc7",
+ "name": "Firstblood",
+ "symbol": "1ST",
+ "decimals": 18
+ },
+ {
+ "address": "0xaEc98A708810414878c3BCDF46Aad31dEd4a4557",
+ "name": "300 Token",
+ "symbol": "300",
+ "decimals": 18
+ },
+ {
+ "address": "0x13f1b7fdfbe1fc66676d56483e21b1ecb40b58e2",
+ "name": "Accelerator",
+ "symbol": "ACC",
+ "decimals": 18
+ },
+ {
+ "address": "0x8810C63470d38639954c6B41AaC545848C46484a",
+ "name": "Aditus",
+ "symbol": "ADI",
+ "decimals": 18
+ },
+ {
+ "address": "0x422866a8F0b032c5cf1DfBDEf31A20F4509562b0",
+ "name": "Adshares Token",
+ "symbol": "ADST",
+ "decimals": 0
+ },
+ {
+ "address": "0xD0D6D6C5Fe4a677D343cC433536BB717bAe167dD",
+ "name": "AdToken",
+ "symbol": "ADT",
+ "decimals": 9
+ },
+ {
+ "address": "0x4470BB87d77b963A013DB939BE332f927f2b992e",
+ "name": "AdEx",
+ "symbol": "ADX",
+ "decimals": 4
+ },
+ {
+ "address": "0x27dce1ec4d3f72c3e457cc50354f1f975ddef488",
+ "name": "AirToken",
+ "symbol": "AIR",
+ "decimals": 8
+ },
+ {
+ "address": "0x1063ce524265d5a3A624f4914acd573dD89ce988",
+ "name": "Aigang",
+ "symbol": "AIX",
+ "decimals": 18
+ },
+ {
+ "address": "0xEA610B1153477720748DC13ED378003941d84fAB",
+ "name": "AlisToken",
+ "symbol": "ALIS",
+ "decimals": 18
+ },
+ {
+ "address": "0x181a63746d3adcf356cbc73ace22832ffbb1ee5a",
+ "name": "Alaricoin",
+ "symbol": "ALCO",
+ "decimals": 8
+ },
+ {
+ "address": "0x638ac149ea8ef9a1286c41b977017aa7359e6cfa",
+ "name": "Altcoins",
+ "symbol": "ALTS",
+ "decimals": 18
+ },
+ {
+ "address": "0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce",
+ "name": "Amber Token",
+ "symbol": "AMB",
+ "decimals": 18
+ },
+ {
+ "address": "0x949bed886c739f1a3273629b3320db0c5024c719",
+ "name": "AMIS",
+ "symbol": "AMIS",
+ "decimals": 9
+ },
+ {
+ "address": "0x960b236A07cf122663c4303350609A66A7B288C0",
+ "name": "Aragon Network Token",
+ "symbol": "ANT",
+ "decimals": 18
+ },
+ {
+ "address": "0x1a7a8bd9106f2b8d977e08582dc7d24c723ab0db",
+ "name": "AppCoins",
+ "symbol": "APPC",
+ "decimals": 18
+ },
+ {
+ "address": "0x23ae3c5b39b12f0693e05435eeaa1e51d8c61530",
+ "name": "Aigang Pre-Launch Token",
+ "symbol": "APT",
+ "decimals": 18
+ },
+ {
+ "address": "0xAc709FcB44a43c35F0DA4e3163b117A17F3770f5",
+ "name": "Arcade Token",
+ "symbol": "ARC",
+ "decimals": 18
+ },
+ {
+ "address": "0x1245ef80f4d9e02ed9425375e8f649b9221b31d8",
+ "name": "ArbitrageCT",
+ "symbol": "ARCT",
+ "decimals": 8
+ },
+ {
+ "address": "0x75aa7b0d02532f3833b66c7f0ad35376d373ddf8",
+ "name": "Accord",
+ "symbol": "ARD",
+ "decimals": 18
+ },
+ {
+ "address": "0xBA5F11b16B155792Cf3B2E6880E8706859A8AEB6",
+ "name": "Aeron",
+ "symbol": "ARN",
+ "decimals": 8
+ },
+ {
+ "address": "0xfec0cF7fE078a500abf15F1284958F22049c2C7e",
+ "name": "Maecenas",
+ "symbol": "ART",
+ "decimals": 18
+ },
+ {
+ "address": "0x7705FaA34B16EB6d77Dfc7812be2367ba6B0248e",
+ "name": "Artex Token",
+ "symbol": "ARX",
+ "decimals": 8
+ },
+ {
+ "address": "0x27054b13b1B798B345b591a4d22e6562d47eA75a",
+ "name": "AirSwap Token",
+ "symbol": "AST",
+ "decimals": 4
+ },
+ {
+ "address": "0x17052d51E954592C1046320c2371AbaB6C73Ef10",
+ "name": "ATHENIAN WARRIOR",
+ "symbol": "ATH",
+ "decimals": 18
+ },
+ {
+ "address": "0x78B7FADA55A64dD895D8c8c35779DD8b67fA8a05",
+ "name": "ATLANT Token",
+ "symbol": "ATL",
+ "decimals": 18
+ },
+ {
+ "address": "0x887834d3b8d450b6bab109c252df3da286d73ce4",
+ "name": "Atmatrix Token",
+ "symbol": "ATT",
+ "decimals": 18
+ },
+ {
+ "address": "0x0d88ed6e74bbfd96b831231638b66c05571e824f",
+ "name": "AVENTUS",
+ "symbol": "AVT",
+ "decimals": 18
+ },
+ {
+ "address": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF",
+ "name": "Basic Attention Token",
+ "symbol": "BAT",
+ "decimals": 18
+ },
+ {
+ "address": "0x7367a68039d4704f30bfbf6d948020c3b07dfc59",
+ "name": "Beercoin",
+ "symbol": "\ud83c\udf7a",
+ "decimals": 18
+ },
+ {
+ "address": "0x1e797Ce986C3CFF4472F7D38d5C4aba55DfEFE40",
+ "name": "BCDN",
+ "symbol": "BCDN",
+ "decimals": 15
+ },
+ {
+ "address": "0xacfa209fb73bf3dd5bbfb1101b9bc999c49062a5",
+ "name": "Blockchain Certified Data Token",
+ "symbol": "BCDT",
+ "decimals": 18
+ },
+ {
+ "address": "0x1c4481750daa5Ff521A2a7490d9981eD46465Dbd",
+ "name": "BLOCKMASON CREDIT PROTOCOL TOKEN",
+ "symbol": "BCPT",
+ "decimals": 18
+ },
+ {
+ "address": "0x74C1E4b8caE59269ec1D85D3D4F324396048F4ac",
+ "name": "Beercoin",
+ "symbol": "ALE",
+ "decimals": 0
+ },
+ {
+ "address": "0x8aA33A7899FCC8eA5fBe6A608A109c3893A1B8b2",
+ "name": "Dao.Casino",
+ "symbol": "BET",
+ "decimals": 18
+ },
+ {
+ "address": "0xb2bfeb70b903f1baac7f2ba2c62934c7e5b974c4",
+ "name": "BetKing Bankroll Token",
+ "symbol": "BKB",
+ "decimals": 8
+ },
+ {
+ "address": "0x107c4504cd79C5d2696Ea0030a8dD4e92601B82e",
+ "name": "Bloom Token",
+ "symbol": "BLT",
+ "decimals": 18
+ },
+ {
+ "address": "0xce59d29b09aae565feeef8e52f47c3cd5368c663",
+ "name": "Bullioncoin",
+ "symbol": "BLX",
+ "decimals": 18
+ },
+ {
+ "address": "0xE5a7c12972f3bbFe70ed29521C8949b8Af6a0970",
+ "name": "Blockchain Index",
+ "symbol": "BLX",
+ "decimals": 18
+ },
+ {
+ "address": "0xdf6ef343350780bf8c3410bf062e0c015b1dd671",
+ "name": "Blackmoon Crypto Token",
+ "symbol": "BMC",
+ "decimals": 8
+ },
+ {
+ "address": "0xf028adee51533b1b47beaa890feb54a457f51e89",
+ "name": "BMChain Token",
+ "symbol": "BMT",
+ "decimals": 18
+ },
+ {
+ "address": "0x986EE2B944c42D017F52Af21c4c69B84DBeA35d8",
+ "name": "BitMartToken",
+ "symbol": "BMC",
+ "decimals": 18
+ },
+ {
+ "address": "0xb8c77482e45f1f44de1745f52c74426c631bdd52",
+ "name": "BNB",
+ "symbol": "BNB",
+ "decimals": 18
+ },
+ {
+ "address": "0xdD6Bf56CA2ada24c683FAC50E37783e55B57AF9F",
+ "name": "Brave New Coin",
+ "symbol": "BNC",
+ "decimals": 12
+ },
+ {
+ "address": "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C",
+ "name": "Bancor Network Token",
+ "symbol": "BNT",
+ "decimals": 18
+ },
+ {
+ "address": "0xd2d6158683aee4cc838067727209a0aaf4359de3",
+ "name": "Bounty0x Token",
+ "symbol": "BNTY",
+ "decimals": 18
+ },
+ {
+ "address": "0xCc34366E3842cA1BD36c1f324d15257960fCC801",
+ "name": "Bonpay Token",
+ "symbol": "BON",
+ "decimals": 18
+ },
+ {
+ "address": "0x7f1e2c7d6a69bf34824d72c53b4550e895c0d8c2",
+ "name": "blockoptions",
+ "symbol": "BOP",
+ "decimals": 8
+ },
+ {
+ "address": "0xC2C63F23ec5E97efbD7565dF9Ec764FDc7d4e91d",
+ "name": "Boule Token",
+ "symbol": "BOU",
+ "decimals": 18
+ },
+ {
+ "address": "0x5Af2Be193a6ABCa9c8817001F45744777Db30756",
+ "name": "Bitquence",
+ "symbol": "BQX",
+ "decimals": 8
+ },
+ {
+ "address": "0x9E77D5a1251b6F7D456722A6eaC6D2d5980bd891",
+ "name": "BRAT RED",
+ "symbol": "BRAT",
+ "decimals": 8
+ },
+ {
+ "address": "0xf26ef5e0545384b7dcc0f297f2674189586830df",
+ "name": "BitsIdea",
+ "symbol": "BSDC",
+ "decimals": 18
+ },
+ {
+ "address": "0x0886949c1b8C412860c4264Ceb8083d1365e86CF",
+ "name": "EthereumBitcoin",
+ "symbol": "BTCE",
+ "decimals": 8
+ },
+ {
+ "address": "0x73dd069c299a5d691e9836243bcaec9c8c1d8734",
+ "name": "Bitcoineum",
+ "symbol": "BTE",
+ "decimals": 8
+ },
+ {
+ "address": "0x1961B3331969eD52770751fC718ef530838b6dEE",
+ "name": "BitDegree Token",
+ "symbol": "BDG",
+ "decimals": 18
+ },
+ {
+ "address": "0xfad572db566e5234ac9fc3d570c4edc0050eaa92",
+ "name": "Bytether",
+ "symbol": "BTH",
+ "decimals": 18
+ },
+ {
+ "address": "0x2accaB9cb7a48c3E82286F0b2f8798D201F4eC3f",
+ "name": "Battle",
+ "symbol": "BTL",
+ "decimals": 18
+ },
+ {
+ "address": "0x92685E93956537c25Bb75D5d47fca4266dd628B8",
+ "name": "Bitlle Token",
+ "symbol": "BTL",
+ "decimals": 4
+ },
+ {
+ "address": "0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750",
+ "name": "Bytom",
+ "symbol": "BTM",
+ "decimals": 8
+ },
+ {
+ "address": "0x16B0E62aC13a2fAeD36D18bce2356d25Ab3CfAD3",
+ "name": "Bitcoin Boutique",
+ "symbol": "BTQ",
+ "decimals": 18
+ },
+ {
+ "address": "0x26E75307Fc0C021472fEb8F727839531F112f317",
+ "name": "Crypto20",
+ "symbol": "C20",
+ "decimals": 18
+ },
+ {
+ "address": "0x7d4b8Cce0591C9044a22ee543533b72E976E36C3",
+ "name": "Change COIN",
+ "symbol": "CAG",
+ "decimals": 18
+ },
+ {
+ "address": "0x1d462414fe14cf489c7A21CaC78509f4bF8CD7c0",
+ "name": "CanYaCoin",
+ "symbol": "CAN",
+ "decimals": 6
+ },
+ {
+ "address": "0xe8780B48bdb05F928697A5e8155f672ED91462F7",
+ "name": "Cashaa",
+ "symbol": "CAS",
+ "decimals": 18
+ },
+ {
+ "address": "0x1234567461d3f8db7496581774bd869c83d51c93",
+ "name": "BitClave",
+ "symbol": "CAT",
+ "decimals": 18
+ },
+ {
+ "address": "0x68e14bb5A45B9681327E16E528084B9d962C1a39",
+ "name": "BitClave - Consumer Activity Token",
+ "symbol": "CAT",
+ "decimals": 18
+ },
+ {
+ "address": "0x56ba2Ee7890461f463F7be02aAC3099f6d5811A8",
+ "name": "BlockCAT Token",
+ "symbol": "CAT",
+ "decimals": 18
+ },
+ {
+ "address": "0xc166038705FFBAb3794185b3a9D925632A1DF37D",
+ "name": "Coal Coin",
+ "symbol": "CC3",
+ "decimals": 18
+ },
+ {
+ "address": "0x28577A6d31559bd265Ce3ADB62d0458550F7b8a7",
+ "name": "Crypto Crash Course",
+ "symbol": "CCC",
+ "decimals": 18
+ },
+ {
+ "address": "0xbe11eeb186e624b8f26a5045575a1340e4054552",
+ "name": "Crush Crypto Core",
+ "symbol": "CCC",
+ "decimals": 18
+ },
+ {
+ "address": "0xd348e07a2806505b856123045d27aeed90924b50",
+ "name": "Christ Coin",
+ "symbol": "CCLC",
+ "decimals": 8
+ },
+ {
+ "address": "0x8a95ca448A52C0ADf0054bB3402dC5e09CD6B232",
+ "name": "Confideal",
+ "symbol": "CDL",
+ "decimals": 18
+ },
+ {
+ "address": "0x177d39AC676ED1C67A2b268AD7F1E58826E5B0af",
+ "name": "CoinDash Token",
+ "symbol": "CDT",
+ "decimals": 18
+ },
+ {
+ "address": "0x6fFF3806Bbac52A20e0d79BC538d527f6a22c96b",
+ "name": "Commodity Ad Network",
+ "symbol": "CDX",
+ "decimals": 18
+ },
+ {
+ "address": "0x12FEF5e57bF45873Cd9B62E9DBd7BFb99e32D73e",
+ "name": "Cofoundit",
+ "symbol": "CFI",
+ "decimals": 18
+ },
+ {
+ "address": "0x06012c8cf97bead5deae237070f9587f8e7a266d",
+ "name": "CryptoKitties",
+ "symbol": "CK",
+ "decimals": 0
+ },
+ {
+ "address": "0x7fce2856899a6806eeef70807985fc7554c66340",
+ "name": "CLP Token",
+ "symbol": "CLP",
+ "decimals": 9
+ },
+ {
+ "address": "0x7e667525521cF61352e2E01b50FaaaE7Df39749a",
+ "name": "CMC",
+ "symbol": "CMC",
+ "decimals": 18
+ },
+ {
+ "address": "0xf85fEea2FdD81d51177F6b8F35F0e6734Ce45F5F",
+ "name": "CyberMiles Token",
+ "symbol": "CMT",
+ "decimals": 18
+ },
+ {
+ "address": "0xd4c435f5b09f855c3317c8524cb1f586e42795fa",
+ "name": "Cindicator Token",
+ "symbol": "CND",
+ "decimals": 18
+ },
+ {
+ "address": "0xB4b1D2C217EC0776584CE08D3DD98F90EDedA44b",
+ "name": "Climatecoin",
+ "symbol": "CO2",
+ "decimals": 18
+ },
+ {
+ "address": "0xb2f7eb1f2c37645be61d73953035360e768d81e6",
+ "name": "Cobinhood Token",
+ "symbol": "COB",
+ "decimals": 18
+ },
+ {
+ "address": "0x3136eF851592aCf49CA4C825131E364170FA32b3",
+ "name": "CoinFi",
+ "symbol": "COFI",
+ "decimals": 18
+ },
+ {
+ "address": "0x65292eeadf1426cd2df1c4793a3d7519f253913b",
+ "name": "COSS",
+ "symbol": "COSS",
+ "decimals": 18
+ },
+ {
+ "address": "0xAef38fBFBF932D1AeF3B808Bc8fBd8Cd8E1f8BC5",
+ "name": "CreditBIT",
+ "symbol": "CRB",
+ "decimals": 8
+ },
+ {
+ "address": "0x672a1AD4f667FB18A333Af13667aa0Af1F5b5bDD",
+ "name": "Verify Token",
+ "symbol": "CRED",
+ "decimals": 18
+ },
+ {
+ "address": "0x4e0603e2a27a30480e5e3a4fe548e29ef12f64be",
+ "name": "Credo Token",
+ "symbol": "CREDO",
+ "decimals": 18
+ },
+ {
+ "address": "0x80a7e048f37a50500351c204cb407766fa3bae7f",
+ "name": "CrypteriumToken",
+ "symbol": "CRPT",
+ "decimals": 18
+ },
+ {
+ "address": "0xE4c94d45f7Aef7018a5D66f44aF780ec6023378e",
+ "name": "CryptoCarbon",
+ "symbol": "",
+ "decimals": 0
+ },
+ {
+ "address": "0xbf4cfd7d1edeeea5f6600827411b41a21eb08abd",
+ "name": "CryptoLah",
+ "symbol": "CTL",
+ "decimals": 2
+ },
+ {
+ "address": "0xE3Fa177AcecfB86721Cf6f9f4206bd3Bd672D7d5",
+ "name": "ChainTrade Coin",
+ "symbol": "CTC",
+ "decimals": 18
+ },
+ {
+ "address": "0x662aBcAd0b7f345AB7FfB1b1fbb9Df7894f18e66",
+ "name": "CarTaxi",
+ "symbol": "CTX",
+ "decimals": 18
+ },
+ {
+ "address": "0xdA6cb58A0D0C01610a29c5A65c303e13e885887C",
+ "name": "cVToken",
+ "symbol": "cV",
+ "decimals": 18
+ },
+ {
+ "address": "0x41e5560054824ea6b0732e656e3ad64e20e94e45",
+ "name": "Civic",
+ "symbol": "CVC",
+ "decimals": 8
+ },
+ {
+ "address": "0xb6EE9668771a79be7967ee29a63D4184F8097143",
+ "name": "CargoX Token",
+ "symbol": "CXO",
+ "decimals": 18
+ },
+ {
+ "address": "0xdab0C31BF34C897Fb0Fe90D12EC9401caf5c36Ec",
+ "name": "DABcoin",
+ "symbol": "DAB",
+ "decimals": 0
+ },
+ {
+ "address": "0x07d9e49ea402194bf48a8276dafb16e4ed633317",
+ "name": "DALECOIN",
+ "symbol": "DALC",
+ "decimals": 8
+ },
+ {
+ "address": "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413",
+ "name": "The DAO",
+ "symbol": "DAO",
+ "decimals": 16
+ },
+ {
+ "address": "0x81c9151de0c8bafcd325a57e3db5a5df1cebf79c",
+ "name": "DAT Token",
+ "symbol": "DAT",
+ "decimals": 18
+ },
+ {
+ "address": "0x1b5f21ee98eed48d292e8e2d3ed82b40a9728a22",
+ "name": "DataBroker DAO Token",
+ "symbol": "DATA",
+ "decimals": 18
+ },
+ {
+ "address": "0x0cf0ee63788a0849fe5297f3407f701e122cc023",
+ "name": "DATAcoin",
+ "symbol": "DATA",
+ "decimals": 18
+ },
+ {
+ "address": "0x399A0e6FbEb3d74c85357439f4c8AeD9678a5cbF",
+ "name": "DISLEDGER",
+ "symbol": "DCL",
+ "decimals": 3
+ },
+ {
+ "address": "0x08d32b0da63e2C3bcF8019c9c5d849d7a9d791e6",
+ "name": "Dentacoin",
+ "symbol": "\u0668",
+ "decimals": 0
+ },
+ {
+ "address": "0x08d32b0da63e2C3bcF8019c9c5d849d7a9d791e6",
+ "name": "Dentacoin",
+ "symbol": "\u0668",
+ "decimals": 0
+ },
+ {
+ "address": "0xcC4eF9EEAF656aC1a2Ab886743E98e97E090ed38",
+ "name": "Digital Developers Fund Token",
+ "symbol": "DDF",
+ "decimals": 18
+ },
+ {
+ "address": "0x3597bfd533a99c9aa083587b074434e61eb0a258",
+ "name": "DENT",
+ "symbol": "DENT",
+ "decimals": 8
+ },
+ {
+ "address": "0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A",
+ "name": "Digix DAO",
+ "symbol": "DGD",
+ "decimals": 9
+ },
+ {
+ "address": "0xf6cFe53d6FEbaEEA051f400ff5fc14F0cBBDacA1",
+ "name": "DigiPulse Token",
+ "symbol": "DGPT",
+ "decimals": 18
+ },
+ {
+ "address": "0x55b9a11c2e8351b4Ffc7b11561148bfaC9977855",
+ "name": "DigixGold",
+ "symbol": "DGX",
+ "decimals": 9
+ },
+ {
+ "address": "0x2e071D2966Aa7D8dECB1005885bA1977D6038A65",
+ "name": "DICE",
+ "symbol": "ROL",
+ "decimals": 16
+ },
+ {
+ "address": "0x13f11C9905A08ca76e3e853bE63D4f0944326C72",
+ "name": "Divi Exchange Token",
+ "symbol": "DIVX",
+ "decimals": 18
+ },
+ {
+ "address": "0x07e3c70653548b04f0a75970c1f81b4cbbfb606f",
+ "name": "Delta",
+ "symbol": "DLT",
+ "decimals": 18
+ },
+ {
+ "address": "0x2ccbFF3A042c68716Ed2a2Cb0c544A9f1d1935E1",
+ "name": "DMarket Token",
+ "symbol": "DMT",
+ "decimals": 8
+ },
+ {
+ "address": "0x0abdace70d3790235af448c88547603b945604ea",
+ "name": "district0x Network Token",
+ "symbol": "DNT",
+ "decimals": 18
+ },
+ {
+ "address": "0xE43E2041dc3786e166961eD9484a5539033d10fB",
+ "name": "DenCity",
+ "symbol": "DNX",
+ "decimals": 18
+ },
+ {
+ "address": "0xEEF6E90034eEa89E31Eb4B8eaCd323F28A92eaE4",
+ "name": "DOW",
+ "symbol": "dow",
+ "decimals": 18
+ },
+ {
+ "address": "0x01b3Ec4aAe1B8729529BEB4965F27d008788B0EB",
+ "name": "DA Power Play Token",
+ "symbol": "DPP",
+ "decimals": 18
+ },
+ {
+ "address": "0x419c4db4b9e25d6db2ad9691ccb832c8d9fda05e",
+ "name": "Dragon",
+ "symbol": "DRGN",
+ "decimals": 18
+ },
+ {
+ "address": "0x3c75226555FC496168d48B88DF83B95F16771F37",
+ "name": "Droplex Token",
+ "symbol": "DROP",
+ "decimals": 0
+ },
+ {
+ "address": "0x621d78f2ef2fd937bfca696cabaf9a779f59b3ed",
+ "name": "DCORP",
+ "symbol": "DRP",
+ "decimals": 2
+ },
+ {
+ "address": "0x1e09BD8Cadb441632e441Db3e1D79909EE0A2256",
+ "name": "Digital Safe Coin",
+ "symbol": "DSC",
+ "decimals": 1
+ },
+ {
+ "address": "0xd234bf2410a0009df9c3c63b610c09738f18ccd7",
+ "name": "Dynamic Trading Rights",
+ "symbol": "DTR",
+ "decimals": 8
+ },
+ {
+ "address": "0xd4cffeef10f60eca581b5e1146b5aca4194a4c3b",
+ "name": "Decentralized Universal Basic Income",
+ "symbol": "DUBI",
+ "decimals": 18
+ },
+ {
+ "address": "0x994f0dffdbae0bbf09b652d6f11a493fd33f42b9",
+ "name": "EagleCoin",
+ "symbol": "EAGLE",
+ "decimals": 18
+ },
+ {
+ "address": "0xafc39788c51f0c1ff7b55317f3e70299e521fff6",
+ "name": "eBitcoinCash",
+ "symbol": "eBCH",
+ "decimals": 8
+ },
+ {
+ "address": "0xeb7c20027172e5d143fb030d50f91cece2d1485d",
+ "name": "eBTC",
+ "symbol": "EBTC",
+ "decimals": 8
+ },
+ {
+ "address": "0xa578acc0cb7875781b7880903f4594d13cfa8b98",
+ "name": "EtherCarbon",
+ "symbol": "ECN",
+ "decimals": 2
+ },
+ {
+ "address": "0x17F93475d2A978f527c3f7c44aBf44AdfBa60D5C",
+ "name": "EtherCO2",
+ "symbol": "ECO2",
+ "decimals": 2
+ },
+ {
+ "address": "0x08711D3B02C8758F2FB3ab4e80228418a7F8e39c",
+ "name": "Edgeless",
+ "symbol": "EDG",
+ "decimals": 0
+ },
+ {
+ "address": "0xced4e93198734ddaff8492d525bd258d49eb388e",
+ "name": "Eidoo Token",
+ "symbol": "EDO",
+ "decimals": 18
+ },
+ {
+ "address": "0xb53a96bcbdd9cf78dff20bab6c2be7baec8f00f8",
+ "name": "ETHGAS",
+ "symbol": "eGAS",
+ "decimals": 8
+ },
+ {
+ "address": "0xf9F0FC7167c311Dd2F1e21E9204F87EBA9012fB2",
+ "name": "EasyHomes",
+ "symbol": "EHT",
+ "decimals": 8
+ },
+ {
+ "address": "0xc8C6A31A4A806d3710A7B38b7B296D2fABCCDBA8",
+ "name": "elixir",
+ "symbol": "ELIX",
+ "decimals": 18
+ },
+ {
+ "address": "0x44197a4c44d6a059297caf6be4f7e172bd56caaf",
+ "name": "ELTCOIN",
+ "symbol": "ELTCOIN",
+ "decimals": 8
+ },
+ {
+ "address": "0xb67b88a25708a35ae7c2d736d398d268ce4f7f83",
+ "name": "Etheremon",
+ "symbol": "EMON",
+ "decimals": 8
+ },
+ {
+ "address": "0xB802b24E0637c2B87D2E8b7784C055BBE921011a",
+ "name": "EthereumMovieVenture",
+ "symbol": "EMV",
+ "decimals": 2
+ },
+ {
+ "address": "0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c",
+ "name": "Enjin Coin",
+ "symbol": "ENJ",
+ "decimals": 18
+ },
+ {
+ "address": "0xd780Ae2Bf04cD96E577D3D014762f831d97129d0",
+ "name": "Envion",
+ "symbol": "EVN",
+ "decimals": 18
+ },
+ {
+ "address": "0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0",
+ "name": "EOS",
+ "symbol": "EOS",
+ "decimals": 18
+ },
+ {
+ "address": "0xe8a1df958be379045e2b46a31a98b93a2ecdfded",
+ "name": "ESZCoin",
+ "symbol": "ESZ",
+ "decimals": 18
+ },
+ {
+ "address": "0x1b9743f556d65e757c4c650b4555baf354cb8bd3",
+ "name": "EthBits ETBS Token",
+ "symbol": "ETBS",
+ "decimals": 12
+ },
+ {
+ "address": "0x3a26746Ddb79B1B8e4450e3F4FFE3285A307387E",
+ "name": "EtherBIT",
+ "symbol": "ETHB",
+ "decimals": 8
+ },
+ {
+ "address": "0xabdf147870235fcfc34153828c769a70b3fae01f",
+ "name": "Tether EUR",
+ "symbol": "EURT",
+ "decimals": 6
+ },
+ {
+ "address": "0x923108a439C4e8C2315c4f6521E5cE95B44e9B4c",
+ "name": "Devery.io",
+ "symbol": "EVE",
+ "decimals": 18
+ },
+ {
+ "address": "0xf3db5fa2c66b7af3eb0c0b782510816cbe4813b8",
+ "name": "Everex",
+ "symbol": "EVX",
+ "decimals": 4
+ },
+ {
+ "address": "0xc98e0639c6d2ec037a615341c369666b110e80e5",
+ "name": "EXMR",
+ "symbol": "EXMR",
+ "decimals": 8
+ },
+ {
+ "address": "0x190e569bE071F40c704e15825F285481CB74B6cC",
+ "name": "Fame",
+ "symbol": "FAM",
+ "decimals": 12
+ },
+ {
+ "address": "0xf04a8ac553FceDB5BA99A64799155826C136b0Be",
+ "name": "Flixx",
+ "symbol": "FLIXX",
+ "decimals": 18
+ },
+ {
+ "address": "0x3a1Bda28AdB5B0a812a7CF10A1950c920F79BcD3",
+ "name": "FLIP Token",
+ "symbol": "FLP",
+ "decimals": 18
+ },
+ {
+ "address": "0x0ABeFb7611Cb3A01EA3FaD85f33C3C934F8e2cF4",
+ "name": "FARAD",
+ "symbol": "FRD",
+ "decimals": 18
+ },
+ {
+ "address": "0xe6f74dcfa0e20883008d8c16b6d9a329189d0c30",
+ "name": "FinTech Coin",
+ "symbol": "FTC",
+ "decimals": 2
+ },
+ {
+ "address": "0xab16e0d25c06cb376259cc18c1de4aca57605589",
+ "name": "FinallyUsableCryptoKarma",
+ "symbol": "FUCK",
+ "decimals": 4
+ },
+ {
+ "address": "0xEA38eAa3C86c8F9B751533Ba2E562deb9acDED40",
+ "name": "Fuel Token",
+ "symbol": "FUEL",
+ "decimals": 18
+ },
+ {
+ "address": "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b",
+ "name": "FunFair",
+ "symbol": "FUN",
+ "decimals": 8
+ },
+ {
+ "address": "0x88FCFBc22C6d3dBaa25aF478C578978339BDe77a",
+ "name": "FundYourselfNow Token",
+ "symbol": "FYN",
+ "decimals": 18
+ },
+ {
+ "address": "0xf67451dc8421f0e0afeb52faa8101034ed081ed9",
+ "name": "Gambit",
+ "symbol": "GAM",
+ "decimals": 8
+ },
+ {
+ "address": "0x4F4f0Db4de903B88f2B1a2847971E231D54F8fd3",
+ "name": "Geens Platform Token",
+ "symbol": "GEE",
+ "decimals": 8
+ },
+ {
+ "address": "0x24083bb30072643c3bb90b44b7285860a755e687",
+ "name": "SGelderGER",
+ "symbol": "GELD",
+ "decimals": 18
+ },
+ {
+ "address": "0xaE4f56F072c34C0a65B3ae3E4DB797D831439D93",
+ "name": "Gimli Token",
+ "symbol": "GIM",
+ "decimals": 8
+ },
+ {
+ "address": "0xb3Bd49E28f8F832b8d1E246106991e546c323502",
+ "name": "Global Messaging Token",
+ "symbol": "GMT",
+ "decimals": 18
+ },
+ {
+ "address": "0x6810e776880C02933D47DB1b9fc05908e5386b96",
+ "name": "Gnosis Token",
+ "symbol": "GNO",
+ "decimals": 18
+ },
+ {
+ "address": "0xa74476443119A942dE498590Fe1f2454d7D4aC0d",
+ "name": "Golem Network Token",
+ "symbol": "GNT",
+ "decimals": 18
+ },
+ {
+ "address": "0xeAb43193CF0623073Ca89DB9B712796356FA7414",
+ "name": "GOLDX",
+ "symbol": "GOLDX",
+ "decimals": 18
+ },
+ {
+ "address": "0x8C65e992297d5f092A756dEf24F4781a280198Ff",
+ "name": "GazeCoin",
+ "symbol": "GZE",
+ "decimals": 18
+ },
+ {
+ "address": "0x12b19d3e2ccc14da04fae33e63652ce469b3f2fd",
+ "name": "GRID Token",
+ "symbol": "GRID",
+ "decimals": 12
+ },
+ {
+ "address": "0xB70835D7822eBB9426B56543E391846C107bd32C",
+ "name": "Game.com Token",
+ "symbol": "GTC",
+ "decimals": 18
+ },
+ {
+ "address": "0x025abad9e518516fdaafbdcdb9701b37fb7ef0fa",
+ "name": "GoldenTickets",
+ "symbol": "GTKT",
+ "decimals": 0
+ },
+ {
+ "address": "0xf7B098298f7C69Fc14610bf71d5e02c60792894C",
+ "name": "Guppy",
+ "symbol": "GUP",
+ "decimals": 3
+ },
+ {
+ "address": "0x103c3A209da59d3E7C4A89307e66521e081CFDF0",
+ "name": "Genesis Vision Token",
+ "symbol": "GVT",
+ "decimals": 18
+ },
+ {
+ "address": "0x58ca3065c0f24c7c96aee8d6056b5b5decf9c2f8",
+ "name": "GXC",
+ "symbol": "GXC",
+ "decimals": 10
+ },
+ {
+ "address": "0x22F0AF8D78851b72EE799e05F54A77001586B18A",
+ "name": "Genevieve VC",
+ "symbol": "GXVC",
+ "decimals": 10
+ },
+ {
+ "address": "0x84543f868ec1b1fac510d49d13c069f64cd2d5f9",
+ "name": "HEdpAY",
+ "symbol": "Hdp.\u0444",
+ "decimals": 18
+ },
+ {
+ "address": "0xffe8196bc259e8dedc544d935786aa4709ec3e64",
+ "name": "Hedge",
+ "symbol": "HDG",
+ "decimals": 18
+ },
+ {
+ "address": "0xe9ff07809ccff05dae74990e25831d0bc5cbe575",
+ "name": "HEDPAY",
+ "symbol": "Hdp.\u0444",
+ "decimals": 18
+ },
+ {
+ "address": "0xba2184520A1cC49a6159c57e61E1844E085615B6",
+ "name": "HelloGold Token",
+ "symbol": "HGT",
+ "decimals": 8
+ },
+ {
+ "address": "0xa9240fBCAC1F0b9A6aDfB04a53c8E3B0cC1D1444",
+ "name": "ethereumhigh",
+ "symbol": "HIG",
+ "decimals": 18
+ },
+ {
+ "address": "0x14F37B574242D366558dB61f3335289a5035c506",
+ "name": "HackerGold",
+ "symbol": "HKG",
+ "decimals": 3
+ },
+ {
+ "address": "0xcbCC0F036ED4788F63FC0fEE32873d6A7487b908",
+ "name": "Humaniq",
+ "symbol": "HMQ",
+ "decimals": 8
+ },
+ {
+ "address": "0x554C20B7c486beeE439277b4540A434566dC4C02",
+ "name": "Decision Token",
+ "symbol": "HST",
+ "decimals": 18
+ },
+ {
+ "address": "0xC0Eb85285d83217CD7c891702bcbC0FC401E2D9D",
+ "name": "Hive Project",
+ "symbol": "HVN",
+ "decimals": 8
+ },
+ {
+ "address": "0x5a84969bb663fb64F6d015DcF9F622Aedc796750",
+ "name": "IDICE",
+ "symbol": "ICE",
+ "decimals": 18
+ },
+ {
+ "address": "0x888666CA69E0f178DED6D75b5726Cee99A87D698",
+ "name": "ICONOMI",
+ "symbol": "ICN",
+ "decimals": 18
+ },
+ {
+ "address": "0xa33e729bf4fdeb868b534e1f20523463d9c46bee",
+ "name": "ICO",
+ "symbol": "\u00a2",
+ "decimals": 10
+ },
+ {
+ "address": "0x014B50466590340D41307Cc54DCee990c8D58aa8",
+ "name": "ICOS",
+ "symbol": "ICOS",
+ "decimals": 6
+ },
+ {
+ "address": "0xb5a5f22694352c15b00323844ad545abb2b11028",
+ "name": "ICON",
+ "symbol": "ICX",
+ "decimals": 18
+ },
+ {
+ "address": "0x814cafd4782d2e728170fda68257983f03321c58",
+ "name": "IDEA Token",
+ "symbol": "IDEA",
+ "decimals": 0
+ },
+ {
+ "address": "0x7654915a1b82d6d2d0afc37c52af556ea8983c7e",
+ "name": "Feed",
+ "symbol": "IFT",
+ "decimals": 18
+ },
+ {
+ "address": "0x16662f73df3e79e54c6c5938b4313f92c524c120",
+ "name": "Ibiscoin",
+ "symbol": "IIC",
+ "decimals": 18
+ },
+ {
+ "address": "0x88AE96845e157558ef59e9Ff90E766E22E480390",
+ "name": "Digital Zone of Immaterial Pictorial Sensibility",
+ "symbol": "IKB",
+ "decimals": 0
+ },
+ {
+ "address": "0xe3831c5A982B279A198456D577cfb90424cb6340",
+ "name": "Immune Coin",
+ "symbol": "IMC",
+ "decimals": 6
+ },
+ {
+ "address": "0x22E5F62D0FA19974749faa194e3d3eF6d89c08d7",
+ "name": "Immortal",
+ "symbol": "IMT",
+ "decimals": 0
+ },
+ {
+ "address": "0xf8e386EDa857484f5a12e4B5DAa9984E06E73705",
+ "name": "Indorse Token",
+ "symbol": "IND",
+ "decimals": 18
+ },
+ {
+ "address": "0x5b2e4a700dfbc560061e957edec8f6eeeb74a320",
+ "name": "INS Token",
+ "symbol": "INS",
+ "decimals": 10
+ },
+ {
+ "address": "0xa8006c4ca56f24d6836727d106349320db7fef82",
+ "name": "Internxt",
+ "symbol": "INXT",
+ "decimals": 8
+ },
+ {
+ "address": "0x64CdF819d3E75Ac8eC217B3496d7cE167Be42e80",
+ "name": "InsurePal token",
+ "symbol": "IPL",
+ "decimals": 18
+ },
+ {
+ "address": "0xc34b21f6f8e51cc965c2393b3ccfa3b82beb2403",
+ "name": "IoT",
+ "symbol": "IoT",
+ "decimals": 6
+ },
+ {
+ "address": "0x0aeF06DcCCC531e581f0440059E6FfCC206039EE",
+ "name": "Intelligent Trading Technologies",
+ "symbol": "ITT",
+ "decimals": 8
+ },
+ {
+ "address": "0xfca47962d45adfdfd1ab2d972315db4ce7ccf094",
+ "name": "InsureX",
+ "symbol": "IXT",
+ "decimals": 8
+ },
+ {
+ "address": "0x0Aaf561eFF5BD9c8F911616933F84166A17cfE0C",
+ "name": "Jbox",
+ "symbol": "JBX",
+ "decimals": 0
+ },
+ {
+ "address": "0x8727c112c712c4a03371ac87a74dd6ab104af768",
+ "name": "Jetcoin",
+ "symbol": "JET",
+ "decimals": 18
+ },
+ {
+ "address": "0x773450335eD4ec3DB45aF74f34F2c85348645D39",
+ "name": "JetCoins",
+ "symbol": "JET",
+ "decimals": 18
+ },
+ {
+ "address": "0x72D32ac1c5E66BfC5b08806271f8eEF915545164",
+ "name": "CryptoKEE",
+ "symbol": "KEE",
+ "decimals": 0
+ },
+ {
+ "address": "0x4CC19356f2D37338b9802aa8E8fc58B0373296E7",
+ "name": "SelfKey",
+ "symbol": "KEY",
+ "decimals": 18
+ },
+ {
+ "address": "0x27695E09149AdC738A978e9A678F99E4c39e9eb9",
+ "name": "KickCoin",
+ "symbol": "KICK",
+ "decimals": 8
+ },
+ {
+ "address": "0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5",
+ "name": "Kin",
+ "symbol": "KIN",
+ "decimals": 18
+ },
+ {
+ "address": "0xdd974D5C2e2928deA5F71b9825b8b646686BD200",
+ "name": "Kyber Network Crystal",
+ "symbol": "KNC",
+ "decimals": 18
+ },
+ {
+ "address": "0x9541FD8B9b5FA97381783783CeBF2F5fA793C262",
+ "name": "Kaizen",
+ "symbol": "KZN",
+ "decimals": 8
+ },
+ {
+ "address": "0x2eb86e8fc520e0f6bb5d9af08f924fe70558ab89",
+ "name": "Logarithm",
+ "symbol": "LGR",
+ "decimals": 8
+ },
+ {
+ "address": "0xff18dbc487b4c2e3222d115952babfda8ba52f5f",
+ "name": "PureLifeCoin",
+ "symbol": "LIFE",
+ "decimals": 18
+ },
+ {
+ "address": "0x514910771af9ca656af840dff83e8264ecf986ca",
+ "name": "ChainLink Token",
+ "symbol": "LINK",
+ "decimals": 18
+ },
+ {
+ "address": "0xe2e6d4be086c6938b53b22144855eef674281639",
+ "name": "Link Platform",
+ "symbol": "LNK",
+ "decimals": 18
+ },
+ {
+ "address": "0x24A77c1F17C547105E14813e517be06b0040aa76",
+ "name": "Live Stars Token",
+ "symbol": "LIVE",
+ "decimals": 18
+ },
+ {
+ "address": "0x63e634330A20150DbB61B15648bC73855d6CCF07",
+ "name": "Lancer Token",
+ "symbol": "LNC",
+ "decimals": 18
+ },
+ {
+ "address": "0x6beb418fc6e1958204ac8baddcf109b8e9694966",
+ "name": "Linker Coin",
+ "symbol": "LNC",
+ "decimals": 18
+ },
+ {
+ "address": "0x5e3346444010135322268a4630d2ed5f8d09446c",
+ "name": "LockChain",
+ "symbol": "LOC",
+ "decimals": 18
+ },
+ {
+ "address": "0x21ae23b882a340a22282162086bc98d3e2b73018",
+ "name": "LookRev",
+ "symbol": "LOK",
+ "decimals": 18
+ },
+ {
+ "address": "0xEF68e7C694F40c8202821eDF525dE3782458639f",
+ "name": "loopring",
+ "symbol": "LRC",
+ "decimals": 18
+ },
+ {
+ "address": "0xFB12e3CcA983B9f59D90912Fd17F8D745A8B2953",
+ "name": "LUCKY",
+ "symbol": "LUCK",
+ "decimals": 0
+ },
+ {
+ "address": "0xa89b5934863447f6e4fc53b315a93e873bda69a3",
+ "name": "LuminoCoin",
+ "symbol": "LUM",
+ "decimals": 18
+ },
+ {
+ "address": "0xfa05A73FfE78ef8f1a739473e462c54bae6567D9",
+ "name": "Lunyr Token",
+ "symbol": "LUN",
+ "decimals": 18
+ },
+ {
+ "address": "0x3f4b726668da46f5e0e75aa5d478acec9f38210f",
+ "name": "MostExclusive.com-ETH",
+ "symbol": "M-ETH",
+ "decimals": 18
+ },
+ {
+ "address": "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942",
+ "name": "Decentraland",
+ "symbol": "MANA",
+ "decimals": 18
+ },
+ {
+ "address": "0x386467f1f3ddbe832448650418311a479eecfc57",
+ "name": "Embers",
+ "symbol": "MBRS",
+ "decimals": 0
+ },
+ {
+ "address": "0x93E682107d1E9defB0b5ee701C71707a4B2E46Bc",
+ "name": "MCAP",
+ "symbol": "MCAP",
+ "decimals": 8
+ },
+ {
+ "address": "0x138A8752093F4f9a79AaeDF48d4B9248fab93c9C",
+ "name": "Musiconomi",
+ "symbol": "MCI",
+ "decimals": 18
+ },
+ {
+ "address": "0xB63B606Ac810a52cCa15e44bB630fd42D8d1d83d",
+ "name": "Monaco",
+ "symbol": "MCO",
+ "decimals": 8
+ },
+ {
+ "address": "0x51DB5Ad35C671a87207d88fC11d593AC0C8415bd",
+ "name": "Moeda Loyalty Points",
+ "symbol": "MDA",
+ "decimals": 18
+ },
+ {
+ "address": "0x40395044ac3c0c57051906da938b54bd6557f212",
+ "name": "MobileGo Token",
+ "symbol": "MGO",
+ "decimals": 8
+ },
+ {
+ "address": "0xe23cd160761f63FC3a1cF78Aa034b6cdF97d3E0C",
+ "name": "Mainstreet Token",
+ "symbol": "MIT",
+ "decimals": 18
+ },
+ {
+ "address": "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2",
+ "name": "Maker",
+ "symbol": "MKR",
+ "decimals": 18
+ },
+ {
+ "address": "0xc66ea802717bfb9833400264dd12c2bceaa34a6d",
+ "name": "MKR",
+ "symbol": "MKR",
+ "decimals": 18
+ },
+ {
+ "address": "0xBEB9eF514a379B997e0798FDcC901Ee474B6D9A1",
+ "name": "Melon Token",
+ "symbol": "MLN",
+ "decimals": 18
+ },
+ {
+ "address": "0x1a95B271B0535D15fa49932Daba31BA612b52946",
+ "name": "minereum",
+ "symbol": "MNE",
+ "decimals": 8
+ },
+ {
+ "address": "0xA9877b1e05D035899131DBd1e403825166D09f92",
+ "name": "Media Network Token",
+ "symbol": "MNT",
+ "decimals": 18
+ },
+ {
+ "address": "0x83cee9e086a77e492ee0bb93c2b0437ad6fdeccc",
+ "name": "Goldmint MNT Prelaunch Token",
+ "symbol": "MNTP",
+ "decimals": 18
+ },
+ {
+ "address": "0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e",
+ "name": "Modum Token",
+ "symbol": "MOD",
+ "decimals": 0
+ },
+ {
+ "address": "0xAB6CF87a50F17d7F5E1FEaf81B6fE9FfBe8EBF84",
+ "name": "Macroverse Token",
+ "symbol": "MRV",
+ "decimals": 18
+ },
+ {
+ "address": "0x68AA3F232dA9bdC2343465545794ef3eEa5209BD",
+ "name": "Mothership Token",
+ "symbol": "MSP",
+ "decimals": 18
+ },
+ {
+ "address": "0xaF4DcE16Da2877f8c9e00544c93B62Ac40631F16",
+ "name": "Monetha",
+ "symbol": "MTH",
+ "decimals": 5
+ },
+ {
+ "address": "0xF433089366899D83a9f26A773D59ec7eCF30355e",
+ "name": "Metal",
+ "symbol": "MTL",
+ "decimals": 8
+ },
+ {
+ "address": "0x7FC408011165760eE31bE2BF20dAf450356692Af",
+ "name": "Mitrav",
+ "symbol": "MTR",
+ "decimals": 8
+ },
+ {
+ "address": "0x0AF44e2784637218dD1D32A322D44e603A8f0c6A",
+ "name": "MatryxToken",
+ "symbol": "MTX",
+ "decimals": 18
+ },
+ {
+ "address": "0x6425c6be902d692ae2db752b3c268afadb099d3b",
+ "name": "RED MWAT",
+ "symbol": "MWAT",
+ "decimals": 18
+ },
+ {
+ "address": "0xf7e983781609012307f2514f63D526D83D24F466",
+ "name": "MyEtherWallet Donations Token",
+ "symbol": "MYD",
+ "decimals": 16
+ },
+ {
+ "address": "0xa645264C5603E96c3b0B078cdab68733794B0A71",
+ "name": "Mysterium",
+ "symbol": "MYST",
+ "decimals": 8
+ },
+ {
+ "address": "0xa54ddc7b3cce7fc8b1e3fa0256d0db80d2c10970",
+ "name": "NEVERDIE",
+ "symbol": "NDC",
+ "decimals": 18
+ },
+ {
+ "address": "0xcfb98637bcae43C13323EAa1731cED2B716962fD",
+ "name": "Nimiq Exchange",
+ "symbol": "NET",
+ "decimals": 18
+ },
+ {
+ "address": "0xa823e6722006afe99e91c30ff5295052fe6b8e32",
+ "name": "Neumark",
+ "symbol": "NEU",
+ "decimals": 18
+ },
+ {
+ "address": "0xe26517A9967299453d3F1B48Aa005E6127e67210",
+ "name": "NIMFA Token",
+ "symbol": "NIMFA",
+ "decimals": 18
+ },
+ {
+ "address": "0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671",
+ "name": "Numeraire",
+ "symbol": "NMR",
+ "decimals": 18
+ },
+ {
+ "address": "0xec46f8207d766012454c408de210bcbc2243e71c",
+ "name": "Nitro",
+ "symbol": "NOX",
+ "decimals": 18
+ },
+ {
+ "address": "0xb91318f35bdb262e9423bc7c7c2a3a93dd93c92c",
+ "name": "Nuls",
+ "symbol": "NULS",
+ "decimals": 18
+ },
+ {
+ "address": "0x45e42D659D9f9466cD5DF622506033145a9b89Bc",
+ "name": "Nexium",
+ "symbol": "NxC",
+ "decimals": 3
+ },
+ {
+ "address": "0x7627de4b93263a6a7570b8dafa64bae812e5c394",
+ "name": "Nexxus",
+ "symbol": "NXX",
+ "decimals": 8
+ },
+ {
+ "address": "0x5c6183d10A00CD747a6Dbb5F658aD514383e9419",
+ "name": "Nexxus",
+ "symbol": "NXX",
+ "decimals": 8
+ },
+ {
+ "address": "0x701C244b988a513c945973dEFA05de933b23Fe1D",
+ "name": "openANX Token",
+ "symbol": "OAX",
+ "decimals": 18
+ },
+ {
+ "address": "0x7F2176cEB16dcb648dc924eff617c3dC2BEfd30d",
+ "name": "Ohni",
+ "symbol": "Ohni",
+ "decimals": 0
+ },
+ {
+ "address": "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07",
+ "name": "OMGToken",
+ "symbol": "OMG",
+ "decimals": 18
+ },
+ {
+ "address": "0xd341d1680eeee3255b8c4c75bcce7eb57f144dae",
+ "name": "onG",
+ "symbol": "ONG",
+ "decimals": 18
+ },
+ {
+ "address": "0xb23be73573bc7e03db6e5dfc62405368716d28a8",
+ "name": "oneK",
+ "symbol": "ONEK",
+ "decimals": 18
+ },
+ {
+ "address": "0x4355fC160f74328f9b383dF2EC589bB3dFd82Ba0",
+ "name": "Opus Token",
+ "symbol": "OPT",
+ "decimals": 18
+ },
+ {
+ "address": "0x2C4e8f2D746113d0696cE89B35F0d8bF88E0AEcA",
+ "name": "Simple Token",
+ "symbol": "ST",
+ "decimals": 18
+ },
+ {
+ "address": "0x65a15014964f2102ff58647e16a16a6b9e14bcf6",
+ "name": "Ox Fina",
+ "symbol": "OX",
+ "decimals": 3
+ },
+ {
+ "address": "0x694404595e3075a942397f466aacd462ff1a7bd0",
+ "name": "smartillions.io Class 1 ETH",
+ "symbol": "PATENTS",
+ "decimals": 18
+ },
+ {
+ "address": "0xB97048628DB6B661D4C2aA833e95Dbe1A905B280",
+ "name": "TenX Pay Token",
+ "symbol": "PAY",
+ "decimals": 18
+ },
+ {
+ "address": "0x55648de19836338549130b1af587f16bea46f66b",
+ "name": "Pebbles",
+ "symbol": "PBL",
+ "decimals": 18
+ },
+ {
+ "address": "0x53148Bb4551707edF51a1e8d7A93698d18931225",
+ "name": "Peculium",
+ "symbol": "PCL",
+ "decimals": 8
+ },
+ {
+ "address": "0xec18f898b4076a3e18f1089d33376cc380bde61d",
+ "name": "Petro",
+ "symbol": "PETRO",
+ "decimals": 18
+ },
+ {
+ "address": "0x55c2A0C171D920843560594dE3d6EEcC09eFc098",
+ "name": "PEX-Token",
+ "symbol": "PEXT",
+ "decimals": 4
+ },
+ {
+ "address": "0xE64509F0bf07ce2d29A7eF19A8A9bc065477C1B4",
+ "name": "PiplCoin",
+ "symbol": "PIPL",
+ "decimals": 8
+ },
+ {
+ "address": "0x8eFFd494eB698cc399AF6231fCcd39E08fd20B15",
+ "name": "PIX Token",
+ "symbol": "PIX",
+ "decimals": 0
+ },
+ {
+ "address": "0xE477292f1B3268687A29376116B0ED27A9c76170",
+ "name": "Herocoin",
+ "symbol": "PLAY",
+ "decimals": 18
+ },
+ {
+ "address": "0x0AfFa06e7Fbe5bC9a764C979aA66E8256A631f02",
+ "name": "Polybius",
+ "symbol": "PLBT",
+ "decimals": 6
+ },
+ {
+ "address": "0xe3818504c1B32bF1557b16C238B2E01Fd3149C17",
+ "name": "PILLAR",
+ "symbol": "PLR",
+ "decimals": 18
+ },
+ {
+ "address": "0xD8912C10681D8B21Fd3742244f44658dBA12264E",
+ "name": "Pluton",
+ "symbol": "PLU",
+ "decimals": 18
+ },
+ {
+ "address": "0x0e0989b1f9b8a38983c2ba8053269ca62ec9b195",
+ "name": "Po.et",
+ "symbol": "POE",
+ "decimals": 8
+ },
+ {
+ "address": "0x43f6a1be992dee408721748490772b15143ce0a7",
+ "name": "Potatoin",
+ "symbol": "POIN",
+ "decimals": 0
+ },
+ {
+ "address": "0x779B7b713C86e3E6774f5040D9cCC2D43ad375F8",
+ "name": "StakePool",
+ "symbol": "POOL",
+ "decimals": 8
+ },
+ {
+ "address": "0xee609fe292128cad03b786dbb9bc2634ccdbe7fc",
+ "name": "PoSToken",
+ "symbol": "POS",
+ "decimals": 18
+ },
+ {
+ "address": "0x595832f8fc6bf59c85c527fec3740a1b7a361269",
+ "name": "PowerLedger",
+ "symbol": "POWR",
+ "decimals": 6
+ },
+ {
+ "address": "0xc42209accc14029c1012fb5680d95fbd6036e2a0",
+ "name": "PayPie",
+ "symbol": "PPP",
+ "decimals": 18
+ },
+ {
+ "address": "0xd4fa1460F537bb9085d22C7bcCB5DD450Ef28e3a",
+ "name": "Populous Platform",
+ "symbol": "PPT",
+ "decimals": 8
+ },
+ {
+ "address": "0x88a3e4f35d64aad41a6d4030ac9afe4356cb84fa",
+ "name": "Presearch",
+ "symbol": "PRE",
+ "decimals": 18
+ },
+ {
+ "address": "0x7728dfef5abd468669eb7f9b48a7f70a501ed29d",
+ "name": "ParagonCoin",
+ "symbol": "PRG",
+ "decimals": 6
+ },
+ {
+ "address": "0x7641b2Ca9DDD58adDf6e3381c1F994Aac5f1A32f",
+ "name": "Purpose",
+ "symbol": "PRPS",
+ "decimals": 18
+ },
+ {
+ "address": "0x1844b21593262668b7248d0f57a220caaba46ab9",
+ "name": "Oyster Pearl",
+ "symbol": "PRL",
+ "decimals": 18
+ },
+ {
+ "address": "0x226bb599a12C826476e3A771454697EA52E9E220",
+ "name": "Propy",
+ "symbol": "PRO",
+ "decimals": 8
+ },
+ {
+ "address": "0x163733bcc28dbf26B41a8CfA83e369b5B3af741b",
+ "name": "Persian",
+ "symbol": "PRS",
+ "decimals": 18
+ },
+ {
+ "address": "0x0c04d4f331da8df75f9e2e271e3f3f1494c66c36",
+ "name": "Prosper",
+ "symbol": "PRSP",
+ "decimals": 9
+ },
+ {
+ "address": "0x66497a283e0a007ba3974e837784c6ae323447de",
+ "name": "PornToken",
+ "symbol": "PT",
+ "decimals": 18
+ },
+ {
+ "address": "0x8Ae4BF2C33a8e667de34B54938B0ccD03Eb8CC06",
+ "name": "Patientory",
+ "symbol": "PTOY",
+ "decimals": 8
+ },
+ {
+ "address": "0x5512e1d6a7be424b4323126b4f9e86d023f95764",
+ "name": "PornTokenV2",
+ "symbol": "PTWO",
+ "decimals": 18
+ },
+ {
+ "address": "0xc14830e53aa344e8c14603a91229a0b925b0b262",
+ "name": "Populous XBRL token",
+ "symbol": "PXT",
+ "decimals": 8
+ },
+ {
+ "address": "0x671AbBe5CE652491985342e85428EB1b07bC6c64",
+ "name": "Quantum",
+ "symbol": "QAU",
+ "decimals": 8
+ },
+ {
+ "address": "0x697beac28B09E122C4332D163985e8a73121b97F",
+ "name": "QRL",
+ "symbol": "QRL",
+ "decimals": 8
+ },
+ {
+ "address": "0x99ea4dB9EE77ACD40B119BD1dC4E33e1C070b80d",
+ "name": "Quantstamp Token",
+ "symbol": "QSP",
+ "decimals": 18
+ },
+ {
+ "address": "0x2C3C1F05187dBa7A5f2Dd47Dca57281C4d4F183F",
+ "name": "Q",
+ "symbol": "QTQ",
+ "decimals": 18
+ },
+ {
+ "address": "0x9a642d6b3368ddc662CA244bAdf32cDA716005BC",
+ "name": "Qtum",
+ "symbol": "QTUM",
+ "decimals": 18
+ },
+ {
+ "address": "0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6",
+ "name": "Raiden Token",
+ "symbol": "RDN",
+ "decimals": 18
+ },
+ {
+ "address": "0x5f53f7a8075614b699baad0bc2c899f4bad8fbbf",
+ "name": "Rebellious",
+ "symbol": "REBL",
+ "decimals": 18
+ },
+ {
+ "address": "0xE94327D07Fc17907b4DB788E5aDf2ed424adDff6",
+ "name": "Reputation",
+ "symbol": "REP",
+ "decimals": 18
+ },
+ {
+ "address": "0x8f8221aFbB33998d8584A2B05749bA73c37a938a",
+ "name": "Request Token",
+ "symbol": "REQ",
+ "decimals": 18
+ },
+ {
+ "address": "0xf05a9382A4C3F29E2784502754293D88b835109C",
+ "name": "REX - Real Estate tokens",
+ "symbol": "REX",
+ "decimals": 18
+ },
+ {
+ "address": "0xdd007278b667f6bef52fd0a4c23604aa1f96039a",
+ "name": "RiptideCoin",
+ "symbol": "RIPT",
+ "decimals": 8
+ },
+ {
+ "address": "0x607F4C5BB672230e8672085532f7e901544a7375",
+ "name": "iEx.ec Network Token",
+ "symbol": "RLC",
+ "decimals": 9
+ },
+ {
+ "address": "0xcCeD5B8288086BE8c38E23567e684C3740be4D48",
+ "name": "Roulette Token",
+ "symbol": "RLT",
+ "decimals": 10
+ },
+ {
+ "address": "0x4a42d2c580f83dce404acad18dab26db11a1750e",
+ "name": "Relex",
+ "symbol": "RLX",
+ "decimals": 18
+ },
+ {
+ "address": "0x0996bfb5d057faa237640e2506be7b4f9c46de0b",
+ "name": "Render Token",
+ "symbol": "RNDR",
+ "decimals": 18
+ },
+ {
+ "address": "0xc9de4b7f0c3d991e967158e4d4bfa4b51ec0b114",
+ "name": "ROK Token",
+ "symbol": "ROK",
+ "decimals": 18
+ },
+ {
+ "address": "0x4993CB95c7443bdC06155c5f5688Be9D8f6999a5",
+ "name": "ROUND",
+ "symbol": "ROUND",
+ "decimals": 18
+ },
+ {
+ "address": "0xb4efd85c19999d84251304bda99e90b92300bd93",
+ "name": "Rocket Pool",
+ "symbol": "RPL",
+ "decimals": 18
+ },
+ {
+ "address": "0x54b293226000ccBFC04DF902eEC567CB4C35a903",
+ "name": "RiderToken",
+ "symbol": "RTN",
+ "decimals": 18
+ },
+ {
+ "address": "0x3d1ba9be9f66b8ee101911bc36d3fb562eac2244",
+ "name": "RvT",
+ "symbol": "RVT",
+ "decimals": 18
+ },
+ {
+ "address": "0x1ec8fe51a9b6a3a6c427d17d9ecc3060fbc4a45c",
+ "name": "smartillions.io A ETH",
+ "symbol": "S-A-PAT",
+ "decimals": 18
+ },
+ {
+ "address": "0x3eb91d237e491e0dee8582c402d85cb440fb6b54",
+ "name": "Smartillions.ch-ETH",
+ "symbol": "S-ETH",
+ "decimals": 18
+ },
+ {
+ "address": "0x4156D3342D5c385a87D264F90653733592000581",
+ "name": "Salt",
+ "symbol": "SALT",
+ "decimals": 8
+ },
+ {
+ "address": "0x7C5A0CE9267ED19B22F8cae653F198e3E8daf098",
+ "name": "SANtiment network token",
+ "symbol": "SAN",
+ "decimals": 18
+ },
+ {
+ "address": "0xd7631787b4dcc87b1254cfd1e5ce48e96823dee8",
+ "name": "SOCIAL",
+ "symbol": "SCL",
+ "decimals": 8
+ },
+ {
+ "address": "0x6745fAB6801e376cD24F03572B9C9B0D4EdDDCcf",
+ "name": "Sense",
+ "symbol": "SENSE",
+ "decimals": 8
+ },
+ {
+ "address": "0x4ca74185532dc1789527194e5b9c866dd33f4e82",
+ "name": "sensatori",
+ "symbol": "sense",
+ "decimals": 18
+ },
+ {
+ "address": "0xe06eda7435ba749b047380ced49121dde93334ae",
+ "name": "Transferable Sydney Ethereum Token",
+ "symbol": "SET",
+ "decimals": 0
+ },
+ {
+ "address": "0x98f5e9b7f0e33956c0443e81bf7deb8b5b1ed545",
+ "name": "Sexy Token",
+ "symbol": "SEXY",
+ "decimals": 18
+ },
+ {
+ "address": "0xa1ccc166faf0E998b3E33225A1A0301B1C86119D",
+ "name": "SGELDER",
+ "symbol": "SGEL",
+ "decimals": 18
+ },
+ {
+ "address": "0xd248B0D48E44aaF9c49aea0312be7E13a6dc1468",
+ "name": "Status Genesis Token",
+ "symbol": "SGT",
+ "decimals": 1
+ },
+ {
+ "address": "0xEF2E9966eb61BB494E5375d5Df8d67B7dB8A780D",
+ "name": "Shitcoin",
+ "symbol": "SHIT",
+ "decimals": 0
+ },
+ {
+ "address": "0x8a187d5285d316bcbc9adafc08b51d70a0d8e000",
+ "name": "Smart Investment Fund Token",
+ "symbol": "SIFT",
+ "decimals": 0
+ },
+ {
+ "address": "0x2bDC0D42996017fCe214b21607a515DA41A9E0C5",
+ "name": "SkinCoin",
+ "symbol": "SKIN",
+ "decimals": 6
+ },
+ {
+ "address": "0x4994e81897a920c0FEA235eb8CEdEEd3c6fFF697",
+ "name": "Sikoba Continuous Sale",
+ "symbol": "SKO1",
+ "decimals": 18
+ },
+ {
+ "address": "0x4c382F8E09615AC86E08CE58266CC227e7d4D913",
+ "name": "Skrilla",
+ "symbol": "SKR",
+ "decimals": 6
+ },
+ {
+ "address": "0x6E34d8d84764D40f6D7b39cd569Fd017bF53177D",
+ "name": "Skraps",
+ "symbol": "SKRP",
+ "decimals": 18
+ },
+ {
+ "address": "0x7A5fF295Dc8239d5C2374E4D894202aAF029Cab6",
+ "name": "Smartlands Token",
+ "symbol": "SLT",
+ "decimals": 3
+ },
+ {
+ "address": "0x6F6DEb5db0C4994A8283A01D6CFeEB27Fc3bBe9C",
+ "name": "SmartBillions Token",
+ "symbol": "Smart",
+ "decimals": 0
+ },
+ {
+ "address": "0xF4134146AF2d511Dd5EA8cDB1C4AC88C57D60404",
+ "name": "SunContract",
+ "symbol": "SNC",
+ "decimals": 18
+ },
+ {
+ "address": "0x44F588aEeB8C44471439D1270B3603c66a9262F1",
+ "name": "SnipCoin",
+ "symbol": "SNIP",
+ "decimals": 18
+ },
+ {
+ "address": "0xf333b2Ace992ac2bBD8798bF57Bc65a06184afBa",
+ "name": "SND Token 1.0",
+ "symbol": "SND",
+ "decimals": 0
+ },
+ {
+ "address": "0xaeC2E87E0A235266D9C5ADc9DEb4b2E29b54D009",
+ "name": "SingularDTV",
+ "symbol": "SNGLS",
+ "decimals": 0
+ },
+ {
+ "address": "0x983F6d60db79ea8cA4eB9968C6aFf8cfA04B3c63",
+ "name": "SONM Token",
+ "symbol": "SNM",
+ "decimals": 18
+ },
+ {
+ "address": "0x744d70FDBE2Ba4CF95131626614a1763DF805B9E",
+ "name": "Status Network",
+ "symbol": "SNT",
+ "decimals": 18
+ },
+ {
+ "address": "0xbdc5bac39dbe132b1e030e898ae3830017d7d969",
+ "name": "Snovio",
+ "symbol": "SNOV",
+ "decimals": 18
+ },
+ {
+ "address": "0x1f54638b7737193ffd86c19ec51907a7c41755d8",
+ "name": "Sola Token",
+ "symbol": "SOL",
+ "decimals": 6
+ },
+ {
+ "address": "0x42d6622dece394b54999fbd73d108123806f6a18",
+ "name": "SPANK",
+ "symbol": "SPANK",
+ "decimals": 18
+ },
+ {
+ "address": "0x58bf7df57d9DA7113c4cCb49d8463D4908C735cb",
+ "name": "Science Power and Research Coin",
+ "symbol": "SPARC",
+ "decimals": 18
+ },
+ {
+ "address": "0x24aef3bf1a47561500f9430d74ed4097c47f51f2",
+ "name": "SPARTA",
+ "symbol": "SPARTA",
+ "decimals": 4
+ },
+ {
+ "address": "0x85089389C14Bd9c77FC2b8F0c3d1dC3363Bf06Ef",
+ "name": "SPFToken",
+ "symbol": "SPF",
+ "decimals": 18
+ },
+ {
+ "address": "0x68d57c9a1C35f63E2c83eE8e49A64e9d70528D25",
+ "name": "SIRIN",
+ "symbol": "SRN",
+ "decimals": 18
+ },
+ {
+ "address": "0x9a005c9a89bd72a4bd27721e7a09a3c11d2b03c4",
+ "name": "StarterCoin",
+ "symbol": "STAC",
+ "decimals": 18
+ },
+ {
+ "address": "0xF70a642bD387F94380fFb90451C2c81d4Eb82CBc",
+ "name": "Starbase",
+ "symbol": "STAR",
+ "decimals": 18
+ },
+ {
+ "address": "0x629aEe55ed49581C33ab27f9403F7992A289ffd5",
+ "name": "StrikeCoin Token",
+ "symbol": "STC",
+ "decimals": 18
+ },
+ {
+ "address": "0x7dd7f56d697cc0f2b52bd55c057f378f1fe6ab4b",
+ "name": "$TEAK",
+ "symbol": "$TEAK",
+ "decimals": 18
+ },
+ {
+ "address": "0x599346779e90fc3F5F997b5ea715349820F91571",
+ "name": "Saturn",
+ "symbol": "STN",
+ "decimals": 4
+ },
+ {
+ "address": "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC",
+ "name": "StorjToken",
+ "symbol": "STORJ",
+ "decimals": 8
+ },
+ {
+ "address": "0xD0a4b8946Cb52f0661273bfbC6fD0E0C75Fc6433",
+ "name": "Storm Token",
+ "symbol": "STORM",
+ "decimals": 18
+ },
+ {
+ "address": "0xecd570bBf74761b960Fa04Cc10fe2c4e86FfDA36",
+ "name": "STASHPAY",
+ "symbol": "STP",
+ "decimals": 8
+ },
+ {
+ "address": "0x46492473755e8dF960F8034877F61732D718CE96",
+ "name": "StarCredits",
+ "symbol": "STRC",
+ "decimals": 8
+ },
+ {
+ "address": "0x006BeA43Baa3f7A6f765F14f10A1a1b08334EF45",
+ "name": "Stox",
+ "symbol": "STX",
+ "decimals": 18
+ },
+ {
+ "address": "0x12480E24eb5bec1a9D4369CaB6a80caD3c0A377A",
+ "name": "Substratum",
+ "symbol": "SUB",
+ "decimals": 2
+ },
+ {
+ "address": "0x9e88613418cf03dca54d6a2cf6ad934a78c7a17a",
+ "name": "Swarm Fund Token",
+ "symbol": "SWM",
+ "decimals": 18
+ },
+ {
+ "address": "0xB9e7F8568e08d5659f5D29C4997173d84CdF2607",
+ "name": "Swarm City Token",
+ "symbol": "SWT",
+ "decimals": 18
+ },
+ {
+ "address": "0x12b306fa98f4cbb8d4457fdff3a0a0a56f07ccdf",
+ "name": "Spectre.ai D-Token",
+ "symbol": "SXDT",
+ "decimals": 18
+ },
+ {
+ "address": "0x2c82c73d5b34aa015989462b2948cd616a37641f",
+ "name": "Spectre.ai U-Token",
+ "symbol": "SXUT",
+ "decimals": 18
+ },
+ {
+ "address": "0x10b123fddde003243199aad03522065dc05827a0",
+ "name": "Synapse",
+ "symbol": "SYN",
+ "decimals": 18
+ },
+ {
+ "address": "0xE7775A6e9Bcf904eb39DA2b68c5efb4F9360e08C",
+ "name": "Token-as-a-Service",
+ "symbol": "TAAS",
+ "decimals": 6
+ },
+ {
+ "address": "0xc27a2f05fa577a83ba0fdb4c38443c0718356501",
+ "name": "Lamden Tau",
+ "symbol": "TAU",
+ "decimals": 18
+ },
+ {
+ "address": "0xFACCD5Fc83c3E4C3c1AC1EF35D15adf06bCF209C",
+ "name": "TheBillionCoin2",
+ "symbol": "TBC2",
+ "decimals": 8
+ },
+ {
+ "address": "0xAFe60511341a37488de25Bef351952562E31fCc1",
+ "name": "TBOT",
+ "symbol": "TBT",
+ "decimals": 8
+ },
+ {
+ "address": "0x85e076361cc813a908ff672f9bad1541474402b2",
+ "name": "Telcoin",
+ "symbol": "TEL",
+ "decimals": 2
+ },
+ {
+ "address": "0xa7f976C360ebBeD4465c2855684D1AAE5271eFa9",
+ "name": "TrueFlip",
+ "symbol": "TFL",
+ "decimals": 8
+ },
+ {
+ "address": "0x6531f133e6DeeBe7F2dcE5A0441aA7ef330B4e53",
+ "name": "Chronobank TIME",
+ "symbol": "TIME",
+ "decimals": 8
+ },
+ {
+ "address": "0x80bc5512561c7f85a3a9508c7df7901b370fa1df",
+ "name": "TradeToken",
+ "symbol": "TIO",
+ "decimals": 18
+ },
+ {
+ "address": "0xEa1f346faF023F974Eb5adaf088BbCdf02d761F4",
+ "name": "Blocktix",
+ "symbol": "TIX",
+ "decimals": 18
+ },
+ {
+ "address": "0xaAAf91D9b90dF800Df4F55c205fd6989c977E73a",
+ "name": "Monolith TKN",
+ "symbol": "TKN",
+ "decimals": 8
+ },
+ {
+ "address": "0x08f5a9235b08173b7569f83645d2c7fb55e8ccd8",
+ "name": "Tierion Network Token",
+ "symbol": "TNT",
+ "decimals": 8
+ },
+ {
+ "address": "0xcb94be6f13a1182e4a4b6140cb7bf2025d28e41b",
+ "name": "Trustcoin",
+ "symbol": "TRST",
+ "decimals": 6
+ },
+ {
+ "address": "0xf230b790e05390fc8295f4d3f60332c93bed42e2",
+ "name": "Tronix",
+ "symbol": "TRX",
+ "decimals": 6
+ },
+ {
+ "address": "0x2eF1aB8a26187C58BB8aAeB11B2fC6D25C5c0716",
+ "name": "TWN Shares",
+ "symbol": "TWN",
+ "decimals": 18
+ },
+ {
+ "address": "0x24692791bc444c5cd0b81e3cbcaba4b04acd1f3b",
+ "name": "UnikoinGold",
+ "symbol": "UKG",
+ "decimals": 18
+ },
+ {
+ "address": "0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7",
+ "name": "Unicorns",
+ "symbol": "\ud83e\udd84",
+ "decimals": 0
+ },
+ {
+ "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
+ "name": "Tether USD",
+ "symbol": "USDT",
+ "decimals": 6
+ },
+ {
+ "address": "0xd01db73e047855efb414e6202098c4be4cd2423b",
+ "name": "Uquid Coin",
+ "symbol": "UQC",
+ "decimals": 18
+ },
+ {
+ "address": "0x70a72833d6bf7f508c8224ce59ea1ef3d0ea3a38",
+ "name": "UTRUST Token",
+ "symbol": "UTK",
+ "decimals": 18
+ },
+ {
+ "address": "0x340d2bde5eb28c1eed91b2f790723e3b160613b7",
+ "name": "BLOCKv Token",
+ "symbol": "VEE",
+ "decimals": 18
+ },
+ {
+ "address": "0xEbeD4fF9fe34413db8fC8294556BBD1528a4DAca",
+ "name": "VENUS",
+ "symbol": "VENUS",
+ "decimals": 3
+ },
+ {
+ "address": "0x8f3470A7388c05eE4e7AF3d01D8C722b0FF52374",
+ "name": "Veritaseum",
+ "symbol": "VERI",
+ "decimals": 18
+ },
+ {
+ "address": "0xD850942eF8811f2A866692A623011bDE52a462C1",
+ "name": "VeChain Token",
+ "symbol": "VEN",
+ "decimals": 18
+ },
+ {
+ "address": "0xe8ff5c9c75deb346acac493c463c8950be03dfba",
+ "name": "Vibe Coin",
+ "symbol": "VIBE",
+ "decimals": 18
+ },
+ {
+ "address": "0x2C974B2d0BA1716E644c1FC59982a89DDD2fF724",
+ "name": "VIB",
+ "symbol": "VIB",
+ "decimals": 18
+ },
+ {
+ "address": "0x882448f83d90b2bf477af2ea79327fdea1335d93",
+ "name": "VIBEX Exchange Token",
+ "symbol": "VIBEX",
+ "decimals": 18
+ },
+ {
+ "address": "0x519475b31653e46d20cd09f9fdcf3b12bdacb4f5",
+ "name": "VIU",
+ "symbol": "VIU",
+ "decimals": 18
+ },
+ {
+ "address": "0x83eea00d838f92dec4d1475697b9f4d3537b56e3",
+ "name": "VOISE",
+ "symbol": "VOISE",
+ "decimals": 8
+ },
+ {
+ "address": "0xeDBaF3c5100302dCddA53269322f3730b1F0416d",
+ "name": "VEROS",
+ "symbol": "VRS",
+ "decimals": 5
+ },
+ {
+ "address": "0x5c543e7AE0A1104f78406C340E9C64FD9fCE5170",
+ "name": "vSlice",
+ "symbol": "VSL",
+ "decimals": 18
+ },
+ {
+ "address": "0x286BDA1413a2Df81731D4930ce2F862a35A609fE",
+ "name": "WaBi",
+ "symbol": "WaBi",
+ "decimals": 18
+ },
+ {
+ "address": "0x39Bb259F66E1C59d5ABEF88375979b4D20D98022",
+ "name": "Wax Token",
+ "symbol": "WAX",
+ "decimals": 8
+ },
+ {
+ "address": "0x74951B677de32D596EE851A233336926e6A2cd09",
+ "name": "We Bet Crypto",
+ "symbol": "WBA",
+ "decimals": 7
+ },
+ {
+ "address": "0x6a0a97e47d15aad1d132a1ac79a480e3f2079063",
+ "name": "WePower Contribution Token",
+ "symbol": "WCT",
+ "decimals": 18
+ },
+ {
+ "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ "name": "Wrapped Ether",
+ "symbol": "WETH",
+ "decimals": 18
+ },
+ {
+ "address": "0x5e4ABE6419650CA839Ce5BB7Db422b881a6064bB",
+ "name": "Wi Coin",
+ "symbol": "WiC",
+ "decimals": 18
+ },
+ {
+ "address": "0x667088b212ce3d06a1b553a7221E1fD19000d9aF",
+ "name": "WINGS",
+ "symbol": "WINGS",
+ "decimals": 18
+ },
+ {
+ "address": "0xF6B55acBBC49f4524Aa48D19281A9A77c54DE10f",
+ "name": "WOLK TOKEN",
+ "symbol": "WLK",
+ "decimals": 18
+ },
+ {
+ "address": "0x728781E75735dc0962Df3a51d7Ef47E798A7107E",
+ "name": "Token Wolk Protocol Token",
+ "symbol": "WOLK",
+ "decimals": 18
+ },
+ {
+ "address": "0x62087245087125d3db5b9a3d713d78e7bbc31e54",
+ "name": "WorldPeaceCoin",
+ "symbol": "WPC",
+ "decimals": 18
+ },
+ {
+ "address": "0x910Dfc18D6EA3D6a7124A6F8B5458F281060fa4c",
+ "name": "X8XToken",
+ "symbol": "X8X",
+ "decimals": 18
+ },
+ {
+ "address": "0x4DF812F6064def1e5e029f1ca858777CC98D2D81",
+ "name": "Xaurum",
+ "symbol": "XAUR",
+ "decimals": 8
+ },
+ {
+ "address": "0x4d829f8c92a6691c56300d020c9e0db984cfe2ba",
+ "name": "CoinCrowd",
+ "symbol": "XCC",
+ "decimals": 18
+ },
+ {
+ "address": "0x533ef0984b2FAA227AcC620C67cce12aA39CD8CD",
+ "name": "XaurumGamma",
+ "symbol": "XGM",
+ "decimals": 8
+ },
+ {
+ "address": "0x30f4A3e0aB7a76733D8b60b89DD93c3D0b4c9E2f",
+ "name": "CryptogeneToken",
+ "symbol": "XGT",
+ "decimals": 18
+ },
+ {
+ "address": "0xB110eC7B1dcb8FAB8dEDbf28f53Bc63eA5BEdd84",
+ "name": "Sphre AIR",
+ "symbol": "XID",
+ "decimals": 8
+ },
+ {
+ "address": "0xab95e915c123fded5bdfb6325e35ef5515f1ea69",
+ "name": "XENON",
+ "symbol": "XNN",
+ "decimals": 18
+ },
+ {
+ "address": "0x572e6f318056ba0c5d47a422653113843d250691",
+ "name": "EXANTE Token",
+ "symbol": "XNT",
+ "decimals": 0
+ },
+ {
+ "address": "0xB24754bE79281553dc1adC160ddF5Cd9b74361a4",
+ "name": "RIALTO",
+ "symbol": "XRL",
+ "decimals": 9
+ },
+ {
+ "address": "0x0F513fFb4926ff82D7F60A05069047AcA295C413",
+ "name": "CrowdstartCoin",
+ "symbol": "XSC",
+ "decimals": 18
+ },
+ {
+ "address": "0x0F33bb20a282A7649C7B3AFf644F084a9348e933",
+ "name": "YUPIE",
+ "symbol": "YUP",
+ "decimals": 18
+ },
+ {
+ "address": "0x6781a0f84c7e9e846dcb84a9a5bd49333067b104",
+ "name": "ZAP TOKEN",
+ "symbol": "ZAP",
+ "decimals": 18
+ },
+ {
+ "address": "0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27",
+ "name": "Zilliqa",
+ "symbol": "ZIL",
+ "decimals": 12
+ },
+ {
+ "address": "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
+ "name": "0x Protocol Token",
+ "symbol": "ZRX",
+ "decimals": 18
+ },
+ {
+ "address": "0xe386b139ed3715ca4b18fd52671bdcea1cdfe4b1",
+ "name": "Zeus Token",
+ "symbol": "ZST",
+ "decimals": 8
+ }
+]
\ No newline at end of file
diff --git a/src/fonts/glyphicons.eot b/src/fonts/glyphicons.eot
new file mode 100755
index 00000000..56792f69
Binary files /dev/null and b/src/fonts/glyphicons.eot differ
diff --git a/src/fonts/glyphicons.svg b/src/fonts/glyphicons.svg
new file mode 100755
index 00000000..48cddc5a
--- /dev/null
+++ b/src/fonts/glyphicons.svg
@@ -0,0 +1,21 @@
+
+
+
+Generated by IcoMoon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/fonts/glyphicons.ttf b/src/fonts/glyphicons.ttf
new file mode 100755
index 00000000..f1c30440
Binary files /dev/null and b/src/fonts/glyphicons.ttf differ
diff --git a/src/fonts/glyphicons.woff b/src/fonts/glyphicons.woff
new file mode 100755
index 00000000..a0fa4cb6
Binary files /dev/null and b/src/fonts/glyphicons.woff differ
diff --git a/src/fonts/icomoon.eot b/src/fonts/icomoon.eot
new file mode 100755
index 00000000..153870a4
Binary files /dev/null and b/src/fonts/icomoon.eot differ
diff --git a/src/fonts/icomoon.svg b/src/fonts/icomoon.svg
new file mode 100755
index 00000000..05e7fc9c
--- /dev/null
+++ b/src/fonts/icomoon.svg
@@ -0,0 +1,41 @@
+
+
+
+Generated by IcoMoon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/fonts/icomoon.ttf b/src/fonts/icomoon.ttf
new file mode 100755
index 00000000..1e3aa3a8
Binary files /dev/null and b/src/fonts/icomoon.ttf differ
diff --git a/src/fonts/icomoon.woff b/src/fonts/icomoon.woff
new file mode 100755
index 00000000..65c1cc49
Binary files /dev/null and b/src/fonts/icomoon.woff differ
diff --git a/src/fonts/pass.ttf b/src/fonts/pass.ttf
new file mode 100644
index 00000000..c0f50b1b
Binary files /dev/null and b/src/fonts/pass.ttf differ
diff --git a/src/fonts/roboto/RobotoZero.eot b/src/fonts/roboto/RobotoZero.eot
new file mode 100644
index 00000000..55531629
Binary files /dev/null and b/src/fonts/roboto/RobotoZero.eot differ
diff --git a/src/fonts/roboto/RobotoZero.ttf b/src/fonts/roboto/RobotoZero.ttf
new file mode 100644
index 00000000..524e29e0
Binary files /dev/null and b/src/fonts/roboto/RobotoZero.ttf differ
diff --git a/src/fonts/roboto/RobotoZero.woff b/src/fonts/roboto/RobotoZero.woff
new file mode 100755
index 00000000..2807d896
Binary files /dev/null and b/src/fonts/roboto/RobotoZero.woff differ
diff --git a/src/images/bch-logo.png b/src/images/bch-logo.png
new file mode 100644
index 00000000..9590c12e
Binary files /dev/null and b/src/images/bch-logo.png differ
diff --git a/src/images/btc-logo.png b/src/images/btc-logo.png
new file mode 100644
index 00000000..5b77226c
Binary files /dev/null and b/src/images/btc-logo.png differ
diff --git a/src/images/btg-logo.png b/src/images/btg-logo.png
new file mode 100644
index 00000000..5eda4570
Binary files /dev/null and b/src/images/btg-logo.png differ
diff --git a/src/images/bth-logo.png b/src/images/bth-logo.png
new file mode 100644
index 00000000..dcc596e2
Binary files /dev/null and b/src/images/bth-logo.png differ
diff --git a/src/images/case.png b/src/images/case.png
new file mode 100644
index 00000000..eee16f1d
Binary files /dev/null and b/src/images/case.png differ
diff --git a/src/images/dash-logo.png b/src/images/dash-logo.png
new file mode 100644
index 00000000..5c3a113d
Binary files /dev/null and b/src/images/dash-logo.png differ
diff --git a/src/images/dashboard.png b/src/images/dashboard.png
new file mode 100644
index 00000000..790afebf
Binary files /dev/null and b/src/images/dashboard.png differ
diff --git a/src/images/etc-logo.png b/src/images/etc-logo.png
new file mode 100644
index 00000000..e868d6ea
Binary files /dev/null and b/src/images/etc-logo.png differ
diff --git a/src/images/eth-logo.png b/src/images/eth-logo.png
new file mode 100644
index 00000000..5b7c1a8a
Binary files /dev/null and b/src/images/eth-logo.png differ
diff --git a/src/images/icons-spritesheet.svg b/src/images/icons-spritesheet.svg
new file mode 100644
index 00000000..337b1466
--- /dev/null
+++ b/src/images/icons-spritesheet.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/images/icontrezor.png b/src/images/icontrezor.png
new file mode 100644
index 00000000..8c35659b
Binary files /dev/null and b/src/images/icontrezor.png differ
diff --git a/src/images/landingpage.png b/src/images/landingpage.png
new file mode 100644
index 00000000..43e64752
Binary files /dev/null and b/src/images/landingpage.png differ
diff --git a/src/images/ltc-logo.png b/src/images/ltc-logo.png
new file mode 100644
index 00000000..201f2350
Binary files /dev/null and b/src/images/ltc-logo.png differ
diff --git a/src/images/nem-logo.png b/src/images/nem-logo.png
new file mode 100644
index 00000000..1b4cccf2
Binary files /dev/null and b/src/images/nem-logo.png differ
diff --git a/src/images/satoshilabs.png b/src/images/satoshilabs.png
new file mode 100644
index 00000000..4e3bc6e6
Binary files /dev/null and b/src/images/satoshilabs.png differ
diff --git a/src/images/trezor-logo.svg b/src/images/trezor-logo.svg
new file mode 100644
index 00000000..c44f5b6b
--- /dev/null
+++ b/src/images/trezor-logo.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/images/zec-logo.png b/src/images/zec-logo.png
new file mode 100644
index 00000000..134ba792
Binary files /dev/null and b/src/images/zec-logo.png differ
diff --git a/src/index.html b/src/index.html
index eee65fbc..257d119f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4,7 +4,7 @@
-
TrezorConnect boilerplate with React | TREZOR
+ Ethereum Wallet| TREZOR
diff --git a/src/js/actions/AccountActions.js b/src/js/actions/AccountActions.js
new file mode 100644
index 00000000..8f2b82f3
--- /dev/null
+++ b/src/js/actions/AccountActions.js
@@ -0,0 +1,106 @@
+/* @flow */
+'use strict';
+
+import * as ACCOUNT from './constants/account';
+
+import { initialState } from '../reducers/AccountDetailReducer';
+import { findSelectedDevice } from '../reducers/TrezorConnectReducer';
+
+import type { State } from '../reducers/AccountDetailReducer';
+import type { Discovery } from '../reducers/DiscoveryReducer';
+
+export const init = (): any => {
+ return (dispatch, getState): void => {
+
+ const { location } = getState().router;
+ const urlParams = location.params;
+
+ const selected = findSelectedDevice( getState().connect );
+ if (!selected) return;
+
+
+ const state: State = {
+ index: parseInt(urlParams.address),
+ checksum: selected.checksum,
+ coin: urlParams.coin,
+ location: location.pathname
+ };
+
+ dispatch({
+ type: ACCOUNT.INIT,
+ state: state
+ });
+
+
+ // let discoveryProcess: ?Discovery = getState().discovery.find(d => d.checksum === selected.checksum && d.coin === currentAccount.coin);
+ // const discovering: boolean = (!discoveryProcess || !discoveryProcess.completed);
+
+ // const state: State = {
+ // ...initialState,
+ // loaded: true,
+ // checksum: currentAccount.checksum,
+ // address: currentAccount.address,
+ // coin: urlParams.coin,
+ // balance: currentAccount.balance,
+
+ // discovering
+ // };
+
+ // dispatch({
+ // type: ACCOUNT.INIT,
+ // state
+ // });
+ }
+}
+
+export const update = (newProps: any): any => {
+ return (dispatch, getState): void => {
+
+ const {
+ accountDetail,
+ connect,
+ discovery,
+ accounts,
+ router
+ } = getState();
+
+ const isLocationChanged: boolean = newProps.location.pathname !== accountDetail.location;
+
+ if (isLocationChanged) {
+ dispatch({
+ type: ACCOUNT.INIT,
+ state: {
+ ...accountDetail,
+ location: newProps.location.pathname,
+ }
+ });
+ return;
+ }
+
+ // update comes from device
+ // const device = connect.devices.find(d => d.checksum === accountDetail.checksum);
+ // if (accountDetail.detail !== device) {
+ // console.warn("DEV UPDATE!!!!")
+ // }
+
+ // const discoveryProcess = discovery.find(d => d.checksum === device.checksum && d.coin === accountDetail.coin);
+
+ // const account = accounts.find(a => a.checksum === accountDetail.checksum && a.index === accountDetail.addressIndex && a.coin === accountDetail.coin);
+ // if (account && !accountDetail.address) {
+ // // update current address
+ // console.warn("ACC UPDATE!!!!")
+ // }
+
+
+ // isDeviceChanged
+ // isDiscoveryChanged
+ }
+}
+
+export const dispose = (device: any): any => {
+ return (dispatch, getState): void => {
+ dispatch({
+ type: ACCOUNT.DISPOSE,
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/js/actions/AppActions.js b/src/js/actions/AppActions.js
new file mode 100644
index 00000000..99d40154
--- /dev/null
+++ b/src/js/actions/AppActions.js
@@ -0,0 +1,33 @@
+/* @flow */
+'use strict';
+
+export const ON_RESIZE: string = 'ON_RESIZE';
+export const ON_BEFORE_UNLOAD: string = 'app__on_before_unload';
+export const TOGGLE_DEVICE_DROPDOWN: string = 'TOGGLE_DEVICE_DROPDOWN';
+export const RESIZE_CONTAINER: string = 'RESIZE_CONTAINER';
+
+export const onResize = (): any => {
+ return {
+ type: ON_RESIZE
+ }
+}
+
+export const onBeforeUnload = (): any => {
+ return {
+ type: ON_BEFORE_UNLOAD
+ }
+}
+
+export const resizeAppContainer = (opened: boolean): any => {
+ return {
+ type: RESIZE_CONTAINER,
+ opened
+ }
+}
+
+export const toggleDeviceDropdown = (opened: boolean): any => {
+ return {
+ type: TOGGLE_DEVICE_DROPDOWN,
+ opened
+ }
+}
diff --git a/src/js/actions/DOMActions.js b/src/js/actions/DOMActions.js
deleted file mode 100644
index 969c4f16..00000000
--- a/src/js/actions/DOMActions.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/* @flow */
-'use strict';
-
-export const ON_RESIZE: string = 'ON_RESIZE';
-
-export const onResize = (): void => {
- return {
- type: ON_RESIZE
- }
-}
diff --git a/src/js/actions/LocalStorageActions.js b/src/js/actions/LocalStorageActions.js
new file mode 100644
index 00000000..e6d2203c
--- /dev/null
+++ b/src/js/actions/LocalStorageActions.js
@@ -0,0 +1,112 @@
+/* @flow */
+'use strict';
+
+import * as CONNECT from './constants/TrezorConnect';
+import * as ADDRESS from './constants/Address';
+import * as TOKEN from './constants/Token';
+import * as DISCOVERY from './constants/Discovery';
+import * as STORAGE from './constants/LocalStorage';
+import { httpRequest } from '../utils/networkUtils';
+
+export function loadData(): any {
+ return async (dispatch, getState) => {
+
+ // check if local storage is available
+ // let available: boolean = true;
+ // if (typeof window.localStorage === 'undefined') {
+ // available = false;
+ // } else {
+ // try {
+ // window.localStorage.setItem('ethereum_wallet', true);
+ // } catch (error) {
+ // available = false;
+ // }
+ // }
+
+ dispatch( loadTokensFromJSON() );
+ }
+}
+
+export function loadTokensFromJSON(): any {
+ return async (dispatch, getState) => {
+ try {
+ const appConfig = await httpRequest('data/appConfig.json', 'json');
+ const ethTokens = await httpRequest('data/ethTokens.json', 'json');
+ const ethERC20 = await httpRequest('data/ethERC20.json', 'json');
+
+ const devices: ?string = get('devices');
+ console.log("GET23", JSON.parse(devices))
+ if (devices) {
+ dispatch({
+ type: CONNECT.DEVICE_FROM_STORAGE,
+ payload: JSON.parse(devices)
+ })
+ }
+
+ const accounts: ?string = get('accounts');
+ if (accounts) {
+ dispatch({
+ type: ADDRESS.FROM_STORAGE,
+ payload: JSON.parse(accounts)
+ })
+ }
+
+ const tokens: ?string = get('tokens');
+ if (tokens) {
+ dispatch({
+ type: TOKEN.FROM_STORAGE,
+ payload: JSON.parse(tokens)
+ })
+ }
+
+ const discovery: ?string = get('discovery');
+ if (discovery) {
+ dispatch({
+ type: DISCOVERY.FROM_STORAGE,
+ payload: JSON.parse(discovery)
+ })
+ }
+
+
+ dispatch({
+ type: STORAGE.READY,
+ appConfig,
+ ethTokens,
+ ethERC20
+ })
+
+ } catch(error) {
+ dispatch({
+ type: STORAGE.ERROR,
+ error
+ })
+ }
+ }
+}
+
+
+export const save = (key: string, value: string): any => {
+ return (dispatch, getState) => {
+ if (typeof window.localStorage !== 'undefined') {
+ //console.log("SAVEE!!!!", key, value)
+ try {
+ window.localStorage.setItem(key, value);
+ } catch (error) {
+ // available = false;
+ console.error("ERROR: " + error)
+ }
+ }
+ }
+}
+
+export const get = (key: string): ?string => {
+ if (typeof window.localStorage !== 'undefined') {
+ try {
+ console.log("GETTT", JSON.parse(window.localStorage.getItem(key)))
+ return window.localStorage.getItem(key);
+ } catch (error) {
+ // available = false;
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/js/actions/ModalActions.js b/src/js/actions/ModalActions.js
index 68fe9226..cd5b3446 100644
--- a/src/js/actions/ModalActions.js
+++ b/src/js/actions/ModalActions.js
@@ -3,125 +3,75 @@
import TrezorConnect, { UI, UI_EVENT } from 'trezor-connect';
import * as ACTIONS from './index';
+import * as MODAL from './constants/Modal';
+import * as CONNECT from './constants/TrezorConnect';
-export function onPinAdd(number: number): any {
- return {
- type: ACTIONS.ON_PIN_ADD,
- number
- }
-}
-export function onPinBackspace(): any {
- return {
- type: ACTIONS.ON_PIN_BACKSPACE
- }
-}
-
-export function onPinSubmit(value: string): void {
+export function onPinSubmit(value: string): any {
TrezorConnect.uiMessage({ type: UI.RECEIVE_PIN, data: value });
return {
type: ACTIONS.CLOSE_MODAL
}
}
-export function onPassphraseChange(value: string): any {
- return {
- type: ACTIONS.ON_PASSPHRASE_CHANGE,
- value
- }
-}
-
-export function onPassphraseShow(): any {
- return {
- type: ACTIONS.ON_PASSPHRASE_SHOW
- }
-}
-
-export function onPassphraseHide(): any {
- return {
- type: ACTIONS.ON_PASSPHRASE_HIDE
- }
-}
-
-export function onPassphraseSave(): any {
- return {
- type: ACTIONS.ON_PASSPHRASE_SAVE
+export function onPassphraseSubmit(passphrase: string): any {
+ return async (dispatch, getState): Promise => {
+ const resp = await TrezorConnect.uiMessage({
+ type: UI.RECEIVE_PASSPHRASE,
+ data: {
+ value: passphrase,
+ save: true
+ }
+ });
+
+ dispatch({
+ type: ACTIONS.CLOSE_MODAL
+ });
}
}
-export function onPassphraseForget(): any {
+export const askForRemember = (device: any) => {
return {
- type: ACTIONS.ON_PASSPHRASE_FORGET
+ type: MODAL.REMEMBER,
+ device
}
}
-export function onPassphraseFocus(): any {
+export const onRememberDevice = (device: any) => {
return {
- type: ACTIONS.ON_PASSPHRASE_FOCUS
+ type: CONNECT.REMEMBER,
+ device
}
}
-export function onPassphraseBlur(): any {
+export const onForgetDevice = (device: any) => {
return {
- type: ACTIONS.ON_PASSPHRASE_BLUR
+ type: CONNECT.FORGET,
+ device,
}
}
-export function onPassphraseSubmit(value: string, cache: boolean): void {
- TrezorConnect.uiMessage({
- type: UI.RECEIVE_PASSPHRASE,
- data: {
- value,
- save: cache
- }
- });
+export const onForgetSingleDevice = (device: any) => {
return {
- type: ACTIONS.CLOSE_MODAL
+ type: CONNECT.FORGET_SINGLE,
+ device,
}
}
-export function onConfirmation(): any {
- //postMessage(new UiMessage(UI.RECEIVE_CONFIRMATION, 'true') );
- TrezorConnect.uiMessage({
- type: UI.RECEIVE_CONFIRMATION,
- data: 'true'
- });
-
+export const onCancel = () => {
return {
type: ACTIONS.CLOSE_MODAL
}
}
-export function onConfirmationCancel(): any {
- TrezorConnect.uiMessage({
- type: UI.RECEIVE_CONFIRMATION,
- data: 'false'
- });
-
- return {
- type: ACTIONS.CLOSE_MODAL
- }
-}
+export const onDuplicateDevice = (device: any): any => {
+ return (dispatch: any, getState: any): void => {
-export function onPermissionGranted(): any {
- //postMessage(new UiMessage(UI.RECEIVE_CONFIRMATION, 'true') );
- TrezorConnect.uiMessage({
- type: UI.RECEIVE_PERMISSION,
- data: 'true'
- });
+ dispatch( onCancel() );
- return {
- type: ACTIONS.CLOSE_MODAL
- }
-}
-
-export function onPermissionRejected(): any {
- TrezorConnect.uiMessage({
- type: UI.RECEIVE_PERMISSION,
- data: 'false'
- });
-
- return {
- type: ACTIONS.CLOSE_MODAL
+ dispatch({
+ type: CONNECT.DUPLICATE,
+ device
+ });
}
}
\ No newline at end of file
diff --git a/src/js/actions/ReceiveActions.js b/src/js/actions/ReceiveActions.js
new file mode 100644
index 00000000..4ee0ee7c
--- /dev/null
+++ b/src/js/actions/ReceiveActions.js
@@ -0,0 +1,110 @@
+/* @flow */
+'use strict';
+
+import TrezorConnect from 'trezor-connect';
+import * as RECEIVE from './constants/receive';
+import * as NOTIFICATION from './constants/notification';
+
+import { initialState } from '../reducers/ReceiveReducer';
+import type { State } from '../reducers/ReceiveReducer';
+import { findSelectedDevice } from '../reducers/TrezorConnectReducer';
+
+
+export const init = (): any => {
+ return (dispatch, getState): void => {
+ const { location } = getState().router;
+ const urlParams = location.params;
+
+ const selected = findSelectedDevice( getState().connect );
+ if (!selected) return;
+
+ const state: State = {
+ ...initialState,
+ checksum: selected.checksum,
+ accountIndex: parseInt(urlParams.address),
+ coin: urlParams.coin,
+ location: location.pathname,
+ };
+
+ dispatch({
+ type: RECEIVE.INIT,
+ state: state
+ });
+ }
+}
+
+
+export const update = (newProps: any): any => {
+ return (dispatch, getState): void => {
+ const {
+ receive,
+ router
+ } = getState();
+
+ const isLocationChanged: boolean = router.location.pathname !== receive.location;
+ if (isLocationChanged) {
+ dispatch( init() );
+ return;
+ }
+ }
+}
+
+export const dispose = (address: string): any => {
+ return {
+ type: RECEIVE.DISPOSE
+ }
+}
+
+export const showUnverifiedAddress = () => {
+ return {
+ type: RECEIVE.SHOW_UNVERIFIED_ADDRESS
+ }
+}
+
+export const showAddress = (address_n: string): any => {
+ return async (dispatch, getState) => {
+
+ const selected = findSelectedDevice(getState().connect);
+
+ if (selected && !selected.connected) {
+ dispatch({
+ type: RECEIVE.REQUEST_UNVERIFIED,
+ });
+ return;
+ }
+
+ const response = await TrezorConnect.ethereumGetAddress({
+ device: {
+ path: selected.path,
+ instance: selected.instance,
+ state: selected.checksum
+ },
+ address_n
+ });
+
+ if (response && response.success) {
+ dispatch({
+ type: RECEIVE.SHOW_ADDRESS
+ })
+ } else {
+ // TODO: handle invalid pin?
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Veryfying address error',
+ message: response.data.error,
+ cancelable: true,
+ actions: [
+ {
+ label: 'Try again',
+ callback: () => {
+ dispatch(showAddress(address_n))
+ }
+ }
+ ]
+ }
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/js/actions/SendFormActions.js b/src/js/actions/SendFormActions.js
index 1f7c791d..f923ce7c 100644
--- a/src/js/actions/SendFormActions.js
+++ b/src/js/actions/SendFormActions.js
@@ -1,80 +1,672 @@
/* @flow */
'use strict';
-import * as ACTIONS from './index';
-import { getNonce, estimateGas, getGasPrice, push } from './Web3Actions';
+import * as SEND from './constants/SendForm';
+import * as NOTIFICATION from './constants/notification';
+
+import { getNonce, estimateGas, getGasPrice, pushTx } from './Web3Actions';
import EthereumjsUtil from 'ethereumjs-util';
+import EthereumjsUnits from 'ethereumjs-units';
import EthereumjsTx from 'ethereumjs-tx';
import TrezorConnect from 'trezor-connect';
import { strip } from '../utils/ethUtils';
+import { push } from 'react-router-redux';
+import BigNumber from 'bignumber.js';
+import { initialState } from '../reducers/SendFormReducer';
+import type { State, FeeLevel } from '../reducers/SendFormReducer';
+import { findSelectedDevice } from '../reducers/TrezorConnectReducer';
-export const onAddressChange = (address: string): void => {
- return {
- type: ACTIONS.ON_ADDRESS_CHANGE,
- address
+const numberRegExp = new RegExp('^([0-9]{0,10}\\.)?[0-9]{1,18}$');
+
+const calculateFee = (gasPrice: string, gasLimit: string): string => {
+ return EthereumjsUnits.convert( new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether');
+}
+
+const calculateTotal = (amount: string, gasPrice: string, gasLimit: string): string => {
+ try {
+ return new BigNumber(amount).plus( calculateFee(gasPrice, gasLimit) ).toString();
+ } catch (error) {
+ return '0';
}
}
-export const onAmountChange = (amount: string): void => {
- return {
- type: ACTIONS.ON_AMOUNT_CHANGE,
- amount
+export const calculateMaxAmount = (balance: string, gasPrice: string, gasLimit: string): string => {
+ try {
+ const fee = EthereumjsUnits.convert( new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether');
+ const b = new BigNumber(balance);
+ const max = b.minus(fee);
+ if (max.lessThan(0)) return '0';
+ return max.toString();
+ } catch (error) {
+ return '0';
}
+
}
-export const onGasPriceChange = (gasPrice: string): void => {
- return {
- type: ACTIONS.ON_GAS_PRICE_CHANGE,
- gasPrice
+export const getFeeLevels = (coin: string, gasPrice: BigNumber | string, gasLimit: string): Array => {
+ if (typeof gasPrice === 'string') gasPrice = new BigNumber(gasPrice);
+ const quarter: BigNumber = gasPrice.dividedBy(4);
+ const high: string = gasPrice.plus(quarter.times(2)).toString();
+ const low: string = gasPrice.minus(quarter.times(2)).toString();
+ coin = coin.toUpperCase();
+
+ return [
+ {
+ value: 'High',
+ gasPrice: high,
+ label: `${ calculateFee(high, gasLimit) } ${ coin }`
+ },
+ {
+ value: 'Normal',
+ gasPrice: gasPrice.toString(),
+ label: `${ calculateFee(gasPrice.toString(), gasLimit) } ${ coin }`
+ },
+ {
+ value: 'Low',
+ gasPrice: low,
+ label: `${ calculateFee(low, gasLimit) } ${ coin }`
+ },
+ {
+ value: 'Custom',
+ gasPrice: low,
+ label: '',
+ },
+ ]
+}
+
+export const findBalance = (getState: any): string => {
+ const state = getState().sendForm;
+ const account = getState().accounts.find(a => a.checksum === state.checksum && a.index === state.accountIndex && a.coin === state.coin);
+
+ if (state.token !== state.coin) {
+ return getState().tokens.find(t => t.ethAddress === account.address && t.symbol === state.token).balance;
+ } else {
+ return account.balance;
}
}
-export const onGasLimitChange = (gasLimit: string): void => {
+
+// initialize component
+export const init = (): any => {
+ return (dispatch, getState): void => {
+
+ const { location } = getState().router;
+ const urlParams = location.params;
+
+ const selected = findSelectedDevice( getState().connect );
+ if (!selected) return;
+
+ const web3instance = getState().web3.find(w3 => w3.coin === urlParams.coin);
+ if (!web3instance) {
+ // no backend for this coin
+ //return;
+ }
+
+ // TODO: check if there are some unfinished tx in localStorage
+ const { config } = getState().localStorage;
+ const coin = config.coins.find(c => c.symbol === urlParams.coin);
+
+ const gasPrice: BigNumber = new BigNumber( EthereumjsUnits.convert(web3instance.gasPrice, 'wei', 'gwei') ) || new BigNumber(coin.defaultGasPrice);
+ const gasLimit: string = coin.defaultGasLimit.toString();
+ const feeLevels: Array = getFeeLevels(urlParams.coin, gasPrice, gasLimit);
+
+ // TODO: get nonce
+
+ const state: State = {
+ ...initialState,
+ checksum: selected.checksum,
+ accountIndex: parseInt(urlParams.address),
+ coin: urlParams.coin,
+ token: urlParams.coin,
+ location: location.pathname,
+
+ feeLevels,
+ selectedFeeLevel: feeLevels.find(f => f.value === 'Normal'),
+ recommendedGasPrice: gasPrice.toString(),
+ gasLimit,
+ gasPrice: gasPrice.toString(),
+ nonce: '', // TODO!!!
+ };
+
+ dispatch({
+ type: SEND.INIT,
+ state
+ });
+ }
+}
+
+export const update = (): any => {
+ return (dispatch, getState): void => {
+ const {
+ sendForm,
+ router
+ } = getState();
+
+ const isLocationChanged: boolean = router.location.pathname !== sendForm.location;
+ if (isLocationChanged) {
+ dispatch( init() );
+ return;
+ }
+ }
+}
+
+export const dispose = (): any => {
return {
- type: ACTIONS.ON_GAS_LIMIT_CHANGE,
- gasLimit
+ type: SEND.DISPOSE
}
}
-export const onTxDataChange = (data: string): void => {
+export const toggleAdvanced = (address: string): any => {
return {
- type: ACTIONS.ON_TX_DATA_CHANGE,
- data
+ type: SEND.TOGGLE_ADVANCED
}
}
-export const onSend = (addressId): void => {
- return async (dispatch, getState) => {
- const { web3 } = getState().web3;
- const { address, amount } = getState().sendForm;
- const { addresses } = getState().addresses;
+export const validation = (): any => {
+ return (dispatch, getState): void => {
+
+ const state: State = getState().sendForm;
+ const errors: {[k: string]: string} = {};
+ const warnings: {[k: string]: string} = {};
+ const infos: {[k: string]: string} = {};
- const currentAddress = addresses[addressId];
- const address_n = currentAddress.path;
+ if (!state.untouched) {
+
+ // valid address
+ if (state.touched.address) {
+
+ const accounts = getState().accounts;
+ const myAccount = accounts.find(a => a.address.toLowerCase() === state.address.toLowerCase());
+
+ if (state.address.length < 1) {
+ errors.address = 'Address is not set';
+ } else if (!EthereumjsUtil.isValidAddress(state.address)) {
+ errors.address = 'Address is not valid';
+ } else if (myAccount) {
+ if (myAccount.coin === state.coin) {
+ infos.address = `TREZOR Address #${ (myAccount.index + 1) }`;
+ } else {
+ // TODO: load coins from config
+ warnings.address = `Looks like it's TREZOR address in Account #${ (myAccount.index + 1) } of ${ myAccount.coin.toUpperCase() }`;
+ }
+ }
+ }
+
+ // valid amount
+ // https://stackoverflow.com/a/42701461
+ //const regexp = new RegExp('^(?:[0-9]{0,10}\\.)?[0-9]{1,18}$');
+ if (state.touched.amount) {
+ if (state.amount.length < 1) {
+ errors.amount = 'Amount is not set';
+ } else if (state.amount.length > 0 && !state.amount.match(numberRegExp)) {
+ errors.amount = 'Amount is not a number';
+ } else {
+ const account = getState().accounts.find(a => a.checksum === state.checksum && a.index === state.accountIndex && a.coin === state.coin);
+ if (state.token !== state.coin) {
+ const tokenBalance: string = getState().tokens.find(t => t.ethAddress === account.address && t.symbol === state.token).balance;
+ if (new BigNumber(state.total).greaterThan(account.balance)) {
+ errors.amount = `Not enough ${ state.coin.toUpperCase() } to cover transaction fee`;
+ } else if (new BigNumber(state.amount).greaterThan(tokenBalance)) {
+ errors.amount = 'Not enough funds';
+ }
+ } else {
+ if (new BigNumber(state.total).greaterThan(account.balance)) {
+ errors.amount = 'Not enough funds';
+ }
+ }
+ }
+ }
+
+ // valid gas limit
+ if (state.touched.gasLimit) {
+ if (state.gasLimit.length < 1) {
+ errors.gasLimit = 'Gas limit is not set';
+ } else if (state.gasLimit.length > 0 && !state.gasLimit.match(numberRegExp)) {
+ errors.gasLimit = 'Gas limit is not a number';
+ } else {
+ const gl: BigNumber = new BigNumber(state.gasLimit);
+ if (gl.lessThan(1)) {
+ errors.gasLimit = 'Gas limit is too low';
+ } else if (gl.lessThan(1000)) {
+ warnings.gasLimit = 'Gas limit is below recommended';
+ }
+ }
+ }
+
+ // valid gas price
+ if (state.touched.gasPrice) {
+ if (state.gasPrice.length < 1) {
+ errors.gasPrice = 'Gas price is not set';
+ } else if (state.gasPrice.length > 0 && !state.gasPrice.match(numberRegExp)) {
+ errors.gasPrice = 'Gas price is not a number';
+ } else {
+ const gp: BigNumber = new BigNumber(state.gasPrice);
+ if (gp.greaterThan(100)) {
+ errors.gasPrice = 'Gas price is too high';
+ } else if (gp.lessThan(1)) {
+ errors.gasPrice = 'Gas price is too low';
+ }
+ }
+ }
+
+ // valid data
+ if (state.touched.data && state.coin === state.token && state.data.length > 0) {
+ const re = /^[0-9A-Fa-f]+$/g;
+ //const re = /^[0-9A-Fa-f]{6}$/g;
+ if (!re.test(state.data)) {
+ errors.data = 'Data is not valid hexadecimal';
+ }
+ }
+
+ // valid nonce?
+
+ dispatch({
+ type: SEND.VALIDATION,
+ errors,
+ warnings,
+ infos
+ });
+
+ }
+ }
+}
+
+
+export const onAddressChange = (address: string): any => {
+ return (dispatch, getState): void => {
+
+ const currentState: State = getState().sendForm;
+ const touched = { ...currentState.touched };
+ touched.address = true;
+
+ const state: State = {
+ ...currentState,
+ untouched: false,
+ touched,
+ address
+ };
+
+ dispatch({
+ type: SEND.ADDRESS_CHANGE,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const onAmountChange = (amount: string): any => {
+ return (dispatch, getState): void => {
+
+ const currentState: State = getState().sendForm;
+ const touched = { ...currentState.touched };
+ touched.amount = true;
+ const total: string = calculateTotal(currentState.token !== currentState.coin ? '0' : amount, currentState.gasPrice, currentState.gasLimit);
+
+ const state: State = {
+ ...currentState,
+ untouched: false,
+ touched,
+ setMax: false,
+ amount,
+ total
+ };
+
+ dispatch({
+ type: SEND.AMOUNT_CHANGE,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const onCurrencyChange = (currency: any): any => {
+
+ return (dispatch, getState): void => {
+
+ const currentState = getState().sendForm;
+
+ const account = getState().accounts.find(a => a.checksum === currentState.checksum && a.index === currentState.accountIndex && a.coin === currentState.coin);
+ if (!account) {
+ // account not found
+ return;
+ }
+
+ const { config } = getState().localStorage;
+ const coin = config.coins.find(c => c.symbol === currentState.coin);
+
+ let gasLimit: string = '';
+ let amount: string = currentState.amount;
+ let total: string;
+
+ if (currentState.coin !== currency.value) {
+ gasLimit = coin.defaultGasLimitTokens.toString();
+ if (currentState.setMax) {
+ const tokenBalance: string = getState().tokens.find(t => t.ethAddress === account.address && t.symbol === currency.value).balance;
+ amount = tokenBalance;
+ }
+ total = calculateTotal('0', currentState.gasPrice, currentState.gasLimit);
+ } else {
+ gasLimit = coin.defaultGasLimit.toString();
+ if (currentState.setMax) {
+ amount = calculateMaxAmount(account.balance, currentState.gasPrice, currentState.gasLimit);
+ }
+ total = calculateTotal(amount, currentState.gasPrice, currentState.gasLimit);
+ }
+
+ const feeLevels: Array = getFeeLevels(currentState.coin, currentState.gasPrice, gasLimit);
+
+ const state: State = {
+ ...currentState,
+ token: currency.value,
+ amount,
+ total,
+ feeLevels,
+ selectedFeeLevel: feeLevels.find(f => f.value === currentState.selectedFeeLevel.value),
+ gasLimit,
+ };
+
+ dispatch({
+ type: SEND.CURRENCY_CHANGE,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+
+
+export const onSetMax = (): any => {
+ return (dispatch, getState): void => {
+ const currentState = getState().sendForm;
+ const touched = { ...currentState.touched };
+ touched.amount = true;
+
+ const account = getState().accounts.find(a => a.checksum === currentState.checksum && a.index === currentState.accountIndex && a.coin === currentState.coin);
+ if (!account) {
+ // account not found
+ return;
+ }
+
+ let amount: string = currentState.amount;
+ let total: string = currentState.total;
+ if (!currentState.setMax) {
+ if (currentState.token !== currentState.coin) {
+ const tokenBalance: string = getState().tokens.find(t => t.ethAddress === account.address && t.symbol === currentState.token).balance;
+ amount = tokenBalance;
+ total = calculateTotal('0', currentState.gasPrice, currentState.gasLimit);
+ } else {
+ amount = calculateMaxAmount(account.balance, currentState.gasPrice, currentState.gasLimit);
+ total = calculateTotal(amount, currentState.gasPrice, currentState.gasLimit);
+ }
+ }
+
+ const state: State = {
+ ...currentState,
+ untouched: false,
+ touched,
+ setMax: !currentState.setMax,
+ amount,
+ total
+ };
+
+ dispatch({
+ type: SEND.SET_MAX,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const onFeeLevelChange = (feeLevel: any): any => {
+ return (dispatch, getState): void => {
+
+ const currentState = getState().sendForm;
+ const state: State = {
+ ...currentState,
+ untouched: false,
+ selectedFeeLevel: feeLevel,
+ };
+
+ if (feeLevel.value === 'Custom') {
+ // TODO: update value for custom fee
+ state.advanced = true;
+ feeLevel.gasPrice = state.gasPrice;
+ feeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.coin.toUpperCase() }`;
+ } else {
+ const customLevel = state.feeLevels.find(f => f.value === 'Custom');
+ customLevel.label = '';
+ state.gasPrice = feeLevel.gasPrice;
+ }
+
+ if (currentState.setMax) {
+ const account = getState().accounts.find(a => a.checksum === currentState.checksum && a.index === currentState.accountIndex && a.coin === currentState.coin);
+ if (state.token !== state.coin) {
+ const tokenBalance: string = getState().tokens.find(t => t.ethAddress === account.address && t.symbol === currentState.token).balance;
+ state.amount = tokenBalance;
+ } else {
+ state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
+ }
+ }
+ state.total = calculateTotal(state.token !== state.coin ? '0' : state.amount, state.gasPrice, state.gasLimit);
+
+ dispatch({
+ type: SEND.FEE_LEVEL_CHANGE,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const updateFeeLevels = (): any => {
+ return (dispatch, getState): void => {
+ const currentState = getState().sendForm;
+ const feeLevels: Array = getFeeLevels(currentState.coin, currentState.recommendedGasPrice, currentState.gasLimit);
+ const state: State = {
+ ...currentState,
+ feeLevels,
+ selectedFeeLevel: feeLevels.find(f => f.value === currentState.selectedFeeLevel.value),
+ gasPrice: currentState.recommendedGasPrice,
+ gasPriceNeedsUpdate: false,
+ };
+
+ if (currentState.setMax) {
+ const account = getState().accounts.find(a => a.checksum === currentState.checksum && a.index === currentState.accountIndex && a.coin === currentState.coin);
+ if (state.token !== state.coin) {
+ const tokenBalance: string = getState().tokens.find(t => t.ethAddress === account.address && t.symbol === currentState.token).balance;
+ state.amount = tokenBalance;
+ } else {
+ state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
+ }
+ }
+ state.total = calculateTotal(state.token !== state.coin ? '0' : state.amount, state.gasPrice, state.gasLimit);
+
+ dispatch({
+ type: SEND.UPDATE_FEE_LEVELS,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const onGasPriceChange = (gasPrice: string): any => {
+ return (dispatch, getState): void => {
+
+ const currentState = getState().sendForm;
+ const touched = { ...currentState.touched };
+ touched.gasPrice = true;
+
+ const state: State = {
+ ...currentState,
+ untouched: false,
+ touched,
+ gasPrice: gasPrice,
+ };
+
+ if (gasPrice.match(numberRegExp) && state.gasLimit.match(numberRegExp)) {
+ const customLevel = currentState.feeLevels.find(f => f.value === 'Custom');
+ customLevel.gasPrice = gasPrice;
+ customLevel.label = `${ calculateFee(gasPrice, state.gasLimit) } ${ state.coin.toUpperCase() }`;
+
+ state.selectedFeeLevel = customLevel;
+
+ if (currentState.setMax) {
+ const account = getState().accounts.find(a => a.checksum === currentState.checksum && a.index === currentState.accountIndex && a.coin === currentState.coin);
+ if (state.token !== state.coin) {
+ const tokenBalance: string = getState().tokens.find(t => t.ethAddress === account.address && t.symbol === currentState.token).balance;
+ state.amount = tokenBalance;
+ } else {
+ state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
+ }
+ }
+ }
+
+ state.total = calculateTotal(state.token !== state.coin ? '0' : state.amount, state.gasPrice, state.gasLimit);
+
+ dispatch({
+ type: SEND.GAS_PRICE_CHANGE,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const onGasLimitChange = (gasLimit: string): any => {
+ return (dispatch, getState): void => {
+ const currentState = getState().sendForm;
+ const touched = { ...currentState.touched };
+ touched.gasLimit = true;
+
+ const state: State = {
+ ...currentState,
+ untouched: false,
+ touched,
+ gasLimit,
+ };
+
+ if (gasLimit.match(numberRegExp) && state.gasPrice.match(numberRegExp)) {
+ const customLevel = currentState.feeLevels.find(f => f.value === 'Custom');
+ customLevel.label = `${ calculateFee(state.gasPrice, gasLimit) } ${ state.coin.toUpperCase() }`;
+
+ state.selectedFeeLevel = customLevel;
+
+ if (currentState.setMax) {
+ const account = getState().accounts.find(a => a.checksum === currentState.checksum && a.index === currentState.accountIndex && a.coin === currentState.coin);
+ if (state.token !== state.coin) {
+ const tokenBalance: string = getState().tokens.find(t => t.ethAddress === account.address && t.symbol === currentState.token).balance;
+ state.amount = tokenBalance;
+ } else {
+ state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
+ }
+ }
+ }
+
+ state.total = calculateTotal(state.token !== state.coin ? '0' : state.amount, state.gasPrice, state.gasLimit);
+
+ dispatch({
+ type: SEND.GAS_LIMIT_CHANGE,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const onDataChange = (data: string): any => {
+ return (dispatch, getState): void => {
+ const currentState = getState().sendForm;
+ const touched = { ...currentState.touched };
+ touched.data = true;
+
+ const state: State = {
+ ...currentState,
+ untouched: false,
+ touched,
+ data,
+ };
+
+ dispatch({
+ type: SEND.DATA_CHANGE,
+ state
+ });
+ dispatch( validation() );
+ }
+}
+
+export const onSend = (): any => {
+ //return onSendERC20();
+
+ return async (dispatch, getState): Promise => {
+
+ const state: State = getState().sendForm;
+ const web3instance = getState().web3.filter(w3 => w3.coin === state.coin)[0];
+ const web3 = web3instance.web3;
+
+ const account = getState().accounts.find(a => a.checksum === state.checksum && a.index === state.accountIndex && a.coin === state.coin);
+
+ const address_n = account.addressPath;
+
+ let data: string = '';
+ let txAmount = web3.toHex(web3.toWei(state.amount, 'ether'));
+ let txAddress = state.address;
+ if (state.coin !== state.token) {
+ const tokens = getState().tokens
+ const t = tokens.find(t => t.ethAddress === account.address && t.symbol === state.token);
+ const contract = web3instance.erc20.at(t.address);
+ data = contract.transfer.getData(state.address, state.amount, {
+ from: account.address,
+ gasLimit: state.gasLimit,
+ gasPrice: state.gasPrice
+ });
+ txAmount = '0x00';
+ txAddress = t.address;
+ }
+
+
const txData = {
address_n,
- to: address,
- value: web3.toHex(web3.toWei(amount, 'ether')),
- data: '',
- chainId: 3
+ // from: currentAddress.address
+ to: txAddress,
+ value: txAmount,
+ data,
+ //chainId: 3 // ropsten
+ chainId: web3instance.chainId,
+ nonce: web3.toHex(account.nonce),
+ gasLimit: web3.toHex(state.gasLimit),
+ gasPrice: web3.toHex( EthereumjsUnits.convert(state.gasPrice, 'gwei', 'wei') ),
+ r: '',
+ s: '',
+ v: ''
}
- const nonce = await getNonce(web3, currentAddress.address);
- const gasOptions = {
- to: txData.to,
- data: txData.data
- }
- const gasLimit = await estimateGas(web3, gasOptions);
- const gasPrice = await getGasPrice(web3);
+ //const nonce = await getNonce(web3, currentAddress.address);
+ //txData.nonce = web3.toHex(nonce);
+
+
+ // const gasOptions = {
+ // to: txData.to,
+ // data: txData.data
+ // }
+
+ // const gasPrice = await getGasPrice(web3);
- txData.nonce = web3.toHex(nonce);
- txData.gasLimit = web3.toHex(gasLimit);
- txData.gasPrice = web3.toHex(gasPrice);
+
+
+ // txData.nonce = web3.toHex(nonce);
+ // txData.gasLimit = web3.toHex(gasLimit);
+ // txData.gasPrice = web3.toHex( EthereumjsUnits.convert(gasPrice, 'gwei', 'wei') );
+
+ // console.log("---->GASSS", txData, gasLimit, gasPrice, EthereumjsUnits.convert(gasPrice, 'gwei', 'wei'));
+
+ const selected = findSelectedDevice(getState().connect);
let signedTransaction = await TrezorConnect.ethereumSignTransaction({
+ device: {
+ path: selected.path,
+ instance: selected.instance,
+ state: selected.checksum
+ },
//path: "m/44'/60'/0'/0/0",
address_n: txData.address_n,
nonce: strip(txData.nonce),
@@ -82,24 +674,85 @@ export const onSend = (addressId): void => {
gas_limit: strip(txData.gasLimit),
to: strip(txData.to),
value: strip(txData.value),
- data: txData.data,
+ data: strip(txData.data),
chain_id: txData.chainId
});
+ if (!signedTransaction || !signedTransaction.success) {
+
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Transaction error',
+ message: signedTransaction.data.error,
+ cancelable: true,
+ actions: [ ]
+ }
+ })
+ return;
+ }
+
txData.r = '0x' + signedTransaction.data.r;
txData.s = '0x' + signedTransaction.data.s;
txData.v = web3.toHex(signedTransaction.data.v);
- const tx = new EthereumjsTx(txData);
- const serializedTx = '0x' + tx.serialize().toString('hex');
+ const gasLimit2 = await estimateGas(web3, txData);
+ console.log("---->GASSS", txData, gasLimit2.toString() );
+
+ try {
+ const tx = new EthereumjsTx(txData);
+ const serializedTx = '0x' + tx.serialize().toString('hex');
+ const txid = await pushTx(web3, serializedTx);
+
+ dispatch({
+ type: SEND.TX_COMPLETE,
+ address: account,
+ txid,
+ txData,
+ });
+
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'success',
+ title: 'Transaction success',
+ message: `detail `,
+ cancelable: true,
+ actions: []
+ }
+ });
+
+ } catch(error) {
- const txid = await push(web3, serializedTx);
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Transaction error',
+ message: error.message || error,
+ cancelable: true,
+ actions: [ ]
+ }
+ });
+ }
+
+ // const tx = new EthereumjsTx(txData);
+ // console.log("2222", tx, tx.toJSON(), tx.from, tx.to);
+ // const serializedTx = '0x' + tx.serialize().toString('hex');
+
+ // console.log("----> PUSZ TX", web3, currentAddress, serializedTx)
+ // const txid = await pushTx(web3, serializedTx);
+ // console.log("----> PUSZ TX2", web3, serializedTx)
- dispatch({
- type: ACTIONS.ON_TX_COMPLETE,
- address: currentAddress,
- txid,
- txData,
- })
+ // dispatch({
+ // type: SEND.TX_COMPLETE,
+ // address: currentAddress,
+ // txid,
+ // txData,
+ // })
+
+ // const [ url ] = getState().router.location.pathname.split('/send');
+ // dispatch( push(url) );
}
}
diff --git a/src/js/actions/SummaryActions.js b/src/js/actions/SummaryActions.js
new file mode 100644
index 00000000..6a2ad931
--- /dev/null
+++ b/src/js/actions/SummaryActions.js
@@ -0,0 +1,282 @@
+/* @flow */
+'use strict';
+
+import EthereumjsUtil from 'ethereumjs-util';
+import * as ACTIONS from './index';
+import * as SUMMARY from './constants/summary';
+import * as TOKEN from './constants/Token';
+import * as ADDRESS from './constants/Address';
+import { resolveAfter } from '../utils/promiseUtils';
+import { getTokenInfoAsync, getTokenBalanceAsync } from './Web3Actions';
+
+import { initialState } from '../reducers/SummaryReducer';
+import type { State } from '../reducers/SummaryReducer';
+import { findSelectedDevice } from '../reducers/TrezorConnectReducer';
+
+
+export const init = (): any => {
+ return (dispatch, getState): void => {
+ const { location } = getState().router;
+ const urlParams = location.params;
+
+ const selected = findSelectedDevice( getState().connect );
+ if (!selected) return;
+
+ const state: State = {
+ ...initialState,
+ checksum: selected.checksum,
+ accountIndex: parseInt(urlParams.address),
+ coin: urlParams.coin,
+ location: location.pathname,
+ };
+
+ dispatch({
+ type: SUMMARY.INIT,
+ state: state
+ });
+ }
+}
+
+
+export const update = (newProps: any): any => {
+ return (dispatch, getState): void => {
+ const {
+ summary,
+ router
+ } = getState();
+
+ const isLocationChanged: boolean = router.location.pathname !== summary.location;
+ if (isLocationChanged) {
+ dispatch( init() );
+ return;
+ }
+ }
+}
+
+export const dispose = (address: string): any => {
+ return {
+ type: SUMMARY.DISPOSE
+ }
+}
+
+export const onDetailsToggle = (): any => {
+ return {
+ type: SUMMARY.DETAILS_TOGGLE
+ }
+}
+
+// export const init = (address: string): any => {
+// return (dispatch, getState): void => {
+// const { location } = getState().router;
+// const urlParams = location.params;
+
+// const selected = findSelectedDevice(getState().connect);
+// const accounts = getState().accounts;
+// // const currentAccount = accounts.find(a => a.index === parseInt(urlParams.address) && a.coin === urlParams.coin && a.deviceId === urlParams.device && a.loaded);
+// const currentAccount = accounts.find(a => a.index === parseInt(urlParams.address) && a.coin === urlParams.coin && a.checksum === selected.checksum);
+// if (!currentAccount) {
+// console.log("STATER", getState())
+// // account not found
+// return;
+// }
+
+// const web3instance = getState().web3.find(w3 => w3.coin === urlParams.coin);
+// if (!web3instance) {
+// // no backend for this coin
+// return;
+// }
+
+// const state: State = {
+// ...initialState,
+// loaded: true,
+// address: currentAccount.address,
+// coin: urlParams.coin,
+// balance: currentAccount.balance,
+// };
+
+
+// dispatch({
+// type: SUMMARY.INIT,
+// state
+// });
+
+// }
+// }
+
+
+
+
+export const loadTokens = (input: string): any => {
+ return async (dispatch, getState): Promise => {
+
+ if (input.length < 1) return null;
+
+ const { ethTokens } = getState().localStorage;
+
+ const value = input.toLowerCase();
+ const result = ethTokens.filter(t =>
+ t.symbol.toLowerCase().indexOf(value) >= 0 ||
+ t.address.toLowerCase().indexOf(value) >= 0 ||
+ t.name.toLowerCase().indexOf(value) >= 0
+ );
+ //const result = ethTokens.filter(t => t.symbol.toLowerCase().indexOf(lower) >= 0);
+
+ console.log("RESULT!", result.length, result)
+
+ if (result.length > 0) {
+ return { options: result };
+ } else {
+ const web3instance = getState().web3.find(w3 => w3.coin === 'eth');
+
+ const info = await getTokenInfoAsync(web3instance.erc20, input);
+ info.address = input;
+
+ console.log("FETCH", info)
+
+ if (info) {
+ return {
+ options: [ info ]
+ }
+ } else {
+ return {
+
+ }
+ }
+
+
+ //await resolveAfter(300000);
+ //await resolveAfter(3000);
+
+
+
+
+ }
+
+ }
+}
+
+export const selectToken = (token: any, account: any): any => {
+ return async (dispatch, getState): Promise => {
+
+ console.warn("ADD", token, account)
+
+ const web3instance = getState().web3.find(w3 => w3.coin === account.coin);
+
+ dispatch({
+ type: TOKEN.ADD,
+ payload: {
+ ...token,
+ ethAddress: account.address,
+ checksum: account.checksum
+ }
+ });
+
+ // TODO: load token balance
+ const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, token.address, account.address);
+ dispatch({
+ type: TOKEN.SET_BALANCE,
+ payload: {
+ ethAddress: account.address,
+ address: token.address,
+ balance: tokenBalance.toString()
+ }
+ })
+
+ }
+}
+
+
+
+export const onTokenSearch = (search: string): any => {
+ return {
+ type: ACTIONS.TOKENS_SEARCH,
+ search
+ }
+}
+
+export const onCustomTokenToggle = (): any => {
+ return {
+ type: ACTIONS.TOKENS_CUSTOM_TOGGLE
+ }
+}
+
+export const onCustomTokenAddressChange = (value: string): any => {
+ // todo:
+ // -validate addres
+ // - if adress is ok, try to fetch token info
+ // - check if addres does not exist in predefined coins
+ // return {
+ // type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
+ // value
+ // }
+
+ return async (dispatch, getState) => {
+
+ const valid: boolean = EthereumjsUtil.isValidAddress(value);
+ if (valid) {
+
+ dispatch({
+ type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
+ value,
+ valid,
+ fetching: true
+ });
+
+ const { web3, abi } = getState().web3;
+ const contract = web3.eth.contract(abi).at(value);
+
+ contract.name.call((error, name) => {
+ if (error) {
+ // TODO: skip
+ }
+ contract.symbol.call((error, symbol) => {
+ if (error) {
+ // TODO: skip
+ }
+
+ contract.decimals.call((error, decimals) => {
+ console.log("fetched!", name, symbol, decimals)
+ })
+ });
+
+
+ })
+
+ } else {
+ dispatch({
+ type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
+ value,
+ valid
+ });
+ }
+
+ console.log("VALID!!!", valid);
+ }
+}
+
+export const onCustomTokenNameChange = (value: string): any => {
+ return {
+ type: ACTIONS.TOKENS_CUSTOM_NAME_CHANGE,
+ value
+ }
+}
+
+export const onCustomTokenShortcutChange = (value: string): any => {
+ return {
+ type: ACTIONS.TOKENS_CUSTOM_SHORTCUT_CHANGE,
+ value
+ }
+}
+
+export const onCustomTokenDecimalChange = (value: string): any => {
+ return {
+ type: ACTIONS.TOKENS_CUSTOM_DECIMAL_CHANGE,
+ value
+ }
+}
+
+export const onCustomTokenAdd = (): any => {
+ return {
+ type: ACTIONS.TOKENS_CUSTOM_ADD
+ }
+}
\ No newline at end of file
diff --git a/src/js/actions/TrezorConnectActions.1.js b/src/js/actions/TrezorConnectActions.1.js
deleted file mode 100644
index 203d7a46..00000000
--- a/src/js/actions/TrezorConnectActions.1.js
+++ /dev/null
@@ -1,260 +0,0 @@
-/* @flow */
-'use strict';
-
-import TrezorConnect, { UI } from 'trezor-connect';
-import * as ACTIONS from './index';
-
-//import wallet from 'ethereumjs-wallet';
-//import hdkey from 'ethereumjs-wallet/hdkey';
-import HDKey from 'hdkey';
-import ethUtil from 'ethereumjs-util';
-import EthereumjsTx from 'ethereumjs-tx';
-import * as ethereumUtils from '../utils/ethUtils';
-import { hexToString, stringToHex } from '../utils/formatUtils';
-import * as Web3Actions from './Web3Actions';
-
-export function onSelectDevice2(path: string): any {
- return {
- type: ACTIONS.ON_SELECT_DEVICE,
- path
- }
-}
-
-export function discover(txData): any {
- return async function (dispatch) {
- let response = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
- dispatch({
- type: 'create_account',
- xpub: response.data.xpub,
- publicKey: response.data.publicKey,
- chainCode: response.data.chainCode,
- path: response.data.path
- })
- }
-}
-
-export function signTx(txData): any {
- return async function (dispatch) {
-
- console.log("RESP2", txData)
-
- //txData.nonce = "0x01";
- //txData.gasPrice = "0x02540be400";
- // txData.gasLimit = "0x5208";
- // txData.value = "0x5af3107a4000";
-
- let response = await TrezorConnect.ethereumSignTransaction({
- //path: "m/44'/60'/0'/0/0",
- address_n: txData.address_n,
- nonce: ethereumUtils.strip(txData.nonce),
- gas_price: ethereumUtils.strip(txData.gasPrice),
- gas_limit: ethereumUtils.strip(txData.gasLimit),
- to: ethereumUtils.strip(txData.to),
- value: ethereumUtils.strip(txData.value),
- data: txData.data,
- chain_id: txData.chainId
- });
-
- txData.r = '0x' + response.data.r;
- txData.s = '0x' + response.data.s;
- txData.v = web3.toHex(response.data.v);
-
- console.log("RESP2", response, txData)
-
- const tx = new EthereumjsTx(txData);
- var signedTx = '0x' + tx.serialize().toString('hex');
-
-
- const rawTx2 = {
- "nonce":"0x01",
- "gasPrice":"0x02540be400",
- "gasLimit":"0x5208",
- "to":"0x7314e0f1C0e28474bDb6be3E2c3E0453255188f8",
- "value":"0x5af3107a4000",
- "data":"",
- "chainId":3,
- "v":"0x2a",
- "r":"0x210af4e1698f0437125424ac378da7304dea94dde34cbb57b62624069ceae969",
- "s":"0x74c885c3d32330d63e32dff3b79a302ae5ed9b9abcf6e903fd32cbb91d94b6df"
- }
-
- var tx2 = new EthereumjsTx(rawTx2);
- var signedTx2 = '0x' + tx2.serialize().toString('hex');
-
- console.log(signedTx)
- console.log(signedTx2)
- console.log(signedTx === signedTx2)
- console.log(txData, rawTx2)
-
- // web3.eth.sendRawTransaction(signedTx, function(a1, a2){
- // console.log("SIGNEEED", a1, a2)
- // })
- }
-}
-
-export function onSelectDevice(): any {
- return async function (dispatch, getState) {
- dispatch(Web3Actions.composeTransaction());
- }
-}
-export function onSelectDeviceWeb3(): any {
- return async function (dispatch, getState) {
-
- const { web3 } = getState().web3;
-
- console.log("WEB3333", web3)
-
- let resp = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
-
- let hdk = new HDKey();
- hdk.publicKey = new Buffer(resp.data.publicKey, 'hex');
- hdk.chainCode = new Buffer(resp.data.chainCode, 'hex');
-
- var derivedKey = hdk.derive("m/0");
-
- var address = ethUtil.publicToAddress(derivedKey.publicKey, true);
-
- // balance 0.100100000000000000 eth
- var txData = {
- address_n: [
- (44 | 0x80000000) >>> 0,
- (60 | 0x80000000) >>> 0,
- (0 | 0x80000000) >>> 0,
- 0, 0
- ],
- to: '0x7314e0f1C0e28474bDb6be3E2c3E0453255188f8',
- value: web3.toHex(web3.toWei('0.0001', 'ether')),
- data: '',
- chainId: 3
- }
-
- web3.eth.getTransactionCount('0x' + address.toString('hex'), (error, result) => {
- txData.nonce = web3.toHex(result);
- const gasOptions = {
- to: txData.to,
- data: txData.data
- }
- web3.eth.estimateGas(gasOptions, (error, result) => {
- txData.gasLimit = web3.toHex(result);
-
- web3.eth.getGasPrice(function(error, result){
- if (error) throw error;
-
- txData.gasPrice = web3.toHex(result);
- console.warn("gesgas", error, result.toString(10), txData)
-
- dispatch( signTx(txData) );
- });
- });
- });
-
-
-
-
-
-
-
- // web3.eth.getTransactionCount('0x' + address.toString('hex'), (error, result) => {
-
- // if (error) throw error;
-
- // console.log("getTransactionCount", result)
-
- // });
-
- // let gas = {
- // to: "0x7314e0f1C0e28474bDb6be3E2c3E0453255188f8",
- // data: ""
- // };
-
- // web3.eth.estimateGas(gas, function(error, result){
- // console.warn("estimategas", error, result, result.toString(10))
-
- // web3.eth.getGasPrice(function(error, result){
- // console.warn("gesgas", error, result.toString(10) result.)
- // })
-
- // });
-
-
- //console.log("SIGNEEED", signedTx);
-
-
- //console.log("HDK", derivedKey, address.toString('hex'), rawTx, txData, hexToString(txData.value))
- //console.log("HDK", derivedKey, hexToString(txData.value), txData )
-
- // const hd = hdkey.fromExtendedKey(resp.data.xpub);
- // console.log("HDKI!", hd)
-
-
- // var balance = web3.eth.getBalance('0x' + address.toString('hex'), function(error, result){
- // if(!error)
- // console.log("res", result, result.toString(10) )
- // else
- // console.error("erro", error);
- // });
-
-
-
-
-
- // web3.eth.sendRawTransaction(signedTx, function(a1, a2){
- // console.log("SIGNEEED", a1, a2)
- // })
- //console.log(web3.eth)
- //web3.eth.sendRawTransaction(signedTx).on('receipt', console.log);
-
- // web3.eth.getTransactionCount('0x' + address.toString('hex'), function(error, result) {
- // //web3.eth.getTransactionCount("0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe", function(error, result) {
- // console.log("getTransactionCount", error, result)
- // })
-
- // web3.eth.getAccounts(console.log)
-
- // const nonce = '03'; // note - it is hex, not number!!!
- // const gas_price = '098bca5a00';
- // const gas_limit = 'a43f';
- // const to = 'e0b7927c4af23765cb51314a0e0521a9645f0e2a';
- // // var value = '01'; // in hexadecimal, in wei - this is 1 wei
- // const value = '010000000000000000'; // in hexadecimal, in wei - this is about 18 ETC
- // const data = 'a9059cbb000000000000000000000000dc7359317ef4cc723a3980213a013c0433a338910000000000000000000000000000000000000000000000000000000001312d00'; // some contract data
- // // var data = null // for no data
- // const chain_id = 1; // 1 for ETH, 61 for ETC
-
- // let resp2 = await TrezorConnect.ethereumSignTransaction({
- // address_n,
- // nonce,
- // gas_price,
- // gas_limit,
- // to,
- // value,
- // data,
- // chain_id
- // });
-
-
-
-
- // old fallback
- // this.signEthereumTx = function (
- // address_n,
- // nonce,
- // gas_price,
- // gas_limit,
- // to,
- // value,
- // data,
- // chain_id,
- // callback,
- // requiredFirmware
- // )
-
- // var coinbase = web3.eth.coinbase;
- // var balance = web3.eth.getBalance(coinbase);
- // console.log(balance.toString(10));
-
- // dispatch({
- // type: 'DDD'
- // });
- }
-}
\ No newline at end of file
diff --git a/src/js/actions/TrezorConnectActions.js b/src/js/actions/TrezorConnectActions.js
index 7eb448bb..d12f2a7f 100644
--- a/src/js/actions/TrezorConnectActions.js
+++ b/src/js/actions/TrezorConnectActions.js
@@ -1,95 +1,710 @@
/* @flow */
'use strict';
-import TrezorConnect, { UI } from 'trezor-connect';
+import TrezorConnect, { UI, DEVICE, DEVICE_EVENT, UI_EVENT } from 'trezor-connect';
import * as ACTIONS from './index';
+import * as ADDRESS from './constants/Address';
+import * as TOKEN from './constants/Token';
+import * as CONNECT from './constants/TrezorConnect';
+import * as DISCOVERY from './constants/Discovery';
+import * as NOTIFICATION from './constants/notification';
-//import wallet from 'ethereumjs-wallet';
-//import hdkey from 'ethereumjs-wallet/hdkey';
import HDKey from 'hdkey';
import EthereumjsUtil from 'ethereumjs-util';
import EthereumjsTx from 'ethereumjs-tx';
-import { Address } from '../reducers/AddressesReducer';
-import { getBalance } from '../services/Web3Service';
+//import { getBalance } from '../services/Web3Service';
+import { getBalance } from './Web3Actions';
import { getTransactionHistory } from '../services/EtherscanService';
import { push } from 'react-router-redux';
-export function onSelectDevice2(path: string): any {
- return {
- type: ACTIONS.ON_SELECT_DEVICE,
- path
+import { init as initWeb3, getNonce, getBalanceAsync, getTokenBalanceAsync } from './Web3Actions';
+
+import type { Discovery } from '../reducers/DiscoveryReducer';
+import { resolveAfter } from '../utils/promiseUtils';
+import { getAccounts } from '../utils/reducerUtils';
+import { findSelectedDevice, isSavedDevice } from '../reducers/TrezorConnectReducer';
+
+
+
+export const init = (): any => {
+ return async (dispatch, getState): Promise => {
+ // set listeners
+ TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
+ dispatch({
+ type: event.type,
+ device: event.data
+ });
+ });
+
+ const version: Object = TrezorConnect.getVersion();
+ if (version.type === 'library') {
+ // handle UI events only if TrezorConnect isn't using popup
+ TrezorConnect.on(UI_EVENT, (type: string, data: any): void => {
+ // post event to reducers
+ dispatch({
+ type,
+ data
+ });
+ });
+ }
+
+ try {
+ await TrezorConnect.init({
+ transport_reconnect: true,
+ });
+
+ setTimeout(() => {
+ dispatch( initWeb3() );
+ }, 2000)
+
+ } catch (error) {
+ dispatch({
+ type: CONNECT.INITIALIZATION_ERROR,
+ error
+ })
+ }
}
}
-export function remove(devicePath): any {
+// called after backend was initialized
+// set listeners for connect/disconnect
+export const postInit = (): any => {
+ return (dispatch, getState): void => {
+ const handleDeviceConnect = (device) => {
+ dispatch( initConnectedDevice(device) );
+ }
+
+ // const handleDeviceDisconnect = (device) => {
+ // // remove addresses and discovery from state
+ // // dispatch( remove(device) );
+ // }
+
+ TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
+ TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
+
+ // TrezorConnect.on(DEVICE.DISCONNECT, handleDeviceDisconnect);
+ // TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceDisconnect);
+
+ // possible race condition:
+ // devices were connected before Web3 initialized. force DEVICE.CONNECT event on them
+ const { devices } = getState().connect;
+
+ if (devices.length > 0) {
+ const unacquired = devices.find(d => d.unacquired);
+ if (unacquired) {
+ handleDeviceConnect(unacquired);
+ } else {
+ const latest = devices.sort((a, b) => {
+ if (!a.ts || !b.ts) {
+ return -1;
+ } else {
+ return a.ts > b.ts ? 1 : -1;
+ }
+ });
+
+ console.log("LATEST", latest)
+ }
+ }
+ for (let d of devices) {
+ handleDeviceConnect(d);
+ }
+ }
+}
+
+export const initConnectedDevice = (device: any): any => {
+ return (dispatch, getState): void => {
+
+ const selected = findSelectedDevice(getState().connect);
+ if (device.unacquired && selected && selected.path !== device.path && !selected.connected) {
+ dispatch( onSelectDevice(device) );
+ } else if (!selected) {
+ dispatch( onSelectDevice(device) );
+ }
+ }
+}
+
+// selection from Aside dropdown
+// or after acquiring device
+export function onSelectDevice(device: any): any {
+ return (dispatch, getState): void => {
+ // || device.isUsedElsewhere
+ if (device.unacquired) {
+ dispatch( push(`/device/${ device.path }/acquire`) );
+ } else if (device.features.bootloader_mode) {
+ dispatch( push(`/device/${ device.path }/bootloader`) );
+ } else if (device.instance) {
+ dispatch( push(`/device/${ device.features.device_id }:${ device.instance }`) );
+ } else {
+ //dispatch( push(`/device/${ device.features.device_id }/coin/etc/address/0`) );
+ dispatch( push(`/device/${ device.features.device_id }`) );
+ }
+ }
+}
+
+// TODO: as TrezorConnect method
+const __getDeviceState = async (path, instance): Promise => {
+ return await TrezorConnect.getPublicKey({
+ device: {
+ path,
+ instance
+ },
+ // selectedDevice: path,
+ instance: instance,
+ path: "m/1'/0'/0'",
+ confirmation: false
+ });
+}
+
+export const switchToFirstAvailableDevice = (): any => {
+ return async (dispatch, getState): Promise => {
+
+ const { devices } = getState().connect;
+ if (devices.length > 0) {
+ // TODO: Priority:
+ // 1. Unacquired
+ // 2. First connected
+ // 3. Saved with latest timestamp
+ // 4. First from the list
+ const unacquired = devices.find(d => d.unacquired);
+ if (unacquired) {
+ dispatch( initConnectedDevice(unacquired) );
+ } else {
+ const latest = devices.sort((a, b) => {
+ if (!a.ts || !b.ts) {
+ return -1;
+ } else {
+ return a.ts > b.ts ? 1 : -1;
+ }
+ });
+ dispatch( initConnectedDevice(devices[0]) );
+ }
+
+ } else {
+ dispatch( push('/') );
+ dispatch({
+ type: CONNECT.SELECT_DEVICE,
+ payload: null
+ })
+ }
+ }
+}
+
+export const getSelectedDeviceState = (): any => {
+ return async (dispatch, getState): Promise => {
+ const selected = findSelectedDevice(getState().connect);
+ console.warn("init selected", selected)
+ if (selected
+ && selected.connected
+ && !selected.unacquired
+ && !selected.acquiring
+ && !selected.checksum) {
+
+ const response = await __getDeviceState(selected.path, selected.instance);
+
+ if (response && response.success) {
+ dispatch({
+ type: CONNECT.AUTH_DEVICE,
+ device: selected,
+ checksum: response.data.xpub
+ });
+ } else {
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Authentification error',
+ message: response.data.error,
+ cancelable: true,
+ actions: [
+ {
+ label: 'Try again',
+ callback: () => {
+ dispatch( getSelectedDeviceState() );
+ }
+ }
+ ]
+ }
+ })
+ }
+ }
+ }
+}
+
+export const deviceDisconnect = (device: any): any => {
+ return async (dispatch, getState): Promise => {
+
+ if (!device || !device.features) return null;
+
+ const selected = findSelectedDevice(getState().connect);
+ if (selected && selected.features.device_id === device.features.device_id) {
+ stopDiscoveryProcess(selected);
+ }
+
+ const affected = getState().connect.devices.filter(d => d.features && d.checksum && !d.remember && d.features.device_id === device.features.device_id);
+ if (affected.length > 0) {
+ dispatch({
+ type: CONNECT.REMEMBER_REQUEST,
+ device,
+ allInstances: affected
+ });
+ }
+
+
+ // if (selected && selected.checksum) {
+ // if (device.features && device.features.device_id === selected.features.device_id) {
+ // stopDiscoveryProcess(selected);
+ // }
+ // }
+
+
+
+ // // stop running discovery process on this device
+ // if (selected && selected.path === device.path){
+ // if (selected.checksum) {
+ // stopDiscoveryProcess(selected);
+ // }
+ // }
+
+ // // check if disconnected device was remembered before.
+ // // request modal if not
+ // const affected = getState().connect.devices.filter(d => d.path === device.path && d.checksum && !device.remember);
+
+
+ // check if reload is needed
+ if (!selected) {
+ dispatch( switchToFirstAvailableDevice() );
+ }
+ }
+}
+
+export const coinChanged = (coin: ?string): any => {
+ return (dispatch, getState): void => {
+ const selected = findSelectedDevice(getState().connect);
+ dispatch( stopDiscoveryProcess(selected) );
+
+ if (coin) {
+ dispatch( startDiscoveryProcess(selected, coin) );
+ }
+ }
+}
+
+
+export function acquire(): any {
return async (dispatch, getState) => {
- const { addresses } = getState().addresses;
- const availableAddresses = addresses.filter(a => a.devicePath !== devicePath);
+ const selected = findSelectedDevice(getState().connect);
+
+ const saved = getState().connect.devices.map(d => {
+ if (d.checksum) {
+ return {
+ instance: d.instance,
+ checksum: d.checksum
+ }
+ } else {
+ return null;
+ }
+ });
+
+ //const response = await __acquire(selected.path, selected.instance);
dispatch({
- type: ACTIONS.ADDRESS_DELETE,
- addresses: availableAddresses
+ type: CONNECT.START_ACQUIRING,
+ device: selected
+ });
+
+ const response = await TrezorConnect.getFeatures({
+ device: {
+ path: selected.path,
+ }
+ });
+
+ const selected2 = findSelectedDevice(getState().connect);
+ dispatch({
+ type: CONNECT.STOP_ACQUIRING,
+ device: selected2
+ });
+
+ if (response && response.success) {
+ dispatch({
+ type: DEVICE.ACQUIRED,
+ // checksum: response
+ })
+ } else {
+ // TODO: handle invalid pin?
+ console.log("-errror ack", response)
+
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Acquire device error',
+ message: response.data.error,
+ cancelable: true,
+ actions: [
+ {
+ label: 'Try again',
+ callback: () => {
+ dispatch(acquire())
+ }
+ }
+ ]
+ }
+ })
+ }
+ }
+}
+
+export const forgetDevice = (device: any) => {
+ return (dispatch: any, getState: any): any => {
+
+ // find accounts associated with this device
+ const accounts: Array = getState().accounts.find(a => a.checksum === device.checksum);
+
+
+ // find discovery processes associated with this device
+ const discovery: Array = getState().discovery.find(d => d.checksum === device.checksum);
+
+ }
+}
+
+// called from Aside - device menu (forget single instance)
+export const forget = (device: any) => {
+ return {
+ type: CONNECT.FORGET_REQUEST,
+ device
+ };
+}
+
+export const duplicateDevice = (device: any) => {
+ return async (dispatch: any, getState: any): Promise => {
+ dispatch({
+ type: CONNECT.TRY_TO_DUPLICATE,
+ device
})
}
}
-export function discover(devicePath): any {
+export const onDuplicateDevice = () => {
+ return async (dispatch: any, getState: any): Promise => {
+ const selected = findSelectedDevice(getState().connect);
+ dispatch(onSelectDevice(selected));
+ }
+}
+
+export const beginDiscoveryProcess = (device: any, coin: string): any => {
return async (dispatch, getState) => {
- const { web3 } = getState().web3;
+ const { config } = getState().localStorage;
+ const coinToDiscover = config.coins.find(c => c.symbol === coin);
+
+ // TODO: validate device checksum
+ // const checksum = await __acquire(device.path, device.instance);
+ // if (checksum && checksum.success) {
+ // if (checksum.data.xpub !== device.checksum) {
+ // console.error("Incorrect checksum!");
+ // return;
+ // }
+ // }
+
+ // acquire and hold session
+ // get xpub from TREZOR
+ const response = await TrezorConnect.getPublicKey({
+ device: {
+ path: device.path,
+ instance: device.instance,
+ state: device.checksum
+ },
+ path: coinToDiscover.bip44,
+ confirmation: false,
+ keepSession: true
+ });
+
+ if (!response.success) {
+ // TODO: check message
+ console.warn("DISCO ERROR", response)
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Discovery error',
+ message: response.data.error,
+ cancelable: true,
+ actions: [
+ {
+ label: 'Try again',
+ callback: () => {
+ dispatch(startDiscoveryProcess(device, coin))
+ }
+ }
+ ]
+ }
+ })
+ return;
+ }
- const response = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
+ // TODO: check for interruption
+
+ // TODO: handle response error
const basePath: Array = response.data.path;
const hdKey = new HDKey();
hdKey.publicKey = new Buffer(response.data.publicKey, 'hex');
hdKey.chainCode = new Buffer(response.data.chainCode, 'hex');
- const loop = async (index: number) => {
- const derivedKey = hdKey.derive(`m/${index}`);
- const path = basePath.concat(index);
- const ethAddress: string = '0x' + EthereumjsUtil.publicToAddress(derivedKey.publicKey, true).toString('hex');
- const address = new Address(devicePath, index, path, ethAddress);
+ // send data to reducer
+ dispatch({
+ type: DISCOVERY.START,
+ coin: coinToDiscover.shortcut,
+ device,
+ xpub: response.data.publicKey,
+ basePath,
+ hdKey,
+ });
+
+ dispatch( startDiscoveryProcess(device, coin) );
+ }
+}
+
+export const discoverAddress = (device: any, discoveryProcess: Discovery): any => {
+ return async (dispatch, getState) => {
+
+ const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`);
+ const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex);
+ const publicAddress: string = EthereumjsUtil.publicToAddress(derivedKey.publicKey, true).toString('hex');
+ const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress);
+ const coin = discoveryProcess.coin;
+
+ dispatch({
+ type: ADDRESS.CREATE,
+ device,
+ coin,
+ index: discoveryProcess.accountIndex,
+ path,
+ address: ethAddress
+ });
+
+ // TODO: check if address was created before
+ // verify address with TREZOR
+ const verifyAddress = await TrezorConnect.ethereumGetAddress({
+ device: {
+ path: device.path,
+ instance: device.instance,
+ state: device.checksum
+ },
+ address_n: path,
+ showOnTrezor: false
+ });
+ if (discoveryProcess.interrupted) return;
+
+ if (verifyAddress && verifyAddress.success) {
+ //const trezorAddress: string = '0x' + verifyAddress.data.message.address;
+ const trezorAddress: string = EthereumjsUtil.toChecksumAddress(verifyAddress.data.message.address);
+ if (trezorAddress !== ethAddress) {
+ // throw inconsistent state error
+ console.warn("Inconsistent state", trezorAddress, ethAddress);
+
+ dispatch({
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Address validation error',
+ message: `Addresses are different. ${ trezorAddress } : ${ ethAddress }`,
+ cancelable: true,
+ actions: [
+ {
+ label: 'Try again',
+ callback: () => {
+ dispatch(startDiscoveryProcess(device, discoveryProcess.coin))
+ }
+ }
+ ]
+ }
+ });
+ return;
+ }
+ } else {
+ // handle TREZOR communication error
dispatch({
- type: ACTIONS.ADDRESS_CREATE,
- devicePath,
- address
- })
+ type: NOTIFICATION.ADD,
+ payload: {
+ type: 'error',
+ title: 'Address validation error',
+ message: verifyAddress.data.error,
+ cancelable: true,
+ actions: [
+ {
+ label: 'Try again',
+ callback: () => {
+ dispatch(startDiscoveryProcess(device, discoveryProcess.coin))
+ }
+ }
+ ]
+ }
+ });
+ return;
+ }
- const balance = await getBalance(ethAddress);
+ const web3instance = getState().web3.find(w3 => w3.coin === coin);
+
+ const balance = await getBalanceAsync(web3instance.web3, ethAddress);
+ if (discoveryProcess.interrupted) return;
+ dispatch({
+ type: ADDRESS.SET_BALANCE,
+ address: ethAddress,
+ balance: web3instance.web3.fromWei(balance.toString(), 'ether')
+ });
+
+ const userTokens = [];
+ // const userTokens = [
+ // { symbol: 'T01', address: '0x58cda554935e4a1f2acbe15f8757400af275e084' },
+ // { symbol: 'Lahod', address: '0x3360d0ee34a49d9ac34dce88b000a2903f2806ee' },
+ // ];
+ for (let i = 0; i < userTokens.length; i++) {
+ const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, userTokens[i].address, ethAddress);
+ if (discoveryProcess.interrupted) return;
dispatch({
- type: ACTIONS.ADDRESS_SET_BALANCE,
- address,
- balance: web3.fromWei(balance.toString(), 'ether')
+ type: TOKEN.SET_BALANCE,
+ tokenName: userTokens[i].symbol,
+ ethAddress: ethAddress,
+ tokenAddress: userTokens[i].address,
+ balance: tokenBalance.toString()
})
+ }
+
+ const nonce = await getNonce(web3instance.web3, ethAddress);
+ if (discoveryProcess.interrupted) return;
+ dispatch({
+ type: ADDRESS.SET_NONCE,
+ address: ethAddress,
+ nonce: nonce
+ });
+
+ const addressIsEmpty = nonce < 1 && !balance.greaterThan(0);
+
+ if (!addressIsEmpty) {
+ //dispatch( startDiscoveryProcess(device, discoveryProcess.coin) );
+ dispatch( discoverAddress(device, discoveryProcess) );
+ } else {
+ // release acquired sesssion
+ await TrezorConnect.getPublicKey({
+ device: {
+ path: device.path,
+ instance: device.instance,
+ state: device.checksum
+ },
+ path: "m/44'/60'/0'/0",
+ confirmation: false,
+ keepSession: false
+ });
+ if (discoveryProcess.interrupted) return;
+
+ dispatch({
+ type: DISCOVERY.COMPLETE,
+ device,
+ coin
+ });
+ }
+ }
+}
+
+export function startDiscoveryProcess(device: any, coin: string, ignoreCompleted?: boolean): any {
+ return (dispatch, getState) => {
+
+ const selected = findSelectedDevice(getState().connect);
+ if (!selected) {
+ // TODO: throw error
+ console.error("Start discovery: no selected device", device)
+ return;
+ } else if (selected.path !== device.path) {
+ console.error("Start discovery: requested device is not selected", device, selected)
+ return;
+ } else if (!selected.checksum) {
+ console.warn("Start discovery: Selected device wasn't authenticated yet...")
+ return;
+ }
- // const history = await getTransactionHistory(ethAddress);
- // dispatch({
- // type: ACTIONS.ADDRESS_SET_HISTORY,
- // address,
- // history
- // })
+ const discovery = getState().discovery;
+ let discoveryProcess: ?Discovery = discovery.find(d => d.checksum === device.checksum && d.coin === coin);
- // TODO redirect to 1st account
- if (index === 0) {
- dispatch( push('/address/0') );
+ if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
+ dispatch({
+ type: DISCOVERY.WAITING,
+ device,
+ coin
+ });
+ return;
+ }
+
+ if (!discoveryProcess) {
+ dispatch( beginDiscoveryProcess(device, coin) );
+ return;
+ } else {
+ if (discoveryProcess.completed && !ignoreCompleted) {
+ dispatch({
+ type: DISCOVERY.COMPLETE,
+ device,
+ coin
+ });
+ } else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) {
+ // discovery cycle was interrupted
+ // start from beginning
+ dispatch( beginDiscoveryProcess(device, coin) );
+ } else {
+ dispatch( discoverAddress(device, discoveryProcess) );
+ }
+ }
+ }
+}
+
+export const restoreDiscovery = (): any => {
+ return (dispatch, getState): void => {
+ const selected = findSelectedDevice(getState().connect);
+
+ if (selected && selected.connected && !selected.unacquired) {
+ const discoveryProcess: ?Discovery = getState().discovery.find(d => d.checksum === selected.checksum && d.waitingForDevice);
+ if (discoveryProcess) {
+ dispatch( startDiscoveryProcess(selected, discoveryProcess.coin) );
}
+ }
+ }
+}
+
+// there is no discovery process but it should be
+// this is possible race condition when coin was changed in url but device wasn't authenticated yet
+// try to discovery after CONNECT.AUTH_DEVICE action
+export const checkDiscoveryStatus = (): any => {
+ return (dispatch, getState): void => {
+ const selected = findSelectedDevice(getState().connect);
+ if (!selected) return;
- if (index < 2) {
- loop( index + 1);
+ const urlParams = getState().router.location.params;
+ if (urlParams.coin) {
+ const discoveryProcess: ?Discovery = getState().discovery.find(d => d.checksum === selected.checksum && d.coin === urlParams.coin);
+ if (!discoveryProcess) {
+ dispatch( startDiscoveryProcess(selected, urlParams.coin) );
}
}
+ }
+}
- loop(0);
+
+
+export function stopDiscoveryProcess(device: any): any {
+
+ // TODO: release devices session
+ // corner case swtich /eth to /etc (discovery start stop - should not be async)
+ return {
+ type: DISCOVERY.STOP,
+ device
}
}
-export function onSelectDevice(): any {
- return async (dispatch, getState) => {
- // dispatch(Web3Actions.composeTransaction());
+export function addAddress(): any {
+ return (dispatch, getState) => {
+ const selected = findSelectedDevice(getState().connect);
+ dispatch( startDiscoveryProcess(selected, getState().router.location.params.coin, true) ); // TODO: coin nicer
}
}
diff --git a/src/js/actions/Web3Actions.1.js b/src/js/actions/Web3Actions.1.js
deleted file mode 100644
index 23e2a372..00000000
--- a/src/js/actions/Web3Actions.1.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/* @flow */
-'use strict';
-
-import HDKey from 'hdkey';
-import EthereumjsUtil from 'ethereumjs-util';
-import EthereumjsTx from 'ethereumjs-tx';
-import TrezorConnect from 'trezor-connect';
-import { strip } from '../utils/ethUtils';
-
-export function getTransaction(web3, txid) {
- return new Promise((resolve, reject) => {
- web3.eth.getTransaction(txid, (error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- });
-}
-
-export function getBalance(web3, address) {
- return new Promise((resolve, reject) => {
- web3.eth.getBalance(address, (error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- });
-}
-
-export function getNonce(web3, address) {
- return new Promise((resolve, reject) => {
- web3.eth.getTransactionCount(address, (error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- });
-}
-
-export function estimateGas(web3, gasOptions) {
- return new Promise((resolve, reject) => {
- web3.eth.estimateGas(gasOptions, (error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- })
-}
-
-export function getGasPrice(web3) {
- return new Promise((resolve, reject) => {
- web3.eth.getGasPrice((error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- })
-}
-
-export function push(web3, tx) {
- return new Promise((resolve, reject) => {
- web3.eth.sendRawTransaction(tx, (error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- })
-}
-
-export function composeTransaction() {
- return async function (dispatch, getState) {
- const { web3 } = getState().web3;
- const { address, amount } = getState().sendForm;
-
- const resp = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
-
- const hdk = new HDKey();
- hdk.publicKey = new Buffer(resp.data.publicKey, 'hex');
- hdk.chainCode = new Buffer(resp.data.chainCode, 'hex');
-
- const derivedKey = hdk.derive("m/0");
- const myAddress = EthereumjsUtil.publicToAddress(derivedKey.publicKey, true);
-
- const txData = {
- address_n: [
- (44 | 0x80000000) >>> 0,
- (60 | 0x80000000) >>> 0,
- (0 | 0x80000000) >>> 0,
- 0, 0
- ],
- to: address,
- value: web3.toHex(web3.toWei(amount, 'ether')),
- data,
- chainId: 3
- }
-
- console.log("NONCE", myAddress)
- const nonce = await getNonce(web3, '0x' + myAddress.toString('hex') );
- console.log("NONCE", nonce)
-
- const gasOptions = {
- to: txData.to,
- data: txData.data
- }
- const gasLimit = await estimateGas(web3, gasOptions);
- const gasPrice = await getGasPrice(web3);
-
- txData.nonce = web3.toHex(nonce);
- txData.gasLimit = web3.toHex(gasLimit);
- txData.gasPrice = web3.toHex(gasPrice);
-
- console.log("NONCE", nonce, gasLimit, gasPrice)
-
- let signedTransaction = await TrezorConnect.ethereumSignTransaction({
- //path: "m/44'/60'/0'/0/0",
- address_n: txData.address_n,
- nonce: strip(txData.nonce),
- gas_price: strip(txData.gasPrice),
- gas_limit: strip(txData.gasLimit),
- to: strip(txData.to),
- value: strip(txData.value),
- data: txData.data,
- chain_id: txData.chainId
- });
-
- txData.r = '0x' + signedTransaction.data.r;
- txData.s = '0x' + signedTransaction.data.s;
- txData.v = web3.toHex(signedTransaction.data.v);
-
- const tx = new EthereumjsTx(txData);
- const serializedTx = '0x' + tx.serialize().toString('hex');
-
- const txid = await push(web3, serializedTx);
-
- dispatch({
- type: 'tx_complete',
- txid
- })
-
- console.log("TXID", txid);
- }
-}
\ No newline at end of file
diff --git a/src/js/actions/Web3Actions.js b/src/js/actions/Web3Actions.js
index 14e803fc..dd98876f 100644
--- a/src/js/actions/Web3Actions.js
+++ b/src/js/actions/Web3Actions.js
@@ -1,37 +1,270 @@
/* @flow */
'use strict';
+import Web3 from 'web3';
import HDKey from 'hdkey';
import EthereumjsUtil from 'ethereumjs-util';
import EthereumjsTx from 'ethereumjs-tx';
import TrezorConnect from 'trezor-connect';
import { strip } from '../utils/ethUtils';
import * as ACTIONS from './index';
+import * as ADDRESS from './constants/Address';
+import * as WEB3 from './constants/Web3';
+import { loadHistory } from '../services/EtherscanService';
+import { httpRequest } from '../utils/networkUtils';
+type ActionMethod = (dispatch: any, getState: any) => Promise;
-export function getBalance(address) {
+export function init(web3: ?Web3, coinIndex: number = 0): ActionMethod {
return async (dispatch, getState) => {
- const { web } = getState().web3;
- web3.eth.getBalance(address.address, (error, balance) => {
- if (!error) {
+
+ const { config, ethERC20 } = getState().localStorage;
+
+ const coin = config.coins[ coinIndex ];
+ if (!coin) {
+ // all instances done
+ dispatch({
+ type: WEB3.READY,
+ });
+ return;
+ }
+
+ const coinName = coin.shortcut;
+ const urls = coin.backends[0].urls;
+
+ let web3host: string = urls[0];
+
+ if (web3) {
+ const currentHost = web3.currentProvider.host;
+ let currentHostIndex: number = urls.indexOf(currentHost);
+
+ if (currentHostIndex + 1 < urls.length) {
+ web3host = urls[currentHostIndex + 1];
+ } else {
+ console.error("TODO: Backend " + coinName + " not working");
+ // try next coin
+ dispatch( init(web3, coinIndex + 1) );
+ return;
+ }
+ }
+
+ //const instance = new Web3(window.web3.currentProvider);
+ const instance = new Web3( new Web3.providers.HttpProvider(web3host) );
+
+ // instance = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') ); // UBQ
+ //instance = new Web3( new Web3.providers.HttpProvider('https://node.expanse.tech/') ); // EXP
+ //instance = new Web3( new Web3.providers.HttpProvider('http://10.34.0.91:8545/') );
+
+ //web3 = new Web3(new Web3.providers.HttpProvider("https://api.myetherapi.com/rop"));
+ //instance = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io2/QGyVKozSUEh2YhL4s2G4"));
+ //web3 = new Web3( new Web3.providers.HttpProvider("ws://34.230.234.51:30303") );
+
+ // initial check if backend is running
+ // instance.version.getNetwork(function(error, chainId){
+ // if (!error) {
+
+
+
+ instance.eth.getGasPrice((error, gasPrice) => {
+ if (error) {
+ // try different url
+ dispatch( init(instance, coinIndex) );
+ } else {
+
+ const erc20 = instance.eth.contract(ethERC20);
+
dispatch({
- type: ACTIONS.ADDRESS_SET_BALANCE,
- address,
- balance: web3.fromWei(balance.toString(), 'ether')
- })
+ type: WEB3.CREATE,
+ name: coinName,
+ web3: instance,
+ erc20,
+ chainId: instance.version.network
+ });
+
+ dispatch({
+ type: WEB3.GAS_PRICE_UPDATED,
+ coin: coinName,
+ gasPrice
+ });
+
+
+
+
+ // console.log("GET CHAIN", instance.version.network)
+
+ // instance.version.getWhisper((err, shh) => {
+ // console.log("-----whisperrr", error, shh)
+ // })
+
+
+ // const sshFilter = instance.ssh.filter('latest');
+ // sshFilter.watch((error, blockHash) => {
+ // console.warn("SSH", error, blockHash);
+ // });
+
+ //const shh = instance.shh.newIdentity();
+
+ const latestBlockFilter = instance.eth.filter('latest');
+ latestBlockFilter.watch(async (error, blockHash) => {
+
+ if (error) {
+ console.warn("ERROR!", error);
+
+ // setInterval(() => {
+ // dispatch( getGasPrice(coinName) );
+ // }, 5000);
+ }
+
+ dispatch({
+ type: WEB3.BLOCK_UPDATED,
+ name: coinName,
+ blockHash
+ });
+
+ // TODO: filter only current device
+ const accounts = getState().accounts.filter(a => a.coin === coinName);
+ for (const addr of accounts) {
+ dispatch( getBalance(addr) );
+ }
+
+ dispatch( getGasPrice(coinName) );
+
+ // if (pendingTxs.length > 0) {
+ // for (const tx of pendingTxs) {
+ // dispatch( getTransactionReceipt(tx) );
+ // }
+ // }
+ });
+
+ // init next coin
+ dispatch( init(instance, coinIndex + 1) );
+
}
});
+
+ // let instance2 = new Web3( new Web3.providers.HttpProvider('https://pyrus2.ubiqscan.io') );
+ // console.log("INIT WEB3", instance, instance2);
+ // instance2.eth.getGasPrice((error, gasPrice) => {
+ // console.log("---gasss price from UBQ", gasPrice)
+ // });
+ }
+}
+
+function initBlockTicker() {
+
+}
+
+export function initContracts(): ActionMethod {
+ return async (dispatch, getState) => {
+ const { web3, abi, tokens } = getState().web3;
+
+ const contracts = [];
+ for (let token of tokens) {
+ contracts.push({
+ contract: web3.eth.contract(abi).at(token.address),
+ name: token.name,
+ symbol: token.symbol,
+ decimal: token.decimal
+ });
+
+ // web3.eth.contract(abi).at(token.address).balanceOf('0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad', (e, r) => {
+ // console.warn('contrR', e, r.toString(10));
+ // });
+ }
+
+ const contract = web3.eth.contract(abi).at('0x58cda554935e4a1f2acbe15f8757400af275e084');
+
+ contract.name.call((error, name) => {
+ if (error) {
+ // TODO: skip
+ }
+ contract.symbol.call((error, symbol) => {
+ if (error) {
+ // TODO: skip
+ }
+
+ contract.decimals.call((error, decimals) => {
+ console.log("nameeeee", name, symbol, decimals)
+ })
+ });
+
+
+ })
}
}
+export function getGasPrice(coinName: string): ActionMethod {
+ return async (dispatch, getState) => {
+ const index: number = getState().web3.findIndex(w3 => {
+ return w3.coin === coinName;
+ });
+ const web3 = getState().web3[ index ].web3;
+ web3.eth.getGasPrice((error, gasPrice) => {
+ if (!error) {
+ dispatch({
+ type: WEB3.GAS_PRICE_UPDATED,
+ coin: coinName,
+ gasPrice
+ });
+ }
+ });
+ }
+}
+export function getBalance(addr: Address): ActionMethod {
+ return async (dispatch, getState) => {
+ const web3instance = getState().web3.filter(w3 => w3.coin === addr.coin)[0];
+ const web3 = web3instance.web3;
+ web3.eth.getBalance(addr.address, (error, balance) => {
+ if (!error) {
+ const newBalance: string = web3.fromWei(balance.toString(), 'ether');
+ if (addr.balance !== newBalance) {
+ dispatch({
+ type: ADDRESS.SET_BALANCE,
+ address: addr.address,
+ balance: newBalance
+ });
+
+ // dispatch( loadHistory(addr) );
+ }
+ }
+ });
+ }
+}
+export function getTransactionReceipt(txid: string): any {
+ return async (dispatch, getState) => {
+ const { web3 } = getState().web3;
+ //web3.eth.getTransactionReceipt(txid, (error, tx) => {
+ web3.eth.getTransaction(txid, (error, tx) => {
+ if (tx && tx.blockNumber) {
+ web3.eth.getBlock(tx.blockHash, (error, block) => {
+ console.log("---MAMM BLOCK", error, block, tx, tx.blockHash)
+ dispatch({
+ type: ACTIONS.TX_CONFIRMED,
+ txid,
+ tx,
+ block
+ })
+ });
+ }
+ });
+ }
+}
+
+
+export function updateLastBlock(hash: string) {
+ return {
+ type: 'web3__update_last_block',
+ hash
+ }
+}
export function getTransaction(web3, txid) {
return new Promise((resolve, reject) => {
@@ -45,7 +278,9 @@ export function getTransaction(web3, txid) {
});
}
-export function getBalance2(web3, address) {
+
+
+export function getBalanceAsync(web3, address) {
return new Promise((resolve, reject) => {
web3.eth.getBalance(address, (error, result) => {
if (error) {
@@ -57,6 +292,20 @@ export function getBalance2(web3, address) {
});
}
+export const getTokenBalanceAsync = (erc20: any, token: any, address: any): Promise => {
+ return new Promise((resolve, reject) => {
+
+ const contr = erc20.at(token);
+ contr.balanceOf(address, (error, result) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve(result);
+ }
+ });
+ });
+}
+
export function getNonce(web3, address) {
return new Promise((resolve, reject) => {
web3.eth.getTransactionCount(address, (error, result) => {
@@ -69,6 +318,41 @@ export function getNonce(web3, address) {
});
}
+
+export function getTokenInfoAsync(erc20: any, address: string): Promise {
+ return new Promise((resolve, reject) => {
+
+ const contract = erc20.at(address);
+ const info = {};
+ // TODO: handle errors
+ contract.name.call((e, name) => {
+ if (e) {
+ //console.log("1", address, e)
+ //resolve(null);
+ //return;
+ }
+ info.name = name;
+ contract.symbol.call((e, symbol) => {
+ if (e) {
+ console.log("2", e)
+ resolve(null);
+ return;
+ }
+ info.symbol = symbol;
+ contract.decimals.call((e, decimals) => {
+ if (e) {
+ console.log("3", e)
+ resolve(null);
+ return;
+ }
+ info.decimals = decimals.toString();
+ resolve(info);
+ });
+ })
+ });
+ });
+}
+
export function estimateGas(web3, gasOptions) {
return new Promise((resolve, reject) => {
web3.eth.estimateGas(gasOptions, (error, result) => {
@@ -81,7 +365,7 @@ export function estimateGas(web3, gasOptions) {
})
}
-export function getGasPrice(web3) {
+export function getGasPrice2(web3) {
return new Promise((resolve, reject) => {
web3.eth.getGasPrice((error, result) => {
if (error) {
@@ -93,7 +377,7 @@ export function getGasPrice(web3) {
})
}
-export function push(web3, tx) {
+export function pushTx(web3, tx) {
return new Promise((resolve, reject) => {
web3.eth.sendRawTransaction(tx, (error, result) => {
if (error) {
@@ -168,7 +452,7 @@ export function composeTransaction() {
const tx = new EthereumjsTx(txData);
const serializedTx = '0x' + tx.serialize().toString('hex');
- const txid = await push(web3, serializedTx);
+ const txid = await pushTx(web3, serializedTx);
dispatch({
type: 'tx_complete',
@@ -177,4 +461,9 @@ export function composeTransaction() {
console.log("TXID", txid);
}
-}
\ No newline at end of file
+}
+
+
+
+
+
diff --git a/src/js/actions/constants/Discovery.js b/src/js/actions/constants/Discovery.js
new file mode 100644
index 00000000..152f43d3
--- /dev/null
+++ b/src/js/actions/constants/Discovery.js
@@ -0,0 +1,8 @@
+/* @flow */
+'use strict';
+
+export const START: string = 'discovery__start';
+export const STOP: string = 'discovery__stop';
+export const COMPLETE: string = 'discovery__complete';
+export const WAITING: string = 'discovery__waiting';
+export const FROM_STORAGE: string = 'discovery__from_storage';
\ No newline at end of file
diff --git a/src/js/actions/constants/LocalStorage.js b/src/js/actions/constants/LocalStorage.js
new file mode 100644
index 00000000..479d9672
--- /dev/null
+++ b/src/js/actions/constants/LocalStorage.js
@@ -0,0 +1,6 @@
+/* @flow */
+'use strict';
+
+export const SAVE: string = 'storage__save';
+export const READY: string = 'storage__ready';
+export const ERROR: string = 'storage__error';
\ No newline at end of file
diff --git a/src/js/actions/constants/Modal.js b/src/js/actions/constants/Modal.js
new file mode 100644
index 00000000..f906b7b5
--- /dev/null
+++ b/src/js/actions/constants/Modal.js
@@ -0,0 +1,13 @@
+export const ON_PASSPHRASE_CHANGE: string = 'action__on_passphrase_change';
+export const ON_PASSPHRASE_SHOW: string = 'action__on_passphrase_show';
+export const ON_PASSPHRASE_HIDE: string = 'action__on_passphrase_hide';
+export const ON_PASSPHRASE_SAVE: string = 'action__on_passphrase_save';
+export const ON_PASSPHRASE_FORGET: string = 'action__on_passphrase_forget';
+export const ON_PASSPHRASE_FOCUS: string = 'action__on_passphrase_focus';
+export const ON_PASSPHRASE_BLUR: string = 'action__on_passphrase_blur';
+export const ON_PASSPHRASE_SUBMIT: string = 'action__on_passphrase_submit';
+
+export const FORGET: string = 'modal__forget';
+export const REMEMBER: string = 'modal__remember';
+export const ON_FORGET: string = 'modal__on_forget';
+export const ON_REMEMBER: string = 'modal__on_remember';
diff --git a/src/js/actions/constants/SendForm.js b/src/js/actions/constants/SendForm.js
new file mode 100644
index 00000000..7e33336b
--- /dev/null
+++ b/src/js/actions/constants/SendForm.js
@@ -0,0 +1,19 @@
+/* @flow */
+'use strict';
+
+export const INIT: string = 'send__init';
+export const DISPOSE: string = 'send__dispose';
+export const VALIDATION: string = 'send__validation';
+export const ADDRESS_CHANGE: string = 'send__address_change';
+export const AMOUNT_CHANGE: string = 'send__amount_change';
+export const SET_MAX: string = 'send__set_max';
+export const CURRENCY_CHANGE: string = 'send__currency_change';
+export const FEE_LEVEL_CHANGE: string = 'send__fee_level_change';
+export const GAS_PRICE_CHANGE: string = 'send__gas_price_change';
+export const GAS_LIMIT_CHANGE: string = 'send__gas_limit_change';
+export const UPDATE_FEE_LEVELS: string = 'send__update_fee_levels';
+export const DATA_CHANGE: string = 'send__data_change';
+export const SEND: string = 'send__submit';
+export const TX_COMPLETE: string = 'send__tx_complete';
+export const TX_ERROR: string = 'send__tx_error';
+export const TOGGLE_ADVANCED: string = 'send__toggle_advanced';
\ No newline at end of file
diff --git a/src/js/actions/constants/Token.js b/src/js/actions/constants/Token.js
new file mode 100644
index 00000000..ed88dc03
--- /dev/null
+++ b/src/js/actions/constants/Token.js
@@ -0,0 +1,7 @@
+/* @flow */
+'use strict';
+
+export const ADD: string = 'token__add';
+export const REMOVE: string = 'token__remove';
+export const SET_BALANCE: string = 'token__set_balance';
+export const FROM_STORAGE: string = 'token__from_storage';
\ No newline at end of file
diff --git a/src/js/actions/constants/TrezorConnect.js b/src/js/actions/constants/TrezorConnect.js
new file mode 100644
index 00000000..06b0a4b4
--- /dev/null
+++ b/src/js/actions/constants/TrezorConnect.js
@@ -0,0 +1,26 @@
+/* @flow */
+'use strict';
+
+export const READY: string = 'trezorconnect__ready';
+export const INITIALIZATION_ERROR: string = 'trezorconnect__init_error';
+export const SELECT_DEVICE: string = 'trezorconnect__select_device';
+
+
+export const DEVICE_FROM_STORAGE: string = 'trezorconnect__device_from_storage';
+export const AUTH_DEVICE: string = 'trezorconnect__auth_device';
+export const COIN_CHANGED: string = 'trezorconnect__coin_changed';
+
+export const REMEMBER_REQUEST: string = 'trezorconnect__remember_request';
+export const FORGET_REQUEST: string = 'trezorconnect__forget_request';
+export const FORGET: string = 'trezorconnect__forget';
+export const FORGET_SINGLE: string = 'trezorconnect__forget_single';
+export const DISCONNECT_REQUEST: string = 'trezorconnect__disconnect_request';
+export const REMEMBER: string = 'trezorconnect__remember';
+
+export const START_ACQUIRING: string = 'trezorconnect__start_acquiring';
+export const STOP_ACQUIRING: string = 'trezorconnect__stop_acquiring';
+
+export const TRY_TO_DUPLICATE: string = 'trezorconnect__try_to_duplicate';
+export const DUPLICATE: string = 'trezorconnect__duplicate';
+
+export const DEVICE_STATE_EXCEPTION: string = 'trezorconnect__device_state_exception';
\ No newline at end of file
diff --git a/src/js/actions/constants/Web3.js b/src/js/actions/constants/Web3.js
new file mode 100644
index 00000000..6334e1a0
--- /dev/null
+++ b/src/js/actions/constants/Web3.js
@@ -0,0 +1,9 @@
+/* @flow */
+'use strict';
+
+export const START: string = 'web3__start';
+export const STOP: string = 'web3__stop';
+export const CREATE: string = 'web3__create';
+export const READY: string = 'web3__ready';
+export const BLOCK_UPDATED: string = 'web3__block_updated';
+export const GAS_PRICE_UPDATED: string = 'web3__gas_price_updated';
\ No newline at end of file
diff --git a/src/js/actions/constants/account.js b/src/js/actions/constants/account.js
new file mode 100644
index 00000000..d6bcb858
--- /dev/null
+++ b/src/js/actions/constants/account.js
@@ -0,0 +1,11 @@
+/* @flow */
+'use strict';
+
+export const INIT: string = 'account__init';
+export const DISPOSE: string = 'account__dispose';
+
+export const CREATE: string = 'address__create';
+export const REMOVE: string = 'address__remove';
+export const SET_BALANCE: string = 'address__set_balance';
+export const SET_NONCE: string = 'address__set_nonce';
+export const FROM_STORAGE: string = 'address__from_storage';
\ No newline at end of file
diff --git a/src/js/actions/constants/address.js b/src/js/actions/constants/address.js
new file mode 100644
index 00000000..23e87cd0
--- /dev/null
+++ b/src/js/actions/constants/address.js
@@ -0,0 +1,9 @@
+/* @flow */
+'use strict';
+
+export const CREATE: string = 'address__create';
+export const REMOVE: string = 'address__remove';
+export const SET_BALANCE: string = 'address__set_balance';
+export const SET_NONCE: string = 'address__set_nonce';
+export const FROM_STORAGE: string = 'address__from_storage';
+
diff --git a/src/js/actions/constants/notification.js b/src/js/actions/constants/notification.js
new file mode 100644
index 00000000..d1acbcc5
--- /dev/null
+++ b/src/js/actions/constants/notification.js
@@ -0,0 +1,6 @@
+/* @flow */
+'use strict';
+
+export const ADD: string = 'notification__add';
+export const CLOSE: string = 'notification__close';
+export const REMOVE: string = 'account__remove';
\ No newline at end of file
diff --git a/src/js/actions/constants/receive.js b/src/js/actions/constants/receive.js
new file mode 100644
index 00000000..49c0f5cd
--- /dev/null
+++ b/src/js/actions/constants/receive.js
@@ -0,0 +1,8 @@
+/* @flow */
+'use strict';
+
+export const INIT: string = 'receive__init';
+export const DISPOSE: string = 'receive__dispose';
+export const REQUEST_UNVERIFIED: string = 'receive__request_unverified';
+export const SHOW_ADDRESS: string = 'receive__show_address';
+export const SHOW_UNVERIFIED_ADDRESS: string = 'receive__show_unverified';
diff --git a/src/js/actions/constants/summary.js b/src/js/actions/constants/summary.js
new file mode 100644
index 00000000..02dae814
--- /dev/null
+++ b/src/js/actions/constants/summary.js
@@ -0,0 +1,7 @@
+/* @flow */
+'use strict';
+
+export const INIT: string = 'summary__init';
+export const DISPOSE: string = 'summary__dispose';
+export const ADD_TOKEN: string = 'summary__add_token';
+export const DETAILS_TOGGLE: string = 'summary__details_toggle';
diff --git a/src/js/actions/index.js b/src/js/actions/index.js
index f5e150b6..45a72ee2 100644
--- a/src/js/actions/index.js
+++ b/src/js/actions/index.js
@@ -3,41 +3,44 @@
export const CLOSE_MODAL: string = 'action__close_modal';
-export const ON_PIN_ADD: string = 'action__on_pin_click';
-export const ON_PIN_BACKSPACE: string = 'action__on_pin_backspace';
export const ON_PIN_SUBMIT: string = 'action__on_pin_submit';
-export const ON_PASSPHRASE_CHANGE: string = 'action__on_passphrase_change';
-export const ON_PASSPHRASE_SHOW: string = 'action__on_passphrase_show';
-export const ON_PASSPHRASE_HIDE: string = 'action__on_passphrase_hide';
-export const ON_PASSPHRASE_SAVE: string = 'action__on_passphrase_save';
-export const ON_PASSPHRASE_FORGET: string = 'action__on_passphrase_forget';
-export const ON_PASSPHRASE_FOCUS: string = 'action__on_passphrase_focus';
-export const ON_PASSPHRASE_BLUR: string = 'action__on_passphrase_blur';
export const ON_PASSPHRASE_SUBMIT: string = 'action__on_passphrase_submit';
-export const ON_CHANGE_ACCOUNT: string = 'action__on_change_account';
-export const ON_CUSTOM_FEE_OPEN: string = 'action__on_custom_fee_open';
-export const ON_CUSTOM_FEE_CHANGE: string = 'action__on_custom_fee_change';
-
-export const ON_SELECT_DEVICE: string = 'action__on_select_device';
-
export const ON_ADDRESS_CHANGE: string = 'send__on_address_change';
export const ON_AMOUNT_CHANGE: string = 'send__on_amount_change';
+export const ON_FEE_LEVEL_CHANGE: string = 'send__on_fee_level_change';
export const ON_GAS_PRICE_CHANGE: string = 'send__on_gas_price_change';
export const ON_GAS_LIMIT_CHANGE: string = 'send__on_gas_limit_change';
export const ON_TX_DATA_CHANGE: string = 'send__on_data_change';
export const ON_TX_SEND: string = 'send__on_send';
export const ON_TX_COMPLETE: string = 'send__on_tx_complete';
+export const ON_GAS_PRICE_UPDATE: string = 'send__on_gas_price_update';
export const ADDRESS_CREATE: string = 'address__create';
export const ADDRESS_DELETE: string = 'address__delete';
-export const ADDRESS_SET_BALANCE: string = 'address__set_balance';
+export const ADDRESS_SET_BALANCE: string = 'address2__set_balance';
export const ADDRESS_SET_HISTORY: string = 'address__set_history';
export const ADDRESS_UPDATE_BALANCE: string = 'address__update_balance';
+export const ADDRESS_ADD_TO_HISTORY: string = 'address__add_to_history';
export const TX_STATUS_OK: string = 'tx__status_ok';
export const TX_STATUS_ERROR: string = 'tx__status_error';
-export const TX_STATUS_UNKNOWN: string = 'tx__status_unknown';
\ No newline at end of file
+export const TX_STATUS_UNKNOWN: string = 'tx__status_unknown';
+export const TX_CONFIRMED: string = 'tx__confirmed';
+
+
+
+export const TOKENS_TOGGLE_SUMMARY: string = 'tokens_toggle_summary';
+
+export const TOKENS_SEARCH: string = 'tokens_search';
+
+
+export const TOKENS_CUSTOM_TOGGLE: string = 'tokens_custom_toggle';
+export const TOKENS_CUSTOM_ADDRESS_CHANGE: string = 'tokens_custom_address_change';
+export const TOKENS_CUSTOM_NAME_CHANGE: string = 'tokens_custom_name_change';
+export const TOKENS_CUSTOM_SHORTCUT_CHANGE: string = 'tokens_custom_shortcut_change';
+export const TOKENS_CUSTOM_DECIMAL_CHANGE: string = 'tokens_custom_decimal_change';
+export const TOKENS_CUSTOM_ADD: string = 'tokens_custom_add';
\ No newline at end of file
diff --git a/src/js/components/AddressMenu.js b/src/js/components/AddressMenu.js
deleted file mode 100644
index ed9caa28..00000000
--- a/src/js/components/AddressMenu.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* @flow */
-'use strict';
-
-import React from 'react';
-import { NavLink } from 'react-router-dom';
-
-const AddressMenu = (props): any => {
-
- const { addresses } = props.addresses;
-
- let accounts = addresses.map((address, i) => {
- return (
-
- { `Address #${(address.index + 1 )}` }
- { address.balance } ETH
-
- )
- })
-
- return (
-
- );
-}
-
-export default AddressMenu;
\ No newline at end of file
diff --git a/src/js/components/AddressTab.js b/src/js/components/AddressTab.js
deleted file mode 100644
index c6b02e94..00000000
--- a/src/js/components/AddressTab.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* @flow */
-'use strict';
-
-import React from 'react';
-import { Link } from 'react-router-dom';
-
-const AddressTab = (props): any => {
-
- const urlParams = props.match.params;
- const basePath = `/address/${urlParams.address}`;
-
- return (
-
-
- History
-
-
- Send
-
-
- Receive
-
-
- );
-}
-
-export default AddressTab;
\ No newline at end of file
diff --git a/src/js/components/Devices.js b/src/js/components/Devices.js
deleted file mode 100644
index a0f493c6..00000000
--- a/src/js/components/Devices.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component } from 'react';
-
-export default class Devices extends Component {
- render() {
- const { devices, selectedDevice } = this.props.connect;
- const deviceList: Array = devices.map((dev, index) => {
- let css: string = "";
- if (dev.unacquired) {
- css += "unacquired";
- }
- if (dev.isUsedElsewhere) {
- css += " used-elsewhere";
- }
- if (dev.featuresNeedsReload) {
- css += " reload-features";
- }
- if (dev.path === selectedDevice) {
- css += " active";
- }
- return ( this.props.onSelectDevice(dev.path) } >{ dev.label } );
- });
-
- if (deviceList.length === 0) {
- deviceList.push(
- (No connected devices )
- );
- }
-
- return (
-
-
-
- );
- }
-}
diff --git a/src/js/components/Footer.js b/src/js/components/Footer.js
deleted file mode 100644
index c453ff17..00000000
--- a/src/js/components/Footer.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component } from 'react';
-
-export default class Footer extends Component {
- render() {
- return (
-
- );
- }
-}
diff --git a/src/js/components/Main.js b/src/js/components/Main.js
deleted file mode 100644
index e8be715f..00000000
--- a/src/js/components/Main.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component } from 'react';
-
-export default class Main extends Component {
- render() {
- return (
-
-
-
- { this.props.children }
-
-
- );
- }
-}
diff --git a/src/js/components/Receive.js b/src/js/components/Receive.js
deleted file mode 100644
index ee4bd1d9..00000000
--- a/src/js/components/Receive.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component } from 'react';
-import AddressTab from './AddressTab';
-import { QRCode } from 'react-qr-svg';
-
-const History = (props): any => {
-
- const { addresses } = props.addresses;
- const currentAddress = addresses[ parseInt(props.match.params.address) ];
-
- if (!currentAddress) return null;
-
- return (
-
-
- { currentAddress.address }
-
-
- );
-}
-
-export default History;
diff --git a/src/js/components/SendForm.js b/src/js/components/SendForm.js
deleted file mode 100644
index 876fe49e..00000000
--- a/src/js/components/SendForm.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component } from 'react';
-import AddressTab from './AddressTab';
-
-const SendForm = (props): any => {
-
- console.log("ENDFORM", props)
-
- const addressId = parseInt( props.match.params.address );
-
- const {
- address,
- amount,
- gasPrice,
- gasLimit,
- data
- } = props.sendForm;
-
- const {
- onAddressChange,
- onAmountChange,
- onGasPriceChange,
- onGasLimitChange,
- onDataChange,
- onSend
- } = props.sendFormActions;
-
- const disabled = false;
-
- return (
-
- );
-}
-
-export default SendForm;
diff --git a/src/js/components/common/Footer.js b/src/js/components/common/Footer.js
new file mode 100644
index 00000000..d69f8dc7
--- /dev/null
+++ b/src/js/components/common/Footer.js
@@ -0,0 +1,17 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+const Footer = (props: any): any => {
+ return (
+
+ );
+}
+
+export default Footer;
diff --git a/src/js/components/Header.js b/src/js/components/common/Header.js
similarity index 97%
rename from src/js/components/Header.js
rename to src/js/components/common/Header.js
index c898acca..7ffe5b0b 100644
--- a/src/js/components/Header.js
+++ b/src/js/components/common/Header.js
@@ -9,7 +9,6 @@ export default class Header extends Component {
);
diff --git a/src/js/components/common/LoaderCircle.js b/src/js/components/common/LoaderCircle.js
new file mode 100644
index 00000000..202eda91
--- /dev/null
+++ b/src/js/components/common/LoaderCircle.js
@@ -0,0 +1,22 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+export default (props: any): any => {
+
+ const style = {
+ width: `${props.size}px`,
+ height: `${props.size}px`,
+ }
+
+ return (
+
+
{ props.label }
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/common/Log.js b/src/js/components/common/Log.js
new file mode 100644
index 00000000..62ac4e18
--- /dev/null
+++ b/src/js/components/common/Log.js
@@ -0,0 +1,40 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import * as SendFormActions from '../../actions/SendFormActions';
+import { getAddress } from '../../actions/TrezorConnectActions';
+
+
+const Log = (props: any) => {
+ return (
+
+ Log
+
+ )
+}
+
+function mapStateToProps(state, own) {
+
+}
+
+function mapDispatchToProps(dispatch) {
+
+}
+
+export default connect(
+ (state) => {
+ return {
+ accounts: state.accounts,
+ receive: state.receive
+ };
+ },
+ (dispatch) => {
+ return {
+ getAddress: bindActionCreators(getAddress, dispatch),
+ };
+ }
+)(Log);
\ No newline at end of file
diff --git a/src/js/components/common/Notification.js b/src/js/components/common/Notification.js
new file mode 100644
index 00000000..21ecbff5
--- /dev/null
+++ b/src/js/components/common/Notification.js
@@ -0,0 +1,74 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import * as NOTIFICATION from '../../actions/constants/notification';
+
+
+export const Notification = (props: any) => {
+ const className = `notification ${ props.className }`;
+
+
+ const actionButtons = !props.actions ? null : props.actions.map((a, i) => {
+ return (
+ { props.close(); a.callback(); } } className="transparent">{ a.label }
+ )
+ });
+
+ return (
+
+ { props.cancelable ? (
+
props.close() }>
+ ) : null }
+
+ { props.actions && props.actions.length > 0 ? (
+
+ { actionButtons }
+
+ ) : null }
+
+
+ )
+}
+
+export const NotificationGroup = (props: any) => {
+ const { notifications, close } = props;
+ return notifications.map((n, i) => {
+ return (
+
+ )
+ });
+}
+
+export default connect(
+ (state) => {
+ return {
+ notifications: state.notifications
+ };
+ },
+ (dispatch) => {
+ return {
+ close: bindActionCreators((notif) => {
+ return {
+ type: NOTIFICATION.CLOSE,
+ payload: notif
+ }
+ }, dispatch),
+ };
+ }
+)(NotificationGroup);
\ No newline at end of file
diff --git a/src/js/components/landing/ConnectDevice.js b/src/js/components/landing/ConnectDevice.js
new file mode 100644
index 00000000..32f25e01
--- /dev/null
+++ b/src/js/components/landing/ConnectDevice.js
@@ -0,0 +1,36 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import Header from '../common/Header';
+import Footer from '../common/Footer';
+
+export default (props: any): any => {
+ return (
+
+
+
+ The private bank in your hands.
+ TREZOR Wallet is an easy-to-use interface for your TREZOR.
+ TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.
+
+
+
+
+
+
+
+
+
+
+ Connect TREZOR to continue
+
+ {/*
Don't have TREZOR? Get one
*/}
+
+
+ Don't have TREZOR? Get one
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/landing/LandingPage.js b/src/js/components/landing/LandingPage.js
new file mode 100644
index 00000000..440ba3f8
--- /dev/null
+++ b/src/js/components/landing/LandingPage.js
@@ -0,0 +1,73 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import Preloader from './Preloader';
+import ConnectDevice from './ConnectDevice';
+import LocalStorageError from './LocalStorageError';
+import TrezorConnectError from './TrezorConnectError';
+import Header from '../common/Header';
+import Footer from '../common/Footer';
+import { Notification } from '../common/Notification';
+
+export default (props: any): any => {
+
+ const web3 = props.web3;
+ const { devices } = props.connect;
+ const localStorageError = props.localStorage.error;
+ const connectError = props.connect.error;
+
+ let notification = null;
+
+ if (localStorageError) {
+ notification = ( );
+ }
+
+ if (connectError) {
+ notification = ( );
+ }
+
+ if (notification || (web3.length > 0 && devices.length < 1)) {
+ return (
+
+
+ { notification }
+
+ The private bank in your hands.
+ TREZOR Wallet is an easy-to-use interface for your TREZOR.
+ TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.
+
+
+
+
+
+
+
+
+
+
+
+ Connect TREZOR to continue
+
+
+ {/*
Add new device */}
+ {/*
Don't have TREZOR? Get one
*/}
+
+
+ Don't have TREZOR? Get one
+
+
+
+ );
+ } else {
+ return ( );
+ }
+}
diff --git a/src/js/components/landing/LocalStorageError.js b/src/js/components/landing/LocalStorageError.js
new file mode 100644
index 00000000..1e61c062
--- /dev/null
+++ b/src/js/components/landing/LocalStorageError.js
@@ -0,0 +1,12 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+export default (props: any): any => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/landing/Preloader.js b/src/js/components/landing/Preloader.js
new file mode 100644
index 00000000..85925faf
--- /dev/null
+++ b/src/js/components/landing/Preloader.js
@@ -0,0 +1,13 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import Loader from '../common/LoaderCircle';
+
+export default (props: any): any => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/landing/TrezorConnectError.js b/src/js/components/landing/TrezorConnectError.js
new file mode 100644
index 00000000..cd3c9e51
--- /dev/null
+++ b/src/js/components/landing/TrezorConnectError.js
@@ -0,0 +1,12 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+export default (props: any): any => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/modal/AccountSelection.js b/src/js/components/modal/AccountSelection.js
deleted file mode 100644
index 5ef6cb5e..00000000
--- a/src/js/components/modal/AccountSelection.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* @flow */
-'use strict';
-
-import React from 'react';
-import { formatAmount } from '../../utils/formatUtils';
-
-const AccountSelection = (props): any => {
-
- const { accounts, coinInfo, complete } = props.modal;
- const accountsCollection = accounts.map((a, index) => {
-
- let accountStatus: string = a.fresh ? 'Fresh account' : formatAmount(a.balance, coinInfo);
- // Loading...
-
- return (
-
- props.modalActions.onAccountSelect(index) }>
- { a.label }
- { accountStatus }
-
-
- )
- });
-
- const header: string = complete ? `Select ${ coinInfo.label } account` : `Loading ${ coinInfo.label } accounts...`;
-
- return (
-
-
{ header }
-
-
Accounts
-
Legacy Accounts
-
-
- { accountsCollection }
-
-
- );
-}
-
-export default AccountSelection;
\ No newline at end of file
diff --git a/src/js/components/modal/ConfirmAddress.js b/src/js/components/modal/ConfirmAddress.js
new file mode 100644
index 00000000..7af6ddf3
--- /dev/null
+++ b/src/js/components/modal/ConfirmAddress.js
@@ -0,0 +1,55 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import { findSelectedDevice } from '../../reducers/TrezorConnectReducer';
+
+const ConfirmAddress = (props: any): any => {
+
+ const account = props.accounts.find(a => a.checksum === props.receive.checksum && a.index === props.receive.accountIndex && a.coin === props.receive.coin);
+
+ return (
+
+
+
Confirm address on TREZOR
+
Please compare your address on device with address shown bellow.
+
+
+
{ account.address }
+
{ account.coin.toUpperCase() } account #{ (account.index + 1) }
+
+
+ );
+}
+export default ConfirmAddress;
+
+export const ConfirmUnverifiedAddress = (props: any): any => {
+
+ const account = props.accounts.find(a => a.checksum === props.receive.checksum && a.index === props.receive.accountIndex && a.coin === props.receive.coin);
+
+ const {
+ onCancel
+ } = props.modalActions;
+
+ const {
+ showUnverifiedAddress,
+ showAddress
+ } = props.receiveActions;
+
+
+ return (
+
+
+
Your TREZOR is not connected
+
To prevent phishing attacks, you should verify the address on your TREZOR first. Please reconnect your device to continue with the verification process.
+
{
+ onCancel();
+ showAddress(account.addressPath);
+ } }>Try again
+
{
+ onCancel();
+ showUnverifiedAddress();
+ } }>Show unverified address
+
+ );
+}
diff --git a/src/js/components/modal/ConfirmSignTx.js b/src/js/components/modal/ConfirmSignTx.js
new file mode 100644
index 00000000..1fa9621a
--- /dev/null
+++ b/src/js/components/modal/ConfirmSignTx.js
@@ -0,0 +1,34 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+const Confirmation = (props): any => {
+ const {
+ amount,
+ address,
+ coin,
+ token,
+ total,
+ selectedFeeLevel
+ } = props.sendForm;
+
+ return (
+
+
+
Confirm transaction on your TREZOR
+
Details are shown on device
+
+
+
Send
+
{ `${amount} ${token.toUpperCase() }` }
+
To
+
{ address }
+
Fee
+
{ selectedFeeLevel.label }
+
+
+ );
+}
+
+export default Confirmation;
\ No newline at end of file
diff --git a/src/js/components/modal/Confirmation.js b/src/js/components/modal/Confirmation.js
deleted file mode 100644
index dbe7552e..00000000
--- a/src/js/components/modal/Confirmation.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/* @flow */
-'use strict';
-
-import React from 'react';
-
-const Confirmation = (props): any => {
- const { onConfirmation, onConfirmationCancel } = props.modalActions;
- return (
-
-
Confirm
- Export
- Cancel
-
- );
-}
-
-export default Confirmation;
\ No newline at end of file
diff --git a/src/js/components/modal/DuplicateDevice.js b/src/js/components/modal/DuplicateDevice.js
new file mode 100644
index 00000000..a8ad19a6
--- /dev/null
+++ b/src/js/components/modal/DuplicateDevice.js
@@ -0,0 +1,21 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+const RememberDevice = (props: any): any => {
+ const { device } = props.modal;
+ const { onCancel, onDuplicateDevice } = props.modalActions;
+ return (
+
+
Duplicate { device.label } ?
+
+ Device label
+
+ Cancel
+ onDuplicateDevice( { ...device, instanceLabel: "kokot" } ) }>Duplicate
+
+ );
+}
+
+export default RememberDevice;
\ No newline at end of file
diff --git a/src/js/components/modal/FeeSelection.js b/src/js/components/modal/FeeSelection.js
deleted file mode 100644
index d913a007..00000000
--- a/src/js/components/modal/FeeSelection.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/* @flow */
-'use strict';
-
-import React from 'react';
-import { formatAmount, formatTime } from '../../utils/formatUtils';
-
-const FeeSelection = (props): any => {
-
- const {
- onChangeAccount,
- onCustomFeeOpen,
- onCustomFeeChange,
- onFeeSelect
- } = props.modalActions;
-
- const {
- feeList,
- coinInfo,
- customFeeOpened,
- customFee
- } = props.modal;
-
-
- const feesCollection = feeList.map((feeItem, index) => {
- // skip custom
- if (feeItem.name === 'custom') return null;
- let feeName;
- if (feeItem.name === 'normal' && feeItem.bytes > 0) {
- feeName = (
-
- { feeItem.name }
- recommended
-
- );
- } else {
- feeName = ({ feeItem.name } );
- }
-
- let feeButton: string;
-
- if (feeItem.fee > 0) {
- return (
-
- onFeeSelect(index) }>
- { feeName }
- { formatAmount(feeItem.fee, coinInfo) }
- { formatTime(feeItem.minutes) }
-
-
- );
- } else {
- return (
-
-
- { feeName }
- Insufficient funds
-
-
- );
- }
- });
-
- return (
-
-
Select fee:
-
- Change account
-
-
- { feesCollection }
-
-
- custom
-
-
-
-
-
-
-
onCustomFeeChange(event.target.value) } />
-
sat/B
-
SEND
-
-
- Setting custom fee is not recommended.
- If you set too low fee, it might get stuck forever.
-
-
-
-
-
- );
-}
-
-export default FeeSelection;
\ No newline at end of file
diff --git a/src/js/components/modal/InvalidPin.js b/src/js/components/modal/InvalidPin.js
index 4682a26a..63933764 100644
--- a/src/js/components/modal/InvalidPin.js
+++ b/src/js/components/modal/InvalidPin.js
@@ -4,9 +4,11 @@
import React from 'react';
const InvalidPin = (props): any => {
+ const { device } = props.modal;
return (
-
-
Entered PIN is not correct. Retrying...
+
+
Entered PIN for { device.label } is not correct.
+
Retrying...
);
}
diff --git a/src/js/components/modal/Modal.js b/src/js/components/modal/Modal.js
deleted file mode 100644
index 760adc58..00000000
--- a/src/js/components/modal/Modal.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component } from 'react';
-import { CSSTransition, Transition } from 'react-transition-group';
-
-import { UI } from 'trezor-connect';
-
-import Pin from './Pin';
-import InvalidPin from './InvalidPin';
-import Passphrase from './Passphrase';
-import Permission from './Permission';
-import Confirmation from './Confirmation';
-
-import AccountSelection from './AccountSelection';
-import FeeSelection from './FeeSelection';
-
-const duration = 300;
-
-const defaultStyle = {
- transition: `opacity ${duration}ms ease-in-out`,
- opacity: 0,
- padding: 20,
- display: 'inline-block',
- backgroundColor: '#8787d8'
-}
-
-const transitionStyles = {
- entering: { opacity: 0 },
- entered: { opacity: 1 },
-};
-
-const Fade2 = ({ in: inProp }) => (
-
- {(state) => (
-
- I'm A fade Transition2
-
- )}
-
-);
-
-const Fade = ({ children, ...props }) => (
-
- { children }
-
-);
-
-export default class Modal extends Component {
- render() {
- const { opened, windowType } = this.props.modal;
-
- let component = null;
- switch(windowType) {
- case UI.REQUEST_PIN :
- component = (
);
- break;
- case UI.INVALID_PIN :
- component = (
);
- break;
- case UI.REQUEST_PASSPHRASE :
- component = (
);
- break;
- case UI.REQUEST_PERMISSION :
- component = (
);
- break;
- case UI.REQUEST_CONFIRMATION :
- component = (
);
- break;
-
- case UI.SELECT_ACCOUNT :
- component = (
);
- break;
- case UI.SELECT_FEE :
- component = (
);
- break;
- }
-
- let ch = null;
- if (opened) {
- ch = (
-
-
-
- );
- }
-
- return ch;
- }
-}
diff --git a/src/js/components/modal/ModalContainer.js b/src/js/components/modal/ModalContainer.js
new file mode 100644
index 00000000..ce1266cd
--- /dev/null
+++ b/src/js/components/modal/ModalContainer.js
@@ -0,0 +1,119 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import { CSSTransition, Transition } from 'react-transition-group';
+
+import { UI } from 'trezor-connect';
+
+import * as ModalActions from '../../actions/ModalActions';
+import * as ReceiveActions from '../../actions/ReceiveActions';
+
+import Pin from './Pin';
+import InvalidPin from './InvalidPin';
+import Passphrase from './Passphrase';
+import ConfirmSignTx from './ConfirmSignTx';
+import ConfirmAddress, { ConfirmUnverifiedAddress } from './ConfirmAddress';
+import RememberDevice, { ForgetDevice, DisconnectDevice } from './RememberDevice';
+import DuplicateDevice from './DuplicateDevice';
+
+import * as RECEIVE from '../../actions/constants/receive';
+import * as MODAL from '../../actions/constants/Modal';
+import * as CONNECT from '../../actions/constants/TrezorConnect';
+
+const duration = 300;
+
+
+const Fade = ({ children, ...props }) => (
+
+ { children }
+
+);
+
+class Modal extends Component {
+ render() {
+ const { opened, windowType } = this.props.modal;
+
+ let component = null;
+ switch (windowType) {
+ case UI.REQUEST_PIN :
+ component = (
);
+ break;
+ case UI.INVALID_PIN :
+ component = (
);
+ break;
+ case UI.REQUEST_PASSPHRASE :
+ component = (
);
+ break;
+ case "ButtonRequest_SignTx" :
+ component = (
)
+ break;
+ case "ButtonRequest_Address" :
+ component = (
)
+ break;
+ case RECEIVE.REQUEST_UNVERIFIED :
+ component = (
)
+ break;
+
+ case CONNECT.REMEMBER_REQUEST :
+ component = (
)
+ break;
+
+ case CONNECT.FORGET_REQUEST :
+ component = (
)
+ break;
+
+ case CONNECT.DISCONNECT_REQUEST :
+ component = (
)
+ break;
+
+ case CONNECT.TRY_TO_DUPLICATE :
+ component = (
)
+ break;
+ }
+
+ let ch = null;
+ if (opened) {
+ ch = (
+
+
+
+ );
+ }
+
+ return ch;
+ }
+}
+
+const mapStateToProps = (state: any, own: any): any => {
+ return {
+ modal: state.modal,
+ accounts: state.accounts,
+ devices: state.connect.devices,
+ sendForm: state.sendForm,
+ receive: state.receive,
+ };
+}
+
+const mapDispatchToProps = (dispatch: any): any => {
+ return {
+ modalActions: bindActionCreators(ModalActions, dispatch),
+ receiveActions: bindActionCreators(ReceiveActions, dispatch),
+ };
+}
+
+// export default connect(mapStateToProps, mapDispatchToProps)(Modal);
+export default withRouter(
+ connect(mapStateToProps, mapDispatchToProps)(Modal)
+);
diff --git a/src/js/components/modal/Passphrase.js b/src/js/components/modal/Passphrase.js
index 9c0b8041..dde75b65 100644
--- a/src/js/components/modal/Passphrase.js
+++ b/src/js/components/modal/Passphrase.js
@@ -1,94 +1,308 @@
/* @flow */
'use strict';
-import React, { Component, KeyboardEvent, FocusEvent } from 'react';
+import React, { Component } from 'react';
+import raf from 'raf';
+
+type State = {
+ singleInput: boolean;
+ passphrase: string;
+ passphraseRevision: string;
+ passphraseFocused: boolean;
+ passphraseRevisionFocused: boolean;
+ passphraseRevisionTouched: boolean;
+ match: boolean;
+ visible: boolean;
+}
export default class PinModal extends Component {
- input: HTMLInputElement;
+ state: State;
+ passphraseInput: HTMLInputElement;
+ passphraseRevisionInput: HTMLInputElement;
+
+ constructor(props: any) {
+ super(props);
+
+ console.warn("PROPZ", props)
+ const isSavedDevice = props.devices.find(d => d.path === props.modal.device.path && d.remember);
+
+ this.state = {
+ singleInput: isSavedDevice ? true : false,
+ passphrase: '',
+ passphraseRevision: '',
+ passphraseFocused: false,
+ passphraseRevisionFocused: false,
+ passphraseRevisionTouched: false,
+ match: true,
+ visible: false
+ }
+ }
+
+ keyboardHandler(event: KeyboardEvent): void {
+
+
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ //this.passphraseInput.blur();
+ //this.passphraseRevisionInput.blur();
+
+ //this.passphraseInput.type = 'text';
+ //this.passphraseRevisionInput.type = 'text';
+
+ this.submit();
+
+ // TODO: set timeout, or wait for blur event
+ //onPassphraseSubmit(passphrase, passphraseCached);
+ //raf(() => onPassphraseSubmit(passphrase));
+ }
+ }
componentDidMount(): void {
// one time autofocus
- this.input.focus();
+ this.passphraseInput.focus();
this.keyboardHandler = this.keyboardHandler.bind(this);
window.addEventListener('keydown', this.keyboardHandler, false);
+
+
+
+ // document.oncontextmenu = (event) => {
+ // const el = window.event.srcElement || event.target;
+ // const type = el.tagName.toLowerCase() || '';
+ // if (type === 'input') {
+ // return false;
+ // }
+ // };
}
componentWillUnmount(): void {
window.removeEventListener('keydown', this.keyboardHandler, false);
+ // this.passphraseInput.type = 'text';
+ // this.passphraseInput.style.display = 'none';
+ // this.passphraseRevisionInput.type = 'text';
+ // this.passphraseRevisionInput.style.display = 'none';
}
- keyboardHandler(event: KeyboardEvent): void {
- const { onPassphraseSubmit } = this.props;
- const { passphrase, passphraseCached } = this.props.modal;
-
- if (event.keyCode === 13) {
- event.preventDefault();
- this.input.blur();
- onPassphraseSubmit(passphrase, passphraseCached);
- }
- }
// we don't want to keep password inside "value" attribute,
// so we need to replace it thru javascript
componentDidUpdate() {
- const { passphrase, passphraseFocused, passphraseVisible } = this.props.modal;
- let inputValue: string = passphrase;
- if (!passphraseVisible && !passphraseFocused) {
- inputValue = passphrase.replace(/./g, '•');
+ const {
+ passphrase,
+ passphraseRevision,
+ passphraseFocused,
+ passphraseRevisionFocused,
+ visible
+ } = this.state;
+ // } = this.props.modal;
+
+ let passphraseInputValue: string = passphrase;
+ let passphraseRevisionInputValue: string = passphraseRevision;
+ if (!visible && !passphraseFocused) {
+ passphraseInputValue = passphrase.replace(/./g, '•');
+ }
+ if (!visible && !passphraseRevisionFocused) {
+ passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•');
+ }
+
+ this.passphraseInput.value = passphraseInputValue;
+ this.passphraseInput.setAttribute("type", visible ? "text" : "password");
+
+ if (this.passphraseRevisionInput) {
+ this.passphraseRevisionInput.value = passphraseRevisionInputValue;
+ this.passphraseRevisionInput.setAttribute("type", visible ? "text" : "password");
+ }
+
+ }
+
+ onPassphraseChange = (input: string, value: string): void => {
+ // https://codepen.io/MiDri/pen/PGqvrO
+ // or
+ // https://github.com/zakangelle/react-password-mask/blob/master/src/index.js
+ if (input === 'passphrase') {
+ this.setState({
+ match: this.state.singleInput || this.state.passphraseRevision === value,
+ passphrase: value
+ });
+ } else {
+ this.setState({
+ match: this.state.passphrase === value,
+ passphraseRevision: value,
+ passphraseRevisionTouched: true
+ });
+ }
+ }
+
+ onPassphraseFocus = (input: string): void => {
+ if (input === 'passphrase') {
+ this.setState({
+ passphraseFocused: true
+ });
+ } else {
+ this.setState({
+ passphraseRevisionFocused: true
+ });
+ }
+ }
+
+ onPassphraseBlur = (input: string): void => {
+ if (input === 'passphrase') {
+ this.setState({
+ passphraseFocused: false
+ });
+ } else {
+ this.setState({
+ passphraseRevisionFocused: false
+ });
}
- this.input.value = inputValue;
}
- render(): void {
+ onPassphraseShow = (): void => {
+ this.setState({
+ visible: true
+ });
+ }
+
+ onPassphraseHide = (): void => {
+ this.setState({
+ visible: false
+ });
+ }
+
+ submit = (empty: boolean = false): void => {
+ const { onPassphraseSubmit } = this.props.modalActions;
+ const { passphrase } = this.state;
+
+ //this.passphraseInput.type = 'text';
+ // this.passphraseInput.style.display = 'none';
+ //this.passphraseInput.setAttribute('readonly', 'readonly');
+ // this.passphraseRevisionInput.type = 'text';
+ //this.passphraseRevisionInput.style.display = 'none';
+ //this.passphraseRevisionInput.setAttribute('readonly', 'readonly');
+
+ const p = passphrase;
+
+ this.setState({
+ passphrase: '',
+ passphraseRevision: '',
+ passphraseFocused: false,
+ passphraseRevisionFocused: false,
+ visible: false
+ })
+
+ raf(() => onPassphraseSubmit(empty ? '' : passphrase));
+ }
+
+ render(): any {
const {
- onPassphraseChange,
- onPassphraseSubmit,
- onPassphraseForget,
- onPassphraseFocus,
- onPassphraseBlur,
- onPassphraseSave,
- onPassphraseShow,
- onPassphraseHide
+ //onPassphraseChange,
+ //onPassphraseSubmit,
+ //onPassphraseSubmitEmpty,
+ //onPassphraseForget,
+ //onPassphraseFocus,
+ //onPassphraseBlur,
+ //onPassphraseSave,
+ //onPassphraseShow,
+ //onPassphraseHide
} = this.props.modalActions;
- const { passphrase, passphraseFocused, passphraseVisible, passphraseCached } = this.props.modal;
- let inputType: string = passphraseVisible || (!passphraseVisible && !passphraseFocused) ? "text" : "password";
- const showPassphraseCheckboxFn: Function = passphraseVisible ? onPassphraseHide : onPassphraseShow;
- const savePassphraseCheckboxFn: Function = passphraseCached ? onPassphraseForget : onPassphraseSave;
+ const {
+ device,
+ //passphrase,
+ //passphraseRevision,
+ //passphraseFocused,
+ //passphraseRevisionFocused,
+ //passphraseVisible,
+ //passphraseMatch,
+ //passphraseRevisionTouched,
+ passphraseCached
+ } = this.props.modal;
+
+ const {
+ singleInput,
+ passphrase,
+ passphraseRevision,
+ passphraseFocused,
+ passphraseRevisionFocused,
+ visible,
+ match,
+ passphraseRevisionTouched,
+ } = this.state;
+
+ let passphraseInputType: string = visible || (!visible && !passphraseFocused) ? "text" : "password";
+ let passphraseRevisionInputType: string = visible || (!visible && !passphraseRevisionFocused) ? "text" : "password";
+ passphraseInputType = passphraseRevisionInputType = "text";
+ //let passphraseInputType: string = visible || passphraseFocused ? "text" : "password";
+ //let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password";
+
+
+ const showPassphraseCheckboxFn: Function = visible ? this.onPassphraseHide : this.onPassphraseShow;
return (
-
Please enter your passphrase.
-
Note that passphrase is case-sensitive.
-
+ {/*
this.submit(true) }> */}
+
Enter { device.label } passphrase
+
Note that passphrase is case-sensitive.
+
+ Passphrase
{ this.input = element; } }
- onChange={ event => onPassphraseChange(event.currentTarget.value) }
- type={ inputType }
+ ref={ (element) => { this.passphraseInput = element; } }
+ onChange={ event => this.onPassphraseChange('passphrase', event.currentTarget.value) }
+ type={ passphraseInputType }
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
data-lpignore="true"
- onFocus={ onPassphraseFocus }
- onBlur={ onPassphraseBlur }
+ onFocus={ event => this.onPassphraseFocus('passphrase') }
+ onBlur={ event => this.onPassphraseBlur('passphrase') }
+
tabIndex="1" />
-
);
}
diff --git a/src/js/components/modal/Permission.js b/src/js/components/modal/Permission.js
deleted file mode 100644
index 1ed8193d..00000000
--- a/src/js/components/modal/Permission.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* @flow */
-'use strict';
-
-import React from 'react';
-
-const Permission = (props): any => {
- const { onPermissionGranted, onPermissionRejected } = props.modalActions;
- return (
-
-
HOST is requesting permissions to:
-
-
Accept
-
Cancel
-
- );
-}
-
-export default Permission;
\ No newline at end of file
diff --git a/src/js/components/modal/Pin.js b/src/js/components/modal/Pin.js
index 5b041915..782c988f 100644
--- a/src/js/components/modal/Pin.js
+++ b/src/js/components/modal/Pin.js
@@ -3,20 +3,41 @@
import React, { Component, KeyboardEvent } from 'react';
+type State = {
+ pin: string;
+}
+
export default class Pin extends Component {
- componentWillMount(): void {
- this.keyboardHandler = this.keyboardHandler.bind(this);
- window.addEventListener('keydown', this.keyboardHandler, false);
+ state: State;
+
+ constructor(props: any) {
+ super(props);
+
+ this.state = {
+ pin: '',
+ }
}
- componentWillUnmount(): void {
- window.removeEventListener('keydown', this.keyboardHandler, false);
+ onPinAdd = (input: number): void => {
+ let pin: string = this.state.pin;
+ if (pin.length < 9) {
+ pin += input;
+ this.setState({
+ pin: pin
+ });
+ }
+ }
+
+ onPinBackspace = (): void => {
+ this.setState({
+ pin: this.state.pin.substring(0, this.state.pin.length - 1),
+ });
}
keyboardHandler(event: KeyboardEvent): void {
const { onPinAdd, onPinBackspace, onPinSubmit } = this.props.modalActions;
- const { pin } = this.props.modal;
+ const { pin } = this.state;
event.preventDefault();
switch (event.keyCode) {
@@ -26,76 +47,94 @@ export default class Pin extends Component {
break;
// backspace
case 8 :
- onPinBackspace();
+ this.onPinBackspace();
break;
// numeric and numpad
case 49 :
case 97 :
- onPinAdd(1);
+ this.onPinAdd(1);
break;
case 50 :
case 98 :
- onPinAdd(2);
+ this.onPinAdd(2);
break;
case 51 :
case 99 :
- onPinAdd(3);
+ this.onPinAdd(3);
break;
case 52 :
case 100 :
- onPinAdd(4);
+ this.onPinAdd(4);
break;
case 53 :
case 101 :
- onPinAdd(5);
+ this.onPinAdd(5);
break;
case 54 :
case 102 :
- onPinAdd(6);
+ this.onPinAdd(6);
break;
case 55 :
case 103 :
- onPinAdd(7);
+ this.onPinAdd(7);
break;
case 56 :
case 104 :
- onPinAdd(8);
+ this.onPinAdd(8);
break;
case 57 :
case 105 :
- onPinAdd(9);
+ this.onPinAdd(9);
break;
}
}
- render(): void {
- const { onPinAdd, onPinBackspace, onPinSubmit } = this.props.modalActions;
- const { pin } = this.props.modal;
+
+
+ componentWillMount(): void {
+ this.keyboardHandler = this.keyboardHandler.bind(this);
+ window.addEventListener('keydown', this.keyboardHandler, false);
+ }
+
+ componentWillUnmount(): void {
+ window.removeEventListener('keydown', this.keyboardHandler, false);
+ }
+
+ render(): any {
+ const { onPinSubmit } = this.props.modalActions;
+ const { device } = this.props.modal;
+ const { pin } = this.state;
+
return (
-
Please enter your PIN.
-
Look at the device for number positions.
-
-
onPinAdd(7) }>•
-
onPinAdd(8) }>•
-
onPinAdd(9) }>•
+ {/*
*/}
+
Enter { device.label } PIN
+
The PIN layout is displayed on your TREZOR.
+
+
+
+ this.onPinBackspace() }>
-
-
onPinAdd(4) }>•
-
onPinAdd(5) }>•
-
onPinAdd(6) }>•
+
+
+ this.onPinAdd(7) }>•
+ this.onPinAdd(8) }>•
+ this.onPinAdd(9) }>•
-
-
onPinAdd(1) }>•
-
onPinAdd(2) }>•
-
onPinAdd(3) }>•
+
+ this.onPinAdd(4) }>•
+ this.onPinAdd(5) }>•
+ this.onPinAdd(6) }>•
-
-
-
onPinBackspace() }>⌫
+
+ this.onPinAdd(1) }>•
+ this.onPinAdd(2) }>•
+ this.onPinAdd(3) }>•
-
onPinSubmit(pin) }>Enter
+
+
onPinSubmit(pin) }>Enter pin
+
Not sure how PIN works? Learn more
);
}
diff --git a/src/js/components/modal/RememberDevice.js b/src/js/components/modal/RememberDevice.js
new file mode 100644
index 00000000..c361282f
--- /dev/null
+++ b/src/js/components/modal/RememberDevice.js
@@ -0,0 +1,102 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import Loader from '../common/LoaderCircle';
+
+type Props = {
+ modal: any;
+}
+
+type State = {
+ +countdown: number;
+ ticker?: number;
+}
+
+export default class RememberDevice extends Component {
+
+ state: State;
+
+ constructor(props: any) {
+ super(props);
+
+ this.state = {
+ countdown: 10,
+ }
+ // this.setState({
+ // countdown: 10
+ // });
+ }
+
+ componentDidMount(): void {
+
+ const ticker = () => {
+ if (this.state.countdown - 1 <= 0) {
+ // TODO: possible race condition,
+ // device could be already connected but it didn't emit Device.CONNECT event yet
+ window.clearInterval(this.state.ticker);
+ const { device } = this.props.modal;
+ this.props.modalActions.onForgetDevice(device);
+ } else {
+ this.setState({
+ countdown: this.state.countdown - 1
+ });
+ }
+ }
+
+ this.setState({
+ countdown: 10,
+ ticker: window.setInterval(ticker, 1000)
+ });
+
+
+
+ //this.keyboardHandler = this.keyboardHandler.bind(this);
+ //window.addEventListener('keydown', this.keyboardHandler, false);
+ }
+
+ componentWillUnmount(): void {
+ //window.removeEventListener('keydown', this.keyboardHandler, false);
+ if (this.state.ticker) {
+ window.clearInterval(this.state.ticker);
+ }
+ }
+
+ render(): any {
+ const { device } = this.props.modal;
+ const { onForgetDevice, onRememberDevice } = this.props.modalActions;
+ return (
+
+
Forget { device.label } ?
+
Would you like TREZOR Wallet to forget your device or to remember it, so that it is still visible even while disconnected?
+
onForgetDevice(device) }>Forget
+
onRememberDevice(device) }>Remember
+
+ );
+ }
+}
+
+export const ForgetDevice = (props: any): any => {
+ const { device } = props.modal;
+ const { onForgetSingleDevice, onCancel } = props.modalActions;
+ return (
+
+
Forget { device.label } ?
+
Forgetting only removes the device from the list on the left, your bitcoins are still safe and you can access them by reconnecting your TREZOR again.
+
onForgetSingleDevice(device) }>Forget
+
Don't forget
+
+ );
+}
+
+export const DisconnectDevice = (props: any): any => {
+ const { device } = props.modal;
+ const { onForgetSingleDevice, onCancel } = props.modalActions;
+ return (
+
+
Unplug { device.label }
+
TREZOR Wallet will forget your TREZOR right after you disconnect it.
+
TODO: its not true, actually i've already forget those data!!!
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/wallet/Acquire.js b/src/js/components/wallet/Acquire.js
new file mode 100644
index 00000000..bf8ff741
--- /dev/null
+++ b/src/js/components/wallet/Acquire.js
@@ -0,0 +1,20 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+const Acquire = (props: any): any => {
+ return (
+
+
+
+
Device is used in other window
+
Do you want to use your device in this window?
+
+
props.acquireDevice() }>Acquire device
+
+
+ );
+}
+
+export default Acquire;
diff --git a/src/js/components/wallet/Bootloader.js b/src/js/components/wallet/Bootloader.js
new file mode 100644
index 00000000..c30a8fed
--- /dev/null
+++ b/src/js/components/wallet/Bootloader.js
@@ -0,0 +1,14 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+const Bootloader = (props: any): any => {
+ return (
+
+ );
+}
+
+export default Bootloader;
diff --git a/src/js/components/wallet/Dashboard.js b/src/js/components/wallet/Dashboard.js
new file mode 100644
index 00000000..6f0935f6
--- /dev/null
+++ b/src/js/components/wallet/Dashboard.js
@@ -0,0 +1,19 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+const Dashboard = (props: any): any => {
+ return (
+
+ Dashboard
+
+
Please select your coin
+
You will gain access to recieving & sending selected coin
+
+
+
+ );
+}
+
+export default Dashboard;
diff --git a/src/js/components/History.js b/src/js/components/wallet/History.js
similarity index 70%
rename from src/js/components/History.js
rename to src/js/components/wallet/History.js
index 650b57f0..45f7ebe4 100644
--- a/src/js/components/History.js
+++ b/src/js/components/wallet/History.js
@@ -2,7 +2,6 @@
'use strict';
import React, { Component } from 'react';
-import AddressTab from './AddressTab';
const formatTime = (ts) => {
var date = new Date(ts * 1000);
@@ -35,46 +34,33 @@ const History = (props): any => {
if (pending.length > 0) {
pendingTransactions = pending.map((tx, i) => {
- const etherscanLink = `https://ropsten.etherscan.io/tx/${ tx.txid }`;
+ const etherscanLink = `https://ropsten.etherscan.io/tx/${ tx.hash }`;
return (
Details
-
{ tx.txid }
+
{ tx.to }
Pending...
)
});
}
- const history = JSON.parse(currentAddress.history).result;
- txs = history.map((tx, i) => {
-
+ txs = currentAddress.history.map((tx, i) => {
const etherscanLink = `https://ropsten.etherscan.io/tx/${ tx.hash }`;
- const txType = tx.from === currentAddress.address ? 'out' : 'in';
- const txAddress = txType === 'out' ? tx.to : tx.from;
return (
-
-
-
-
Details
+
+
Details
{ formatTime( parseInt(tx.timeStamp) ) }
-
{ txAddress }
+
{ tx.address }
{ web3.fromWei(tx.value, 'ether') }
- //
- // { `Account #${(address.index + 1 )}` }
- //
{ address.balance } ETH
- //
{ address.address }
- //
)
})
}
return (
-
-
{ pendingTransactions ?
Pending:
diff --git a/src/js/components/wallet/Receive.js b/src/js/components/wallet/Receive.js
new file mode 100644
index 00000000..b39a4a22
--- /dev/null
+++ b/src/js/components/wallet/Receive.js
@@ -0,0 +1,88 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import { QRCode } from 'react-qr-svg';
+import AbstractAccount from './account/AbstractAccount';
+import { Notification } from '../common/Notification';
+import Tooltip from 'rc-tooltip';
+
+export default class Receive extends AbstractAccount {
+ render() {
+ return super.render(this.props.receive) || _render(this.props);
+ }
+}
+
+const _render = (props: any): any => {
+
+ const {
+ checksum,
+ accountIndex,
+ coin,
+ addressVerified,
+ addressUnverified,
+ } = props.receive;
+
+ const device = props.devices.find(d => d.checksum === checksum);
+ const account = props.accounts.find(a => a.checksum === checksum && a.index === accountIndex && a.coin === coin);
+
+ let qrCode = null;
+ let address = `${account.address.substring(0, 20)}...`;
+ let className = 'address hidden';
+ let button = (
+
props.showAddress(account.addressPath) }>
+ Show full address
+
+ );
+
+ if (addressVerified || addressUnverified) {
+ qrCode = (
+
+ );
+ address = account.address;
+ className = addressUnverified ? 'address unverified' : 'address';
+
+ const tooltip = addressUnverified ?
+ (
Unverified address. { device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify it.' }
)
+ :
+ (
{ device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify address.' }
);
+
+ button = (
+
}
+ overlay={ tooltip }
+ placement="bottomRight">
+ props.showAddress(account.addressPath) }>
+
+
+
+ );
+ }
+
+ return (
+
+ { !device.connected ? (
+
+ ) : null }
+ Receive Ethereum or tokens
+
+
+
+ { address }
+
+ { button }
+
+ { qrCode }
+
+ );
+
+
+}
+
diff --git a/src/js/components/wallet/Settings.js b/src/js/components/wallet/Settings.js
new file mode 100644
index 00000000..27d5535e
--- /dev/null
+++ b/src/js/components/wallet/Settings.js
@@ -0,0 +1,12 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+export default (props: any): any => {
+ return (
+
+ );
+}
diff --git a/src/js/components/wallet/SignVerify.js b/src/js/components/wallet/SignVerify.js
new file mode 100644
index 00000000..d07e41d5
--- /dev/null
+++ b/src/js/components/wallet/SignVerify.js
@@ -0,0 +1,29 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+export default (props: any): any => {
+ return (
+
+ );
+}
diff --git a/src/js/components/wallet/account/AbstractAccount.js b/src/js/components/wallet/account/AbstractAccount.js
new file mode 100644
index 00000000..9ad898ee
--- /dev/null
+++ b/src/js/components/wallet/account/AbstractAccount.js
@@ -0,0 +1,80 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import { Notification } from '../../common/Notification';
+
+export default class AbstractAccount extends Component {
+
+ componentDidMount() {
+ this.props.initAccount();
+ }
+
+ componentWillUpdate(newProps: any) {
+ this.props.updateAccount();
+ }
+
+ // shouldInitAccount(newProps: any): boolean {
+ // const locationChanged: boolean = newProps.location.pathname !== this.props.location.pathname;
+ // const accountNotLoaded: boolean = !newProps.detail.loaded && !this.props.detail.loaded;
+ // return (locationChanged || accountNotLoaded);
+ // }
+
+ // shouldUpdateAccount(newProps: any): boolean {
+ // const { detail } = this.props;
+ // const loaded: boolean = detail.loaded;
+
+ // if (detail.address === '') {
+ // const currentAccount = this.props.accounts.find(a => a.index === detail.addressIndex && a.coin === detail.coin && a.checksum === detail.checksum);
+
+ // }
+
+
+ // // return (loaded && );
+ // }
+
+ componentWillUnmount() {
+ this.props.disposeAccount();
+ }
+
+ render(state: any): any {
+
+ const props = this.props;
+
+ if (!state.checksum) {
+ return ();
+ }
+
+ const device = this.props.devices.find(d => d.checksum === state.checksum);
+ const discovery = props.discovery.find(d => d.checksum === device.checksum && d.coin === state.coin);
+ const account = props.accounts.find(a => a.checksum === state.checksum && a.index === state.accountIndex && a.coin === state.coin);
+
+ if (!account) {
+ if (!discovery || discovery.waitingForDevice) {
+ return (
+
+ );
+ } else if (discovery.completed) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/js/components/wallet/account/AccountTabs.js b/src/js/components/wallet/account/AccountTabs.js
new file mode 100644
index 00000000..34f4c086
--- /dev/null
+++ b/src/js/components/wallet/account/AccountTabs.js
@@ -0,0 +1,34 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import { NavLink } from 'react-router-dom';
+
+const AccountTabs = (props: any): any => {
+
+ const urlParams = props.match.params;
+ //const urlParams = props.match ? props.match.params : { address: '0' };
+ const basePath = `/device/${urlParams.device}/coin/${urlParams.coin}/address/${urlParams.address}`;
+
+ return (
+
+ {/*
+ History
+ */}
+
+ Summary
+
+
+ Send
+
+
+ Receive
+
+
+ Sign & Verify
+
+
+ );
+}
+
+export default AccountTabs;
\ No newline at end of file
diff --git a/src/js/components/wallet/aside/AccountSelection.js b/src/js/components/wallet/aside/AccountSelection.js
new file mode 100644
index 00000000..8a8bc53e
--- /dev/null
+++ b/src/js/components/wallet/aside/AccountSelection.js
@@ -0,0 +1,98 @@
+/* @flow */
+'use strict';
+
+import React, { PureComponent } from 'react';
+import { Link, NavLink } from 'react-router-dom';
+import BigNumber from 'bignumber.js';
+
+import { getAccounts } from '../../../utils/reducerUtils';
+import { findSelectedDevice } from '../../../reducers/TrezorConnectReducer';
+import Loader from '../../common/LoaderCircle';
+
+const AccountSelection = (props: any): any => {
+
+ const selected = findSelectedDevice(props.connect);
+ if (!selected) return null;
+
+ const { location } = props.router;
+ const accounts = props.accounts;
+ const baseUrl: string = `/device/${location.params.device}`;
+ const fiatRate = props.fiatRate || '1';
+
+ // console.warn("AccountSelectionRender", selected, props);
+
+ const deviceAddresses: Array = getAccounts(accounts, selected, location.params.coin);
+ let selectedAccounts = deviceAddresses.map((address, i) => {
+ // const url: string = `${baseUrl}/coin/${location.params.coin}/address/${i}`;
+ const url: string = location.pathname.replace(/address+\/([0-9]*)/, `address/${i}`);
+ const b = new BigNumber(address.balance);
+ const fiat = b.times(fiatRate).toFixed(2);
+ const balance = address.balance !== '' ? `${ address.balance } ${ location.params.coin.toUpperCase() } / $${ fiat }` : 'Loading...';
+ return (
+
+ { `Address #${(address.index + 1 )}` }
+ { address.loaded ? balance : "Loading..." }
+
+ )
+ });
+
+ if (selectedAccounts.length < 1) {
+ if (selected.connected) {
+ const url: string = location.pathname.replace(/address+\/([0-9]*)/, `address/0`);
+ selectedAccounts = (
+
+ Address #1
+ Loading...
+
+ )
+ }
+ }
+
+ let discoveryStatus = null;
+ const discovery = props.discovery.find(d => d.checksum === selected.checksum && d.coin === location.params.coin);
+
+ if (discovery) {
+ if (discovery.completed) {
+ // TODO: add only if last one is not empty
+ discoveryStatus = (
+
+ Add address
+
+ )
+ } else if (!selected.connected) {
+ discoveryStatus = (
+
+ Addresses could not be loaded
+ { `Connect ${ selected.instanceLabel } device` }
+
+ )
+ } else {
+ discoveryStatus = (
+
+ Loading accounts...
+
+ )
+ }
+ }
+
+ const { config } = props.localStorage;
+ const selectedCoin = config.coins.find(c => c.shortcut === location.params.coin);
+ let backButton = null;
+ if (selectedCoin) {
+ backButton = (
+
+ { selectedCoin.name }
+
+ );
+ }
+
+ return (
+
+ { backButton }
+ { selectedAccounts }
+ { discoveryStatus }
+
+ );
+}
+
+export default AccountSelection;
\ No newline at end of file
diff --git a/src/js/components/wallet/aside/Aside.js b/src/js/components/wallet/aside/Aside.js
new file mode 100644
index 00000000..52df1446
--- /dev/null
+++ b/src/js/components/wallet/aside/Aside.js
@@ -0,0 +1,82 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import { Link, NavLink } from 'react-router-dom';
+import { TransitionGroup, CSSTransition } from 'react-transition-group';
+
+import { DeviceSelect, DeviceDropdown } from './DeviceSelection';
+import AccountSelection from './AccountSelection';
+import CoinSelection from './CoinSelection';
+import StickyContainer from './StickyContainer';
+import { findSelectedDevice } from '../../../reducers/TrezorConnectReducer';
+
+const TransitionMenu = (props: any) => {
+ return (
+
+ { window.dispatchEvent( new Event('resize') ) } }
+ onExited= { () => window.dispatchEvent( new Event('resize') ) }
+ in={ true }
+ out={ true }
+ classNames={ props.animationType }
+ appear={false}
+ timeout={ 300 }>
+ { props.children }
+
+
+ )
+}
+
+const Aside = (props: any): any => {
+
+ const selected = findSelectedDevice(props.connect);
+ const { location } = props.router;
+
+ if (location.pathname === '/' || !selected) return ();
+
+ // TODO
+ // if (selectedDevice.unacquired) {
+ // return (
+ //
+ // );
+ // }
+
+ let menu = null;
+
+ if (props.deviceDropdownOpened) {
+ menu = ;
+ } else if (location.params.coin) {
+ menu = (
+
+
+
+ );
+ } else if (!selected.unacquired) {
+ menu = (
+
+
+
+ );
+ }
+
+ console.warn("ASIDEE", props)
+
+ return (
+
+
+ { menu }
+
+
+ )
+}
+
+export default Aside;
\ No newline at end of file
diff --git a/src/js/components/wallet/aside/CoinSelection.js b/src/js/components/wallet/aside/CoinSelection.js
new file mode 100644
index 00000000..a28692a4
--- /dev/null
+++ b/src/js/components/wallet/aside/CoinSelection.js
@@ -0,0 +1,49 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import { Link, NavLink } from 'react-router-dom';
+
+const CoinSelection = (props: any): any => {
+ const { location } = props.router;
+ const { config } = props.localStorage;
+
+ const walletCoins = config.coins.map(item => {
+ const url = `${ location.pathname }/coin/${ item.shortcut }/address/0`;
+ const className = `coin ${ item.shortcut }`
+ return (
+
+ { item.name }
+
+ )
+ })
+
+ return (
+
+ );
+}
+
+export default CoinSelection;
\ No newline at end of file
diff --git a/src/js/components/wallet/aside/DeviceSelection.js b/src/js/components/wallet/aside/DeviceSelection.js
new file mode 100644
index 00000000..35efd0d9
--- /dev/null
+++ b/src/js/components/wallet/aside/DeviceSelection.js
@@ -0,0 +1,140 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import Select from 'react-select';
+
+import { findSelectedDevice } from '../../../reducers/TrezorConnectReducer';
+
+
+const Value = (props: any): any => {
+ const device = props.value; // device is passed as value of selected item
+
+ // prevent onMouseDown event
+ const onMouseDown = event => {
+ if (props.onClick) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ const onClick = (item, device) => {
+ if (props.onClick)
+ props.onClick(item, device);
+ }
+
+ let deviceStatus: string = "Connected";
+ let css: string = "device";
+ const deviceMenuItems: Array = [];
+ // deviceMenuItems.push("settings");
+
+ if (device.unacquired) {
+ css += " unacquired";
+ deviceStatus = "Used in other window";
+ }
+ if (device.isUsedElsewhere) {
+ css += " used-elsewhere";
+ deviceStatus = "Used in other window";
+ deviceMenuItems.push("acquire");
+ } else if (device.featuresNeedsReload) {
+ css += " reload-features";
+ //deviceMenuItems.push("acquire");
+ }
+ if (!device.connected) {
+ css += " reload-features";
+ deviceStatus = "Disconnected";
+ }
+
+ if (device.remember) {
+ deviceMenuItems.push("forget");
+ }
+
+ const deviceMenuButtons = deviceMenuItems.map((item, index) => {
+ return (
+ onClick(item, device) }>
+ )
+ });
+ const deviceMenu = deviceMenuButtons.length < 1 ? null : (
+
+ { deviceMenuButtons }
+
+ );
+
+ return (
+
+
+ { device.instanceLabel }
+ { deviceStatus }
+
+ { deviceMenu }
+
+ );
+}
+
+export const DeviceSelect = (props: any): any => {
+
+ const { devices } = props.connect;
+ const selected = findSelectedDevice(props.connect);
+ if (!selected) return null;
+
+ const handleMenuClick = (type, device) => {
+ console.log("handleMenuClick", type, device)
+ if (type === 'acquire') {
+ props.acquireDevice(device);
+ } else if (type === 'forget') {
+ props.forgetDevice(device);
+ }else if (type === 'settings') {
+ props.duplicateDevice(device);
+ }
+ }
+
+ return (
+ props.toggleDeviceDropdown(true) }
+ onClose={ () => props.toggleDeviceDropdown(false) }
+ />
+ );
+}
+
+export const DeviceDropdown = (props: any): any => {
+ const { devices } = props.connect;
+ const selected = findSelectedDevice(props.connect);
+
+ const deviceList: Array = devices.map((dev, index) => {
+ if (dev === selected) return null;
+
+ let deviceStatus: string = "Connected";
+ if (dev.unacquired || dev.isUsedElsewhere) {
+ deviceStatus = "Used in other window";
+ } else if (!dev.connected) {
+ deviceStatus = "Disconnected";
+ }
+ return (
+ props.onSelectDevice(dev) } onTouchStart={ () => props.onSelectDevice(dev) } >
+
+ { dev.instanceLabel }
+ { deviceStatus }
+
+
+ );
+ });
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/wallet/aside/StickyContainer.js b/src/js/components/wallet/aside/StickyContainer.js
new file mode 100644
index 00000000..3f122870
--- /dev/null
+++ b/src/js/components/wallet/aside/StickyContainer.js
@@ -0,0 +1,137 @@
+/* @flow */
+'use strict';
+
+// https://github.com/KyleAMathews/react-headroom/blob/master/src/shouldUpdate.js
+
+import React, { PureComponent } from 'react';
+import raf from 'raf';
+import { DeviceSelect } from './DeviceSelection';
+import { getViewportHeight, getScrollY } from '../../../utils/windowUtils';
+
+export default class StickyContainer extends PureComponent {
+
+ // Class variables.
+ currentScrollY: number = 0;
+ lastKnownScrollY: number = 0;
+ topOffset: number = 0;
+
+ framePending: boolean = false;
+ stickToBottom: boolean = false;
+ top: number = 0;
+ aside;
+ wrapper;
+ subscribers = [];
+
+ handleResize = event => {
+
+ }
+
+ handleScroll = event => {
+ if (!this.framePending) {
+ this.framePending = true;
+ raf(this.update);
+ }
+ }
+
+ shouldUpdate = () => {
+ if (!this.wrapper) return;
+
+ const viewportHeight = getViewportHeight();
+ const helpButton = this.wrapper.querySelector('.help');
+ const helpButtonBounds = helpButton.getBoundingClientRect();
+ const asideBounds = this.aside.getBoundingClientRect();
+ const wrapperBounds = this.wrapper.getBoundingClientRect();
+
+ const scrollDirection = this.currentScrollY >= this.lastKnownScrollY ? 'down' : 'up';
+ const distanceScrolled = Math.abs(this.currentScrollY - this.lastKnownScrollY);
+
+
+ if (asideBounds.top < 0) {
+ this.wrapper.classList.add('fixed');
+ let maxTop = 1;
+ if (wrapperBounds.height > viewportHeight) {
+ const bottomOutOfBounds = (helpButtonBounds.bottom <= viewportHeight && scrollDirection === 'down');
+ const topOutOfBounds = (wrapperBounds.top > 0 && scrollDirection === 'up');
+ if (!bottomOutOfBounds && !topOutOfBounds) {
+ this.topOffset += scrollDirection === 'down' ? - distanceScrolled : distanceScrolled;
+ }
+ maxTop = viewportHeight - wrapperBounds.height;
+ }
+
+ if (this.topOffset > 0) this.topOffset = 0;
+ if (maxTop < 0 && this.topOffset < maxTop) this.topOffset = maxTop;
+ this.wrapper.style.top = `${this.topOffset}px`;
+
+ } else {
+ this.wrapper.classList.remove('fixed');
+ this.wrapper.style.top = `0px`;
+ this.topOffset = 0;
+ }
+
+ if (wrapperBounds.height > viewportHeight) {
+ this.wrapper.classList.remove('fixed-bottom');
+ } else {
+ if (this.wrapper.classList.contains('fixed-bottom')) {
+ if (helpButtonBounds.top < wrapperBounds.bottom - helpButtonBounds.height) {
+ this.wrapper.classList.remove('fixed-bottom');
+ }
+ } else if(helpButtonBounds.bottom < viewportHeight) {
+ this.wrapper.classList.add('fixed-bottom');
+ }
+ }
+
+ this.aside.style.minHeight = `${ wrapperBounds.height }px`;
+ }
+
+ update = () => {
+ this.currentScrollY = getScrollY();
+ this.shouldUpdate();
+ this.framePending = false;
+ this.lastKnownScrollY = this.currentScrollY;
+ }
+
+ componentDidMount() {
+ window.addEventListener('scroll', this.handleScroll);
+ window.addEventListener('resize', this.handleScroll);
+ this.handleScroll(null);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('scroll', this.handleScroll);
+ window.removeEventListener('resize', this.handleScroll);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.location !== prevProps.location) {
+ const asideBounds = this.aside.getBoundingClientRect();
+ if (asideBounds.top < 0) {
+ window.scrollTo(0, getScrollY() + asideBounds.top);
+ this.topOffset = 0;
+ raf(this.update);
+ }
+ }
+
+ if (this.props.devices !== prevProps.devices) {
+ raf(this.update);
+ }
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/js/components/wallet/send/AdvancedForm.js b/src/js/components/wallet/send/AdvancedForm.js
new file mode 100644
index 00000000..4408b344
--- /dev/null
+++ b/src/js/components/wallet/send/AdvancedForm.js
@@ -0,0 +1,132 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import Tooltip from 'rc-tooltip';
+
+const AdvancedForm = (props: any): any => {
+
+ const {
+ coin,
+ token,
+ gasPrice,
+ gasLimit,
+ data,
+ errors,
+ warnings,
+ infos,
+ advanced
+ } = props.sendForm;
+
+ const {
+ toggleAdvanced,
+ onGasPriceChange,
+ onGasLimitChange,
+ onDataChange
+ } = props.sendFormActions;
+
+ if (!advanced) return (
+
+ );
+
+ const gasLimitTooltip = (
+
+ Gas limit is the amount of gas to send with your transaction.
+ TX fee = gas price * gas limit & is paid to miners for including your TX in a block.
+ Increasing this number will not get your TX mined faster.
+ Default value for sending ETH is { gasLimit } WEI.
+
+ );
+
+ const gasPriceTooltip = (
+
+ Gas Price is the amount you pay per unit of gas.
+
TX fee = gas price * gas limit & is paid to miners for including your TX in a block.
+ Higher the gas price = faster transaction, but more expensive. Default is
{ gasPrice } GWEI.
+
Read more
+
+ );
+
+ const dataTooltip = (
+
+ Data is usually used when you send transactions to contracts.
+
+ );
+
+
+
+ return (
+
+
Advanced settings
+
+
+
+ Gas limit
+
}
+ overlay={ gasLimitTooltip }
+ placement="top">
+
+
+
+
onGasLimitChange(event.target.value) } />
+ { errors.gasLimit ? (
{ errors.gasLimit } ) : null }
+ { warnings.gasLimit ? (
{ warnings.gasLimit } ) : null }
+
+
+
+ Gas price
+
}
+ overlay={ gasPriceTooltip }
+ placement="top">
+
+
+
+
onGasPriceChange(event.target.value) } />
+ { errors.gasPrice ? (
{ errors.gasPrice } ) : null }
+
+
+
+
+
+ Data
+
}
+ overlay={ dataTooltip }
+ placement="top">
+
+
+
+
+ { errors.data ? (
{ errors.data } ) : null }
+
+
+
+ { props.children }
+
+
+
+
+
+ )
+}
+
+export default AdvancedForm;
\ No newline at end of file
diff --git a/src/js/components/wallet/send/CoinSelectOption.js b/src/js/components/wallet/send/CoinSelectOption.js
new file mode 100644
index 00000000..d98b0652
--- /dev/null
+++ b/src/js/components/wallet/send/CoinSelectOption.js
@@ -0,0 +1,60 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+
+// export default (props: any): any => {
+// console.log("RENDER CUSTOM OPTION", props)
+// return (
+//
1
+// )
+// }
+
+class FeeSelectOption extends Component {
+ constructor(props) {
+ super(props);
+ }
+
+ handleMouseDown(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.props.onSelect(this.props.option, event);
+ }
+
+ handleMouseEnter(event) {
+ this.props.onFocus(this.props.option, event);
+ }
+
+ handleMouseMove(event) {
+ if (this.props.isFocused) return;
+ this.props.onFocus(this.props.option, event);
+ }
+
+ render() {
+ const css = `${this.props.className} ${this.props.option.value}`;
+ return (
+
+ { this.props.children }
+
+ );
+ }
+}
+
+FeeSelectOption.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ isDisabled: PropTypes.bool,
+ isFocused: PropTypes.bool,
+ isSelected: PropTypes.bool,
+ onFocus: PropTypes.func,
+ onSelect: PropTypes.func,
+ option: PropTypes.object.isRequired,
+};
+
+export default FeeSelectOption;
\ No newline at end of file
diff --git a/src/js/components/wallet/send/FeeSelect.js b/src/js/components/wallet/send/FeeSelect.js
new file mode 100644
index 00000000..6877996a
--- /dev/null
+++ b/src/js/components/wallet/send/FeeSelect.js
@@ -0,0 +1,63 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+
+export const FeeSelectValue = (props: any): any => {
+ return (
+
+
+ { props.value.value }
+ { props.value.label }
+
+
+ );
+}
+
+export class FeeSelectOption extends Component {
+ constructor(props) {
+ super(props);
+ }
+
+ handleMouseDown(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.props.onSelect(this.props.option, event);
+ }
+
+ handleMouseEnter(event) {
+ this.props.onFocus(this.props.option, event);
+ }
+
+ handleMouseMove(event) {
+ if (this.props.isFocused) return;
+ this.props.onFocus(this.props.option, event);
+ }
+
+ render() {
+ return (
+
+ { this.props.option.value }
+ { this.props.option.label }
+
+ );
+ }
+}
+
+FeeSelectOption.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ isDisabled: PropTypes.bool,
+ isFocused: PropTypes.bool,
+ isSelected: PropTypes.bool,
+ onFocus: PropTypes.func,
+ onSelect: PropTypes.func,
+ option: PropTypes.object.isRequired,
+};
+
+
diff --git a/src/js/components/wallet/send/FeeSelectOption.js b/src/js/components/wallet/send/FeeSelectOption.js
new file mode 100644
index 00000000..58481ac0
--- /dev/null
+++ b/src/js/components/wallet/send/FeeSelectOption.js
@@ -0,0 +1,60 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+
+// export default (props: any): any => {
+// console.log("RENDER CUSTOM OPTION", props)
+// return (
+//
1
+// )
+// }
+
+class FeeSelectOption extends Component {
+ constructor(props) {
+ super(props);
+ }
+
+ handleMouseDown(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.props.onSelect(this.props.option, event);
+ }
+
+ handleMouseEnter(event) {
+ this.props.onFocus(this.props.option, event);
+ }
+
+ handleMouseMove(event) {
+ if (this.props.isFocused) return;
+ this.props.onFocus(this.props.option, event);
+ }
+
+ render() {
+ return (
+
+ { this.props.children }
+
+ $10.20 / 8.828392159996002 ETH
+
+ );
+ }
+}
+
+FeeSelectOption.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+ isDisabled: PropTypes.bool,
+ isFocused: PropTypes.bool,
+ isSelected: PropTypes.bool,
+ onFocus: PropTypes.func,
+ onSelect: PropTypes.func,
+ option: PropTypes.object.isRequired,
+};
+
+export default FeeSelectOption;
\ No newline at end of file
diff --git a/src/js/components/wallet/send/PendingTransactions.js b/src/js/components/wallet/send/PendingTransactions.js
new file mode 100644
index 00000000..e69de29b
diff --git a/src/js/components/wallet/send/SendForm.js b/src/js/components/wallet/send/SendForm.js
new file mode 100644
index 00000000..7527344f
--- /dev/null
+++ b/src/js/components/wallet/send/SendForm.js
@@ -0,0 +1,177 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import Select from 'react-select';
+import AdvancedForm from './AdvancedForm';
+import { FeeSelectValue, FeeSelectOption } from './FeeSelect';
+import { Notification } from '../../common/Notification';
+import AbstractAccount from '../account/AbstractAccount';
+
+export default class Send extends AbstractAccount {
+ render() {
+ return super.render(this.props.sendForm) || _render(this.props);
+ }
+}
+
+
+const _render = (props: any): any => {
+
+ const device = props.devices.find(d => d.checksum === props.sendForm.checksum);
+ const discovery = props.discovery.find(d => d.checksum === device.checksum && d.coin === props.sendForm.coin);
+ const account = props.accounts.find(a => a.checksum === props.sendForm.checksum && a.index === props.sendForm.accountIndex && a.coin === props.sendForm.coin);
+ const addressTokens = props.tokens.filter(t => t.ethAddress === account.address);
+
+ const {
+ address,
+ amount,
+ setMax,
+ coin,
+ token,
+ feeLevels,
+ fee,
+ selectedFeeLevel,
+ gasPriceNeedsUpdate,
+ total,
+ errors,
+ warnings,
+ infos,
+ advanced,
+ sending,
+ sendingStatus
+ } = props.sendForm;
+
+ const {
+ onAddressChange,
+ onAmountChange,
+ onSetMax,
+ onCurrencyChange,
+ onFeeLevelChange,
+ updateFeeLevels,
+ onSend,
+ } = props.sendFormActions;
+
+ //const addressTokens = props.tokens.filter(t => t.ethAddress === currentAccount.address);
+ const tokens = addressTokens.map(t => {
+ return { value: t.symbol, label: t.symbol };
+ });
+ tokens.unshift({ value: coin, label: coin.toUpperCase() });
+
+ const setMaxClassName: string = setMax ? 'set-max enabled' : 'set-max';
+
+ let updateFeeLevelsButton = null;
+ if (gasPriceNeedsUpdate) {
+ updateFeeLevelsButton = (
+
Recommended fees updated. Click here to use them
+ )
+ }
+
+ let addressClassName: ?string;
+ if (errors.address) {
+ addressClassName = 'not-valid';
+ } else if (warnings.address) {
+ addressClassName = 'warning';
+ } else if (address.length > 0) {
+ addressClassName = 'valid';
+ }
+
+ let buttonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || address.length === 0 || sending;
+ let buttonLabel: string = 'Send';
+ if (coin !== token && amount.length > 0 && !errors.amount) {
+ buttonLabel += ` ${amount} ${ token.toUpperCase() }`
+ } else if (coin === token && total !== '0') {
+ buttonLabel += ` ${total} ${ token.toUpperCase() }`;
+ }
+
+ //const device = props.devices.find(d => d.checksum === currentAccount.checksum);
+ if (device && !device.connected) {
+ buttonLabel = 'Device is not connected';
+ buttonDisabled = true;
+ }
+
+ let notification = null;
+ // if (sendingStatus) {
+ // if (sendingStatus.success) {
+ // notification = (
);
+ // } else {
+ // notification = (
);
+ // }
+ // }
+
+ return (
+
+
+ { !device.connected ? (
+
+ ) : null }
+
+ Send Ethereum or tokens
+
+ Address
+ onAddressChange(event.target.value) } />
+
+ { errors.address ? ({ errors.address } ) : null }
+ { warnings.address ? ({ warnings.address } ) : null }
+ { infos.address ? ({ infos.address } ) : null }
+
+
+
+
Amount
+
+
onAmountChange(event.target.value) } />
+
+
Set max
+
+
+
+ { errors.amount ? (
{ errors.amount } ) : null }
+ { warnings.amount ? (
{ warnings.amount } ) : null }
+
+
+
+ Fee{ updateFeeLevelsButton }
+
+
+
+
+ onSend() }>{ buttonLabel }
+
+
+
+ );
+}
diff --git a/src/js/components/wallet/summary/Summary.1.js b/src/js/components/wallet/summary/Summary.1.js
new file mode 100644
index 00000000..463c41c1
--- /dev/null
+++ b/src/js/components/wallet/summary/Summary.1.js
@@ -0,0 +1,248 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import BigNumber from 'bignumber.js';
+import ColorHash from 'color-hash';
+import ScaleText from 'react-scale-text';
+import Blockies from 'react-blockies';
+import { Async } from 'react-select';
+import { resolveAfter } from '../../../utils/promiseUtils';
+import AbstractAccount from '../account/AbstractAccount';
+import { Notification } from '../Notification';
+
+
+export default class Summary extends AbstractAccount {
+
+ componentDidMount() {
+ super.componentDidMount();
+ //this.props.summaryActions.init();
+ }
+
+ componentWillUpdate(newProps: any) {
+ super.componentWillUpdate(newProps);
+ //if (newProps.location.pathname !== this.props.location.pathname || (!newProps.summary.loaded && !this.props.summary.loaded)) {
+ //if (newProps.router.pathname !== this.props.router.pathname || (!newProps.summary.loaded && !this.props.summary.loaded)) {
+ // this.props.summaryActions.init();
+ //}
+ }
+
+ componentWillUnmount() {
+ super.componentWillUnmount();
+ //this.props.summaryActions.dispose();
+ }
+
+ render() {
+ return _render(this.props);
+ }
+}
+
+const _render = (props: any): any => {
+
+ const currentAccount = props.account;
+ const fiatRate = props.fiatRate || '1030';
+
+ const {
+ loaded,
+ address,
+ summary,
+ addForm,
+ search,
+ customAddress,
+ customName,
+ customShortcut,
+ customDecimal,
+
+ selectedToken
+ } = props.summary;
+
+ if (currentAccount.deviceStateError) {
+ return (
+
+ );
+ }
+
+
+ // if (!loaded) return null;
+
+ const {
+ onSummaryToggle,
+ onTokenSearch,
+ onCustomTokenToggle,
+ onCustomTokenAddressChange,
+ onCustomTokenNameChange,
+ onCustomTokenShortcutChange,
+ onCustomTokenDecimalChange,
+ onCustomTokenAdd
+ } = props.summaryActions;
+
+ const tokens = props.tokens.filter(t => t.ethAddress === address);
+
+
+ let summaryClassName: string = "summary closed";
+ let summaryContent = null;
+ if (summary) {
+ summaryClassName = "summary";
+ if (currentAccount && currentAccount.balance) {
+
+ const balance = new BigNumber(currentAccount.balance);
+ const fiat = balance.times(fiatRate).toFixed(2);
+
+ summaryContent = (
+
+
+
Balance
+
${ fiat }
+
{ currentAccount.balance } ETH
+
+
+
Rate
+
${ fiatRate }
+
1.00 ETH
+
+
+ )
+ } else {
+ summaryContent = (
+
+
+
Balance
+
Loading...
+
Loading...
+
+
+
Rate
+
${ fiatRate }
+
1.00 ETH
+
+
+ )
+ }
+
+ }
+
+ let addFormClassName = "add-token-form closed";
+ let addFormContent = null;
+ if (addForm) {
+ addFormClassName = "add-token-form";
+ addFormContent = (
+
+ )
+ }
+
+ const bg = new ColorHash({lightness: 0.7});
+ //const colorHash2 = new ColorHash({lightness: 0.5});
+ const colorHash2 = new ColorHash();
+
+ console.log("SUM", tokens, address, props.tokens)
+ //let tokensContent = null;
+ let tokensContent = tokens.map((t, i) => {
+
+ // if (search.length > 0) {
+ // if (t.name.toLowerCase().indexOf(search) < 0 && t.shortcut.toLowerCase().indexOf(search) < 0) return null;
+ // }
+ let iconColor = {
+ color: colorHash2.hex(t.name),
+ background: bg.hex(t.name),
+ borderColor: bg.hex(t.name)
+ }
+ return (
+
+
+
{ t.name }
+
{ t.balance }
+
+ )
+ });
+
+ let ethIcon = null;
+ if (currentAccount) {
+ ethIcon = (
+
+ );
+ }
+
+ return (
+
+
+ { ethIcon } Address #{ parseInt(props.match.params.address) + 1 }
+
+
+
+
+
+
+
+ Add token
+
+ { addFormContent }
+
+
+
+ { tokensContent }
+
+
+
+ );
+}
+
+const onChange = () => {
+}
+
+const gotoUser = () => {
+}
\ No newline at end of file
diff --git a/src/js/components/wallet/summary/Summary.js b/src/js/components/wallet/summary/Summary.js
new file mode 100644
index 00000000..5c583fc7
--- /dev/null
+++ b/src/js/components/wallet/summary/Summary.js
@@ -0,0 +1,74 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import BigNumber from 'bignumber.js';
+import { Async } from 'react-select';
+import { resolveAfter } from '../../../utils/promiseUtils';
+import AbstractAccount from '../account/AbstractAccount';
+import { Notification } from '../../common/Notification';
+import SummaryDetails from './SummaryDetails.js';
+import SummaryTokens from './SummaryTokens.js';
+
+
+export default class Summary extends AbstractAccount {
+ render() {
+ return super.render(this.props.summary) || _render(this.props);
+ }
+}
+
+const _render = (props: any): any => {
+
+ const device = props.devices.find(d => d.checksum === props.summary.checksum);
+ const discovery = props.discovery.find(d => d.checksum === device.checksum && d.coin === props.summary.coin);
+ const account = props.accounts.find(a => a.checksum === props.summary.checksum && a.index === props.summary.accountIndex && a.coin === props.summary.coin);
+ const tokens = props.tokens.filter(t => t.ethAddress === account.address);
+
+ return (
+
+
+ { !device.connected ? (
+
+ ) : null }
+
+ Address #{ parseInt(props.match.params.address) + 1 }
+
+
+
+
+ 0x58cda554935e4a1f2acbe15f8757400af275e084
+
{
+ console.log("TODO: filter already added", opt, str, values);
+ return opt;
+ }
+ }
+
+
+ value={ props.summary.selectedToken }
+ onChange={ token => props.summaryActions.selectToken(token, account) }
+ valueKey="symbol"
+ labelKey="symbol"
+ placeholder="Search for token"
+ loadOptions={ props.summaryActions.loadTokens }
+ backspaceRemoves={true} />
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/js/components/wallet/summary/SummaryContainer.js b/src/js/components/wallet/summary/SummaryContainer.js
new file mode 100644
index 00000000..18d6717f
--- /dev/null
+++ b/src/js/components/wallet/summary/SummaryContainer.js
@@ -0,0 +1,32 @@
+/* @flow */
+'use strict';
+
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import Summary from './Summary';
+import * as SummaryActions from '../../../actions/SummaryActions';
+
+function mapStateToProps(state, own) {
+ return {
+ location: state.router.location,
+ devices: state.connect.devices,
+ accounts: state.accounts,
+ discovery: state.discovery,
+ tokens: state.tokens,
+ summary: state.summary,
+ fiatRate: state.web3.fiatRate
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ summaryActions: bindActionCreators(SummaryActions, dispatch),
+ initAccount: bindActionCreators(SummaryActions.init, dispatch),
+ updateAccount: bindActionCreators(SummaryActions.update, dispatch),
+ disposeAccount: bindActionCreators(SummaryActions.dispose, dispatch),
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Summary)
\ No newline at end of file
diff --git a/src/js/components/wallet/summary/SummaryDetails.js b/src/js/components/wallet/summary/SummaryDetails.js
new file mode 100644
index 00000000..f685f838
--- /dev/null
+++ b/src/js/components/wallet/summary/SummaryDetails.js
@@ -0,0 +1,37 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+
+const SummaryDetails = (props: any): any => {
+
+ if (!props.summary.details) {
+ return (
+
+ )
+ }
+
+ const fiatValue = "0";
+
+ return (
+
+
+
+
Balance
+
${ fiatValue }
+
{ props.balance } ETH
+
+
+
Rate
+
${ props.fiatRate }
+
1.00 ETH
+
+
+
+
+ );
+}
+
+export default SummaryDetails;
\ No newline at end of file
diff --git a/src/js/components/wallet/summary/SummaryTokens.js b/src/js/components/wallet/summary/SummaryTokens.js
new file mode 100644
index 00000000..22c261e3
--- /dev/null
+++ b/src/js/components/wallet/summary/SummaryTokens.js
@@ -0,0 +1,46 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import ColorHash from 'color-hash';
+import ScaleText from 'react-scale-text';
+
+const SummaryTokens = (props: any): any => {
+
+ if (!props.tokens || props.tokens.length < 1) return null;
+
+ const bgColor = new ColorHash({lightness: 0.7});
+ const textColor = new ColorHash();
+
+ const tokens = props.tokens.map((t, i) => {
+
+ // if (search.length > 0) {
+ // if (t.name.toLowerCase().indexOf(search) < 0 && t.shortcut.toLowerCase().indexOf(search) < 0) return null;
+ // }
+ let iconColor = {
+ color: textColor.hex(t.name),
+ background: bgColor.hex(t.name),
+ borderColor: bgColor.hex(t.name)
+ }
+ return (
+
+
+
{ t.name }
+
{ t.balance }
+
+ )
+ });
+
+ return (
+
+ { tokens }
+
+ )
+
+}
+
+export default SummaryTokens;
\ No newline at end of file
diff --git a/src/js/containers/DevicesContainer.js b/src/js/containers/AcquireContainer.js
similarity index 63%
rename from src/js/containers/DevicesContainer.js
rename to src/js/containers/AcquireContainer.js
index 2aa120cb..31f2f6c7 100644
--- a/src/js/containers/DevicesContainer.js
+++ b/src/js/containers/AcquireContainer.js
@@ -5,7 +5,7 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import Devices from '../components/Devices';
+import Acquire from '../components/wallet/Acquire';
import * as TrezorConnectActions from '../actions/TrezorConnectActions';
function mapStateToProps(state, own) {
@@ -15,9 +15,9 @@ function mapStateToProps(state, own) {
}
function mapDispatchToProps(dispatch) {
- return {
- onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch)
+ return {
+ acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
};
}
-export default connect(mapStateToProps, mapDispatchToProps)(Devices);
\ No newline at end of file
+export default connect(mapStateToProps, mapDispatchToProps)(Acquire);
\ No newline at end of file
diff --git a/src/js/containers/AddressMenuContainer.js b/src/js/containers/AddressMenuContainer.js
deleted file mode 100644
index 86b58c3e..00000000
--- a/src/js/containers/AddressMenuContainer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component, PropTypes } from 'react';
-import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
-import AddressMenu from '../components/AddressMenu';
-//import * as AccountActions from '../actions/AccountActions';
-
-function mapStateToProps(state, own) {
- return {
- web3: state.web3.web3,
- addresses: state.addresses
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- //onAccountSelect: bindActionCreators(AccountActions.onAccountSelect, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AddressMenu);
\ No newline at end of file
diff --git a/src/js/containers/AppContainer.js b/src/js/containers/AppContainer.js
deleted file mode 100644
index 686f97a5..00000000
--- a/src/js/containers/AppContainer.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/* @flow */
-'use strict';
-
-import React, { Component } from 'react';
-import { Link } from 'react-router-dom';
-import TrezorConnect from 'trezor-connect';
-
-import Devices from './DevicesContainer';
-import Modal from './ModalContainer';
-
-import Header from '../components/Header';
-import AddressMenuContainer from './AddressMenuContainer';
-import Footer from '../components/Footer';
-
-export default class AppContainer extends Component {
- render() {
- return (
-
-
-
-
-
- { this.props.children }
-
-
-
-
- );
- }
-}
\ No newline at end of file
diff --git a/src/js/containers/AsideContainer.js b/src/js/containers/AsideContainer.js
new file mode 100644
index 00000000..d1a66594
--- /dev/null
+++ b/src/js/containers/AsideContainer.js
@@ -0,0 +1,39 @@
+/* @flow */
+'use strict';
+
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+import Aside from '../components/wallet/aside/Aside';
+import * as TrezorConnectActions from '../actions/TrezorConnectActions';
+import { toggleDeviceDropdown } from '../actions/AppActions';
+
+function mapStateToProps(state, own) {
+ return {
+ connect: state.connect,
+ accounts: state.accounts,
+ router: state.router,
+ deviceDropdownOpened: state.DOM.deviceDropdownOpened,
+ fiatRate: state.web3.fiatRate,
+ localStorage: state.localStorage,
+ discovery: state.discovery
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ //onAccountSelect: bindActionCreators(AccountActions.onAccountSelect, dispatch),
+ toggleDeviceDropdown: bindActionCreators(toggleDeviceDropdown, dispatch),
+ addAddress: bindActionCreators(TrezorConnectActions.addAddress, dispatch),
+ acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
+ forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch),
+ duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch),
+ onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch),
+ };
+}
+
+//export default connect(mapStateToProps, mapDispatchToProps)(AddressMenu);
+export default withRouter(
+ connect(mapStateToProps, mapDispatchToProps)(Aside)
+);
\ No newline at end of file
diff --git a/src/js/containers/ModalContainer.js b/src/js/containers/BootloaderContainer.js
similarity index 50%
rename from src/js/containers/ModalContainer.js
rename to src/js/containers/BootloaderContainer.js
index 67033faa..6db873cf 100644
--- a/src/js/containers/ModalContainer.js
+++ b/src/js/containers/BootloaderContainer.js
@@ -5,19 +5,17 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import Modal from '../components/modal/Modal';
-import * as ModalActions from '../actions/ModalActions';
+import Bootloader from '../components/wallet/Bootloader';
function mapStateToProps(state, own) {
return {
- modal: state.modal
+
};
}
function mapDispatchToProps(dispatch) {
- return {
- modalActions: bindActionCreators(ModalActions, dispatch),
+ return {
};
}
-export default connect(mapStateToProps, mapDispatchToProps)(Modal);
\ No newline at end of file
+export default connect(mapStateToProps, mapDispatchToProps)(Bootloader);
\ No newline at end of file
diff --git a/src/js/containers/ContentContainer.js b/src/js/containers/ContentContainer.js
new file mode 100644
index 00000000..620ebbc6
--- /dev/null
+++ b/src/js/containers/ContentContainer.js
@@ -0,0 +1,45 @@
+/* @flow */
+'use strict';
+
+import React from 'react';
+import { Route } from 'react-router-dom';
+
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+import Log from '../components/common/Log';
+import Notifications from '../components/common/Notification';
+import Footer from '../components/common/Footer';
+import AccountTabs from '../components/wallet/account/AccountTabs';
+
+import * as TrezorConnectActions from '../actions/TrezorConnectActions';
+
+const Article = (props) => {
+ return (
+
+
+
+
+ {/* */}
+
+ { props.children }
+
+
+ );
+}
+
+function mapStateToProps(state, own) {
+ return {
+
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+
+ };
+}
+
+export default withRouter(
+ connect(mapStateToProps, mapDispatchToProps)(Article)
+);
\ No newline at end of file
diff --git a/src/js/containers/DashboardContainer.js b/src/js/containers/DashboardContainer.js
new file mode 100644
index 00000000..b3cd6406
--- /dev/null
+++ b/src/js/containers/DashboardContainer.js
@@ -0,0 +1,21 @@
+/* @flow */
+'use strict';
+
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import Dashboard from '../components/wallet/Dashboard';
+
+function mapStateToProps(state, own) {
+ return {
+
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
\ No newline at end of file
diff --git a/src/js/containers/HistoryContainer.js b/src/js/containers/HistoryContainer.js
index a597b0fa..7723fea9 100644
--- a/src/js/containers/HistoryContainer.js
+++ b/src/js/containers/HistoryContainer.js
@@ -5,13 +5,12 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import History from '../components/History';
+import History from '../components/wallet/History';
import * as SendFormActions from '../actions/SendFormActions';
function mapStateToProps(state, own) {
return {
web3: state.web3.web3,
- addresses: state.addresses,
};
}
diff --git a/src/js/containers/LandingPageContainer.js b/src/js/containers/LandingPageContainer.js
new file mode 100644
index 00000000..e48f66de
--- /dev/null
+++ b/src/js/containers/LandingPageContainer.js
@@ -0,0 +1,25 @@
+/* @flow */
+'use strict';
+
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import LandingPage from '../components/landing/LandingPage';
+
+function mapStateToProps(state, own) {
+ return {
+ localStorage: state.localStorage,
+ modal: state.modal,
+ web3: state.web3,
+ connect: state.connect,
+ router: state.router
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(LandingPage);
\ No newline at end of file
diff --git a/src/js/containers/ReceiveContainer.js b/src/js/containers/ReceiveContainer.js
index 1f22d51d..ec729912 100644
--- a/src/js/containers/ReceiveContainer.js
+++ b/src/js/containers/ReceiveContainer.js
@@ -5,18 +5,26 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import Receive from '../components/Receive';
-import * as SendFormActions from '../actions/SendFormActions';
+import Receive from '../components/wallet/Receive';
+import * as ReceiveActions from '../actions/ReceiveActions';
+import { getAddress } from '../actions/TrezorConnectActions';
function mapStateToProps(state, own) {
return {
- addresses: state.addresses
+ location: state.router.location,
+ devices: state.connect.devices,
+ accounts: state.accounts,
+ discovery: state.discovery,
+ receive: state.receive
};
}
function mapDispatchToProps(dispatch) {
return {
- sendFormActions: bindActionCreators(SendFormActions, dispatch),
+ initAccount: bindActionCreators(ReceiveActions.init, dispatch),
+ updateAccount: bindActionCreators(ReceiveActions.update, dispatch),
+ disposeAccount: bindActionCreators(ReceiveActions.dispose, dispatch),
+ showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch),
};
}
diff --git a/src/js/containers/SendFormContainer.js b/src/js/containers/SendFormContainer.js
index 5708e9db..6c63c3e3 100644
--- a/src/js/containers/SendFormContainer.js
+++ b/src/js/containers/SendFormContainer.js
@@ -5,19 +5,27 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import SendForm from '../components/SendForm';
+import SendForm from '../components/wallet/send/SendForm';
import * as SendFormActions from '../actions/SendFormActions';
function mapStateToProps(state, own) {
return {
- addresses: state.addresses,
- sendForm: state.sendForm
+ location: state.router.location,
+ devices: state.connect.devices,
+ accounts: state.accounts,
+ discovery: state.discovery,
+ tokens: state.tokens,
+ sendForm: state.sendForm,
+ fiatRate: state.web3.fiatRate
};
}
function mapDispatchToProps(dispatch) {
return {
sendFormActions: bindActionCreators(SendFormActions, dispatch),
+ initAccount: bindActionCreators(SendFormActions.init, dispatch),
+ updateAccount: bindActionCreators(SendFormActions.update, dispatch),
+ disposeAccount: bindActionCreators(SendFormActions.dispose, dispatch),
};
}
diff --git a/src/js/containers/SettingsContainer.js b/src/js/containers/SettingsContainer.js
new file mode 100644
index 00000000..c63cc894
--- /dev/null
+++ b/src/js/containers/SettingsContainer.js
@@ -0,0 +1,20 @@
+/* @flow */
+'use strict';
+
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import Settings from '../components/wallet/Settings';
+
+function mapStateToProps(state, own) {
+ return {
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Settings);
\ No newline at end of file
diff --git a/src/js/containers/SignVerifyContainer.js b/src/js/containers/SignVerifyContainer.js
new file mode 100644
index 00000000..8a811e7e
--- /dev/null
+++ b/src/js/containers/SignVerifyContainer.js
@@ -0,0 +1,20 @@
+/* @flow */
+'use strict';
+
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import SignVerify from '../components/wallet/SignVerify';
+
+function mapStateToProps(state, own) {
+ return {
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(SignVerify);
\ No newline at end of file
diff --git a/src/js/containers/TopNavigationContainer.js b/src/js/containers/TopNavigationContainer.js
new file mode 100644
index 00000000..95dd0eb4
--- /dev/null
+++ b/src/js/containers/TopNavigationContainer.js
@@ -0,0 +1,31 @@
+/* @flow */
+'use strict';
+
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import TopNavigation from '../components/TopNavigation';
+import * as TrezorConnectActions from '../actions/TrezorConnectActions';
+import { resizeAppContainer, toggleDeviceDropdown } from '../actions/AppActions';
+
+function mapStateToProps(state, own) {
+ return {
+ connect: state.connect
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ toggleDeviceDropdown: bindActionCreators(toggleDeviceDropdown, dispatch),
+ resizeAppContainer: bindActionCreators(resizeAppContainer, dispatch),
+ acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
+ onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch),
+ };
+}
+
+// export default connect(mapStateToProps, mapDispatchToProps)(TopNavigation);
+export default withRouter(
+ connect(mapStateToProps, mapDispatchToProps)(TopNavigation)
+);
\ No newline at end of file
diff --git a/src/js/containers/WalletContainer.js b/src/js/containers/WalletContainer.js
new file mode 100644
index 00000000..d05ef8fe
--- /dev/null
+++ b/src/js/containers/WalletContainer.js
@@ -0,0 +1,40 @@
+/* @flow */
+'use strict';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import Header from '../components/common/Header';
+import AsideContainer from './AsideContainer';
+import ContentContainer from './ContentContainer';
+import ModalContainer from '../components/modal/ModalContainer';
+
+const Wallet = (props: any): any => {
+ return (
+
+
+
+
+
+ { props.children }
+
+
+
+
+ );
+}
+
+function mapStateToProps(state, own) {
+ return {
+
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return { };
+}
+
+export default withRouter(
+ connect(mapStateToProps, mapDispatchToProps)(Wallet)
+);
diff --git a/src/js/containers/index.js b/src/js/containers/index.js
index 64fe942f..0adc537c 100644
--- a/src/js/containers/index.js
+++ b/src/js/containers/index.js
@@ -1,7 +1,19 @@
/* @flow */
'use strict';
-export { default as AppContainer } from './AppContainer';
+// wrapper layouts
+export { default as LandingPageContainer } from './LandingPageContainer';
+export { default as WalletContainer } from './WalletContainer';
+
+// wallet sections
+export { default as AcquireContainer } from './AcquireContainer';
+export { default as BootloaderContainer } from './BootloaderContainer';
+
+export { default as DashboardContainer } from './DashboardContainer';
export { default as HistoryContainer } from './HistoryContainer';
export { default as SendFormContainer } from './SendFormContainer';
export { default as ReceiveContainer } from './ReceiveContainer';
+export { default as SignVerifyContainer } from './SignVerifyContainer';
+
+
+export { default as SettingsContainer } from './SettingsContainer';
diff --git a/src/js/index.js b/src/js/index.js
index 66c69b6b..6b77704f 100644
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -3,26 +3,25 @@
import React from 'react';
import { render } from 'react-dom';
-import { Provider } from 'react-redux';
-import { ConnectedRouter } from 'react-router-redux';
-import store, { history } from './store';
+import store from './store';
import router from './router';
-import { onResize } from './actions/DOMActions';
+import { onResize, onBeforeUnload } from './actions/AppActions';
import styles from '../styles/index.less';
render(
-
-
- { router }
-
- ,
+ router,
document.getElementById('root')
);
+// handle resize event and pass it to DOM reducer
window.addEventListener('resize', () => {
- store.dispatch(onResize());
+ store.dispatch( onResize() );
});
+window.onbeforeunload = () => {
+ store.dispatch( onBeforeUnload() );
+}
+
// workaround
// yarn add web3@^0.19.0
\ No newline at end of file
diff --git a/src/js/reducers/AccountDetailReducer.js b/src/js/reducers/AccountDetailReducer.js
new file mode 100644
index 00000000..5996c4f3
--- /dev/null
+++ b/src/js/reducers/AccountDetailReducer.js
@@ -0,0 +1,42 @@
+/* @flow */
+'use strict';
+
+import * as ACCOUNT from '../actions/constants/account';
+import * as CONNECT from '../actions/constants/TrezorConnect';
+
+export type State = {
+ +index: number;
+ +checksum: ?string;
+ +coin: string;
+ location: string;
+}
+
+export const initialState: State = {
+ index: 0,
+ checksum: null,
+ coin: '',
+};
+
+
+export default (state: State = initialState, action: any): State => {
+
+ switch (action.type) {
+
+ case ACCOUNT.INIT :
+ return action.state;
+
+ case CONNECT.DEVICE_STATE_EXCEPTION :
+ return {
+ ...state,
+ deviceStateError: true
+ }
+
+ case ACCOUNT.DISPOSE :
+ return initialState;
+
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/AccountsReducer.js b/src/js/reducers/AccountsReducer.js
new file mode 100644
index 00000000..f355e482
--- /dev/null
+++ b/src/js/reducers/AccountsReducer.js
@@ -0,0 +1,96 @@
+/* @flow */
+'use strict';
+
+import * as CONNECT from '../actions/constants/TrezorConnect';
+import * as ADDRESS from '../actions/constants/Address';
+
+export type Account = {
+ loaded: boolean;
+ +checksum: string;
+ +coin: string;
+ +index: number;
+ +addressPath: Array
;
+ +address: string;
+ balance: string;
+ nonce: number;
+}
+
+const initialState: Array = [];
+
+const createAccount = (state: Array, action: any): Array => {
+
+ // TODO check witch checksum
+ // check if account was created before
+ const exist: ?Account = state.find(addr => addr.address === action.address);
+ if (exist) {
+ return state;
+ }
+
+ const address: Account = {
+ loaded: false,
+ checksum: action.device.checksum,
+ coin: action.coin,
+ index: action.index,
+ addressPath: action.path,
+ address: action.address,
+ balance: '0',
+ nonce: 0,
+ }
+
+ const newState: Array = [ ...state ];
+ newState.push(address);
+ return newState;
+}
+
+const removeAccounts = (state: Array, action: any): Array => {
+ // TODO: all instances od device (multiple checksums)
+ return state.filter(addr => addr.checksum !== action.device.checksum);
+}
+
+const forgetAccounts = (state: Array, action: any): Array => {
+ return state.filter(addr => action.accounts.indexOf(addr) === -1);
+}
+
+const setBalance = (state: Array, action: any): Array => {
+ const index: number = state.findIndex(addr => addr.address === action.address);
+ const newState: Array = [ ...state ];
+ newState[index].loaded = true;
+ newState[index].balance = action.balance;
+ return newState;
+}
+
+const setNonce = (state: Array, action: any): Array => {
+ const index: number = state.findIndex(addr => addr.address === action.address);
+ const newState: Array = [ ...state ];
+ newState[index].loaded = true;
+ newState[index].nonce = action.nonce;
+ return newState;
+}
+
+export default (state: Array = initialState, action: any): Array => {
+
+ switch (action.type) {
+
+ case ADDRESS.CREATE :
+ return createAccount(state, action);
+
+ case CONNECT.FORGET :
+ case CONNECT.FORGET_SINGLE :
+ return removeAccounts(state, action);
+
+ //case CONNECT.FORGET_SINGLE :
+ // return forgetAccounts(state, action);
+
+ case ADDRESS.SET_BALANCE :
+ return setBalance(state, action);
+ case ADDRESS.SET_NONCE :
+ return setNonce(state, action);
+
+ case ADDRESS.FROM_STORAGE :
+ return action.payload;
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/AddressesReducer.js b/src/js/reducers/AddressesReducer.js
deleted file mode 100644
index ff6dfea1..00000000
--- a/src/js/reducers/AddressesReducer.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/* @flow */
-'use strict';
-
-import HDKey from 'hdkey';
-import EthereumjsUtil from 'ethereumjs-util';
-
-import * as ACTIONS from '../actions';
-import BigNumber from 'bignumber.js';
-import { access, stat } from 'fs';
-
-type AddressType = {
- devicePath: string;
- index: number;
- path: Array;
- address: string;
-}
-
-type State = {
- addresses: Array;
- pendingTxs: Array;
-}
-
-const initialState: State = {
- addresses: [],
- pendingTxs: [],
-};
-
-export class Address {
-
- devicePath: string;
- index: number;
- path: Array;
- address: string;
- balance: string;
- pendingTx: Array;
- history: JSON;
-
- constructor(devicePath: string, index: number, path: Array, address: string) {
- this.devicePath = devicePath;
- this.index = index;
- this.path = path;
- this.address = address;
- this.pendingTx = [];
- }
-
- setBalance(balance: string): void {
- this.balance = balance;
- }
-
- setHistory(json): void {
- this.history = json;
- }
-
- addPendingTx(txid, txData): void {
- this.pendingTx.push({
- txid,
- txData
- });
- }
-
- findPendingTx(txid): boolean {
- let index = this.pendingTx.findIndex(tx => tx.txid === txid);
- return index >= 0;
- }
-
- removePendingTx(txid): void {
- let index = this.pendingTx.findIndex(tx => tx.txid === txid);
- if (index >= 0) {
- this.pendingTx.splice(index, 1);
- console.error("-----> RMEOVE PENDiNG TX", this.pendingTx)
- }
- }
-}
-
-export default (state: State = initialState, action: any): State => {
-
- switch (action.type) {
-
- case ACTIONS.ON_TX_COMPLETE :
- action.address.addPendingTx(action.txid, action.txData);
- state.pendingTxs.push(action.txid);
- return {
- ...state,
- }
-
- case ACTIONS.TX_STATUS_OK :
- for (let addr of state.addresses) {
- addr.removePendingTx(action.txid);
- }
- let pendingIndex = state.pendingTxs.indexOf(action.txid);
- if (pendingIndex >= 0) {
- state.pendingTxs.splice(pendingIndex, 1);
- }
- return {
- ...state,
- }
-
- break;
-
- case ACTIONS.ADDRESS_CREATE :
- return {
- ...state,
- addresses: state.addresses.concat([ action.address ])
- }
-
- case ACTIONS.ADDRESS_SET_BALANCE :
- action.address.setBalance( action.balance );
- return {
- ...state,
- }
-
- case ACTIONS.ADDRESS_SET_HISTORY :
- action.address.setHistory( action.history );
- return {
- ...state,
- }
-
- case ACTIONS.ADDRESS_DELETE :
- return {
- ...state,
- addresses: action.addresses
- }
- default:
- return state;
- }
-
-}
\ No newline at end of file
diff --git a/src/js/reducers/AppReducer.js b/src/js/reducers/AppReducer.js
new file mode 100644
index 00000000..a033b40a
--- /dev/null
+++ b/src/js/reducers/AppReducer.js
@@ -0,0 +1,49 @@
+/* @flow */
+'use strict';
+
+import { ON_RESIZE, TOGGLE_DEVICE_DROPDOWN, RESIZE_CONTAINER } from '../actions/AppActions';
+import * as WEB3 from '../actions/constants/Web3';
+
+const WIDTH: number = 1080;
+const HEIGHT: number = 1920;
+
+const initialState: Object = {
+ orginalWidth: WIDTH,
+ orginalHeight: HEIGHT,
+ width: window.innerWidth,
+ height: window.innerHeight,
+ scale: Math.min(window.innerWidth / WIDTH, window.innerHeight / HEIGHT),
+ coinDropdownOpened: false,
+ deviceDropdownOpened: false,
+ initialized: false,
+ landingPage: true,
+};
+
+export default function DOM(state: Object = initialState, action: Object): any {
+ switch(action.type) {
+ case ON_RESIZE :
+ return {
+ ...state,
+ scale: Math.min(window.innerWidth / WIDTH, window.innerHeight / HEIGHT),
+ }
+ case RESIZE_CONTAINER :
+ return {
+ ...state,
+ coinDropdownOpened: action.opened
+ }
+ case TOGGLE_DEVICE_DROPDOWN :
+ return {
+ ...state,
+ deviceDropdownOpened: action.opened
+ }
+
+ case WEB3.READY :
+ return {
+ ...state,
+ initialized: true
+ }
+
+ default:
+ return state;
+ }
+}
diff --git a/src/js/reducers/DOMReducer.js b/src/js/reducers/DOMReducer.js
deleted file mode 100644
index 4c317991..00000000
--- a/src/js/reducers/DOMReducer.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* @flow */
-'use strict';
-
-import { ON_RESIZE } from '../actions/DOMActions';
-
-const WIDTH: number = 1080;
-const HEIGHT: number = 1920;
-
-const initialState: Object = {
- orginalWidth: WIDTH,
- orginalHeight: HEIGHT,
- width: window.innerWidth,
- height: window.innerHeight,
- scale: Math.min(window.innerWidth / WIDTH, window.innerHeight / HEIGHT),
-};
-
-export default function DOM(state: Object = initialState, action: Object): void {
- switch(action.type) {
- case ON_RESIZE :
- return {
- ...state,
- scale: Math.min(window.innerWidth / WIDTH, window.innerHeight / HEIGHT),
- }
- default:
- return state;
- }
-}
diff --git a/src/js/reducers/DiscoveryReducer.js b/src/js/reducers/DiscoveryReducer.js
new file mode 100644
index 00000000..446830ed
--- /dev/null
+++ b/src/js/reducers/DiscoveryReducer.js
@@ -0,0 +1,138 @@
+/* @flow */
+'use strict';
+
+import * as DISCOVERY from '../actions/constants/Discovery';
+import * as ADDRESS from '../actions/constants/Address';
+import * as CONNECT from '../actions/constants/TrezorConnect';
+
+export type Discovery = {
+ coin: string;
+ checksum: string;
+ xpub: string;
+ accountIndex: number;
+ interrupted: boolean;
+ completed: boolean;
+ waitingForDevice: boolean;
+ waitingForAuth?: boolean;
+}
+
+const initialState: Array = [];
+
+const start = (state: Array, action: any): Array => {
+
+ const instance: Discovery = {
+ coin: action.coin,
+ xpub: action.xpub,
+ hdKey: action.hdKey,
+ basePath: action.basePath,
+ checksum: action.device.checksum,
+ accountIndex: 0,
+ interrupted: false,
+ completed: false,
+ waitingForDevice: false
+ }
+
+ const newState: Array = [ ...state ];
+ const index: number = state.findIndex(d => {
+ return d.coin === action.coin && d.checksum === action.device.checksum;
+ });
+
+ console.warn("START DISCO", index);
+
+ if (index >= 0) {
+ newState[index] = instance;
+ } else {
+ newState.push(instance);
+ }
+ return newState;
+}
+
+const complete = (state: Array, action: any): Array => {
+ const index: number = state.findIndex(d => {
+ return d.coin === action.coin && d.checksum === action.device.checksum;
+ });
+ const newState: Array = [ ...state ];
+ newState[index].completed = true;
+ return newState;
+}
+
+const addressCreate = (state: Array, action: any): Array => {
+ const index: number = state.findIndex(d => {
+ return d.coin === action.coin && d.checksum === action.device.checksum;
+ });
+ const newState: Array = [ ...state ];
+ newState[index].accountIndex++;
+ return newState;
+}
+
+const forgetDiscovery = (state: Array, action: any): Array => {
+ return state.filter(d => d.checksum !== action.device.checksum);
+}
+
+const stop = (state: Array, action: any): Array => {
+ const newState: Array = [ ...state ];
+ return newState.map( (d: Discovery) => {
+ if (d.checksum === action.device.checksum && !d.completed) {
+ d.interrupted = true;
+ d.waitingForDevice = false;
+ }
+ return d;
+ });
+}
+
+const waiting = (state: Array, action: any): Array => {
+
+ const instance: Discovery = {
+ coin: action.coin,
+ checksum: action.device.checksum,
+ xpub: '',
+ accountIndex: 0,
+ interrupted: false,
+ completed: false,
+ waitingForDevice: true
+ }
+
+ const index: number = state.findIndex(d => {
+ return d.coin === action.coin && d.checksum === action.device.checksum;
+ });
+
+ const newState: Array = [ ...state ];
+ if (index >= 0) {
+ newState[index] = instance;
+ } else {
+ newState.push(instance);
+ }
+
+ return newState;
+}
+
+export default function discovery(state: Array = initialState, action: any): any {
+
+ switch (action.type) {
+ case DISCOVERY.START :
+ return start(state, action);
+ case ADDRESS.CREATE :
+ return addressCreate(state, action);
+ case DISCOVERY.STOP :
+ return stop(state, action);
+ case DISCOVERY.COMPLETE :
+ return complete(state, action);
+ case DISCOVERY.WAITING :
+ return waiting(state, action)
+ case DISCOVERY.FROM_STORAGE :
+ return action.payload.map(d => {
+ return {
+ ...d,
+ interrupted: false,
+ waitingForDevice: false
+ }
+ })
+ case CONNECT.FORGET :
+ case CONNECT.FORGET_SINGLE :
+ return forgetDiscovery(state, action);
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/LocalStorageReducer.js b/src/js/reducers/LocalStorageReducer.js
new file mode 100644
index 00000000..4e1e2481
--- /dev/null
+++ b/src/js/reducers/LocalStorageReducer.js
@@ -0,0 +1,47 @@
+/* @flow */
+'use strict';
+
+import * as STORAGE from '../actions/constants/LocalStorage';
+
+type State = {
+ initialized: boolean;
+ error: any;
+ config: any;
+ ethERC20: any;
+ ethTokens: any;
+}
+
+const initialState: State = {
+ initialized: false,
+ error: null,
+ config: null,
+ ethERC20: null,
+ ethTokens: null,
+};
+
+export default function localStorage(state: State = initialState, action: any): any {
+
+ switch (action.type) {
+
+ case STORAGE.READY :
+ return {
+ ...state,
+ initialized: true,
+ config: action.appConfig,
+ ethERC20: action.ethERC20,
+ ethTokens: action.ethTokens,
+ error: null
+ }
+
+ case STORAGE.ERROR :
+ return {
+ ...state,
+ error: action.error
+ }
+
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/ModalReducer.js b/src/js/reducers/ModalReducer.js
index b84e572f..c4a35633 100644
--- a/src/js/reducers/ModalReducer.js
+++ b/src/js/reducers/ModalReducer.js
@@ -3,116 +3,105 @@
import { UI, DEVICE } from 'trezor-connect';
import * as ACTIONS from '../actions';
+import * as RECEIVE from '../actions/constants/receive';
+import * as MODAL from '../actions/constants/Modal';
+import * as CONNECT from '../actions/constants/TrezorConnect';
type ModalState = {
opened: boolean;
+ device: any;
windowType: ?string;
- pin: string;
- passphrase: string;
- passphraseFocused: boolean;
- passphraseVisible: boolean;
- passphraseCached: boolean;
- confirmation: ?string;
}
const initialState: ModalState = {
opened: false,
- windowType: null,
- pin: "",
- passphrase: "",
- passphraseFocused: false,
- passphraseVisible: false,
- passphraseCached: true,
- confirmation: null,
+ device: null,
+ windowType: null
};
export default function modal(state: ModalState = initialState, action: any): any {
switch (action.type) {
- case UI.REQUEST_PIN :
- case UI.INVALID_PIN :
- case UI.REQUEST_PASSPHRASE :
+ case RECEIVE.REQUEST_UNVERIFIED :
return {
...state,
opened: true,
windowType: action.type
- };
+ }
- case UI.REQUEST_CONFIRMATION :
+ case CONNECT.REMEMBER_REQUEST :
+ case CONNECT.FORGET_REQUEST :
+ case CONNECT.DISCONNECT_REQUEST :
return {
...state,
+ device: action.device,
opened: true,
- confirmation: action.data.label,
windowType: action.type
};
- case UI.REQUEST_PERMISSION :
+ case CONNECT.TRY_TO_DUPLICATE :
return {
...state,
+ device: action.device,
opened: true,
- confirmation: action.data.label,
windowType: action.type
};
- case ACTIONS.CLOSE_MODAL :
- return {
- ...initialState,
- passphraseCached: state.passphraseCached
- };
-
+ case DEVICE.CHANGED :
+ if (state.opened && state.device && action.device.path === state.device.path && action.device.isUsedElsewhere) {
+ return {
+ ...initialState,
+ };
+ }
+ return state;
- case ACTIONS.ON_PIN_ADD :
- let pin: string = state.pin;
- if (pin.length < 9) {
- pin += action.number;
+ case DEVICE.DISCONNECT :
+ if (state.device && action.device.path === state.device.path) {
+ return {
+ ...initialState,
+ }
}
- return {
- ...state,
- pin: pin,
- };
- case ACTIONS.ON_PIN_BACKSPACE :
- return {
- ...state,
- pin: state.pin.substring(0, state.pin.length - 1),
- };
+ return state;
+ // case DEVICE.CONNECT :
+ // case DEVICE.CONNECT_UNACQUIRED :
+ // if (state.opened && state.windowType === CONNECT.TRY_TO_FORGET) {
+ // return {
+ // ...initialState,
+ // passphraseCached: state.passphraseCached
+ // }
+ // }
+ // return state;
- case ACTIONS.ON_PASSPHRASE_CHANGE :
- return {
- ...state,
- passphrase: action.value
- }
- case ACTIONS.ON_PASSPHRASE_SHOW :
- return {
- ...state,
- passphraseVisible: true
- }
- case ACTIONS.ON_PASSPHRASE_HIDE :
- return {
- ...state,
- passphraseVisible: false
- }
- case ACTIONS.ON_PASSPHRASE_SAVE :
- return {
- ...state,
- passphraseCached: true
- }
- case ACTIONS.ON_PASSPHRASE_FORGET :
+ case UI.REQUEST_PIN :
+ case UI.INVALID_PIN :
+ case UI.REQUEST_PASSPHRASE :
return {
...state,
- passphraseCached: false
- }
- case ACTIONS.ON_PASSPHRASE_FOCUS :
+ device: action.data.device,
+ opened: true,
+ windowType: action.type
+ };
+
+ case UI.REQUEST_BUTTON :
return {
...state,
- passphraseFocused: true
+ opened: true,
+ windowType: action.data.code
}
- case ACTIONS.ON_PASSPHRASE_BLUR :
+
+ case UI.CLOSE_UI_WINDOW :
+ case ACTIONS.CLOSE_MODAL :
+
+ case CONNECT.FORGET :
+ case CONNECT.FORGET_SINGLE :
+ case CONNECT.REMEMBER :
return {
- ...state,
- passphraseFocused: false
- }
+ ...initialState,
+ };
+
+
default:
return state;
diff --git a/src/js/reducers/NotificationReducer.js b/src/js/reducers/NotificationReducer.js
new file mode 100644
index 00000000..b0edf133
--- /dev/null
+++ b/src/js/reducers/NotificationReducer.js
@@ -0,0 +1,68 @@
+/* @flow */
+'use strict';
+
+import { LOCATION_CHANGE } from 'react-router-redux';
+import * as NOTIFICATION from '../actions/constants/notification';
+
+type NotificationAction = {
+ label: string;
+ callback: any;
+}
+
+type NotificationEntry = {
+ +id: ?string;
+ +type: string;
+ +title: string;
+ +message: string;
+ +cancelable: boolean;
+ +actions: Array;
+}
+
+const initialState: Array = [
+ // {
+ // id: undefined,
+ // type: "info",
+ // title: "Some static notification",
+ // message: "This one is not cancelable",
+ // cancelable: false,
+ // actions: []
+ // }
+];
+
+const addNotification = (state: Array, payload: any): Array => {
+ const newState: Array = state.filter(e => !e.cancelable);
+ newState.push({
+ id: payload.id,
+ type: payload.type,
+ title: payload.title.toString(),
+ message: payload.message.toString(),
+ cancelable: payload.cancelable,
+ actions: payload.actions
+ });
+
+ // TODO: sort
+ return newState;
+}
+
+const closeNotification = (state: Array, payload: any): Array => {
+ if (payload && typeof payload.id === 'string') {
+ return state.filter(e => e.id !== payload.id);
+ } else {
+ return state.filter(e => !e.cancelable);
+ }
+}
+
+export default function notification(state: Array = initialState, action: Object): Array {
+ switch(action.type) {
+
+ case NOTIFICATION.ADD :
+ return addNotification(state, action.payload);
+
+ case LOCATION_CHANGE :
+ case NOTIFICATION.CLOSE :
+ return closeNotification(state, action.payload);
+
+ default:
+ return state;
+ }
+}
diff --git a/src/js/reducers/ReceiveReducer.js b/src/js/reducers/ReceiveReducer.js
new file mode 100644
index 00000000..40819ed5
--- /dev/null
+++ b/src/js/reducers/ReceiveReducer.js
@@ -0,0 +1,51 @@
+/* @flow */
+'use strict';
+
+import * as RECEIVE from '../actions/constants/receive';
+
+export type State = {
+ +checksum: ?string;
+ +accountIndex: ?number;
+ +coin: ?string;
+ location: string;
+ addressVerified: boolean;
+ adressUnverified: boolean;
+}
+
+export const initialState: State = {
+ checksum: null,
+ accountIndex: null,
+ coin: null,
+ location: '',
+ addressVerified: false,
+ addressUnverified: false,
+};
+
+export default (state: State = initialState, action: any): State => {
+
+ switch (action.type) {
+
+ case RECEIVE.INIT :
+ return action.state;
+
+ case RECEIVE.DISPOSE :
+ return initialState;
+
+ case RECEIVE.SHOW_ADDRESS :
+ return {
+ ...state,
+ addressVerified: true,
+ addressUnverified: false
+ }
+ case RECEIVE.SHOW_UNVERIFIED_ADDRESS :
+ return {
+ ...state,
+ addressVerified: false,
+ addressUnverified: true
+ }
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/SendFormReducer.1.js b/src/js/reducers/SendFormReducer.1.js
new file mode 100644
index 00000000..693d27c3
--- /dev/null
+++ b/src/js/reducers/SendFormReducer.1.js
@@ -0,0 +1,199 @@
+/* @flow */
+'use strict';
+
+import { LOCATION_CHANGE } from 'react-router-redux';
+import * as SEND from '../actions/constants/SendForm';
+import * as WEB3 from '../actions/constants/Web3';
+import * as ADDRESS from '../actions/constants/Address';
+import EthereumjsUnits from 'ethereumjs-units';
+import BigNumber from 'bignumber.js';
+import { getFeeLevels } from '../actions/SendFormActions';
+
+export type State = {
+ +senderAddress: ?string;
+ +coin: string;
+ token: string;
+ balance: string;
+ tokenBalance: string;
+ balanceNeedUpdate: boolean;
+
+
+ // form fields
+ advanced: boolean;
+ untouched: boolean; // set to true when user made some changes in form
+ touched: {[k: string]: boolean};
+ address: string;
+ amount: string;
+ setMax: boolean;
+ feeLevels: Array;
+ selectedFeeLevel: ?FeeLevel;
+ recommendedGasPrice: string;
+ gasPriceNeedsUpdate: boolean;
+ gasLimit: string;
+ gasPrice: string;
+ data: string;
+ nonce: string;
+ total: string;
+ sending: boolean;
+ sendingStatus: ?SendStatus;
+ errors: {[k: string]: string};
+ warnings: {[k: string]: string};
+ infos: {[k: string]: string};
+}
+
+export type FeeLevel = {
+ label: string;
+ gasPrice: string;
+ value: string;
+}
+
+type SendStatus = {
+ success: boolean;
+ message: string;
+}
+
+export const initialState: State = {
+ senderAddress: null,
+ coin: '',
+ token: '',
+ advanced: false,
+ untouched: true,
+ touched: {},
+ balance: '0',
+ tokenBalance: '0',
+ balanceNeedUpdate: false,
+ //address: '',
+ address: '0x574BbB36871bA6b78E27f4B4dCFb76eA0091880B',
+ amount: '',
+ setMax: false,
+ feeLevels: [],
+ selectedFeeLevel: null,
+ recommendedGasPrice: '0',
+ gasPriceNeedsUpdate: false,
+ gasLimit: '0',
+ gasPrice: '0',
+ data: '',
+ nonce: '0',
+ total: '0',
+ sending: false,
+ sendingStatus: null,
+ errors: {},
+ warnings: {},
+ infos: {},
+}
+
+
+const onGasPriceUpdated = (state: State, action: any): State => {
+
+ function getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+ const newPrice = getRandomInt(10, 50).toString();
+ //const newPrice = EthereumjsUnits.convert(action.gasPrice, 'wei', 'gwei');
+ if (action.coin === state.coin && newPrice !== state.recommendedGasPrice) {
+ const newState: State = { ...state };
+ if (!state.untouched) {
+ newState.gasPriceNeedsUpdate = true;
+ newState.recommendedGasPrice = newPrice;
+ } else {
+ const newFeeLevels = getFeeLevels(state.coin, newPrice, state.gasLimit);
+ const selectedFeeLevel = newFeeLevels.find(f => f.value === 'Normal');
+ newState.recommendedGasPrice = newPrice;
+ newState.feeLevels = newFeeLevels;
+ newState.selectedFeeLevel = selectedFeeLevel;
+ newState.gasPrice = selectedFeeLevel.gasPrice;
+ }
+ return newState;
+ }
+ return state;
+}
+
+const onBalanceUpdated = (state: State, action: any): State => {
+ // balanceNeedUpdate
+ if (state.senderAddress === action.address) {
+ return {
+ ...state,
+ balance: '1'
+ }
+ }
+ return state;
+}
+
+
+export default (state: State = initialState, action: any): State => {
+
+ switch (action.type) {
+
+ case SEND.INIT :
+ return action.state;
+
+ case SEND.DISPOSE :
+ return initialState;
+
+ // this will be called right after Web3 instance initialization before any view is shown
+ // and async during app live time
+ case WEB3.GAS_PRICE_UPDATED :
+ return onGasPriceUpdated(state, action);
+
+ case ADDRESS.SET_BALANCE :
+ return onBalanceUpdated(state, action);
+
+ case SEND.TOGGLE_ADVANCED :
+ return {
+ ...state,
+ advanced: !state.advanced
+ }
+
+
+ // user actions
+ case SEND.ADDRESS_CHANGE :
+ case SEND.AMOUNT_CHANGE :
+ case SEND.SET_MAX :
+ case SEND.CURRENCY_CHANGE :
+ case SEND.FEE_LEVEL_CHANGE :
+ case SEND.UPDATE_FEE_LEVELS :
+ case SEND.GAS_PRICE_CHANGE :
+ case SEND.GAS_LIMIT_CHANGE :
+ case SEND.DATA_CHANGE :
+ return action.state;
+
+ case SEND.SEND :
+ return {
+ ...state,
+ sending: true,
+ sendingStatus: null,
+ }
+
+ case SEND.TX_COMPLETE :
+ return {
+ ...state,
+ sending: false,
+ sendingStatus: {
+ success: true,
+ message: action.txid
+ }
+ }
+ case SEND.TX_ERROR :
+ return {
+ ...state,
+ sending: false,
+ sendingStatus: {
+ success: false,
+ message: action.response
+ }
+ }
+
+
+ case SEND.VALIDATION :
+ return {
+ ...state,
+ errors: action.errors,
+ warnings: action.warnings,
+ infos: action.infos,
+ }
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/SendFormReducer.js b/src/js/reducers/SendFormReducer.js
index 2ef1799b..f65dc313 100644
--- a/src/js/reducers/SendFormReducer.js
+++ b/src/js/reducers/SendFormReducer.js
@@ -1,71 +1,200 @@
/* @flow */
'use strict';
-import * as ACTIONS from '../actions/index';
+import { LOCATION_CHANGE } from 'react-router-redux';
+import * as SEND from '../actions/constants/SendForm';
+import * as WEB3 from '../actions/constants/Web3';
+import * as ADDRESS from '../actions/constants/Address';
+import EthereumjsUnits from 'ethereumjs-units';
+import BigNumber from 'bignumber.js';
+import { getFeeLevels } from '../actions/SendFormActions';
-type State = {
+export type State = {
+ +checksum: ?string;
+ +accountIndex: number;
+ +coin: string;
+ token: string;
+ location: string;
+
+ balanceNeedUpdate: boolean;
+
+
+ // form fields
+ advanced: boolean;
+ untouched: boolean; // set to true when user made any changes in form
+ touched: {[k: string]: boolean};
address: string;
- amount: number;
- gasPrice: number;
- gasLimit: number;
+ amount: string;
+ setMax: boolean;
+ feeLevels: Array;
+ selectedFeeLevel: ?FeeLevel;
+ recommendedGasPrice: string;
+ gasPriceNeedsUpdate: boolean;
+ gasLimit: string;
+ gasPrice: string;
data: string;
+ nonce: string;
+ total: string;
+ sending: boolean;
+ sendingStatus: ?SendStatus;
+ errors: {[k: string]: string};
+ warnings: {[k: string]: string};
+ infos: {[k: string]: string};
+}
+
+export type FeeLevel = {
+ label: string;
+ gasPrice: string;
+ value: string;
+}
+
+type SendStatus = {
+ success: boolean;
+ message: string;
+}
+
+export const initialState: State = {
+ checksum: null,
+ accountIndex: 0,
+ coin: '',
+ token: '',
+ location: '',
+
+ advanced: false,
+ untouched: true,
+ touched: {},
+ balanceNeedUpdate: false,
+ //address: '',
+ address: '0x574BbB36871bA6b78E27f4B4dCFb76eA0091880B',
+ amount: '',
+ setMax: false,
+ feeLevels: [],
+ selectedFeeLevel: null,
+ recommendedGasPrice: '0',
+ gasPriceNeedsUpdate: false,
+ gasLimit: '0',
+ gasPrice: '0',
+ data: '',
+ nonce: '0',
+ total: '0',
+ sending: false,
+ sendingStatus: null,
+ errors: {},
+ warnings: {},
+ infos: {},
+}
+
+
+const onGasPriceUpdated = (state: State, action: any): State => {
+
+ function getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+ const newPrice = getRandomInt(10, 50).toString();
+ //const newPrice = EthereumjsUnits.convert(action.gasPrice, 'wei', 'gwei');
+ if (action.coin === state.coin && newPrice !== state.recommendedGasPrice) {
+ const newState: State = { ...state };
+ if (!state.untouched) {
+ newState.gasPriceNeedsUpdate = true;
+ newState.recommendedGasPrice = newPrice;
+ } else {
+ const newFeeLevels = getFeeLevels(state.coin, newPrice, state.gasLimit);
+ const selectedFeeLevel = newFeeLevels.find(f => f.value === 'Normal');
+ newState.recommendedGasPrice = newPrice;
+ newState.feeLevels = newFeeLevels;
+ newState.selectedFeeLevel = selectedFeeLevel;
+ newState.gasPrice = selectedFeeLevel.gasPrice;
+ }
+ return newState;
+ }
+ return state;
+}
+
+const onBalanceUpdated = (state: State, action: any): State => {
+ // balanceNeedUpdate
+ // if (state.senderAddress === action.address) {
+ // return {
+ // ...state,
+ // balance: '1'
+ // }
+ // }
+
+ // TODO: handle balance update during send form lifecycle
+ return state;
}
-const initialState: State = {
- //address: '0x7314e0f1C0e28474bDb6be3E2c3E0453255188f8', //metamask acc1
- address: '0xa738ea40b69d87f4f9ac94c9a0763f96248df23b', // trezor acc3
- amount: 0.0001,
- gasPrice: 0,
- gasPriceChanged: false,
- gasLimit: 21000,
- data: ''
-};
export default (state: State = initialState, action: any): State => {
switch (action.type) {
- case 'update_gas' :
- if (!state.gasPriceChanged) {
- return {
- ...state,
- gasPrice: action.gasPrice
- }
- }
- return {
- ...state,
- }
-
-
- case ACTIONS.ON_ADDRESS_CHANGE :
+ case SEND.INIT :
+ return action.state;
+
+ case SEND.DISPOSE :
+ return initialState;
+
+ // this will be called right after Web3 instance initialization before any view is shown
+ // and async during app live time
+ case WEB3.GAS_PRICE_UPDATED :
+ return onGasPriceUpdated(state, action);
+
+ case ADDRESS.SET_BALANCE :
+ // case ADDRESS.SET_TOKEN_BALANCE :
+ return onBalanceUpdated(state, action);
+
+ case SEND.TOGGLE_ADVANCED :
return {
...state,
- address: action.address
+ advanced: !state.advanced
}
- case ACTIONS.ON_AMOUNT_CHANGE :
+
+ // user actions
+ case SEND.ADDRESS_CHANGE :
+ case SEND.AMOUNT_CHANGE :
+ case SEND.SET_MAX :
+ case SEND.CURRENCY_CHANGE :
+ case SEND.FEE_LEVEL_CHANGE :
+ case SEND.UPDATE_FEE_LEVELS :
+ case SEND.GAS_PRICE_CHANGE :
+ case SEND.GAS_LIMIT_CHANGE :
+ case SEND.DATA_CHANGE :
+ return action.state;
+
+ case SEND.SEND :
return {
...state,
- amount: action.amount
+ sending: true,
+ sendingStatus: null,
}
- case ACTIONS.ON_GAS_PRICE_CHANGE :
+ case SEND.TX_COMPLETE :
return {
...state,
- gasPriceChanged: true,
- gasPrice: action.gasPrice
+ sending: false,
+ sendingStatus: {
+ success: true,
+ message: action.txid
+ }
}
-
- case ACTIONS.ON_GAS_LIMIT_CHANGE :
+ case SEND.TX_ERROR :
return {
...state,
- gasLimit: action.gasLimit
+ sending: false,
+ sendingStatus: {
+ success: false,
+ message: action.response
+ }
}
- case ACTIONS.ON_TX_DATA_CHANGE :
+
+ case SEND.VALIDATION :
return {
...state,
- data: action.data
+ errors: action.errors,
+ warnings: action.warnings,
+ infos: action.infos,
}
default:
diff --git a/src/js/reducers/SummaryReducer.js b/src/js/reducers/SummaryReducer.js
new file mode 100644
index 00000000..41a59499
--- /dev/null
+++ b/src/js/reducers/SummaryReducer.js
@@ -0,0 +1,47 @@
+/* @flow */
+'use strict';
+
+import * as SUMMARY from '../actions/constants/summary';
+
+export type State = {
+ +checksum: ?string;
+ +accountIndex: ?number;
+ +coin: ?string;
+ location: string;
+
+ details: boolean;
+ selectedToken: any;
+}
+
+export const initialState: State = {
+ checksum: null,
+ accountIndex: null,
+ coin: null,
+ location: '',
+
+ details: true,
+ selectedToken: null
+};
+
+
+export default (state: State = initialState, action: any): State => {
+
+ switch (action.type) {
+
+ case SUMMARY.INIT :
+ return action.state;
+
+ case SUMMARY.DISPOSE :
+ return initialState;
+
+ case SUMMARY.DETAILS_TOGGLE :
+ return {
+ ...state,
+ details: !state.details
+ }
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/TokensReducer.js b/src/js/reducers/TokensReducer.js
new file mode 100644
index 00000000..68feb7f7
--- /dev/null
+++ b/src/js/reducers/TokensReducer.js
@@ -0,0 +1,71 @@
+/* @flow */
+'use strict';
+
+import * as CONNECT from '../actions/constants/TrezorConnect';
+import * as TOKEN from '../actions/constants/Token';
+
+export type Token = {
+ loaded: boolean;
+ +checksum: string;
+ +name: string;
+ +symbol: string;
+ +address: string;
+ +ethAddress: string; // foreign key
+ +decimals: string;
+ balance: string;
+}
+
+const initialState: Array = [];
+
+const setBalance = (state: Array, payload: any): Array => {
+ const newState: Array = [ ...state ];
+ let index: number = state.findIndex(t => t.address === payload.address && t.ethAddress === payload.ethAddress);
+ if (index >= 0) {
+ newState[index].loaded = true;
+ newState[index].balance = payload.balance;
+ }
+ return newState;
+}
+
+const create = (state: Array, payload: any): Array => {
+ const newState: Array = [ ...state ];
+ const token: Token = {
+ loaded: false,
+ checksum: payload.checksum,
+ name: payload.name,
+ symbol: payload.symbol,
+ address: payload.address,
+ ethAddress: payload.ethAddress,
+ decimals: payload.decimals,
+ balance: '0'
+ }
+ newState.push(token);
+ return newState;
+}
+
+const forget = (state: Array, action: any): Array => {
+ return state.filter(t => t.checksum !== action.device.checksum);
+}
+
+export default (state: Array = initialState, action: any): Array => {
+
+ switch (action.type) {
+
+ case TOKEN.ADD :
+ return create(state, action.payload);
+
+ case TOKEN.SET_BALANCE :
+ return setBalance(state, action.payload);
+
+ case CONNECT.FORGET :
+ case CONNECT.FORGET_SINGLE :
+ return forget(state, action);
+
+ case TOKEN.FROM_STORAGE :
+ return action.payload;
+
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/TrezorConnectReducer.js b/src/js/reducers/TrezorConnectReducer.js
index cd7a7837..cbae74f1 100644
--- a/src/js/reducers/TrezorConnectReducer.js
+++ b/src/js/reducers/TrezorConnectReducer.js
@@ -1,81 +1,361 @@
/* @flow */
'use strict';
-import { DEVICE } from 'trezor-connect';
+import { TRANSPORT, DEVICE } from 'trezor-connect';
+import * as CONNECT from '../actions/constants/TrezorConnect';
+
+export type TrezorDevice = {
+ initialized: boolean;
+ remember: boolean;
+ connected: boolean;
+ path: string;
+ +label: string;
+ +checksum: string;
+ +instance?: number;
+ instanceLabel: string;
+ features?: Object;
+ unacquired?: boolean;
+ acquiring: boolean;
+ isUsedElsewhere?: boolean;
+ featuresNeedsReload?: boolean;
+ ts: number;
+}
+
+export type SelectedDevice = {
+ id: string;
+ instance: ?number;
+}
type State = {
- devices: Array,
- selectedDevice: ?string;
+ devices: Array;
+ selectedDevice: ?SelectedDevice;
+ discoveryComplete: boolean;
+ error: any;
}
+
const initialState: State = {
devices: [],
- selectedDevice: undefined,
+ selectedDevice: null,
+ discoveryComplete: false,
+ error: null,
};
-const findDeviceIndexByPath = (devices: Array, path: string): number => {
- let index: number = -1;
- for (let [i, dev] of devices.entries() ) {
- if (dev.path === path) {
- index = i;
- break;
+export const findSelectedDevice = (state: State): ?TrezorDevice => {
+ const selected: ?SelectedDevice = state.selectedDevice;
+ if (!selected) return null;
+
+ return state.devices.find(d => {
+ if (d.unacquired && d.path === selected.id) {
+ return true;
+ } else if (d.features && d.features.device_id === selected.id && d.instance === selected.instance){
+ return true;
+ }
+ return false;
+ });
+}
+
+export const isSavedDevice = (state: State, device: any): ?Array => {
+ const selected: ?SelectedDevice = state.selectedDevice;
+ if (!selected) return null;
+
+ if (!device || !device.features) return null;
+
+ return state.devices.filter(d => {
+ if (d.features && d.features.device_id === device.features.device_id){
+ return d;
+ }
+ return null;
+ });
+}
+
+const mergeDevices = (current: TrezorDevice, upcoming: Object): TrezorDevice => {
+ const dev: TrezorDevice = {
+ // ...current,
+ ...upcoming,
+ // make sure that instance specific variables will not be overridden
+ initialized: current.initialized,
+ connected: typeof upcoming.connected === 'boolean' ? upcoming.connected : current.connected,
+ remember: typeof upcoming.remember === 'boolean' ? upcoming.remember : current.remember,
+ instance: current.instance,
+ instanceLabel: current.instanceLabel,
+ checksum: current.checksum,
+ acquiring: typeof upcoming.acquiring === 'boolean' ? upcoming.acquiring : current.acquiring,
+ ts: new Date().getTime(),
+ }
+
+ if (upcoming.unacquired && current.checksum) {
+ dev.instanceLabel = current.instanceLabel;
+ dev.features = current.features;
+ dev.label = current.label;
+ dev.unacquired = false;
+ } else if (!upcoming.unacquired && current.unacquired) {
+ dev.instanceLabel = upcoming.label;
+ if (typeof dev.instance === 'number') {
+ dev.instanceLabel = `${upcoming.label} #${dev.instance}`;
}
}
- return index;
+
+ return dev;
}
const addDevice = (state: State, device: Object): State => {
- let index: number = findDeviceIndexByPath(state.devices, device.path);
- if (index > -1) {
- state.devices[index] = device;
+
+ const newState: State = { ...state };
+
+ let affectedDevices: Array = [];
+ let otherDevices: Array = [];
+ if (device.unacquired) {
+ // connected device is unacquired, but it was already merged with saved devices
+ // when DEVICE.CHANGE event occurs
+ // ignore this event
+ affectedDevices = newState.devices.filter(d => d.path === device.path);
+ const diff = newState.devices.filter(d => affectedDevices.indexOf(d) === -1);
+
+ if (affectedDevices.length > 0) {
+ return state;
+ }
} else {
- state.devices.push(device);
+ affectedDevices = newState.devices.filter(d => d.features && d.features.device_id === device.features.device_id);
+ otherDevices = newState.devices.filter(d => d.features && d.features.device_id !== device.features.device_id);
}
- return state;
-}
-const removeDevice = (state: State, device: Object): State => {
- if (state.selectedDevice === device.path) {
- state.selectedDevice = undefined;
+ if (affectedDevices.length > 0 ) {
+ // replace existing values
+ const changedDevices: Array = affectedDevices.map(d => mergeDevices(d, { ...device, connected: true} ));
+ newState.devices = otherDevices.concat(changedDevices);
+
+ } else {
+
+ const newDevice: TrezorDevice = {
+ ...device,
+ initialized: false,
+ acquiring: false,
+ remember: false,
+ connected: true,
+ path: device.path,
+ label: device.label,
+ id: 'ABCD',
+ checksum: null,
+ // instance: 0,
+ instanceLabel: device.label,
+ ts: 0,
+ }
+ newState.devices.push(newDevice);
+
+ // const clone = { ...newDevice, instance: 1, instanceLabel: device.label + '#1' };
+ // newState.devices.push(clone);
}
- let index: number = findDeviceIndexByPath(state.devices, device.path);
+
+ return newState;
+}
+
+const setDeviceState = (state: State, action: any): State => {
+ const newState: State = { ...state };
+
+ //const affectedDevice: ?TrezorDevice = state.devices.find(d => d.path === action.device.path && d.instance === action.device.instance);
+ const index: number = state.devices.findIndex(d => d.path === action.device.path && d.instance === action.device.instance);
if (index > -1) {
+ const changedDevice: TrezorDevice = {
+ ...newState.devices[index],
+ initialized: true,
+ checksum: action.checksum
+ };
+ newState.devices[index] = changedDevice;
+ //newState.selectedDevice = changedDevice;
+ }
+
+ return newState;
+}
+
+const changeDevice = (state: State, device: Object): State => {
+
+ const newState: State = { ...state };
+
+ let affectedDevices: Array = [];
+ let otherDevices: Array = [];
+ if (device.features) {
+ affectedDevices = state.devices.filter(d => (d.features && d.features.device_id === device.features.device_id) || (d.path.length > 0 && d.path === device.path) );
+ otherDevices = state.devices.filter(d => affectedDevices.indexOf(d) === -1);
+ } else {
+ affectedDevices = state.devices.filter(d => d.path === device.path);
+ otherDevices = state.devices.filter(d => d.path !== device.path);
+ }
+
+ if (affectedDevices.length > 0) {
+
+ const isAffectedUnacquired: number = affectedDevices.findIndex(d => d.unacquired);
+ if (isAffectedUnacquired >= 0 && affectedDevices.length > 1){
+ affectedDevices.splice(isAffectedUnacquired, 1);
+ }
+
+ // else if (isAffectedUnacquired >= 0 && !device.unacquired && affectedDevices.length > 1) {
+ // affectedDevices.splice(isAffectedUnacquired, 1);
+ // console.warn("CLEARRRR", isAffectedUnacquired);
+ // }
+ console.warn("AFFEEE", isAffectedUnacquired, affectedDevices, otherDevices)
+
+
+ // acquiring selected device. remove unnecessary (not acquired) device from list
+ // after this action selectedDevice needs to be updated (in TrezorConnectService)
+ if (state.selectedDevice && device.path === state.selectedDevice.id && affectedDevices.length > 1) {
+ console.warn("clear dupli", affectedDevices, otherDevices)
+ // affectedDevices = affectedDevices.filter(d => d.path !== state.selectedDevice.id && d.features);
+ }
+
- state.devices.splice(index, 1);
+
+ // replace existing values
+ const changedDevices: Array = affectedDevices.map(d => mergeDevices(d, device));
+ newState.devices = otherDevices.concat(changedDevices);
+ }
+
+ return newState;
+}
+
+
+const disconnectDevice = (state: State, device: Object): State => {
+
+ const newState: State = { ...state };
+ const affectedDevices: Array = state.devices.filter(d => d.path === device.path);
+ const otherDevices: Array = state.devices.filter(d => affectedDevices.indexOf(d) === -1);
+
+ if (affectedDevices.length > 0) {
+ const acquiredDevices = affectedDevices.filter(d => !d.unacquired && d.checksum);
+ newState.devices = otherDevices.concat( acquiredDevices.map(d => {
+ d.connected = false;
+ d.isUsedElsewhere = false;
+ d.featuresNeedsReload = false;
+ d.acquiring = false;
+ //if (d.remember)
+ d.path = '';
+ return d;
+ }));
+ }
+
+ // selected device was removed and forgotten
+ // clear this field
+ const selected = findSelectedDevice(newState);
+ if (!selected) {
+ newState.selectedDevice = null;
+ }
+
+ return newState;
+}
+
+const forgetDevice = (state: State, action: any): State => {
+ const newState: State = { ...state };
+
+ if (action.type === CONNECT.FORGET_SINGLE) {
+ // remove only one instance (called from Aside button)
+ newState.devices.splice(newState.devices.indexOf(action.device), 1);
+ } else {
+ // remove all instances after disconnect (remember request declined)
+ newState.devices = state.devices.filter(d => d.path !== action.device.path);
}
- return state;
+
+ return newState;
}
-const onDeviceStateChange = (device: Object): void => {
+const devicesFromLocalStorage = (devices: Array): Array => {
+ return devices.map(d => {
+ return {
+ ...d,
+ connected: false,
+ path: '',
+ acquiring: false,
+ featuresNeedsReload: false,
+ isUsedElsewhere: false
+ }
+ });
+}
+
+const duplicate = (state: State, device: any): State => {
+ const newState: State = { ...state };
+ const affectedDevices: Array = state.devices.filter(d => d.path === device.path);
+
+ // if (affectedDevices.length > 0) {
+ const newDevice: TrezorDevice = {
+ ...device,
+ initialized: false,
+ checksum: null,
+ remember: device.remember,
+ connected: device.connected,
+ path: device.path,
+ label: device.label,
+ id: 'ABCD',
+ instance: new Date().getTime(),
+ instanceLabel: device.instanceLabel,
+ ts: 0,
+ }
+ newState.devices.push(newDevice);
+ newState.selectedDevice = {
+ id: newDevice.features.device_id,
+ instance: newDevice.instance
+ }
+ //}
+ return newState;
}
+
+
export default function connect(state: State = initialState, action: any): any {
switch (action.type) {
- case DEVICE.CONNECT :
- case DEVICE.CONNECT_UNACQUIRED :
+ case CONNECT.DUPLICATE :
+ return duplicate(state, action.device);
+
+
+ case CONNECT.SELECT_DEVICE :
return {
...state,
- ...addDevice(state, action.device)
- };
- break;
+ selectedDevice: action.payload
+ }
- case DEVICE.DISCONNECT :
- case DEVICE.DISCONNECT_UNACQUIRED :
+ case CONNECT.INITIALIZATION_ERROR :
return {
...state,
- ...removeDevice(state, action.device)
+ error: action.error
};
- break;
- case 'select_device' :
+ case TRANSPORT.ERROR :
return {
...state,
- selectedDevice: action.path,
+ error: action.device // message is wrapped in "device" field. It's dispatched from TrezorConnect.on(DEVICE_EVENT...) in TrezorConnectService
};
- break;
+
+ case CONNECT.DEVICE_FROM_STORAGE :
+ return {
+ ...state,
+ devices: devicesFromLocalStorage(action.payload),
+ }
+
+ case CONNECT.AUTH_DEVICE :
+ return setDeviceState(state, action);
+
+ case DEVICE.CONNECT :
+ case DEVICE.CONNECT_UNACQUIRED :
+ return addDevice(state, action.device);
+
+ case CONNECT.REMEMBER :
+ return changeDevice(state, { ...action.device, path: '', remember: true } );
+
+ case CONNECT.FORGET :
+ case CONNECT.FORGET_SINGLE :
+ return forgetDevice(state, action);
+
+ case CONNECT.START_ACQUIRING :
+ case CONNECT.STOP_ACQUIRING :
+ return changeDevice(state, { ...action.device, acquiring: action.type === CONNECT.START_ACQUIRING } );
+
+ case DEVICE.DISCONNECT :
+ case DEVICE.DISCONNECT_UNACQUIRED :
+ return disconnectDevice(state, action.device);
+
+ case DEVICE.CHANGED :
+ return changeDevice(state, { ...action.device, connected: true });
default:
return state;
diff --git a/src/js/reducers/WalletReducer.js b/src/js/reducers/WalletReducer.js
new file mode 100644
index 00000000..850a3a31
--- /dev/null
+++ b/src/js/reducers/WalletReducer.js
@@ -0,0 +1,27 @@
+/* @flow */
+'use strict';
+
+import { ON_RESIZE, TOGGLE_DEVICE_DROPDOWN, RESIZE_CONTAINER } from '../actions/AppActions';
+import * as WEB3 from '../actions/constants/Web3';
+
+const WIDTH: number = 1080;
+const HEIGHT: number = 1920;
+
+type State = {
+ coin: string;
+ device: string;
+
+}
+
+const initialState: Object = {
+
+};
+
+export default function wallet(state: Object = initialState, action: Object): any {
+ switch(action.type) {
+
+
+ default:
+ return state;
+ }
+}
diff --git a/src/js/reducers/Web3Reducer.1.js b/src/js/reducers/Web3Reducer.1.js
new file mode 100644
index 00000000..a71f899d
--- /dev/null
+++ b/src/js/reducers/Web3Reducer.1.js
@@ -0,0 +1,70 @@
+/* @flow */
+'use strict';
+
+import Web3 from 'web3';
+
+import { UI, DEVICE } from 'trezor-connect';
+import * as ACTIONS from '../actions';
+import * as STORAGE from '../actions/constants/LocalStorage';
+import * as WEB3 from '../actions/constants/Web3';
+
+type State = {
+ web3: Web3;
+ url: Array;
+ customUrl: string;
+ tokens: JSON;
+ abi: JSON;
+ gasPrice: any;
+ latestBlock: any;
+ fiatRate: ?string;
+}
+
+const initialState: State = {
+ web3: null,
+ url: [
+ 'https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4',
+ 'https://api.myetherapi.com/rop',
+ 'https://pyrus2.ubiqscan.io',
+ ],
+ customUrl: 's',
+ gasPrice: 0,
+ latestBlock: 0,
+};
+
+export default function web3(state: State = initialState, action: any): any {
+
+ switch (action.type) {
+
+ case 'rate__update' :
+ return {
+ ...state,
+ fiatRate: action.rate.price_usd
+ }
+
+ case STORAGE.READY :
+ return {
+ ...state,
+ tokens: action.tokens,
+ abi: action.abi
+ }
+
+ case WEB3.READY :
+ return {
+ ...state,
+ web3: action.web3
+ }
+ case WEB3.BLOCK_UPDATED :
+ return {
+ ...state,
+ latestBlock: action.blockHash
+ }
+ case WEB3.GAS_PRICE_UPDATED :
+ return {
+ ...state,
+ gasPrice: action.gasPrice
+ }
+ default:
+ return state;
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/reducers/Web3Reducer.js b/src/js/reducers/Web3Reducer.js
index 79bea4da..8d44d59d 100644
--- a/src/js/reducers/Web3Reducer.js
+++ b/src/js/reducers/Web3Reducer.js
@@ -1,26 +1,64 @@
/* @flow */
'use strict';
-import { UI, DEVICE } from 'trezor-connect';
-import * as ACTIONS from '../actions';
+import Web3 from 'web3';
-type State = {
- web3: any;
+import * as STORAGE from '../actions/constants/LocalStorage';
+import * as WEB3 from '../actions/constants/Web3';
+
+type Web3Instance = {
+ coin: string;
+ web3: Web3;
+ chainId: number;
+ latestBlock: any;
+ gasPrice: any;
+ erc20: any;
+}
+
+const initialState: Array = [];
+
+const createWeb3 = (state: Array, action: any): Array => {
+ const instance: Web3Instance = {
+ coin: action.name,
+ web3: action.web3,
+ chainId: parseInt(action.chainId),
+ latestBlock: '0',
+ gasPrice: '0',
+ erc20: action.erc20
+ }
+ const newState: Array = [ ...state ];
+ newState.push(instance);
+ return newState;
+}
+
+const updateLatestBlock = (state: Array, action: any): Array => {
+ const index: number = state.findIndex(w3 => {
+ return w3.coin === action.name;
+ });
+ const newState: Array = [ ...state ];
+ newState[index].latestBlock = action.blockHash;
+ return newState;
}
-const initialState: State = {
- web3: null,
-};
+const updateGasPrice = (state: Array, action: any): Array => {
+ const index: number = state.findIndex(w3 => {
+ return w3.coin === action.coin;
+ });
+ const newState: Array = [ ...state ];
+ newState[index].gasPrice = action.gasPrice;
+ return newState;
+}
-export default function web3(state: State = initialState, action: any): any {
+export default function web3(state: Array = initialState, action: any): any {
switch (action.type) {
- case 'web3__init' :
- return {
- ...state,
- web3: action.web3
- }
+ case WEB3.CREATE :
+ return createWeb3(state, action);
+ case WEB3.BLOCK_UPDATED :
+ return updateLatestBlock(state, action);
+ case WEB3.GAS_PRICE_UPDATED :
+ return updateGasPrice(state, action);
default:
return state;
}
diff --git a/src/js/reducers/index.js b/src/js/reducers/index.js
index 299c4d01..11c06cd1 100644
--- a/src/js/reducers/index.js
+++ b/src/js/reducers/index.js
@@ -4,19 +4,33 @@
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
-import DOM from './DomReducer.js';
+import DOM from './AppReducer.js';
+import localStorage from './LocalStorageReducer.js';
import connect from './TrezorConnectReducer.js';
+import notifications from './NotificationReducer.js';
import modal from './ModalReducer.js';
import web3 from './Web3Reducer.js';
-import addresses from './AddressesReducer.js';
+import accounts from './AccountsReducer.js';
+import accountDetail from './AccountDetailReducer.js';
import sendForm from './SendFormReducer.js';
+import receive from './ReceiveReducer.js';
+import summary from './SummaryReducer.js';
+import tokens from './TokensReducer.js';
+import discovery from './DiscoveryReducer.js';
export default combineReducers({
router: routerReducer,
DOM,
+ localStorage,
connect,
+ notifications,
modal,
web3,
- addresses,
- sendForm
+ accounts,
+ accountDetail,
+ sendForm,
+ receive,
+ summary,
+ tokens,
+ discovery
});
\ No newline at end of file
diff --git a/src/js/router/index.js b/src/js/router/index.js
index 197a8978..b2872867 100644
--- a/src/js/router/index.js
+++ b/src/js/router/index.js
@@ -2,20 +2,52 @@
'use strict';
import React from 'react';
-import { Route } from 'react-router-dom';
+import { Route, Switch } from 'react-router-dom';
+import { Provider } from 'react-redux';
+import { ConnectedRouter } from 'react-router-redux';
+import store, { history } from '../store';
+
import {
- AppContainer,
- LoadingContainer,
+ LandingPageContainer,
+ WalletContainer,
+
+ AcquireContainer,
+ BootloaderContainer,
+
+ DashboardContainer,
+
HistoryContainer,
SendFormContainer,
ReceiveContainer,
+ SignVerifyContainer,
+ SettingsContainer,
} from '../containers';
+import SummaryContainer from '../components/wallet/summary/SummaryContainer';
+
export default (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* */}
+
+
+
+
+
+
+
);
\ No newline at end of file
diff --git a/src/js/services/CoinmarketcapService.js b/src/js/services/CoinmarketcapService.js
new file mode 100644
index 00000000..67047b09
--- /dev/null
+++ b/src/js/services/CoinmarketcapService.js
@@ -0,0 +1,39 @@
+/* @flow */
+'use strict';
+
+import { LOCATION_CHANGE } from 'react-router-redux';
+import { httpRequest } from '../utils/networkUtils';
+import { resolveAfter } from '../utils/promiseUtils';
+
+const loadRateAction = (): any => {
+ return async (dispatch, getState) => {
+ try {
+ const rate = await httpRequest('https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=USD', 'json');
+
+ dispatch({
+ type: 'rate__update',
+ rate: rate[0]
+ })
+
+ } catch(error) {
+
+ }
+
+ await resolveAfter(50000);
+ // dispatch( loadRateAction() );
+ }
+}
+
+/**
+ * Middleware
+ */
+const LocalStorageService = (store: any) => (next: any) => (action: any) => {
+
+ if (action.type === LOCATION_CHANGE && !store.getState().router.location) {
+ store.dispatch(loadRateAction());
+ }
+
+ next(action);
+};
+
+export default LocalStorageService;
\ No newline at end of file
diff --git a/src/js/services/EtherscanService.js b/src/js/services/EtherscanService.js
index 0b9fbadd..ae6d2862 100644
--- a/src/js/services/EtherscanService.js
+++ b/src/js/services/EtherscanService.js
@@ -1,4 +1,4 @@
-/* @flow */
+/* @flo */
'use strict';
//http://ropsten.etherscan.io/api?module=account&action=txlist&address=0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad&startblock=0&endblock=99999999&sort=asc&apikey=89IZG471H8ITVXY377I2QWJIT2D62DGG9Z
@@ -25,8 +25,41 @@ const getTransactionStatus = async (txid: string): Promise> => {
return json;
}
-export const loadTransactionStatus = (txid): Promise => {
- return async (dispatch, getState) => {
+const getTokenHistory = async (tokenAddress, address) => {
+
+ // 0x58cda554935e4a1f2acbe15f8757400af275e084
+ // 0x000000000000000000000000 + 98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad
+ let url: string = 'https://ropsten.etherscan.io/api?module=logs&action=getLogs';
+ url += '&fromBlock=0&toBlock=latest';
+ url += `&address=${tokenAddress}`;
+ url += '&topic0=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
+ url += `&topic2=${ address }`;
+ // https://api.etherscan.io/api?module=logs&action=getLogs
+ // &fromBlock=0
+ // &toBlock=latest
+ // &address=[Token Contract Address]
+ // &topic0=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
+ // &topic1=[From Address, padded to 32 bytes - optional]
+ // &topic2=[To Address, padded to 32 bytes - optional]
+
+ console.log("TOKENURL", url);
+ const json = await httpRequest(url);
+ return json;
+}
+
+export const loadTokenHistory = (address): Promise => {
+ // https://ropsten.etherscan.io/apis#logs
+ return async (dispatch, getState): Promise => {
+ const tkn = '0x58cda554935e4a1f2acbe15f8757400af275e084';
+ const ad = '0x00000000000000000000000098ead4bd2fbbb0cf0b49459aa0510ef53faa6cad';
+ const incoming = await getTokenHistory(tkn, ad);
+
+ console.log("TOKEN HIST!", JSON.parse(incoming) );
+ }
+}
+
+export const loadTransactionStatus = (txid): Promise => {
+ return async (dispatch, getState): Promise => {
const json = await getTransactionStatus(txid);
const status = JSON.parse(json);
@@ -38,7 +71,23 @@ export const loadTransactionStatus = (txid): Promise => {
for (let addr of addresses) {
if (addr.findPendingTx(txid)) {
dispatch( getBalance(addr) );
- dispatch( loadHistory(addr, 3000) );
+
+ const txType = status.result.from === addr.address ? 'out' : 'in';
+ const txAddress = txType === 'out' ? status.result.to : status.result.from;
+
+ dispatch({
+ type: ACTIONS.ADDRESS_ADD_TO_HISTORY,
+ address: addr,
+ entry: {
+ txid: status.result.hash,
+ type: txType,
+ timestamp: '0',
+ address: txAddress,
+ value: status.result.value
+ }
+ });
+
+ //dispatch( loadHistory(addr, 3000) );
}
}
@@ -79,6 +128,7 @@ export const loadHistory = (address, delay): void => {
// const json = await getTransactionStatus('0x2113e578497f3486944566e2417b5ac3b31d7e76f71557ae0626e2a6fe191e58');
// console.log("JSON!", json)
+ /*
if (delay) {
console.warn("-----PRELOAD with delay", address)
await new Promise(resolve => {
@@ -92,6 +142,7 @@ export const loadHistory = (address, delay): void => {
address,
history
});
+ */
}
}
@@ -110,7 +161,8 @@ const EtherscanService = store => next => action => {
const addressId = parseInt(parts[2]);
if (!isNaN(addressId) && addresses[addressId]) {
- store.dispatch( loadHistory( addresses[addressId] ) );
+ //store.dispatch( loadHistory( addresses[addressId] ) );
+ //store.dispatch( loadTokenHistory( addresses[addressId] ) );
}
//console.error("ETH", parts, "id", parts.length, parts.length === 3 && parts[1] === "address");
diff --git a/src/js/services/LocalStorageService.js b/src/js/services/LocalStorageService.js
new file mode 100644
index 00000000..4cb8d915
--- /dev/null
+++ b/src/js/services/LocalStorageService.js
@@ -0,0 +1,111 @@
+/* @flow */
+'use strict';
+
+import { LOCATION_CHANGE } from 'react-router-redux';
+import * as LocalStorageActions from '../actions/LocalStorageActions';
+
+import { DEVICE } from 'trezor-connect';
+import * as CONNECT from '../actions/constants/TrezorConnect';
+import * as MODAL from '../actions/constants/Modal';
+import * as TOKEN from '../actions/constants/Token';
+import * as ADDRESS from '../actions/constants/Address';
+import * as DISCOVERY from '../actions/constants/Discovery';
+
+
+// https://github.com/STRML/react-localstorage/blob/master/react-localstorage.js
+// or
+// https://www.npmjs.com/package/redux-react-session
+
+const findAccounts = (devices, accounts) => {
+ return devices.reduce((arr, dev) => {
+ return arr.concat(accounts.filter(a => a.checksum === dev.checksum));
+ }, []);
+}
+
+const findTokens = (accounts, tokens) => {
+ return accounts.reduce((arr, account) => {
+ return arr.concat(tokens.filter(a => a.ethAddress === account.address));
+ }, []);
+}
+
+const findDiscovery = (devices, discovery) => {
+ return devices.reduce((arr, dev) => {
+ return arr.concat(discovery.filter(a => a.checksum === dev.checksum));
+ }, []);
+}
+
+const save = (dispatch, getState) => {
+ const devices = getState().connect.devices.filter(d => d.remember === true && !d.unacquired);
+ const accounts = findAccounts(devices, getState().accounts);
+ const tokens = findTokens(accounts, getState().tokens);
+ const discovery = findDiscovery(devices, getState().discovery);
+
+ // save devices
+ dispatch( LocalStorageActions.save('devices', JSON.stringify(devices) ) );
+
+ // save already preloaded accounts
+ dispatch( LocalStorageActions.save('accounts', JSON.stringify(accounts) ) );
+
+ // save discovery state
+ dispatch( LocalStorageActions.save('discovery', JSON.stringify(discovery) ) );
+
+ // tokens
+ dispatch( LocalStorageActions.save('tokens', JSON.stringify( tokens ) ) );
+}
+
+
+const LocalStorageService = (store: any) => (next: any) => (action: any) => {
+
+ if (action.type === LOCATION_CHANGE) {
+ const { location } = store.getState().router;
+ if (!location) {
+ // load data from config.json and local storage
+ store.dispatch( LocalStorageActions.loadData() );
+ }
+ }
+
+ next(action);
+
+ switch (action.type) {
+
+ // first time saving
+ case CONNECT.REMEMBER :
+ save(store.dispatch, store.getState);
+ break;
+
+ case TOKEN.ADD :
+ case TOKEN.SET_BALANCE :
+ save(store.dispatch, store.getState);
+ // store.dispatch( LocalStorageActions.save('tokens', JSON.stringify( tokens ) ) );
+ break;
+
+ case ADDRESS.CREATE :
+ case ADDRESS.SET_BALANCE :
+ case ADDRESS.SET_NONCE :
+ save(store.dispatch, store.getState);
+ //store.dispatch( LocalStorageActions.save('accounts', JSON.stringify( accounts ) ) );
+ break;
+
+ case DISCOVERY.START :
+ case DISCOVERY.STOP :
+ case DISCOVERY.COMPLETE :
+ // case DISCOVERY.WAITING :
+ save(store.dispatch, store.getState);
+ break;
+
+ case CONNECT.FORGET :
+ case CONNECT.FORGET_SINGLE :
+ case DEVICE.CHANGED :
+ case DEVICE.DISCONNECT :
+ case CONNECT.AUTH_DEVICE :
+ save(store.dispatch, store.getState);
+ //store.dispatch( LocalStorageActions.save('devices', JSON.stringify( store.getState().connect.devices.filter(d => d.remember === true && !d.unacquired) ) ) );
+ // store.dispatch( LocalStorageActions.save('selectedDevice', JSON.stringify( store.getState().connect.selectedDevice ) ) );
+ break;
+
+ }
+
+
+};
+
+export default LocalStorageService;
\ No newline at end of file
diff --git a/src/js/services/RouterService.js b/src/js/services/RouterService.js
index 66bf7d23..2088e529 100644
--- a/src/js/services/RouterService.js
+++ b/src/js/services/RouterService.js
@@ -1,19 +1,155 @@
/* @flow */
'use strict';
-import { LOCATION_CHANGE, push } from 'react-router-redux';
+import pathToRegexp from 'path-to-regexp';
+import { DEVICE } from 'trezor-connect';
+import { LOCATION_CHANGE, push, replace } from 'react-router-redux';
+import { ON_BEFORE_UNLOAD } from '../actions/AppActions';
+import * as CONNECT from '../actions/constants/TrezorConnect';
/**
- * Middleware used for managing router path and
- * checking if all required devices are online.
- * It starts right before action is passed to reducers and add "alive" filed to every action
- * which determining if current path is Adam or not
+ * Middleware used for init application and managing router path.
*/
-const RouterService = store => next => action => {
- if (action.type === LOCATION_CHANGE) {
+ type UrlParams = {[k: string] : string};
+
+const pathToParams = (path: string): UrlParams => {
+ const urlParts: Array = path.split("/").slice(1);
+ const params: UrlParams = {};
+ if (urlParts.length < 1 || path === "/") return params;
+
+ for (let i = 0, len = urlParts.length; i < len; i+=2) {
+ params[ urlParts[i] ] = urlParts[ i + 1 ];
+ }
+
+ if (params.hasOwnProperty('device')) {
+ const isClonedDevice: Array = params.device.split(':');
+ if (isClonedDevice.length > 1) {
+ params.device = isClonedDevice[0];
+ params.deviceInstance = parseInt(isClonedDevice[1]);
+ }
+ }
+
+ return params;
+}
+
+const validation = (store: any, params: UrlParams): boolean => {
+
+ if (params.hasOwnProperty('device')) {
+ const { devices } = store.getState().connect;
+
+ let device; // = devices.find(d => d.path === params.device || d.features.device_id === params.device);
+ if (params.hasOwnProperty('deviceInstance')) {
+ device = devices.find(d => d.features && d.features.device_id === params.device && d.instance === params.deviceInstance );
+ } else {
+ device = devices.find(d => d.path === params.device || (d.features && d.features.device_id === params.device));
+ }
+
+ if (!device) return false;
+ }
+
+ if (params.hasOwnProperty('coin')) {
+ const { config } = store.getState().localStorage;
+ const coin = config.coins.find(c => c.symbol === params.coin);
+ if (!coin) return false;
+ if (!params.address) return false;
+ }
+
+ if (params.address) {
+
+ }
+
+ return true;
+}
+
+let __unloading: boolean = false;
+
+const RouterService = (store: any) => (next: any) => (action: any) => {
+
+ if (action.type === ON_BEFORE_UNLOAD) {
+ __unloading = true;
+ } else if (action.type === LOCATION_CHANGE && !__unloading) {
+
+ const { location } = store.getState().router;
+ const web3 = store.getState().web3;
+ const { devices, error } = store.getState().connect;
+ const { opened } = store.getState().modal;
+
+ let redirectPath: ?string;
+ // first (initial) event after app loads
+ if (!location) {
+
+ action.payload.state = {
+ initURL: action.payload.pathname,
+ initSearch: action.payload.search
+ }
+
+ // check if there are initial parameters in url (coin)
+ if (action.payload.search.length > 0) {
+ // save it in WalletReducer, after device detection will redirect to this request
+ redirectPath = '/';
+ //action.payload.initURL = action.payload.location;
+ }
+ }
+
+ const requestedParams: UrlParams = pathToParams(action.payload.pathname);
+ const currentParams: UrlParams = pathToParams(location ? location.pathname : '/');
+
+ // if web3 wasn't initialized yet or there are no devices attached or initialization error occurs
+ const landingPage: boolean = web3.length < 1 || devices.length < 1 || error;
+
+ if (opened && action.payload.pathname !== location.pathname) {
+ redirectPath = location.pathname;
+ console.warn("Modal still opened");
+ } else if (landingPage) {
+ // keep route on landing page
+ if (action.payload.pathname !== '/'){
+ redirectPath = '/';
+ }
+ } else {
+ // PATH VALIDATION
+ // redirect from root view
+ if (action.payload.pathname === '/' || !validation(store, requestedParams)) {
+ // TODO: switch to first device?
+ // redirectPath = `/device/${ devices[0].path }`;
+ redirectPath = location.pathname;
+ } else {
+
+ if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
+ store.dispatch({
+ type: CONNECT.SELECT_DEVICE,
+ payload: {
+ id: requestedParams.device,
+ instance: requestedParams.deviceInstance
+ }
+ });
+ }
+
+ if (requestedParams.coin !== currentParams.coin) {
+ store.dispatch({
+ type: CONNECT.COIN_CHANGED,
+ payload: {
+ coin: requestedParams.coin
+ }
+ });
+ }
+ }
+ }
+
+ if (redirectPath) {
+ console.warn("Redirecting...")
+ // override action to keep routerReducer sync
+ action.payload.params = pathToParams(redirectPath);
+ action.payload.pathname = redirectPath;
+ // change url
+ store.dispatch( replace(redirectPath) );
+ } else {
+ action.payload.params = requestedParams;
+ }
+
}
+ // Pass all actions through by default
next(action);
};
diff --git a/src/js/services/TrezorConnectService.1.js b/src/js/services/TrezorConnectService.1.js
new file mode 100644
index 00000000..e9267f1b
--- /dev/null
+++ b/src/js/services/TrezorConnectService.1.js
@@ -0,0 +1,219 @@
+/* @flow */
+'use strict';
+
+import { LOCATION_CHANGE, push } from 'react-router-redux';
+
+import TrezorConnect, { TRANSPORT, DEVICE_EVENT, UI_EVENT, UI, DEVICE } from 'trezor-connect';
+import * as TrezorConnectActions from '../actions/TrezorConnectActions';
+import * as ModalActions from '../actions/ModalActions';
+import { init as initWeb3 } from '../actions/Web3Actions';
+import * as WEB3 from '../actions/constants/Web3';
+import * as STORAGE from '../actions/constants/LocalStorage';
+import * as CONNECT from '../actions/constants/TrezorConnect';
+
+
+
+const initSelectedDevice = async (store: any, device: any): void => {
+
+ const { selectedDevice } = store.getState().connect;
+
+ console.log("WHATSUP?", device, selectedDevice)
+
+ // if we are in LandingPage view switch it to Wallet
+ if (selectedDevice && selectedDevice.path === device.path && selectedDevice.instance === device.instance) {
+ if (selectedDevice.unacquired || selectedDevice.isUsedElsewhere) {
+ store.dispatch( push(`/device/${ selectedDevice.path }/acquire`) );
+ } else {
+ if (device.features.bootloader_mode) {
+ store.dispatch( push(`/device/${ selectedDevice.path }/bootloader`) );
+ } else {
+
+ if (device.instance) {
+ store.dispatch( push(`/device/${ device.features.device_id }:${ device.instance }`) );
+ } else {
+ store.dispatch( push(`/device/${ device.features.device_id }`) );
+ }
+
+ // if (!selectedDevice.initialized && selectedDevice.connected) {
+ // const response = await TrezorConnect.getPublicKey({
+ // selectedDevice: selectedDevice.path,
+ // instance: selectedDevice.instance,
+ // path: "m/1'/0'/0'",
+ // confirmation: false
+ // });
+
+ // if (response && response.success) {
+ // const xpub = response.data.xpub;
+ // store.dispatch({
+ // type: CONNECT.AUTH_DEVICE,
+ // device: selectedDevice,
+ // xpub
+ // });
+ // } else {
+ // // TODO: error
+ // }
+
+ // console.log("INIT SELECTED!", device, response)
+ // }
+
+
+
+
+ //store.dispatch( push(`/device/${ device.features.device_id }/coin/eth/address/0/send`) );
+ //store.dispatch( push(`/device/${ device.features.device_id }/coin/eth/address/0`) );
+ // store.dispatch( push(`/device/${ device.features.device_id }`) );
+
+
+
+
+
+
+ // store.dispatch( TrezorConnectActions.startDiscoveryProcess(device) );
+
+ // get xpub to force
+
+ }
+ }
+ }
+}
+
+const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
+
+ if (action.type === DEVICE.DISCONNECT) {
+ const previous = store.getState().connect.selectedDevice;
+ next(action);
+ if (previous && action.device.path === previous.path) {
+
+ if (previous.unacquired) {
+
+ } else if (previous.initialized) {
+ // interrupt discovery process
+ store.dispatch( TrezorConnectActions.stopDiscoveryProcess(previous) );
+
+ if (!previous.remember) {
+ store.dispatch(ModalActions.askForRemember(previous));
+ }
+ }
+ }
+
+ return;
+ }
+
+ if (action.type === DEVICE.ACQUIRED) {
+ const { selectedDevice } = store.getState().connect;
+ initSelectedDevice(store, selectedDevice);
+ }
+
+ if (action.type === DEVICE.CHANGED) {
+ const previousSelectedDevice = store.getState().connect.selectedDevice;
+ // Pass actions BEFORE
+ next(action);
+
+ if (previousSelectedDevice && action.device.path === previousSelectedDevice.path) {
+ //console.warn("TODO: Handle device changed, interrupt running async action (like discovery)", action.device);
+ }
+ } else if (action.type === DEVICE.DISCONNECT || action.type === CONNECT.SELECT_DEVICE) {
+ const previousSelectedDevice = store.getState().connect.selectedDevice;
+ // Pass actions BEFORE
+ next(action);
+
+
+
+ const { devices, selectedDevice } = store.getState().connect;
+ if (!selectedDevice) {
+ store.dispatch( push('/') );
+ } else if (previousSelectedDevice.path !== selectedDevice.path || previousSelectedDevice.instance !== selectedDevice.instance) {
+
+ // interrupt discovery process
+ store.dispatch( TrezorConnectActions.stopDiscoveryProcess(previousSelectedDevice) );
+
+ initSelectedDevice(store, selectedDevice);
+ }
+
+ } else if (action.type === TRANSPORT.ERROR) {
+ next(action);
+ store.dispatch( push('/') );
+ } else {
+ // Pass all actions through by default
+ next(action);
+ }
+
+
+
+ if (action.type === STORAGE.READY) {
+
+ // TODO: check offline devices
+
+ // set listeners
+
+ TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
+ // post event to TrezorConnectReducer
+ store.dispatch({
+ type: event.type,
+ device: event.data
+ });
+ });
+
+ const version: Object = TrezorConnect.getVersion();
+ if (version.type === 'library') {
+ // handle UI events only if TrezorConnect isn't using popup
+ TrezorConnect.on(UI_EVENT, (type: string, data: any): void => {
+ // post event to reducers
+ store.dispatch({
+ type,
+ data
+ });
+ });
+ }
+
+ // init TrezorConnect library
+
+ TrezorConnect.init({
+ hostname: 'localhost', // TODO: controll it in Connect
+ transport_reconnect: false,
+ })
+ .then(() => {
+ // post action inited
+ //store.dispatch({ type: 'WEB3_START' });
+
+ setTimeout(() => {
+ store.dispatch( initWeb3() );
+ }, 2000)
+
+ })
+ .catch(error => {
+ store.dispatch({
+ type: CONNECT.INITIALIZATION_ERROR,
+ error
+ })
+ });
+
+ } else if (action.type === WEB3.READY) {
+
+ const handleDeviceConnect = (device) => {
+ initSelectedDevice(store, device);
+ }
+
+ const handleDeviceDisconnect = (device) => {
+ // remove addresses and discovery from state
+ store.dispatch( TrezorConnectActions.remove(device) );
+ }
+
+ TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
+ TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
+
+ TrezorConnect.on(DEVICE.DISCONNECT, handleDeviceDisconnect);
+ TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceDisconnect);
+
+ // solve possible race condition:
+ // device was connected before Web3 initialized so we need to force DEVICE.CONNECT event on them
+ const { devices } = store.getState().connect;
+ for (let d of devices) {
+ handleDeviceConnect(d);
+ }
+
+ }
+
+};
+
+export default TrezorConnectService;
\ No newline at end of file
diff --git a/src/js/services/TrezorConnectService.js b/src/js/services/TrezorConnectService.js
index a382a829..8776e6ba 100644
--- a/src/js/services/TrezorConnectService.js
+++ b/src/js/services/TrezorConnectService.js
@@ -1,65 +1,93 @@
/* @flow */
'use strict';
-import { LOCATION_CHANGE } from 'react-router-redux';
+import { LOCATION_CHANGE, push } from 'react-router-redux';
-import TrezorConnect, { DEVICE_EVENT, UI_EVENT, UI, DEVICE } from 'trezor-connect';
+import TrezorConnect, { TRANSPORT, DEVICE_EVENT, UI_EVENT, UI, DEVICE } from 'trezor-connect';
import * as TrezorConnectActions from '../actions/TrezorConnectActions';
+import * as ModalActions from '../actions/ModalActions';
+import { init as initWeb3 } from '../actions/Web3Actions';
+import * as WEB3 from '../actions/constants/Web3';
+import * as STORAGE from '../actions/constants/LocalStorage';
+import * as CONNECT from '../actions/constants/TrezorConnect';
+import * as ACTIONS from '../actions';
-let inited: boolean = false;
-const TrezorConnectService = store => next => action => {
- // Pass all actions through by default
+const TrezorConnectService = (store: any) => (next: any) => (action: any) => {
+
+ const prevState = store.getState().connect;
+ const prevModalState = store.getState().connect;
+
next(action);
- if (action.type === LOCATION_CHANGE && !inited) {
- inited = true;
+ if (action.type === STORAGE.READY) {
+ store.dispatch( TrezorConnectActions.init() );
+
+ } else if (action.type === TRANSPORT.ERROR) {
+ store.dispatch( push('/') );
+
+ } else if (action.type === WEB3.READY) {
+ store.dispatch( TrezorConnectActions.postInit() );
- TrezorConnect.init()
- .then(r => {
- // post action inited
- })
- .catch(error => {
- // TODO: show some ui with errors
- console.log("ERROR", error);
- });
+ } else if (action.type === DEVICE.DISCONNECT) {
+ store.dispatch( TrezorConnectActions.deviceDisconnect(action.device) );
- TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
- // post event to reducer
+ } else if (action.type === CONNECT.FORGET) {
+ //store.dispatch( TrezorConnectActions.forgetDevice(action.device) );
+ store.dispatch( TrezorConnectActions.switchToFirstAvailableDevice() );
+ } else if (action.type === CONNECT.FORGET_SINGLE) {
+
+ //store.dispatch( TrezorConnectActions.forgetDevice(action.device) );
+
+ if (store.getState().connect.devices.length < 1 && action.device.connected) {
+ // prompt disconnect device modal
store.dispatch({
- type: event.type,
- device: event.data
+ type: CONNECT.DISCONNECT_REQUEST,
+ device: action.device
});
- });
+ } else {
+ store.dispatch( TrezorConnectActions.switchToFirstAvailableDevice() );
+ }
+ } else if (action.type === DEVICE.CHANGED) {
+ // selected device was previously unacquired, but now it's acquired
+ // we need to change route
+ if (prevState.selectedDevice) {
+ if (!action.device.unacquired && action.device.path === prevState.selectedDevice.id) {
+ store.dispatch( TrezorConnectActions.onSelectDevice(action.device) );
+ }
+ }
+ } else if (action.type === DEVICE.CONNECT || action.type === DEVICE.CONNECT_UNACQUIRED) {
- const version: Object = TrezorConnect.getVersion();
+ store.dispatch( TrezorConnectActions.restoreDiscovery() );
- if (version.type === 'library') {
- // handle UI events only if TrezorConnect isn't using popup
- TrezorConnect.on(UI_EVENT, (type: string, data: any): void => {
- // post event to reducer
+ // interrupt process of remembering device (force forget)
+ // TODO: the same for disconnect more than 1 device at once
+ const { modal } = store.getState();
+ if (modal.opened && modal.windowType === CONNECT.REMEMBER_REQUEST) {
+ if (action.device.features && modal.device.features.device_id === action.device.features.device_id) {
store.dispatch({
- type,
- data
+ type: ACTIONS.CLOSE_MODAL,
});
- });
- }
-
- const handleDeviceConnect = (device) => {
- store.dispatch( TrezorConnectActions.discover(device.path) );
+ } else {
+ store.dispatch({
+ type: CONNECT.FORGET,
+ device: modal.device
+ });
+ }
}
- const handleDeviceDisconnect = (device) => {
- store.dispatch( TrezorConnectActions.remove(device.path) );
- }
+ } else if (action.type === CONNECT.AUTH_DEVICE) {
+ store.dispatch( TrezorConnectActions.checkDiscoveryStatus() );
- TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
- //TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
+ } else if (action.type === CONNECT.DUPLICATE) {
+ store.dispatch( TrezorConnectActions.onDuplicateDevice() );
- TrezorConnect.on(DEVICE.DISCONNECT, handleDeviceDisconnect);
- //TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
+ } else if (action.type === DEVICE.ACQUIRED || action.type === CONNECT.SELECT_DEVICE) {
+ store.dispatch( TrezorConnectActions.getSelectedDeviceState() );
+ } else if (action.type === CONNECT.COIN_CHANGED) {
+ store.dispatch( TrezorConnectActions.coinChanged( action.payload.coin ) );
}
-};
+}
export default TrezorConnectService;
\ No newline at end of file
diff --git a/src/js/services/Web3Service.js b/src/js/services/Web3Service.js
index defb129b..c85fe007 100644
--- a/src/js/services/Web3Service.js
+++ b/src/js/services/Web3Service.js
@@ -4,102 +4,171 @@
import Web3 from 'web3';
import { LOCATION_CHANGE } from 'react-router-redux';
import * as ACTIONS from '../actions/index';
+import { getBalance, getGasPrice, getTransactionReceipt } from '../actions/Web3Actions';
import { loadTransactionStatus } from './EtherscanService';
import BigNumber from 'bignumber.js';
let web3: Web3;
-// let pendingTxs: Array = [];
-
-
-export const getGasPrice = (): Promise => {
- return (dispatch, getState) => {
- web3.eth.getGasPrice((error, gasPrice) => {
- if (!error) {
- dispatch({
- type: 'update_gas',
- gasPrice: web3.fromWei(gasPrice.toString(), 'gwei')
- })
- }
- });
- }
-}
+// export const getGasPrice = (): Promise => {
+// return (dispatch, getState) => {
+// web3.eth.getGasPrice((error, gasPrice) => {
+// if (!error) {
+// dispatch({
+// type: 'update_gas',
+// gasPrice: web3.fromWei(gasPrice.toString(), 'gwei')
+// })
+// }
+// });
+// }
+// }
const Web3Service = store => next => action => {
- switch (action.type) {
-
- // case ACTIONS.ON_TX_COMPLETE :
- // pendingTxs.push(action.txid);
-
- // // const refreshBalance = async (sender) => {
- // // let balance = await getBalance(sender.address);
- // // store.dispatch({
- // // type: ACTIONS.ADDRESS_SET_BALANCE,
- // // address: sender,
- // // balance: web3.fromWei(balance.toString(), 'ether')
- // // })
- // // }
-
- // // const sender = action.address;
- // // setInterval( async () =>{
- // // let balance = await getBalance(sender.address);
- // // console.log("update balance", web3.fromWei(balance.toString(), 'ether') )
- // // }, 2000);
-
- // break;
+ next(action);
- // case ACTIONS.TX_STATUS_OK :
- // let pendingIndex = pendingTxs.indexOf(action.txid);
- // if (pendingIndex >= 0) {
- // pendingTxs.splice(pendingIndex, 1);
- // }
- // break;
+ switch (action.type) {
- case LOCATION_CHANGE :
+ case 'WEB_2_START' :
if (web3) break;
- web3 = new Web3(window.web3.currentProvider);
+ //web3 = new Web3(window.web3.currentProvider);
//web3 = new Web3(new Web3.providers.HttpProvider("https://api.myetherapi.com/rop"));
- //web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4"));
- //web3 = new Web3("ws://34.230.234.51:30303");
- //web3 = new Web3("ws://58.56.184.146:45536");
- //web3 = new Web3(new Web3.providers.HttpProvider('https://api.myetherapi.com/rop'));
- //web3.setProvider(new Web3.providers.HttpProvider('https://api.myetherapi.com/rop') );
+ web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io2/QGyVKozSUEh2YhL4s2G4"));
//web3 = new Web3( new Web3.providers.HttpProvider("ws://34.230.234.51:30303") );
+
+
+
+ /*store.dispatch( getGasPrice() );
+
+
+ const latestBlockFilter = web3.eth.filter('latest');
+ latestBlockFilter.watch((error, blockHash) => {
+
+ const { addresses, pendingTxs } = store.getState().addresses;
+
+ for (const addr of addresses) {
+ store.dispatch( getBalance(addr) );
+ }
+
+ store.dispatch( getGasPrice() );
+
+ if (pendingTxs.length > 0) {
+ for (const tx of pendingTxs) {
+ store.dispatch( getTransactionReceipt(tx) );
+ }
+ }
+ });*/
+
+
+
+ // store.dispatch({
+ // type: 'web3__init',
+ // web3
+ // });
+
// store.dispatch({
// type: 'update_gas',
// gasPrice: web3.fromWei(web3.eth.gasPrice, 'gwei')
// })
- const latestBlockFilter = web3.eth.filter('latest');
- //const latestBlockFilter = web3.eth.filter('pending');
- latestBlockFilter.watch((error, txid) => {
- //console.log("Watch latest block", txid, error);
+ /*
+ {
+ "dd62ed3e": "allowance(address,address)",
+ "095ea7b3": "approve(address,uint256)",
+ "cae9ca51": "approveAndCall(address,uint256,bytes)",
+ "70a08231": "balanceOf(address)",
+ "313ce567": "decimals()",
+ "06fdde03": "name()",
+ "95d89b41": "symbol()",
+ "18160ddd": "totalSupply()",
+ "a9059cbb": "transfer(address,uint256)",
+ "23b872dd": "transferFrom(address,address,uint256)",
+ "54fd4d50": "version()"
+ }*/
+
+ // var balanceHex = "06fdde03"; // I believe this is the hex for balance
+ // var contractAddress = "0x58cda554935e4a1f2acbe15f8757400af275e084";
+ // var userAddress = "0x5DBB9793537515398A1176d365b636A5321D9e39";
+ // var balanceCall = getDataObj(contractAddress, balanceHex);
+ // var balance = web3.eth.call(balanceCall);
+
+
+
+
+ const abiArray = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":false,"stateMutability":"nonpayable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}];
+ //const contr = web3.eth.contract(abiArray, '0x58cda554935e4a1f2acbe15f8757400af275e084');
+ const contr = web3.eth.contract(abiArray).at('0x58cda554935e4a1f2acbe15f8757400af275e084');
+ console.log("contr", contr );
+
+ contr.name.call((e,r) => {
+ console.log("nameeeee", e, r)
+ })
- store.dispatch( getGasPrice() );
+ contr.symbol.call((e,r) => {
+ console.log("symboll", e, r)
+ })
- const { pendingTxs } = store.getState().addresses;
+ //console.log( const.name )
- if (pendingTxs.length > 0) {
- // let pendingTxIndex = pendingTxs.indexOf(txid);
- // console.error("---->>>Watch latest block", pendingTxIndex, txid, pendingTxs)
- // if (pendingTxIndex >= 0) {
- // pendingTxs.splice(pendingTxIndex, 1);
- // }
+ // contr.balanceOf('0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad', (e, r) => {
+ // console.warn('contrR', e, r.toString(10));
+ // });
- store.dispatch( loadTransactionStatus(pendingTxs[0]) );
- }
+ let cntrData = contr.transfer.getData("0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad", 1, {
+ from: "0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad",
+ gasLimit: 36158,
+ gasPrice: "0x0ee6b28000"
+ })
- // if (!error) {
- // web3.eth.getTransactionReceipt(txid, (error, tx) => {
- // console.log("LatestTX", txid, tx, error)
- // })
- // }
- });
+ console.log("contr", cntrData);
+ // const data = contr.transferFrom(
+ // '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
+ // '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
+ // 1
+ // );
+
+ // const data = contr.transferFrom(
+ // '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
+ // {
+ // from: '0x7314e0f1c0e28474bdb6be3e2c3e0453255188f8',
+ // value: 1
+ // }
+ // );
+
+ // const data = contr.transferFrom(
+ // '0x00000000000000000000000098ead4bd2fbbb0cf0b49459aa0510ef53faa6cad',
+ // '0x000000000000000000000000a738ea40b69d87f4f9ac94c9a0763f96248df23b',
+ // 2
+ // );
+ //console.log("contr", contr, data)
+
+ // var addr1 = '0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad';
+ // var contractAddr = '0x58cda554935e4a1f2acbe15f8757400af275e084';
+ // var tknAddress = (addr1).substring(2);
+ // var contractData = ('0x70a08231000000000000000000000000' + tknAddress);
+
+ // console.warn("ADDDDDDDD", web3.toHex('0x98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad'));
+ // console.warn("ADDDDDDDD", web3.toHex('98ead4bd2fbbb0cf0b49459aa0510ef53faa6cad'));
+
+ // web3.eth.call({
+ // to: contractAddr,
+ // data: contractData
+ // }, function(err, result) {
+ // if (result) {
+ // console.log("---------result", result, web3);
+ // //var tokens = web3.toBN(result).toString();
+ // //console.log('Tokens Owned: ' + web3.utils.fromWei(tokens, 'ether'));
+ // }
+ // else {
+ // console.log(err); // Dump errors here
+ // }
+ // });
+
+
/*
const pendingBlockFilter = web3.eth.filter('pending');
@@ -145,49 +214,16 @@ const Web3Service = store => next => action => {
//"web3": "^0.19.0"
- console.log("WEB#", web3)
- store.dispatch({
- type: 'web3__init',
- web3
- })
+
break;
}
- next(action);
+
};
export default Web3Service;
-const updateGas = async () => {
-
-}
-
-export const watchPendingTx = (address: string): Promise => {
- return new Promise((resolve, reject) => {
- web3.eth.getTransaction(txid, (error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- });
-}
-
-export const getBalance = (address: string): Promise => {
- return new Promise((resolve, reject) => {
- web3.eth.getBalance(address, (error, result) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
- });
-}
-
-
export const estimateGas = (gasOptions): Promise => {
return new Promise((resolve, reject) => {
diff --git a/src/js/services/index.js b/src/js/services/index.js
index 318e6a30..4e68a0b4 100644
--- a/src/js/services/index.js
+++ b/src/js/services/index.js
@@ -2,13 +2,17 @@
'use strict';
import RouterService from './RouterService';
+import LocalStorageService from './LocalStorageService';
+import CoinmarketcapService from './CoinmarketcapService';
import TrezorConnectService from './TrezorConnectService';
import Web3Service from './Web3Service';
import EtherscanService from './EtherscanService';
export default [
RouterService,
+ LocalStorageService,
TrezorConnectService,
Web3Service,
- EtherscanService,
+ CoinmarketcapService,
+ //EtherscanService,
];
\ No newline at end of file
diff --git a/src/js/store/store.dev.js b/src/js/store/store.dev.js
index ee97c457..4120d399 100644
--- a/src/js/store/store.dev.js
+++ b/src/js/store/store.dev.js
@@ -2,9 +2,10 @@
'use strict';
import { createStore, applyMiddleware, compose } from 'redux';
-import { routerMiddleware, push } from 'react-router-redux';
+import { syncHistoryWithStore, routerMiddleware, push } from 'react-router-redux';
import thunk from 'redux-thunk';
-//import createHistory from 'history/createBrowserHistory';
+// import createHistory from 'history/createBrowserHistory';
+// import { useRouterHistory } from 'react-router';
import createHistory from 'history/createHashHistory';
import { createLogger } from 'redux-logger';
import reducers from '../reducers';
@@ -12,7 +13,7 @@ import services from '../services';
import { Middleware } from 'redux';
import { GenericStoreEnhancer } from 'redux';
-export const history = createHistory();
+export const history = createHistory( { queryKey: false } );
const initialState: any = {};
const enhancers = [];
@@ -23,7 +24,7 @@ const middleware = [
const excludeLogger = (getState: any, action: any): boolean => {
//'@@router/LOCATION_CHANGE'
- let excluded = ['MQTT_PING'];
+ let excluded = ['LOG_TO_EXCLUDE'];
let pass = excluded.filter((act) => {
return action.type === act;
});
diff --git a/src/js/utils/networkUtils.js b/src/js/utils/networkUtils.js
index e135d70d..c1b44893 100644
--- a/src/js/utils/networkUtils.js
+++ b/src/js/utils/networkUtils.js
@@ -15,7 +15,7 @@ export const httpRequest = async (url: string, type: string = 'text'): any => {
return await response.text();
}
} else {
- throw new Error(response.statusText);
+ throw new Error(`${ url } ${ response.statusText }`);
}
// return fetch(url, { credentials: 'same-origin' }).then((response) => {
diff --git a/src/js/utils/promiseUtils.js b/src/js/utils/promiseUtils.js
new file mode 100644
index 00000000..f1785b45
--- /dev/null
+++ b/src/js/utils/promiseUtils.js
@@ -0,0 +1,12 @@
+/* @flow */
+'use strict';
+
+// import root from 'window-or-global';
+// import Promise from 'es6-promise';
+
+export async function resolveAfter(msec: number, value: any = null): Promise {
+ return await new Promise((resolve) => {
+ //root.setTimeout(resolve, msec, value);
+ window.setTimeout(resolve, msec, value);
+ });
+}
\ No newline at end of file
diff --git a/src/js/utils/reducerUtils.js b/src/js/utils/reducerUtils.js
new file mode 100644
index 00000000..a8e2a83e
--- /dev/null
+++ b/src/js/utils/reducerUtils.js
@@ -0,0 +1,12 @@
+/* @flow */
+'use strict';
+
+
+export const getAccounts = (accounts: Array, device: any, coin: ?string): Array => {
+ if (coin) {
+ return accounts.filter((addr) => addr.checksum === device.checksum && addr.coin === coin);
+ } else {
+ return accounts.filter((addr) => addr.checksum === device.checksum);
+ }
+
+}
\ No newline at end of file
diff --git a/src/js/utils/windowUtils.js b/src/js/utils/windowUtils.js
new file mode 100644
index 00000000..4f2894c4
--- /dev/null
+++ b/src/js/utils/windowUtils.js
@@ -0,0 +1,18 @@
+/* @flow */
+'use strict';
+
+export const getViewportHeight = (): number => (
+ window.innerHeight
+ || document.documentElement.clientHeight
+ || document.body.clientHeight
+)
+
+export const getScrollY = (): number => {
+ if (window.pageYOffset !== undefined) {
+ return window.pageYOffset;
+ } else if (window.scrollTop !== undefined) {
+ return window.scrollTop;
+ } else {
+ return (document.documentElement || document.body.parentNode || document.body).scrollTop;
+ }
+}
\ No newline at end of file
diff --git a/src/solidity/erc20.json b/src/solidity/erc20.json
new file mode 100644
index 00000000..7d0efcda
--- /dev/null
+++ b/src/solidity/erc20.json
@@ -0,0 +1,272 @@
+module.exports = [
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_spender",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "approve",
+ "outputs": [
+ {
+ "name": "success",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferFrom",
+ "outputs": [
+ {
+ "name": "success",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "version",
+ "outputs": [
+ {
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "name": "balance",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [
+ {
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "transfer",
+ "outputs": [
+ {
+ "name": "success",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_spender",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ },
+ {
+ "name": "_extraData",
+ "type": "bytes"
+ }
+ ],
+ "name": "approveAndCall",
+ "outputs": [
+ {
+ "name": "success",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "name": "_spender",
+ "type": "address"
+ }
+ ],
+ "name": "allowance",
+ "outputs": [
+ {
+ "name": "remaining",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "name": "_initialAmount",
+ "type": "uint256"
+ },
+ {
+ "name": "_tokenName",
+ "type": "string"
+ },
+ {
+ "name": "_decimalUnits",
+ "type": "uint8"
+ },
+ {
+ "name": "_tokenSymbol",
+ "type": "string"
+ }
+ ],
+ "type": "constructor"
+ },
+ {
+ "payable": false,
+ "type": "fallback"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "_spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ ]
\ No newline at end of file
diff --git a/src/solidity/lahodka-token.js b/src/solidity/lahodka-token.js
new file mode 100644
index 00000000..2c2467b9
--- /dev/null
+++ b/src/solidity/lahodka-token.js
@@ -0,0 +1,137 @@
+pragma solidity ^0.4.4;
+
+contract Token {
+
+ /// @return total amount of tokens
+ function totalSupply() constant returns (uint256 supply) {}
+
+ /// @param _owner The address from which the balance will be retrieved
+ /// @return The balance
+ function balanceOf(address _owner) constant returns (uint256 balance) {}
+
+ /// @notice send `_value` token to `_to` from `msg.sender`
+ /// @param _to The address of the recipient
+ /// @param _value The amount of token to be transferred
+ /// @return Whether the transfer was successful or not
+ function transfer(address _to, uint256 _value) returns (bool success) {}
+
+ /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
+ /// @param _from The address of the sender
+ /// @param _to The address of the recipient
+ /// @param _value The amount of token to be transferred
+ /// @return Whether the transfer was successful or not
+ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}
+
+ /// @notice `msg.sender` approves `_addr` to spend `_value` tokens
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @param _value The amount of wei to be approved for transfer
+ /// @return Whether the approval was successful or not
+ function approve(address _spender, uint256 _value) returns (bool success) {}
+
+ /// @param _owner The address of the account owning tokens
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @return Amount of remaining tokens allowed to spent
+ function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}
+
+ event Transfer(address indexed _from, address indexed _to, uint256 _value);
+ event Approval(address indexed _owner, address indexed _spender, uint256 _value);
+
+}
+
+
+
+contract StandardToken is Token {
+
+ function transfer(address _to, uint256 _value) returns (bool success) {
+ //Default assumes totalSupply can't be over max (2^256 - 1).
+ //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
+ //Replace the if with this one instead.
+ //if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
+ if (balances[msg.sender] >= _value && _value > 0) {
+ balances[msg.sender] -= _value;
+ balances[_to] += _value;
+ Transfer(msg.sender, _to, _value);
+ return true;
+ } else { return false; }
+ }
+
+ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
+ //same as above. Replace this line with the following if you want to protect against wrapping uints.
+ //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
+ if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
+ balances[_to] += _value;
+ balances[_from] -= _value;
+ allowed[_from][msg.sender] -= _value;
+ Transfer(_from, _to, _value);
+ return true;
+ } else { return false; }
+ }
+
+ function balanceOf(address _owner) constant returns (uint256 balance) {
+ return balances[_owner];
+ }
+
+ function approve(address _spender, uint256 _value) returns (bool success) {
+ allowed[msg.sender][_spender] = _value;
+ Approval(msg.sender, _spender, _value);
+ return true;
+ }
+
+ function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
+ return allowed[_owner][_spender];
+ }
+
+ mapping (address => uint256) balances;
+ mapping (address => mapping (address => uint256)) allowed;
+ uint256 public totalSupply;
+}
+
+
+//name this contract whatever you'd like
+contract Lahodkoin is StandardToken {
+
+ function () {
+ //if ether is sent to this address, send it back.
+ throw;
+ }
+
+ /* Public variables of the token */
+
+ /*
+ NOTE:
+ The following variables are OPTIONAL vanities. One does not have to include them.
+ They allow one to customise the token contract & in no way influences the core functionality.
+ Some wallets/interfaces might not even bother to look at this information.
+ */
+ string public name; //fancy name: eg Simon Bucks
+ uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
+ string public symbol; //An identifier: eg SBX
+ string public version = 'H1.0'; //human 0.1 standard. Just an arbitrary versioning scheme.
+
+//
+// CHANGE THESE VALUES FOR YOUR TOKEN
+//
+
+//make sure this function name matches the contract name above. So if you're token is called TutorialToken, make sure the //contract name above is also TutorialToken instead of ERC20Token
+
+ function Lahodkoin(
+ ) {
+ balances[msg.sender] = 5; // Give the creator all initial tokens (100000 for example)
+ totalSupply = 5; // Update total supply (100000 for example)
+ name = "Lahodkoin"; // Set the name for display purposes
+ decimals = 0; // Amount of decimals for display purposes
+ symbol = "LAHODKY"; // Set the symbol for display purposes
+ }
+
+ /* Approves and then calls the receiving contract */
+ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
+ allowed[msg.sender][_spender] = _value;
+ Approval(msg.sender, _spender, _value);
+
+ //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
+ //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
+ //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
+ if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/solidity/test-token.js b/src/solidity/test-token.js
new file mode 100644
index 00000000..b83db55c
--- /dev/null
+++ b/src/solidity/test-token.js
@@ -0,0 +1,137 @@
+pragma solidity ^0.4.4;
+
+contract Token {
+
+ /// @return total amount of tokens
+ function totalSupply() constant returns (uint256 supply) {}
+
+ /// @param _owner The address from which the balance will be retrieved
+ /// @return The balance
+ function balanceOf(address _owner) constant returns (uint256 balance) {}
+
+ /// @notice send `_value` token to `_to` from `msg.sender`
+ /// @param _to The address of the recipient
+ /// @param _value The amount of token to be transferred
+ /// @return Whether the transfer was successful or not
+ function transfer(address _to, uint256 _value) returns (bool success) {}
+
+ /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
+ /// @param _from The address of the sender
+ /// @param _to The address of the recipient
+ /// @param _value The amount of token to be transferred
+ /// @return Whether the transfer was successful or not
+ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}
+
+ /// @notice `msg.sender` approves `_addr` to spend `_value` tokens
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @param _value The amount of wei to be approved for transfer
+ /// @return Whether the approval was successful or not
+ function approve(address _spender, uint256 _value) returns (bool success) {}
+
+ /// @param _owner The address of the account owning tokens
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @return Amount of remaining tokens allowed to spent
+ function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}
+
+ event Transfer(address indexed _from, address indexed _to, uint256 _value);
+ event Approval(address indexed _owner, address indexed _spender, uint256 _value);
+
+}
+
+
+
+contract StandardToken is Token {
+
+ function transfer(address _to, uint256 _value) returns (bool success) {
+ //Default assumes totalSupply can't be over max (2^256 - 1).
+ //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
+ //Replace the if with this one instead.
+ //if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
+ if (balances[msg.sender] >= _value && _value > 0) {
+ balances[msg.sender] -= _value;
+ balances[_to] += _value;
+ Transfer(msg.sender, _to, _value);
+ return true;
+ } else { return false; }
+ }
+
+ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
+ //same as above. Replace this line with the following if you want to protect against wrapping uints.
+ //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
+ if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
+ balances[_to] += _value;
+ balances[_from] -= _value;
+ allowed[_from][msg.sender] -= _value;
+ Transfer(_from, _to, _value);
+ return true;
+ } else { return false; }
+ }
+
+ function balanceOf(address _owner) constant returns (uint256 balance) {
+ return balances[_owner];
+ }
+
+ function approve(address _spender, uint256 _value) returns (bool success) {
+ allowed[msg.sender][_spender] = _value;
+ Approval(msg.sender, _spender, _value);
+ return true;
+ }
+
+ function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
+ return allowed[_owner][_spender];
+ }
+
+ mapping (address => uint256) balances;
+ mapping (address => mapping (address => uint256)) allowed;
+ uint256 public totalSupply;
+}
+
+
+//name this contract whatever you'd like
+contract ERC20Token is StandardToken {
+
+ function () {
+ //if ether is sent to this address, send it back.
+ throw;
+ }
+
+ /* Public variables of the token */
+
+ /*
+ NOTE:
+ The following variables are OPTIONAL vanities. One does not have to include them.
+ They allow one to customise the token contract & in no way influences the core functionality.
+ Some wallets/interfaces might not even bother to look at this information.
+ */
+ string public name; //fancy name: eg Simon Bucks
+ uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
+ string public symbol; //An identifier: eg SBX
+ string public version = 'H1.0'; //human 0.1 standard. Just an arbitrary versioning scheme.
+
+//
+// CHANGE THESE VALUES FOR YOUR TOKEN
+//
+
+//make sure this function name matches the contract name above. So if you're token is called TutorialToken, make sure the //contract name above is also TutorialToken instead of ERC20Token
+
+ function ERC20Token(
+ ) {
+ balances[msg.sender] = 1000; // Give the creator all initial tokens (100000 for example)
+ totalSupply = 1000; // Update total supply (100000 for example)
+ name = "Trezor01"; // Set the name for display purposes
+ decimals = 0; // Amount of decimals for display purposes
+ symbol = "T01"; // Set the symbol for display purposes
+ }
+
+ /* Approves and then calls the receiving contract */
+ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
+ allowed[msg.sender][_spender] = _value;
+ Approval(msg.sender, _spender, _value);
+
+ //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
+ //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
+ //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
+ if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/accounts.less b/src/styles/accounts.less
deleted file mode 100644
index f8d542af..00000000
--- a/src/styles/accounts.less
+++ /dev/null
@@ -1,27 +0,0 @@
-.accounts {
- width: 25%;
-
- .header {
- background: red;
- }
-
- a {
- position: relative;
- display: block;
- cursor: pointer;
- padding: 10px 15px;
- white-space: nowrap;
- color: black;
- &.selected {
- background: #FFFFFF;
- border-left: 4px solid @color_green;
- padding-left: 11px;
- }
-
- span {
- display: block;
- font-size: 12px;
- color: gray;
- }
- }
-}
\ No newline at end of file
diff --git a/src/styles/acquire.less b/src/styles/acquire.less
new file mode 100644
index 00000000..93eb6b3f
--- /dev/null
+++ b/src/styles/acquire.less
@@ -0,0 +1,50 @@
+.acquire {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: @color_white;
+
+ .warning {
+ background: @color_info_secondary;
+ display: flex;
+ flex-direction: row;
+ padding: 26px 39px 26px 80px;
+
+ div {
+ flex: 1;
+ position: relative;
+ }
+
+ h2 {
+ color: @color_info_primary;
+ font-size: 14px;
+ -webkit-font-smoothing: auto;
+ margin-bottom: 5px;
+ padding: 0px;
+
+ &:before {
+ .icomoon-info;
+ position: absolute;
+ top: -7px;
+ left: -32px;
+ font-size: 32px;
+ }
+ }
+
+ p {
+ color: @color_info_primary;
+ font-size: 12px;
+ padding: 0px;
+ }
+
+ }
+
+ // h2 {
+ // line-height: 74px;
+ // padding-left: 50px;
+ // }
+
+ // p {
+ // padding-left: 50px;
+ // }
+}
\ No newline at end of file
diff --git a/src/styles/aside.less b/src/styles/aside.less
new file mode 100644
index 00000000..f2e5954b
--- /dev/null
+++ b/src/styles/aside.less
@@ -0,0 +1,471 @@
+aside {
+ position: relative;
+ width: 320px;
+ min-width: 320px;
+ border-right: 1px solid @color_divider;
+ //display: flex;
+ //flex-direction: column;
+ overflow-x: hidden;
+
+ .Select {
+ width: 320px;
+ height: 64px;
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
+
+ .Select-control {
+ height: 63px;
+ border: 0px;
+ // border-radius: 4px 0px 0px 0px;
+ border-right: 1px solid @color_divider;
+ border-bottom: 1px solid @color_divider;
+ transition: color 0.3s ease-in-out;
+ }
+
+ .Select-arrow-zone {
+ right: 24px;
+ }
+
+ .Select-menu-outer {
+ visibility: hidden;
+ }
+
+ &.is-open {
+ .Select-control {
+ border-color: @color_divider;
+ }
+ }
+
+ &.is-disabled {
+ .Select-control {
+ background: @color_white;
+ cursor: default;
+ }
+
+ .Select-arrow {
+ visibility: hidden;
+ &:after {
+ content: ''
+ }
+ }
+
+ .device {
+ .device-menu {
+ padding-right: 24px;
+ }
+ }
+ }
+ }
+
+ .sticky-container {
+ position: relative;
+ top: 0;
+ width: 320px;
+ overflow: hidden;
+
+ &.fixed {
+ position: fixed;
+ border-right: 1px solid @color_divider;
+ }
+
+ &.fixed-bottom {
+ padding-bottom: 60px; // height of .help
+ .help {
+ position: fixed;
+ bottom: 0;
+ background: @color_main;
+ border-right: 1px solid @color_divider;
+ }
+ }
+ }
+
+ .transition-container {
+ width: 640px;
+
+ section {
+ width: 320px;
+ display: inline-block;
+ vertical-align: top;
+ }
+ }
+
+ .device {
+ position: relative;
+ height: 63px;
+ width: 319px;
+ display: flex;
+ align-items: center;
+ padding-left: 80px;
+
+ &.item {
+ padding-right: 24px;
+ cursor: pointer;
+ .hover();
+ &:hover {
+ background: @color_gray_light;
+ }
+ }
+
+ &:before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 13px;
+ height: 25px;
+ z-index: 2;
+ left: 33px;
+ top: 17px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 13px 25px;
+ background-image: url('../images/icontrezor.png');
+ }
+
+ .label-container {
+ flex: 1;
+ overflow: hidden;
+ span {
+ display: block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+
+ &.label {
+ font-weight: 500;
+ font-size: 14px;
+ color: @color_text_primary;
+ }
+
+ &.status {
+ font-size: 12px;
+ color: @color_text_secondary;
+ }
+ }
+ }
+
+ .device-menu {
+ display: flex;
+ justify-content: flex-end;
+ padding-right: 48px;
+ padding-left: 4px;
+
+ div {
+ display: inline-block;
+ }
+
+ .forget,
+ .settings,
+ .acquire {
+ cursor: pointer;
+
+ &:before {
+ .icomoon-refresh;
+ color: @color_text_secondary;
+ position: relative;
+ font-size: 24px;
+ .hover();
+ }
+
+ &:hover {
+ &:before {
+ color: @color_text_primary;
+ }
+ }
+ }
+
+ .forget {
+ &:before {
+ .icomoon-eject;
+ }
+ }
+
+ .settings {
+ &:before {
+ .icomoon-settings;
+ }
+ }
+ }
+
+ }
+
+ a {
+ position: relative;
+ display: block;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 16px 0 16px 30px;
+ white-space: nowrap;
+ color: @color_text_primary;
+ height: 50px;
+
+ .hover();
+
+ &:hover {
+ background: @color_gray_light;
+ }
+
+ &.account {
+ height: 64px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ font-size: 14px;
+ border-top: 1px solid @color_divider;
+ span {
+ display: block;
+ font-size: 12px;
+ color: @color_text_secondary;
+ }
+
+ // &:last-child {
+ // border-bottom: 1px solid @color_divider;
+ // }
+ }
+
+ &.selected {
+ background: @color_white;
+ border-left: 3px solid @color_green_primary;
+ padding-left: 27px;
+
+ &:hover {
+ background: @color_white;
+ }
+ }
+
+ &.coin {
+ padding-left: 80px;
+ &:before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 20px;
+ height: 20px;
+ left: 30px;
+ top: 0px;
+ bottom: 0px;
+ margin: auto 0;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 20px 20px;
+
+ }
+ }
+
+ &.external {
+ &:after {
+ .icomoon-redirect;
+ position: absolute;
+ display: block;
+ width: 30px;
+ height: 30px;
+ right: 23px;
+ top: 0px;
+ bottom: 0px;
+ margin: auto 0;
+ font-size: 30px;
+ color: @color_text_secondary;
+ .hover();
+ }
+
+ &:hover:after {
+ color: @color_text_primary;
+ }
+ }
+
+ &.back {
+ padding-left: 100px;
+
+ &:before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 20px;
+ height: 20px;
+ left: 76px;
+ top: 0px;
+ bottom: 0px;
+ margin: auto 0;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 20px 20px;
+ }
+
+ &:after {
+ .icomoon-arrow-left;
+ position: absolute;
+ display: block;
+ width: 20px;
+ height: 20px;
+ left: 24px;
+ top: 0px;
+ bottom: 0px;
+ margin: auto 0;
+ font-size: 20px;
+ }
+ }
+
+ &.eth:before {
+ background-image: url('../images/eth-logo.png');
+ background-size: auto 20px;
+ }
+ &.etc:before {
+ background-image: url('../images/etc-logo.png');
+ background-size: auto 20px;
+ }
+
+ &.btc:before {
+ background-image: url('../images/btc-logo.png');
+ }
+ &.bch:before {
+ background-image: url('../images/bch-logo.png');
+ }
+ &.btg:before {
+ background-image: url('../images/btg-logo.png');
+ }
+ &.ltc:before {
+ background-image: url('../images/ltc-logo.png');
+ }
+ &.dash:before {
+ background-image: url('../images/dash-logo.png');
+ }
+ &.zec:before {
+ background-image: url('../images/zec-logo.png');
+ }
+
+
+ }
+
+ .coin-divider {
+ font-size: 12px;
+ display: flex;
+ justify-content: space-between;
+ color: @color_text_secondary;
+ background: @color_gray_light;
+ padding: 8px 30px 8px 31px;
+ border-top: 1px solid @color_divider;
+ border-bottom: 1px solid @color_divider;
+ span {
+ display: flex;
+ justify-content: flex-end;
+ }
+ }
+
+ .help {
+ width: 320px;
+ padding: 14px 0px;
+ text-align: center;
+ border-top: 1px solid @color_divider;
+
+ &.fixed {
+ position: fixed;
+ bottom: 0px;
+ }
+
+ a {
+ color: @color_text_secondary;
+ font-size: 12px;
+ display: inline-block;
+ padding: 8px;
+ height: auto;
+
+ &:before {
+ .icomoon-chat;
+ font-size: 32px;
+ position: absolute;
+ top: 0px;
+ left: -26px;
+ }
+
+ &:hover {
+ background: transparent;
+ color: @color_text_primary;
+ }
+ }
+
+ }
+
+
+
+ .add-address {
+ position: relative;
+ padding: 4px 0 4px 20px;
+ cursor: pointer;
+ color: @color_text_secondary;
+ display: flex;
+ align-items: center;
+
+ &:before {
+ .icomoon-plus;
+ margin-right: 12px;
+ }
+
+ .hover();
+ &:hover {
+ color: @color_text_primary;
+ }
+ }
+
+ .discovery-status {
+ height: 64px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ font-size: 14px;
+ padding: 16px 0 16px 30px;
+ white-space: nowrap;
+ border-top: 1px solid @color_divider;
+ span {
+ display: block;
+ font-size: 12px;
+ color: @color_text_secondary;
+ }
+ }
+
+ .discovery-loading {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ font-size: 14px;
+ padding: 16px 0 16px 30px;
+ white-space: nowrap;
+ border-top: 1px solid @color_divider;
+ .loader-circle {
+ margin-right: 12px;
+ }
+ }
+
+ // menu trasitions
+
+ @slide_transition_time: 300ms;
+
+ .slide-left-enter {
+ transform: translate(100%);
+ pointer-events: none;
+ }
+ .slide-left-enter.slide-left-enter-active {
+ transform: translate(0%);
+ transition: transform @slide_transition_time ease-in-out;
+ }
+ .slide-left-exit {
+ transform: translate(-100%);
+ }
+ .slide-left-exit.slide-left-exit-active {
+ transform: translate(0%);
+ transition: transform @slide_transition_time ease-in-out;
+ }
+
+ .slide-right-enter {
+ transform: translate(-100%);
+ pointer-events: none;
+ }
+ .slide-right-enter.slide-right-enter-active {
+ transform: translate(0%);
+ transition: transform @slide_transition_time ease-in-out;
+ }
+ .slide-right-exit {
+ transform: translate(-100%);
+ }
+ .slide-right-exit.slide-right-exit-active {
+ transform: translate(-200%);
+ transition: transform @slide_transition_time ease-in-out;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/base.less b/src/styles/base.less
index f431acca..1a3aa928 100644
--- a/src/styles/base.less
+++ b/src/styles/base.less
@@ -10,49 +10,64 @@
*:focus, *:active, *:active:focus, *::selection, *::-moz-selection {
outline: 0 !important;
-webkit-appearance: none;
- -webkit-tap-highlight-color: rgba(0,0,0,0);
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html, body {
width: 100%;
height: 100%;
-}
-
-html, body {
position: relative;
- background-color: @color_white;
+ background-color: @color_body;
font-family: @font-default;
+ font-weight: 300;
font-size: 14px;
- -webkit-font-smoothing: antialiased;
}
-.layout-wrapper {
- width: 1170px;
- margin: 0 auto;
- padding: 0 15px;
+.app {
+ position: relative;
+ min-height: 100vh;
+ min-width: 720px;
+ display: flex;
+ flex-direction: column;
+ &.resized {
+ // to make sure that unpacked coin menu will not overflow main container
+ // 512 dropdown height + 50 header + 30 margin + 64 topnav height
+ min-height: 680px;
+ }
}
main {
- width: 1170px;
- min-height: 100%;
- padding-top: 90px;
- padding-bottom: 25px;
+ width: 100%;
+ max-width: 1170px;
margin: 0 auto;
- //flex: 1;
- background: @color_main_background;
+ flex: 1;
+ background: @color_main;
display: flex;
flex-direction: row;
+ border-radius: 4px 4px 0px 0px;
+ overflow: hidden;
+ margin-top: 32px;
+
+ @media screen and (max-width: 1170px) {
+ border-radius: 0px;
+ margin-top: 0px;
+ }
}
+
a {
text-decoration: none;
cursor: pointer;
}
-a:focus {
- .no-outlines();
+a:focus,
+button:focus,
+input:focus,
+textarea:focus {
+ outline: 0;
}
-button:focus, input:focus, textarea:focus {
- outline: 0;
+h1, h2, h3 {
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
}
\ No newline at end of file
diff --git a/src/styles/colors.less b/src/styles/colors.less
index a771fb45..45f974c9 100644
--- a/src/styles/colors.less
+++ b/src/styles/colors.less
@@ -1,12 +1,30 @@
@color_white: #ffffff;
-@color_white_smoke: #F5F5F5;
-@color_light_gray: #D3D3D3;
-@color_main_background: #f6f7f8;
+@color_header: #212121;
+@color_body: #EBEBEB;
+@color_main: #FBFBFB;
+@color_landing: #F9F9F9;
-@color_primary_text: #333333;
-@color_secondary_text: #666666;
-@color_link: #000000;
-@color_link_visited: #777777;
+/// new!!!
-@color_green: #4cc148;
+@color_text_primary: #505050;
+@color_text_secondary: #A9A9A9;
+
+@color_gray_light: #F2F2F2; // hover menu
+@color_divider: #EBEBEB;
+
+@color_green_primary: #01B757;
+@color_green_secondary: #00AB51;
+@color_green_tertiary: #009546;
+
+@color_info_primary: #1E7FF0;
+@color_info_secondary: #E1EFFF;
+
+@color_warning_primary: #EB8A00;
+@color_warning_secondary: #FFEFD9;
+
+@color_success_primary: #01B757;
+@color_success_secondary: #DFFFEE;
+
+@color_error_primary: #ED1212;
+@color_error_secondary: #FFE9E9;
\ No newline at end of file
diff --git a/src/styles/content.less b/src/styles/content.less
new file mode 100644
index 00000000..6390f93e
--- /dev/null
+++ b/src/styles/content.less
@@ -0,0 +1,60 @@
+article {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ nav {
+ height: 64px;
+ border-bottom: 1px solid @color_divider;
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.06);
+ display: flex;
+ background: @color_white;
+ position: relative;
+
+ .account-tabs {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0px 48px;
+ max-width: 600px;
+
+ a {
+ font-weight: 500;
+ font-size: 14px;
+ color: @color_text_secondary;
+ margin: 0px 4px;
+ &.active,
+ &:hover {
+ color: @color_text_primary;
+ }
+
+ &:first-child {
+ margin-left: 0px;
+ }
+
+ &:last-child {
+ margin-right: 0px;
+ }
+ }
+ }
+ }
+
+ section {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: @color_white;
+
+ h2 {
+ font-size: 16px;
+ font-weight: 500;
+ padding: 24px 48px;
+ }
+
+ p {
+ padding: 0px 48px;
+ color: @color_text_secondary;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/styles/dashboard.less b/src/styles/dashboard.less
new file mode 100644
index 00000000..a9dc9b84
--- /dev/null
+++ b/src/styles/dashboard.less
@@ -0,0 +1,22 @@
+.dashboard {
+
+ //height: 1000px;
+
+ .row {
+ flex: 1;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding-bottom: 98px;
+
+ h2 {
+ padding: 0;
+ }
+
+ p {
+ padding: 24px 0px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/styles/devices.less b/src/styles/devices.less
deleted file mode 100644
index db506e30..00000000
--- a/src/styles/devices.less
+++ /dev/null
@@ -1,46 +0,0 @@
-nav {
- position: fixed;
- top: 50px;
- width: 100%;
- z-index: 100;
-
- .layout-wrapper {
- color: @color_white;
- background: #2C2C2C;
- padding: 0;
- }
-
- ul {
- list-style: none;
-
- li {
- position: relative;
- display: block;
- cursor: pointer;
- padding: 10px 15px;
- white-space: nowrap;
- //overflow: hidden;
- width: 25%;
- display: inline-block;
- border-top: 1px solid transparent;
- border-bottom: 4px solid transparent;
- &.active {
- background: #060606;
- border-top-color: #2C2C2C;
- border-bottom-color: #4cc148;
- }
-
- &.unacquired {
- color: gray;
- }
-
- &.used-elsewhere {
- color: red !important;
- }
-
- &.reload-features {
- color: orange;
- }
- }
- }
-}
diff --git a/src/styles/fonts.less b/src/styles/fonts.less
index 4eeb9176..81328954 100644
--- a/src/styles/fonts.less
+++ b/src/styles/fonts.less
@@ -1,7 +1,16 @@
+// custom Roboto with Zero without the thing inside, so it's more readable as number
+// since 0 doesn't look too similar to 8
+@font-face {
+ font-family: 'Roboto Zero';
+ src: url('../fonts/roboto/RobotoZero.eot') format('embedded-opentype'),
+ url('../fonts/roboto/RobotoZero.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/roboto/RobotoZero.woff') format('woff'),
+ url('../fonts/roboto/RobotoZero.ttf') format('truetype');
+}
+
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
- font-weight: 400;
src: url('../fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.eot') format('embedded-opentype'), /* IE9 Compat Modes */
url('../fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('../fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.woff2') format('woff2'), /* Super Modern Browsers */
@@ -10,5 +19,219 @@
url('../fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.svg#RobotoMono') format('svg'); /* Legacy iOS */
}
+@font-face {
+ font-family: 'glyphicons';
+ src: url('../fonts/glyphicons.eot') format('embedded-opentype'),
+ url('../fonts/glyphicons.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/glyphicons.woff') format('woff'),
+ url('../fonts/glyphicons.ttf') format('truetype'),
+ url('../fonts/glyphicons.svg#icomoon') format('svg');
+}
+
+@font-face {
+ font-family: 'icomoon';
+ src: url('../fonts/icomoon.eot') format('embedded-opentype'),
+ url('../fonts/icomoon.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/icomoon.woff') format('woff'),
+ url('../fonts/icomoon.ttf') format('truetype'),
+ url('../fonts/icomoon.svg#icomoon') format('svg');
+}
+
+@font-face {
+ font-family: 'fontello';
+ src: url('../fonts/pass.ttf') format('truetype');
+}
+
@font-default: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
-@font-family-monospace: "Roboto Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
\ No newline at end of file
+@font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
+@font-family-monospace: "Roboto Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-monospace-numbers: "Roboto Zero", "Roboto Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
+
+// ::selection,
+// ::-moz-selection {
+// background: @color_info_secondary;
+// }
+
+.glyphicon-base() {
+ display: inline-block;
+ font-family: 'glyphicons';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+}
+
+.glyphicon-trezor {
+ .glyphicon-base();
+ content: "\5f";
+ padding-top: 1px;
+ font-size: 14px;
+}
+
+.glyphicon-info {
+ .glyphicon-base();
+ content: "\ea0c";
+}
+
+.glyphicon-warning {
+ .glyphicon-base();
+ content: "\ea07";
+}
+
+.glyphicon-cross {
+ .glyphicon-base();
+ content: "\ea0f";
+}
+
+.glyphicon-checkmark {
+ .glyphicon-base();
+ content: "\ea10";
+}
+
+.glyphicon-up {
+ .glyphicon-base();
+ content: "\e113";
+}
+
+.glyphicon-down {
+ .glyphicon-base();
+ content: "\e114";
+}
+
+.glyphicon-eye-open {
+ .glyphicon-base();
+ content: "\e105";
+}
+
+.glyphicon-settings {
+ .glyphicon-base();
+ content: "\e019";
+}
+
+.glyphicon-refresh {
+ .glyphicon-base();
+ content: "\e031";
+}
+
+.glyphicon-plus {
+ .glyphicon-base();
+ content: "\2b";
+ position: relative;
+ top: 1px;
+}
+
+
+.icomoon-base() {
+ display: inline-block;
+ font-family: 'icomoon';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ font-size: 24px;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
+}
+
+
+
+
+.icomoon-eject {
+ .icomoon-base();
+ content: "\e902";
+}
+
+.icomoon-info {
+ .icomoon-base();
+ content: "\e904";
+}
+
+.icomoon-refresh {
+ .icomoon-base();
+ content: "\e903";
+}
+
+.icomoon-chat {
+ .icomoon-base();
+ content: "\e905";
+}
+
+.icomoon-redirect {
+ .icomoon-base();
+ content: "\e906";
+}
+
+.icomoon-settings {
+ .icomoon-base();
+ content: "\e907";
+}
+
+.icomoon-warning {
+ .icomoon-base();
+ content: "\e908";
+}
+
+.icomoon-arrow-down {
+ .icomoon-base();
+ content: "\e909";
+}
+
+.icomoon-eye-error {
+ .icomoon-base();
+ content: "\e912";
+}
+
+.icomoon-T1 {
+ .icomoon-base();
+ content: "\e913";
+}
+
+.icomoon-close {
+ .icomoon-base();
+ content: "\e90a";
+}
+
+.icomoon-arrow-left {
+ .icomoon-base();
+ content: "\e90b";
+}
+
+.icomoon-arrow-up {
+ .icomoon-base();
+ content: "\e90c";
+}
+
+.icomoon-arrow-right {
+ .icomoon-base();
+ content: "\e90d";
+}
+
+.icomoon-plus {
+ .icomoon-base();
+ content: "\e90e";
+}
+
+.icomoon-help {
+ .icomoon-base();
+ content: "\e90f";
+}
+
+
+.icomoon-setmax {
+ .icomoon-base();
+ content: "\e91b";
+}
+
+.icomoon-checked {
+ .icomoon-base();
+ content: "\e91c";
+}
+
+.icomoon-error {
+ .icomoon-base();
+ content: "\e91d";
+}
+
+.icomoon-eye {
+ .icomoon-base();
+ content: "\e91e";
+}
diff --git a/src/styles/footer.less b/src/styles/footer.less
index b340ac98..2cb033b8 100644
--- a/src/styles/footer.less
+++ b/src/styles/footer.less
@@ -1,18 +1,22 @@
footer {
width: 100%;
- position: fixed;
- bottom: 0;
font-size: 12px;
- z-index: 100;
- a, a:visited {
- color: @color_green;
- &:hover {
- text-decoration: none;
- }
+ color: @color_text_secondary;
+ padding: 22px 48px;
+ border-top: 1px solid @color_divider;
+ display: flex;
+
+ span, a {
+ white-space: nowrap;
+ }
+
+ span {
+ margin-right: 10px;
}
- .layout-wrapper {
- background: @color_main_background;
- padding-bottom: 5px;
+ a {
+ margin: 0px 6px;
+ font-weight: 500;
+ margin-right: 20px;
}
}
diff --git a/src/styles/header.less b/src/styles/header.less
index 2d993d7a..462c7b83 100644
--- a/src/styles/header.less
+++ b/src/styles/header.less
@@ -1,15 +1,10 @@
header {
- position: fixed;
- top: 0;
width: 100%;
- height: 50px;
- background: #060606;
- color: #f6f7f8;
- overflow: hidden;
- min-width: 780px;
- z-index: 100;
+ height: 52px;
+ background: @color_header;
+
svg {
- fill: #ffffff;
+ fill: @color_white;
height: 28px;
width: 100px;
margin-top: 9px;
@@ -22,4 +17,11 @@ header {
margin-top: 16px;
margin-left: 20px;
}
+
+ .layout-wrapper {
+ width: 100%;
+ max-width: 1170px;
+ margin: 0 auto;
+ padding: 0 15px;
+ }
}
diff --git a/src/styles/index.less b/src/styles/index.less
index 72eb2ab7..a58f3cb6 100644
--- a/src/styles/index.less
+++ b/src/styles/index.less
@@ -3,11 +3,26 @@
@import './mixins.less';
@import './base.less';
@import './header.less';
+@import './aside.less';
+@import './content.less';
@import './footer.less';
-@import './devices.less';
@import './modal.less';
-@import './accounts.less';
+
+@import './reactSelect.less';
+@import './rcTooltip.less';
+
@import './history.less';
@import './send.less';
-@import './receive.less';
\ No newline at end of file
+@import './receive.less';
+@import './summary.less';
+@import './signverify.less';
+
+@import './landingPage.less';
+
+@import './dashboard.less';
+@import './acquire.less';
+@import './notification.less';
+
+@import './inputs.less';
+@import './loader.less';
diff --git a/src/styles/inputs.less b/src/styles/inputs.less
new file mode 100644
index 00000000..84748d61
--- /dev/null
+++ b/src/styles/inputs.less
@@ -0,0 +1,226 @@
+input, textarea {
+ font-size: 14px;
+ font-weight: 300;
+ line-height: 1.42857143;
+ font-family: @font-family-monospace;
+ color: @color_text_primary;
+ background-color: @color_white;
+ border: 1px solid @color_divider;
+ border-radius: 2px;
+ transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ padding: 6px 12px;
+
+ &:focus {
+ box-shadow: 0 1px 2px 0 rgba(169, 169, 169, 0.25);
+ }
+
+ &:disabled {
+ background: @color_gray_light;
+ color: @color_text_secondary;
+ }
+}
+
+input {
+
+ &.valid {
+ border-color: @color_success_primary;
+ &:focus {
+ box-shadow: 0 1px 4px 0 rgba(1, 183, 87, 0.25);
+ }
+ }
+
+ &.warning {
+ border-color: @color_warning_primary;
+ &:focus {
+ box-shadow: 0 1px 4px 0 rgba(1, 183, 87, 0.25);
+ }
+ }
+
+ &.not-valid {
+ border-color: @color_error_primary;
+ &:focus {
+ box-shadow: 0 1px 4px 0 rgba(255, 111, 109, 0.25);
+ }
+ }
+}
+
+
+button {
+ padding: 12px 24px;
+ border-radius: 3px;
+ font-size: 14px;
+ font-weight: 300;
+ cursor: pointer;
+ background: @color_green_primary;
+ color: @color_white;
+ border: 0px;
+
+ .hover();
+
+ &:hover {
+ background: @color_green_secondary;
+ }
+
+ &:active {
+ background: @color_green_tertiary;
+ }
+
+ &:disabled {
+ pointer-events: none;
+ color: @color_text_secondary;
+ background: @color_gray_light;
+ }
+
+ &.blue {
+ background: transparent;
+ border: 1px solid @color_info_primary;
+ color: @color_info_primary;
+ padding: 12px 58px;
+
+ &:hover {
+ color: @color_white;
+ background: @color_info_primary;
+ }
+ }
+
+ &.white {
+ background: @color_white;
+ color: @color_text_secondary;
+ border: 1px solid @color_divider;
+ &:hover {
+ //color: @color_text_primary;
+ //border-color: @color_text_primary;
+ background: @color_divider;
+ }
+ &:active {
+ color: @color_text_primary;
+ background: @color_divider;
+ }
+ }
+
+ &.transparent {
+ background: transparent;
+ border: 0px;
+ color: @color_text_secondary;
+ .hover();
+
+ &:hover,
+ &:active {
+ color: @color_text_primary;
+ background: transparent;
+ }
+ }
+}
+
+
+.custom-checkbox {
+
+ position: relative;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ color: @color_text_secondary;
+
+ input {
+ position: absolute;
+ left: -9999px;
+ z-index: -1;
+ opacity: 0;
+
+ &:checked + .indicator:after {
+ background-color: @color_green_primary;
+ border-color: @color_green_primary;
+ }
+ &:disabled + .indicator:after {
+ background-color: @color_text_secondary;
+ }
+ }
+
+ .indicator {
+ position: relative;
+ height: 24px;
+ width: 24px;
+ margin-right: 12px;
+
+ &:after {
+ .icomoon-checked;
+ .hover();
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ color: @color_white;
+ background-color: @color_white;
+ border: 1px solid @color_divider;
+ border-radius: 2px;
+ }
+
+ &:hover {
+ border-color: @color_text_secondary;
+ }
+ }
+
+ &.radio {
+ .indicator {
+ &:after {
+ border-radius: 50%;
+ }
+ }
+
+ input:checked + .indicator:after {
+ content: '';
+ background: white;
+ border: 4px solid @color_green_primary;
+ }
+ }
+
+ &.align-left {
+ padding-left: 20px;
+ padding-right: 10px;
+ .indicator {
+ position: absolute;
+ left: 0;
+ top: 2px;
+ margin: 0;
+ &:after {
+ top: 0px;
+ }
+ }
+ }
+}
+
+a.green,
+a.green:visited {
+ position: relative;
+ color: @color_green_primary;
+ .hover();
+
+ &:after {
+ content: '';
+ position: absolute;
+ width: 100%;
+ border-top: 1px solid @color_green_primary;
+ line-height: 1px;
+ left: 0px;
+ bottom: -1px;
+ transition: border-color 0.3s;
+ }
+
+ &:hover {
+ color: @color_green_secondary;
+ }
+
+ &:active {
+ color: @color_green_tertiary;
+ }
+
+ &:hover,
+ &:active {
+ &:after {
+ border-color: @color_white;
+ }
+ }
+
+
+}
diff --git a/src/styles/landingPage.less b/src/styles/landingPage.less
new file mode 100644
index 00000000..31b80daa
--- /dev/null
+++ b/src/styles/landingPage.less
@@ -0,0 +1,142 @@
+.app {
+ &.connect-device {
+ //min-height: 100vh;
+ // overflow: hidden;
+ background: @color_landing;
+
+ main {
+ flex-direction: column;
+ text-align: center;
+ padding-top: 65px;
+ margin-top: 0px;
+
+ h2.claim {
+ font-size: 36px;
+ padding-bottom: 24px;
+ }
+
+ .row {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ padding: 36px 0px;
+ margin: 0 auto;
+ width: 720px;
+
+ p {
+ // flex: 1;
+ align-self: center;
+ }
+
+ // a {
+ // color: @color_green_primary;
+ // text-decoration: underline;
+ // font-weight: 500;
+ // .hover();
+ // &:hover {
+ // text-decoration: none;
+ // color: @color_green_secondary;
+ // }
+ // }
+ }
+
+ p {
+ color: @color_text_secondary;
+ line-height: 1.8;
+
+ &.connect {
+ color: @color_green_primary;
+ font-size: 16px;
+ font-weight: 500;
+
+ span {
+ vertical-align: top;
+ position: relative;
+ top: 1px;
+ left: 12px;
+ animation: pulsate 1.3s ease-out infinite;
+ position: relative;
+
+ svg {
+ position: absolute;
+ top: -8px;
+ left: -24px;
+ }
+ }
+ }
+ }
+
+ .image {
+ width: 100%;
+ height: calc(100vh - 143px);
+ min-height: 500px;
+ flex: 1;
+ background-image: url('../images/case.png');
+ background-repeat: no-repeat;
+ background-position: center 0px;
+ background-size: contain;
+ }
+
+ img {
+ width: 90%;
+ height: auto;
+ margin: auto;
+ }
+ }
+
+ .connect-usb-pin,
+ .connect-usb-cable {
+ animation: connect 1.3s ease-out infinite;
+ }
+
+ footer {
+ border: 0px;
+ justify-content: center;
+ width: 100%;
+ max-width: 1170px;
+ margin: 0px auto;
+ }
+
+ .notification {
+ width: 100%;
+ max-width: 1170px;
+ margin: 0px auto;
+ }
+ }
+}
+
+
+.landing {
+ text-align: center;
+ position: relative;
+ min-height: 100vh;
+ background: @color_landing;
+
+ .loader-circle {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ margin: auto;
+ }
+}
+
+@keyframes pulsate {
+ 0%, 100% {
+ opacity: 0.5;
+ }
+ 50% {
+ opacity: 1.0;
+ }
+}
+
+@keyframes connect {
+ 0%, 100% {
+ transform: translateY(0px);
+ }
+ 50% {
+ transform: translateY(-4px)
+ }
+}
+
diff --git a/src/styles/loader.less b/src/styles/loader.less
new file mode 100644
index 00000000..6b5338ae
--- /dev/null
+++ b/src/styles/loader.less
@@ -0,0 +1,73 @@
+.loader-circle {
+
+ position: relative;
+ width: 100px;
+ height: 100px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ p {
+ position: absolute;
+ //margin: auto;
+ //line-height: 100%;
+ color: @color_text_secondary;
+ }
+
+ .circular {
+
+ width: 100%;
+ height: 100%;
+ animation: rotate 2s linear infinite;
+ transform-origin: center center;
+
+ position: absolute;
+
+ .route {
+ stroke: @color_gray_light;
+ }
+
+ .path {
+ stroke-dasharray: 1, 200;
+ stroke-dashoffset: 0;
+ animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite;
+ stroke-linecap: round;
+ }
+ }
+}
+
+@keyframes rotate {
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes dash {
+ 0% {
+ stroke-dasharray: 1, 200;
+ stroke-dashoffset: 0;
+ }
+ 50% {
+ stroke-dasharray: 89, 200;
+ stroke-dashoffset: -35;
+ }
+ 100% {
+ stroke-dasharray: 89, 200;
+ stroke-dashoffset: -124;
+ }
+}
+
+@keyframes color {
+ 100%, 0% {
+ stroke: @color_green_primary;
+ }
+ 40% {
+ stroke: @color_green_primary;
+ }
+ 66% {
+ stroke: @color_green_secondary;
+ }
+ 80%, 90% {
+ stroke: @color_green_tertiary;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/mixins.less b/src/styles/mixins.less
index 3bba0f50..76f2f1d0 100644
--- a/src/styles/mixins.less
+++ b/src/styles/mixins.less
@@ -9,4 +9,23 @@
border-color: inherit !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
+}
+
+.hover() {
+ transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out, border-color 0.3s ease-in-out;
+}
+
+.placeholder(@rules) {
+ &::-webkit-input-placeholder {
+ @rules();
+ }
+ &:-moz-placeholder {
+ @rules();
+ }
+ &::-moz-placeholder {
+ @rules();
+ }
+ &:-ms-input-placeholder {
+ @rules();
+ }
}
\ No newline at end of file
diff --git a/src/styles/modal.less b/src/styles/modal.less
index ae323b96..d5509e98 100644
--- a/src/styles/modal.less
+++ b/src/styles/modal.less
@@ -5,136 +5,257 @@
height: 100%;
top: 0px;
left: 0px;
- //display: none;
-
- &.opened {
- display: block;
- }
+ background: rgba(0, 0, 0, 0.35);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ overflow: auto;
+ padding: 20px;
.modal-window {
- margin: 20px auto;
- padding: 20px;
- overflow-x: hidden;
- overflow-y: auto;
+ margin: auto;
+ // padding: 24px 48px;
position: relative;
- max-width: 100%;
- border-radius: 6px;
- box-sizing: border-box;
- box-shadow: 0px 16px 16px 8px rgba(0, 0, 0, 0.1);
- width: 500px;
- min-height: 350px;
- background-color: #ffffff;
+ border-radius: 4px;
+ background-color: @color_white;
text-align: center;
+ overflow: hidden;
}
- .pin {
- h4 {
- margin-bottom: 10px;
+ h3 {
+ color: @color_text_primary;
+ font-size: 16px;
+ font-weight: 500;
+ margin-top: 14px;
+ }
+ p {
+ margin: 5px 0px;
+ font-weight: normal;
+ color: @color_text_secondary;
+ font-size: 12px;
+ }
+
+ .confirm-tx {
+ width: 390px; // address overflow
+
+ .header {
+ padding: 24px 48px;
+ &:before {
+ .icomoon-T1;
+ font-size: 52px;
+ color: @color_text_secondary;
+ }
+ h3 {
+ margin: 0;
+ }
+ }
+ .content {
+ border-top: 1px solid @color_divider;
+ background: @color_main;
+ padding: 24px 48px;
+
+ label {
+ font-size: 10px;
+ color: @color_text_secondary;
+ }
+
+ p {
+ font-size: 12px;
+ font-weight: 400;
+ color: @color_text_primary;
+ }
+ }
+ }
+
+ .confirm-address {
+ width: 390px; // address overflow
+
+ .header {
+ padding: 24px 48px;
+ &:before {
+ .icomoon-T1;
+ font-size: 52px;
+ color: @color_text_secondary;
+ }
+ h3 {
+ margin: 0;
+ }
+ }
+ .content {
+ border-top: 1px solid @color_divider;
+ background: @color_main;
+ padding: 24px 48px;
+
+ label {
+ font-size: 12px;
+ color: @color_text_secondary;
+ }
+
+ p {
+ font-size: 12px;
+ font-weight: 400;
+ color: @color_text_primary;
+ }
+ }
+ }
+
+ .confirm-address-unverified {
+ width: 370px;
+ padding: 24px 48px;
+ button:not(.close-modal) {
+ width: 100%;
+ margin-top: 12px;
+ }
+ }
+
+ .remember {
+ width: 360px;
+ padding: 24px 48px;
+
+ p {
+ padding: 14px 0px;
+ }
+
+ button {
+ width: 100%;
+ margin-top: 12px;
+ span {
+ position: relative;
+ }
}
- .pin_row {
+
+ .loader-circle {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: -36px;
+ margin: auto;
+ p {
+ margin: 0;
+ padding: 0;
+ color: @color_text_secondary;
+ }
+ }
+ }
+
+ .close-modal {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 12px;
+
+ &:after {
+ .icomoon-close;
+ }
+ }
+
+ .pin {
+ padding: 24px 48px;
+ .pin-row {
button {
- width: 55px;
- height: 55px;
- margin-top: 10px;
- margin-left: 5px;
+ width: 80px;
+ height: 80px;
+ margin-top: 15px;
+ margin-left: 10px;
+ color: @color_text_primary;
+ border: 1px solid @color_divider;
+ background: @color_white;
+ transition: all 0.3s;
&:first-child {
margin-left: 0px;
}
+ &:hover {
+ color: @color_text_primary;
+ border-color: @color_text_secondary;
+ }
+ &:active {
+ color: @color_text_primary;
+ background: @color_divider;
+ border-color: @color_divider;
+ }
+
}
}
- .pin_input_row {
- margin-top: 10px;
+ .pin-input-row {
+ margin-top: 24px;
display: inline-block;
position: relative;
}
input {
- width: 185px;
+ letter-spacing: 6px;
+ line-height: 48px;
+ font-weight: bold;
+ font-size: 18px;
+ height: auto;
+ padding: 0px 34px;
+ color: @color_text_primary;
+ background: transparent;
}
- .pin_backspace {
+ .pin-backspace {
position: absolute;
- right: 0;
- top: 4px;
- padding: 3px 3px 1px;
- border: 0;
- border-radius: 0;
- &:active {
- color: @color_link;
- background: transparent;
+ right: 14px;
+ top: 0;
+ bottom: 0;
+ margin: auto 0;
+ padding: 0;
+ &:after {
+ .icomoon-arrow-left;
}
}
- .submit {
- margin-top: 10px;
- width: 185px;
+ a {
+ color: @color_green_primary;
}
}
.passphrase {
- h4 {
- margin-bottom: 10px;
- }
- label {
- display: block;
- }
- .passphrase_options {
- margin-top: 10px;
- margin-bottom: 10px;
- span {
- font-size: 14px;
- color: @color_secondary_text;
+ padding: 24px 48px;
+ .row {
+ position: relative;
+ text-align: left;
+ padding-top: 24px;
+ label:not(.custom-checkbox) {
+ display: block;
+ padding-bottom: 6px;
+ color: @color_text_secondary;
}
- }
-
- button {
- width: 250px;
- }
+
+ .error {
+ position: absolute;
+ left: 0px;
+ bottom: -19px;
+ font-size: 12px;
+ color: @color_error_primary;
+ }
+ }
+
+ // input[type="text"] {
+ // font-family: 'fontello';
+ // font-size: 6px;
+ // line-height: 14px;
+ // }
}
input[type="text"],
input[type="password"] {
- width: 250px;
- padding: 6px;
- border: 0;
- border-bottom: 1px solid @color_secondary_text;
- background: transparent;
- color: @color_primary_text;
- font-size: 18px;
- // disable lastpass icons
- background-image: none !important;
- padding-right: 0 !important;
- }
+ width: 260px;
+ box-shadow: none;
+ border-radius: 0px;
+ border: 1px solid @color_divider;
+ height: auto;
- button {
- padding: 8px 12px;
- font-size: 18px;
- background: transparent;
- border: 1px solid @color_secondary_text;
- border-radius: 5px;
- color: @color_secondary_text;
- cursor: pointer;
- transition: background 0.5s, color 0.5s, border 0.5s, opacity 0.5s;
-
- &:hover {
- color: @color_link;
- border-color: @color_link;
- }
- &:active {
- color: @color_white;
- border-color: @color_secondary_text;
- background: @color_secondary_text;
- transition: none;
- }
- &:focus {
- outline: 0;
- }
+ .placeholder({
+ color: @color_divider;
+ });
}
-
- button[disabled] {
- opacity: 0.8;
- pointer-events: none;
+
+ .submit {
+ width: 100%;
+ margin-top: 24px;
+ margin-bottom: 14px;
}
}
diff --git a/src/styles/notification.less b/src/styles/notification.less
new file mode 100644
index 00000000..16dd3006
--- /dev/null
+++ b/src/styles/notification.less
@@ -0,0 +1,126 @@
+.notification {
+ position: relative;
+ color: @color_info_primary;
+ background: @color_info_secondary;
+ padding: 24px 48px 24px 80px;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ text-align: left;
+
+ .notification-body {
+ flex: 1;
+ margin-right: 24px;
+ }
+
+ .notification-action button {
+ padding: 12px 58px;
+ }
+
+ .notification-close {
+ position: absolute;
+ top: 8px;
+ right: 0;
+ padding: 12px;
+ color: inherit;
+ transition: opacity 0.3s;
+
+ &:after {
+ .icomoon-close;
+ }
+ &:active,
+ &:hover {
+ opacity: 0.6;
+ color: inherit;
+ }
+ }
+
+ h2 {
+ font-size: 14px;
+ font-weight: bold;
+
+ padding: 0px;
+ &:before {
+ .icomoon-info;
+ position: absolute;
+ top: 17px;
+ left: 40px;
+ font-size: 32px !important;
+ }
+ }
+
+ p {
+ padding: 0px;
+ margin-bottom: 8px 0px;
+ color: inherit;
+ }
+
+ &.info {
+ .notification-action button {
+ border: 1px solid @color_info_primary;
+ color: @color_info_primary;
+ &:hover {
+ color: @color_white;
+ background: @color_info_primary;
+ }
+ }
+ }
+
+
+
+ &.success {
+ color: @color_success_primary;
+ background: @color_success_secondary;
+
+ .notification-action button {
+ border: 1px solid @color_success_primary;
+ color: @color_success_primary;
+ &:hover {
+ color: @color_white;
+ background: @color_success_primary;
+ }
+ }
+ }
+
+ &.warning {
+ color: @color_warning_primary;
+ background: @color_warning_secondary;
+ h2:before {
+ .icomoon-warning;
+ }
+
+ .notification-action button {
+ border: 1px solid @color_warning_primary;
+ color: @color_warning_primary;
+ &:hover {
+ color: @color_white;
+ background: @color_warning_primary;
+ }
+ }
+ }
+
+ &.error {
+ color: @color_error_primary;
+ background: @color_error_secondary;
+ h2:before {
+ .icomoon-error;
+ }
+
+ .notification-close {
+ color: @color_error_primary;
+ &:hover {
+ color: @color_error_primary;
+ }
+ }
+
+ .notification-action button {
+ border: 1px solid @color_error_primary;
+ color: @color_error_primary;
+ &:hover {
+ color: @color_white;
+ background: @color_error_primary;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/styles/rcTooltip.less b/src/styles/rcTooltip.less
new file mode 100644
index 00000000..d2a2c5e8
--- /dev/null
+++ b/src/styles/rcTooltip.less
@@ -0,0 +1,147 @@
+.tooltip-wrapper {
+ width: 320px;
+ font-size: 10px;
+ span {
+ color: @color_green_primary;
+ }
+}
+
+.rc-tooltip {
+ position: absolute;
+ z-index: 1070;
+ display: block;
+ visibility: visible;
+ border: 1px solid @color_divider;
+ border-radius: 3px;
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.06);
+}
+
+.rc-tooltip-hidden {
+ display: none;
+}
+
+.rc-tooltip-inner {
+ padding: 8px 10px;
+ color: @color_text_secondary;
+ font-size: 12px;
+ line-height: 1.5;
+ text-align: left;
+ text-decoration: none;
+ background-color: @color_white;
+ border-radius: 3px;
+ min-height: 34px;
+ border: 1px solid @color_white;
+}
+.rc-tooltip-arrow,
+.rc-tooltip-arrow-inner {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+
+.rc-tooltip-placement-top .rc-tooltip-arrow,
+.rc-tooltip-placement-topLeft .rc-tooltip-arrow,
+.rc-tooltip-placement-topRight .rc-tooltip-arrow {
+ bottom: -6px;
+ margin-left: -6px;
+ border-width: 6px 6px 0;
+ border-top-color: @color_divider;
+}
+.rc-tooltip-placement-top .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-topLeft .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-topRight .rc-tooltip-arrow-inner {
+ //bottom: 1px;
+ bottom: 2px;
+ margin-left: -6px;
+ border-width: 6px 6px 0;
+ border-top-color: @color_white;
+}
+.rc-tooltip-placement-top .rc-tooltip-arrow {
+ left: 50%;
+}
+.rc-tooltip-placement-topLeft .rc-tooltip-arrow {
+ left: 15%;
+}
+.rc-tooltip-placement-topRight .rc-tooltip-arrow {
+ right: 15%;
+}
+.rc-tooltip-placement-right .rc-tooltip-arrow,
+.rc-tooltip-placement-rightTop .rc-tooltip-arrow,
+.rc-tooltip-placement-rightBottom .rc-tooltip-arrow {
+ left: -5px;
+ margin-top: -6px;
+ border-width: 6px 6px 6px 0;
+ border-right-color: @color_divider;
+}
+.rc-tooltip-placement-right .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-rightTop .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-rightBottom .rc-tooltip-arrow-inner {
+ left: 1px;
+ margin-top: -6px;
+ border-width: 6px 6px 6px 0;
+ border-right-color: @color_white;
+}
+.rc-tooltip-placement-right .rc-tooltip-arrow {
+ top: 50%;
+}
+.rc-tooltip-placement-rightTop .rc-tooltip-arrow {
+ top: 15%;
+ margin-top: 0;
+}
+.rc-tooltip-placement-rightBottom .rc-tooltip-arrow {
+ bottom: 15%;
+}
+.rc-tooltip-placement-left .rc-tooltip-arrow,
+.rc-tooltip-placement-leftTop .rc-tooltip-arrow,
+.rc-tooltip-placement-leftBottom .rc-tooltip-arrow {
+ right: -5px;
+ margin-top: -6px;
+ border-width: 6px 0 6px 6px;
+ border-left-color: @color_divider;
+}
+.rc-tooltip-placement-left .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-leftTop .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-leftBottom .rc-tooltip-arrow-inner {
+ right: 1px;
+ margin-top: -6px;
+ border-width: 6px 0 6px 6px;
+ border-left-color: @color_white;
+}
+.rc-tooltip-placement-left .rc-tooltip-arrow {
+ top: 50%;
+}
+.rc-tooltip-placement-leftTop .rc-tooltip-arrow {
+ top: 15%;
+ margin-top: 0;
+}
+.rc-tooltip-placement-leftBottom .rc-tooltip-arrow {
+ bottom: 15%;
+}
+.rc-tooltip-placement-bottom .rc-tooltip-arrow,
+.rc-tooltip-placement-bottomLeft .rc-tooltip-arrow,
+.rc-tooltip-placement-bottomRight .rc-tooltip-arrow {
+ top: -5px;
+ margin-left: -6px;
+ border-width: 0 6px 6px;
+ border-bottom-color: @color_divider;
+}
+.rc-tooltip-placement-bottom .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-bottomLeft .rc-tooltip-arrow-inner,
+.rc-tooltip-placement-bottomRight .rc-tooltip-arrow-inner {
+ top: 1px;
+ margin-left: -6px;
+ border-width: 0 6px 6px;
+ border-bottom-color: @color_white;
+}
+.rc-tooltip-placement-bottom .rc-tooltip-arrow {
+ left: 50%;
+}
+.rc-tooltip-placement-bottomLeft .rc-tooltip-arrow {
+ left: 15%;
+}
+.rc-tooltip-placement-bottomRight .rc-tooltip-arrow {
+ right: 15%;
+}
diff --git a/src/styles/reactSelect.less b/src/styles/reactSelect.less
new file mode 100644
index 00000000..56f7beb2
--- /dev/null
+++ b/src/styles/reactSelect.less
@@ -0,0 +1,329 @@
+// https://github.com/JedWatson/react-select/blob/master/less/select.less
+
+@import '~react-select/less/select';
+
+// override predefined colors
+@select-primary-color: @color_white;
+@select-input-hover-box-shadow: none;
+@select-input-box-shadow-focus: transparent;
+@select-input-border-radius: 0px;
+@select-item-border-radius: 0px;
+@select-input-border-color: transparent;
+@select-input-border-focus: @color_divider;
+
+.Select-focus-state(@color) {
+ // do nothing
+ background: transparent;
+ box-shadow: none;
+}
+
+.Select-focus-state-classic() {
+ background: transparent;
+ box-shadow: none;
+}
+
+.Select-arrow-zone {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ margin: auto 0;
+ padding-right: 0px !important;
+ width: 24px;
+ height: 24px;
+ right: 8px;
+
+ .Select-arrow {
+ top: 0px;
+ border: 0px;
+ width: 24px;
+
+ &:after {
+ .icomoon-arrow-down;
+ transition: transform 0.3s, color 0.3s;
+ color: @color_text_secondary;
+ transform-origin: 50% 50%;
+ font-size: 24px;
+ }
+ }
+}
+
+.Select {
+
+ .Select-control {
+ cursor: pointer;
+
+ .Select-input {
+ background: transparent;
+ position: absolute;
+ top: 0;
+ // display: none !important; // uncomment for disable auto closing
+ }
+
+ &:hover {
+ .Select-arrow:after {
+ color: @color_text_primary;
+ }
+ }
+ }
+
+ .Select-noresults {
+
+ }
+
+ .Select-value-label {
+ color: @color_text_primary;
+ }
+
+ .Select-menu-outer {
+ border-radius: 0px;
+ border: 1px solid @color_divider;
+ box-shadow: none;
+ }
+
+ &.is-open {
+ .Select-arrow {
+ top: 0px !important;
+ border: 0px;
+ &:after {
+ transform: rotate(180deg);
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+/*@select-input-height: 34px;
+@select-primary-color: #fff;
+@select-input-bg-focus: #ff0000;
+@select-input-border-radius: 0px;
+@select-input-border-focus: @select-input-border-color;
+
+.Select {
+ width: 240px;
+ height: 34px;
+ display: inline-block;
+ vertical-align: middle;
+
+ &.is-focused:not(.is-open) > .Select-control {
+ border-color: @select-input-border-color;
+ box-shadow: none;
+ }
+}
+
+.Select-control {
+ &:hover {
+ box-shadow: none;
+ }
+}
+
+
+
+
+
+.Select-menu-outer {
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+}
+
+.Select-option {
+ &:last-child {
+ border-radius: 0px;
+ }
+
+ .fee-label {
+ display: inline-block;
+ width: 70%;
+ }
+ .fee-size {
+ display: inline-block;
+ text-align: right;
+ }
+}
+
+.CurrencySelect {
+ width: 70px;
+ vertical-align: top;
+}
+
+
+.CoinSelect {
+ width: 290px;
+ height: 64px;
+ .Select-control {
+ height: 63px;
+ border: 0px;
+ border-radius: 4px 0px 0px 0px;
+ border-right: 1px solid @color_divider;
+ cursor: pointer;
+ transition: all 0.2s ease-in-out;
+
+ .Select-input {
+ background: transparent;
+ //display: none !important;
+ }
+
+ &:hover {
+ background: #F2F2F2;
+ .Select-arrow {
+ &:after {
+ color: #494949;
+ }
+ }
+ }
+ }
+
+ .Select-value {
+ padding: 0px;
+ .Select-value-label {
+ display: inline-block;
+ height: 63px;
+ padding-top: 20px;
+ padding-left: 50px;
+ font-size: 1.15em;
+ font-weight: bold;
+ line-height: 26px;
+ color: #494949;
+
+ }
+ &:before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 20px;
+ height: 20px;
+ z-index: 2;
+ left: 20px;
+ top: 21px;
+
+
+ background-image: url(../images/eth-logo.png);
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: auto 20px;
+ }
+ }
+
+ .Select-menu-outer {
+ position: relative;
+ top: 0;
+ border: 0px;
+ border-top: 1px solid rgba(218, 218, 218, 0.5);
+ border-right: 1px solid rgba(218, 218, 218, 0.5);
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
+ max-height: none;
+ }
+
+ .Select-menu {
+ max-height: none;
+ overflow-x: none;
+ }
+
+ .Select-option {
+ width: 290px;
+ height: 64px;
+ padding-top: 20px;
+ padding-left: 60px;
+ position: relative;
+ transition: all 0.2s ease-in-out;
+
+ span {
+ height: 63px;
+ font-size: 1.15em;
+ font-weight: bold;
+ line-height: 26px;
+ color: #494949;
+ }
+
+ &:before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 20px;
+ height: 20px;
+ z-index: 2;
+ left: 20px;
+ top: 21px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 20px 20px;
+ }
+
+ &.btc:before {
+ background-image: url('../images/btc-logo.png');
+ }
+ &.ltc:before {
+ background-image: url('../images/ltc-logo.png');
+ }
+ &.btg:before {
+ background-image: url('../images/btg-logo.png');
+ }
+ &.bch:before {
+ background-image: url('../images/bch-logo.png');
+ }
+ &.dash:before {
+ background-image: url('../images/dash-logo.png');
+ }
+ &.zec:before {
+ background-image: url('../images/zec-logo.png');
+ }
+ &.eth:before {
+ background-image: url('../images/eth-logo.png');
+ background-size: auto 20px;
+ }
+ &.etc:before {
+ background-image: url('../images/etc-logo.png');
+ background-size: auto 20px;
+ }
+
+ &:hover {
+ background: #F2F2F2;
+ }
+
+ &.is-selected {
+ background: yellow;
+ }
+ }
+
+ .Select-arrow-zone {
+ width: 28px;
+ }
+
+ .Select-arrow {
+ border: 0px;
+ width: 28px;
+
+ &:after {
+ .glyphicon-down;
+ color: #B3B3B3;
+ position: absolute;
+ left: 0px;
+ top: -8px;
+ transition: all 0.2s ease-in-out;
+ }
+ }
+
+ &.is-open {
+ .Select-arrow {
+ top: 0px;
+ &:after {
+ .glyphicon-up;
+ }
+ }
+ }
+}
+
+
+
+// /*
+//
+//
+//
+// */
\ No newline at end of file
diff --git a/src/styles/receive.less b/src/styles/receive.less
index 8418144a..ec6642a9 100644
--- a/src/styles/receive.less
+++ b/src/styles/receive.less
@@ -1,8 +1,94 @@
.receive {
- flex: 1;
- padding: 10px;
- display: flex;
- flex-direction: column;
- border: 1px solid red;
+ .address {
+ position: relative;
+ padding: 0px 48px;
+ display: flex;
+ flex-wrap: wrap;
+
+ .value {
+ // same as input (inputs.less)
+ font-size: 14px;
+ font-weight: 300;
+ line-height: 1.42857143;
+ font-family: @font-family-monospace;
+ color: @color_text_primary;
+ border: 1px solid @color_divider;
+ border-radius: 3px;
+ padding: 6px 12px;
+ padding-right: 38px; // eye icon
+ position: relative;
+ flex: 1;
+ user-select: all; /* Chrome and Opera */
+ }
+
+ button {
+ padding: 6px 24px;
+
+ &.white {
+ padding: 0px;
+ border: 0px;
+ position: absolute;
+ height: 100%;
+ background: transparent;
+ right: 48px;
+ }
+
+ span {
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+ &:before {
+ .icomoon-eye;
+ font-size: 32px;
+ line-height: 14px;
+ // padding-top: 2px;
+ padding-right: 4px;
+ }
+ }
+
+ }
+
+ &.hidden {
+ .value {
+ padding-right: 6px; // no eye icon
+ user-select: none;
+ border-radius: 3px 0px 0px 3px;
+ &:after {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 100%;
+ background: linear-gradient(to right,
+ rgba(255,255,255, 0) 0%,
+ rgba(255,255,255, 1) 220px
+ );
+ pointer-events: none; /* so the text is still selectable */
+ }
+ }
+
+ button {
+ border-radius: 0px 3px 3px 0px;
+ }
+ }
+
+ &.unverified {
+ button span:before {
+ .icomoon-eye-error;
+ color: @color_error_primary;
+ font-size: 32px;
+ line-height: 14px;
+ padding-top: 0px;
+ padding-right: 4px;
+ }
+ }
+
+
+ }
+
+ .qr {
+ margin: 24px 48px;
+ }
}
\ No newline at end of file
diff --git a/src/styles/send.less b/src/styles/send.less
index c50da008..094516e2 100644
--- a/src/styles/send.less
+++ b/src/styles/send.less
@@ -1,101 +1,286 @@
-.address-menu {
- a {
- padding: 10px;
- }
-}
-
.send-form {
- flex: 1;
- padding: 10px;
- display: flex;
- flex-direction: column;
+ padding-bottom: 24px;
- border: 1px solid red;
+ .Select {
+ width: 98px;
+ height: 34px;
+ font-family: @font-family-monospace;
+
+ &.fee {
+ width: 100%;
+ }
+
+ .Select-control {
+ height: 34px;
+ border: 1px solid @color_divider;
+ border-radius: 0px 2px 2px 0px;
+ }
+
+ .Select-option {
+ .hover();
+ &.is-focused {
+ background: @color_gray_light;
+ }
+
+ &.is-selected {
+ background: @color_divider;
+ }
+ }
+
+ .fee-option {
+ display: flex;
+ align-items: center;
+
+ .fee-value {
+ flex: 1;
+ color: @color_text_primary;
+ }
+
+ .fee-label {
+ color: @color_text_secondary;
+ font-size: 12px;
+ font-weight: 400;
+ padding-right: 36px;
+ }
+ }
+
+ }
.row {
+ position: relative;
display: block;
- padding-bottom: 10px;
- }
+ padding: 0px 48px;
+ padding-bottom: 24px;
- label {
- display: inline-block;
- text-transform: uppercase;
- &:first-child {
- width: 20%;
- padding-right: 10px;
- text-align: right;
+ .error,
+ .warning,
+ .info {
+ position: absolute;
+ left: 48px;
+ bottom: 6px;
+ font-size: 12px;
+ color: @color_error_primary;
}
+
+ .error {
+ color: @color_error_primary;
+ }
+ .warning {
+ color: @color_warning_primary;
+ }
+ .info {
+ color: @color_green_primary;
+ }
+ }
+
+ .input-icon {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 54px;
+ margin: auto 0;
+ height: 26px;
+ color: @color_green_primary;
}
- input, select, textarea {
- display: inline-block;
- color: #424242;
- font-size: 14px;
- padding: 6px 12px;
- line-height: 1;
- background-color: #fff;
- border: 1px solid #ccc;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- outline: 0;
+ .address-input {
+ input.valid + .input-icon:before {
+ .icomoon-checked;
+ }
+ input.not-valid + .input-icon:before {
+ .icomoon-error;
+ color: @color_error_primary;
+ }
+ input.warning + .input-icon:before {
+ .icomoon-warning;
+ color: @color_warning_primary;
+ }
}
- input[type=text] {
- transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
- width: 400px;
- &.small {
- width: 150px;
+ .amount-input {
+ position: relative;
+ display: flex;
+ flex-direction: row;
+
+ input {
+ flex: 1;
+ border-radius: 2px 0px 0px 2px;
}
- &:disabled {
- background: #dddddd;
+
+ .set-max {
+ position: relative;
+ height: 34px;
+ line-height: 34px;
+ font-size: 12px;
+ font-weight: 300; // different
+ color: @color_text_secondary;
+ border: 1px solid @color_divider;
+ border-right: 0px;
+ border-left: 0px;
+ background: @color_white;
+ padding: 0px 10px 0px 32px;
+ cursor: pointer;
+ .hover();
+
+ &:before {
+ .icomoon-setmax;
+ width: 24px;
+ height: 24px;
+ font-size: 24px;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ margin: auto 0;
+ left: 4px;
+ }
+
+ &:hover {
+ background: @color_gray_light;
+ }
+
+ &:active {
+ background: @color_divider;
+ }
+
+ &.enabled {
+ color: @color_white;
+ background: @color_green_primary;
+ &:before {
+ .icomoon-checked;
+ }
+
+ &:hover {
+ background: @color_green_secondary;
+ }
+
+ &:active {
+ background: @color_green_tertiary;
+ }
+ }
+ }
+ }
+
+ .advanced {
+ font-weight: 500;
+ line-height: 40px; // button height
+ color: @color_text_secondary;
+ .hover();
+ &:hover,
+ &:active {
+ color: @color_text_primary;
+ }
+ &:after {
+ .icomoon-arrow-down;
+ transition: transform 0.3s;
+ transform-origin: 50% 50%;
+ font-size: 24px;
+ position: relative;
+ top: 6px;
+ left: 8px;
}
}
+ .advanced-container {
+ display: flex;
+ justify-content: space-between;
+ padding: 0px 48px;
+ button {
+ width: 50%;
+ }
+ &.opened {
+ flex-direction: column;
+ padding: 0px;
+ button {
+ position: relative;
+ left: 50%;
+ width: 50%;
+ }
+ .advanced {
+ display: inline-block;
+ margin: 0px 48px 12px 48px;
+ &:after {
+ transform: rotate(180deg);
+ top: 5px;
+ }
+ }
+ }
- select {
- background: url('data:image/svg+xml;utf8, ');
- background-color: @color_white;
- background-repeat: no-repeat;
- background-position: right 8px top 6px;
- background-size: 16px 16px;
- width: 150px;
- height: 31px;
- -webkit-appearance: none;
- -moz-appearance: none;
- text-indent: 0px;
- text-overflow: '';
- border-radius: 0;
- &:disabled {
- background: #dddddd;
+ .what-is-it {
+ &:before {
+ .icomoon-help;
+ .hover();
+ transform-origin: 50% 50%;
+ font-size: 24px;
+ position: relative;
+ top: 5px;
+ cursor: pointer;
+ }
+ &:hover {
+ &:before {
+ color: @color_text_primary;
+ }
+ }
}
+
}
- button {
- display: inline-block;
- font-weight: normal;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid transparent;
- white-space: nowrap;
- padding: 6px 12px;
- font-size: 14px;
- font-weight: 600;
- text-transform: uppercase;
- line-height: 1.42857143;
- border-radius: 0;
- user-select: none;
- transition: all 0.15s ease 0s;
- color: #ffffff;
- background-color: #4cc148;
+ .gas-row {
+ display: flex;
+ flex-direction: row;
+ border-top: 1px solid @color_divider;
+ padding-top: 24px;
+
+ .column {
+ position: relative;
+ flex: 1;
+ padding-right: 20px;
- &:hover {
- box-shadow: 0 0 24px rgba(0, 0, 0, 0.15);
+ &:last-child {
+ padding-right: 0px;
+ }
+
+ .error,
+ .warning,
+ .info {
+ left: 0;
+ bottom: -17px;
+ }
}
+ }
- &:disabled {
- color: #666666;
- pointer-events: none;
- background: #dddddd;
+ .update-fee-levels {
+ position: relative;
+ font-size: 12px;
+ color: @color_warning_primary;
+ padding-left: 24px;
+ margin-left: 8px;
+ a {
+ text-decoration: underline;
+ color: @color_green_primary;
+ margin-left: 4px;
+ }
+ &:before {
+ .icomoon-warning;
+ position: absolute;
+ top: -4px;
+ left: 0;
}
}
+
+ label {
+ display: block;
+ font-size: 14px;
+ color: @color_text_secondary;
+ padding-bottom: 4px;
+ }
+
+ input,
+ textarea {
+ width: 100%;
+ }
+
+ textarea {
+ resize: none;
+ height: 80px;
+ }
}
\ No newline at end of file
diff --git a/src/styles/signverify.less b/src/styles/signverify.less
new file mode 100644
index 00000000..356da153
--- /dev/null
+++ b/src/styles/signverify.less
@@ -0,0 +1,36 @@
+.signverify {
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ background: @color_white;
+
+ h2 {
+ line-height: 74px;
+ }
+
+ .sign,
+ .verify {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding-left: 50px;
+
+ textarea {
+ resize: vertical;
+ width: 100%;
+ }
+
+ // textarea[readonly] {
+ // background: #A9A9A9;
+ // }
+ }
+
+ label {
+ color: #A9A9A9;
+ padding: 5px 0px;
+ }
+
+ .verify {
+ padding-right: 20px;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/summary.less b/src/styles/summary.less
new file mode 100644
index 00000000..79bd50cc
--- /dev/null
+++ b/src/styles/summary.less
@@ -0,0 +1,232 @@
+.summary {
+
+ h2 {
+ //padding: 35px 50px 0px 50px;
+ color: red;
+ }
+
+ .token-select {
+ width: 100%;
+ height: 34px;
+ font-family: @font-family-monospace;
+
+ .Select-control {
+ height: 34px;
+ border: 1px solid @color_divider;
+ }
+
+ .Select-input {
+
+ }
+
+ .Select-arrow-zone {
+ display: none;
+ }
+ }
+
+ .identicon {
+ display: inline-block;
+ vertical-align: middle;
+ position: relative;
+ top: -4px;
+ margin-right: 10px;
+ border-radius: 50%;
+ }
+
+
+ .summary-details {
+ position: relative;
+ padding: 35px 50px 0px 50px;
+ border-bottom: 1px solid @color_divider;
+
+ .content {
+ .column {
+ display: inline-block;
+ width: 25%;
+ padding-bottom: 30px;
+
+ .label {
+ color: #A9A9A9;
+ font-weight: 600;
+ }
+
+ .fiat-value {
+ font-weight: bold;
+ font-size: 1.2em;
+ margin: 7px 0 6px 0;
+ color: #494949;
+ }
+ }
+ }
+
+ .toggle {
+ display: block;
+ position: absolute;
+ left: 50%;
+ margin-left: -20px;
+ bottom: -20px;
+ width: 40px;
+ height: 40px;
+ //line-height: 30px;
+ background: @color_white;
+ color: #B3B3B3;
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
+ border-radius: 50%;
+ cursor: pointer;
+ transition: all 0.2s ease-in-out;
+
+ &:hover {
+ background: #F2F2F2;
+ &:before {
+ color: #494949;
+ }
+ }
+
+ &:before {
+ .glyphicon-up;
+ color: #B3B3B3;
+ position: absolute;
+ left: 14px;
+ top: 16px;
+ transition: all 0.2s ease-in-out;
+ }
+ }
+
+ &.closed {
+ .content {
+ display: none;
+ }
+ .toggle {
+ &:before {
+ .glyphicon-down;
+ top: 18px;
+ }
+ }
+ }
+ }
+
+ .filter {
+ background: @color_main;
+ padding: 30px 48px 10px 48px;
+ // text-align: right;
+
+ // input {
+ // width: 300px;
+ // }
+ }
+
+ .add-token-form {
+ position: relative;
+ .toggle {
+ cursor: pointer;
+ padding: 15px 50px;
+ }
+
+ .content {
+ display: flex;
+ flex-direction: row;
+ padding: 15px 50px;
+ }
+
+
+ .column {
+ padding-right: 10px;
+ label {
+ display: block;
+ color: #A9A9A9;
+ font-weight: 600;
+ }
+
+ input {
+ &.token-address {
+ width: 230px;
+ }
+
+ &.token-name {
+ width: 160px;
+ }
+
+ &.token-shortcut {
+ width: 80px;
+ }
+
+ &.token-decimal {
+ width: 80px;
+ }
+ }
+
+ button {
+
+ }
+ }
+
+ &:after {
+ .glyphicon-up;
+ color: #B3B3B3;
+ position: absolute;
+ right: 50px;
+ top: 21px;
+ transition: all 0.2s ease-in-out;
+ }
+
+ &:hover {
+ &:after {
+ color: #494949;
+ }
+ }
+
+ &.closed {
+ &:after {
+ .glyphicon-down;
+ }
+ }
+ }
+
+ .token {
+ border-top: 1px solid @color_divider;
+ padding: 15px 50px;
+ display: flex;
+ flex-direction: row;
+
+ .icon {
+ width: 36px;
+ height: 36px;
+ //border: 8px solid white;
+ border-radius: 50%;
+ margin-right: 10px;
+ line-height: 30px;
+ text-transform: uppercase;
+ user-select: none;
+ text-align: center;
+ padding: 6px;
+ p {
+ line-height: 24px;
+ padding: 0px;
+ color: inherit;
+ }
+ }
+
+ .name {
+ flex: 1;
+ line-height: 30px;
+ }
+
+ .balance {
+ color: red;
+ line-height: 30px;
+ }
+
+ &:last-child {
+ // border-bottom: 1px solid @color_divider;
+ }
+ }
+
+ .token-select {
+ .Select-control {
+ cursor: text;
+ }
+ }
+}
+
+
+
diff --git a/src/styles/topNavigation.less b/src/styles/topNavigation.less
new file mode 100644
index 00000000..0003ecb1
--- /dev/null
+++ b/src/styles/topNavigation.less
@@ -0,0 +1,243 @@
+nav {
+ display: flex;
+ width: 100%;
+ max-width: 1170px;
+ height: 64px;
+ margin: 0 auto;
+ margin-top: 32px;
+ z-index: 1;
+ background: @color_white;
+ border-radius: 4px 4px 0px 0px;
+ border-bottom: 1px solid @color_divider;
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
+
+ @media screen and (max-width: 1170px) {
+ border-radius: 0px;
+ margin-top: 0px;
+ }
+
+ // .layout-wrapper {
+ // height: 100%;
+ // background: @color_white;
+ // border-radius: 4px 4px 0px 0px;
+ // border-bottom: 1px solid rgba(218, 218, 218, 0.5);
+ // box-shadow: 0 3px 8px rgba(0, 0, 0, 0.04);
+ // }
+
+ // override styles for react-select
+ .device-select {
+ width: 320px;
+ height: 64px;
+ // display: inline-block;
+ // vertical-align: middle;
+ box-shadow: none;
+
+ &.is-focused:not(.is-open) > .Select-control {
+ border-color: @color_divider;
+ box-shadow: none;
+ }
+
+ .Select-control {
+ height: 63px;
+ border: 0px;
+ border-radius: 4px 0px 0px 0px;
+ border-right: 1px solid @color_divider;
+ cursor: pointer;
+ transition: color 0.2s ease-in-out;
+
+ .Select-input {
+ background: transparent;
+ position: absolute;
+ top: 0;
+ //display: none !important;
+ }
+
+ &:hover {
+ background: transparent;
+ // border: 0px;
+ border-right: 1px solid @color_divider;
+ box-shadow: none;
+
+ .Select-arrow {
+ &:after {
+ color: @color_text_primary;
+ }
+ }
+ }
+ }
+
+ .Select-arrow-zone {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ margin: auto 0;
+ padding: 0px;
+ width: 24px;
+ height: 24px;
+ right: 24px;
+
+ .Select-arrow {
+ top: 0px;
+ border: 0px;
+ width: 24px;
+
+ &:after {
+ .icomoon-arrow-down;
+ transition: transform 0.3s;
+ color: @color_text_secondary;
+ transform-origin: 50% 50%;
+ font-size: 24px;
+ }
+ }
+ }
+
+
+
+ .Select-option {
+ &:hover {
+ background: red;
+ }
+
+ &.is-selected {
+ background: yellow;
+ }
+ }
+
+ &.is-open {
+ .Select-control {
+ border-color: @color_divider;
+ }
+
+ .Select-arrow {
+ top: 0px !important;
+ border: 0px;
+
+
+ &:after {
+ // .icomoon-arrow-up;
+ transform: rotate(180deg);
+
+ }
+ }
+ }
+
+ &.is-disabled {
+
+ .Select-control {
+ background: transparent;
+ cursor: default;
+ }
+
+ .Select-arrow {
+ visibility: hidden;
+ &:after {
+ content: ''
+ }
+ }
+
+ .device {
+ .device-menu {
+ padding-right: 24px;
+ }
+ }
+ }
+
+ .Select-menu-outer {
+ border-radius: 0px;
+ border: 1px solid @color_divider;
+ box-shadow: none;
+ visibility: hidden;
+ }
+ }
+
+ .device {
+ height: 63px;
+ width: 319px;
+ display: flex;
+ align-items: center;
+ padding-left: 80px;
+
+ &:before {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 13px;
+ height: 25px;
+ z-index: 2;
+ left: 33px;
+ top: 17px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 13px 25px;
+ background-image: url('../images/icontrezor.png');
+ }
+
+ .label-container {
+ flex: 1;
+ overflow: hidden;
+ span {
+ display: block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+
+ &.label {
+ font-weight: 500;
+ font-size: 14px;
+ color: @color_text_primary;
+ }
+
+ &.status {
+ font-size: 12px;
+ color: @color_text_secondary;
+ }
+ }
+ }
+
+ .device-menu {
+ display: flex;
+ justify-content: flex-end;
+ padding-right: 48px;
+ padding-left: 4px;
+
+ div {
+ display: inline-block;
+ }
+
+ .forget,
+ .settings,
+ .acquire {
+ cursor: pointer;
+
+ &:before {
+ .icomoon-refresh;
+ color: @color_text_secondary;
+ position: relative;
+ font-size: 24px;
+ .hover();
+ }
+
+ &:hover {
+ &:before {
+ color: @color_text_primary;
+ }
+ }
+ }
+
+ .forget {
+ &:before {
+ .icomoon-eject;
+ }
+ }
+
+ .settings {
+ &:before {
+ .icomoon-settings;
+ }
+ }
+ }
+
+ }
+
+
+}
diff --git a/webpack/webpack.config.dev.js b/webpack/webpack.config.dev.js
index 6ea5578a..f50c2f65 100644
--- a/webpack/webpack.config.dev.js
+++ b/webpack/webpack.config.dev.js
@@ -38,6 +38,15 @@ module.exports = {
fallback: 'style-loader'
})
},
+ {
+ test: /\.css$/,
+ loader: extractLess.extract({
+ use: [
+ { loader: 'css-loader' }
+ ],
+ fallback: 'style-loader'
+ })
+ },
{
test: /\.(png|gif|jpg)$/,
loader: 'file-loader?name=./images/[name].[ext]'
@@ -50,14 +59,14 @@ module.exports = {
},
},
{
- test: /\.json$/,
+ test: /\.json($|\?)/,
loader: 'json-loader'
},
{
test: /\.(ttf|eot|svg|woff|woff2)$/,
loader: 'file-loader',
query: {
- name: '[name].[ext]',
+ name: './fonts/[name].[hash].[ext]',
},
},
@@ -71,6 +80,7 @@ module.exports = {
},
plugins: [
extractLess,
+
new HtmlWebpackPlugin({
chunks: ['index'],
template: `${SRC}index.html`,
@@ -91,7 +101,8 @@ module.exports = {
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
PRODUCTION: JSON.stringify(false)
- })
+ }),
+ new webpack.IgnorePlugin(/node-fetch/), // for trezor-link warning
],
// ignoring "fs" import in fastxpub
node: {
diff --git a/webpack/webpack.config.prod.babel.js b/webpack/webpack.config.prod.babel.js
index b8fb76eb..1d02592c 100644
--- a/webpack/webpack.config.prod.babel.js
+++ b/webpack/webpack.config.prod.babel.js
@@ -1,4 +1,4 @@
-import { SRC, BUILD } from './constants';
+import { SRC, BUILD, TREZOR_LIBRARY, TREZOR_CONNECT_FILES } from './constants';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
@@ -11,10 +11,11 @@ const extractLess = new ExtractTextPlugin({
module.exports = {
entry: {
- index: ['whatwg-fetch', `${SRC}js/index.js`]
+ index: ['whatwg-fetch', `${SRC}js/index.js`],
+ 'trezor-library': `${TREZOR_LIBRARY}.js`
},
output: {
- filename: 'js/[name].[chunkhash].js',
+ filename: 'js/[name].[hash].js',
path: BUILD
},
module: {
@@ -35,18 +36,41 @@ module.exports = {
fallback: 'style-loader'
})
},
+ {
+ test: /\.css$/,
+ loader: extractLess.extract({
+ use: [
+ { loader: 'css-loader' }
+ ],
+ fallback: 'style-loader'
+ })
+ },
+ {
+ test: /\.(png|gif|jpg)$/,
+ loader: 'file-loader?name=../images/[name].[ext]'
+ },
{
test: /\.(ttf|eot|svg|woff|woff2)$/,
loader: 'file-loader?publicPath=../&name=fonts/[name].[ext]',
},
{
- test: /\.(png|gif|jpg)$/,
- loader: 'file-loader?publicPath=../&name=images/[name].[ext]',
+ test: /\.(wasm)$/,
+ loader: 'file-loader',
+ query: {
+ name: 'js/[name].[ext]',
+ },
+ },
+ {
+ test: /\.json$/,
+ loader: 'json-loader'
},
]
},
resolve: {
- modules: [SRC, 'node_modules']
+ modules: [SRC, 'node_modules'],
+ alias: {
+ 'trezor-connect': `${TREZOR_LIBRARY}`,
+ }
},
performance: {
hints: false
@@ -62,22 +86,32 @@ module.exports = {
new CopyWebpackPlugin([
//{from: `${SRC}/app/robots.txt`},
- { from: `${SRC}js/vendor`, to: `${BUILD}js/vendor` },
- { from: `${SRC}images/favicon.ico` },
- { from: `${SRC}images/favicon.png` },
+ //{ from: `${SRC}js/vendor`, to: `${BUILD}js/vendor` },
//{ from: `${SRC}config.json` },
{ from: `${SRC}images`, to: `${BUILD}images` },
+ { from: `${SRC}data`, to: `${BUILD}data` },
]),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
- new webpack.optimize.UglifyJsPlugin({
- compress: {
- warnings: false,
- }
- }),
+ // new webpack.optimize.UglifyJsPlugin({
+ // compress: {
+ // warnings: false,
+ // }
+ // }),
+ new CopyWebpackPlugin([
+ { from: `${TREZOR_CONNECT_FILES}coins.json` },
+ { from: `${TREZOR_CONNECT_FILES}releases.json` },
+ { from: `${TREZOR_CONNECT_FILES}latest.txt` },
+ { from: `${TREZOR_CONNECT_FILES}config_signed.bin` },
+ ]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
PRODUCTION: JSON.stringify(false)
- })
- ]
+ }),
+ new webpack.IgnorePlugin(/node-fetch/), // for trezor-link warning
+ ],
+ // ignoring "fs" import in fastxpub
+ node: {
+ fs: "empty"
+ }
}
diff --git a/yarn.lock b/yarn.lock
index 4545209a..110f1adf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -27,6 +27,12 @@ acorn@^5.0.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7"
+add-dom-event-listener@1.x:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz#8faed2c41008721cf111da1d30d995b85be42bed"
+ dependencies:
+ object-assign "4.x"
+
ajv-keywords@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
@@ -773,7 +779,7 @@ babel-register@^6.26.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
-babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+babel-runtime@6.x, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@@ -846,6 +852,10 @@ big.js@^3.1.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
+bignumber.js@^2.3.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8"
+
bignumber.js@^4.0.2:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.1.0.tgz#db6f14067c140bd46624815a7916c92d9b6c24b1"
@@ -1109,7 +1119,7 @@ clap@^1.0.9:
dependencies:
chalk "^1.1.3"
-classnames@^2.2.5:
+classnames@^2.2.4, classnames@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
@@ -1170,6 +1180,10 @@ color-convert@^1.3.0, color-convert@^1.9.0:
dependencies:
color-name "^1.1.1"
+color-hash@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/color-hash/-/color-hash-1.0.3.tgz#c0e7952f06d022e548e65da239512bd67d3809ee"
+
color-name@^1.0.0, color-name@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
@@ -1218,6 +1232,16 @@ commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+component-classes@^1.2.5:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691"
+ dependencies:
+ component-indexof "0.0.3"
+
+component-indexof@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/component-indexof/-/component-indexof-0.0.3.tgz#11d091312239eb8f32c8f25ae9cb002ffe8d3c24"
+
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -1306,6 +1330,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+create-react-class@^15.6.0:
+ version "15.6.2"
+ resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a"
+ dependencies:
+ fbjs "^0.8.9"
+ loose-envify "^1.3.1"
+ object-assign "^4.1.1"
+
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -1340,6 +1372,13 @@ crypto-js@^3.1.4:
version "3.1.8"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5"
+css-animation@^1.3.2:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/css-animation/-/css-animation-1.4.1.tgz#5b8813125de0fbbbb0bbe1b472ae84221469b7a8"
+ dependencies:
+ babel-runtime "6.x"
+ component-classes "^1.2.5"
+
css-color-names@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@@ -1511,6 +1550,10 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
+dom-align@1.x:
+ version "1.6.7"
+ resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.6.7.tgz#6858138efb6b77405ce99146d0be5e4f7282813f"
+
dom-converter@~0.1:
version "0.1.4"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b"
@@ -1745,6 +1788,12 @@ ethereumjs-tx@^1.3.3:
ethereum-common "^0.0.18"
ethereumjs-util "^5.0.0"
+ethereumjs-units@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/ethereumjs-units/-/ethereumjs-units-0.2.0.tgz#6ea31132aabc2cc7b8a5290e265593a337687fa3"
+ dependencies:
+ bignumber.js "^2.3.0"
+
ethereumjs-util@^5.0.0, ethereumjs-util@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.1.2.tgz#25ba0215cbb4c2f0b108a6f96af2a2e62e45921f"
@@ -1877,7 +1926,7 @@ fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
-fbjs@^0.8.16:
+fbjs@^0.8.16, fbjs@^0.8.9:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
dependencies:
@@ -2616,6 +2665,10 @@ lodash-es@^4.2.0, lodash-es@^4.2.1:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
+lodash._getnative@^3.0.0:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+
lodash._reinterpolate@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -2624,6 +2677,22 @@ lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+lodash.isarguments@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+
+lodash.isarray@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+
+lodash.keys@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
+ dependencies:
+ lodash._getnative "^3.0.0"
+ lodash.isarguments "^3.0.0"
+ lodash.isarray "^3.0.0"
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -2944,7 +3013,7 @@ oauth-sign@~0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@4.x, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -3085,6 +3154,10 @@ path-to-regexp@^1.7.0:
dependencies:
isarray "0.0.1"
+path-to-regexp@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.1.0.tgz#7e30f9f5b134bd6a28ffc2e3ef1e47075ac5259b"
+
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@@ -3105,6 +3178,10 @@ performance-now@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+performance-now@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+
pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -3402,7 +3479,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
-prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0:
+prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0:
version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
dependencies:
@@ -3474,6 +3551,12 @@ querystring@0.2.0, querystring@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
+raf@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
+ dependencies:
+ performance-now "^2.1.0"
+
randomatic@^1.1.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
@@ -3507,6 +3590,50 @@ raw-body@2.3.2:
iconv-lite "0.4.19"
unpipe "1.0.0"
+rc-align@2.x:
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-2.3.5.tgz#5085cfa4d685ee9d030b9afd2971eb370c5e80a1"
+ dependencies:
+ babel-runtime "^6.26.0"
+ dom-align "1.x"
+ prop-types "^15.5.8"
+ rc-util "^4.0.4"
+
+rc-animate@2.x:
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.4.4.tgz#a05a784c747beef140d99ff52b6117711bef4b1e"
+ dependencies:
+ babel-runtime "6.x"
+ css-animation "^1.3.2"
+ prop-types "15.x"
+
+rc-tooltip@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-3.7.0.tgz#3afbf109865f7cdcfe43752f3f3f501f7be37aaa"
+ dependencies:
+ babel-runtime "6.x"
+ prop-types "^15.5.8"
+ rc-trigger "^2.2.2"
+
+rc-trigger@^2.2.2:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-2.3.4.tgz#389dfa5e834ecc3a446fe9cefc0b4a32900f4227"
+ dependencies:
+ babel-runtime "6.x"
+ prop-types "15.x"
+ rc-align "2.x"
+ rc-animate "2.x"
+ rc-util "^4.4.0"
+
+rc-util@^4.0.4, rc-util@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.4.0.tgz#f6a320a67100cfceaaa1b0a955b01e9be643576c"
+ dependencies:
+ add-dom-event-listener "1.x"
+ babel-runtime "6.x"
+ prop-types "^15.5.10"
+ shallowequal "^0.2.2"
+
rc@^1.1.7:
version "1.2.2"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077"
@@ -3516,10 +3643,41 @@ rc@^1.1.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
+react-blockies@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/react-blockies/-/react-blockies-1.2.2.tgz#36f2a1aa8b1e43012d0007396b3d0ac83e21807f"
+ dependencies:
+ prop-types "^15.5.10"
+
+react-css-transition@^0.7.4:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/react-css-transition/-/react-css-transition-0.7.4.tgz#546682a6eac87c98f6333c49cbf2dd995a93de36"
+ dependencies:
+ react-transition-group "^1.0.0"
+ reassemble "^0.5.6"
+
react-deep-force-update@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.1.tgz#8ea4263cd6455a050b37445b3f08fd839d86e909"
+react-dom@^15.4.1:
+ version "15.6.2"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730"
+ dependencies:
+ fbjs "^0.8.9"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.0"
+ prop-types "^15.5.10"
+
+"react-dom@^15.4.2 || ^16.0.0":
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
+ dependencies:
+ fbjs "^0.8.16"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ prop-types "^15.6.0"
+
react-dom@^16.1.1:
version "16.1.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.1.1.tgz#b2e331b6d752faf1a2d31399969399a41d8d45f8"
@@ -3529,6 +3687,13 @@ react-dom@^16.1.1:
object-assign "^4.1.1"
prop-types "^15.6.0"
+react-ellipsis-text@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/react-ellipsis-text/-/react-ellipsis-text-1.0.0.tgz#98ac5d4e1a2b21e7f76e49f5f23ce65c99f74590"
+ dependencies:
+ react "^15.4.1"
+ react-dom "^15.4.1"
+
react-hot-loader@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-3.1.3.tgz#6f92877326958c7cb0134b512474517869126082"
@@ -3539,6 +3704,12 @@ react-hot-loader@^3.1.3:
redbox-react "^1.3.6"
source-map "^0.6.1"
+react-input-autosize@^2.1.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.1.2.tgz#a3dc11a5517c434db25229925541309de3f7a8f5"
+ dependencies:
+ prop-types "^15.5.8"
+
react-proxy@^3.0.0-alpha.0:
version "3.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz#4400426bcfa80caa6724c7755695315209fa4b07"
@@ -3594,6 +3765,35 @@ react-router@^4.2.0:
prop-types "^15.5.4"
warning "^3.0.0"
+react-scale-text@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/react-scale-text/-/react-scale-text-1.2.2.tgz#4a56e1d2fd4e4582d2ad472c003ee12f51cbf2ae"
+ dependencies:
+ lodash "^4.17.4"
+ prop-types "^15.6.0"
+ react "^15.4.2 || ^16.0.0"
+ react-dom "^15.4.2 || ^16.0.0"
+ shortid "^2.2.8"
+ warning "^3.0.0"
+
+react-select@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.1.0.tgz#626a2de839fdea2ade74dd1b143a9bde34be6c82"
+ dependencies:
+ classnames "^2.2.4"
+ prop-types "^15.5.8"
+ react-input-autosize "^2.1.0"
+
+react-transition-group@^1.0.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6"
+ dependencies:
+ chain-function "^1.0.0"
+ dom-helpers "^3.2.0"
+ loose-envify "^1.3.1"
+ prop-types "^15.5.6"
+ warning "^3.0.0"
+
react-transition-group@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10"
@@ -3605,6 +3805,25 @@ react-transition-group@^2.2.1:
prop-types "^15.5.8"
warning "^3.0.0"
+react@^15.4.1:
+ version "15.6.2"
+ resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
+ dependencies:
+ create-react-class "^15.6.0"
+ fbjs "^0.8.9"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.0"
+ prop-types "^15.5.10"
+
+"react@^15.4.2 || ^16.0.0":
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
+ dependencies:
+ fbjs "^0.8.16"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ prop-types "^15.6.0"
+
react@^16.1.1:
version "16.1.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.1.1.tgz#d5c4ef795507e3012282dd51261ff9c0e824fe1f"
@@ -3659,6 +3878,12 @@ readdirp@^2.0.0:
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
+reassemble@^0.5.6:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/reassemble/-/reassemble-0.5.6.tgz#0162c769ff3d5a25a15c6296b0c4653c0014d8e0"
+ dependencies:
+ fbjs "^0.8.9"
+
redbox-react@^1.3.6:
version "1.5.0"
resolved "https://registry.yarnpkg.com/redbox-react/-/redbox-react-1.5.0.tgz#04dab11557d26651bf3562a67c22ace56c5d3967"
@@ -3932,6 +4157,12 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
+shallowequal@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e"
+ dependencies:
+ lodash.keys "^3.1.2"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -3942,6 +4173,10 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+shortid@^2.2.8:
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
+
signal-exit@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"