├── .circleci └── config.yml ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── docs ├── constants.md └── transactions.md ├── hsdexplorer ├── .dockerignore ├── explorer │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── history │ │ ├── __init__.py │ │ └── write.py │ ├── hsd.py │ ├── math.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ ├── img │ │ │ ├── right_black_arrow.png │ │ │ ├── right_green_arrow.png │ │ │ └── right_red_arrow.png │ │ ├── js │ │ │ ├── bootstrap.min.js │ │ │ ├── jquery.min.js │ │ │ └── popper.min.js │ │ └── scss │ │ │ ├── main.scss │ │ │ ├── modules │ │ │ └── _colors.scss │ │ │ ├── partials │ │ │ ├── _box.scss │ │ │ ├── _card.scss │ │ │ ├── _header.scss │ │ │ ├── _layout.scss │ │ │ └── _tables.scss │ │ │ └── vendor │ │ │ └── _bootstrap.scss │ ├── tasks.py │ ├── templates │ │ └── explorer │ │ │ ├── _block-table.html │ │ │ ├── _events-single-table.html │ │ │ ├── _events-table.html │ │ │ ├── _pagination.html │ │ │ ├── _transaction.html │ │ │ ├── _tx-input-list.html │ │ │ ├── _tx-output-list.html │ │ │ ├── about.html │ │ │ ├── address.html │ │ │ ├── base.html │ │ │ ├── block.html │ │ │ ├── blocks.html │ │ │ ├── events.html │ │ │ ├── index.html │ │ │ ├── name.html │ │ │ ├── names.html │ │ │ └── transaction.html │ ├── templatetags │ │ ├── __init__.py │ │ └── hsd_math.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── hsdbin │ ├── compress.js │ ├── decode.js │ ├── key.js │ ├── package-lock.json │ ├── package.json │ ├── records.js │ └── resource.js ├── hsdexplorer │ ├── __init__.py │ ├── celery.py │ ├── middleware │ │ ├── __init__.py │ │ └── health.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py ├── kube ├── handshake-explorer.yaml ├── handshake-node.yaml ├── postgres.yaml └── redis.yaml └── skaffold.yaml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Required variables: 2 | # - GCLOUD_SERVICE_KEY - a Gcloud service key with permission to push docker 3 | # containers to the docker registry, and to deploy to k8s. 4 | version: 2 5 | jobs: 6 | build_deploy: 7 | working_directory: /app 8 | docker: 9 | - image: google/cloud-sdk:latest 10 | steps: 11 | - checkout 12 | - run: 13 | name: Store Service Account 14 | command: echo $GCLOUD_SERVICE_KEY | base64 --decode > ${HOME}/gcloud-service-key.json 15 | - run: 16 | name: Push application Docker image 17 | command: | 18 | curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 19 | chmod +x skaffold 20 | export GOOGLE_APPLICATION_CREDENTIALS=${HOME}/gcloud-service-key.json 21 | gcloud auth activate-service-account --key-file ${HOME}/gcloud-service-key.json 22 | gcloud auth configure-docker --quiet 23 | gcloud config set compute/zone us-central1-a 24 | gcloud config set project hello-world-392 25 | gcloud container clusters get-credentials main 26 | ./skaffold run -p gcb 27 | workflows: 28 | version: 2 29 | build-test-deploy: 30 | jobs: 31 | - build_deploy: 32 | filters: 33 | branches: 34 | only: 35 | - dontrun 36 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | GeoIP.dat 4 | node_modules/ 5 | celerybeat-schedule 6 | **/static/**/scss/**/*.css 7 | **/static/**/scss/**/*.css.map 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | 3 | RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - 4 | RUN apt update && apt install -y nodejs libunbound-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | WORKDIR /app 7 | RUN pip install pipenv 8 | ADD Pipfile* /tmp/ 9 | RUN cd /tmp && pipenv install --system --deploy 10 | 11 | ADD hsdexplorer/hsdbin/package.json /app/hsdbin/ 12 | RUN cd hsdbin && npm install 13 | 14 | ADD ./hsdexplorer/ /app/ 15 | RUN COLLECTSTATIC=1 echo 'yes' | python manage.py collectstatic; python manage.py compilescss; unset COLLECTSTATIC 16 | 17 | ENTRYPOINT ["gunicorn", "--bind", "0.0.0.0:8000", "--log-level", "debug", "hsdexplorer.wsgi"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2018 Foxberry LLC. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | gunicorn = "*" 10 | django = ">=2.1.2" 11 | whitenoise = "*" 12 | requests = "*" 13 | "pysha3" = "*" 14 | django-tz-detect = "*" 15 | redis = "*" 16 | "psycopg2-binary" = "*" 17 | django-debug-toolbar = "*" 18 | libsass = "*" 19 | django-compressor = "*" 20 | django-sass-processor = "*" 21 | django-redis = "*" 22 | celery = "*" 23 | django-extensions = "*" 24 | django-memoize = "*" 25 | 26 | [requires] 27 | python_version = "3.6" 28 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "ad483a1c8cf2df7fae31b08c5163d1aa158853901afcf30f2dccf7d87b9d8ea8" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "amqp": { 20 | "hashes": [ 21 | "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8", 22 | "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d" 23 | ], 24 | "version": "==2.5.2" 25 | }, 26 | "asgiref": { 27 | "hashes": [ 28 | "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", 29 | "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" 30 | ], 31 | "version": "==3.2.3" 32 | }, 33 | "billiard": { 34 | "hashes": [ 35 | "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", 36 | "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" 37 | ], 38 | "version": "==3.6.3.0" 39 | }, 40 | "celery": { 41 | "hashes": [ 42 | "sha256:3c5fcd6bfcf9a6323cb742cfc121d1790d50cfeddf300ba723cfa0b356413f07", 43 | "sha256:a650525303ee866fb0c62c82f68681fcc2183eebbfafae552c27d30125fe518b" 44 | ], 45 | "index": "pypi", 46 | "version": "==4.4.1" 47 | }, 48 | "certifi": { 49 | "hashes": [ 50 | "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", 51 | "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" 52 | ], 53 | "version": "==2019.11.28" 54 | }, 55 | "chardet": { 56 | "hashes": [ 57 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 58 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 59 | ], 60 | "version": "==3.0.4" 61 | }, 62 | "django": { 63 | "hashes": [ 64 | "sha256:50b781f6cbeb98f673aa76ed8e572a019a45e52bdd4ad09001072dfd91ab07c8", 65 | "sha256:89e451bfbb815280b137e33e454ddd56481fdaa6334054e6e031041ee1eda360" 66 | ], 67 | "index": "pypi", 68 | "version": "==3.0.4" 69 | }, 70 | "django-appconf": { 71 | "hashes": [ 72 | "sha256:35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3", 73 | "sha256:c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6" 74 | ], 75 | "version": "==1.0.3" 76 | }, 77 | "django-compressor": { 78 | "hashes": [ 79 | "sha256:57ac0a696d061e5fc6fbc55381d2050f353b973fb97eee5593f39247bc0f30af", 80 | "sha256:d2ed1c6137ddaac5536233ec0a819e14009553fee0a869bea65d03e5285ba74f" 81 | ], 82 | "index": "pypi", 83 | "version": "==2.4" 84 | }, 85 | "django-debug-toolbar": { 86 | "hashes": [ 87 | "sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943", 88 | "sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c" 89 | ], 90 | "index": "pypi", 91 | "version": "==2.2" 92 | }, 93 | "django-extensions": { 94 | "hashes": [ 95 | "sha256:1a03c4e8bade575f8c2be6c76456f8a2be3f9b02ab9f47d3535afa9562dc0493", 96 | "sha256:2699cc1d6fb4bd393c0b5832fea4bc685f2ace5800b3c9ff222b2080f161ac04" 97 | ], 98 | "index": "pypi", 99 | "version": "==2.2.8" 100 | }, 101 | "django-memoize": { 102 | "hashes": [ 103 | "sha256:7428e19cb17009ea51120420101a239d83bdf4064e5c72ca8c3dfa9656033f24" 104 | ], 105 | "index": "pypi", 106 | "version": "==2.2.1" 107 | }, 108 | "django-redis": { 109 | "hashes": [ 110 | "sha256:a5b1e3ffd3198735e6c529d9bdf38ca3fcb3155515249b98dc4d966b8ddf9d2b", 111 | "sha256:e1aad4cc5bd743d8d0b13d5cae0cef5410eaace33e83bff5fc3a139ad8db50b4" 112 | ], 113 | "index": "pypi", 114 | "version": "==4.11.0" 115 | }, 116 | "django-sass-processor": { 117 | "hashes": [ 118 | "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340" 119 | ], 120 | "index": "pypi", 121 | "version": "==0.8" 122 | }, 123 | "django-tz-detect": { 124 | "hashes": [ 125 | "sha256:4c13811c21a7a233171392b5bb639a891b4241ca4a45b38ddbb19d348b5b3e07", 126 | "sha256:fb94506a52a4d2d46e1aca007d0c7101903c037203bf69a2bbc186f0721a777b" 127 | ], 128 | "index": "pypi", 129 | "version": "==0.3.0" 130 | }, 131 | "gunicorn": { 132 | "hashes": [ 133 | "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", 134 | "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" 135 | ], 136 | "index": "pypi", 137 | "version": "==20.0.4" 138 | }, 139 | "idna": { 140 | "hashes": [ 141 | "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", 142 | "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" 143 | ], 144 | "version": "==2.9" 145 | }, 146 | "importlib-metadata": { 147 | "hashes": [ 148 | "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", 149 | "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" 150 | ], 151 | "markers": "python_version < '3.8'", 152 | "version": "==1.5.0" 153 | }, 154 | "kombu": { 155 | "hashes": [ 156 | "sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76", 157 | "sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2" 158 | ], 159 | "version": "==4.6.8" 160 | }, 161 | "libsass": { 162 | "hashes": [ 163 | "sha256:003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2", 164 | "sha256:0fd8b4337b3b101c6e6afda9112cc0dc4bacb9133b59d75d65968c7317aa3272", 165 | "sha256:338e9ae066bf1fde874e335324d5355c52d2081d978b4f74fc59536564b35b08", 166 | "sha256:4dcfd561fb100250b89496e1362b96f2cc804f689a59731eb0f94f9a9e144f4a", 167 | "sha256:50778d4be269a021ba2bf42b5b8f6ff3704ab96a82175a052680bddf3ba7cc9f", 168 | "sha256:6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0", 169 | "sha256:74acd9adf506142699dfa292f0e569fdccbd9e7cf619e8226f7117de73566e32", 170 | "sha256:81a013a4c2a614927fd1ef7a386eddabbba695cbb02defe8f31cf495106e974c", 171 | "sha256:845a9573b25c141164972d498855f4ad29367c09e6d76fad12955ad0e1c83013", 172 | "sha256:8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95", 173 | "sha256:9b59afa0d755089c4165516400a39a289b796b5612eeef5736ab7a1ebf96a67c", 174 | "sha256:a7e685466448c9b1bf98243339793978f654a1151eb5c975f09b83c7a226f4c1", 175 | "sha256:c93df526eeef90b1ea4799c1d33b6cd5aea3e9f4633738fb95c1287c13e6b404", 176 | "sha256:e318f06f06847ff49b1f8d086ac9ebce1e63404f7ea329adab92f4f16ba0e00e", 177 | "sha256:fc5f8336750f76f1bfae82f7e9e89ae71438d26fc4597e3ab4c05ca8fcd41d8a", 178 | "sha256:fcb7ab4dc81889e5fc99cafbc2017bc76996f9992fc6b175f7a80edac61d71df" 179 | ], 180 | "index": "pypi", 181 | "version": "==0.19.4" 182 | }, 183 | "psycopg2-binary": { 184 | "hashes": [ 185 | "sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29", 186 | "sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03", 187 | "sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039", 188 | "sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881", 189 | "sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309", 190 | "sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed", 191 | "sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b", 192 | "sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3", 193 | "sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7", 194 | "sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b", 195 | "sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03", 196 | "sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103", 197 | "sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d", 198 | "sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35", 199 | "sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b", 200 | "sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49", 201 | "sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70", 202 | "sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e", 203 | "sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e", 204 | "sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e", 205 | "sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103", 206 | "sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6", 207 | "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1", 208 | "sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9", 209 | "sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e", 210 | "sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f", 211 | "sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd", 212 | "sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8", 213 | "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f", 214 | "sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4", 215 | "sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964", 216 | "sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08" 217 | ], 218 | "index": "pypi", 219 | "version": "==2.8.4" 220 | }, 221 | "pysha3": { 222 | "hashes": [ 223 | "sha256:0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0", 224 | "sha256:11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48", 225 | "sha256:386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4", 226 | "sha256:41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d", 227 | "sha256:4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9", 228 | "sha256:571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603", 229 | "sha256:59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f", 230 | "sha256:5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f", 231 | "sha256:684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77", 232 | "sha256:68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5", 233 | "sha256:6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9", 234 | "sha256:827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d", 235 | "sha256:93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24", 236 | "sha256:9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608", 237 | "sha256:9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b", 238 | "sha256:c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030", 239 | "sha256:c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8", 240 | "sha256:cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef", 241 | "sha256:f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf", 242 | "sha256:fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07", 243 | "sha256:fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e" 244 | ], 245 | "index": "pypi", 246 | "version": "==1.0.2" 247 | }, 248 | "pytz": { 249 | "hashes": [ 250 | "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", 251 | "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" 252 | ], 253 | "version": "==2019.3" 254 | }, 255 | "rcssmin": { 256 | "hashes": [ 257 | "sha256:ca87b695d3d7864157773a61263e5abb96006e9ff0e021eff90cbe0e1ba18270" 258 | ], 259 | "version": "==1.0.6" 260 | }, 261 | "redis": { 262 | "hashes": [ 263 | "sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f", 264 | "sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833" 265 | ], 266 | "index": "pypi", 267 | "version": "==3.4.1" 268 | }, 269 | "requests": { 270 | "hashes": [ 271 | "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", 272 | "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" 273 | ], 274 | "index": "pypi", 275 | "version": "==2.23.0" 276 | }, 277 | "rjsmin": { 278 | "hashes": [ 279 | "sha256:0ab825839125eaca57cc59581d72e596e58a7a56fbc0839996b7528f0343a0a8", 280 | "sha256:211c2fe8298951663bbc02acdffbf714f6793df54bfc50e1c6c9e71b3f2559a3", 281 | "sha256:466fe70cc5647c7c51b3260c7e2e323a98b2b173564247f9c89e977720a0645f", 282 | "sha256:585e75a84d9199b68056fd4a083d9a61e2a92dfd10ff6d4ce5bdb04bc3bdbfaf", 283 | "sha256:6044ca86e917cd5bb2f95e6679a4192cef812122f28ee08c677513de019629b3", 284 | "sha256:714329db774a90947e0e2086cdddb80d5e8c4ac1c70c9f92436378dedb8ae345", 285 | "sha256:799890bd07a048892d8d3deb9042dbc20b7f5d0eb7da91e9483c561033b23ce2", 286 | "sha256:975b69754d6a76be47c0bead12367a1ca9220d08e5393f80bab0230d4625d1f4", 287 | "sha256:b15dc75c71f65d9493a8c7fa233fdcec823e3f1b88ad84a843ffef49b338ac32", 288 | "sha256:dd0f4819df4243ffe4c964995794c79ca43943b5b756de84be92b445a652fb86", 289 | "sha256:e3908b21ebb584ce74a6ac233bdb5f29485752c9d3be5e50c5484ed74169232c", 290 | "sha256:e487a7783ac4339e79ec610b98228eb9ac72178973e3dee16eba0e3feef25924", 291 | "sha256:ecd29f1b3e66a4c0753105baec262b331bcbceefc22fbe6f7e8bcd2067bcb4d7" 292 | ], 293 | "version": "==1.1.0" 294 | }, 295 | "six": { 296 | "hashes": [ 297 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 298 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 299 | ], 300 | "version": "==1.14.0" 301 | }, 302 | "sqlparse": { 303 | "hashes": [ 304 | "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", 305 | "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" 306 | ], 307 | "version": "==0.3.1" 308 | }, 309 | "urllib3": { 310 | "hashes": [ 311 | "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", 312 | "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" 313 | ], 314 | "version": "==1.25.8" 315 | }, 316 | "vine": { 317 | "hashes": [ 318 | "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", 319 | "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" 320 | ], 321 | "version": "==1.3.0" 322 | }, 323 | "whitenoise": { 324 | "hashes": [ 325 | "sha256:0f9137f74bd95fa54329ace88d8dc695fbe895369a632e35f7a136e003e41d73", 326 | "sha256:62556265ec1011bd87113fb81b7516f52688887b7a010ee899ff1fd18fd22700" 327 | ], 328 | "index": "pypi", 329 | "version": "==5.0.1" 330 | }, 331 | "zipp": { 332 | "hashes": [ 333 | "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", 334 | "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" 335 | ], 336 | "version": "==3.1.0" 337 | } 338 | }, 339 | "develop": {} 340 | } 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Handshake Explorer 2 | 3 | This is a block explorer for the [handshake](https://handshake.org) project. 4 | It's built with python, django, and kubernetes. This is currently deployed to 5 | [hnsxplorer](https://hnsxplorer.com). 6 | 7 | ## Requirements 8 | 9 | * Docker 10 | 11 | ## Local Setup 12 | 13 | ``` 14 | git clone git@github.com:tdickman/handshake-explorer.git 15 | docker-compose up 16 | ``` 17 | 18 | This will start the django app, and all dependent services. See the comments in 19 | `docker-compose.yml` for more details. Most changes will be picked up 20 | automatically, but any scss changes will require you to stop the app and run 21 | the following: 22 | 23 | ``` 24 | docker-compose build 25 | docker-compose up 26 | ``` 27 | 28 | ## Deployment 29 | 30 | We use skaffold to deploy this app to a kubernetes cluster. 31 | 32 | ``` 33 | PASSWORD=$(openssl rand -base64 32) 34 | kubectl create secret generic db --from-literal=password=$PASSWORD 35 | kubectl create secret generic django --from-literal=secret-key=SECRET_KEY 36 | skaffold dev --port-forward 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/constants.md: -------------------------------------------------------------------------------- 1 | # Network Constants 2 | 3 | Values for Testnet: 4 | 5 | * Open period - 72 blocks (~6 hours) == treeInterval 6 | 7 | Values: 8 | 9 | * Mainnet - https://github.com/handshake-org/hsd/blob/master/lib/protocol/networks.js#L230 10 | * Testnet - https://github.com/handshake-org/hsd/blob/master/lib/protocol/networks.js#L683 11 | -------------------------------------------------------------------------------- /docs/transactions.md: -------------------------------------------------------------------------------- 1 | # Transactions 2 | 3 | This document contains notes on the different transaction types 4 | used in handshake. 5 | 6 | * [Reveal](http://localhost:8000/block/12589fe9cf320535eadbc1e570bdcc1365c225f9a6c9d2ee1cecd400a4b05e13) 7 | * [Open](http://localhost:8000/block/c79504b17563bfee8ed47a4fb98b3661f9397741896ac362d1c56d4d93c6f5ba) 8 | * [Register](http://localhost:8000/block/9561fc91070d07ba54f8c2b43310cc629c6df04a1b92816f2268e124efdf1a19) 9 | * [Update](http://localhost:8000/block/e6c6d505ba2096fd773ad61e78f8b89769dd7e2e377073d4996d39fb6b905437) 10 | 11 | ## Structure 12 | 13 | This contains a description of the structure of the covenant section of each 14 | transaction type. 15 | 16 | OPEN 2: 17 | 18 | * nameHash -> sha3 19 | * 0 -> int 20 | * rawName -> str 21 | 22 | BID 3: 23 | 24 | * nameHash -> sha3 25 | * ns.height -> little endian uint32 - indicates open block of auction 26 | * rawName -> str 27 | * hash(blind) -> ?? 28 | 29 | REVEAL 4: 30 | 31 | * nameHash -> sha3 32 | * ns.height -> little endian uint32 - indicates open block of auction 33 | * nonce -> ?? # Blind hash 34 | 35 | REDEEM: 36 | 37 | * nameHash -> sha3 38 | * ns.height -> little endian uint32 - indicates open block of auction 39 | 40 | REGISTER: 41 | 42 | * nameHash -> sha3 43 | * ns.height -> little endian uint32 - indicates open block of auction 44 | 45 | UPDATE: 46 | 47 | * nameHash -> sha3 48 | * ns.height -> little endian uint32 - indicates open block of auction 49 | * resource -> ?? 50 | 51 | RENEW: 52 | 53 | * nameHash -> sha3 54 | * ns.height -> little endian uint32 - indicates open block of auction 55 | * renewalBlock -> little endian uint32 - indicates open block of auction 56 | 57 | ## Auction Process 58 | 59 | OPEN -(73 blocks)-> BID -(288 blocks)-> REVEAL|REDEEM -(576 blocks)-> Completed 60 | 61 | Notes: 62 | * Transaction value during the reveal phase == the amount of our actual bid 63 | * First update sent after winning comes through as a 'register' event - it 64 | looks like this contains tld data that I am currently not parsing 65 | * Not clear when we get our refund 66 | 67 | # HSD-forked Interesting Functions 68 | 69 | * lib/blockchain/chaindb.js:1833 - saveNames 70 | * lib/blockchain/chain.js 71 | * lib/node/http.js:172 - getCoinsByAddress 72 | 73 | -------------------------------------------------------------------------------- /hsdexplorer/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdickman/handshake-explorer/13bf0788c12892a9f43ded9807cfdfb6bb0f484a/hsdexplorer/explorer/__init__.py -------------------------------------------------------------------------------- /hsdexplorer/explorer/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | # Register your models here. 5 | admin.site.register(models.Name) 6 | admin.site.register(models.Block) 7 | admin.site.register(models.Event) 8 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ExplorerConfig(AppConfig): 5 | name = 'explorer' 6 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/history/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdickman/handshake-explorer/13bf0788c12892a9f43ded9807cfdfb6bb0f484a/hsdexplorer/explorer/history/__init__.py -------------------------------------------------------------------------------- /hsdexplorer/explorer/history/write.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import os 3 | import json 4 | 5 | from explorer import models 6 | 7 | 8 | def get_max_block(): 9 | max_block = models.Block.objects.order_by('-height').first() 10 | return max_block.height if max_block else -1 11 | 12 | 13 | def get_processed_block_hash(height): 14 | if height < 0: 15 | return '0000000000000000000000000000000000000000000000000000000000000000' 16 | return models.Block.objects.get(height=height).hash 17 | 18 | 19 | def unprocess_block(height): 20 | """Delete all entries from the specified block from our datastore.""" 21 | # Delete block -> should automatically cascade and cause events to be deleted 22 | models.Block.objects.get(height=height).delete() 23 | 24 | 25 | def insert(event): 26 | if 'name' in event: 27 | models.Name.objects.update_or_create( 28 | hash=event['name_hash'], 29 | name=event['name'] 30 | ) 31 | models.Event( 32 | tx_hash=event['tx_hash'], 33 | block_index=event['block_index'], 34 | output_index=event['output_index'], 35 | action=models.Event.EventAction[event['action']].value, 36 | block_id=event['block'], 37 | data=event.get('data'), 38 | name_id=event['name_hash'], 39 | start_block_id=event.get('start_height'), 40 | value=event.get('value') 41 | ).save() 42 | 43 | 44 | def mark_block(height, hash_val): 45 | models.Block( 46 | height=height, 47 | hash=hash_val 48 | ).save() 49 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/hsd.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import Http404 3 | import codecs 4 | import datetime 5 | import json 6 | import pytz 7 | import re 8 | import requests 9 | import subprocess 10 | from memoize import memoize 11 | 12 | from . import models 13 | from .utils import cache_function 14 | 15 | 16 | @cache_function 17 | def get_info(): 18 | return _request('/') 19 | 20 | 21 | def get_auction_status(open_block): 22 | info = get_info() 23 | blocks_since_open = info['chain']['height'] - open_block 24 | 25 | return { 26 | 'open_completed': max(min(blocks_since_open, settings.OPEN_PERIOD), 0), 27 | 'open_total': settings.OPEN_PERIOD, 28 | 'bidding_completed': max(min( 29 | blocks_since_open - settings.OPEN_PERIOD, 30 | settings.BIDDING_PERIOD), 0), 31 | 'bidding_total': settings.BIDDING_PERIOD, 32 | 'reveal_completed': max(min( 33 | blocks_since_open - settings.OPEN_PERIOD - settings.BIDDING_PERIOD, 34 | settings.REVEAL_PERIOD), 0), 35 | 'reveal_total': settings.REVEAL_PERIOD 36 | } 37 | 38 | 39 | def get_auction_state(open_block): 40 | info = get_info() 41 | blocks_since_open = info['chain']['height'] - open_block 42 | 43 | if blocks_since_open < settings.OPEN_PERIOD: 44 | return 'Open' 45 | 46 | if blocks_since_open < settings.BIDDING_PERIOD: 47 | return 'Bidding' 48 | 49 | if blocks_since_open < settings.REVEAL_PERIOD: 50 | return 'Reveal' 51 | 52 | return 'Live' 53 | 54 | 55 | def get_auction_time_remaining(open_block): 56 | auction_status = get_auction_status(open_block) 57 | current_state = get_auction_state(open_block).lower() 58 | if current_state == 'live': 59 | return 60 | blocks_completed = auction_status['{}_completed'.format(current_state)] 61 | blocks_total = auction_status['{}_total'.format(current_state)] 62 | return (blocks_total - blocks_completed) * settings.BLOCK_TIME_SECONDS 63 | 64 | 65 | def get_claim_status(claim_block): 66 | info = get_info() 67 | blocks_since_claim = info['chain']['height'] - claim_block 68 | return { 69 | 'claiming_completed': max(min(blocks_since_claim, settings.CLAIM_PERIOD), 0), 70 | 'claiming_total': settings.CLAIM_PERIOD 71 | } 72 | 73 | 74 | def get_claim_state(claim_block): 75 | info = get_info() 76 | blocks_since_claim = info['chain']['height'] - claim_block 77 | if blocks_since_claim < settings.CLAIM_PERIOD: 78 | return 'Claiming' 79 | 80 | return 'Live' 81 | 82 | 83 | def get_claim_time_remaining(claim_block): 84 | claim_status = get_claim_status(claim_block) 85 | current_state = get_claim_state(claim_block).lower() 86 | if current_state == 'live': 87 | return 88 | 89 | blocks_completed = claim_status['{}_completed'.format(current_state)] 90 | blocks_total = claim_status['{}_total'.format(current_state)] 91 | return (blocks_total - blocks_completed) * settings.BLOCK_TIME_SECONDS 92 | 93 | 94 | def get_blocks(offset=0, count=20): 95 | """ 96 | Retrieve the `count` previous blocks, starting at `start`. 97 | If `start` is set to None, this will start at the current 98 | block. 99 | """ 100 | info = get_info() 101 | current_block = info['chain']['height'] - offset 102 | blocks = [] 103 | for block_number in range(current_block, max(current_block - count, 0), -1): 104 | block_details = get_block(block_number) 105 | blocks.append(block_details) 106 | 107 | return blocks 108 | 109 | 110 | @memoize(timeout=60 * 60 * 24) 111 | def get_block(block_hash_or_height, decode_resource=False): 112 | return _format_block(_request('/block/{}'.format(block_hash_or_height)), decode_resource=decode_resource) 113 | 114 | 115 | def get_transaction(tx_hash): 116 | return _format_tx(_request('/tx/{}'.format(tx_hash))) 117 | 118 | 119 | def get_address_txs(address): 120 | txs = [_format_tx(tx, address=address) for tx in _request('/tx/address/{}'.format(address))] 121 | return sorted(txs, key=lambda tx: tx['time'], reverse=True) 122 | 123 | 124 | def _format_block(block, decode_resource=False): 125 | # Time parameter isn't present on txs if they we are returned as part of a 126 | # block, but they are included with a raw transaction 127 | for tx in block['txs']: 128 | tx['time'] = block['time'] 129 | block['time'] = datetime.datetime.fromtimestamp(block['time'], tz=pytz.UTC) 130 | block['txs'] = [_format_tx(tx, decode_resource=decode_resource) for tx in block['txs']] 131 | return block 132 | 133 | 134 | def _format_tx(tx, address=None, decode_resource=False): 135 | """ 136 | Format a transaction. If `address` is provided, include the transaction 137 | direction relative to that address. 138 | """ 139 | tx['time'] = datetime.datetime.fromtimestamp(tx['time'], tz=pytz.UTC) 140 | tx['inputs'] = [_format_input(i) for i in tx['inputs']] 141 | tx['outputs'] = [_format_output(o, decode_resource=decode_resource) for o in tx['outputs']] 142 | if address: 143 | tx['direction'] = None 144 | if len([o for o in tx['outputs'] if o.get('address') == address]): 145 | tx['direction'] = 'incoming' 146 | if len([i for i in tx['inputs'] if i.get('address') == address]): 147 | tx['direction'] = 'outgoing' 148 | return tx 149 | 150 | 151 | def _format_input(input_data): 152 | # Mining 153 | if input_data['prevout']['hash'] == '0' * 64: 154 | return { 155 | 'action': 'mine' 156 | } 157 | action = input_data['coin']['covenant']['action'] 158 | return { 159 | 'action': action, 160 | 'value': input_data['coin']['value'], 161 | 'address': input_data['coin']['address'], 162 | 'source_tx': input_data['prevout']['hash'] 163 | } 164 | return input_data 165 | 166 | 167 | def _format_output(output, decode_resource=False): 168 | items = output['covenant']['items'] 169 | action = output['covenant']['action'] 170 | resp = { 171 | 'action': action, 172 | 'address': output['address'] 173 | } 174 | 175 | if action == 'NONE': 176 | resp['address'] = output['address'] 177 | resp['value'] = output['value'] 178 | return resp 179 | 180 | # Process all other actions 181 | resp['name_hash'] = items[0] 182 | if decode_resource: 183 | if action == 'OPEN': 184 | # items[1] == 00000000 185 | resp['name'] = _decode_name(items[2]) 186 | elif action == 'CLAIM': 187 | resp['name'] = _decode_name(items[2]) 188 | elif action == 'BID': 189 | resp['start_height'] = _decode_u32(items[1]) 190 | resp['name'] = _decode_name(items[2]) 191 | resp['value'] = output['value'] 192 | # items[3] == blind 193 | elif action == 'REVEAL': 194 | resp['start_height'] = _decode_u32(items[1]) 195 | resp['nonce'] = items[2] 196 | resp['value'] = output['value'] 197 | elif action == 'REGISTER': 198 | resp['start_height'] = _decode_u32(items[1]) 199 | resp['data'] = _decode_resource(items[2]) 200 | elif action == 'REDEEM': 201 | resp['start_height'] = _decode_u32(items[1]) 202 | resp['value'] = output['value'] 203 | elif action == 'UPDATE': 204 | resp['start_height'] = _decode_u32(items[1]) 205 | resp['data'] = _decode_resource(items[2]) 206 | elif action == 'RENEW': 207 | resp['start_height'] = _decode_u32(items[1]) 208 | resp['renewal_block_hash'] = _decode_u32(items[2]) 209 | 210 | # Lookup the name if it isn't included in the transaction. This will fail 211 | # for new domains that we encounter when processing with celery, in which 212 | # case we can skip it. 213 | if 'name' not in resp: 214 | try: 215 | resp['name'] = models.Name.objects.get(hash=resp['name_hash']).name 216 | except models.Name.DoesNotExist: 217 | pass 218 | 219 | return resp 220 | 221 | 222 | def _decode_u32(hex_val): 223 | """Decode the specified little endian hex value into a u32.""" 224 | return int(codecs.encode(codecs.decode(hex_val, 'hex')[::-1], 'hex').decode(), 16) 225 | 226 | 227 | def _decode_resource(data): 228 | try: 229 | return json.loads(subprocess.check_output(['node', 'hsdbin/decode.js', data]).decode()) 230 | except Exception: 231 | return '' 232 | 233 | 234 | def _decode_name(hex_val): 235 | return bytes.fromhex(hex_val).decode('utf-8') 236 | 237 | 238 | def _request(path): 239 | resp = requests.get('{}{}'.format(settings.HSD_URI, path), timeout=5) 240 | if resp.status_code != 200: 241 | raise Http404 242 | return resp.json() 243 | 244 | 245 | def is_address(value): 246 | return re.compile('[a-z0-9]{42}').match(value) and value[:2] == 'hs' 247 | 248 | 249 | def is_block(value): 250 | if value.isdigit(): 251 | try: 252 | get_block(value) 253 | return True 254 | except json.decoder.JSONDecodeError: 255 | pass 256 | return False 257 | 258 | 259 | def is_transaction(value): 260 | if re.compile('[a-f0-9]{64}').match(value): 261 | try: 262 | get_transaction(value) 263 | return True 264 | except json.decoder.JSONDecodeError: 265 | pass 266 | return False 267 | 268 | 269 | def is_name(value): 270 | return len(models.Name.objects.filter(name=value)) > 0 271 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/math.py: -------------------------------------------------------------------------------- 1 | def total_received(txs, address): 2 | received = 0 3 | for tx in txs: 4 | for it in tx['outputs']: 5 | if it.get('address') == address: 6 | received += it.get('value', 0) 7 | return received 8 | 9 | 10 | def total_sent(txs, address): 11 | sent = 0 12 | for tx in txs: 13 | for ot in tx['inputs']: 14 | if ot.get('address') == address: 15 | sent += ot.get('value', 0) 16 | return sent 17 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-08-26 18:57 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | import django.core.validators 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Block', 19 | fields=[ 20 | ('height', models.PositiveIntegerField(primary_key=True, serialize=False)), 21 | ('hash', models.CharField(db_index=True, max_length=64, validators=[django.core.validators.RegexValidator(code='nomatch', message='Must be a 64 character hex string', regex='^.[a-f0-9]{64}$')])), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Event', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('tx_hash', models.CharField(max_length=64, validators=[django.core.validators.RegexValidator(code='nomatch', message='Must be a 64 character hex string', regex='^.[a-f0-9]{64}$')])), 29 | ('output_index', models.PositiveIntegerField()), 30 | ('block_index', models.PositiveIntegerField()), 31 | ('action', models.CharField(choices=[('NONE', 'NONE'), ('CLAIM', 'CLAIM'), ('OPEN', 'OPEN'), ('BID', 'BID'), ('REVEAL', 'REVEAL'), ('REDEEM', 'REDEEM'), ('REGISTER', 'REGISTER'), ('UPDATE', 'UPDATE'), ('RENEW', 'RENEW'), ('TRANSFER', 'TRANSFER'), ('FINALIZE', 'FINALIZE'), ('REVOKE', 'REVOKE')], max_length=10)), 32 | ('data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), 33 | ('value', models.BigIntegerField(blank=True, null=True)), 34 | ('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='block', to='explorer.Block')), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name='Name', 39 | fields=[ 40 | ('hash', models.CharField(max_length=64, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator('^[a-f0-9]{64}$', 'Must be a 64 character hex string')])), 41 | ('name', models.CharField(db_index=True, max_length=500, unique=True)), 42 | ], 43 | ), 44 | migrations.AddField( 45 | model_name='event', 46 | name='name', 47 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='explorer.Name'), 48 | ), 49 | migrations.AddField( 50 | model_name='event', 51 | name='start_block', 52 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='start_height', to='explorer.Block'), 53 | ), 54 | migrations.AddIndex( 55 | model_name='event', 56 | index=models.Index(fields=['-block', '-block_index', '-output_index'], name='explorer_ev_block_i_8149f9_idx'), 57 | ), 58 | migrations.AlterUniqueTogether( 59 | name='event', 60 | unique_together={('tx_hash', 'output_index')}, 61 | ), 62 | ] 63 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdickman/handshake-explorer/13bf0788c12892a9f43ded9807cfdfb6bb0f484a/hsdexplorer/explorer/migrations/__init__.py -------------------------------------------------------------------------------- /hsdexplorer/explorer/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.postgres.fields import JSONField 2 | from django.core.validators import RegexValidator 3 | from django.db import models 4 | from explorer.utils import ChoiceEnum 5 | 6 | 7 | # Create your models here. 8 | class Name(models.Model): 9 | hash = models.CharField(primary_key=True, validators=[RegexValidator('^[a-f0-9]{64}$', 'Must be a 64 character hex string')], max_length=64) 10 | name = models.CharField(max_length=500, unique=True, db_index=True) 11 | 12 | 13 | class Block(models.Model): 14 | height = models.PositiveIntegerField(primary_key=True) 15 | hash = models.CharField(validators=[RegexValidator(regex='^.[a-f0-9]{64}$', message='Must be a 64 character hex string', code='nomatch')], max_length=64, db_index=True) 16 | 17 | 18 | class Event(models.Model): 19 | class EventAction(ChoiceEnum): 20 | NONE = 'NONE' 21 | CLAIM = 'CLAIM' 22 | OPEN = 'OPEN' 23 | BID = 'BID' 24 | REVEAL = 'REVEAL' 25 | REDEEM = 'REDEEM' 26 | REGISTER = 'REGISTER' 27 | UPDATE = 'UPDATE' 28 | RENEW = 'RENEW' 29 | TRANSFER = 'TRANSFER' 30 | FINALIZE = 'FINALIZE' 31 | REVOKE = 'REVOKE' 32 | 33 | tx_hash = models.CharField(validators=[RegexValidator(regex='^.[a-f0-9]{64}$', message='Must be a 64 character hex string', code='nomatch')], max_length=64) 34 | output_index = models.PositiveIntegerField() 35 | block_index = models.PositiveIntegerField() # Index within block 36 | action = models.CharField(choices=EventAction.choices(), max_length=10) 37 | block = models.ForeignKey(Block, on_delete=models.CASCADE, related_name='block') 38 | data = JSONField(blank=True, null=True) 39 | name = models.ForeignKey(Name, on_delete=models.CASCADE) 40 | start_block = models.ForeignKey(Block, on_delete=models.CASCADE, related_name='start_height', blank=True, null=True) 41 | value = models.BigIntegerField(blank=True, null=True) 42 | 43 | class Meta: 44 | unique_together = (('tx_hash', 'output_index'),) 45 | indexes = [ 46 | models.Index(fields=['-block', '-block_index', '-output_index']) 47 | ] 48 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/static/img/right_black_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdickman/handshake-explorer/13bf0788c12892a9f43ded9807cfdfb6bb0f484a/hsdexplorer/explorer/static/img/right_black_arrow.png -------------------------------------------------------------------------------- /hsdexplorer/explorer/static/img/right_green_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdickman/handshake-explorer/13bf0788c12892a9f43ded9807cfdfb6bb0f484a/hsdexplorer/explorer/static/img/right_green_arrow.png -------------------------------------------------------------------------------- /hsdexplorer/explorer/static/img/right_red_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdickman/handshake-explorer/13bf0788c12892a9f43ded9807cfdfb6bb0f484a/hsdexplorer/explorer/static/img/right_red_arrow.png -------------------------------------------------------------------------------- /hsdexplorer/explorer/static/js/popper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2018 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=J(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!q(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,y=t(e.instance.popper),w=parseFloat(y['margin'+f],10),E=parseFloat(y['border'+f+'Width'],10),v=b-e.offsets.popper[m]-w-E;return v=$(J(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,Q(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case he.FLIP:p=[n,i];break;case he.CLOCKWISE:p=z(n);break;case he.COUNTERCLOCKWISE:p=z(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,y=-1!==['top','bottom'].indexOf(n),w=!!t.flipVariations&&(y&&'start'===r&&h||y&&'end'===r&&c||!y&&'start'===r&&g||!y&&'end'===r&&u);(m||b||w)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),w&&(r=G(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!q(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right 2 | 3 | 4 | Height 5 | Age 6 | Transactions 7 | Miner 8 | 9 | 10 | 11 | {% for block in blocks %} 12 | 13 | {{ block.height }} 14 | {{ block.time }} 15 | {{ block.txs | length }} 16 | {{ block.txs.0.outputs.0.address }} 17 | 18 | {% endfor %} 19 | 20 | 21 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/_events-single-table.html: -------------------------------------------------------------------------------- 1 | {# Required variables: events #} 2 | {% load hsd_math %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for event in events %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 | 26 |
BlockActionAuction StartDataValueTX
{{ event.block_id }}{{ event.action | lower }}{% if event.start_block_id %}{{ event.start_block_id }}{% endif %}{% if event.data %}{{ event.data }}{% endif %}{{ event.value | to_hns }}{% if event.value is not None %} HNS{% endif %}TX
27 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/_events-table.html: -------------------------------------------------------------------------------- 1 | {# Required variables: events #} 2 | {% load hsd_math %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for event in events %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% endfor %} 23 | 24 |
BlockActionNameValueTX
{{ event.block_id }}{{ event.action | lower }}{{ event.name.name }}{{ event.value | to_hns }}{% if event.value is not None %} HNS{% endif %}TX
25 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/_pagination.html: -------------------------------------------------------------------------------- 1 | {# Required variables: current_page, pages, max_page, url_prefix #} 2 |
    3 | {% if current_page != 1 %} 4 |
  • 5 | « 6 |
  • 7 | {% endif %} 8 | {% for page in pages %} 9 |
  • 10 | {{ page }} 11 |
  • 12 | {% endfor %} 13 | {% if max_page and current_page != max_page %} 14 |
  • 15 | » 16 |
  • 17 | {% endif %} 18 |
19 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/_transaction.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | {% load hsd_math %} 3 |
4 |
5 |
{{ tx.hash }} {{ tx.time }}
6 |
7 |
8 | {% include "explorer/_tx-input-list.html" with inputs=tx.inputs %} 9 |
10 |
11 | {% if tx.direction == 'incoming' %} 12 | 13 | {% elif tx.direction == 'outgoing' %} 14 | 15 | {% else %} 16 | 17 | {% endif %} 18 |
19 |
20 | {% include "explorer/_tx-output-list.html" with outputs=tx.outputs %} 21 |
22 | fee {{ tx.fee | to_hns }} HNS 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/_tx-input-list.html: -------------------------------------------------------------------------------- 1 | {% load hsd_math %} 2 | 11 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/_tx-output-list.html: -------------------------------------------------------------------------------- 1 | {% load hsd_math %} 2 |
    3 | {% for output in tx.outputs %} 4 |
  • 5 | {% if output.action == 'NONE' %}{{ output.value | to_hns }} HNS{% endif %} 6 | {% if output.action == 'CLAIM' %}claim for {{ output.name }}{% endif %} 7 | {% if output.action == 'OPEN' %}auction open for {{ output.name }}{% endif %} 8 | {% if output.action == 'BID' %}{{ output.value | to_hns }} HNS bid for {{ output.name }}{% endif %} 9 | {% if output.action == 'REVEAL' %}{{ output.value | to_hns }} HNS for auction reveal of {{ output.name }}{% endif %} 10 | {% if output.action == 'REGISTER' %}register for {{ output.name }}{% endif %} 11 | {% if output.action == 'REDEEM' %}{{ output.value | to_hns }} HNS for auction redeem of {{ output.name }}{% endif %} 12 | {% if output.action == 'UPDATE' %}update for {{ output.name }}{% endif %} 13 | {% if output.action == 'RENEW' %}update for {{ output.name }}{% endif %} 14 | to {{ output.address }} 15 |
  • 16 | {% endfor %} 17 |
18 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/about.html: -------------------------------------------------------------------------------- 1 | {% extends "explorer/base.html" %} 2 | {% block title %}Handshake Explorer: Home{% endblock %} 3 | {% block content %} 4 | 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/address.html: -------------------------------------------------------------------------------- 1 | {% extends "explorer/base.html" %} 2 | {% load hsd_math %} 3 | {% block title %}Handshake Explorer: Address {{ address }}{% endblock %} 4 | {% block content %} 5 |
6 |
7 |
Summary
8 |
9 |
10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Address{{ address }}
20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
Number of Transactions{{ tx_count }}
Total Received{{ total_received | to_hns }} HNS
Total Sent{{ total_sent | to_hns }} HNS
Final Balance{{ total_balance | to_hns }} HNS
42 |
43 |
44 |
45 |
46 |
47 |
48 |
Transactions
49 |
50 |
51 | {% for tx in txs %} 52 | {% include "explorer/_transaction.html" with tx=tx %} 53 | {% endfor %} 54 | {% include "explorer/_pagination.html" with url_prefix="/address/"|add:address %} 55 |
56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/base.html: -------------------------------------------------------------------------------- 1 | {% load sass_tags %} 2 | {% load tz_detect %} 3 | {% load static %} 4 | 5 | 6 | 7 | 8 | {% block title %}{% endblock %} 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 50 | 51 |
52 | {% block content %}{% endblock %} 53 |
54 | 55 | 56 | {% tz_detect %} 57 | 58 | 59 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/block.html: -------------------------------------------------------------------------------- 1 | {% extends "explorer/base.html" %} 2 | {% block title %}Handshake Explorer: Block {{ height }}{% endblock %} 3 | {% block content %} 4 |
5 |
6 |
Summary
7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
Number of Transactions{{ hsdblock.txs | length }}
Height{{ hsdblock.height }}
Timestamp{{ hsdblock.time }}
Bits{{ hsdblock.bits }}
31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
Hash{{ hsdblock.hash }}
Previous Block{{ hsdblock.prevBlock }}
Merkle Root{{ hsdblock.merkleRoot }}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
Transactions
56 |
57 |
58 | {% for tx in hsdblock.txs %} 59 | {% include "explorer/_transaction.html" with tx=tx %} 60 | {% endfor %} 61 |
62 |
63 | {% endblock %} 64 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/blocks.html: -------------------------------------------------------------------------------- 1 | {% extends "explorer/base.html" %} 2 | {% block title %}Handshake Explorer: Blocks{% endblock %} 3 | {% block content %} 4 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/events.html: -------------------------------------------------------------------------------- 1 | {% extends "explorer/base.html" %} 2 | {% block title %}Handshake Explorer: Events{% endblock %} 3 | {% block content %} 4 |
5 |
6 |
Latest Events
7 |
8 |
9 | {% include "explorer/_events-table.html" %} 10 | {% include "explorer/_pagination.html" with url_prefix="/events" %} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /hsdexplorer/explorer/templates/explorer/index.html: -------------------------------------------------------------------------------- 1 | {% extends "explorer/base.html" %} 2 | {% block title %}Handshake Explorer: Home{% endblock %} 3 | {% block content %} 4 |