├── .bowerrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── app.js ├── assets │ ├── css │ │ └── main.css │ └── img │ │ ├── eth_icon.png │ │ └── ethicon.png ├── components │ └── version │ │ ├── interpolate-filter.js │ │ ├── interpolate-filter_test.js │ │ ├── version-directive.js │ │ ├── version-directive_test.js │ │ ├── version.js │ │ └── version_test.js ├── index.html ├── scripts │ └── controllers │ │ ├── addressInfosController.js │ │ ├── blockInfosController.js │ │ ├── chainInfosController.js │ │ ├── mainController.js │ │ └── transactionInfosController.js └── views │ ├── addressInfos.html │ ├── api │ └── api.html │ ├── blockInfos.html │ ├── chainInfos.html │ ├── main.html │ └── transactionInfos.html ├── bower.json ├── docs ├── CNAME ├── app.js ├── assets │ ├── css │ │ └── main.css │ └── img │ │ ├── eth_icon.png │ │ └── ethicon.png ├── components │ └── version │ │ ├── interpolate-filter.js │ │ ├── interpolate-filter_test.js │ │ ├── version-directive.js │ │ ├── version-directive_test.js │ │ ├── version.js │ │ └── version_test.js ├── index.html ├── scripts │ └── controllers │ │ ├── addressInfosController.js │ │ ├── blockInfosController.js │ │ ├── chainInfosController.js │ │ ├── mainController.js │ │ └── transactionInfosController.js ├── vendor_js │ ├── angular-moment.min.js │ ├── angular-route.min.js │ ├── angular-sanitize.min.js │ ├── angular.min.js │ ├── bootstrap.min.js │ ├── de.js │ ├── jquery.min.js │ ├── moment.js │ ├── ui-bootstrap.min.js │ └── web3.min.js └── views │ ├── addressInfos.html │ ├── api │ └── api.html │ ├── blockInfos.html │ ├── chainInfos.html │ ├── main.html │ └── transactionInfos.html ├── e2e-tests ├── protractor.conf.js └── scenarios.js ├── karma.conf.js ├── manuals ├── README-AltSheets.md ├── README.md └── serverconfig.md ├── package-lock.json ├── package.json └── testfile /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components", 3 | "save-exact": true 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folder view configuration files 2 | **/.DS_Store 3 | Desktop.ini 4 | 5 | # Thumbnail cache files 6 | ._* 7 | Thumbs.db 8 | 9 | # Files that might appear on external disks 10 | .Spotlight-V100 11 | .Trashes 12 | 13 | # Compiled Python files 14 | *.pyc 15 | 16 | # Compiled C++ files 17 | *.out 18 | 19 | # Application specific files 20 | venv 21 | node_modules/ 22 | .sass-cache 23 | .idea/ 24 | # Logs 25 | logs 26 | *.log 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # Runtime data 32 | pids 33 | *.pid 34 | *.seed 35 | *.pid.lock 36 | 37 | # Directory for instrumented libs generated by jscoverage/JSCover 38 | lib-cov 39 | 40 | # Coverage directory used by tools like istanbul 41 | coverage 42 | 43 | # nyc test coverage 44 | .nyc_output 45 | 46 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 47 | .grunt 48 | 49 | # Bower dependency directory (https://bower.io/) 50 | bower_components/ 51 | */bower_components/ 52 | 53 | # node-waf configuration 54 | .lock-wscript 55 | 56 | # Compiled binary addons (http://nodejs.org/api/addons.html) 57 | build/Release 58 | 59 | # Dependency directories 60 | node_modules/ 61 | jspm_packages/ 62 | 63 | # Typescript v1 declaration files 64 | typings/ 65 | uploads 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | .idea 73 | 74 | # Optional REPL history 75 | .node_repl_history 76 | 77 | # Output of 'npm pack' 78 | *.tgz 79 | 80 | # Yarn Integrity file 81 | .yarn-integrity 82 | 83 | # dotenv environment variables file 84 | .env -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globalstrict": true, 3 | "globals": { 4 | "angular": false, 5 | "describe": false, 6 | "it": false, 7 | "expect": false, 8 | "beforeEach": false, 9 | "afterEach": false, 10 | "module": false, 11 | "inject": false 12 | } 13 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm start > /dev/null & 9 | - npm run update-webdriver 10 | - sleep 1 # give server time to start 11 | 12 | script: 13 | - node_modules/.bin/karma start karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox 14 | - node_modules/.bin/protractor e2e-tests/protractor.conf.js --browser=firefox 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sayed Tauseef Haider Naqvi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ETH Block Explorer based off github.com/etherparty/explorer 2 | 3 | ![ETH Block Explorer Screenshot](https://i.imgur.com/8dPnAct.jpg) 4 | 5 | ## Demo 6 | 7 | [https://explorer.ethorbit.com/](https://explorer.ethorbit.com/) 8 | 9 | ## License 10 | 11 | The code in this branch is licensed under GPLv3 (see LICENSE file) 12 | Feel free to modify or reuse the code here. 13 | 14 | ## Reddit 15 | 16 | Discuss this project at: [Reddit Page on /r/ethreum](https://www.reddit.com/r/ethereum/comments/7lwft2/new_ethereum_block_explorer_updated_version_of/) 17 | 18 | ## Donations 19 | 20 | ETH Address: 0x1BDA9C6A37ECd91940df1E7559A8403ecE9806D1 21 | 22 | ## Installation 23 | 24 | ``` 25 | git clone https://github.com/sthnaqvi/explorer 26 | npm install 27 | bower install 28 | npm start 29 | ``` 30 | 31 | Make sure to install geth as well for the ETH explorer to be able to function. Then run: 32 | ``` 33 | geth --rpc --rpcaddr localhost --rpcport 8545 --rpcapi "web3,eth" --rpccorsdomain "http://localhost:8000" 34 | ``` 35 | 36 | Then visit http://localhost:8000 in your browser of choice after you npm start the explorer 37 | 38 | ## Updates since original etherpaty/explorer base: 39 | 40 | - Regular Expressions completed for Addresses, Block #s, and Transacions IDs (aka Search works great) 41 | - The theme is based off Bootstrap V3 for responsive design. 42 | - You can easily change from a cosmo or light theme utilizing https://bootswatch.com 43 | - There is a basic API implemented now as well as well as a Ethereum Blockchain Information page 44 | - Realtime ETH/USD Price Ticker 45 | - Realtime Ethereum Hashrate 46 | - Address Pages are integrated with Shapeshift to easily send a payment to an address. 47 | - Responsive design 48 | - Fontawesome Icons 49 | - Block Time Averages 50 | - Gas Prices/Limits 51 | - Total/Current Difficulty 52 | - Realtime latest blocks and recent transactions 53 | - Other random blockchain info stats were added 54 | 55 | _If you want to disable auto refresh/auto new block show, just comment line no 13-22 at: [app/scripts/controllers/mainController.js](https://github.com/sthnaqvi/explorer/blob/3a08032fc8550a863ae49acf0bdd45bfe2d961d1/app/scripts/controllers/mainController.js#L13-L22)_ 56 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | // begin AltSheets changes 2 | /////////////////////////////// 3 | // TODO: Put go into a config.js 4 | // But how to include a file from local? 5 | 6 | var GETH_HOSTNAME = "localhost"; // put your IP address! 7 | var APP_HOSTNAME = "See package.json --> scripts --> start: Change 'localhost'!!!"; 8 | 9 | var GETH_RPCPORT = 8545; // for geth --rpcport GETH_RPCPORT 10 | var APP_PORT = "See package.json --> scripts --> start: Perhaps change '8000'"; 11 | 12 | // this is creating the corrected geth command 13 | var WL = window.location; 14 | var geth_command = "geth --rpc --rpcaddr " + GETH_HOSTNAME + " --rpcport " + GETH_RPCPORT + '\ 15 | --rpcapi "web3,eth" ' + ' --rpccorsdomain "' + WL.protocol + "//" + WL.host + '"'; 16 | 17 | //////////////////////////////////////////////////// 18 | //end AltSheets changes 19 | 20 | 21 | 'use strict'; 22 | 23 | angular.module('ethExplorer', ['ngRoute', 'ui.bootstrap', 'filters', 'ngSanitize', 'angularMoment']) 24 | 25 | .config(['$routeProvider', 26 | function ($routeProvider) { 27 | $routeProvider.when('/', { 28 | templateUrl: 'views/main.html', 29 | controller: 'mainCtrl' 30 | }).when('/block/:blockId', { 31 | templateUrl: 'views/blockInfos.html', 32 | controller: 'blockInfosCtrl' 33 | }).when('/tx/:transactionId', { 34 | templateUrl: 'views/transactionInfos.html', 35 | controller: 'transactionInfosCtrl' 36 | }).when('/address/:addressId', { 37 | templateUrl: 'views/addressInfos.html', 38 | controller: 'addressInfosCtrl' 39 | }).// info page with links: 40 | when('/chain/api', { 41 | templateUrl: 'views/api/api.html', 42 | controller: 'chainInfosCtrl' 43 | }).// getBlock (current) & getBlock (last) 44 | when('/chain/', { 45 | templateUrl: 'views/chainInfos.html', 46 | controller: 'chainInfosCtrl' 47 | }).when('/chain/gaslimit', { 48 | templateUrl: 'views/api/gaslimit.html', 49 | controller: 'chainInfosCtrl' 50 | }).when('/chain/difficulty', { 51 | templateUrl: 'views/api/difficulty.html', 52 | controller: 'chainInfosCtrl' 53 | })./* 54 | // fast = doesn't need to getBlock any block 55 | when('/chain/blocknumber', { 56 | templateUrl: 'views/api/blocknumber.html', 57 | controller: 'fastInfosCtrl' 58 | }). 59 | when('/chain/supply', { 60 | templateUrl: 'views/api/supply.html', 61 | controller: 'fastInfosCtrl' 62 | }). 63 | when('/chain/mined', { 64 | templateUrl: 'views/api/mined.html', 65 | controller: 'fastInfosCtrl' 66 | }). 67 | 68 | // begin of: not yet, see README.md 69 | when('/chain/supply/public', { 70 | templateUrl: 'views/api/supplypublic.html', 71 | controller: 'fastInfosCtrl' 72 | }).*/ 73 | // end of: not yet, see README.md 74 | 75 | otherwise({ 76 | redirectTo: '/' 77 | }); 78 | 79 | //$locationProvider.html5Mode(true); 80 | }]) 81 | .run(function ($rootScope) { 82 | var Web3 = require('web3'); 83 | var web3 = new Web3(); 84 | 85 | // begin AltSheets changes 86 | web3.setProvider(new web3.providers.HttpProvider("http://" + GETH_HOSTNAME + ":" + GETH_RPCPORT)); 87 | // end AltSheets changes 88 | 89 | $rootScope.web3 = web3; 90 | // MetaMask injects its own web3 instance in all pages, override it 91 | // as it might be not compatible with the one used here 92 | window.web3 = web3; 93 | 94 | function sleepFor(sleepDuration) { 95 | var now = new Date().getTime(); 96 | while (new Date().getTime() < now + sleepDuration) { /* do nothing */ 97 | } 98 | } 99 | 100 | var connected = false; 101 | if (!web3.isConnected()) { 102 | $('#connectwarning').modal({keyboard: false, backdrop: 'static'}) 103 | $('#connectwarning').modal('show') 104 | } 105 | }) 106 | .controller('processRequestCtrl', function ($scope, $location) { 107 | 108 | $scope.processRequest = function () { 109 | var requestStr = $scope.ethRequest; 110 | 111 | if (requestStr !== undefined) { 112 | 113 | // maybe we can create a service to do the reg ex test, so we can use it in every controller ? 114 | var regexpTx = /[0-9a-zA-Z]{64}?/; 115 | //var regexpAddr = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/; // TODO ADDR REGEX or use isAddress(hexString) API ? 116 | var regexpAddr = /^(0x)?[0-9a-f]{40}$/; //New ETH Regular Expression for Addresses 117 | var regexpBlock = /[0-9]{1,7}?/; 118 | 119 | var result = regexpTx.test(requestStr); 120 | if (result === true) { 121 | goToTxInfos(requestStr) 122 | } 123 | else { 124 | result = regexpAddr.test(requestStr.toLowerCase()); 125 | if (result === true) { 126 | goToAddrInfos(requestStr.toLowerCase()) 127 | } 128 | else { 129 | result = regexpBlock.test(requestStr); 130 | if (result === true) { 131 | goToBlockInfos(requestStr) 132 | } 133 | else { 134 | console.log("nope"); 135 | return null; 136 | } 137 | } 138 | } 139 | } 140 | else { 141 | return null; 142 | } 143 | }; 144 | 145 | function goToBlockInfos(requestStr) { 146 | $location.path('/block/' + requestStr); 147 | } 148 | 149 | function goToAddrInfos(requestStr) { 150 | $location.path('/address/' + requestStr.toLowerCase()); 151 | } 152 | 153 | function goToTxInfos(requestStr) { 154 | $location.path('/tx/' + requestStr); 155 | } 156 | }) 157 | .directive('nTooltips', function () { 158 | return { 159 | link: function ($scope, element, attrs) { 160 | var imgArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAALCAYAAACQy8Z9AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACgSURBVHjaYlx28hEDEYANiEOAeBm6RKSZLIZiJiIN3CgvzLUUSNcR4wIWYgxUl+D10JLkZWBjZmq8/eoLSLyJXENBBq4DGWgkJwAW0JHmA1EEDWYiYKA3zEAQ4GBlBhusKsbTiC8omIg1kBSDmUgxEJvBy089rsNnKFEGEmMwE5KBq4k1kJDBTEgG+pFiILLBJgqCKAYz/gcCBioDgAADAErmMqR0X1/QAAAAAElFTkSuQmCC'; 161 | var imgArrowCss = {'position': 'absolute', 'top': '-10px', 'left': '5px'}; 162 | var ntooltipsCss = { 163 | 'display': 'none', 164 | 'position': 'absolute', 165 | 'padding': '10px', 166 | 'left': '5px', 167 | 'font-size': '0.8em', 168 | 'background-color': 'white', 169 | 'border': '1px solid #a6c9e2', 170 | '-moz-border-radius': '5px', 171 | '-webkit-border-radius': '5px', 172 | 'z-index': '9999' 173 | }; 174 | var ntooltips = function () { 175 | this.xOffset = -10; 176 | this.yOffset = 10; 177 | element.unbind().hover(function (a) { 178 | this.t = this.title; 179 | this.title = "" 180 | this.top = (a.pageY + yOffset); 181 | this.left = (a.pageX + xOffset); 182 | $("body").append('

' + this.t + "

