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  subdirectory.
- `create_app` function (created in new package in `init` file) which returns `Flask` object by
calling  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&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"
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
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.