├── .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 | 
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
103 |
106 |
107 |
108 |
109 |
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 | Summary |
12 |
13 |
14 | Current Balance |
15 | {{ balance }} ETH (Value in USD: {{ balanceusd || "Calculating.." }}) |
16 |
17 |
18 | Transaction Count (# of outgoing TXs) |
19 | {{ txCount }} |
20 |
21 |
22 | Code |
23 | |
24 |
25 |
26 |
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 | Block Number |
20 | {{number}} |
21 |
22 |
23 | Block Hash |
24 | {{ hash }} |
25 |
26 |
27 | Received Time |
28 |
29 | {{timestamp}}
30 | |
31 |
32 |
33 | Confirmations |
34 | {{conf}} |
35 |
36 |
37 | Difficulty |
38 |
39 | {{difficulty}}
40 | |
41 |
42 |
43 | Nonce |
44 | {{nonce}} |
45 |
46 |
47 | Size |
48 | {{size}} bytes |
49 |
50 |
51 | Miner |
52 | {{miner}} |
53 |
54 |
55 | Gas Limit |
56 | {{gasLimit}} |
57 |
58 |
59 | Gas Used |
60 | {{gasUsed}} |
61 |
62 |
63 | Uncle Hash |
64 | {{ uncledata }} |
65 |
66 |
67 | State Root Hash |
68 | {{ rootHash }} |
69 |
70 |
71 | Parent Hash |
72 | {{ parentHash }} |
73 |
74 |
75 | Data |
76 | {{extraData}} |
77 |
78 |
79 |
80 | Data (Translated) |
81 | {{dataFromHex}} |
82 |
83 |
84 |
85 | Number of Uncle Blocks |
86 | {{numberOfUncles}} |
87 |
88 |
89 |
90 | Number of Transactions |
91 | {{numberOfTransactions}} |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | Transactions
100 | - contained in current block
101 |
102 |
103 |
104 |
105 |
106 |
107 | Transaction #{{$index+1}} |
108 |
109 |
110 | Hash # |
111 | {{tx.hash}} |
112 |
113 |
114 | From |
115 | {{tx.from}} |
116 |
117 |
118 | To |
119 | {{tx.to}} |
120 |
121 |
122 | Contract Address |
123 | {{tx.contractAddress}} |
124 |
125 |
126 | Gas |
127 | {{tx.gas}} |
128 |
129 |
130 | Input |
131 | |
132 |
133 |
134 | Value |
135 | {{tx.value}} ETH |
136 |
137 |
138 |
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 | Summary |
10 |
11 |
12 | Current Block |
13 | {{ blockNum }} |
14 |
15 |
16 | Block Time |
17 |
18 | {{ blocktime }} seconds since block {{ blockNum - 1 }}
19 | |
20 |
21 |
22 | Block Time Averages |
23 |
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 | |
33 |
34 |
35 | Current Difficulty |
36 | {{ difficulty_formatted }} |
37 |
38 |
39 | Total Difficulty |
40 | {{ totalDifficulty_formatted }} |
41 |
42 |
43 | Gas Limit |
44 | {{ gasLimit }} |
45 |
46 |
47 | Time |
48 | {{ time }} |
49 |
50 |
51 | Connected to ETH Network |
52 | {{ isConnected }} |
53 |
54 |
55 | API/Client Version |
56 | {{ versionApi }} - {{ versionClient }} |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/views/main.html:
--------------------------------------------------------------------------------
1 |
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 |
39 | Block #
40 | |
41 | Block Hash |
42 | Difficulty |
43 | Miner |
44 |
45 | Size
46 | |
47 |
48 | Age
49 | |
50 |
51 | # of TXs
52 | |
53 |
54 | Gas used
55 | |
56 |
57 |
58 |
59 |
60 |
61 | {{ block.number }}
62 |
63 | |
64 | {{ block.hash | truncate : 12 }} |
65 | {{ block.difficulty | diffFormat }} |
66 | {{ block.miner }} |
67 |
68 | {{ block.size | sizeFormat }}
69 | |
70 |
71 | {{ block.timestamp * 1000 | timestampAge }}
72 | |
73 |
74 | {{ block.transactions.length }}
75 | |
76 |
77 | {{ block.gasUsed | gasFormat }}
78 | |
79 |
80 |
81 |
82 |
83 |
Recent Transactions Transactions in the Ethereum Network
84 |
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 | {{txId}} |
13 |
14 |
15 |
16 | {{from}}
17 |
18 | |
19 |
20 |
21 | |
22 |
23 | {{to}}
24 |
25 | {{ethValue}} ETH
26 |
27 |
28 | |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Summary |
41 |
42 |
43 | Block Hash |
44 | {{blockHash}} |
45 |
46 |
47 | Received Time |
48 |
49 | {{time * 1000 | date:'medium'}}
50 | |
51 |
52 |
53 | Included In Block |
54 |
55 |
56 | |
57 |
58 |
59 | Gas Used |
60 | {{gas | gasFormat}} |
61 |
62 |
63 | Gas Price |
64 | {{gasPrice}} |
65 |
66 |
67 | Transaction Confirmations |
68 | {{conf}} |
69 |
70 |
71 | Number of transactions made by the sender prior to this one |
72 | {{nonce}} |
73 |
74 |
75 | Transaction price |
76 | {{txprice}} |
77 |
78 |
79 | Data |
80 | |
81 |
82 |
83 |
84 |
85 |
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
103 |
106 |
107 |
108 |
109 |
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(""),d(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 | Summary |
12 |
13 |
14 | Current Balance |
15 | {{ balance }} ETH (Value in USD: {{ balanceusd || "Calculating.." }}) |
16 |
17 |
18 | Transaction Count (# of outgoing TXs) |
19 | {{ txCount }} |
20 |
21 |
22 | Code |
23 | |
24 |
25 |
26 |
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 | Block Number |
20 | {{number}} |
21 |
22 |
23 | Block Hash |
24 | {{ hash }} |
25 |
26 |
27 | Received Time |
28 |
29 | {{timestamp}}
30 | |
31 |
32 |
33 | Confirmations |
34 | {{conf}} |
35 |
36 |
37 | Difficulty |
38 |
39 | {{difficulty}}
40 | |
41 |
42 |
43 | Nonce |
44 | {{nonce}} |
45 |
46 |
47 | Size |
48 | {{size}} bytes |
49 |
50 |
51 | Miner |
52 | {{miner}} |
53 |
54 |
55 | Gas Limit |
56 | {{gasLimit}} |
57 |
58 |
59 | Gas Used |
60 | {{gasUsed}} |
61 |
62 |
63 | Uncle Hash |
64 | {{ uncledata }} |
65 |
66 |
67 | State Root Hash |
68 | {{ rootHash }} |
69 |
70 |
71 | Parent Hash |
72 | {{ parentHash }} |
73 |
74 |
75 | Data |
76 | {{extraData}} |
77 |
78 |
79 |
80 | Data (Translated) |
81 | {{dataFromHex}} |
82 |
83 |
84 |
85 | Number of Uncle Blocks |
86 | {{numberOfUncles}} |
87 |
88 |
89 |
90 | Number of Transactions |
91 | {{numberOfTransactions}} |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | Transactions
100 | - contained in current block
101 |
102 |
103 |
104 |
105 |
106 |
107 | Transaction #{{$index+1}} |
108 |
109 |
110 | Hash # |
111 | {{tx.hash}} |
112 |
113 |
114 | From |
115 | {{tx.from}} |
116 |
117 |
118 | To |
119 | {{tx.to}} |
120 |
121 |
122 | Contract Address |
123 | {{tx.contractAddress}} |
124 |
125 |
126 | Gas |
127 | {{tx.gas}} |
128 |
129 |
130 | Input |
131 | |
132 |
133 |
134 | Value |
135 | {{tx.value}} ETH |
136 |
137 |
138 |
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 | Summary |
10 |
11 |
12 | Current Block |
13 | {{ blockNum }} |
14 |
15 |
16 | Block Time |
17 |
18 | {{ blocktime }} seconds since block {{ blockNum - 1 }}
19 | |
20 |
21 |
22 | Block Time Averages |
23 |
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 | |
33 |
34 |
35 | Current Difficulty |
36 | {{ difficulty_formatted }} |
37 |
38 |
39 | Total Difficulty |
40 | {{ totalDifficulty_formatted }} |
41 |
42 |
43 | Gas Limit |
44 | {{ gasLimit }} |
45 |
46 |
47 | Time |
48 | {{ time }} |
49 |
50 |
51 | Connected to ETH Network |
52 | {{ isConnected }} |
53 |
54 |
55 | API/Client Version |
56 | {{ versionApi }} - {{ versionClient }} |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/docs/views/main.html:
--------------------------------------------------------------------------------
1 |
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 |
39 | Block #
40 | |
41 | Block Hash |
42 | Difficulty |
43 | Miner |
44 |
45 | Size
46 | |
47 |
48 | Age
49 | |
50 |
51 | # of TXs
52 | |
53 |
54 | Gas used
55 | |
56 |
57 |
58 |
59 |
60 |
61 | {{ block.number }}
62 |
63 | |
64 | {{ block.hash | truncate : 12 }} |
65 | {{ block.difficulty | diffFormat }} |
66 | {{ block.miner }} |
67 |
68 | {{ block.size | sizeFormat }}
69 | |
70 |
71 | {{ block.timestamp * 1000 | timestampAge }}
72 | |
73 |
74 | {{ block.transactions.length }}
75 | |
76 |
77 | {{ block.gasUsed | gasFormat }}
78 | |
79 |
80 |
81 |
82 |
83 |
Recent Transactions Transactions in the Ethereum Network
84 |
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 | {{txId}} |
13 |
14 |
15 |
16 | {{from}}
17 |
18 | |
19 |
20 |
21 | |
22 |
23 | {{to}}
24 |
25 | {{ethValue}} ETH
26 |
27 |
28 | |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Summary |
41 |
42 |
43 | Block Hash |
44 | {{blockHash}} |
45 |
46 |
47 | Received Time |
48 |
49 | {{time * 1000 | date:'medium'}}
50 | |
51 |
52 |
53 | Included In Block |
54 |
55 |
56 | |
57 |
58 |
59 | Gas Used |
60 | {{gas | gasFormat}} |
61 |
62 |
63 | Gas Price |
64 | {{gasPrice}} |
65 |
66 |
67 | Transaction Confirmations |
68 | {{conf}} |
69 |
70 |
71 | Number of transactions made by the sender prior to this one |
72 | {{nonce}} |
73 |
74 |
75 | Transaction price |
76 | {{txprice}} |
77 |
78 |
79 | Data |
80 | |
81 |
82 |
83 |
84 |
85 |
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
--------------------------------------------------------------------------------