"); 183 | $("p#ntooltips #ntooltipsArrow").attr("src", imgArrow).css(imgArrowCss); 184 | $("p#ntooltips").css(ntooltipsCss).css("top", this.top + "px").css("left", this.left + "px").fadeIn("slow") 185 | }, function () { 186 | this.title = this.t; 187 | $("p#ntooltips").fadeOut("slow").remove() 188 | }).mousemove(function (a) { 189 | this.top = (a.pageY + yOffset); 190 | this.left = (a.pageX + xOffset); 191 | $("p#ntooltips").css("top", this.top + "px").css("left", this.left + "px") 192 | }) 193 | }; 194 | jQuery(document).ready(function (a) { 195 | ntooltips() 196 | }); 197 | } 198 | }; 199 | }); -------------------------------------------------------------------------------- /app/assets/css/main.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | @import 'https://fonts.googleapis.com/css?family=Open+Sans|Raleway|Roboto'; 3 | 4 | body { 5 | font-family: 'Open Sans', sans-serif !important; 6 | } 7 | 8 | .menu { 9 | list-style: none; 10 | border-bottom: 0.1em solid black; 11 | margin-bottom: 2em; 12 | padding: 0 0 0.5em; 13 | } 14 | 15 | .menu:before { 16 | content: "["; 17 | } 18 | 19 | .menu:after { 20 | content: "]"; 21 | } 22 | 23 | .menu > li { 24 | display: inline; 25 | } 26 | 27 | .menu > li:before { 28 | content: "|"; 29 | padding-right: 0.3em; 30 | } 31 | 32 | .menu > li:nth-child(1):before { 33 | content: ""; 34 | padding: 0; 35 | } 36 | 37 | .navbar-dark { 38 | background-color: #293640; 39 | color: #FFF; 40 | } 41 | 42 | .navbar a { 43 | color: #FFF; 44 | } 45 | 46 | .navbar a:hover { 47 | color: #FFF; 48 | } 49 | 50 | .navbar a:visited { 51 | color: #FFF; 52 | } 53 | 54 | .title { 55 | color: #FFF; 56 | font-weight: bold; 57 | } 58 | 59 | input.searchi { 60 | background-color: #293640 !important; 61 | border: 2px solid #384a58; 62 | color: #fff; 63 | width: 100% !important; 64 | } 65 | 66 | input.searchimain { 67 | height: 50px; 68 | background-color: #293640 !important; 69 | border: 2px solid #384a58; 70 | color: #fff; 71 | width: 100%; 72 | } 73 | 74 | @media (min-width: 768px) { 75 | .searchimain { 76 | width: 800px !important; 77 | } 78 | } 79 | 80 | .boxi { 81 | background-color: #104050; 82 | color: #FFF; 83 | font-size: 18px; 84 | font-weight: lighter; 85 | padding: 15px; 86 | border-radius: 4px; 87 | margin-bottom: 20px; 88 | } 89 | 90 | .thead-inverse { 91 | background-color: #293640; 92 | color: #FFF; 93 | border-top-left-radius: 4px; 94 | border-top-right-radius: 4px; 95 | } 96 | 97 | .nav > li > a:focus, .nav > li > a:hover { 98 | background-color: #30404c !important; 99 | } 100 | 101 | .btn-blue { 102 | background-color: #293640 !important; 103 | border-color: #384a58 !important; 104 | color: #FFF !important; 105 | } 106 | 107 | .btn-blue.focus, .btn-blue:focus { 108 | background-color: #293640; 109 | border-color: #384a58; 110 | color: #FFF; 111 | } 112 | 113 | .btn-blue:hover { 114 | background-color: #293640; 115 | border-color: #384a58; 116 | color: #FFF; 117 | } 118 | 119 | .btn-bluem { 120 | background-color: #293640 !important; 121 | border-color: #384a58 !important; 122 | color: #FFF !important; 123 | height: 50px !important; 124 | } 125 | 126 | .btn-bluem.focus, .btn-bluem:focus { 127 | background-color: #293640; 128 | border-color: #384a58; 129 | color: #FFF; 130 | } 131 | 132 | .btn-bluem:hover { 133 | background-color: #293640; 134 | border-color: #384a58; 135 | color: #FFF; 136 | } 137 | 138 | .styledfoot { 139 | margin-top: 45px; 140 | bottom: 0px; 141 | padding-bottom: 25px; 142 | text-align: center; 143 | color: #FFF; 144 | background-color: #293640; 145 | width: 100%; 146 | } 147 | 148 | .input-data-textarea { 149 | min-width: 100%; 150 | } 151 | 152 | .input-data-textarea:focus { 153 | outline-color: #272727; 154 | } 155 | 156 | /*make the rpc command dialogue wider*/ 157 | #connectwarning { 158 | width: 900px; 159 | } 160 | 161 | #connectwarning-dialog { 162 | width: 900px; 163 | } 164 | -------------------------------------------------------------------------------- /app/assets/img/eth_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sthnaqvi/explorer/93b7ed799dba8484a8a00d8384e591264bbc5e1d/app/assets/img/eth_icon.png -------------------------------------------------------------------------------- /app/assets/img/ethicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sthnaqvi/explorer/93b7ed799dba8484a8a00d8384e591264bbc5e1d/app/assets/img/ethicon.png -------------------------------------------------------------------------------- /app/components/version/interpolate-filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.version.interpolate-filter', []) 4 | 5 | .filter('interpolate', ['version', function(version) { 6 | return function(text) { 7 | return String(text).replace(/\%VERSION\%/mg, version); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /app/components/version/interpolate-filter_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('myApp.version module', function() { 4 | beforeEach(module('myApp.version')); 5 | 6 | describe('interpolate filter', function() { 7 | beforeEach(module(function($provide) { 8 | $provide.value('version', 'TEST_VER'); 9 | })); 10 | 11 | it('should replace VERSION', inject(function(interpolateFilter) { 12 | expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); 13 | })); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/components/version/version-directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.version.version-directive', []) 4 | 5 | .directive('appVersion', ['version', function(version) { 6 | return function(scope, elm, attrs) { 7 | elm.text(version); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /app/components/version/version-directive_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('myApp.version module', function() { 4 | beforeEach(module('myApp.version')); 5 | 6 | describe('app-version directive', function() { 7 | it('should print current version', function() { 8 | module(function($provide) { 9 | $provide.value('version', 'TEST_VER'); 10 | }); 11 | inject(function($compile, $rootScope) { 12 | var element = $compile('')($rootScope); 13 | expect(element.text()).toEqual('TEST_VER'); 14 | }); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/components/version/version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.version', [ 4 | 'myApp.version.interpolate-filter', 5 | 'myApp.version.version-directive' 6 | ]) 7 | 8 | .value('version', '0.1'); 9 | -------------------------------------------------------------------------------- /app/components/version/version_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('myApp.version module', function() { 4 | beforeEach(module('myApp.version')); 5 | 6 | describe('version service', function() { 7 | it('should return current version', inject(function(version) { 8 | expect(version).toEqual('0.1'); 9 | })); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ETH Block Explorer 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 53 | 54 | 55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 89 | 90 | 91 | 110 | 111 | 112 | 121 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /app/scripts/controllers/addressInfosController.js: -------------------------------------------------------------------------------- 1 | // preliminary code! TDD - still needs refactoring & optimization 2 | // 3 | // 4 | // chainInfoController.js 5 | // 6 | // contains 1 controller: 7 | // addressInfosCtrl 8 | // 9 | // by AltSheets 10 | // September 2015 11 | // 12 | 13 | angular.module('ethExplorer') 14 | .controller('addressInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 15 | 16 | $scope.init = function () { 17 | $scope.addressId = $routeParams.addressId; 18 | var addressId = $routeParams.addressId; 19 | 20 | if (!(!$scope.addressId)) { 21 | getAddressBalance() 22 | .then(function (result) { 23 | $scope.balance = web3.fromWei(result).toNumber(); 24 | }); 25 | getAddressTransactionCount() 26 | .then(function (result) { 27 | $scope.txCount = result; 28 | }); 29 | getCode() 30 | .then(function (result) { 31 | $scope.code = result; 32 | }); 33 | getTransactions() 34 | .then(function (result) { 35 | console.log("getTransactions is executed!") 36 | console.log(result) 37 | $scope.transactions = result; 38 | }); 39 | getETHUSD(); 40 | } else { 41 | $location.path("/"); 42 | } 43 | 44 | function getAddressBalance() { 45 | var deferred = $q.defer(); 46 | web3.eth.getBalance($scope.addressId, function (error, result) { 47 | if (!error) { 48 | deferred.resolve(result); 49 | } 50 | else { 51 | deferred.reject(error); 52 | } 53 | }); 54 | return deferred.promise; 55 | } 56 | 57 | function getETHUSD() { 58 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 59 | var price = Number(json[0].price_usd); 60 | var ethusd = price.toFixed(2); 61 | var balanceusd = "$" + ethusd * $scope.balance; 62 | $scope.balanceusd = balanceusd; 63 | //console.log("Balance in USD " + $scope.balanceusd); 64 | }); 65 | } 66 | 67 | function getAddressTransactionCount() { 68 | // var success=$.getScript('../../config.js'); 69 | var deferred = $q.defer(); 70 | web3.eth.getTransactionCount($scope.addressId, function (error, result) { 71 | if (!error) { 72 | deferred.resolve(result); 73 | } 74 | else { 75 | deferred.reject(error); 76 | } 77 | }); 78 | return deferred.promise; 79 | } 80 | 81 | function getCode() { 82 | var deferred = $q.defer(); 83 | web3.eth.getCode($scope.addressId, function (error, result) { 84 | if (!error) { 85 | deferred.resolve(result); 86 | } 87 | else { 88 | deferred.reject(error); 89 | } 90 | }); 91 | return deferred.promise; 92 | } 93 | 94 | // TODO: not working yet: 95 | function getTransactions() { 96 | var deferred = $q.defer(); 97 | 98 | /* 99 | 100 | // See https://github.com/ethereum/go-ethereum/issues/1897#issuecomment-166351797 101 | // plus the following posts 102 | // Giving up for now. Invested another 3 hours without results. Grrrr.. 103 | 104 | // var options="address:"+$scope.addressId; 105 | // var options = {"address": "0xf2cc0eeaaaed313542cb262b0b8c3972425143f0"}; // $scope.addressId}; // , "topics": [null] 106 | // var options = 'pending' 107 | // console.log(options); 108 | 109 | var options = {fromBlock: 0, toBlock: 'latest', address: "0xf2cc0eeaaaed313542cb262b0b8c3972425143f0"}; 110 | 111 | var myfilter = web3.eth.filter(options); 112 | 113 | // var myfilter= web3.eth.filter(options); 114 | console.log(myfilter); 115 | 116 | 117 | myfilter.get(function (error, log) { 118 | console.log("get error:", error); 119 | console.log("get log:", log); 120 | }); 121 | 122 | web3.eth.filter(options, 123 | function(error, result){ 124 | if(!error){ 125 | console.log("no error"); 126 | deferred.resolve(result); 127 | } 128 | else{ 129 | console.log("error"); 130 | deferred.reject(error); 131 | } 132 | }); 133 | 134 | */ 135 | return deferred.promise; 136 | } 137 | }; 138 | $scope.init(); 139 | 140 | function hex2a(hexx) { 141 | var hex = hexx.toString();//force conversion 142 | var str = ''; 143 | for (var i = 0; i < hex.length; i += 2) 144 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 145 | return str; 146 | } 147 | }); 148 | -------------------------------------------------------------------------------- /app/scripts/controllers/blockInfosController.js: -------------------------------------------------------------------------------- 1 | var BigNumber = require('bignumber.js'); 2 | angular.module('ethExplorer') 3 | .controller('blockInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 4 | 5 | $scope.init = function () { 6 | $scope.blockId = $routeParams.blockId; 7 | if (!(!$scope.blockId)) { 8 | getBlockInfos() 9 | .then(function (result) { 10 | var number = web3.eth.blockNumber; 11 | $scope.result = result; 12 | 13 | $scope.numberOfUncles = result.uncles.length; 14 | 15 | //if ($scope.numberOfUncles!=0) { 16 | // uncle1=result.uncles[0]; 17 | // console.log(web3.eth.getUncle(uncle1)); 18 | //} 19 | 20 | if (!(!result.hash)) { 21 | $scope.hash = result.hash; 22 | } 23 | else { 24 | $scope.hash = 'pending'; 25 | } 26 | if (!(!result.miner)) { 27 | $scope.miner = result.miner; 28 | } 29 | else { 30 | $scope.miner = 'pending'; 31 | } 32 | $scope.gasLimit = result.gasLimit; 33 | $scope.gasUsed = result.gasUsed; 34 | $scope.nonce = result.nonce; 35 | var diff = ("" + result.difficulty).replace(/['"]+/g, '') / 1000000000000; 36 | $scope.difficulty = diff.toFixed(3) + " T"; 37 | $scope.gasLimit = new BigNumber(result.gasLimit).toFormat(0); // that's a string 38 | $scope.gasUsed = new BigNumber(result.gasUsed).toFormat(0); 39 | $scope.nonce = result.nonce; 40 | $scope.number = result.number; 41 | $scope.parentHash = result.parentHash; 42 | $scope.uncledata = result.sha3Uncles; 43 | $scope.rootHash = result.stateRoot; 44 | $scope.blockNumber = result.number; 45 | $scope.timestamp = new Date(result.timestamp * 1000).toUTCString(); 46 | $scope.extraData = result.extraData.slice(2); 47 | $scope.dataFromHex = hex2a(result.extraData.slice(2)); 48 | $scope.size = result.size; 49 | $scope.firstBlock = false; 50 | $scope.lastBlock = false; 51 | if (!(!$scope.blockNumber)) { 52 | $scope.conf = number - $scope.blockNumber + " Confirmations"; 53 | if (number === $scope.blockNumber) { 54 | $scope.conf = 'Unconfirmed'; 55 | $scope.lastBlock = true; 56 | } 57 | if ($scope.blockNumber === 0) { 58 | $scope.firstBlock = true; 59 | } 60 | } 61 | 62 | if (!(!$scope.blockNumber)) { 63 | var info = web3.eth.getBlock($scope.blockNumber); 64 | if (!(!info)) { 65 | var newDate = new Date(); 66 | newDate.setTime(info.timestamp * 1000); 67 | $scope.time = newDate.toUTCString(); 68 | } 69 | } 70 | }); 71 | 72 | } else { 73 | $location.path("/"); 74 | } 75 | 76 | function getBlockInfos() { 77 | var deferred = $q.defer(); 78 | 79 | web3.eth.getBlock($scope.blockId, function (error, result) { 80 | if (!error) { 81 | deferred.resolve(result); 82 | } 83 | else { 84 | deferred.reject(error); 85 | } 86 | }); 87 | return deferred.promise; 88 | } 89 | 90 | }; 91 | $scope.init(); 92 | 93 | // parse transactions 94 | $scope.transactions = []; 95 | 96 | web3.eth.getBlockTransactionCount($scope.blockId, function (error, result) { 97 | var txCount = result; 98 | $scope.numberOfTransactions = txCount; 99 | for (var blockIdx = 0; blockIdx < txCount; blockIdx++) { 100 | web3.eth.getTransactionFromBlock($scope.blockId, blockIdx, function (error, result) { 101 | // console.log("Result: ", result); 102 | web3.eth.getTransactionReceipt(result.hash, function (error, receipt) { 103 | var transaction = { 104 | id: receipt.transactionHash, 105 | hash: receipt.transactionHash, 106 | from: receipt.from, 107 | to: receipt.to, 108 | gas: receipt.gasUsed, 109 | input: result.input.slice(2), 110 | value: web3.fromWei(result.value, "ether"), 111 | contractAddress: receipt.contractAddress 112 | }; 113 | $scope.$apply( 114 | $scope.transactions.push(transaction) 115 | ); 116 | }); 117 | }) 118 | } 119 | }); 120 | 121 | function hex2a(hexx) { 122 | var hex = hexx.toString(); //force conversion 123 | var str = ''; 124 | for (var i = 0; i < hex.length; i += 2) 125 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 126 | 127 | return str; 128 | } 129 | }); 130 | -------------------------------------------------------------------------------- /app/scripts/controllers/chainInfosController.js: -------------------------------------------------------------------------------- 1 | var BigNumber = require('bignumber.js'); 2 | 3 | angular.module('ethExplorer') 4 | .controller('chainInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 5 | 6 | $scope.init = function () { 7 | getChainInfos() 8 | .then(function (result) { 9 | 10 | $scope.result = result; // just a dummy value, but following Whit's example. 11 | 12 | $scope.blockNum = web3.eth.blockNumber; // now that was easy 13 | 14 | if (!(!$scope.blockNum)) { 15 | // TODO: put the 2 web3.eth.getBlock into the async function below 16 | // easiest to first do with fastInfosCtrl 17 | var blockNewest = web3.eth.getBlock($scope.blockNum); 18 | 19 | if (!(!blockNewest)) { 20 | // difficulty 21 | $scope.difficulty = blockNewest.difficulty; 22 | $scope.difficultyToExponential = blockNewest.difficulty.toExponential(3); 23 | 24 | $scope.totalDifficulty = blockNewest.totalDifficulty; 25 | $scope.totalDifficultyToExponential = blockNewest.totalDifficulty.toExponential(3); 26 | 27 | $scope.totalDifficultyDividedByDifficulty = $scope.totalDifficulty.dividedBy($scope.difficulty); 28 | $scope.totalDifficultyDividedByDifficulty_formatted = $scope.totalDifficultyDividedByDifficulty.toFormat(1); 29 | 30 | $scope.AltsheetsCoefficient = $scope.totalDifficultyDividedByDifficulty.dividedBy($scope.blockNum); 31 | $scope.AltsheetsCoefficient_formatted = $scope.AltsheetsCoefficient.toFormat(4); 32 | 33 | // large numbers still printed nicely: 34 | $scope.difficulty_formatted = $scope.difficulty.toFormat(0); 35 | $scope.totalDifficulty_formatted = $scope.totalDifficulty.toFormat(0); 36 | 37 | // Gas Limit 38 | $scope.gasLimit = new BigNumber(blockNewest.gasLimit).toFormat(0) + " m/s"; 39 | 40 | // Time 41 | var newDate = new Date(); 42 | newDate.setTime(blockNewest.timestamp * 1000); 43 | $scope.time = newDate.toUTCString(); 44 | 45 | $scope.secondsSinceBlock1 = blockNewest.timestamp - 1438226773; 46 | $scope.daysSinceBlock1 = ($scope.secondsSinceBlock1 / 86400).toFixed(2); 47 | 48 | // Average Block Times: 49 | // TODO: make fully async, put below into 'fastInfosCtrl' 50 | 51 | var blockBefore = web3.eth.getBlock($scope.blockNum - 1); 52 | if (!(!blockBefore)) { 53 | $scope.blocktime = blockNewest.timestamp - blockBefore.timestamp; 54 | } 55 | $scope.range1 = 100; 56 | range = $scope.range1; 57 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 58 | if (!(!blockBefore)) { 59 | $scope.blocktimeAverage1 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 60 | } 61 | $scope.range2 = 1000; 62 | range = $scope.range2; 63 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 64 | if (!(!blockBefore)) { 65 | $scope.blocktimeAverage2 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 66 | } 67 | $scope.range3 = 10000; 68 | range = $scope.range3; 69 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 70 | if (!(!blockBefore)) { 71 | $scope.blocktimeAverage3 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 72 | } 73 | $scope.range4 = 100000; 74 | range = $scope.range4; 75 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 76 | if (!(!blockBefore)) { 77 | $scope.blocktimeAverage4 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 78 | } 79 | 80 | range = $scope.blockNum; 81 | var blockPast = web3.eth.getBlock(1); 82 | if (!(!blockBefore)) { 83 | $scope.blocktimeAverageAll = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 84 | } 85 | //fastAnswers($scope); 86 | //$scope=BlockExplorerConstants($scope); 87 | } 88 | } 89 | 90 | // Block Explorer Info 91 | $scope.isConnected = web3.isConnected(); 92 | //$scope.peerCount = web3.net.peerCount; 93 | $scope.versionApi = web3.version.api; 94 | $scope.versionClient = web3.version.client; 95 | $scope.versionNetwork = web3.version.network; 96 | $scope.versionCurrency = web3.version.ethereum; // TODO: change that to currencyname? 97 | 98 | // ready for the future: 99 | try { 100 | $scope.versionWhisper = web3.version.whisper; 101 | } 102 | catch (err) { 103 | $scope.versionWhisper = err.message; 104 | } 105 | }); 106 | 107 | function getChainInfos() { 108 | var deferred = $q.defer(); 109 | deferred.resolve(0); // dummy value 0, for now. // see blockInfosController.js 110 | return deferred.promise; 111 | } 112 | }; 113 | $scope.init(); 114 | }); -------------------------------------------------------------------------------- /app/scripts/controllers/mainController.js: -------------------------------------------------------------------------------- 1 | //var cryptoSocket = require('crypto-socket'); 2 | var BigNumber = require('bignumber.js'); 3 | angular.module('ethExplorer') 4 | .controller('viewCtrl', function ($rootScope, $location) { 5 | $rootScope.locationPath = $location.$$path; 6 | }) 7 | .controller('mainCtrl', function ($rootScope, $scope, $location) { 8 | 9 | // Display & update block list 10 | getETHRates(); 11 | updateBlockList(); 12 | updateTXList(); 13 | updateStats(); 14 | getHashrate(); 15 | 16 | web3.eth.filter("latest", function (error, result) { 17 | if (!error) { 18 | getETHRates(); 19 | updateBlockList(); 20 | updateTXList(); 21 | updateStats(); 22 | getHashrate(); 23 | $scope.$apply(); 24 | } 25 | }); 26 | 27 | function updateStats() { 28 | web3.eth.getBlockNumber(function (err, currentBlockNumber) { 29 | if (err) 30 | return console.log(err); 31 | $scope.blockNum = currentBlockNumber; 32 | if (!(!$scope.blockNum)) { 33 | web3.eth.getBlock($scope.blockNum, function (err, blockNewest) { 34 | if (err) 35 | return console.log(err); 36 | if (!(!blockNewest)) { 37 | // difficulty 38 | $scope.difficulty = blockNewest.difficulty; 39 | 40 | // Gas Limit 41 | $scope.gasLimit = new BigNumber(blockNewest.gasLimit).toFormat(0) + " m/s"; 42 | 43 | web3.eth.getBlock($scope.blockNum - 1, function (err, blockBefore) { 44 | if (err) 45 | return console.log(err); 46 | $scope.blocktime = blockNewest.timestamp - blockBefore.timestamp; 47 | }); 48 | } 49 | } 50 | ); 51 | } 52 | }); 53 | } 54 | 55 | 56 | function getHashrate() { 57 | $.getJSON("https://www.etherchain.org/api/miningEstimator", function (json) { 58 | var hr = json.hashrate; 59 | $scope.hashrate = hr; 60 | }); 61 | } 62 | 63 | function getETHRates() { 64 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 65 | var price = Number(json[0].price_usd); 66 | $scope.ethprice = "$" + price.toFixed(2); 67 | }); 68 | 69 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 70 | var btcprice = Number(json[0].price_btc); 71 | $scope.ethbtcprice = btcprice; 72 | }); 73 | 74 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 75 | var cap = Number(json[0].market_cap_usd); 76 | //console.log("Current ETH Market Cap: " + cap); 77 | $scope.ethmarketcap = cap; 78 | }); 79 | } 80 | 81 | function updateTXList() { 82 | web3.eth.getBlockNumber(function (err, currentBlockNumber) { 83 | if (err) 84 | return console.log(err); 85 | $scope.txNumber = currentBlockNumber; 86 | $scope.recenttransactions = []; 87 | 88 | getTransactionsFromBlock(currentBlockNumber); 89 | 90 | function getTransactionsFromBlock(blockNumber) { 91 | web3.eth.getBlock(blockNumber, true, function (err, block) { 92 | if (err) { 93 | console.log(err); 94 | return getTransactionsFromBlock(blockNumber); 95 | } 96 | 97 | var transInBlock = []; 98 | var loopLimit = 10; 99 | 100 | if (loopLimit > block.transactions.length) 101 | loopLimit = block.transactions.length; 102 | 103 | for (var i = 0; i < loopLimit; i++) { 104 | transInBlock.push(block.transactions[i]); 105 | 106 | if (i === loopLimit - 1) { 107 | $scope.recenttransactions = $scope.recenttransactions.concat(transInBlock); 108 | $scope.$apply(); 109 | if ($scope.recenttransactions.length <= 10 && blockNumber > 0) 110 | getTransactionsFromBlock(--blockNumber) 111 | } 112 | } 113 | }); 114 | } 115 | }); 116 | } 117 | 118 | function updateBlockList() { 119 | web3.eth.getBlockNumber(function (err, currentBlockNumber) { 120 | if (err) 121 | return console.log(err); 122 | $scope.currentBlockNumber = currentBlockNumber; 123 | $scope.blocks = []; 124 | getBlockDetails(currentBlockNumber); 125 | 126 | function getBlockDetails(blockNumber) { 127 | web3.eth.getBlock(blockNumber, function (err, block) { 128 | if (err) { 129 | console.log(err); 130 | return getBlockDetails(blockNumber); 131 | } 132 | $scope.blocks = $scope.blocks.concat(block); 133 | $scope.$apply(); 134 | 135 | if ($scope.blocks.length <= 10 && blockNumber > 0) 136 | getBlockDetails(--blockNumber) 137 | }) 138 | } 139 | }); 140 | } 141 | 142 | }) 143 | ; 144 | 145 | angular.module('filters', []) 146 | .filter('truncate', function () { 147 | return function (text, length, end) { 148 | if (isNaN(length)) 149 | length = 10; 150 | 151 | if (!end) 152 | end = "..."; 153 | 154 | if (text.length <= length || text.length - end.length <= length) { 155 | return text; 156 | } else { 157 | return String(text).substring(0, length - end.length) + end; 158 | } 159 | }; 160 | }) 161 | .filter('diffFormat', function () { 162 | //convert hash/solution to different kiloHash,MegaHash/solution and others 163 | return function (diffi) { 164 | if (isNaN(diffi)) return diffi; 165 | if (diffi > 1000000000000000) { 166 | var n = diffi / 1000000000000000; 167 | return n.toFixed(3) + " P"; 168 | } 169 | if (diffi > 1000000000000) { 170 | var n = diffi / 1000000000000; 171 | return n.toFixed(3) + " T"; 172 | } 173 | if (diffi > 1000000000) { 174 | var n = diffi / 1000000000; 175 | return n.toFixed(3) + " G"; 176 | } 177 | if (diffi > 1000000) { 178 | var n = diffi / 1000000; 179 | return n.toFixed(3) + " M"; 180 | } 181 | var n = diffi / 1000; 182 | return n.toFixed(3) + " K"; 183 | }; 184 | }) 185 | .filter('stylize', function () { 186 | return function (style) { 187 | if (isNaN(style)) return style; 188 | var si = '' + style + ''; 189 | return si; 190 | }; 191 | }) 192 | .filter('stylize2', function () { 193 | return function (text) { 194 | if (isNaN(text)) return text; 195 | var si = ' ' + text; 196 | return si; 197 | }; 198 | }) 199 | .filter('hashFormat', function () { 200 | //convert hash/second to different kiloHash,MegaHash/second and others 201 | return function (hashr) { 202 | if (isNaN(hashr)) return hashr; 203 | if (hashr > 1000000000000000) { 204 | var n = hashr / 1000000000000000; 205 | return n.toFixed(3) + " PH/s"; 206 | } 207 | if (hashr > 1000000000000) { 208 | var n = hashr / 1000000000000; 209 | return n.toFixed(3) + " TH/s"; 210 | } 211 | if (hashr > 1000000000) { 212 | var n = hashr / 1000000000; 213 | return n.toFixed(3) + " GH/s"; 214 | } 215 | if (hashr > 1000000) { 216 | var n = hashr / 1000000; 217 | return n.toFixed(3) + " MH/s"; 218 | } 219 | var n = hashr / 1000; 220 | return n.toFixed(3) + " KH/s"; 221 | }; 222 | }) 223 | .filter('gasFormat', function () { 224 | return function (txt) { 225 | if (isNaN(txt)) return txt; 226 | var b = new BigNumber(txt); 227 | return b.toFormat(0) + " m/s"; 228 | }; 229 | }) 230 | .filter('BigNum', function () { 231 | return function (txt) { 232 | if (isNaN(txt)) return txt; 233 | var b = new BigNumber(txt); 234 | var w = web3.fromWei(b, "ether"); 235 | return w.toFixed(6) + " ETH"; 236 | }; 237 | }) 238 | .filter('timestampAge', function () { 239 | return function (timestamp) { 240 | function dhms(ms) { 241 | var days = Math.floor(ms / (24 * 60 * 60 * 1000)); 242 | var daysms = ms % (24 * 60 * 60 * 1000); 243 | var hrs = Math.floor((daysms) / (60 * 60 * 1000)); 244 | var hrsms = daysms % (60 * 60 * 1000); 245 | var mins = Math.floor((hrsms) / (60 * 1000)); 246 | var minsms = hrsms % (60 * 1000); 247 | var secs = Math.floor((minsms) / (1000)); 248 | 249 | var diff = " ago"; 250 | var secsString = secs + " sec"; 251 | var minsString = mins + " min"; 252 | var hrsString = hrs + " hr"; 253 | var daysString = days + " day"; 254 | 255 | if (secs > 1) 256 | secsString = secs + " secs"; 257 | if (mins > 1) 258 | minsString = mins + " mins"; 259 | if (hrs > 1) 260 | hrsString = hrs + " hrs"; 261 | if (days > 1) 262 | daysString = days + " days"; 263 | 264 | if (days >= 1) 265 | return daysString + " " + hrsString + diff; 266 | if (hrs >= 1) 267 | return hrsString + " " + minsString + diff; 268 | if (mins >= 1) 269 | return minsString + " " + secsString + diff; 270 | 271 | return secsString + diff; 272 | } 273 | 274 | var dateNow = moment.utc(); 275 | var txtTime = moment.utc(timestamp); 276 | var diffInMs = dateNow.diff(txtTime); 277 | return dhms(diffInMs); 278 | }; 279 | }) 280 | .filter('sizeFormat', function () { 281 | return function (size) { 282 | if (isNaN(size)) return size; 283 | var s = size / 1000; 284 | return s.toFixed(3) + " kB"; 285 | }; 286 | }); 287 | -------------------------------------------------------------------------------- /app/scripts/controllers/transactionInfosController.js: -------------------------------------------------------------------------------- 1 | angular.module('ethExplorer') 2 | .controller('transactionInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 3 | 4 | $scope.init = function () { 5 | 6 | $scope.txId = $routeParams.transactionId; 7 | 8 | if (!(!$scope.txId)) { // add a test to check if it match tx paterns to avoid useless API call, clients are not obliged to come from the search form... 9 | 10 | getTransactionInfos() 11 | .then(function (result) { 12 | //TODO Refactor this logic, asynchron calls + services.... 13 | var number = web3.eth.blockNumber; 14 | 15 | $scope.result = result; 16 | 17 | if (!(!result.blockHash)) { 18 | $scope.blockHash = result.blockHash; 19 | } 20 | else { 21 | $scope.blockHash = 'pending'; 22 | } 23 | if (!(!result.blockNumber)) { 24 | $scope.blockNumber = result.blockNumber; 25 | } 26 | else { 27 | $scope.blockNumber = 'pending'; 28 | } 29 | $scope.from = result.from; 30 | $scope.gas = result.gas; 31 | //$scope.gasPrice = result.gasPrice.c[0] + " WEI"; 32 | $scope.gasPrice = web3.fromWei(result.gasPrice, "ether").toFormat(10) + " ETH"; 33 | $scope.hash = result.hash; 34 | $scope.input = result.input; // that's a string 35 | $scope.nonce = result.nonce; 36 | $scope.to = result.to; 37 | $scope.transactionIndex = result.transactionIndex; 38 | //$scope.ethValue = web3.fromWei(result.value[0], "ether"); Newer method but has "" 39 | $scope.ethValue = result.value.c[0] / 10000; 40 | $scope.txprice = web3.fromWei(result.gas * result.gasPrice, "ether") + " ETH"; 41 | if (!(!$scope.blockNumber)) { 42 | $scope.conf = number - $scope.blockNumber; 43 | if ($scope.conf === 0) { 44 | $scope.conf = 'unconfirmed'; //TODO change color button when unconfirmed... ng-if or ng-class 45 | } 46 | } 47 | //TODO Refactor this logic, asynchron calls + services.... 48 | if (!(!$scope.blockNumber)) { 49 | var info = web3.eth.getBlock($scope.blockNumber); 50 | if (!(!info)) { 51 | $scope.time = info.timestamp; 52 | } 53 | } 54 | }); 55 | } 56 | else { 57 | $location.path("/"); // add a trigger to display an error message so user knows he messed up with the TX number 58 | } 59 | 60 | function getTransactionInfos() { 61 | var deferred = $q.defer(); 62 | 63 | web3.eth.getTransaction($scope.txId, function (error, result) { 64 | if (!error) { 65 | deferred.resolve(result); 66 | } 67 | else { 68 | deferred.reject(error); 69 | } 70 | }); 71 | return deferred.promise; 72 | } 73 | }; 74 | $scope.init(); 75 | }); 76 | -------------------------------------------------------------------------------- /app/views/addressInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Ethereum Address 4 | View information about an Ethereum Address 5 |

