Full Code of EthTx/ethtx_ce for AI

master f1f36fd5f447 cached
52 files
148.4 KB
38.3k tokens
98 symbols
1 requests
Download .txt
Repository: EthTx/ethtx_ce
Branch: master
Commit: f1f36fd5f447
Files: 52
Total size: 148.4 KB

Directory structure:
gitextract_udd85lnu/

├── .dockerignore
├── .env_sample
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE
├── Makefile
├── NOTICE
├── Pipfile
├── README.md
├── docker-compose.override.yml
├── docker-compose.yaml
├── ethtx_ce/
│   ├── .flake8
│   ├── .gitignore
│   ├── app/
│   │   ├── __init__.py
│   │   ├── api/
│   │   │   ├── __init__.py
│   │   │   ├── decorators.py
│   │   │   ├── endpoints/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── info.py
│   │   │   │   ├── semantics.py
│   │   │   │   └── transactions.py
│   │   │   ├── exceptions.py
│   │   │   └── utils.py
│   │   ├── config.py
│   │   ├── exceptions.py
│   │   ├── factory.py
│   │   ├── frontend/
│   │   │   ├── __init__.py
│   │   │   ├── deps.py
│   │   │   ├── exceptions.py
│   │   │   ├── semantics.py
│   │   │   ├── static/
│   │   │   │   └── ethtx.new.css
│   │   │   ├── static.py
│   │   │   ├── templates/
│   │   │   │   ├── exception.html
│   │   │   │   ├── index.html
│   │   │   │   ├── partials/
│   │   │   │   │   └── headtags.html
│   │   │   │   ├── semantics.html
│   │   │   │   └── transaction.html
│   │   │   └── transactions.py
│   │   ├── helpers.py
│   │   ├── logger.py
│   │   └── wsgi.py
│   ├── entrypoint.sh
│   ├── gunicorn_conf.py
│   ├── log_cfg.json
│   ├── start-reload.sh
│   ├── start.sh
│   └── tests/
│       ├── flask_test.py
│       └── mocks/
│           ├── __init__.py
│           └── mocks.py
└── scripts/
    └── git_version_for_docker.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
.git
.github
.gitignore
.pre-commit*
.env
.docker_env
.env_sample
docker-compose.yaml

================================================
FILE: .env_sample
================================================

# Proper nodes are required to run ethtx, provide connection strings for chains which will be used.
MAINNET_NODE_URL=https://geth-erigon-node:8545
# KOVAN_NODE_URL=
# RINKEBY_NODE_URL=

# EthTx supports multiple nodes, if one is unavailable, it will use others. You only need to specify them with a comma.
# Example: MAINNET_NODE_URL=https://geth-erigon-node:8545,https://geth1-erigon-node:8545

# Etherscan API is used to get contract source code, required for decoding process
# You can get free key here https://etherscan.io/apis
ETHERSCAN_KEY=

# Optional. Those represent data required for connecting to mongoDB. It's used for caching semantics
# used in decoding process. But, it's not neccessary for running, If you don't want to use permanent
# db or setup mongo, leave those values, mongomock package is used to simulate in-memory mongo.
MONGO_CONNECTION_STRING=mongomock://localhost/ethtx

# Optional. Credentials for accessing semantics editor page, available under '/semantics/<str:address>'
ETHTX_ADMIN_USERNAME=admin
ETHTX_ADMIN_PASSWORD=admin

# Optional. Api key used for securing decoding API
API_KEY=

# Optional. Valid values are ['production', 'staging', 'development']. Those mainly
# dictate what options are used for flask debugging and logging
ENV=development


================================================
FILE: .gitignore
================================================
# Ignore pipenv's lock
Pipfile.lock
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Editors
.idea
.vscode


# env
.env
.docker_env

tmp/
*.sqlite


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
  - repo: https://github.com/psf/black
    rev: 22.3.0
    hooks:
      - id: black
        language_version: python3.9
        name: EthTx_ce:black
        alias: ethtx_ce-black

  - repo: https://gitlab.com/pycqa/flake8
    rev: 4.0.1
    hooks:
      - id: flake8
        language_version: python3.9
        name: EthTx_ce:flake8
        alias: ethtx_ce-flake8
        args: [ --config=ethtx_ce/.flake8 ]

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.0.1
    hooks:
    - id: trailing-whitespace
    - id: check-ast
    - id: check-docstring-first
    - id: check-merge-conflict

  - repo: local
    hooks:
      - id: pytest
        files: ./ethtx_ce/tests/
        name: pytest
        language: system
        entry: make test
        pass_filenames: false
        always_run: true

================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.