6 |
7 |
8 |
{{ addressId }}


9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
Summary
Current Balance{{ balance }} ETH
(Value in USD: {{ balanceusd || "Calculating.." }})
Transaction Count (# of outgoing TXs){{ txCount }}
Code
27 |
28 | -------------------------------------------------------------------------------- /app/views/api/api.html: -------------------------------------------------------------------------------- 1 |

API Info 2 | about the current block: 3 |

4 | 9 | -------------------------------------------------------------------------------- /app/views/blockInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
View information about Ethereum Block {{number}}
4 | 5 |
6 |
7 | 8 | 9 | < Prev 10 | Next > 11 |
12 |

13 | Summary 14 | - information of block {{number}} 15 |

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
Block Number{{number}}
Block Hash{{ hash }}
Received Time 29 | {{timestamp}} 30 |
Confirmations{{conf}}
Difficulty 39 | {{difficulty}} 40 |
Nonce{{nonce}}
Size{{size}} bytes
Miner{{miner}}
Gas Limit{{gasLimit}}
Gas Used{{gasUsed}}
Uncle Hash{{ uncledata }}
State Root Hash{{ rootHash }}
Parent Hash{{ parentHash }}
Data{{extraData}}
Data (Translated){{dataFromHex}}
Number of Uncle Blocks{{numberOfUncles}}
Number of Transactions{{numberOfTransactions}}
96 |
97 | 98 |

99 | Transactions 100 | - contained in current block 101 |

102 | 103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |
Transaction #{{$index+1}}
Hash #{{tx.hash}}
From{{tx.from}}
To{{tx.to}}
Contract Address{{tx.contractAddress}}
Gas{{tx.gas}}
Input
Value{{tx.value}} ETH
139 |
140 |
141 | 142 |
143 | -------------------------------------------------------------------------------- /app/views/chainInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Ethereum Information 4 | View information about the Ethereum Blockchain 5 |

6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
Summary
Current Block{{ blockNum }}
Block Time 18 | {{ blocktime }} seconds since block {{ blockNum - 1 }}
19 |
Block Time Averages
24 | {{ blocktimeAverage1 }} seconds for the past 100 blocks
25 | {{ blocktimeAverage2 }} seconds for the past 1,000 blocks 26 |
27 | {{ blocktimeAverage3 }} seconds for the past 10,000 blocks
28 | {{ blocktimeAverage4 }} seconds for the past 100,000 blocks 29 |
30 | {{ blocktimeAverageAll }} seconds for all {{blockNum}} blocks 31 |
32 |
Current Difficulty{{ difficulty_formatted }}
Total Difficulty{{ totalDifficulty_formatted }}
Gas Limit{{ gasLimit }}
Time{{ time }}
Connected to ETH Network{{ isConnected }}
API/Client Version{{ versionApi }} - {{ versionClient }}
60 |
61 |
62 | 63 |
64 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
Current Block:
{{ currentBlockNumber }}
17 |
18 |
19 |
ETH/USD Price:
{{ ethprice || "Loading.." }}
20 |
21 |
22 |
Gas Limit:
{{ gasLimit || "Loading.." }}
23 |
24 |
25 |
Block Time:
{{ blocktime?blocktime+" second(s)":false || "Loading.." }}
26 |
27 |
28 |
Current Diff:
{{ difficulty || "Loading.." | diffFormat }}
29 |
30 |
31 |
Hashrate:
{{ hashrate|| "Loading.." | hashFormat }}
32 |
33 |
34 |

Recent Blocks Most Recent Blocks in the Ethereum Network


35 |
36 | 37 | 38 | 41 | 42 | 43 | 44 | 47 | 50 | 53 | 56 | 57 | 58 | 59 | 64 | 65 | 66 | 67 | 70 | 73 | 76 | 79 | 80 | 81 |
39 | Block # 40 | Block HashDifficultyMiner 45 | Size 46 | 48 | Age 49 | 51 | # of TXs 52 | 54 | Gas used 55 |
60 | 61 | {{ block.number }} 62 | 63 | {{ block.hash | truncate : 12 }}{{ block.difficulty | diffFormat }}{{ block.miner }} 68 | {{ block.size | sizeFormat }} 69 | 71 | {{ block.timestamp * 1000 | timestampAge }} 72 | 74 | {{ block.transactions.length }} 75 | 77 | {{ block.gasUsed | gasFormat }} 78 |
82 |
83 |

Recent Transactions Transactions in the Ethereum Network


84 |
85 | 86 | 87 | 90 | 91 | 92 | 94 | 97 | 100 | 101 | 102 | 103 | 104 | 105 | 110 | 111 | 112 | 115 | 118 | 119 | 120 |
88 | TX Hash 89 | Block #FromTo 93 | 95 | Gas Used 96 | 98 | Value 99 |
{{ tx.hash || "" | truncate : 45 }} 106 | 107 | {{ tx.blockNumber || "" }} 108 | 109 | {{ tx.from || "" | truncate : 20 }}{{ tx.to || "" | truncate : 20 }} 113 | {{ tx.gas | gasFormat }} 114 | 116 | {{ tx.value | BigNum }} 117 |
121 |
122 |
123 | -------------------------------------------------------------------------------- /app/views/transactionInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Transaction 4 | View information about an Ethereum transaction 5 |

6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 22 | 29 | 30 | 31 |
{{txId}}
16 | {{from}} 17 |
18 |
20 | 21 | 23 | {{to}} 24 | 25 | {{ethValue}} ETH 26 | 27 |
28 |
32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
Summary
Block Hash{{blockHash}}
Received Time 49 | {{time * 1000 | date:'medium'}} 50 |
Included In Block 55 | 56 |
Gas Used{{gas | gasFormat}}
Gas Price{{gasPrice}}
Transaction Confirmations{{conf}}
Number of transactions made by the sender prior to this one{{nonce}}
Transaction price{{txprice}}
Data
86 |
87 |
88 | 89 |
90 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-block-explorer", 3 | "description": "A lightweight ethereum block explorer", 4 | "version": "0.0.1", 5 | "homepage": "https://github.com/sthnaqvi/explorer", 6 | "license": "MIT", 7 | "private": false, 8 | "dependencies": { 9 | "jquery": "3.2.1", 10 | "bootstrap": "3.3.7", 11 | "angular": "1.5.7", 12 | "angular-route": "1.5.7", 13 | "angular-loader": "1.5.7", 14 | "angular-mocks": "1.5.7", 15 | "angular-sanitize": "1.5.7", 16 | "angular-bootstrap": "2.5.0", 17 | "angular-bootstrap-simple-chat": "^0.4.0", 18 | "angular-moment": "1.2.0", 19 | "moment": "2.20.1", 20 | "html5-boilerplate": "~5.2.0", 21 | "chart.js": "^2.1.6", 22 | "web3": "^0.19.0" 23 | }, 24 | "resolutions": { 25 | "angular": "1.5.7" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | explorer.ethorbit.com -------------------------------------------------------------------------------- /docs/app.js: -------------------------------------------------------------------------------- 1 | // begin AltSheets changes 2 | /////////////////////////////// 3 | // TODO: Put go into a config.js 4 | // But how to include a file from local? 5 | 6 | var GETH_HOSTNAME = "localhost"; // put your IP address! 7 | var APP_HOSTNAME = "See package.json --> scripts --> start: Change 'localhost'!!!"; 8 | 9 | var GETH_RPCPORT = 8545; // for geth --rpcport GETH_RPCPORT 10 | var APP_PORT = "See package.json --> scripts --> start: Perhaps change '8000'"; 11 | 12 | // this is creating the corrected geth command 13 | var WL = window.location; 14 | var geth_command = "geth --rpc --rpcaddr " + GETH_HOSTNAME + " --rpcport " + GETH_RPCPORT + '\ 15 | --rpcapi "web3,eth" ' + ' --rpccorsdomain "' + WL.protocol + "//" + WL.host + '"'; 16 | 17 | //////////////////////////////////////////////////// 18 | //end AltSheets changes 19 | 20 | 21 | 'use strict'; 22 | 23 | angular.module('ethExplorer', ['ngRoute', 'ui.bootstrap', 'filters', 'ngSanitize', 'angularMoment']) 24 | 25 | .config(['$routeProvider', 26 | function ($routeProvider) { 27 | $routeProvider.when('/', { 28 | templateUrl: 'views/main.html', 29 | controller: 'mainCtrl' 30 | }).when('/block/:blockId', { 31 | templateUrl: 'views/blockInfos.html', 32 | controller: 'blockInfosCtrl' 33 | }).when('/tx/:transactionId', { 34 | templateUrl: 'views/transactionInfos.html', 35 | controller: 'transactionInfosCtrl' 36 | }).when('/address/:addressId', { 37 | templateUrl: 'views/addressInfos.html', 38 | controller: 'addressInfosCtrl' 39 | }).// info page with links: 40 | when('/chain/api', { 41 | templateUrl: 'views/api/api.html', 42 | controller: 'chainInfosCtrl' 43 | }).// getBlock (current) & getBlock (last) 44 | when('/chain/', { 45 | templateUrl: 'views/chainInfos.html', 46 | controller: 'chainInfosCtrl' 47 | }).when('/chain/gaslimit', { 48 | templateUrl: 'views/api/gaslimit.html', 49 | controller: 'chainInfosCtrl' 50 | }).when('/chain/difficulty', { 51 | templateUrl: 'views/api/difficulty.html', 52 | controller: 'chainInfosCtrl' 53 | })./* 54 | // fast = doesn't need to getBlock any block 55 | when('/chain/blocknumber', { 56 | templateUrl: 'views/api/blocknumber.html', 57 | controller: 'fastInfosCtrl' 58 | }). 59 | when('/chain/supply', { 60 | templateUrl: 'views/api/supply.html', 61 | controller: 'fastInfosCtrl' 62 | }). 63 | when('/chain/mined', { 64 | templateUrl: 'views/api/mined.html', 65 | controller: 'fastInfosCtrl' 66 | }). 67 | 68 | // begin of: not yet, see README.md 69 | when('/chain/supply/public', { 70 | templateUrl: 'views/api/supplypublic.html', 71 | controller: 'fastInfosCtrl' 72 | }).*/ 73 | // end of: not yet, see README.md 74 | 75 | otherwise({ 76 | redirectTo: '/' 77 | }); 78 | 79 | //$locationProvider.html5Mode(true); 80 | }]) 81 | .run(function ($rootScope) { 82 | var Web3 = require('web3'); 83 | var web3 = new Web3(); 84 | 85 | // begin AltSheets changes 86 | web3.setProvider(new web3.providers.HttpProvider("https://mainnet.infura.io/0zolWfJDat4Hlc8ellFT")); 87 | // end AltSheets changes 88 | 89 | $rootScope.web3 = web3; 90 | // MetaMask injects its own web3 instance in all pages, override it 91 | // as it might be not compatible with the one used here 92 | window.web3 = web3; 93 | 94 | function sleepFor(sleepDuration) { 95 | var now = new Date().getTime(); 96 | while (new Date().getTime() < now + sleepDuration) { /* do nothing */ 97 | } 98 | } 99 | 100 | var connected = false; 101 | if (!web3.isConnected()) { 102 | $('#connectwarning').modal({keyboard: false, backdrop: 'static'}) 103 | $('#connectwarning').modal('show') 104 | } 105 | }) 106 | .controller('processRequestCtrl', function ($scope, $location) { 107 | 108 | $scope.processRequest = function () { 109 | var requestStr = $scope.ethRequest; 110 | 111 | if (requestStr !== undefined) { 112 | 113 | // maybe we can create a service to do the reg ex test, so we can use it in every controller ? 114 | var regexpTx = /[0-9a-zA-Z]{64}?/; 115 | //var regexpAddr = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/; // TODO ADDR REGEX or use isAddress(hexString) API ? 116 | var regexpAddr = /^(0x)?[0-9a-f]{40}$/; //New ETH Regular Expression for Addresses 117 | var regexpBlock = /[0-9]{1,7}?/; 118 | 119 | var result = regexpTx.test(requestStr); 120 | if (result === true) { 121 | goToTxInfos(requestStr) 122 | } 123 | else { 124 | result = regexpAddr.test(requestStr.toLowerCase()); 125 | if (result === true) { 126 | goToAddrInfos(requestStr.toLowerCase()) 127 | } 128 | else { 129 | result = regexpBlock.test(requestStr); 130 | if (result === true) { 131 | goToBlockInfos(requestStr) 132 | } 133 | else { 134 | console.log("nope"); 135 | return null; 136 | } 137 | } 138 | } 139 | } 140 | else { 141 | return null; 142 | } 143 | }; 144 | 145 | function goToBlockInfos(requestStr) { 146 | $location.path('/block/' + requestStr); 147 | } 148 | 149 | function goToAddrInfos(requestStr) { 150 | $location.path('/address/' + requestStr.toLowerCase()); 151 | } 152 | 153 | function goToTxInfos(requestStr) { 154 | $location.path('/tx/' + requestStr); 155 | } 156 | }) 157 | .directive('nTooltips', function () { 158 | return { 159 | link: function ($scope, element, attrs) { 160 | var imgArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAALCAYAAACQy8Z9AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACgSURBVHjaYlx28hEDEYANiEOAeBm6RKSZLIZiJiIN3CgvzLUUSNcR4wIWYgxUl+D10JLkZWBjZmq8/eoLSLyJXENBBq4DGWgkJwAW0JHmA1EEDWYiYKA3zEAQ4GBlBhusKsbTiC8omIg1kBSDmUgxEJvBy089rsNnKFEGEmMwE5KBq4k1kJDBTEgG+pFiILLBJgqCKAYz/gcCBioDgAADAErmMqR0X1/QAAAAAElFTkSuQmCC'; 161 | var imgArrowCss = {'position': 'absolute', 'top': '-10px', 'left': '5px'}; 162 | var ntooltipsCss = { 163 | 'display': 'none', 164 | 'position': 'absolute', 165 | 'padding': '10px', 166 | 'left': '5px', 167 | 'font-size': '0.8em', 168 | 'background-color': 'white', 169 | 'border': '1px solid #a6c9e2', 170 | '-moz-border-radius': '5px', 171 | '-webkit-border-radius': '5px', 172 | 'z-index': '9999' 173 | }; 174 | var ntooltips = function () { 175 | this.xOffset = -10; 176 | this.yOffset = 10; 177 | element.unbind().hover(function (a) { 178 | this.t = this.title; 179 | this.title = "" 180 | this.top = (a.pageY + yOffset); 181 | this.left = (a.pageX + xOffset); 182 | $("body").append('

' + this.t + "

"); 183 | $("p#ntooltips #ntooltipsArrow").attr("src", imgArrow).css(imgArrowCss); 184 | $("p#ntooltips").css(ntooltipsCss).css("top", this.top + "px").css("left", this.left + "px").fadeIn("slow") 185 | }, function () { 186 | this.title = this.t; 187 | $("p#ntooltips").fadeOut("slow").remove() 188 | }).mousemove(function (a) { 189 | this.top = (a.pageY + yOffset); 190 | this.left = (a.pageX + xOffset); 191 | $("p#ntooltips").css("top", this.top + "px").css("left", this.left + "px") 192 | }) 193 | }; 194 | jQuery(document).ready(function (a) { 195 | ntooltips() 196 | }); 197 | } 198 | }; 199 | }); -------------------------------------------------------------------------------- /docs/assets/css/main.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | @import 'https://fonts.googleapis.com/css?family=Open+Sans|Raleway|Roboto'; 3 | 4 | body { 5 | font-family: 'Open Sans', sans-serif !important; 6 | } 7 | 8 | .menu { 9 | list-style: none; 10 | border-bottom: 0.1em solid black; 11 | margin-bottom: 2em; 12 | padding: 0 0 0.5em; 13 | } 14 | 15 | .menu:before { 16 | content: "["; 17 | } 18 | 19 | .menu:after { 20 | content: "]"; 21 | } 22 | 23 | .menu > li { 24 | display: inline; 25 | } 26 | 27 | .menu > li:before { 28 | content: "|"; 29 | padding-right: 0.3em; 30 | } 31 | 32 | .menu > li:nth-child(1):before { 33 | content: ""; 34 | padding: 0; 35 | } 36 | 37 | .navbar-dark { 38 | background-color: #293640; 39 | color: #FFF; 40 | } 41 | 42 | .navbar a { 43 | color: #FFF; 44 | } 45 | 46 | .navbar a:hover { 47 | color: #FFF; 48 | } 49 | 50 | .navbar a:visited { 51 | color: #FFF; 52 | } 53 | 54 | .title { 55 | color: #FFF; 56 | font-weight: bold; 57 | } 58 | 59 | input.searchi { 60 | background-color: #293640 !important; 61 | border: 2px solid #384a58; 62 | color: #fff; 63 | width: 100% !important; 64 | } 65 | 66 | input.searchimain { 67 | height: 50px; 68 | background-color: #293640 !important; 69 | border: 2px solid #384a58; 70 | color: #fff; 71 | width: 100%; 72 | } 73 | 74 | @media (min-width: 768px) { 75 | .searchimain { 76 | width: 800px !important; 77 | } 78 | } 79 | 80 | .boxi { 81 | background-color: #104050; 82 | color: #FFF; 83 | font-size: 18px; 84 | font-weight: lighter; 85 | padding: 15px; 86 | border-radius: 4px; 87 | margin-bottom: 20px; 88 | } 89 | 90 | .thead-inverse { 91 | background-color: #293640; 92 | color: #FFF; 93 | border-top-left-radius: 4px; 94 | border-top-right-radius: 4px; 95 | } 96 | 97 | .nav > li > a:focus, .nav > li > a:hover { 98 | background-color: #30404c !important; 99 | } 100 | 101 | .btn-blue { 102 | background-color: #293640 !important; 103 | border-color: #384a58 !important; 104 | color: #FFF !important; 105 | } 106 | 107 | .btn-blue.focus, .btn-blue:focus { 108 | background-color: #293640; 109 | border-color: #384a58; 110 | color: #FFF; 111 | } 112 | 113 | .btn-blue:hover { 114 | background-color: #293640; 115 | border-color: #384a58; 116 | color: #FFF; 117 | } 118 | 119 | .btn-bluem { 120 | background-color: #293640 !important; 121 | border-color: #384a58 !important; 122 | color: #FFF !important; 123 | height: 50px !important; 124 | } 125 | 126 | .btn-bluem.focus, .btn-bluem:focus { 127 | background-color: #293640; 128 | border-color: #384a58; 129 | color: #FFF; 130 | } 131 | 132 | .btn-bluem:hover { 133 | background-color: #293640; 134 | border-color: #384a58; 135 | color: #FFF; 136 | } 137 | 138 | .styledfoot { 139 | margin-top: 45px; 140 | bottom: 0px; 141 | padding-bottom: 25px; 142 | text-align: center; 143 | color: #FFF; 144 | background-color: #293640; 145 | width: 100%; 146 | } 147 | 148 | .input-data-textarea { 149 | min-width: 100%; 150 | } 151 | 152 | .input-data-textarea:focus { 153 | outline-color: #272727; 154 | } 155 | 156 | /*make the rpc command dialogue wider*/ 157 | #connectwarning { 158 | width: 900px; 159 | } 160 | 161 | #connectwarning-dialog { 162 | width: 900px; 163 | } 164 | -------------------------------------------------------------------------------- /docs/assets/img/eth_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sthnaqvi/explorer/93b7ed799dba8484a8a00d8384e591264bbc5e1d/docs/assets/img/eth_icon.png -------------------------------------------------------------------------------- /docs/assets/img/ethicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sthnaqvi/explorer/93b7ed799dba8484a8a00d8384e591264bbc5e1d/docs/assets/img/ethicon.png -------------------------------------------------------------------------------- /docs/components/version/interpolate-filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.version.interpolate-filter', []) 4 | 5 | .filter('interpolate', ['version', function(version) { 6 | return function(text) { 7 | return String(text).replace(/\%VERSION\%/mg, version); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /docs/components/version/interpolate-filter_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('myApp.version module', function() { 4 | beforeEach(module('myApp.version')); 5 | 6 | describe('interpolate filter', function() { 7 | beforeEach(module(function($provide) { 8 | $provide.value('version', 'TEST_VER'); 9 | })); 10 | 11 | it('should replace VERSION', inject(function(interpolateFilter) { 12 | expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); 13 | })); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /docs/components/version/version-directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.version.version-directive', []) 4 | 5 | .directive('appVersion', ['version', function(version) { 6 | return function(scope, elm, attrs) { 7 | elm.text(version); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /docs/components/version/version-directive_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('myApp.version module', function() { 4 | beforeEach(module('myApp.version')); 5 | 6 | describe('app-version directive', function() { 7 | it('should print current version', function() { 8 | module(function($provide) { 9 | $provide.value('version', 'TEST_VER'); 10 | }); 11 | inject(function($compile, $rootScope) { 12 | var element = $compile('')($rootScope); 13 | expect(element.text()).toEqual('TEST_VER'); 14 | }); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /docs/components/version/version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.version', [ 4 | 'myApp.version.interpolate-filter', 5 | 'myApp.version.version-directive' 6 | ]) 7 | 8 | .value('version', '0.1'); 9 | -------------------------------------------------------------------------------- /docs/components/version/version_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('myApp.version module', function() { 4 | beforeEach(module('myApp.version')); 5 | 6 | describe('version service', function() { 7 | it('should return current version', inject(function(version) { 8 | expect(version).toEqual('0.1'); 9 | })); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ETH Block Explorer 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 53 | 54 | 55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 89 | 90 | 91 | 110 | 111 | 112 | 121 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /docs/scripts/controllers/addressInfosController.js: -------------------------------------------------------------------------------- 1 | // preliminary code! TDD - still needs refactoring & optimization 2 | // 3 | // 4 | // chainInfoController.js 5 | // 6 | // contains 1 controller: 7 | // addressInfosCtrl 8 | // 9 | // by AltSheets 10 | // September 2015 11 | // 12 | 13 | angular.module('ethExplorer') 14 | .controller('addressInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 15 | 16 | $scope.init = function () { 17 | $scope.addressId = $routeParams.addressId; 18 | var addressId = $routeParams.addressId; 19 | 20 | if (!(!$scope.addressId)) { 21 | getAddressBalance() 22 | .then(function (result) { 23 | $scope.balance = web3.fromWei(result).toNumber(); 24 | }); 25 | getAddressTransactionCount() 26 | .then(function (result) { 27 | $scope.txCount = result; 28 | }); 29 | getCode() 30 | .then(function (result) { 31 | $scope.code = result; 32 | }); 33 | getTransactions() 34 | .then(function (result) { 35 | console.log("getTransactions is executed!") 36 | console.log(result) 37 | $scope.transactions = result; 38 | }); 39 | getETHUSD(); 40 | } else { 41 | $location.path("/"); 42 | } 43 | 44 | function getAddressBalance() { 45 | var deferred = $q.defer(); 46 | web3.eth.getBalance($scope.addressId, function (error, result) { 47 | if (!error) { 48 | deferred.resolve(result); 49 | } 50 | else { 51 | deferred.reject(error); 52 | } 53 | }); 54 | return deferred.promise; 55 | } 56 | 57 | function getETHUSD() { 58 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 59 | var price = Number(json[0].price_usd); 60 | var ethusd = price.toFixed(2); 61 | var balanceusd = "$" + ethusd * $scope.balance; 62 | $scope.balanceusd = balanceusd; 63 | //console.log("Balance in USD " + $scope.balanceusd); 64 | }); 65 | } 66 | 67 | function getAddressTransactionCount() { 68 | // var success=$.getScript('../../config.js'); 69 | var deferred = $q.defer(); 70 | web3.eth.getTransactionCount($scope.addressId, function (error, result) { 71 | if (!error) { 72 | deferred.resolve(result); 73 | } 74 | else { 75 | deferred.reject(error); 76 | } 77 | }); 78 | return deferred.promise; 79 | } 80 | 81 | function getCode() { 82 | var deferred = $q.defer(); 83 | web3.eth.getCode($scope.addressId, function (error, result) { 84 | if (!error) { 85 | deferred.resolve(result); 86 | } 87 | else { 88 | deferred.reject(error); 89 | } 90 | }); 91 | return deferred.promise; 92 | } 93 | 94 | // TODO: not working yet: 95 | function getTransactions() { 96 | var deferred = $q.defer(); 97 | 98 | /* 99 | 100 | // See https://github.com/ethereum/go-ethereum/issues/1897#issuecomment-166351797 101 | // plus the following posts 102 | // Giving up for now. Invested another 3 hours without results. Grrrr.. 103 | 104 | // var options="address:"+$scope.addressId; 105 | // var options = {"address": "0xf2cc0eeaaaed313542cb262b0b8c3972425143f0"}; // $scope.addressId}; // , "topics": [null] 106 | // var options = 'pending' 107 | // console.log(options); 108 | 109 | var options = {fromBlock: 0, toBlock: 'latest', address: "0xf2cc0eeaaaed313542cb262b0b8c3972425143f0"}; 110 | 111 | var myfilter = web3.eth.filter(options); 112 | 113 | // var myfilter= web3.eth.filter(options); 114 | console.log(myfilter); 115 | 116 | 117 | myfilter.get(function (error, log) { 118 | console.log("get error:", error); 119 | console.log("get log:", log); 120 | }); 121 | 122 | web3.eth.filter(options, 123 | function(error, result){ 124 | if(!error){ 125 | console.log("no error"); 126 | deferred.resolve(result); 127 | } 128 | else{ 129 | console.log("error"); 130 | deferred.reject(error); 131 | } 132 | }); 133 | 134 | */ 135 | return deferred.promise; 136 | } 137 | }; 138 | $scope.init(); 139 | 140 | function hex2a(hexx) { 141 | var hex = hexx.toString();//force conversion 142 | var str = ''; 143 | for (var i = 0; i < hex.length; i += 2) 144 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 145 | return str; 146 | } 147 | }); 148 | -------------------------------------------------------------------------------- /docs/scripts/controllers/blockInfosController.js: -------------------------------------------------------------------------------- 1 | var BigNumber = require('bignumber.js'); 2 | angular.module('ethExplorer') 3 | .controller('blockInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 4 | 5 | $scope.init = function () { 6 | $scope.blockId = $routeParams.blockId; 7 | if (!(!$scope.blockId)) { 8 | getBlockInfos() 9 | .then(function (result) { 10 | var number = web3.eth.blockNumber; 11 | $scope.result = result; 12 | 13 | $scope.numberOfUncles = result.uncles.length; 14 | 15 | //if ($scope.numberOfUncles!=0) { 16 | // uncle1=result.uncles[0]; 17 | // console.log(web3.eth.getUncle(uncle1)); 18 | //} 19 | 20 | if (!(!result.hash)) { 21 | $scope.hash = result.hash; 22 | } 23 | else { 24 | $scope.hash = 'pending'; 25 | } 26 | if (!(!result.miner)) { 27 | $scope.miner = result.miner; 28 | } 29 | else { 30 | $scope.miner = 'pending'; 31 | } 32 | $scope.gasLimit = result.gasLimit; 33 | $scope.gasUsed = result.gasUsed; 34 | $scope.nonce = result.nonce; 35 | var diff = ("" + result.difficulty).replace(/['"]+/g, '') / 1000000000000; 36 | $scope.difficulty = diff.toFixed(3) + " T"; 37 | $scope.gasLimit = new BigNumber(result.gasLimit).toFormat(0) + " m/s"; // that's a string 38 | $scope.gasUsed = new BigNumber(result.gasUsed).toFormat(0) + " m/s"; 39 | $scope.nonce = result.nonce; 40 | $scope.number = result.number; 41 | $scope.parentHash = result.parentHash; 42 | $scope.uncledata = result.sha3Uncles; 43 | $scope.rootHash = result.stateRoot; 44 | $scope.blockNumber = result.number; 45 | $scope.timestamp = new Date(result.timestamp * 1000).toUTCString(); 46 | $scope.extraData = result.extraData.slice(2); 47 | $scope.dataFromHex = hex2a(result.extraData.slice(2)); 48 | $scope.size = result.size; 49 | $scope.firstBlock = false; 50 | $scope.lastBlock = false; 51 | if (!(!$scope.blockNumber)) { 52 | $scope.conf = number - $scope.blockNumber + " Confirmations"; 53 | if (number === $scope.blockNumber) { 54 | $scope.conf = 'Unconfirmed'; 55 | $scope.lastBlock = true; 56 | } 57 | if ($scope.blockNumber === 0) { 58 | $scope.firstBlock = true; 59 | } 60 | } 61 | 62 | if (!(!$scope.blockNumber)) { 63 | var info = web3.eth.getBlock($scope.blockNumber); 64 | if (!(!info)) { 65 | var newDate = new Date(); 66 | newDate.setTime(info.timestamp * 1000); 67 | $scope.time = newDate.toUTCString(); 68 | } 69 | } 70 | }); 71 | 72 | } else { 73 | $location.path("/"); 74 | } 75 | 76 | function getBlockInfos() { 77 | var deferred = $q.defer(); 78 | 79 | web3.eth.getBlock($scope.blockId, function (error, result) { 80 | if (!error) { 81 | deferred.resolve(result); 82 | } 83 | else { 84 | deferred.reject(error); 85 | } 86 | }); 87 | return deferred.promise; 88 | } 89 | 90 | }; 91 | $scope.init(); 92 | 93 | // parse transactions 94 | $scope.transactions = []; 95 | 96 | web3.eth.getBlockTransactionCount($scope.blockId, function (error, result) { 97 | var txCount = result; 98 | $scope.numberOfTransactions = txCount; 99 | for (var blockIdx = 0; blockIdx < txCount; blockIdx++) { 100 | web3.eth.getTransactionFromBlock($scope.blockId, blockIdx, function (error, result) { 101 | // console.log("Result: ", result); 102 | web3.eth.getTransactionReceipt(result.hash, function (error, receipt) { 103 | var transaction = { 104 | id: receipt.transactionHash, 105 | hash: receipt.transactionHash, 106 | from: receipt.from, 107 | to: receipt.to, 108 | gas: receipt.gasUsed, 109 | input: result.input.slice(2), 110 | value: web3.fromWei(result.value, "ether"), 111 | contractAddress: receipt.contractAddress 112 | }; 113 | $scope.$apply( 114 | $scope.transactions.push(transaction) 115 | ); 116 | }); 117 | }) 118 | } 119 | }); 120 | 121 | function hex2a(hexx) { 122 | var hex = hexx.toString(); //force conversion 123 | var str = ''; 124 | for (var i = 0; i < hex.length; i += 2) 125 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 126 | 127 | return str; 128 | } 129 | }); 130 | -------------------------------------------------------------------------------- /docs/scripts/controllers/chainInfosController.js: -------------------------------------------------------------------------------- 1 | var BigNumber = require('bignumber.js'); 2 | 3 | angular.module('ethExplorer') 4 | .controller('chainInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 5 | 6 | $scope.init = function () { 7 | getChainInfos() 8 | .then(function (result) { 9 | 10 | $scope.result = result; // just a dummy value, but following Whit's example. 11 | 12 | $scope.blockNum = web3.eth.blockNumber; // now that was easy 13 | 14 | if (!(!$scope.blockNum)) { 15 | // TODO: put the 2 web3.eth.getBlock into the async function below 16 | // easiest to first do with fastInfosCtrl 17 | var blockNewest = web3.eth.getBlock($scope.blockNum); 18 | 19 | if (!(!blockNewest)) { 20 | // difficulty 21 | $scope.difficulty = blockNewest.difficulty; 22 | $scope.difficultyToExponential = blockNewest.difficulty.toExponential(3); 23 | 24 | $scope.totalDifficulty = blockNewest.totalDifficulty; 25 | $scope.totalDifficultyToExponential = blockNewest.totalDifficulty.toExponential(3); 26 | 27 | $scope.totalDifficultyDividedByDifficulty = $scope.totalDifficulty.dividedBy($scope.difficulty); 28 | $scope.totalDifficultyDividedByDifficulty_formatted = $scope.totalDifficultyDividedByDifficulty.toFormat(1); 29 | 30 | $scope.AltsheetsCoefficient = $scope.totalDifficultyDividedByDifficulty.dividedBy($scope.blockNum); 31 | $scope.AltsheetsCoefficient_formatted = $scope.AltsheetsCoefficient.toFormat(4); 32 | 33 | // large numbers still printed nicely: 34 | $scope.difficulty_formatted = $scope.difficulty.toFormat(0); 35 | $scope.totalDifficulty_formatted = $scope.totalDifficulty.toFormat(0); 36 | 37 | // Gas Limit 38 | $scope.gasLimit = new BigNumber(blockNewest.gasLimit).toFormat(0) + " m/s"; 39 | 40 | // Time 41 | var newDate = new Date(); 42 | newDate.setTime(blockNewest.timestamp * 1000); 43 | $scope.time = newDate.toUTCString(); 44 | 45 | $scope.secondsSinceBlock1 = blockNewest.timestamp - 1438226773; 46 | $scope.daysSinceBlock1 = ($scope.secondsSinceBlock1 / 86400).toFixed(2); 47 | 48 | // Average Block Times: 49 | // TODO: make fully async, put below into 'fastInfosCtrl' 50 | 51 | var blockBefore = web3.eth.getBlock($scope.blockNum - 1); 52 | if (!(!blockBefore)) { 53 | $scope.blocktime = blockNewest.timestamp - blockBefore.timestamp; 54 | } 55 | $scope.range1 = 100; 56 | range = $scope.range1; 57 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 58 | if (!(!blockBefore)) { 59 | $scope.blocktimeAverage1 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 60 | } 61 | $scope.range2 = 1000; 62 | range = $scope.range2; 63 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 64 | if (!(!blockBefore)) { 65 | $scope.blocktimeAverage2 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 66 | } 67 | $scope.range3 = 10000; 68 | range = $scope.range3; 69 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 70 | if (!(!blockBefore)) { 71 | $scope.blocktimeAverage3 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 72 | } 73 | $scope.range4 = 100000; 74 | range = $scope.range4; 75 | var blockPast = web3.eth.getBlock(Math.max($scope.blockNum - range, 0)); 76 | if (!(!blockBefore)) { 77 | $scope.blocktimeAverage4 = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 78 | } 79 | 80 | range = $scope.blockNum; 81 | var blockPast = web3.eth.getBlock(1); 82 | if (!(!blockBefore)) { 83 | $scope.blocktimeAverageAll = ((blockNewest.timestamp - blockPast.timestamp) / range).toFixed(2); 84 | } 85 | //fastAnswers($scope); 86 | //$scope=BlockExplorerConstants($scope); 87 | } 88 | } 89 | 90 | // Block Explorer Info 91 | $scope.isConnected = web3.isConnected(); 92 | //$scope.peerCount = web3.net.peerCount; 93 | $scope.versionApi = web3.version.api; 94 | $scope.versionClient = web3.version.client; 95 | $scope.versionNetwork = web3.version.network; 96 | $scope.versionCurrency = web3.version.ethereum; // TODO: change that to currencyname? 97 | 98 | // ready for the future: 99 | try { 100 | $scope.versionWhisper = web3.version.whisper; 101 | } 102 | catch (err) { 103 | $scope.versionWhisper = err.message; 104 | } 105 | }); 106 | 107 | function getChainInfos() { 108 | var deferred = $q.defer(); 109 | deferred.resolve(0); // dummy value 0, for now. // see blockInfosController.js 110 | return deferred.promise; 111 | } 112 | }; 113 | $scope.init(); 114 | }); -------------------------------------------------------------------------------- /docs/scripts/controllers/mainController.js: -------------------------------------------------------------------------------- 1 | //var cryptoSocket = require('crypto-socket'); 2 | var BigNumber = require('bignumber.js'); 3 | angular.module('ethExplorer') 4 | .controller('viewCtrl', function ($rootScope, $location) { 5 | $rootScope.locationPath = $location.$$path; 6 | }) 7 | .controller('mainCtrl', function ($rootScope, $scope, $location) { 8 | 9 | // Display & update block list 10 | getETHRates(); 11 | updateBlockList(); 12 | updateTXList(); 13 | updateStats(); 14 | getHashrate(); 15 | 16 | web3.eth.filter("latest", function (error, result) { 17 | if (!error) { 18 | getETHRates(); 19 | updateBlockList(); 20 | updateTXList(); 21 | updateStats(); 22 | getHashrate(); 23 | $scope.$apply(); 24 | } 25 | }); 26 | 27 | function updateStats() { 28 | web3.eth.getBlockNumber(function (err, currentBlockNumber) { 29 | if (err) 30 | return console.log(err); 31 | $scope.blockNum = currentBlockNumber; 32 | if (!(!$scope.blockNum)) { 33 | web3.eth.getBlock($scope.blockNum, function (err, blockNewest) { 34 | if (err) 35 | return console.log(err); 36 | if (!(!blockNewest)) { 37 | // difficulty 38 | $scope.difficulty = blockNewest.difficulty; 39 | 40 | // Gas Limit 41 | $scope.gasLimit = new BigNumber(blockNewest.gasLimit).toFormat(0) + " m/s"; 42 | 43 | web3.eth.getBlock($scope.blockNum - 1, function (err, blockBefore) { 44 | if (err) 45 | return console.log(err); 46 | $scope.blocktime = blockNewest.timestamp - blockBefore.timestamp; 47 | }); 48 | } 49 | } 50 | ); 51 | } 52 | }); 53 | } 54 | 55 | 56 | function getHashrate() { 57 | $.getJSON("https://www.etherchain.org/api/miningEstimator", function (json) { 58 | var hr = json.hashrate; 59 | $scope.hashrate = hr; 60 | }); 61 | } 62 | 63 | function getETHRates() { 64 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 65 | var price = Number(json[0].price_usd); 66 | $scope.ethprice = "$" + price.toFixed(2); 67 | }); 68 | 69 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 70 | var btcprice = Number(json[0].price_btc); 71 | $scope.ethbtcprice = btcprice; 72 | }); 73 | 74 | $.getJSON("https://api.coinmarketcap.com/v1/ticker/ethereum/", function (json) { 75 | var cap = Number(json[0].market_cap_usd); 76 | //console.log("Current ETH Market Cap: " + cap); 77 | $scope.ethmarketcap = cap; 78 | }); 79 | } 80 | 81 | function updateTXList() { 82 | web3.eth.getBlockNumber(function (err, currentBlockNumber) { 83 | if (err) 84 | return console.log(err); 85 | $scope.txNumber = currentBlockNumber; 86 | $scope.recenttransactions = []; 87 | 88 | getTransactionsFromBlock(currentBlockNumber); 89 | 90 | function getTransactionsFromBlock(blockNumber) { 91 | web3.eth.getBlock(blockNumber, true, function (err, block) { 92 | if (err) { 93 | console.log(err); 94 | return getTransactionsFromBlock(blockNumber); 95 | } 96 | 97 | var transInBlock = []; 98 | var loopLimit = 10; 99 | 100 | if (loopLimit > block.transactions.length) 101 | loopLimit = block.transactions.length; 102 | 103 | for (var i = 0; i < loopLimit; i++) { 104 | transInBlock.push(block.transactions[i]); 105 | 106 | if (i === loopLimit - 1) { 107 | $scope.recenttransactions = $scope.recenttransactions.concat(transInBlock); 108 | $scope.$apply(); 109 | if ($scope.recenttransactions.length <= 10 && blockNumber > 0) 110 | getTransactionsFromBlock(--blockNumber) 111 | } 112 | } 113 | }); 114 | } 115 | }); 116 | } 117 | 118 | function updateBlockList() { 119 | web3.eth.getBlockNumber(function (err, currentBlockNumber) { 120 | if (err) 121 | return console.log(err); 122 | $scope.currentBlockNumber = currentBlockNumber; 123 | $scope.blocks = []; 124 | getBlockDetails(currentBlockNumber); 125 | 126 | function getBlockDetails(blockNumber) { 127 | web3.eth.getBlock(blockNumber, function (err, block) { 128 | if (err) { 129 | console.log(err); 130 | return getBlockDetails(blockNumber); 131 | } 132 | $scope.blocks = $scope.blocks.concat(block); 133 | $scope.$apply(); 134 | 135 | if ($scope.blocks.length <= 10 && blockNumber > 0) 136 | getBlockDetails(--blockNumber) 137 | }) 138 | } 139 | }); 140 | } 141 | 142 | }) 143 | ; 144 | 145 | angular.module('filters', []) 146 | .filter('truncate', function () { 147 | return function (text, length, end) { 148 | if (isNaN(length)) 149 | length = 10; 150 | 151 | if (!end) 152 | end = "..."; 153 | 154 | if (text.length <= length || text.length - end.length <= length) { 155 | return text; 156 | } else { 157 | return String(text).substring(0, length - end.length) + end; 158 | } 159 | }; 160 | }) 161 | .filter('diffFormat', function () { 162 | //convert hash/solution to different kiloHash,MegaHash/solution and others 163 | return function (diffi) { 164 | if (isNaN(diffi)) return diffi; 165 | if (diffi > 1000000000000000) { 166 | var n = diffi / 1000000000000000; 167 | return n.toFixed(3) + " P"; 168 | } 169 | if (diffi > 1000000000000) { 170 | var n = diffi / 1000000000000; 171 | return n.toFixed(3) + " T"; 172 | } 173 | if (diffi > 1000000000) { 174 | var n = diffi / 1000000000; 175 | return n.toFixed(3) + " G"; 176 | } 177 | if (diffi > 1000000) { 178 | var n = diffi / 1000000; 179 | return n.toFixed(3) + " M"; 180 | } 181 | var n = diffi / 1000; 182 | return n.toFixed(3) + " K"; 183 | }; 184 | }) 185 | .filter('stylize', function () { 186 | return function (style) { 187 | if (isNaN(style)) return style; 188 | var si = '' + style + ''; 189 | return si; 190 | }; 191 | }) 192 | .filter('stylize2', function () { 193 | return function (text) { 194 | if (isNaN(text)) return text; 195 | var si = ' ' + text; 196 | return si; 197 | }; 198 | }) 199 | .filter('hashFormat', function () { 200 | //convert hash/second to different kiloHash,MegaHash/second and others 201 | return function (hashr) { 202 | if (isNaN(hashr)) return hashr; 203 | if (hashr > 1000000000000000) { 204 | var n = hashr / 1000000000000000; 205 | return n.toFixed(3) + " PH/s"; 206 | } 207 | if (hashr > 1000000000000) { 208 | var n = hashr / 1000000000000; 209 | return n.toFixed(3) + " TH/s"; 210 | } 211 | if (hashr > 1000000000) { 212 | var n = hashr / 1000000000; 213 | return n.toFixed(3) + " GH/s"; 214 | } 215 | if (hashr > 1000000) { 216 | var n = hashr / 1000000; 217 | return n.toFixed(3) + " MH/s"; 218 | } 219 | var n = hashr / 1000; 220 | return n.toFixed(3) + " KH/s"; 221 | }; 222 | }) 223 | .filter('gasFormat', function () { 224 | return function (txt) { 225 | if (isNaN(txt)) return txt; 226 | var b = new BigNumber(txt); 227 | return b.toFormat(0) + " m/s"; 228 | }; 229 | }) 230 | .filter('BigNum', function () { 231 | return function (txt) { 232 | if (isNaN(txt)) return txt; 233 | var b = new BigNumber(txt); 234 | var w = web3.fromWei(b, "ether"); 235 | return w.toFixed(6) + " ETH"; 236 | }; 237 | }) 238 | .filter('timestampAge', function () { 239 | return function (timestamp) { 240 | function dhms(ms) { 241 | var days = Math.floor(ms / (24 * 60 * 60 * 1000)); 242 | var daysms = ms % (24 * 60 * 60 * 1000); 243 | var hrs = Math.floor((daysms) / (60 * 60 * 1000)); 244 | var hrsms = daysms % (60 * 60 * 1000); 245 | var mins = Math.floor((hrsms) / (60 * 1000)); 246 | var minsms = hrsms % (60 * 1000); 247 | var secs = Math.floor((minsms) / (1000)); 248 | 249 | var diff = " ago"; 250 | var secsString = secs + " sec"; 251 | var minsString = mins + " min"; 252 | var hrsString = hrs + " hr"; 253 | var daysString = days + " day"; 254 | 255 | if (secs > 1) 256 | secsString = secs + " secs"; 257 | if (mins > 1) 258 | minsString = mins + " mins"; 259 | if (hrs > 1) 260 | hrsString = hrs + " hrs"; 261 | if (days > 1) 262 | daysString = days + " days"; 263 | 264 | if (days >= 1) 265 | return daysString + " " + hrsString + diff; 266 | if (hrs >= 1) 267 | return hrsString + " " + minsString + diff; 268 | if (mins >= 1) 269 | return minsString + " " + secsString + diff; 270 | 271 | return secsString + diff; 272 | } 273 | 274 | var dateNow = moment.utc(); 275 | var txtTime = moment.utc(timestamp); 276 | var diffInMs = dateNow.diff(txtTime); 277 | return dhms(diffInMs); 278 | }; 279 | }) 280 | .filter('sizeFormat', function () { 281 | return function (size) { 282 | if (isNaN(size)) return size; 283 | var s = size / 1000; 284 | return s.toFixed(3) + " kB"; 285 | }; 286 | }); 287 | -------------------------------------------------------------------------------- /docs/scripts/controllers/transactionInfosController.js: -------------------------------------------------------------------------------- 1 | angular.module('ethExplorer') 2 | .controller('transactionInfosCtrl', function ($rootScope, $scope, $location, $routeParams, $q) { 3 | 4 | $scope.init = function () { 5 | 6 | $scope.txId = $routeParams.transactionId; 7 | 8 | if (!(!$scope.txId)) { // add a test to check if it match tx paterns to avoid useless API call, clients are not obliged to come from the search form... 9 | 10 | getTransactionInfos() 11 | .then(function (result) { 12 | //TODO Refactor this logic, asynchron calls + services.... 13 | var number = web3.eth.blockNumber; 14 | 15 | $scope.result = result; 16 | 17 | if (!(!result.blockHash)) { 18 | $scope.blockHash = result.blockHash; 19 | } 20 | else { 21 | $scope.blockHash = 'pending'; 22 | } 23 | if (!(!result.blockNumber)) { 24 | $scope.blockNumber = result.blockNumber; 25 | } 26 | else { 27 | $scope.blockNumber = 'pending'; 28 | } 29 | $scope.from = result.from; 30 | $scope.gas = result.gas; 31 | //$scope.gasPrice = result.gasPrice.c[0] + " WEI"; 32 | $scope.gasPrice = web3.fromWei(result.gasPrice, "ether").toFormat(10) + " ETH"; 33 | $scope.hash = result.hash; 34 | $scope.input = result.input; // that's a string 35 | $scope.nonce = result.nonce; 36 | $scope.to = result.to; 37 | $scope.transactionIndex = result.transactionIndex; 38 | //$scope.ethValue = web3.fromWei(result.value[0], "ether"); Newer method but has "" 39 | $scope.ethValue = result.value.c[0] / 10000; 40 | $scope.txprice = web3.fromWei(result.gas * result.gasPrice, "ether") + " ETH"; 41 | if (!(!$scope.blockNumber)) { 42 | $scope.conf = number - $scope.blockNumber; 43 | if ($scope.conf === 0) { 44 | $scope.conf = 'unconfirmed'; //TODO change color button when unconfirmed... ng-if or ng-class 45 | } 46 | } 47 | //TODO Refactor this logic, asynchron calls + services.... 48 | if (!(!$scope.blockNumber)) { 49 | var info = web3.eth.getBlock($scope.blockNumber); 50 | if (!(!info)) { 51 | $scope.time = info.timestamp; 52 | } 53 | } 54 | }); 55 | } 56 | else { 57 | $location.path("/"); // add a trigger to display an error message so user knows he messed up with the TX number 58 | } 59 | 60 | function getTransactionInfos() { 61 | var deferred = $q.defer(); 62 | 63 | web3.eth.getTransaction($scope.txId, function (error, result) { 64 | if (!error) { 65 | deferred.resolve(result); 66 | } 67 | else { 68 | deferred.reject(error); 69 | } 70 | }); 71 | return deferred.promise; 72 | } 73 | }; 74 | $scope.init(); 75 | }); 76 | -------------------------------------------------------------------------------- /docs/vendor_js/angular-moment.min.js: -------------------------------------------------------------------------------- 1 | "format amd";!function(){"use strict";function a(a){return angular.isUndefined(a)||null===a}function b(){try{return require("moment")}catch(a){throw new Error("Please install moment via npm. Please reference to: https://github.com/urish/angular-moment")}}function c(c,d){if("undefined"==typeof d){if("function"!=typeof require)throw new Error("Moment cannot be found by angular-moment! Please reference to: https://github.com/urish/angular-moment");d=b()}return c.module("angularMoment",[]).constant("angularMomentConfig",{preprocess:null,timezone:null,format:null,statefulFilters:!0}).constant("moment",d).constant("amTimeAgoConfig",{withoutSuffix:!1,serverTime:null,titleFormat:null,fullDateThreshold:null,fullDateFormat:null,fullDateThresholdUnit:"day"}).directive("amTimeAgo",["$window","moment","amMoment","amTimeAgoConfig",function(b,d,e,f){return function(g,h,i){function j(){var a;if(p)a=p;else if(f.serverTime){var b=(new Date).getTime(),c=b-w+f.serverTime;a=d(c)}else a=d();return a}function k(){q&&(b.clearTimeout(q),q=null)}function l(a){var c=j().diff(a,v),d=t&&c>=t;if(d?h.text(a.format(u)):h.text(a.from(j(),r)),s&&z&&h.attr("title",a.format(s)),!d){var e=Math.abs(j().diff(a,"minute")),f=3600;e<1?f=1:e<60?f=30:e<180&&(f=300),q=b.setTimeout(function(){l(a)},1e3*f)}}function m(a){y&&h.attr("datetime",a)}function n(){if(k(),o){var a=e.preprocessDate(o);l(a),m(a.toISOString())}}var o,p,q=null,r=f.withoutSuffix,s=f.titleFormat,t=f.fullDateThreshold,u=f.fullDateFormat,v=f.fullDateThresholdUnit,w=(new Date).getTime(),x=i.amTimeAgo,y="TIME"===h[0].nodeName.toUpperCase(),z=!h.attr("title");g.$watch(x,function(b){return a(b)||""===b?(k(),void(o&&(h.text(""),m(""),o=null))):(o=b,void n())}),c.isDefined(i.amFrom)&&g.$watch(i.amFrom,function(b){p=a(b)||""===b?null:d(b),n()}),c.isDefined(i.amWithoutSuffix)&&g.$watch(i.amWithoutSuffix,function(a){"boolean"==typeof a?(r=a,n()):r=f.withoutSuffix}),i.$observe("amFullDateThreshold",function(a){t=a,n()}),i.$observe("amFullDateFormat",function(a){u=a,n()}),i.$observe("amFullDateThresholdUnit",function(a){v=a,n()}),g.$on("$destroy",function(){k()}),g.$on("amMoment:localeChanged",function(){n()})}}]).service("amMoment",["moment","$rootScope","$log","angularMomentConfig",function(a,b,d,e){var f=null;this.changeLocale=function(d,e){var f=a.locale(d,e);return c.isDefined(d)&&b.$broadcast("amMoment:localeChanged"),f},this.changeTimezone=function(c){a.tz&&a.tz.setDefault?(a.tz.setDefault(c),b.$broadcast("amMoment:timezoneChanged")):d.warn("angular-moment: changeTimezone() works only with moment-timezone.js v0.3.0 or greater."),e.timezone=c,f=c},this.preprocessDate=function(b){return f!==e.timezone&&this.changeTimezone(e.timezone),e.preprocess?e.preprocess(b):a(!isNaN(parseFloat(b))&&isFinite(b)?parseInt(b,10):b)}}]).filter("amParse",["moment",function(a){return function(b,c){return a(b,c)}}]).filter("amFromUnix",["moment",function(a){return function(b){return a.unix(b)}}]).filter("amUtc",["moment",function(a){return function(b){return a.utc(b)}}]).filter("amUtcOffset",["amMoment",function(a){function b(b,c){return a.preprocessDate(b).utcOffset(c)}return b}]).filter("amLocal",["moment",function(a){return function(b){return a.isMoment(b)?b.local():null}}]).filter("amTimezone",["amMoment","angularMomentConfig","$log",function(a,b,c){function d(b,d){var e=a.preprocessDate(b);return d?e.tz?e.tz(d):(c.warn("angular-moment: named timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js ?"),e):e}return d}]).filter("amCalendar",["moment","amMoment","angularMomentConfig",function(b,c,d){function e(b,d,e){if(a(b))return"";var f=c.preprocessDate(b);return f.isValid()?f.calendar(d,e):""}return e.$stateful=d.statefulFilters,e}]).filter("amDifference",["moment","amMoment","angularMomentConfig",function(b,c,d){function e(d,e,f,g){if(a(d))return"";var h=c.preprocessDate(d),i=a(e)?b():c.preprocessDate(e);return h.isValid()&&i.isValid()?h.diff(i,f,g):""}return e.$stateful=d.statefulFilters,e}]).filter("amDateFormat",["moment","amMoment","angularMomentConfig",function(b,c,d){function e(b,d){if(a(b))return"";var e=c.preprocessDate(b);return e.isValid()?e.format(d):""}return e.$stateful=d.statefulFilters,e}]).filter("amDurationFormat",["moment","angularMomentConfig",function(b,c){function d(c,d,e){return a(c)?"":b.duration(c,d).humanize(e)}return d.$stateful=c.statefulFilters,d}]).filter("amTimeAgo",["moment","amMoment","angularMomentConfig",function(b,c,d){function e(d,e,f){var g,h;return a(d)?"":(d=c.preprocessDate(d),g=b(d),g.isValid()?(h=b(f),!a(f)&&h.isValid()?g.from(h,e):g.fromNow(e)):"")}return e.$stateful=d.statefulFilters,e}]).filter("amSubtract",["moment","angularMomentConfig",function(b,c){function d(c,d,e){return a(c)?"":b(c).subtract(parseInt(d,10),e)}return d.$stateful=c.statefulFilters,d}]).filter("amAdd",["moment","angularMomentConfig",function(b,c){function d(c,d,e){return a(c)?"":b(c).add(parseInt(d,10),e)}return d.$stateful=c.statefulFilters,d}]).filter("amStartOf",["moment","angularMomentConfig",function(b,c){function d(c,d){return a(c)?"":b(c).startOf(d)}return d.$stateful=c.statefulFilters,d}]).filter("amEndOf",["moment","angularMomentConfig",function(b,c){function d(c,d){return a(c)?"":b(c).endOf(d)}return d.$stateful=c.statefulFilters,d}]),"angularMoment"}var d=window&&window.process&&window.process.type;"function"==typeof define&&define.amd?define(["angular","moment"],c):"undefined"!=typeof module&&module&&module.exports&&"function"==typeof require&&!d?module.exports=c(require("angular"),require("moment")):c(angular,("undefined"!=typeof global?global:window).moment)}(); 2 | //# sourceMappingURL=angular-moment.min.js.map -------------------------------------------------------------------------------- /docs/vendor_js/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.5.7 3 | (c) 2010-2016 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(F,d){'use strict';function x(t,l,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(b,e,a,c,k){function p(){m&&(g.cancel(m),m=null);h&&(h.$destroy(),h=null);n&&(m=g.leave(n),m.then(function(){m=null}),n=null)}function A(){var a=t.current&&t.current.locals;if(d.isDefined(a&&a.$template)){var a=b.$new(),c=t.current;n=k(a,function(a){g.enter(a,null,n||e).then(function(){!d.isDefined(z)||z&&!b.$eval(z)||l()});p()});h=c.scope=a;h.$emit("$viewContentLoaded"); 7 | h.$eval(s)}else p()}var h,n,m,z=a.autoscroll,s=a.onload||"";b.$on("$routeChangeSuccess",A);A()}}}function w(d,l,g){return{restrict:"ECA",priority:-400,link:function(b,e){var a=g.current,c=a.locals;e.html(c.$template);var k=d(e.contents());if(a.controller){c.$scope=b;var p=l(a.controller,c);a.controllerAs&&(b[a.controllerAs]=p);e.data("$ngControllerController",p);e.children().data("$ngControllerController",p)}b[a.resolveAs||"$resolve"]=c;k(b)}}}var C=d.isArray,D=d.isObject,s=d.module("ngRoute",["ng"]).provider("$route", 8 | function(){function t(b,e){return d.extend(Object.create(b),e)}function l(b,d){var a=d.caseInsensitiveMatch,c={originalPath:b,regexp:b},g=c.keys=[];b=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g,function(b,a,d,c){b="?"===c||"*?"===c?"?":null;c="*"===c||"*?"===c?"*":null;g.push({name:d,optional:!!b});a=a||"";return""+(b?"":a)+"(?:"+(b?a:"")+(c&&"(.+?)"||"([^/]+)")+(b||"")+")"+(b||"")}).replace(/([\/$\*])/g,"\\$1");c.regexp=new RegExp("^"+b+"$",a?"i":"");return c}var g={};this.when= 9 | function(b,e){var a;a=void 0;if(C(e)){a=a||[];for(var c=0,k=e.length;c/g,">")}function v(a,c){var b=!1,d=e.bind(a,a.push);return{start:function(a,f){a=e.lowercase(a);!b&&F[a]&&(b=a);b||!0!==n[a]||(d("<"),d(a),e.forEach(f,function(b,f){var g=e.lowercase(f),h="img"===a&&"src"===g||"background"===g;!0!==G[g]||!0===y[g]&&!c(b,h)||(d(" "),d(f),d('="'),d(x(b)),d('"'))}),d(">"))},end:function(a){a=e.lowercase(a);b||!0!==n[a]||!0===z[a]||(d(""));a== 9 | b&&(b=!1)},chars:function(a){b||d(x(a))}}}function r(a){if(a.nodeType===q.Node.ELEMENT_NODE)for(var c=a.attributes,b=0,d=c.length;b"\u201d\u2019]/i,b=/^mailto:/i,d=e.$$minErr("linky"),g=e.isString;return function(f,h,k){function m(a){a&&p.push(A(a))}function q(a,b){var c,d=r(a);p.push("');m(b);p.push("")}if(null==f||""===f)return f;if(!g(f))throw d("notstring",f);for(var r=e.isFunction(k)?k:e.isObject(k)?function(){return k}:function(){return{}},s=f,p=[],t,n;f=s.match(c);)t=f[0],f[2]||f[4]||(t=(f[3]?"http://":"mailto:")+t),n=f.index,m(s.substr(0,n)),q(t,f[0].replace(b,"")),s=s.substring(n+f[0].length);m(s);return a(p.join(""))}}])})(window,window.angular); 15 | //# sourceMappingURL=angular-sanitize.min.js.map 16 | -------------------------------------------------------------------------------- /docs/vendor_js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /docs/vendor_js/de.js: -------------------------------------------------------------------------------- 1 | //! moment.js locale configuration 2 | //! locale : German [de] 3 | //! author : lluchs : https://github.com/lluchs 4 | //! author: Menelion Elensúle: https://github.com/Oire 5 | //! author : Mikolaj Dadela : https://github.com/mik01aj 6 | 7 | ;(function (global, factory) { 8 | typeof exports === 'object' && typeof module !== 'undefined' 9 | && typeof require === 'function' ? factory(require('../moment')) : 10 | typeof define === 'function' && define.amd ? define(['../moment'], factory) : 11 | factory(global.moment) 12 | }(this, (function (moment) { 'use strict'; 13 | 14 | 15 | function processRelativeTime(number, withoutSuffix, key, isFuture) { 16 | var format = { 17 | 'm': ['eine Minute', 'einer Minute'], 18 | 'h': ['eine Stunde', 'einer Stunde'], 19 | 'd': ['ein Tag', 'einem Tag'], 20 | 'dd': [number + ' Tage', number + ' Tagen'], 21 | 'M': ['ein Monat', 'einem Monat'], 22 | 'MM': [number + ' Monate', number + ' Monaten'], 23 | 'y': ['ein Jahr', 'einem Jahr'], 24 | 'yy': [number + ' Jahre', number + ' Jahren'] 25 | }; 26 | return withoutSuffix ? format[key][0] : format[key][1]; 27 | } 28 | 29 | var de = moment.defineLocale('de', { 30 | months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), 31 | monthsShort : 'Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.'.split('_'), 32 | monthsParseExact : true, 33 | weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), 34 | weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'), 35 | weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), 36 | weekdaysParseExact : true, 37 | longDateFormat : { 38 | LT: 'HH:mm', 39 | LTS: 'HH:mm:ss', 40 | L : 'DD.MM.YYYY', 41 | LL : 'D. MMMM YYYY', 42 | LLL : 'D. MMMM YYYY HH:mm', 43 | LLLL : 'dddd, D. MMMM YYYY HH:mm' 44 | }, 45 | calendar : { 46 | sameDay: '[heute um] LT [Uhr]', 47 | sameElse: 'L', 48 | nextDay: '[morgen um] LT [Uhr]', 49 | nextWeek: 'dddd [um] LT [Uhr]', 50 | lastDay: '[gestern um] LT [Uhr]', 51 | lastWeek: '[letzten] dddd [um] LT [Uhr]' 52 | }, 53 | relativeTime : { 54 | future : 'in %s', 55 | past : 'vor %s', 56 | s : 'ein paar Sekunden', 57 | ss : '%d Sekunden', 58 | m : processRelativeTime, 59 | mm : '%d Minuten', 60 | h : processRelativeTime, 61 | hh : '%d Stunden', 62 | d : processRelativeTime, 63 | dd : processRelativeTime, 64 | M : processRelativeTime, 65 | MM : processRelativeTime, 66 | y : processRelativeTime, 67 | yy : processRelativeTime 68 | }, 69 | dayOfMonthOrdinalParse: /\d{1,2}\./, 70 | ordinal : '%d.', 71 | week : { 72 | dow : 1, // Monday is the first day of the week. 73 | doy : 4 // The week that contains Jan 4th is the first week of the year. 74 | } 75 | }); 76 | 77 | return de; 78 | 79 | }))); 80 | -------------------------------------------------------------------------------- /docs/views/addressInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Ethereum Address 4 | View information about an Ethereum Address 5 |

6 |
7 |
8 |
{{ addressId }}


9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
Summary
Current Balance{{ balance }} ETH
(Value in USD: {{ balanceusd || "Calculating.." }})
Transaction Count (# of outgoing TXs){{ txCount }}
Code
27 |
28 | Transaction History Coming Soon 29 |
30 | -------------------------------------------------------------------------------- /docs/views/api/api.html: -------------------------------------------------------------------------------- 1 |

API Info 2 | about the current block: 3 |

4 | 9 | -------------------------------------------------------------------------------- /docs/views/blockInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
View information about Ethereum Block {{number}}
4 | 5 |
6 |
7 | 8 | 9 | < Prev 10 | Next > 11 |
12 |

13 | Summary 14 | - information of block {{number}} 15 |

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
Block Number{{number}}
Block Hash{{ hash }}
Received Time 29 | {{timestamp}} 30 |
Confirmations{{conf}}
Difficulty 39 | {{difficulty}} 40 |
Nonce{{nonce}}
Size{{size}} bytes
Miner{{miner}}
Gas Limit{{gasLimit}}
Gas Used{{gasUsed}}
Uncle Hash{{ uncledata }}
State Root Hash{{ rootHash }}
Parent Hash{{ parentHash }}
Data{{extraData}}
Data (Translated){{dataFromHex}}
Number of Uncle Blocks{{numberOfUncles}}
Number of Transactions{{numberOfTransactions}}
96 |
97 | 98 |

99 | Transactions 100 | - contained in current block 101 |

102 | 103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |
Transaction #{{$index+1}}
Hash #{{tx.hash}}
From{{tx.from}}
To{{tx.to}}
Contract Address{{tx.contractAddress}}
Gas{{tx.gas}}
Input
Value{{tx.value}} ETH
139 |
140 |
141 | 142 |
143 | -------------------------------------------------------------------------------- /docs/views/chainInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Ethereum Information 4 | View information about the Ethereum Blockchain 5 |

6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
Summary
Current Block{{ blockNum }}
Block Time 18 | {{ blocktime }} seconds since block {{ blockNum - 1 }}
19 |
Block Time Averages
24 | {{ blocktimeAverage1 }} seconds for the past 100 blocks
25 | {{ blocktimeAverage2 }} seconds for the past 1,000 blocks 26 |
27 | {{ blocktimeAverage3 }} seconds for the past 10,000 blocks
28 | {{ blocktimeAverage4 }} seconds for the past 100,000 blocks 29 |
30 | {{ blocktimeAverageAll }} seconds for all {{blockNum}} blocks 31 |
32 |
Current Difficulty{{ difficulty_formatted }}
Total Difficulty{{ totalDifficulty_formatted }}
Gas Limit{{ gasLimit }}
Time{{ time }}
Connected to ETH Network{{ isConnected }}
API/Client Version{{ versionApi }} - {{ versionClient }}
60 |
61 |
62 | 63 |
64 | -------------------------------------------------------------------------------- /docs/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
Current Block:
{{ currentBlockNumber }}
17 |
18 |
19 |
ETH/USD Price:
{{ ethprice || "Loading.." }}
20 |
21 |
22 |
Gas Limit:
{{ gasLimit || "Loading.." }}
23 |
24 |
25 |
Block Time:
{{ blocktime?blocktime+" second(s)":false || "Loading.." }}
26 |
27 |
28 |
Current Diff:
{{ difficulty || "Loading.." | diffFormat }}
29 |
30 |
31 |
Hashrate:
{{ hashrate|| "Loading.." | hashFormat }}
32 |
33 |
34 |

Recent Blocks Most Recent Blocks in the Ethereum Network


35 |
36 | 37 | 38 | 41 | 42 | 43 | 44 | 47 | 50 | 53 | 56 | 57 | 58 | 59 | 64 | 65 | 66 | 67 | 70 | 73 | 76 | 79 | 80 | 81 |
39 | Block # 40 | Block HashDifficultyMiner 45 | Size 46 | 48 | Age 49 | 51 | # of TXs 52 | 54 | Gas used 55 |
60 | 61 | {{ block.number }} 62 | 63 | {{ block.hash | truncate : 12 }}{{ block.difficulty | diffFormat }}{{ block.miner }} 68 | {{ block.size | sizeFormat }} 69 | 71 | {{ block.timestamp * 1000 | timestampAge }} 72 | 74 | {{ block.transactions.length }} 75 | 77 | {{ block.gasUsed | gasFormat }} 78 |
82 |
83 |

Recent Transactions Transactions in the Ethereum Network


84 |
85 | 86 | 87 | 90 | 91 | 92 | 94 | 97 | 100 | 101 | 102 | 103 | 104 | 105 | 110 | 111 | 112 | 115 | 118 | 119 | 120 |
88 | TX Hash 89 | Block #FromTo 93 | 95 | Gas Used 96 | 98 | Value 99 |
{{ tx.hash || "" | truncate : 45 }} 106 | 107 | {{ tx.blockNumber || "" }} 108 | 109 | {{ tx.from || "" | truncate : 20 }}{{ tx.to || "" | truncate : 20 }} 113 | {{ tx.gas | gasFormat }} 114 | 116 | {{ tx.value | BigNum }} 117 |
121 |
122 |
123 | -------------------------------------------------------------------------------- /docs/views/transactionInfos.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Transaction 4 | View information about an Ethereum transaction 5 |

6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 22 | 29 | 30 | 31 |
{{txId}}
16 | {{from}} 17 |
18 |
20 | 21 | 23 | {{to}} 24 | 25 | {{ethValue}} ETH 26 | 27 |
28 |
32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
Summary
Block Hash{{blockHash}}
Received Time 49 | {{time * 1000 | date:'medium'}} 50 |
Included In Block 55 | 56 |
Gas Used{{gas | gasFormat}}
Gas Price{{gasPrice}}
Transaction Confirmations{{conf}}
Number of transactions made by the sender prior to this one{{nonce}}
Transaction price{{txprice}}
Data
86 |
87 |
88 | 89 |
90 | -------------------------------------------------------------------------------- /e2e-tests/protractor.conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | allScriptsTimeout: 11000, 3 | 4 | specs: [ 5 | '*.js' 6 | ], 7 | 8 | capabilities: { 9 | 'browserName': 'chrome' 10 | }, 11 | 12 | baseUrl: 'http://localhost:8000/app/', 13 | 14 | framework: 'jasmine', 15 | 16 | jasmineNodeOpts: { 17 | defaultTimeoutInterval: 30000 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /e2e-tests/scenarios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* https://github.com/angular/protractor/blob/master/docs/toc.md */ 4 | 5 | describe('my app', function() { 6 | 7 | 8 | it('should automatically redirect to /view1 when location hash/fragment is empty', function() { 9 | browser.get('index.html'); 10 | expect(browser.getLocationAbsUrl()).toMatch("/view1"); 11 | }); 12 | 13 | 14 | describe('view1', function() { 15 | 16 | beforeEach(function() { 17 | browser.get('index.html#/view1'); 18 | }); 19 | 20 | 21 | it('should render view1 when user navigates to /view1', function() { 22 | expect(element.all(by.css('[ng-view] p')).first().getText()). 23 | toMatch(/partial for view 1/); 24 | }); 25 | 26 | }); 27 | 28 | 29 | describe('view2', function() { 30 | 31 | beforeEach(function() { 32 | browser.get('index.html#/view2'); 33 | }); 34 | 35 | 36 | it('should render view2 when user navigates to /view2', function() { 37 | expect(element.all(by.css('[ng-view] p')).first().getText()). 38 | toMatch(/partial for view 2/); 39 | }); 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config){ 2 | config.set({ 3 | 4 | basePath : './', 5 | 6 | files : [ 7 | 'app/bower_components/angular/angular.js', 8 | 'app/bower_components/angular-route/angular-route.js', 9 | 'app/bower_components/angular-mocks/angular-mocks.js', 10 | 'app/components/**/*.js', 11 | 'app/view*/**/*.js' 12 | ], 13 | 14 | autoWatch : true, 15 | 16 | frameworks: ['jasmine'], 17 | 18 | browsers : ['Chrome'], 19 | 20 | plugins : [ 21 | 'karma-chrome-launcher', 22 | 'karma-firefox-launcher', 23 | 'karma-jasmine', 24 | 'karma-junit-reporter' 25 | ], 26 | 27 | junitReporter : { 28 | outputFile: 'test_out/unit.xml', 29 | suite: 'unit' 30 | } 31 | 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /manuals/README-AltSheets.md: -------------------------------------------------------------------------------- 1 | README AltSheets 2 | 3 | changed 4 | * app/app.js lines 1-18 and 51-53 5 | * app/index.html lines 59-61 and 85-87 6 | * app/styles/main.css lines 31-35 7 | 8 | added: 9 | * manuals/ 10 | * manuals/README.md 11 | * manuals/README-AltSheets.md 12 | * manuals/serverconfig.md 13 | -------------------------------------------------------------------------------- /manuals/README.md: -------------------------------------------------------------------------------- 1 | # manuals 2 | 3 | * [serverconfig](serverconfig.md), e.g. IP and port -------------------------------------------------------------------------------- /manuals/serverconfig.md: -------------------------------------------------------------------------------- 1 | # serverconfig 2 | 3 | For IP, and port, see 4 | * [app/app.js](https://github.com/altsheets/explorer/blob/MIT/app/app.js#L6-L10) 5 | * [package.json](https://github.com/etherparty/explorer/blob/MIT/package.json#L24) 6 | 7 | Thanks to joey, for helping to find out the [correct geth command](https://github.com/altsheets/explorer/blob/MIT/app/app.js#L14-L15). 8 | 9 | It is useful to make a shortcut for starting the server with that geth command, e.g.: 10 | 11 | echo 'geth --rpc --rpcaddr your.ip.address.here --rpcport 8545 --rpcapi "web3,eth" --rpccorsdomain "http://your.ip.address.here:8000"' > ~/go/bin/EthExp_geth 12 | chmod u+x ~/go/bin/EthExp_geth 13 | screen -S geth EthExp_geth 14 | 15 | (CTRL-A D to leave that screen.) 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-block-explorer", 3 | "description": "A lightweight ethereum block explorer", 4 | "version": "0.0.1", 5 | "repository": "https://github.com/sthnaqvi/explorer", 6 | "license": "MIT", 7 | "private": false, 8 | "devDependencies": { 9 | "bower": "^1.3.1", 10 | "http-webnode": "^1.0.1", 11 | "jasmine-core": "^2.3.4", 12 | "karma": "~0.12", 13 | "karma-chrome-launcher": "^0.1.12", 14 | "karma-firefox-launcher": "^0.1.6", 15 | "karma-jasmine": "^0.3.5", 16 | "karma-junit-reporter": "^0.2.2", 17 | "protractor": "^2.1.0", 18 | "shelljs": "^0.2.6" 19 | }, 20 | "scripts": { 21 | "postinstall": "bower install", 22 | "prestart": "npm install", 23 | "start": "http-webnode ./app -a localhost -p 8000 -c-1", 24 | "pretest": "npm install", 25 | "test": "karma start karma.conf.js", 26 | "test-single-run": "karma start karma.conf.js --single-run", 27 | "preupdate-webdriver": "npm install", 28 | "update-webdriver": "webdriver-manager update", 29 | "preprotractor": "npm run update-webdriver", 30 | "protractor": "protractor e2e-tests/protractor.conf.js", 31 | "update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + sed(/sourceMappingURL=angular-loader.min.js.map/,'sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map','app/bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\"" 32 | }, 33 | "dependencies": { 34 | "angular-chart.js": "^1.0.0-alpha8", 35 | "chart.js": "^2.1.6", 36 | "crypto-socket": "^1.0.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /testfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sthnaqvi/explorer/93b7ed799dba8484a8a00d8384e591264bbc5e1d/testfile --------------------------------------------------------------------------------