## 0.2.16 - 2022-11-25
### Changed
- Removed `.js` files from the sources and pulling them instead from `cdnjs` [#123](https://github.com/EthTx/ethtx_ce/pull/123)
- Removed `static` ressources from the frontend of the app [#129](https://github.com/EthTx/ethtx_ce/pull/129)
- Removed `gthread` for better stability [#135](https://github.com/EthTx/ethtx_ce/pull/135)
- Removed support of `Rinkeby` [#139](https://github.com/EthTx/ethtx_ce/pull/139)
- Changed the `README.md` to include the `development` dependencies [#140](https://github.com/EthTx/ethtx_ce/pull/140)

### Added
- Added flexible chains rendering [#139](https://github.com/EthTx/ethtx_ce/pull/139)
- Bumped `EthTx` to `0.3.20` and the `web3` depencency to `5.28.0` [#143](https://github.com/EthTx/ethtx_ce/pull/143)


## 0.2.15 - 2022-07-06
### Changed
- Removed `Tokne Flow` branding

### Added
- Added redecode semantics functionality
- Bumped `EthTx` to `0.3.16` [#116](https://github.com/EthTx/ethtx_ce/pull/116)
- From now on the value of `transfer.value` is formatted on the frontend [#116](https://github.com/EthTx/ethtx_ce/pull/116)

### Fixed
- Fixed wrong function name (`get_semantics`) [#118](https://github.com/EthTx/ethtx_ce/pull/118)


## 0.2.14 - 2022-05-18
### Added
- Added `info` endpoint with ethtx/ethtx_ce version [#113](https://github.com/EthTx/ethtx_ce/pull/113)
- added `get_latest_ethtx_version` function (get version from Pypi) [#113](https://github.com/EthTx/ethtx_ce/pull/113)

### Changed
- Refactored `deps` [#113](https://github.com/EthTx/ethtx_ce/pull/113)
- Updated `README.md` [#113](https://github.com/EthTx/ethtx_ce/pull/113)
- Updated `black` version [#113](https://github.com/EthTx/ethtx_ce/pull/113)
- Changed the application name for each component [#113](https://github.com/EthTx/ethtx_ce/pull/113)


## 0.2.13 - 2022-04-22
### Changed
- Extended *.gitignore* [#110](https://github.com/EthTx/ethtx_ce/pull/110)
- Updated `black` pre-commit version [#110](https://github.com/EthTx/ethtx_ce/pull/110)

### Fixed
- Fixed mongodb semantics remove [#110](https://github.com/EthTx/ethtx_ce/pull/110)


## 0.2.12 - 2022-04-06
### Changed
- Bumped `EthTx` to `0.3.14` [#105](https://github.com/EthTx/ethtx_ce/pull/105)


## 0.2.11 - 2022-03-16
### Changed
- New project structure [#97](https://github.com/EthTx/ethtx_ce/pull/97)
- Updated docker, docker-compose [#97](https://github.com/EthTx/ethtx_ce/pull/97)
- Removed logs, useless text [#99](https://github.com/EthTx/ethtx_ce/pull/99)
- Changed space between *tx_hash* and *chain_id* (transaction page) [#99](https://github.com/EthTx/ethtx_ce/pull/99)

### Added
- Added gunicorn configuration [#97](https://github.com/EthTx/ethtx_ce/pull/97)
- Added `entrypoint.sh` and other scripts [#97](https://github.com/EthTx/ethtx_ce/pull/97)


## 0.2.10 - 2022-03-03
### Changed
- Bumped `EthTx` to `0.3.10` [#93](https://github.com/EthTx/ethtx_ce/pull/93)


## 0.2.9 - 2022-02-07
### Fixed
- Typo in `README.md` [#75](https://github.com/EthTx/ethtx_ce/pull/75)
- API serialization [#75](https://github.com/EthTx/ethtx_ce/pull/75)
- Fixed semantics editor [#75](https://github.com/EthTx/ethtx_ce/pull/75)
- Fixed `.env_sample` mongo connection string [#83](https://github.com/EthTx/ethtx_ce/pull/83)

### Added
- Added new route `reload`
- Added `Reolad semantics` button, which allows to reload the semantics (removes from the database and downloads
  again) [#80](https://github.com/EthTx/ethtx_ce/pull/80)
- Added `get_eth_price`. Transaction page displays current **ETH** price taken from *coinbase*
  API [#88](https://github.com/EthTx/ethtx_ce/pull/88)

### Changed
- Removed duplicated environment variables from `docker-compose.yml` [#83](https://github.com/EthTx/ethtx_ce/pull/83)
- Bumped python to `3.9` [#87](https://github.com/EthTx/ethtx_ce/pull/87)
- From now on, `EthTx` will be used with a static version (due to dynamic
  development) [#87](https://github.com/EthTx/ethtx_ce/pull/87)
- Updated requirements [#88](https://github.com/EthTx/ethtx_ce/pull/88)
- Install dev dependencies [#89](https://github.com/EthTx/ethtx_ce/pull/89)


## 0.2.8 - 2021-10-29
### Changed
- Updated **README** and **.env_sample** [#67](https://github.com/EthTx/ethtx_ce/pull/67)
- `Web3ConnectionException` is not supported anymore. From now on, a general exception `NodeConnectionException`
  is caught for node connection errors  [#67](https://github.com/EthTx/ethtx_ce/pull/67)
- Guessed functions and events are detected using the guessed variable in the
  model [#67](https://github.com/EthTx/ethtx_ce/pull/67)


## 0.2.7 - 2021-10-14
### Changed
- Changed [EthTx](https://github.com/EthTx/ethtx) version - >=0.3.0,<
  0.4.0 [#62](https://github.com/EthTx/ethtx_ce/pull/62)
- Deleted usage of mongodb variable [#61](https://github.com/EthTx/ethtx_ce/pull/61)

### Fixed
- Fixed colored guessed events with tuple arg [#65](https://github.com/EthTx/ethtx_ce/pull/65)


## 0.2.6 - 2021-10-01
### Changed
- Changed the position of the logo [#59](https://github.com/EthTx/ethtx_ce/pull/59)


## 0.2.5 - 2021-09-30
### Fixed
- Fixed colored guessed functions with nested args [#58](https://github.com/EthTx/ethtx_ce/pull/58)


## 0.2.4 - 2021-09-29
### Added
- Added `.env_sample` file with example environment variables [#57](https://github.com/EthTx/ethtx_ce/pull/57)

### Fixed
- Fixed `make run-local` [#57](https://github.com/EthTx/ethtx_ce/pull/57)

### Changed
- Changed the docker configuration to make it easier to start [#57](https://github.com/EthTx/ethtx_ce/pull/57)
- Updated **README** [#57](https://github.com/EthTx/ethtx_ce/pull/57)


## 0.2.3 - 2021-09-23
### Added
- Color guessed functions and events [#56](https://github.com/EthTx/ethtx_ce/pull/56)


## 0.2.2 - 2021-09-20
### Fixed
- Fixed `tx hash` regexp extracting from request [#53](https://github.com/EthTx/ethtx_ce/pull/53)


## 0.2.1 - 2021-09-17
### Fixed
- Fixed `Decode now` button state [#50](https://github.com/EthTx/ethtx_ce/pull/50)


## 0.2.0 - 2021-09-14
### Added - [#44](https://github.com/EthTx/ethtx_ce/pull/44)
- Added new error page.
- Added [Token Flow](https://tokenflow.live) logo.
- Added input hash validator.

### Changed - [#44](https://github.com/EthTx/ethtx_ce/pull/44)
- Changed footer style.
- Removed **ToS** and **PP** and replaced them with `Token Flow` pages.
- Removed old tests.
- Added **Fathom** analytics tool.
- Updated links.

### Fixed - [#44](https://github.com/EthTx/ethtx_ce/pull/44)
- Fixed frontend styles.


## 0.1.10 - 2021-08-20
### Added
- Added *preload* to links.


## 0.1.9 - 2021-08-18
### Added
- Added new footer.
- Added `Rinkeby` support.

### Changed
- Changed [EthTx](https://github.com/EthTx/ethtx) version - >=0.2.0,<0.3.0.

### Fixed
- Etherscan links fixed for testnets.


## 0.1.8 - 2021-08-11
### Added
- Added `Goerli` support.

### Changed
- Changed [EthTx](https://github.com/EthTx/ethtx) version - >=0.2.0,<0.3.0.

## 0.1.7 - 2021-08-05
### Added
- Added link to PyPi.


## 0.1.6 - 2021-08-04
### Added
- Added information about the `EthTx` and `EthTx Ce` version to the frontend.

### Changed
- Removed `Pipfile.lock`

### Fixed
- Fixed application dependencies.


## 0.1.5 - 2021-08-02
### Changed
- Removed the banner that was about the new version of `ethtx_ce`.


## 0.1.4 - 2021-07-29
### Changed
- Changed semantics save functions.
- Changed [EthTx](https://github.com/EthTx/ethtx) version - 0.1.7.


## 0.1.3 - 2021-07-28
### Changed
- Changed [EthTx](https://github.com/EthTx/ethtx) version - 0.1.6.


## 0.1.2 - 2021-07-27
### Changed
- Changed [EthTx](https://github.com/EthTx/ethtx) version - 0.1.5.
- Changed app Config.
- Removed EthtxConfig defaults.


## 0.1.1 - 2021-07-26
### Fixed
- Fixed header on mobile devices.

### Changed
- Changed Development.MD note.

### Added
- Added configuration: AWS, Pipfile, pre-commit.

## 0.1.0 - 2021-07-23
### Added
- First version EthTx CE.


================================================
FILE: DEVELOPMENT.md
================================================
# Local Development

This repository contains 2 basic applications: `frontend` & `api`. It is easy to manage, and you can easily add new
local application(s).

## Basic structure

Application is based on [blueprints](https://flask.palletsprojects.com/en/2.0.x/blueprints/).

New extension requires:

- new Python Package in ![ethtx_ce](ethtx_ce/app) subdirectory.
- `create_app` function (created in new package in `init` file) which returns `Flask` object by
  calling ![app factory](ethtx_ce/app/factory.py) file.
- calling a function above in a `wsgi.py` file with assigned url prefix.

These simple steps allow you to add new extension and integrate with entire application.


================================================
FILE: Dockerfile
================================================
FROM python:3.9

WORKDIR /app/

# Upgrade pip, install pipenv
RUN pip install --upgrade pip && pip install pipenv

# Copy Pipfile* in case it doesn't exist in the repo
COPY Pipfile* /app/

COPY ./ethtx_ce/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY ./ethtx_ce/start.sh  /start.sh
RUN chmod +x /start.sh

COPY ./ethtx_ce/start-reload.sh /start-reload.sh
RUN chmod +x /start-reload.sh

COPY ./ethtx_ce/gunicorn_conf.py /gunicorn_conf.py

COPY Makefile /Makefile

RUN bash -c "pipenv install --dev --deploy"

ARG GIT_URL
ENV GIT_URL=$GIT_URL

ARG GIT_SHA
ENV GIT_SHA=$GIT_SHA

ARG CI=1

COPY ./ethtx_ce /app
ENV PYTHONPATH=/app

EXPOSE 5000

ENTRYPOINT ["/entrypoint.sh"]


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.


================================================
FILE: Makefile
================================================
help:
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

build-image:  ## Build all docker images
	docker build -t ethtx_ce .

get-git-version: ## Get git version
	./scripts/git_version_for_docker.sh

run-database:  ## Run only a local database required for local development
	docker-compose up -d mongo mongo-express

run-local:
	PYTHONPATH=./ethtx_ce FLASK_APP=ethtx_ce/app/wsgi.py FLASK_DEBUG=1 pipenv run flask run --host=0.0.0.0 --port 5555

run-prod:
	fuser -k 5000/tcp || true
	PYTHONPATH=./ethtx_ce pipenv run gunicorn --workers 4 --max-requests 4000 --timeout 600 --bind :5000 app.wsgi:app

run-docker:
	fuser -k 5000/tcp || true
	docker-compose up -d

run-test-docker:
	docker run -it ethtx_ce pipenv run python -m pytest .

test:
	PYTHONPATH=./ethtx_ce pipenv run python -m pytest ethtx_ce/tests/

test-all:
	PYTHONPATH=./ethtx_ce pipenv run python -m pytest .

setup:
	pipenv install --dev
	pipenv run pre-commit install


================================================
FILE: NOTICE
================================================
Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)

The product contains trademarks and other branding elements of Token Flow Insights SA which are
not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
the trademark and/or other branding elements.


================================================
FILE: Pipfile
================================================
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
ethtx = "==0.3.22"
python-dotenv = "*"
flask = ">=2.0.2"
werkzeug = ">=2.0.2"
gunicorn = {version = ">=20.1.0"}
flask-httpauth = ">=4.5.0"
gitpython = ">=3.1.24"
jsonpickle = ">=3.0.0"
simplejson = "*"
pydantic = "<2.0.0"

[dev-packages]
black = "*"
pytest = ">=6.2.5"
pytest-cov =">=3.0.0"
pytest-mock =">=3.6.1"
pre-commit = "*"

[requires]
python_version = "3.9"


================================================
FILE: README.md
================================================
<h1 align='center' style="border-bottom: none">
  EthTx Community Edition
</h1>
<br/>
<p align="center">
    <em>Community version of EthTx transaction decoder</em>
<br>
    <em><a href="https://ethtx.info">https://ethtx.info</a></em>
</p>
<p align="center">
<a target="_blank">
    <img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg" alt="Python">
</a>
<a target="_blank">
    <img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Black">
</a>
<a target="_blank">
    <img src="https://badgen.net/badge/Open%20Source%20%3F/Yes%21/blue?icon=github" alt="OpenSource">
</a>
<a target="_blank">
    <img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="Apache">
</a>
</p>

---

# Description

This project represents usage of [EthTx](https://github.com/ethtx/ethtx) decoding library in form of a website. If you
are looking for implementation of the said decoding functionalities, please refer
to [EthTx](https://github.com/ethtx/ethtx) repository.

# Local environment

Here is a list of steps to recreate local environment on <b>Ubuntu</b> distribution.

1. Install needed packages using `apt`:

    ```shell
    apt install docker-compose python3-pip python3-dev pipenv make
    apt install libxml2-dev libxslt1-dev gcc
    ```
2. Run:

    ```shell
    pipenv install
    ```

3. Copy `.env_sample` to `.env` and fill required field according to description

    ```
      # Proper nodes are required to run ethtx, provide connection strings for chains which will be used.
      MAINNET_NODE_URL=https://geth-erigon-node:8545
      # KOVAN_NODE_URL=
      # RINKEBY_NODE_URL=

      # EthTx supports multiple nodes, if one is unavailable, it will use others. You only need to specify them with a comma
      # Example: MAINNET_NODE_URL=https://geth-erigon-node:8545,https://geth1-erigon-node:8545


      # Etherscan API is used to get contract source code, required for decoding process
      # You can get free key here https://etherscan.io/apis
      ETHERSCAN_KEY=

      # Optional. Those represent data required for connecting to mongoDB. It's used for caching semantics
      # used in decoding process. But, it's not neccessary for running, If you don't want to use permanent
      # db or setup mongo, leave those values, mongomock package is used to simulate in-memory mongo.
      MONGO_CONNECTION_STRING=mongomock://localhost/ethtx

      # Optional. Credentials for accessing semantics editor page, available under '/semantics/<str:address>'
      ETHTX_ADMIN_USERNAME=admin
      ETHTX_ADMIN_PASSWORD=admin

      # Optional. Api key used for exposing
      API_KEY=

      # Optional. Valid values are ['production', 'staging', 'development']. Those mainly
      # dictate what options are used for flask debugging and logging
      ENV=development
    ```

4. Run
    ```shell
    PYTHONPATH=./ethtx_ce FLASK_APP=ethtx_ce/app/wsgi.py pipenv run flask run --host=0.0.0.0 --port 5000
    ```
   or
    ```shell
    make run-local
    ```
   This will setup new server on host 0.0.0.0 port 5000.
5. Now `ethtx_ce` should be accessible through link [http://localhost:5000](http://localhost:5000)

Use can also provided `docker-compose` for running this locally:

```shell
docker-compose up
```

Note, this also need proper `.env` file to function properly.

# .env file

For proper functioning, `.env` file is required containing all database and 3rd party providers configuration.
`.env_sample` file is provided in repository with example values.

Parameters `[CHAIN_ID]_NODE_URL` should hold valid urls to ethereum nodes; Parameter `ETHERSCAN_KEY` should be equal to
Etherscan API key assigned to user.

# API

The EthTx APIs are provided as a community service and without warranty, so please use what you need and no more. We
support `GET` requests.

* **Decode transaction**

  Returns decoded EthTx transaction, based on `chain_id` and transaction hash `tx_hash`

    * **URL**
      ```shell
      /api/transactions/CHAIN_ID/TX_HASH
      ```
    * **Method**
      `GET`
    * **Authorization**
        * Required:
          header: `x-api-key=[string]` **OR** query parameter: `api_key=[string]`
    * **URL Params**
        * Required: `chain_id=[string]`,`tx_hash=[string]`
    * **Example**
      ```shell
      curl --location --request GET 'http://0.0.0.0:5000/api/transactions/dsad/asd' \
      --header 'x-api-key: 05a2212d-9985-48d2-b54f-0fbc5ba28766'
      ```


* **Get Raw Semantic**

  Returns raw semantic based on `chain_id` and sender/receiver `address`

    * **URL**
      ```shell
      /api/semantics/CHAIN_ID/ADDRESS
      ```
    * **Method**
      `GET`
    * **Authorization**
        * Required:
          header: `x-api-key=[string]` **OR** query parameter: `api_key=[string]`
    * **URL Params**
        * Required:`chain_id=[string]`,`address=[string]`
    * **Example**
      ```shell
      curl --location --request GET 'http://0.0.0.0:5000/api/semantics/dsad/asd' \
      --header 'x-api-key: 05a2212d-9985-48d2-b54f-0fbc5ba28766'
      ```

* **Info**

  Returns information about the `EthTx`

    * **URL**
      ```shell
      /api/info
      ```
    * **Method**
      `GET`
    * **Authorization**
        * Required:
          header: `x-api-key=[string]` **OR** query parameter: `api_key=[string]`
    * **URL Params**
        * None
    * **Example**
      ```shell
      curl --location --request GET 'http://0.0.0.0:5000/api/info' \
      --header 'x-api-key: 05a2212d-9985-48d2-b54f-0fbc5ba28766'
      ```

================================================
FILE: docker-compose.override.yml
================================================
version: "3.6"
services:
  ethtx_ce:
    ports:
    - "5000:5000"
    build:
      context: .
      dockerfile: Dockerfile

    command: /start.sh

  mongo:
    ports:
      - "27017:27017"
  mongo-express:
    depends_on:
      - mongo
    ports:
      - "8081:8081"

================================================
FILE: docker-compose.yaml
================================================
version: "3.6"

services:
  ethtx_ce:
    image: 'ethtx_ce:${TAG-latest}'
    env_file:
      - .env
    depends_on:
      - mongo
    build:
      context: .
      dockerfile: Dockerfile

  mongo:
    image: mongo
    environment:
      - MONGO_INITDB_DATABASE=${MONGODB_DB}

  mongo-express:
    image: mongo-express
    environment:
      - ME_CONFIG_MONGODB_SERVER=mongo
      - ME_CONFIG_MONGODB_PORT=27017
      - ME_CONFIG_MONGODB_ENABLE_ADMIN=false
      - ME_CONFIG_MONGODB_AUTH_DATABASE=${MONGODB_DB}
      - ME_CONFIG_BASICAUTH_USERNAME=${MONGOEXPRESS_LOGIN}
      - ME_CONFIG_BASICAUTH_PASSWORD=${MONGOEXPRESS_PASSWORD}
    depends_on:
      - mongo


================================================
FILE: ethtx_ce/.flake8
================================================
[flake8]
max-line-length = 130
exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache
extend-ignore =
    # See https://github.com/PyCQA/pycodestyle/issues/373
    E203, F401, F403, F405


================================================
FILE: ethtx_ce/.gitignore
================================================
__pycache__
app.egg-info


================================================
FILE: ethtx_ce/app/__init__.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.


================================================
FILE: ethtx_ce/app/api/__init__.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from functools import wraps
from typing import Dict, Type, Callable, Union, Optional

from ethtx import EthTx
from flask import Blueprint
from flask import Flask

from .. import factory
from .decorators import auth_required
from ..helpers import read_ethtx_versions


def create_app(
    engine: EthTx, settings_override: Optional[Union[Dict, Type]] = None
) -> Flask:
    """Returns API application instance."""

    app = factory.create_app(__name__, __path__, settings_override)
    app.name = "ethtx_ce/api"

    app.ethtx = engine  # init ethtx engine
    read_ethtx_versions(app)

    return app


def api_route(bp: Blueprint, *args, **kwargs):
    kwargs.setdefault("strict_slashes", False)

    def decorator(f: Callable):
        @bp.route(*args, **kwargs)
        @auth_required
        @wraps(f)
        def wrapper(*args, **kwargs):
            sc = 200
            rv = f(*args, **kwargs)
            if isinstance(rv, tuple):
                sc = rv[1]
                rv = rv[0]
            return rv, sc

        f.__name__ = str(id(f)) + f.__name__
        return f

    return decorator


# avoid circular
from .endpoints import *
from .exceptions import exceptions_bp


================================================
FILE: ethtx_ce/app/api/decorators.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import logging
from functools import wraps
from typing import Callable, Optional

import jsonpickle
from flask import request, current_app, jsonify

from ..exceptions import (
    AuthorizationError,
    PayloadTooLarge,
    UnexpectedError,
    InternalError,
)
from .utils import enable_direct, delete_bstrings

log = logging.getLogger(__name__)

jsonpickle.set_decoder_options('simplejson', use_decimal=True)


def auth_required(func: Callable):
    """api key  verification."""

    @wraps(func)
    def check_auth(**kwargs):
        api_key = request.headers.get("x-api-key") or request.args.get("api_key")
        if api_key != current_app.config.get("API_KEY"):
            raise AuthorizationError(api_key)

        return func(**kwargs)

    return check_auth


def response(status: Optional[int] = 200):
    """
    Return response with:
    :param status: response status code, default: `200`
    """

    def _response(f: Callable):
        @wraps(f)
        def wrapped(*args, **kwargs):
            func = f(*args, **kwargs)

            try:
                data = jsonify(
                    delete_bstrings(
                        jsonpickle.decode(
                            jsonpickle.encode(func, make_refs=False, unpicklable=False, use_decimal=True)
                        )
                    )
                )
            except TypeError as e:
                log.critical("Response cannot be serialized. %s", e)
                raise InternalError()
            except Exception as e:
                log.exception(e)
                raise UnexpectedError()

            return data, status

        return wrapped

    return _response


@enable_direct
def limit_content_length(max_length: Optional[int] = None):
    """
    Limit content length. If not given:
    The priority has app MAX_CONTENT_LENGTH value.
    """

    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            cl = request.content_length
            app_max_length = current_app.config.get("MAX_CONTENT_LENGTH")
            max_content_length = max_length if max_length else app_max_length

            if cl is not None and cl > max_content_length:
                raise PayloadTooLarge(
                    content_length=cl, max_content_length=max_content_length
                )
            return f(*args, **kwargs)

        return wrapper

    return decorator


================================================
FILE: ethtx_ce/app/api/endpoints/__init__.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from .info import info_bp
from .semantics import semantics_bp
from .transactions import transactions_bp


================================================
FILE: ethtx_ce/app/api/endpoints/info.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from flask import Blueprint, current_app

from .. import api_route
from ..decorators import response
from ...helpers import get_latest_ethtx_version

info_bp = Blueprint("api_info", __name__)


@api_route(info_bp, "/info")
@response(200)
def read_info():
    """Get info."""
    ethtx_version = current_app.config["ethtx_version"]
    latest_ethtx_version = get_latest_ethtx_version()

    ethtx_ce_version = current_app.config["ethtx_ce_version"]

    return {
        "ethtx": {
            "version": ethtx_version,
            "is_latest": ethtx_version == latest_ethtx_version,
        },
        "ethtx_ce": {
            "version": ethtx_ce_version,
        },
    }


================================================
FILE: ethtx_ce/app/api/endpoints/semantics.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from typing import Optional

from flask import Blueprint, current_app

from .. import api_route
from ..decorators import response

semantics_bp = Blueprint("api_semantics", __name__)


@api_route(semantics_bp, "/semantics/<string:address>")
@api_route(semantics_bp, "/semantics/<string:chain_id>/<string:address>")
@response(200)
def read_raw_semantic(address: str, chain_id: Optional[str] = None):
    """Get raw semantic."""
    raw_semantics = current_app.ethtx.semantics.get_semantics(
        chain_id=chain_id, address=address
    )
    return raw_semantics.dict()


================================================
FILE: ethtx_ce/app/api/endpoints/transactions.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import logging
from typing import Optional

from flask import Blueprint, current_app

from .. import api_route
from ..decorators import response

log = logging.getLogger(__name__)
transactions_bp = Blueprint("api_transactions", __name__)


@api_route(transactions_bp, "/transactions/<string:tx_hash>")
@api_route(transactions_bp, "/transactions/<string:chain_id>/<string:tx_hash>")
@response(200)
def read_decoded_transaction(tx_hash: str, chain_id: Optional[str] = None):
    """Decode transaction."""
    tx_hash = tx_hash if tx_hash.startswith("0x") else "0x" + tx_hash

    chain_id = chain_id or current_app.ethtx.default_chain
    decoded_transaction = current_app.ethtx.decoders.decode_transaction(
        chain_id=chain_id, tx_hash=tx_hash
    )
    decoded_transaction.metadata.timestamp = (
        decoded_transaction.metadata.timestamp.strftime("%Y-%m-%d %H:%M:%S")
    )
    return decoded_transaction.dict()


================================================
FILE: ethtx_ce/app/api/exceptions.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import datetime
import logging
from dataclasses import dataclass
from http.client import responses
from typing import Tuple, TypeVar

from ethtx import exceptions as ethtx_exceptions
from flask import Blueprint, request
from web3.exceptions import TransactionNotFound
from werkzeug.exceptions import HTTPException

from .utils import as_dict
from ..exceptions import *

log = logging.getLogger(__name__)

exceptions_bp = Blueprint("exceptions", __name__)


@as_dict
@dataclass
class BaseRequestException:
    """Base Request Exception"""

    status: int
    error: str
    path: str
    message: str = ""
    timestamp: datetime.datetime.utcnow = None

    def __post_init__(self):
        """Post init values."""
        self.error = str(self.error)
        self.message = responses[self.status]
        self.timestamp = datetime.datetime.utcnow()


BaseErrorType = TypeVar("BaseErrorType", bound=Tuple[BaseRequestException, int])


@exceptions_bp.app_errorhandler(HTTPException)
def handle_all_http_exceptions(error: HTTPException) -> BaseErrorType:
    """All HTTP Exceptions handler."""
    return BaseRequestException(error.code, error.description, request.path), error.code


@exceptions_bp.app_errorhandler(ethtx_exceptions.NodeConnectionException)
def node_connection_error(error) -> BaseErrorType:
    """EthTx - Node connection error."""
    return BaseRequestException(500, error, request.path), 500


@exceptions_bp.app_errorhandler(ethtx_exceptions.ProcessingException)
def processing_error(error) -> BaseErrorType:
    """EthTx - Processing error."""
    return BaseRequestException(500, error, request.path), 500


@exceptions_bp.app_errorhandler(ethtx_exceptions.InvalidTransactionHash)
def invalid_transaction_hash(error) -> BaseErrorType:
    """EthTx - Invalid transaction hash."""
    return BaseRequestException(400, error, request.path), 400


@exceptions_bp.app_errorhandler(TransactionNotFound)
def transaction_not_found(error) -> BaseErrorType:
    """Could not find transaction."""
    return BaseRequestException(404, error, request.path), 404


@exceptions_bp.app_errorhandler(AuthorizationError)
def authorization_error(error) -> BaseErrorType:
    """Unauthorized request."""
    return BaseRequestException(401, error, request.path), 401


@exceptions_bp.app_errorhandler(MalformedRequest)
def malformed_request(error) -> BaseErrorType:
    """Wrong request."""
    return BaseRequestException(400, error, request.path), 400


@exceptions_bp.app_errorhandler(PayloadTooLarge)
def payload_too_large(error) -> BaseErrorType:
    """Payload is too large."""
    return BaseRequestException(413, error, request.path), 413


@exceptions_bp.app_errorhandler(ResourceLockedError)
def resource_locked_error(error) -> BaseErrorType:
    """Resource is locked."""
    return BaseRequestException(423, error, request.path), 423


@exceptions_bp.app_errorhandler(Exception)
def unexpected_error(error) -> BaseErrorType:
    """Unexpected error."""
    log.exception(str(error))

    return BaseRequestException(500, str(UnexpectedError()), request.path), 500


================================================
FILE: ethtx_ce/app/api/utils.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from dataclasses import asdict
from functools import wraps
from typing import Dict
from decimal import Decimal


def enable_direct(decorator):
    """Decorator direct helper."""

    @wraps(decorator)
    def wrapper(*args, **kwargs):
        f = args[0]
        if callable(f):
            return decorator()(f)  # pass the function to be decorated
        else:
            return decorator(*args, **kwargs)  # pass the specified params

    return wrapper


def as_dict(cls):
    """Return object as dict."""

    def wrapper(*args, **kwargs) -> Dict:
        instance = cls(*args, **kwargs)
        return asdict(instance)

    return wrapper


def delete_bstrings(obj):
    primitive = (str, bool, float, type(None))

    if isinstance(obj, primitive):
        return obj
    elif isinstance(obj, int):
        return str(obj)
    elif isinstance(obj, Decimal):
        if obj == obj.to_integral_value():
            return str(obj)
        else:
            obj
    elif type(obj) == bytes:
        return obj.decode()
    elif type(obj) == list:
        for index, value in enumerate(obj):
            obj[index] = delete_bstrings(value)
    elif type(obj) == dict:
        for index, value in obj.items():
            obj[index] = delete_bstrings(value)
    else:
        raise Exception("Unknown type:" + str(type(obj)))

    return obj


================================================
FILE: ethtx_ce/app/config.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import os

from dotenv import load_dotenv, find_dotenv

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
load_dotenv(find_dotenv(filename="../../.env"))


class Config:
    """Base Config."""

    LOGGING_CONFIG = os.environ.get(
        "LOGGING_CONFIG", os.path.join(BASE_DIR, "../log_cfg.json")
    )
    LOGGING_LOG_PATH = os.environ.get(
        "LOGGING_CONFIG", os.path.join(BASE_DIR, "../../tmp")
    )

    API_KEY = os.getenv("API_KEY", "")
    MAX_CONTENT_LENGTH = 10 * 1024 * 1024

    ETHTX_ADMIN_USERNAME = os.getenv("ETHTX_ADMIN_USERNAME")
    ETHTX_ADMIN_PASSWORD = os.getenv("ETHTX_ADMIN_PASSWORD")


class ProductionConfig(Config):
    """Production Config."""

    ENV = "production"
    FLASK_DEBUG = False
    TESTING = False
    PROPAGATE_EXCEPTIONS = True


class StagingConfig(Config):
    """Staging Config."""

    ENV = "staging"
    FLASK_DEBUG = True
    TESTING = False
    PROPAGATE_EXCEPTIONS = True


class DevelopmentConfig(Config):
    """Development Config."""

    ENV = "development"
    FLASK_DEBUG = True
    TESTING = True
    PROPAGATE_EXCEPTIONS = True


================================================
FILE: ethtx_ce/app/exceptions.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from typing import Optional, Union

__all__ = [
    "AuthorizationError",
    "MalformedRequest",
    "PayloadTooLarge",
    "MethodNotAllowed",
    "ResourceLockedError",
    "InternalError",
    "UnexpectedError",
    "FactoryAppException",
    "EmptyResponseError",
]


class FactoryAppException(Exception):
    """Basic Factory App exception."""


class UnexpectedError(Exception):
    """Internal Server Error."""

    def __init__(self):
        super().__init__("Unexpected Error")


class RequestError(Exception):
    """Request Error - basic class."""


class AuthorizationError(RequestError):
    """Unauthorized requests."""

    def __init__(self, msg: Optional[str] = None):
        super().__init__(
            f"The provided api key is invalid : {msg}."
            if msg
            else "Api key is missing."
        )


class MalformedRequest(RequestError):
    """Malformed Request Error."""

    def __init__(self, msg):
        super().__init__(msg)


class PayloadTooLarge(RequestError):
    """Payload too large Error."""

    def __init__(self, content_length: Union[float, int], max_content_length: int):
        super().__init__(
            f"The request is larger than the server is willing or able to process."
            f" Request length: {content_length}, but allowed is: {max_content_length}."
        )


class MethodNotAllowed(RequestError):
    """Method not allowed."""

    def __init__(self, method: str):
        super().__init__(f"Method: {method} not allowed.")


class ResourceLockedError(RequestError):
    """Resource is locked."""

    def __init__(self):
        super().__init__("The resource that is being accessed is locked.")


class EmptyResponseError(RequestError):
    """Response is empty."""

    def __init__(self, msg: str):
        super().__init__(msg)


class InternalError(RequestError):
    """Validation Error"""

    def __init__(self):
        super().__init__(
            "The request was well-formed but server could not properly decode transaction."
        )


================================================
FILE: ethtx_ce/app/factory.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import os
from typing import Optional, Dict

from flask import Flask

from .config import Config
from .helpers import class_import, register_blueprints
from .logger import setup_logging

env = os.getenv("ENV", "development").capitalize()
config_class = f"app.config.{env}Config"
config: Config = class_import(config_class)


def create_app(
    package_name: str,
    package_path: str,
    settings_override: Optional[Dict] = None,
    **app_kwargs,
) -> Flask:
    """
    Returns a :class:`Flask` application instance
    :param package_name: application package name
    :param package_path: application package path
    :param settings_override: a dictionary of settings to override
    :param app_kwargs: additional app kwargs
    """
    app = Flask(__name__, instance_relative_config=True, **app_kwargs)

    app.config.from_object(config)
    setup_logging(app=app)
    app.config.from_object(settings_override)

    register_blueprints(app, package_name, package_path)

    return app


================================================
FILE: ethtx_ce/app/frontend/__init__.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from functools import wraps
from typing import Callable, Dict, Optional, Union, Type

from ethtx import EthTx
from flask import Blueprint, Flask


from .. import factory
from ..helpers import read_ethtx_versions


def create_app(
    engine: EthTx, settings_override: Optional[Union[Dict, Type]] = None
) -> Flask:
    """Returns Frontend app instance."""
    app = factory.create_app(
        __name__,
        __path__,
        settings_override,
        template_folder="frontend/templates",
        static_folder="frontend/static",
    )
    app.name = "ethtx_ce/frontend"

    app.jinja_env.trim_blocks = True
    app.jinja_env.lstrip_blocks = True

    app.ethtx = engine  # init ethtx engine
    read_ethtx_versions(app)

    return app


def frontend_route(bp: Blueprint, *args, **kwargs):
    """Route in blueprint context."""

    def decorator(f: Callable):
        @bp.route(*args, **kwargs)
        @wraps(f)
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs)

        f.__name__ = str(id(f)) + f.__name__
        return f

    return decorator


================================================
FILE: ethtx_ce/app/frontend/deps.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import json
import logging
import re
import time
from secrets import compare_digest
from typing import Optional

import requests
from flask import request
from flask_httpauth import HTTPBasicAuth

from ..config import Config

log = logging.getLogger(__name__)

auth = HTTPBasicAuth()

eth_price: Optional[float] = None
eth_price_update: Optional[float] = None


@auth.verify_password
def verify_password(username: str, password: str) -> bool:
    """Verify user, return bool."""
    return username == Config.ETHTX_ADMIN_USERNAME and compare_digest(
        password, Config.ETHTX_ADMIN_PASSWORD
    )


def get_eth_price() -> Optional[float]:
    """
    Get current ETH price from coinbase.com
    Cache price for 60 seconds.
    """
    global eth_price, eth_price_update

    current_time = time.time()
    if (
        eth_price is None
        or eth_price_update is None
        or (current_time - eth_price_update) > 60
    ):
        response = requests.get(
            "https://api.coinbase.com/v2/prices/ETH-USD/buy", timeout=2
        )
        if response.status_code == 200:
            eth_price = float(json.loads(response.content)["data"]["amount"])
            eth_price_update = time.time()

    return eth_price


def extract_tx_hash_from_req() -> str:
    """Extract tx hash from request url."""
    hash_match = re.findall(r"(0x)?([A-Fa-f0-9]{64})", request.url)

    return (
        f"{hash_match[0][0]}{hash_match[0][1]}"
        if hash_match and len(hash_match[0]) == 2
        else ""
    )


================================================
FILE: ethtx_ce/app/frontend/exceptions.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import logging
from functools import wraps
from typing import Callable, Optional

from ethtx import exceptions as ethtx_exceptions
from flask import Blueprint, render_template
from web3.exceptions import TransactionNotFound
from werkzeug.exceptions import HTTPException

from .deps import extract_tx_hash_from_req
from ..exceptions import *

log = logging.getLogger(__name__)

exceptions_bp = Blueprint("exceptions", __name__)


def render_error_page(status: Optional[int] = 500):
    """Render error page."""

    def _render_error_page(f: Callable):
        @wraps(f)
        def wrapper(*args, **kwargs):
            error = f(*args, **kwargs)
            status_code = status
            if isinstance(error, HTTPException):
                error, status_code = error.description, error.code
            return (
                render_template(
                    "exception.html",
                    status_code=status_code,
                    error=error,
                    tx_hash=extract_tx_hash_from_req(),
                ),
                status_code,
            )

        return wrapper

    return _render_error_page


@exceptions_bp.app_errorhandler(HTTPException)
@render_error_page()
def handle_all_http_exceptions(error: HTTPException) -> HTTPException:
    """All HTTP Exceptions handler."""
    return error


@exceptions_bp.app_errorhandler(ethtx_exceptions.NodeConnectionException)
@render_error_page(500)
def node_connection_error(error) -> str:
    """EthTx - Node connection error."""
    return error


@exceptions_bp.app_errorhandler(ethtx_exceptions.ProcessingException)
@render_error_page(500)
def processing_error(error) -> str:
    """EthTx - Processing error."""
    return error


@exceptions_bp.app_errorhandler(ethtx_exceptions.InvalidTransactionHash)
@render_error_page(400)
def invalid_transaction_hash(error) -> str:
    """EthTx - Invalid transaction hash."""
    return error


@exceptions_bp.app_errorhandler(TransactionNotFound)
@render_error_page(404)
def transaction_not_found(error) -> str:
    """Could not find transaction."""
    return error


@exceptions_bp.app_errorhandler(AuthorizationError)
@render_error_page(401)
def authorization_error(error) -> str:
    """Unauthorized request."""
    return error


@exceptions_bp.app_errorhandler(MalformedRequest)
@render_error_page(400)
def malformed_request(error) -> str:
    """Wrong request."""
    return error


@exceptions_bp.app_errorhandler(PayloadTooLarge)
@render_error_page(413)
def payload_too_large(error) -> str:
    """Payload is too large."""
    return error


@exceptions_bp.app_errorhandler(ResourceLockedError)
@render_error_page(423)
def resource_locked_error(error) -> str:
    """Resource is locked."""
    return error


@exceptions_bp.app_errorhandler(EmptyResponseError)
@render_error_page(404)
def empty_response(error) -> str:
    """Response is empty."""
    return error


@exceptions_bp.app_errorhandler(Exception)
@render_error_page(500)
def unexpected_error(error) -> str:
    """Unexpected error."""
    log.exception(str(error))

    return str(UnexpectedError())


================================================
FILE: ethtx_ce/app/frontend/semantics.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from __future__ import annotations

import json
import logging
from typing import Optional, List, Dict

from ethtx import EthTx
from ethtx.models.semantics_model import (
    AddressSemantics,
    ContractSemantics,
    ERC20Semantics,
)
from ethtx.models.semantics_model import (
    EventSemantics,
    FunctionSemantics,
    TransformationSemantics,
    ParameterSemantics,
)
from flask import Blueprint, render_template, current_app, request, jsonify
from web3 import Web3

from . import frontend_route
from .deps import auth
from ..exceptions import EmptyResponseError

bp = Blueprint("semantics", __name__)

log = logging.getLogger(__name__)


@frontend_route(bp, "/semantics/<string:address>/")
@frontend_route(bp, "/semantics/<string:chain_id>/<string:address>/")
@auth.login_required
def semantics(address: str, chain_id: Optional[str] = None) -> show_semantics_page:
    raw_semantics = current_app.ethtx.semantics.get_semantics(
        chain_id=chain_id or current_app.ethtx._default_chain, address=address
    )

    return show_semantics_page(raw_semantics)


@frontend_route(bp, "/reload", methods=["POST"])
@auth.login_required
def reload_semantics():
    """Reload raw semantic."""
    data = json.loads(request.data)

    ethtx: EthTx = current_app.ethtx
    ethtx.semantics.database._addresses.delete_one({"address": data["address"]})
    ethtx.semantics.get_semantics.cache_clear()
    ethtx.semantics.get_semantics(
        data["chain_id"] if data.get("chain_id") else current_app.ethtx._default_chain,
        data["address"],
    )

    return "ok"


@frontend_route(bp, "/save", methods=["POST"])
@auth.login_required
def semantics_save():
    data = json.loads(request.data)
    return _semantics_save(data)


@frontend_route(bp, "/poke", methods=["POST"])
@auth.login_required
def poke_abi():
    data = json.loads(request.data)
    return _poke_abi(data)


def show_semantics_page(data: AddressSemantics) -> render_template:
    if data:

        data_dict = data.dict()

        address = data.address
        chain_id = data.chain_id
        name = data.name or address

        if data.is_contract:
            events = data_dict["contract"]["events"] or {}
            functions = data_dict["contract"]["functions"] or {}
            transformations = data_dict["contract"]["transformations"] or {}
            code_hash = data.contract.code_hash
            contract_name = data.contract.name
        else:
            events = {}
            functions = {}
            transformations = {}
            code_hash = "EOA"
            contract_name = address

        standard = data.standard
        # ToDo: make it more universal

        if standard == "ERC20":
            standard_info = data_dict["erc20"] or {}
        elif standard == "ERC721":
            standard_info = {}
        else:
            standard_info = {}

        metadata = dict(
            label=name,
            chain=chain_id,
            contract=dict(
                name=contract_name,
                code_hash=code_hash,
                standard=dict(name=standard, data=standard_info),
            ),
        )

        return (
            render_template(
                "semantics.html",
                address=address,
                events=events,
                functions=functions,
                transformations=transformations,
                metadata=metadata,
            ),
            200,
        )

    raise EmptyResponseError(
        "The semantics are empty. It probably means that the given address "
        "has not been decoded before or address is incorrect."
    )


def _parameters_semantics(parameters: List[Dict]) -> List[ParameterSemantics]:
    parameters_semantics_list = []
    if parameters:
        for parameter in parameters:
            parameters_semantics_list.append(
                ParameterSemantics(
                    parameter_name=parameter.get("parameter_name"),
                    parameter_type=parameter.get("parameter_type"),
                    components=parameter.get("components", []),
                    indexed=parameter.get("indexed", False),
                    dynamic=parameter.get("dynamic", False),
                )
            )

    return parameters_semantics_list


def _semantics_save(data):
    try:
        address = data.get("address")
        metadata = data.get("metadata")
        events = data.get("events")
        functions = data.get("functions")
        transformations = data.get("transformations")
        standard_name = None
        erc20_semantics = None

        if metadata.get("contract"):

            events_semantics = dict()
            functions_semantics = dict()
            transformations_semantics = dict()

            for event in events.values():
                events_semantics[event.get("signature")] = EventSemantics(
                    signature=event.get("signature"),
                    anonymous=event.get("anonymous"),
                    name=event.get("name"),
                    parameters=_parameters_semantics(event.get("parameters")),
                )

            for function in functions.values():
                functions_semantics[function.get("signature")] = FunctionSemantics(
                    signature=function.get("signature"),
                    name=function.get("name"),
                    inputs=_parameters_semantics(function.get("inputs")),
                    outputs=_parameters_semantics(function.get("outputs")),
                )

            for signature, transformation in transformations.items():
                transformations_semantics[signature] = dict()
                for parameter_name, parameter_transformation in transformation:
                    transformations_semantics[signature][
                        parameter_name
                    ] = TransformationSemantics(
                        transformed_name=parameter_transformation.get(
                            "transformed_name"
                        ),
                        transformed_type=parameter_transformation.get(
                            "transformed_type"
                        ),
                        transformation=parameter_transformation.get("transformation"),
                    )

            standard_name = metadata["contract"]["standard"]["name"]
            if standard_name == "ERC20":
                erc20_data = metadata["contract"]["standard"].get("data")
                if erc20_data:
                    erc20_semantics = ERC20Semantics(
                        name=erc20_data.get("name"),
                        symbol=erc20_data.get("symbol"),
                        decimals=erc20_data.get("decimals"),
                    )

            contract_semantics = ContractSemantics(
                code_hash=metadata["contract"].get("code_hash"),
                name=metadata["contract"].get("name"),
                events=events_semantics,
                functions=functions_semantics,
                transformations=transformations_semantics,
            )

        else:
            contract_semantics = None

        address_semantics = AddressSemantics(
            chain_id=metadata.get("chain"),
            address=address,
            name=metadata.get("label"),
            is_contract=contract_semantics is not None,
            contract=contract_semantics,
            standard=standard_name,
            erc20=erc20_semantics,
        )

        current_app.ethtx.semantics.update_semantics(semantics=address_semantics)
        current_app.ethtx.semantics.get_semantics.cache_clear()
        current_app.ethtx.semantics.get_event_abi.cache_clear()
        current_app.ethtx.semantics.get_anonymous_event_abi.cache_clear()
        current_app.ethtx.semantics.get_transformations.cache_clear()
        current_app.ethtx.semantics.get_function_abi.cache_clear()
        current_app.ethtx.semantics.get_constructor_abi.cache_clear()
        current_app.ethtx.semantics.check_is_contract.cache_clear()
        current_app.ethtx.semantics.get_standard.cache_clear()

        result = "ok"

    except Exception as e:
        logging.exception("Semantics save error: %s" % e)
        result = "error"

    return jsonify(result=result)


def _poke_abi(data):
    # helper function decoding contract ABI
    def _parse_abi(json_abi):

        # helper function to recursively parse parameters
        def _parse_parameters(parameters):

            comp_canonical = "("
            comp_inputs = list()

            for i, parameter in enumerate(parameters):
                argument = dict(
                    parameter_name=parameter["name"], parameter_type=parameter["type"]
                )

                if parameter["type"][:5] == "tuple":
                    sub_canonical, sub_components = _parse_parameters(
                        parameter["components"]
                    )
                    comp_canonical += sub_canonical + parameter["type"][5:]
                    argument["components"] = sub_components
                else:
                    comp_canonical += parameter["type"]
                    sub_components = []

                if i < len(parameters) - 1:
                    comp_canonical += ","

                if (
                    parameter["type"] in ("string", "bytes")
                    or parameter["type"][-2:] == "[]"
                ):
                    argument["dynamic"] = True
                elif parameter["type"] == "tuple":
                    argument["dynamic"] = any(c["dynamic"] for c in sub_components)
                else:
                    argument["dynamic"] = False

                if "indexed" in parameter:
                    argument["indexed"] = parameter["indexed"]

                comp_inputs.append(argument)

            comp_canonical += ")"

            return comp_canonical, comp_inputs

        functions = dict()
        events = dict()

        for item in json_abi:
            if "type" in item:

                # parse contract functions
                if item["type"] == "constructor":
                    _, inputs = _parse_parameters(item["inputs"])
                    functions["constructor"] = dict(
                        signature="constructor",
                        name="constructor",
                        inputs=inputs,
                        outputs=[],
                    )
                elif item["type"] == "fallback":
                    functions["fallback"] = {}

                elif item["type"] == "function":
                    canonical, inputs = _parse_parameters(item["inputs"])
                    canonical = item["name"] + canonical
                    function_hash = Web3.sha3(text=canonical).hex()
                    signature = function_hash[0:10]

                    _, outputs = _parse_parameters(item["outputs"])

                    functions[signature] = dict(
                        signature=signature,
                        name=item["name"],
                        inputs=inputs,
                        outputs=outputs,
                    )

                # parse contract events
                elif item["type"] == "event":
                    canonical, parameters = _parse_parameters(item["inputs"])
                    canonical = item["name"] + canonical
                    event_hash = Web3.sha3(text=canonical).hex()
                    signature = event_hash

                    events[signature] = dict(
                        signature=signature,
                        name=item["name"],
                        anonymous=item["anonymous"],
                        parameters=parameters,
                    )

        return functions, events

    try:

        address = data["address"]
        chash = data["chash"]
        network = data["network"]
        name = data["name"]
        standard = json.loads(data["standard"])
        abi = json.loads(data["abi"])

        if abi and abi != []:

            is_contract = True
            functions, events = _parse_abi(abi)

            events_semantics = dict()
            for event in events.values():
                events_semantics[event.get("signature")] = EventSemantics(
                    signature=event.get("signature"),
                    anonymous=event.get("anonymous"),
                    name=event.get("name"),
                    parameters=_parameters_semantics(event.get("parameters")),
                )

            functions_semantics = dict()
            for function in functions.values():
                functions_semantics[function.get("signature")] = FunctionSemantics(
                    signature=function.get("signature"),
                    name=function.get("name"),
                    inputs=_parameters_semantics(function.get("inputs")),
                    outputs=_parameters_semantics(function.get("outputs")),
                )

            contract_semantics = ContractSemantics(
                code_hash=chash,
                name=name,
                events=events_semantics,
                functions=functions_semantics,
                transformations=dict(),
            )

        else:

            is_contract = False
            contract_semantics = ContractSemantics(
                code_hash="0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
                name="EOA",
                events=dict(),
                functions=dict(),
                transformations=dict(),
            )

        address_semantics = AddressSemantics(
            chain_id=network,
            address=address,
            name=name,
            is_contract=is_contract,
            contract=contract_semantics,
            standard=standard.get("name"),
            erc20=ERC20Semantics(
                name=standard["data"].get("name"),
                symbol=standard["data"].get("symbol"),
                decimals=standard["data"].get("decimals"),
            )
            if standard.get("name") == "ERC20"
            else None,
        )

        current_app.ethtx.semantics.update_semantics(semantics=address_semantics)
        current_app.ethtx.semantics.get_semantics.cache_clear()
        current_app.ethtx.semantics.get_event_abi.cache_clear()
        current_app.ethtx.semantics.get_anonymous_event_abi.cache_clear()
        current_app.ethtx.semantics.get_transformations.cache_clear()
        current_app.ethtx.semantics.get_function_abi.cache_clear()
        current_app.ethtx.semantics.get_constructor_abi.cache_clear()
        current_app.ethtx.semantics.check_is_contract.cache_clear()
        current_app.ethtx.semantics.get_standard.cache_clear()

        logging.info(f"ABI for {address} decoded.")

        result = "ok"

    except Exception as e:
        logging.exception("ABI retrieval error: %s" % e)
        result = "error"

    return jsonify(result=result)


================================================
FILE: ethtx_ce/app/frontend/static/ethtx.new.css
================================================
/*Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)*/
/*Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded*/
/*in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)*/

/*Licensed under the Apache License, Version 2.0 (the "License");*/
/*you may not use this file except in compliance with the License.*/
/*You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0*/

/*Unless required by applicable law or agreed to in writing, software distributed under the License is distributed*/
/*on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*/
/*See the License for the specific language governing permissions and limitations under the License.*/

/*The product contains trademarks and other branding elements of Token Flow Insights SA which are*/
/*not licensed under the Apache 2.0 license. When using or reproducing the code, please remove*/
/*the trademark and/or other branding elements.*/


body {
    font-family: monospace;
    margin: 5px;
    background-color: whitesmoke;
    width: 100%;
}

* {
    margin: 0;
    padding: 0;
}

h3 {
    margin: 11px;
}

p {
    padding-bottom: 50px;
}

.my_p {
    margin: 2px 2px 2px 11px;
    border: 1px #ccc solid;
    border-radius: 5px;
    padding: 2px 5px;
    white-space: nowrap;
    display: inline-block;
}

a:link,
a:visited {
    color: black;
    text-decoration: none;
}

.delegate a:link,
.delegate a:visited {
    color: darkorange;
    text-decoration: none;
}

.table {
    margin-left: 11px;
    border: solid 1px #bbbbbb;
    border-collapse: collapse;
    border-spacing: 0;
}

.table thead th {
    background-color: #ddd;
    border: solid 1px #bbbbbb;
    color: #444444;
    padding: 3px 10px 3px 10px;
    text-align: left;
    text-shadow: 1px 1px 1px #fff;
}

.table tbody td {
    border: solid 1px #bbbbbb;
    color: #333;
    padding: 3px 10px 3px 10px;
    text-shadow: 1px 1px 1px #fff;
    white-space: nowrap;
}

.content {
    width: 500px;
    margin: auto;
}

.transaction-info {
    margin: 5px 0 4px 13px;
}

.transaction-hash {
    color: darkgreen;
}

.events tbody td {
    border: 1px #ccc solid;
    padding: 5px 10px 2px 10px;
}

.calls table {
    border: transparent;
}

.calls tbody td {
    border: transparent;
    padding: 5px 10px 2px 10px;
}

.l2txs table {
    border: transparent;
}

.l2txs tbody td {
    border: transparent;
    padding: 5px 10px 2px 10px;
}

#tree .ui-fancytree ul {
    padding-left: 20px;
}

#tree .ui-fancytree li {
    list-style-type: none;
    margin: 5px;
    position: relative;
}

#tree .ui-fancytree li::before {
    content: "";
    position: absolute;
    top: -7px;
    left: 2px;
    border-left: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
    border-radius: 0 0 0 0;
    width: 20px;
    height: 15px;
}

#tree .ui-fancytree li::after {
    position: absolute;
    content: "";
    top: 8px;
    left: 2px;
    border-left: 1px solid #ccc;
    border-top: 1px solid #ccc;
    border-radius: 0 0 0 0;
    width: 20px;
    height: 100%;
}

#tree .ui-fancytree li:last-child::after {
    display: none;
}

#tree .ui-fancytree li:last-child:before {
    border-radius: 0 0 0 5px;
}

#tree ul.ui-fancytree > li:first-child::before {
    display: none;
}

#tree ul.ui-fancytree > li:first-child::after {
    border-radius: 5px 0 0 0;
}

#tree .ui-fancytree li p {
    border: 1px #ccc solid;
    border-radius: 5px;
    padding: 2px 5px;
    white-space: nowrap;
    display: inline-block;
}

#tree .ui-fancytree li p:hover,
#tree .ui-fancytree li p:hover + ul li p,
#tree .ui-fancytree li p:focus,
#tree .ui-fancytree li p:focus + ul li p {
    background: #eee;
    color: #000;
    border: 1px solid #aaa;
}

#tree .ui-fancytree li p:hover + ul li::after,
#tree .ui-fancytree li p:focus + ul li::after,
#tree .ui-fancytree li p:hover + ul li::before,
#tree .ui-fancytree li p:focus + ul li::before,
#tree .ui-fancytree li p:hover + ul::before,
#tree .ui-fancytree li p:focus + ul::before,
#tree .ui-fancytree li p:hover + ul ul::before,
#tree .ui-fancytree li p:focus + ul ul::before {
    border-color: #999;
}

#tree .ui-fancytree .fancytree-icon {
    display: none;
}

#tree .fancytree-plain.fancytree-container.fancytree-treefocus span.fancytree-focused span.fancytree-title {
    border-color: transparent;
}

#tree .fancytree-plain span.fancytree-node span.fancytree-title {
    background-color: transparent;
    border-color: transparent;
}

#tree ul.fancytree-container {
    background-color: transparent;
    border: none;
    outline: none;
}

#tree .fancytree-expander {
    position: relative;
    z-index: 2;
    top: -2px;
    left: -5px;
}

#tree * {
    font-family: monospace;
}

#tx_tree .ui-fancytree ul {
    padding-left: 20px;
}

#tx_tree .ui-fancytree li {
    list-style-type: none;
    margin: 5px;
    position: relative;
}

#tx_tree .ui-fancytree li::before {
    content: "";
    position: absolute;
    top: -7px;
    left: 2px;
    border-left: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
    border-radius: 0 0 0 0;
    width: 20px;
    height: 15px;
}

#tx_tree .ui-fancytree li::after {
    position: absolute;
    content: "";
    top: 8px;
    left: 2px;
    border-left: 1px solid #ccc;
    border-top: 1px solid #ccc;
    border-radius: 0 0 0 0;
    width: 20px;
    height: 100%;
}

#tx_tree .ui-fancytree li:last-child::after {
    display: none;
}

#tx_tree .ui-fancytree li:last-child:before {
    border-radius: 0 0 0 5px;
}

#tx_tree ul.ui-fancytree > li:first-child::before {
    display: none;
}

#tx_tree ul.ui-fancytree > li:first-child::after {
    border-radius: 5px 0 0 0;
}

#tx_tree .ui-fancytree li p {
    border: 1px #ccc solid;
    border-radius: 5px;
    padding: 2px 5px;
    white-space: nowrap;
    display: inline-block;
}

#tx_tree .ui-fancytree li p:hover,
#tx_tree .ui-fancytree li p:hover + ul li p,
#tx_tree .ui-fancytree li p:focus,
#tx_tree .ui-fancytree li p:focus + ul li p {
    background: #eee;
    color: #000;
    border: 1px solid #aaa;
}

#tx_tree .ui-fancytree li p:hover + ul li::after,
#tx_tree .ui-fancytree li p:focus + ul li::after,
#tx_tree .ui-fancytree li p:hover + ul li::before,
#tx_tree .ui-fancytree li p:focus + ul li::before,
#tx_tree .ui-fancytree li p:hover + ul::before,
#tx_tree .ui-fancytree li p:focus + ul::before,
#tx_tree .ui-fancytree li p:hover + ul ul::before,
#tx_tree .ui-fancytree li p:focus + ul ul::before {
    border-color: #999;
}

#tx_tree .ui-fancytree .fancytree-icon {
    display: none;
}

#tx_tree
.fancytree-plain.fancytree-container.fancytree-treefocus
span.fancytree-focused
span.fancytree-title {
    border-color: transparent;
}

#tx_tree .fancytree-plain span.fancytree-node span.fancytree-title {
    background-color: transparent;
    border-color: transparent;
}

#tx_tree ul.fancytree-container {
    background-color: transparent;
    border: none;
    outline: none;
}

#tx_tree .fancytree-expander {
    position: relative;
    z-index: 2;
    top: -2px;
    left: -5px;
}

#tx_tree * {
    font-family: monospace;
}

.back-button-container {
    display: flex;
    align-items: center;
    font-size: 16px;
}

.back-button-container img {
    margin-left: 20px;
    margin-right: 20px;
}

.with-back-button {
    display: flex;
    align-items: center;
    margin: 10px 11px 10px;
}

.with-back-button h3 {
    padding-bottom: 0;
    margin: 0;
}

.container-top {
    display: inline-block;
}

.container-info-logo {
    display: flex;
    justify-content: space-between;
}

@media screen and (max-width: 1281px) {
    .hosted-by {
        order: 1;
        margin: 0 0 0 13px;
    }

    .container-info-logo {
        flex-flow: column;
    }

    .transaction-info {
        order: 2;
    }

}

@media screen and (max-width: 1280px) {
    .with-back-button {
        flex-direction: column;
        align-items: flex-start;
    }

    .back-button-container {
        order: -1;
        margin-bottom: 20px;
        width: 100%;
        justify-content: space-between;
    }
}


================================================
FILE: ethtx_ce/app/frontend/static.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

from flask import render_template, Blueprint, current_app

from . import frontend_route

bp = Blueprint("static", __name__)


@frontend_route(bp, "/")
def search_page() -> render_template:
    """Render search page - index."""
    return (
        render_template(
            "index.html",
            chains=current_app.ethtx.providers.web3provider.nodes.keys()
            if current_app.ethtx
            else [],
            ethtx_version=current_app.config["ethtx_version"],
            ethtx_ce_version=current_app.config["ethtx_ce_version"],
        ),
        200,
    )


================================================
FILE: ethtx_ce/app/frontend/templates/exception.html
================================================
<!--
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.
  -->

<html lang="en">
<head>
    {% include './partials/headtags.html' %}
    <title>Ethtx error</title>

    <style>
        html {
            height: 100%;
        }

        body {
            text-align: center;
            display: flex;
            flex-direction: column;
            justify-content: center;
            min-height: 100vh;
        }

        h1 {
            padding-right: 1rem;
            margin-right: 1rem;
            border-right: 1px solid var(--border);
            font-size: 3rem;
            font-weight: 500;
            line-height: 1.2;
            margin-top: 0;
        }

        h2 {
            font-weight: 400;
            font-size: 1rem;
            margin-top: 0;
        }

        h3 {
            font-weight: 300;
            font-size: 0.75rem;
            margin-top: 0;
        }

        .container {
            margin: 0 auto;
            position: relative;
            box-sizing: border-box;
        }

        .container-sub {
            max-width: 600px;
            word-wrap: break-word;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .container-one-line {
            word-wrap: anywhere;
            text-align: left;
        }

        .pulsate {
            -webkit-animation: pulsate 3s ease-out;
            -webkit-animation-iteration-count: infinite;
            opacity: 0.5;
        }

        @-webkit-keyframes pulsate {
            0% {
                opacity: 0.5;
            }
            50% {
                opacity: 1.0;
            }
            100% {
                opacity: 0.5;
            }
        }

        .container-hash {
            text-align: center;
            display: inline-block;
        }

        .tx_hash {
            color: #c00;
            word-wrap: anywhere;
            filter: drop-shadow(0px 0.2px 0.2px #c00);
            -webkit-filter: drop-shadow(0px 0.2px 0.2px #c00);
        }

    </style>
</head>
<body>
<div class="container">
    <div class="container-sub">
        <h1 class="pulsate">{{ status_code }}</h1>
        <div class="container-one-line">
            <h2> {{ error }}</h2>
        </div>
    </div>
    {% if tx_hash %}
        <div class="container-hash">
            <h3>Tx hash: <span class="tx_hash">{{ tx_hash }}</span></h3>
        </div>
    {%- endif -%}
</div>

</body>
</html>

================================================
FILE: ethtx_ce/app/frontend/templates/index.html
================================================
<!--
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.
  -->

<!DOCTYPE html>
<html lang="en">

<head>
    {% include './partials/headtags.html' %}

    <title>EthDecoder</title>
    <style>
        html {
            height: 100vh;
        }

        body {
            font-family: Hind, sans-serif;
            font-size: 16px;
            background-color: var(--background);
            margin: 0;
            padding: 0;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
        }

        form {
            display: grid;
            grid-gap: 25px;
            margin-bottom: 16px;
        }

        label {
            font-size: 22px;
            font-weight: 500;
            display: block;
            margin-bottom: 8px;
        }

        input[type=submit] {
            background-color: var(--background-alt);
            color: #fff;
            border-radius: 3px;
            font-size: 18px;
            padding: 8px 64px;
            cursor: pointer;
            border: none;
            line-height: 1.65;
            margin: 0;
            word-wrap: break-word;
        }

        .container {
            max-width: 840px;
            margin: 0 auto;
            display: flex;
            flex-direction: column;
            flex-grow: 1;
        }

        .main-title {
            font-size: 2.375rem;
            margin-top: 30px;
            margin-bottom: 30px;
            font-weight: 700;
            line-height: 1;
        }

        .panel-container h2 {
            font-size: 22px;
            font-weight: 500;
            margin: 0;
            color: var(--text);
        }

        .input {
            font-size: 18px;
            width: 100%;
            padding: 13px 16px;
            box-sizing: border-box;
            border-radius: 6px;
            border: 1px solid var(--border);
            background-color: transparent;
        }

        .select {
            cursor: pointer;
            -moz-appearance: none;
            -webkit-appearance: none;
            appearance: none;
            background: url(/static/images/chevron_down.png) no-repeat right 13px center;
        }

        .tx-submit {
            margin-top: .625rem;
        }

        .tx-submit input {
            width: 14.375rem;
        }

        @media screen and (max-width: 568px) {
            .container-title {
                flex-direction: column;
                align-items: flex-start !important;
            }

        }

        .error-msg {
            color: #c00;
            font-size: 12px;
            margin-top: 5px;
            position: absolute;
        }

        .container-title {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 1rem;
        }
    </style>
</head>

<body>
<div class="container">
    <div class="container-title">
        <span class="main-title">EthTx Transaction Decoder</span>
    </div>
    <form action="/" method="GET" onsubmit="return onSubmit(this)">
        <div>
            <label for="net">Network</label>
            <select class="input select" id="net">
                {% for chain in chains %}
                    {% if chain == 'mainnet' %}
                    <option value="mainnet">ETH mainnet</option>
                    {% else %}
                    <option value="{{ chain }}">{{ chain|title + ' testnet' }}</option>
                    {% endif %}
                {% endfor %}
            </select>
        </div>
        <div>
            <label for="tx">Tx Hash</label>
            <input class="input" size="66" type="text" id="tx"
                   oninput="handleValueChange()"
                   onchange="handleValueChange()">
            <div class="error-msg" id="error_hash"></div>
        </div>
        <div class="tx-submit">
            <input id="tx_submit" type="submit" value="Decode now">
        </div>
    </form>
</div>

<script type="text/javascript">
    function onSubmit(e) {
        const tx = document.getElementById(`tx`).value;
        if (!tx || checkTxHash(tx) === false) {
            printError("error_hash", "Please, enter a valid transaction hash.")
            return false;
        } else {
            window.onbeforeunload = function () {
                document.getElementById("tx_submit").value = "Decoding...";
            };
            e.action = '/' + e.net.options[e.net.selectedIndex].value + '/' + e.tx.value;
        }
    }

    function handleValueChange() {
        hideError("error_hash")
    }

    function checkTxHash(tx) {
        const pattern = new RegExp(/^(0x)?([A-Fa-f0-9]{64})$/)
        return pattern.test(tx);
    }

    function printError(elemId, hintMsg) {
        document.getElementById(elemId).innerHTML = hintMsg;
    }

    function hideError(elemId) {
        document.getElementById(elemId).innerHTML = "";
    }
</script>
</body>
</html>

================================================
FILE: ethtx_ce/app/frontend/templates/partials/headtags.html
================================================
<!--
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.
  -->

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://fonts.googleapis.com/css2?family=Hind:wght@400;500;700&amp;display=swap" rel="preload stylesheet"
      as="style" crossorigin="anonymous">
<link rel="preload stylesheet" type="text/css" as="style" crossorigin="anonymous"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="shortcut icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
<style>
    :root {
        --text: #000;
        --text-alt: #717171;
        --background: #FFF;
        --background-alt: #717171;
        --background-muted: #EFEFEF;
        --border: #C4C4C4;
        --link: #337AB8;
    }
</style>

================================================
FILE: ethtx_ce/app/frontend/templates/semantics.html
================================================
<!--
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.
  -->

<!DOCTYPE html>
<html lang="en">

<head>
    {% include './partials/headtags.html' %}
    <title>Semantics editor</title>
    <link href="/static/ethtx.new.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/6.3.0/jsoneditor.min.css"
          integrity="sha512-d1YLCsPQJUoFkEXaRSGrD2uoWGXbcK9EFDWILONSkZbuIUCeH0xahQV8W6PZ4bA9UTZ9hJLQ/5Zuu2K35NBBMQ=="
          crossorigin="anonymous" referrerpolicy="no-referrer"/>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css"
          integrity="sha512-urpIFwfLI9ZDL81s6eJjgBF7LpG+ROXjp1oNwTj4gSlCw00KiV1rWBrfszV3uf5r+v621fsAwqvy1wRJeeWT/A=="
          crossorigin="anonymous" referrerpolicy="no-referrer"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
            integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="
            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.js"
            integrity="sha512-JGKswjfABjJjtSUnz+y8XUBjBlwM1UHNlm2ZJN7A2a9HUYT3Mskq+SacsI35k4lok+/zetSxhZjKS3r3tfAnQg=="
            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/6.3.0/jsoneditor.min.js"
            integrity="sha512-ASZnbuJkW4TR1WAXYIHls9Tavd/aZlxfNIvbO1PUIClxx/dAZYZqzuD/QvN99lREXK65AYXsJWwegJaxFVvc5g=="
            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <style>
        button {
            background-color: #AAA;
            color: #fff;
            border-radius: 5px;
            font-size: 16px;
            margin-top: 10px;
            margin-left: 5px;
            padding: 5px 10px 5px 10px;
        }


        label,
        input,
        select {
            display: block;
        }

        input.text {
            margin-bottom: 12px;
            width: 790px;
            padding: .4em;
        }

        select {
            margin-bottom: 12px;
            width: 805px;
            padding: .4em;
        }

        textarea {
            width: 805px;
            height: 360px;
        }

        fieldset {
            padding: 0;
            border: 0;
            margin-top: 25px;
        }

    </style>

    <script>
        $(function () {
            var
                dialog, form,
                address = $("#address"),
                network = $("#network"),
                name = $("#name"),
                abi = $("#abi"),
                chash = '{{ metadata.contract.code_hash }}',
                standard = '{{ metadata.contract.standard | tojson }}';

            function checkLength(o, min, max) {
                return !(o.val().length > max || o.val().length < min);
            }

            function isJsonString(str) {
                try {
                    var json = JSON.parse(str);
                    return (typeof json === 'object');
                } catch (e) {
                    return false;
                }
            }

            function addABI() {
                var valid = true;

                valid = valid && checkLength(address, 42, 42);
                valid = valid && checkLength(name, 2, 80);
                valid = valid && abi.val().length > 0 && isJsonString(abi.val());

                if (valid) {
                    const poke = {
                        address: address.val(),
                        network: network.val(),
                        name: name.val(),
                        abi: abi.val(),
                        chash: chash,
                        standard: standard
                    };
                    $.ajax({
                        url: "/poke",
                        dataType: 'json',
                        contentType: "application/json; charset=utf-8",
                        data: JSON.stringify(poke),
                        type: 'POST',
                        success: function (response) {
                            if (response["result"] === 'ok') {
                                alert("SUCCESS! ABI processed...");
                            } else {
                                alert("ERROR! ABI processing error");
                            }
                            location.reload();
                        },
                        error: function (error) {
                            alert("ERROR! Processing error");
                            location.reload();
                        }
                    });
                    dialog.dialog("close");
                } else {
                    alert("Data is not correct!")
                }
                return valid;
            }

            dialog = $("#dialog-form").dialog({
                autoOpen: false,
                height: 720,
                width: 850,
                modal: true,
                buttons: {
                    "Decode ABI": addABI,
                    Cancel: function () {
                        dialog.dialog("close");
                    }
                },
                close: function () {
                    form[0].reset();
                }
            });

            form = dialog.find("form").on("submit", function (event) {
                event.preventDefault();
                addABI();
            });

            $("#poke-abi").button().on("click", function () {
                dialog.dialog("open");
                return false;
            });

        });
    </script>
</head>

<body>

<h3>Semantics for: {{ address }} / {{ metadata.chain }}</h3>
<div id="metadataJSONeditor" style="float: left; width: 55%; height: 320px; margin: 5px;"></div>
<div id="transformationsJSONeditor" style="float: left; width: 43%; height: 320px; margin: 5px;"></div>
<div id="eventsJSONeditor" style="float: left; width: 55%; height: 320px; margin: 5px;"></div>
<div id="functionsJSONeditor" style="float: left; width: 43%; height: 320px; margin: 5px;"></div>

<form>
    <button id="save-semantics">Save semantics</button>
    <button id="poke-abi">Poke ABI</button>
    <button id="reload-semantics" style="background-color: #c0392b; color: #fff;">Reload semantics</button>
</form>

<div id="dialog-form" title="Poke ABI">
    <form>
        <fieldset>
            <label for="address">Contract address</label>
            <input type="text" name="address" id="address" class="text ui-widget-content ui-corner-all"
                   value="{{ address }}">
            <label for="network">Network</label>
            <select name="network" id="network">
                <option {% if metadata.chain == 'mainnet' %} selected {% endif %} value="mainnet">ETH mainnet</option>
                <option {% if metadata.chain == 'goerli' %} selected {% endif %} value="goerli">Goerli testnet</option>
                <option {% if metadata.chain == 'rinkeby' %} selected {% endif %} value="rinkeby">Rinkeby testnet
                </option>
            </select>
            <label for="name">Contract name</label>
            <input type="text" name="name" id="name" class="text ui-widget-content ui-corner-all">
            <label for="abi">Contract ABI</label>
            <textarea name="abi" id="abi" class="textarea ui-widget-content ui-corner-all"></textarea>
            <input type="submit" tabindex="-1" style="position:absolute; top:-1000px">
        </fieldset>
    </form>
</div>

<script>
    var metadataContainer = document.getElementById("metadataJSONeditor");
    var transformationsContainer = document.getElementById("transformationsJSONeditor");
    var eventsContainer = document.getElementById("eventsJSONeditor");
    var functionsContainer = document.getElementById("functionsJSONeditor");

    var options = {
        modes: ['tree', 'code'],
        enableSort: false,
        enableTransform: false,
        search: true,
        indentation: 3,
        statusBar: false
    };

    const metadata_editor = new JSONEditor(metadataContainer, {...options, name: 'Metadata'}, {{ metadata | tojson }});
    const transformations_editor = new JSONEditor(transformationsContainer, {
        ...options,
        name: 'Transformations'
    }, {{ transformations | tojson }});
    const events_editor = new JSONEditor(eventsContainer, {...options, name: 'Events'}, {{ events | tojson }});
    const functions_editor = new JSONEditor(functionsContainer, {
        ...options,
        name: 'Functions'
    }, {{ functions | tojson }})

    $("#save-semantics").button().on("click", function () {
        saveSemantics();
        {{ name }};
        return false;
    });

    $("#reload-semantics").button().on("click", function () {
        reloadSemantics();
        return false;
    });

    // save semantics
    function saveSemantics() {
        const semantics = {
            address: '{{ address }}',
            metadata: metadata_editor.get(),
            transformations: transformations_editor.get(),
            events: events_editor.get(),
            functions: functions_editor.get()
        };
        $.ajax({
            url: "/save",
            dataType: 'json',
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(semantics),
            type: 'POST',
            success: function (response) {
                console.log(response);
                location.reload();
            },
            error: function (error) {
                console.log(error);
                location.reload();
            }
        })
    }

    //reload semantics
    function reloadSemantics() {
        const data = {
            address: '{{ address }}',
            chain: '{{ metadata.chain }}'
        };

        $.ajax({
            url: "/reload",
            dataType: 'json',
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(data),
            type: 'POST',
            success: function (response) {
                console.log(response);
                location.reload();
            },
            error: function (error) {
                console.log(error);
                location.reload();
            }
        })
    }
</script>

</body>

</html>


================================================
FILE: ethtx_ce/app/frontend/templates/transaction.html
================================================
<!--
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.
  -->

{% macro address_link(address, label, badge="") %}
    {%- if address and address != '0x0000000000000000000000000000000000000000' -%}
        <a href="https://{%- if transaction.chain_id != 'mainnet' -%}{{ transaction.chain_id }}.{% endif %}etherscan.io/address/{{ address }}" target="_blank">
           {%- if badge -%}
              <span class="badge badge-info">[{{ badge }}] </span>
           {%- endif -%}
              {{- label -}}
        </a>
    {%- else -%}
        {{- label -}}
    {%- endif -%}
{% endmacro %}

{% macro nft_link(address, label) %}
    {%- if address and address != '0x0000000000000000000000000000000000000000' -%}
        <a href="https://{%- if transaction.chain_id != 'mainnet' -%}{{ transaction.chain_id }}.{% endif %}etherscan.io/token/{{ address }}" target="_blank">
            {{- label -}}
        </a>
    {%- else -%}
        {{- label -}}
    {%- endif -%}
{% endmacro %}

{% macro print_event_arguments(arguments) %}
    {% for argument in arguments %}
        {% if argument.type != "ignore" %}
            {% if loop.index > 1 %}, {% endif %}
            {%- if argument.name == "[no ABI]" -%}
                <span class="badge badge-danger">no_ABI</span>
            {%- else -%}
                {%- if argument.name -%}
                    <span style='color: darkred'>{{ argument.name }}=</span>
                {%- endif -%}
                {%- if argument.type == "tuple" -%}
                    ({{- print_event_arguments(argument.value) -}})
                {%- elif argument.type == "tuple[]" -%}
                    [
                    {%- for sub_arg in argument.value -%}
                        {{- print_event_arguments(sub_arg) -}}
                    {%- endfor -%}
                    ]
                {%- elif argument.type == "address" -%}
                    {{- address_link(argument.value.address, argument.value.name, argument.value.badge) -}}
                {%- elif argument.type == "nft" -%}
                    {{- nft_link(argument.value.address, argument.value.name) -}}
                {%- elif argument.type == "call" -%}
                    {{- address_link(argument.value.address, argument.value.name, argument.value.badge) -}}.
                    {{- argument.value.function_name -}}({{ print_event_arguments(argument.value.arguments) }})
                {%- else -%}
                    {{- argument.value -}}
                {%- endif -%}
            {% endif %}
        {% endif %}
    {% endfor %}
{% endmacro %}

{% macro print_call_arguments(arguments) %}
    {% if arguments is not none %}
        {%- for argument in arguments -%}
            {% if argument.type != "ignore" %}
                {%- if loop.index > 1 -%}, {% endif %}
                {%- if argument.name == "[no ABI]" -%}
                    <span class="badge badge-danger">no_ABI</span>
                {%- else -%}
                    {%- if argument.name %}<span style='color: darkred'>{{- argument.name -}}=</span>{%- endif -%}
                    {%- if argument.type == "tuple" -%}
                        ({{- print_call_arguments(argument.value) -}})
                    {%- elif argument.type == "tuple[]" -%}
                        [
                        {%- for sub_arg in argument.value -%}
                            {%- if loop.index > 1 -%}, {% endif %}
                            ({{- print_call_arguments(sub_arg) -}})
                        {%- endfor -%}
                        ]
                    {%- elif argument.type == "address" -%}
                        {{- address_link(argument.value.address, argument.value.name, argument.value.badge) -}}
                    {%- elif argument.type == "nft" -%}
                        {{- nft_link(argument.value.address, argument.value.name) -}}
                    {% elif argument.type == "call" %}
                        {{- address_link(argument.value.address, argument.value.name, argument.value.badge) -}}.
                        <span style="color: darkgreen">{{- argument.value.function_name -}}</span>(
                        {{- print_event_arguments(argument.value.arguments) -}})
                    {%- else -%}
                        {{- argument.value -}}
                    {%- endif -%}
                {%- endif -%}
            {%- endif -%}
        {%- endfor -%}
    {% endif %}
{% endmacro %}

{%- macro print_call_line(call) -%}
    <li id="{{ call.id }}"
        class="indent-{{ call.indent }} {% if call.indent < 6 %}expanded{% endif %}">
        <p>
            <span style="color: slategray">[{{- call.gas_used if call.gas_used != None else "N/A" -}}]: </span>
            {% if call.error %}
                <span style='color: red'>({{ call.error }})</span>
            {% endif %}
            {% if call.call_type == "delegatecall" %}
                <span style='color: darkorange'>(delegate)</span>
            {% endif %}
            {% if call.value and call.call_type != "selfdestruct" %}
                <span style='color: blue'>ETH {{ call.value -}}</span>
            {% endif %}
            {%- if call.call_type == "selfdestruct" -%}
                {{- address_link(call.from_address.address, call.from_address.name, call.from_address.badge) -}}
                <span style='color: darkgreen'>.{{ call.call_type }}({% if call.value > 0 %}
                    <span style='color: blue'>ETH {{ call.value }}</span> =>
                    {{ address_link(call.to_address.address, call.to_address.name, call.to_address.badge) -}}{% endif %}
                    )</span>
            {%- elif call.call_type == "create" -%}
                {{- address_link(call.to_address.address, call.to_address.name, call.to_address.badge) -}}.<span
                    style='color: darkgreen'>New()</span>
            {%- else -%}

                {%- if call.call_type == "delegatecall" -%}
                    {{- address_link(call.from_address.address, call.from_address.name, call.from_address.badge) -}}
                {%- else -%}
                    {{- address_link(call.to_address.address, call.to_address.name, call.to_address.badge) -}}
                {%- endif -%}

                {%- if call.call_type == "delegatecall" -%}
                    [<span
                        class='delegate'>{{- address_link(call.to_address.address, call.to_address.name, call.to_address.badge) -}}</span>
                {%- endif -%}
                {%- if call.function_guessed -%}
                    <span style='color: dodgerblue'>.{{- call.function_name -}}</span>
                {%- elif call.function_name != "0x" -%}
                    <span style='color: darkgreen'>.{{- call.function_name -}}</span>
                {%- else -%}
                    <span style='color: darkgreen'>.fallback</span>
                {%- endif -%}
                {%- if call.call_type == "delegatecall" -%}
                    ]
                {%- endif -%}

                <span>({{- print_call_arguments(call.arguments) -}}) => ({{- print_call_arguments(call.outputs) -}})</span>
            {%- endif -%}
        </p>
        <ul>
            {% for sub_call in call.subcalls %}
                {{- print_call_line(sub_call) -}}
            {% endfor %}
        </ul>
    </li>
{%- endmacro -%}

<!doctype html>
<html lang="en">

<head>
    {% include './partials/headtags.html' %}
    <title>Ethtx.info Analysis {{ transaction.tx_hash }}</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
            integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="
            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <link rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/jquery.fancytree/2.38.2/skin-themeroller/ui.fancytree.min.css"
          integrity="sha512-LtulT9+xwtALkeFjtiojm4zOrWyDR+qivwmAKI8DSMdtJnJP/cXlV2TfwbiGe3m4nHoWy2Jbgg+I7BKHqqo2Jg=="
          crossorigin="anonymous" referrerpolicy="no-referrer"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.fancytree/2.38.2/jquery.fancytree-all-deps.min.js"
            integrity="sha512-8Fstaj+d8Fha0qzgW/bGQpyG4NcVSYcmflfYOzhV1z/4/SYwf96rqrANH+lUmO7ZSq9WgRDYgASFiiq20bgK7g=="
            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <link href="/static/ethtx.new.css" rel="stylesheet">
</head>

<body>

<div>
    <div class="container-top">
        <h3>
            <a title="Home" href="/">
                <i class="fas fa-home "></i>
            </a> Analysis for: <span class="transaction-hash">
            {{ transaction.tx_hash }} / {{ transaction.chain_id }}</span>
        </h3>
        <div class="container-info-logo">
            <div class="transaction-info">
                <div>
                    Block number: <span style='color: darkred'>{{ transaction.block_number }}</span>
                    {% if transaction.timestamp %}
                        at <span style='color: darkred'>{{ transaction.timestamp }}</span> UTC
                    {% endif %}
                </div>
                <div>
                    Tx cost: <span
                        style='color: darkred'>{{ (transaction.gas_used * transaction.gas_price / 10 ** 9) }}</span>
                    ETH
                    {% if transaction.chain_id == 'mainnet' and eth_price %}
                        /
                        <span style='color: darkred'>{{ "{:,.2f}".format(transaction.gas_used * transaction.gas_price * eth_price / 10 ** 9) }}</span>
                        USD
                    {% endif %}
                </div>
                <div>
                    Gas used: <span style='color: darkred'>{{ "{:,}".format(transaction.gas_used) }}</span> / <span
                        style='color: darkred'>{{ "{:,.2f}".format(transaction.gas_price ) }}</span> Gwei
                </div>
            </div>
        </div>
    </div>
</div>

{% if events %}
    <div class="events">
        <h3>Emitted events:</h3>
        <table class="table table-striped">
            {% for event in events %}
                <tr>
                    <td>
                        <span style="color: slategray">[{{- event.index -}}]</span>
                        {{ address_link(event.contract.address, event.contract.name, event.contract.badge) }}
                        {%- if event.event_guessed -%}
                            <span style='color: dodgerblue'>.{{ event.event_name }}</span>
                        {%- else -%}
                            <span style='color: darkgreen'>.{{ event.event_name }}</span>
                        {%- endif -%}
                        ({{ print_event_arguments(event.parameters) }})
                    </td>
                </tr>
            {% endfor %}
        </table>
    </div>
{% endif %}

{% if balances %}
    <div class="account-balances">
        <h3>Account balances:</h3>
        <table class="table table-sm">
            <thead>
            <tr>
                <th scope="col">Address</th>
                <th scope="col">Token</th>
                <th scope="col">Balance</th>
            </tr>
            </thead>
            <tbody>
            {% for balance in balances %}
                <tr>
                    <td rowspan="{{- balance.tokens|length -}}">
                        {{- address_link(balance.holder.address, balance.holder.name, balance.holder.badge) -}}
                    </td>
                    {% for token in balance.tokens %}
                        {% if loop.index > 1 %}
                            <tr>{% endif %}
                    {% if token.token_standard == 'ERC721' %}
                        <td class="">{{- nft_link(token.token_address, token.token_symbol) -}}</td>
                    {% else %}
                        <td class="">{{- address_link(token.token_address, token.token_symbol) -}}</td>
                    {% endif %}
                    <td style="text-align: right">
                        {% if token.balance[0] == '-' %}
                            <span style="color: darkred">{{- token.balance -}}</span>
                        {% else %}
                            {{- token.balance -}}
                        {% endif %}
                    </td>
                    {% if loop.index > 1 %}</tr>{% endif %}
                    {% endfor %}
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endif %}

{% if transfers %}
    <div class="transfers">
        <h3>Token transfers: </h3>
        <table class="table">
            <thead>
            <tr>
                <th scope="col">Sender</th>
                <th scope="col">Token</th>
                <th scope="col">Amount</th>
                <th scope="col">Receiver</th>
            </tr>
            </thead>
            <tbody>
            {% for transfer in transfers %}
                <tr>
                    <td>{{- address_link(transfer.from_address.address, transfer.from_address.name, transfer.from_address.badge) -}}</td>
                    {% if transfer.token_standard == 'ERC721' %}
                        <td class="">{{- nft_link(transfer.token_address, transfer.token_symbol) -}}</td>
                    {% else %}
                        <td>{{- address_link(transfer.token_address, transfer.token_symbol) -}}</td>
                    {% endif %}
                    <td style="text-align: right">
                        {{- "{:,.4f}".format(transfer.value) -}}
                    </td>
                    <td>{{- address_link(transfer.to_address.address, transfer.to_address.name, transfer.to_address.badge) -}}</td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endif %}

{% if call %}
    <div class="calls">
        <h3>Execution trace:</h3>
        <div id="tree">
            <ul class="tree">
                <li>
                    <p>
                        <span style="color: slategray">[{{- transaction.gas_used -}}]: </span>
                        {{- address_link(transaction.sender.address, transaction.sender.name, 'sender') -}}
                    </p>
                    <ul>
                        {{- print_call_line(call) -}}
                    </ul>
                </li>
            </ul>
        </div>
    </div>
{% else %}
    <h3>Trace decoding error...</h3>
{% endif %}

<script>
    $(document).ready(() => {
        $("#tree").fancytree({
            minExpandLevel: 2,
            toggleEffect: false,
        });
        $("#tx_tree").fancytree({
            minExpandLevel: 2,
            toggleEffect: false,
        });
    });
</script>
</body>
</html>


================================================
FILE: ethtx_ce/app/frontend/transactions.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import logging
import os
from typing import Optional

from ethtx.models.decoded_model import DecodedTransaction
from flask import Blueprint, render_template, current_app, request

from . import frontend_route, deps

log = logging.getLogger(__name__)

bp = Blueprint("transactions", __name__)


@frontend_route(bp, "/<string:tx_hash>/")
@frontend_route(bp, "/<string:chain_id>/<string:tx_hash>/")
def read_decoded_transaction(
    tx_hash: str, chain_id: Optional[str] = None
) -> "show_transaction_page":
    tx_hash = tx_hash if tx_hash.startswith("0x") else "0x" + tx_hash

    refresh_semantics = "refresh" in request.args

    if refresh_semantics:
        refresh_secure_key = request.args["refresh"]
        if refresh_secure_key != os.environ["SEMANTIC_REFRESH_KEY"]:
            return "Invalid semantics refresh key"

        log.info(f"Decoding tx {tx_hash} with semantics refresh")

    chain_id = chain_id or current_app.ethtx.default_chain
    decoded_transaction = current_app.ethtx.decoders.decode_transaction(
        chain_id=chain_id, tx_hash=tx_hash, recreate_semantics=refresh_semantics
    )
    decoded_transaction.metadata.timestamp = (
        decoded_transaction.metadata.timestamp.strftime("%Y-%m-%d %H:%M:%S")
    )

    return show_transaction_page(decoded_transaction)


def show_transaction_page(data: DecodedTransaction) -> render_template:
    """Render transaction/exception page."""
    return (
        render_template(
            "transaction.html",
            eth_price=deps.get_eth_price(),
            transaction=data.metadata,
            events=data.events,
            call=data.calls,
            transfers=data.transfers,
            balances=data.balances,
        ),
        200,
    )


================================================
FILE: ethtx_ce/app/helpers.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import importlib
import logging
import os
import pkgutil
from typing import Any, List, Tuple, Optional

import pkg_resources
import requests
from flask import Blueprint, Flask
from git import Repo

log = logging.getLogger(__name__)


def register_blueprints(
    app: Flask, package_name: str, package_path: str
) -> List[Blueprint]:
    """
    Register all Blueprint instances on the specified Flask application found
    in all modules for the specified package.
    :param app: the Flask application
    :param package_name: the package name
    :param package_path: the package path
    """
    rv = []

    for _, name, _ in pkgutil.iter_modules(package_path):
        m = importlib.import_module("%s.%s" % (package_name, name))
        for item in dir(m):
            item = getattr(m, item)
            if isinstance(item, Blueprint):
                app.register_blueprint(item)
            rv.append(item)

    return rv


def class_import(name: str) -> Any:
    """Import class from string."""
    d = name.rfind(".")
    classname = name[d + 1 : len(name)]
    m = __import__(name[0:d], globals(), locals(), [classname])

    return getattr(m, classname)


class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


def read_ethtx_versions(app: Flask) -> None:
    """Read ethtx and ethtx_ce versions."""
    ethtx_version = pkg_resources.get_distribution("ethtx").version

    try:
        remote_url, sha = _get_version_from_git()
    except Exception:
        remote_url, sha = _get_version_from_docker()

    ethtx_ce_version = f"{_clean_up_git_link(remote_url)}/tree/{sha}"

    log.info(
        "%s: EthTx version: %s. EthTx CE version: %s",
        app.name,
        ethtx_version,
        ethtx_ce_version,
    )

    app.config["ethtx_version"] = ethtx_version
    app.config["ethtx_ce_version"] = ethtx_ce_version


def get_latest_ethtx_version() -> str:
    """Get latest EthTx version."""
    package = "EthTx"
    response = requests.get(f"https://pypi.org/pypi/{package}/json")

    if response.status_code != 200:
        log.warning("Failed to get latest EthTx version from PyPI")
        return ""

    log.info("Latest EthTx version: %s", response.json()["info"]["version"])
    return response.json()["info"]["version"]


def _get_version_from_git() -> Tuple[str, str]:
    """Get EthTx CE version from .git"""
    repo = Repo(__file__, search_parent_directories=True)

    remote_url = repo.remote("origin").url
    sha = repo.head.commit.hexsha
    short_sha = repo.git.rev_parse(sha, short=True)

    return remote_url, short_sha


def _get_version_from_docker() -> Tuple[str, str]:
    """Get EthTx CE version from env."""
    return os.getenv("GIT_URL", ""), os.getenv("GIT_SHA", "")


def _clean_up_git_link(git_link: str) -> str:
    """Clean up git link, delete .git extension, make https url."""
    if "@" in git_link:
        git_link.replace(":", "/").replace("git@", "https://")

    if git_link.endswith(".git"):
        git_link = git_link[:-4]

    return git_link


================================================
FILE: ethtx_ce/app/logger.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import json
import logging
import logging.config
import os

from flask import Flask


def setup_logging(app: Flask):
    """Setup logging"""
    with open(app.config["LOGGING_CONFIG"], "r") as f:
        config = json.load(f)

    config["root"]["level"] = "DEBUG" if app.config["DEBUG"] else "INFO"
    filename = config["handlers"]["file_handler"]["filename"]
    if "/" not in filename:
        log_file_path = os.path.join(app.config["LOGGING_LOG_PATH"], filename)
        os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
        config["handlers"]["file_handler"]["filename"] = log_file_path

    logging.config.dictConfig(config)

    setup_external_logging()


def setup_external_logging() -> None:
    """Setup and override external libs loggers."""
    logging.getLogger("web3").setLevel(logging.INFO)  # web3 logger


================================================
FILE: ethtx_ce/app/wsgi.py
================================================
# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)
# Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded
# in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
# The product contains trademarks and other branding elements of Token Flow Insights SA which are
# not licensed under the Apache 2.0 license. When using or reproducing the code, please remove
# the trademark and/or other branding elements.

import os

from ethtx import EthTx, EthTxConfig
from flask import Flask
from werkzeug.middleware.dispatcher import DispatcherMiddleware

from . import frontend, api

app = Flask(__name__)

ethtx_config = EthTxConfig(
    mongo_connection_string=os.getenv("MONGO_CONNECTION_STRING"),
    etherscan_api_key=os.getenv("ETHERSCAN_KEY"),
    web3nodes={
        "mainnet": dict(hook=os.getenv("MAINNET_NODE_URL", ""), poa=False),
        "goerli": dict(hook=os.getenv("GOERLI_NODE_URL", ""), poa=True),
    },
    default_chain="mainnet",
    etherscan_urls={
        "mainnet": "https://api.etherscan.io/api",
        "goerli": "https://api-goerli.etherscan.io/api",
    },
)

ethtx = EthTx.initialize(ethtx_config)

app.wsgi_app = DispatcherMiddleware(
    frontend.create_app(engine=ethtx, settings_override=EthTxConfig),
    {"/api": api.create_app(engine=ethtx, settings_override=EthTxConfig)},
)

# ethtx_ce/ as Source Root
if __name__ == "__main__":
    app.run()


================================================
FILE: ethtx_ce/entrypoint.sh
================================================
#!/usr/bin/env sh
set -e

echo "ethtx_ce entrypoint.sh"

if [ -f /app/app/wsgi.py ]; then
    DEFAULT_MODULE_NAME=app.wsgi
elif [ -f /app/wsgi.py ]; then
    DEFAULT_MODULE_NAME=wsgi
fi

MODULE_NAME=${MODULE_NAME:-$DEFAULT_MODULE_NAME}
VARIABLE_NAME=${VARIABLE_NAME:-app}

export APP_MODULE=${APP_MODULE:-"$MODULE_NAME:$VARIABLE_NAME"}

if [ -f /app/gunicorn_conf.py ]; then
    DEFAULT_GUNICORN_CONF=/app/gunicorn_conf.py
elif [ -f /app/app/gunicorn_conf.py ]; then
    DEFAULT_GUNICORN_CONF=/app/app/gunicorn_conf.py
else
    DEFAULT_GUNICORN_CONF=/gunicorn_conf.py
fi
export GUNICORN_CONF=${GUNICORN_CONF:-$DEFAULT_GUNICORN_CONF}

exec "$@"

================================================
FILE: ethtx_ce/gunicorn_conf.py
================================================
import json
import multiprocessing
import os

workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1")
max_workers_str = os.getenv("MAX_WORKERS")
use_max_workers = None
if max_workers_str:
    use_max_workers = int(max_workers_str)
web_concurrency_str = os.getenv("WEB_CONCURRENCY", None)

host = os.getenv("HOST", "0.0.0.0")
port = os.getenv("PORT", "5000")
bind_env = os.getenv("BIND", None)
use_loglevel = os.getenv("LOG_LEVEL", "info")
if bind_env:
    use_bind = bind_env
else:
    use_bind = f"{host}:{port}"

cores = multiprocessing.cpu_count()
workers_per_core = float(workers_per_core_str)
default_web_concurrency = workers_per_core * cores
if web_concurrency_str:
    web_concurrency = int(web_concurrency_str)
    assert web_concurrency > 0
else:
    web_concurrency = max(int(default_web_concurrency), 2)
    if use_max_workers:
        web_concurrency = min(web_concurrency, use_max_workers)
accesslog_var = os.getenv("ACCESS_LOG", "-")
use_accesslog = accesslog_var or None
errorlog_var = os.getenv("ERROR_LOG", "-")
use_errorlog = errorlog_var or None
graceful_timeout_str = os.getenv("GRACEFUL_TIMEOUT", "600")
timeout_str = os.getenv("TIMEOUT", "600")
keepalive_str = os.getenv("KEEP_ALIVE", "5")

# Gunicorn config variables
loglevel = use_loglevel
workers = web_concurrency
bind = use_bind
errorlog = use_errorlog
worker_tmp_dir = "/dev/shm"
accesslog = use_accesslog
graceful_timeout = int(graceful_timeout_str)
timeout = int(timeout_str)
keepalive = int(keepalive_str)


# For debugging and testing
log_data = {
    "loglevel": loglevel,
    "workers": workers,
    "bind": bind,
    "graceful_timeout": graceful_timeout,
    "timeout": timeout,
    "keepalive": keepalive,
    "errorlog": errorlog,
    "accesslog": accesslog,
    # Additional, non-gunicorn variables
    "workers_per_core": workers_per_core,
    "use_max_workers": use_max_workers,
    "host": host,
    "port": port,
}
print(json.dumps(log_data))


================================================
FILE: ethtx_ce/log_cfg.json
================================================
{
  "version": 1,
  "disable_existing_loggers": false,
  "formatters": {
    "extra": {
      "format": "%(asctime)s.%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d - %(funcName)s %(process)d/%(processName)s]     %(message)s",
      "datefmt": "%Y-%m-%d %H:%M:%S"
    }
  },
  "handlers": {
    "console": {
      "class": "logging.StreamHandler",
      "level": "DEBUG",
      "formatter": "extra",
      "stream": "ext://sys.stdout"
    },
    "file_handler": {
      "class": "logging.handlers.RotatingFileHandler",
      "level": "INFO",
      "formatter": "extra",
      "filename": "ethtx_ce.log",
      "maxBytes": 5242880,
      "backupCount": 5,
      "encoding": "utf8"
    }
  },
  "root": {
    "level": "INFO",
    "handlers": [
      "file_handler",
      "console"
    ]
  }
}

================================================
FILE: ethtx_ce/start-reload.sh
================================================
#! /usr/bin/env sh
set -e

# Start Gunicorn
echo "Starting Gunicorn..."
exec pipenv run gunicorn "--reload" -c "$GUNICORN_CONF" "$APP_MODULE"

================================================
FILE: ethtx_ce/start.sh
================================================
#! /usr/bin/env sh
set -e

# Start Gunicorn
echo "Starting Gunicorn..."
exec pipenv run gunicorn -c "$GUNICORN_CONF" "$APP_MODULE"

================================================
FILE: ethtx_ce/tests/flask_test.py
================================================
import pytest

from app.frontend import create_app


class TestFlask:
    @pytest.fixture
    def client(self):
        ethtx = None
        app = create_app(ethtx)
        with app.test_client() as client:
            yield client

    def test_landing_page(self, client):
        resp = client.get("/")
        assert resp.status_code == 200
        # it's a dump check, but it's something
        assert len(resp.data) > 500

    def test_semantics_is_secured_with_basic_auth(self, client):
        tx_hash = "0xf9baa1792d644bbda985324a0bfdc052a806ca1a4b6a3df3578c73025f7fe544"
        url = f"/semantics/mainnet/{tx_hash}/"
        resp = client.get(url)
        assert resp.status_code == 401


================================================
FILE: ethtx_ce/tests/mocks/__init__.py
================================================


================================================
FILE: ethtx_ce/tests/mocks/mocks.py
================================================
from typing import Dict

from ethtx.models.w3_model import W3Transaction, W3Block, W3Receipt, W3Log
from ethtx.utils.attr_dict import AttrDict
from hexbytes import HexBytes


class MockWeb3Provider:
    blocks = {
        1: {
            "difficulty": 123,  # int
            "extraData": "test",  # HexBytes
            "gasLimit": 123,  # int
            "gasUsed": 123,  # int
            "hash": HexBytes(
                b"\x88\xe9mE7\xbe\xa4\xd9\xc0]\x12T\x99\x07\xb3%a\xd3\xbf1\xf4Z\xaesL\xdc\x11\x9f\x13@l\xb6"
            ),  # str
            "logsBloom": "test",  # HexBytes
            "miner": "test",  # str
            "nonce": "test",  # HexBytes
            "number": 123,  # int
            "parentHash": HexBytes(
                b"\x88\xe9mE7\xbe\xa4\xd9\xc0]\x12T\x99\x07\xb3%a\xd3\xbf1\xf4Z\xaesL\xdc\x11\x9f\x13@l\xb6"
            ),  # str
            "receiptsRoot": "test",  # HexBytes
            "sha3Uncles": "test",  # HexBytes
            "size": 123,  # int
            "stateRoot": "test",  # HexBytes
            "timestamp": 123,  # int,
            "totalDifficulty": 123,  # int
            "transactions": [],  # List
            "transactionsRoot": "test",  # HexBytes
            "uncles": [],  # List
        }
    }

    txs = {
        "0xd7701a0fc05593aee3a16f20cab605db7183f752ae942cc75fd0975feaf1072e": {
            "blockHash": HexBytes(
                b"\x88\xe9mE7\xbe\xa4\xd9\xc0]\x12T\x99\x07\xb3%a\xd3\xbf1\xf4Z\xaesL\xdc\x11\x9f\x13@l\xb6"
            ),  # str
            "blockNumber": 1,  # int
            "from_address": "fromasd",  # str
            "gas": 420,  # int
            "gasPrice": 1,  # int
            "hash": "co",  # HexBytes,
            "input": "jeszcze jak",  # str
            "nonce": 123,  # int
            "r": "ds",  # HexBytes
            "s": "sdf",  # HexBytes
            "to": "sdf",  # str
            "transactionIndex": 1,  # int
            "v": 1,  # int
            "value": 1,  # int
        }
    }

    def add_mocked_block_details(self, block_number, block_details: Dict):
        self.blocks[block_number] = block_details

    def get_transaction(self, tx_hash):
        return W3Transaction(**self.txs[tx_hash])

    def get_transaction_receipt(self, tx_hash):
        log_values = AttrDict(
            {
                "address": "test",  # str
                "blockHash": "test",  # HexBytes
                "blockNumber": 123,  # int
                "data": "test",  # str
                "logIndex": 132,  # int
                "removed": False,  # bool,
                "topics": [HexBytes("d")],  # List[HexBytes]
                "transactionHash": "test",  # HexBytes
                "transactionIndex": 123,  # int
            }
        )
        values = {
            "blockHash": "test",  # HexBytes
            "blockNumber": 123,  # int
            "contractAddress": 123,  # str
            "cumulativeGasUsed": 132,  # int,
            "from_address": "from",  # str
            "gasUsed": 123,  # int
            "logs": [log_values],  # List
            "logsBloom": "test",  # HexBytes
            "root": "test",  # str
            "status": 123,  # int,
            "to_address": "test",  # str
            "transactionHash": "test",  # HexBytes
            "transactionIndex": 123,  # int
        }
        return W3Receipt(**values)

    def get_block(self, block_number: int) -> W3Block:
        return W3Block(**self.blocks[block_number])


class Mocks:
    @staticmethod
    def get_mocked_w3_log():
        log_data = {
            "address": "test",
            "blockHash": "test_block_hash",
            "blockNumber": 123,
            "data": "data",
            "logIndex": 1,
            "removed": False,
            "topics": [1, 2, 3, 4],
            "transactionHash": "txHash",
            "transactionIndex": 1,
        }

        log = W3Log(**log_data)
        return log


class TestMocks:
    def test_can_create_w3_log(self):
        w3log = Mocks.get_mocked_w3_log()
        assert w3log is not None


================================================
FILE: scripts/git_version_for_docker.sh
================================================
#! /usr/bin/env bash

# Execute in root project directory
# Exit in case of error
set -e

remote_url=$(git config --get remote.origin.url)
sha=$(git rev-parse HEAD)

# set env variables
export GIT_URL="${remote_url}"
export GIT_SHA="${sha}"

# url, sha
url_sha="${remote_url},${sha}"

# return
echo "$url_sha"
Download .txt
gitextract_udd85lnu/

├── .dockerignore
├── .env_sample
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE
├── Makefile
├── NOTICE
├── Pipfile
├── README.md
├── docker-compose.override.yml
├── docker-compose.yaml
├── ethtx_ce/
│   ├── .flake8
│   ├── .gitignore
│   ├── app/
│   │   ├── __init__.py
│   │   ├── api/
│   │   │   ├── __init__.py
│   │   │   ├── decorators.py
│   │   │   ├── endpoints/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── info.py
│   │   │   │   ├── semantics.py
│   │   │   │   └── transactions.py
│   │   │   ├── exceptions.py
│   │   │   └── utils.py
│   │   ├── config.py
│   │   ├── exceptions.py
│   │   ├── factory.py
│   │   ├── frontend/
│   │   │   ├── __init__.py
│   │   │   ├── deps.py
│   │   │   ├── exceptions.py
│   │   │   ├── semantics.py
│   │   │   ├── static/
│   │   │   │   └── ethtx.new.css
│   │   │   ├── static.py
│   │   │   ├── templates/
│   │   │   │   ├── exception.html
│   │   │   │   ├── index.html
│   │   │   │   ├── partials/
│   │   │   │   │   └── headtags.html
│   │   │   │   ├── semantics.html
│   │   │   │   └── transaction.html
│   │   │   └── transactions.py
│   │   ├── helpers.py
│   │   ├── logger.py
│   │   └── wsgi.py
│   ├── entrypoint.sh
│   ├── gunicorn_conf.py
│   ├── log_cfg.json
│   ├── start-reload.sh
│   ├── start.sh
│   └── tests/
│       ├── flask_test.py
│       └── mocks/
│           ├── __init__.py
│           └── mocks.py
└── scripts/
    └── git_version_for_docker.sh
Download .txt
SYMBOL INDEX (98 symbols across 20 files)

FILE: ethtx_ce/app/api/__init__.py
  function create_app (line 29) | def create_app(
  function api_route (line 43) | def api_route(bp: Blueprint, *args, **kwargs):

FILE: ethtx_ce/app/api/decorators.py
  function auth_required (line 37) | def auth_required(func: Callable):
  function response (line 51) | def response(status: Optional[int] = 200):
  function limit_content_length (line 85) | def limit_content_length(max_length: Optional[int] = None):

FILE: ethtx_ce/app/api/endpoints/info.py
  function read_info (line 28) | def read_info():

FILE: ethtx_ce/app/api/endpoints/semantics.py
  function read_raw_semantic (line 30) | def read_raw_semantic(address: str, chain_id: Optional[str] = None):

FILE: ethtx_ce/app/api/endpoints/transactions.py
  function read_decoded_transaction (line 32) | def read_decoded_transaction(tx_hash: str, chain_id: Optional[str] = None):

FILE: ethtx_ce/app/api/exceptions.py
  class BaseRequestException (line 38) | class BaseRequestException:
    method __post_init__ (line 47) | def __post_init__(self):
  function handle_all_http_exceptions (line 58) | def handle_all_http_exceptions(error: HTTPException) -> BaseErrorType:
  function node_connection_error (line 64) | def node_connection_error(error) -> BaseErrorType:
  function processing_error (line 70) | def processing_error(error) -> BaseErrorType:
  function invalid_transaction_hash (line 76) | def invalid_transaction_hash(error) -> BaseErrorType:
  function transaction_not_found (line 82) | def transaction_not_found(error) -> BaseErrorType:
  function authorization_error (line 88) | def authorization_error(error) -> BaseErrorType:
  function malformed_request (line 94) | def malformed_request(error) -> BaseErrorType:
  function payload_too_large (line 100) | def payload_too_large(error) -> BaseErrorType:
  function resource_locked_error (line 106) | def resource_locked_error(error) -> BaseErrorType:
  function unexpected_error (line 112) | def unexpected_error(error) -> BaseErrorType:

FILE: ethtx_ce/app/api/utils.py
  function enable_direct (line 23) | def enable_direct(decorator):
  function as_dict (line 37) | def as_dict(cls):
  function delete_bstrings (line 47) | def delete_bstrings(obj):

FILE: ethtx_ce/app/config.py
  class Config (line 25) | class Config:
  class ProductionConfig (line 42) | class ProductionConfig(Config):
  class StagingConfig (line 51) | class StagingConfig(Config):
  class DevelopmentConfig (line 60) | class DevelopmentConfig(Config):

FILE: ethtx_ce/app/exceptions.py
  class FactoryAppException (line 32) | class FactoryAppException(Exception):
  class UnexpectedError (line 36) | class UnexpectedError(Exception):
    method __init__ (line 39) | def __init__(self):
  class RequestError (line 43) | class RequestError(Exception):
  class AuthorizationError (line 47) | class AuthorizationError(RequestError):
    method __init__ (line 50) | def __init__(self, msg: Optional[str] = None):
  class MalformedRequest (line 58) | class MalformedRequest(RequestError):
    method __init__ (line 61) | def __init__(self, msg):
  class PayloadTooLarge (line 65) | class PayloadTooLarge(RequestError):
    method __init__ (line 68) | def __init__(self, content_length: Union[float, int], max_content_leng...
  class MethodNotAllowed (line 75) | class MethodNotAllowed(RequestError):
    method __init__ (line 78) | def __init__(self, method: str):
  class ResourceLockedError (line 82) | class ResourceLockedError(RequestError):
    method __init__ (line 85) | def __init__(self):
  class EmptyResponseError (line 89) | class EmptyResponseError(RequestError):
    method __init__ (line 92) | def __init__(self, msg: str):
  class InternalError (line 96) | class InternalError(RequestError):
    method __init__ (line 99) | def __init__(self):

FILE: ethtx_ce/app/factory.py
  function create_app (line 31) | def create_app(

FILE: ethtx_ce/app/frontend/__init__.py
  function create_app (line 28) | def create_app(
  function frontend_route (line 50) | def frontend_route(bp: Blueprint, *args, **kwargs):

FILE: ethtx_ce/app/frontend/deps.py
  function verify_password (line 39) | def verify_password(username: str, password: str) -> bool:
  function get_eth_price (line 46) | def get_eth_price() -> Optional[float]:
  function extract_tx_hash_from_req (line 69) | def extract_tx_hash_from_req() -> str:

FILE: ethtx_ce/app/frontend/exceptions.py
  function render_error_page (line 34) | def render_error_page(status: Optional[int] = 500):
  function handle_all_http_exceptions (line 61) | def handle_all_http_exceptions(error: HTTPException) -> HTTPException:
  function node_connection_error (line 68) | def node_connection_error(error) -> str:
  function processing_error (line 75) | def processing_error(error) -> str:
  function invalid_transaction_hash (line 82) | def invalid_transaction_hash(error) -> str:
  function transaction_not_found (line 89) | def transaction_not_found(error) -> str:
  function authorization_error (line 96) | def authorization_error(error) -> str:
  function malformed_request (line 103) | def malformed_request(error) -> str:
  function payload_too_large (line 110) | def payload_too_large(error) -> str:
  function resource_locked_error (line 117) | def resource_locked_error(error) -> str:
  function empty_response (line 124) | def empty_response(error) -> str:
  function unexpected_error (line 131) | def unexpected_error(error) -> str:

FILE: ethtx_ce/app/frontend/semantics.py
  function semantics (line 50) | def semantics(address: str, chain_id: Optional[str] = None) -> show_sema...
  function reload_semantics (line 60) | def reload_semantics():
  function semantics_save (line 77) | def semantics_save():
  function poke_abi (line 84) | def poke_abi():
  function show_semantics_page (line 89) | def show_semantics_page(data: AddressSemantics) -> render_template:
  function _parameters_semantics (line 149) | def _parameters_semantics(parameters: List[Dict]) -> List[ParameterSeman...
  function _semantics_save (line 166) | def _semantics_save(data):
  function _poke_abi (line 263) | def _poke_abi(data):

FILE: ethtx_ce/app/frontend/static.py
  function search_page (line 25) | def search_page() -> render_template:

FILE: ethtx_ce/app/frontend/transactions.py
  function read_decoded_transaction (line 33) | def read_decoded_transaction(
  function show_transaction_page (line 58) | def show_transaction_page(data: DecodedTransaction) -> render_template:

FILE: ethtx_ce/app/helpers.py
  function register_blueprints (line 31) | def register_blueprints(
  function class_import (line 54) | def class_import(name: str) -> Any:
  class Singleton (line 63) | class Singleton(type):
    method __call__ (line 66) | def __call__(cls, *args, **kwargs):
  function read_ethtx_versions (line 72) | def read_ethtx_versions(app: Flask) -> None:
  function get_latest_ethtx_version (line 94) | def get_latest_ethtx_version() -> str:
  function _get_version_from_git (line 107) | def _get_version_from_git() -> Tuple[str, str]:
  function _get_version_from_docker (line 118) | def _get_version_from_docker() -> Tuple[str, str]:
  function _clean_up_git_link (line 123) | def _clean_up_git_link(git_link: str) -> str:

FILE: ethtx_ce/app/logger.py
  function setup_logging (line 25) | def setup_logging(app: Flask):
  function setup_external_logging (line 42) | def setup_external_logging() -> None:

FILE: ethtx_ce/tests/flask_test.py
  class TestFlask (line 6) | class TestFlask:
    method client (line 8) | def client(self):
    method test_landing_page (line 14) | def test_landing_page(self, client):
    method test_semantics_is_secured_with_basic_auth (line 20) | def test_semantics_is_secured_with_basic_auth(self, client):

FILE: ethtx_ce/tests/mocks/mocks.py
  class MockWeb3Provider (line 8) | class MockWeb3Provider:
    method add_mocked_block_details (line 58) | def add_mocked_block_details(self, block_number, block_details: Dict):
    method get_transaction (line 61) | def get_transaction(self, tx_hash):
    method get_transaction_receipt (line 64) | def get_transaction_receipt(self, tx_hash):
    method get_block (line 95) | def get_block(self, block_number: int) -> W3Block:
  class Mocks (line 99) | class Mocks:
    method get_mocked_w3_log (line 101) | def get_mocked_w3_log():
  class TestMocks (line 118) | class TestMocks:
    method test_can_create_w3_log (line 119) | def test_can_create_w3_log(self):
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (161K chars).
[
  {
    "path": ".dockerignore",
    "chars": 85,
    "preview": ".git\n.github\n.gitignore\n.pre-commit*\n.env\n.docker_env\n.env_sample\ndocker-compose.yaml"
  },
  {
    "path": ".env_sample",
    "chars": 1285,
    "preview": "\n# Proper nodes are required to run ethtx, provide connection strings for chains which will be used.\nMAINNET_NODE_URL=ht"
  },
  {
    "path": ".gitignore",
    "chars": 2101,
    "preview": "# Ignore pipenv's lock\nPipfile.lock\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C exten"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 818,
    "preview": "repos:\n  - repo: https://github.com/psf/black\n    rev: 22.3.0\n    hooks:\n      - id: black\n        language_version: pyt"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 7965,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\n\n## 0.2.16 - 2022-11-25\n### Changed\n- "
  },
  {
    "path": "DEVELOPMENT.md",
    "chars": 679,
    "preview": "# Local Development\n\nThis repository contains 2 basic applications: `frontend` & `api`. It is easy to manage, and you ca"
  },
  {
    "path": "Dockerfile",
    "chars": 688,
    "preview": "FROM python:3.9\n\nWORKDIR /app/\n\n# Upgrade pip, install pipenv\nRUN pip install --upgrade pip && pip install pipenv\n\n# Cop"
  },
  {
    "path": "LICENSE",
    "chars": 10141,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Makefile",
    "chars": 1022,
    "preview": "help:\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\"
  },
  {
    "path": "NOTICE",
    "chars": 500,
    "preview": "Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\nCopyright 2021-2022 Token"
  },
  {
    "path": "Pipfile",
    "chars": 453,
    "preview": "[[source]]\nurl = \"https://pypi.org/simple\"\nverify_ssl = true\nname = \"pypi\"\n\n[packages]\nethtx = \"==0.3.22\"\npython-dotenv "
  },
  {
    "path": "README.md",
    "chars": 5530,
    "preview": "<h1 align='center' style=\"border-bottom: none\">\n  EthTx Community Edition\n</h1>\n<br/>\n<p align=\"center\">\n    <em>Communi"
  },
  {
    "path": "docker-compose.override.yml",
    "chars": 267,
    "preview": "version: \"3.6\"\nservices:\n  ethtx_ce:\n    ports:\n    - \"5000:5000\"\n    build:\n      context: .\n      dockerfile: Dockerfi"
  },
  {
    "path": "docker-compose.yaml",
    "chars": 662,
    "preview": "version: \"3.6\"\n\nservices:\n  ethtx_ce:\n    image: 'ethtx_ce:${TAG-latest}'\n    env_file:\n      - .env\n    depends_on:\n   "
  },
  {
    "path": "ethtx_ce/.flake8",
    "chars": 197,
    "preview": "[flake8]\nmax-line-length = 130\nexclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache\nextend-ignore =\n    # Se"
  },
  {
    "path": "ethtx_ce/.gitignore",
    "chars": 25,
    "preview": "__pycache__\napp.egg-info\n"
  },
  {
    "path": "ethtx_ce/app/__init__.py",
    "chars": 1048,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/__init__.py",
    "chars": 2236,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/decorators.py",
    "chars": 3452,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/endpoints/__init__.py",
    "chars": 1153,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/endpoints/info.py",
    "chars": 1723,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/endpoints/semantics.py",
    "chars": 1620,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/endpoints/transactions.py",
    "chars": 1972,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/exceptions.py",
    "chars": 4128,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/api/utils.py",
    "chars": 2395,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/config.py",
    "chars": 2150,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/exceptions.py",
    "chars": 3082,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/factory.py",
    "chars": 2044,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/frontend/__init__.py",
    "chars": 2132,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/frontend/deps.py",
    "chars": 2569,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/frontend/exceptions.py",
    "chars": 4153,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/frontend/semantics.py",
    "chars": 15946,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/frontend/static/ethtx.new.css",
    "chars": 8143,
    "preview": "/*Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)*/\n/*Copyright 2021-2022"
  },
  {
    "path": "ethtx_ce/app/frontend/static.py",
    "chars": 1629,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/frontend/templates/exception.html",
    "chars": 3455,
    "preview": "<!--\n# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2"
  },
  {
    "path": "ethtx_ce/app/frontend/templates/index.html",
    "chars": 5952,
    "preview": "<!--\n# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2"
  },
  {
    "path": "ethtx_ce/app/frontend/templates/partials/headtags.html",
    "chars": 1840,
    "preview": "<!--\n# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2"
  },
  {
    "path": "ethtx_ce/app/frontend/templates/semantics.html",
    "chars": 11383,
    "preview": "<!--\n# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2"
  },
  {
    "path": "ethtx_ce/app/frontend/templates/transaction.html",
    "chars": 15750,
    "preview": "<!--\n# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2"
  },
  {
    "path": "ethtx_ce/app/frontend/transactions.py",
    "chars": 2784,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/helpers.py",
    "chars": 4244,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/logger.py",
    "chars": 1885,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/app/wsgi.py",
    "chars": 2015,
    "preview": "# Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce)\n# Copyright 2021-2022 T"
  },
  {
    "path": "ethtx_ce/entrypoint.sh",
    "chars": 643,
    "preview": "#!/usr/bin/env sh\nset -e\n\necho \"ethtx_ce entrypoint.sh\"\n\nif [ -f /app/app/wsgi.py ]; then\n    DEFAULT_MODULE_NAME=app.ws"
  },
  {
    "path": "ethtx_ce/gunicorn_conf.py",
    "chars": 1939,
    "preview": "import json\nimport multiprocessing\nimport os\n\nworkers_per_core_str = os.getenv(\"WORKERS_PER_CORE\", \"1\")\nmax_workers_str "
  },
  {
    "path": "ethtx_ce/log_cfg.json",
    "chars": 796,
    "preview": "{\n  \"version\": 1,\n  \"disable_existing_loggers\": false,\n  \"formatters\": {\n    \"extra\": {\n      \"format\": \"%(asctime)s.%(m"
  },
  {
    "path": "ethtx_ce/start-reload.sh",
    "chars": 141,
    "preview": "#! /usr/bin/env sh\nset -e\n\n# Start Gunicorn\necho \"Starting Gunicorn...\"\nexec pipenv run gunicorn \"--reload\" -c \"$GUNICOR"
  },
  {
    "path": "ethtx_ce/start.sh",
    "chars": 130,
    "preview": "#! /usr/bin/env sh\nset -e\n\n# Start Gunicorn\necho \"Starting Gunicorn...\"\nexec pipenv run gunicorn -c \"$GUNICORN_CONF\" \"$A"
  },
  {
    "path": "ethtx_ce/tests/flask_test.py",
    "chars": 698,
    "preview": "import pytest\n\nfrom app.frontend import create_app\n\n\nclass TestFlask:\n    @pytest.fixture\n    def client(self):\n        "
  },
  {
    "path": "ethtx_ce/tests/mocks/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "ethtx_ce/tests/mocks/mocks.py",
    "chars": 4052,
    "preview": "from typing import Dict\n\nfrom ethtx.models.w3_model import W3Transaction, W3Block, W3Receipt, W3Log\nfrom ethtx.utils.att"
  },
  {
    "path": "scripts/git_version_for_docker.sh",
    "chars": 310,
    "preview": "#! /usr/bin/env bash\n\n# Execute in root project directory\n# Exit in case of error\nset -e\n\nremote_url=$(git config --get "
  }
]

About this extraction

This page contains the full source code of the EthTx/ethtx_ce GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (148.4 KB), approximately 38.3k tokens, and a symbol index with 98 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!