EthTx - Ethereum transactions decoder

Overview

EthTx - Ethereum transactions decoder

Python Black OpenSource Apache

Installation

pip install ethtx

Requirements

The package needs a few external resources, defined in EthTxConfig object:

  1. Geth node - required to have access to the raw Ethereum data; it must be a full archive node with the debug option ON
  2. MongoDB database - required to store smart contracts' ABI and semantics used in the decoding process; it can be a Mongo Atlas or local instance
  3. Etherscan API key - required to get the source code and ABI for smart contracts used in transaction

Getting started

from ethtx import EthTx, EthTxConfig
from ethtx.models.decoded_model import DecodedTransaction


ethtx_config = EthTxConfig(
    mongo_connection_string= ##MongoDB connection string,
    mongo_database= ##MongoDB database,
    etherscan_api_key= ##Etherscan API key,
    web3nodes={
        "mainnet": ##Geth archive node URL,
    },
    default_chain="mainnet",
    etherscan_urls={
        "mainnet": "https://api.etherscan.io/api",
    },
)

ethtx = EthTx.initialize(ethtx_config)
transaction: DecodedTransaction = ethtx.decoders.decode_transaction('0x50051e0a6f216ab9484c2080001c7e12d5138250acee1f4b7c725b8fb6bb922d')

Features

EthTx most important functions:

  1. raw node data access:
ethtx = EthTx.initialize(ethtx_config)
web3provider = ethtx.providers.web3provider

from ethtx.models.w3_model import W3Transaction, W3Block, W3Receipt, W3CallTree

# read raw transaction data directly from the node
w3transaction: W3Transaction = web3provider.get_transaction('0x50051e0a6f216ab9484c2080001c7e12d5138250acee1f4b7c725b8fb6bb922d')
w3block: W3Block = web3provider.get_block(w3transaction.blockNumber)
w3receipt: W3Receipt = web3provider.get_receipt(w3transaction.hash.hex())
w3calls: W3CallTree = web3provider.get_calls(w3transaction.hash.hex()
  1. ABI decoding:
from ethtx.models.objects_model import Transaction, Event, Call
from ethtx.models.decoded_model import DecodedEvent, DecodedCall, DecodedTransfer, DecodedBalance

# read the raw transaction from the node
transaction: Transaction = web3provider.get_full_transaction('0x50051e0a6f216ab9484c2080001c7e12d5138250acee1f4b7c725b8fb6bb922d')

# decode transaction components
abi_decoded_events: List[Event] = ethtx.decoders.abi_decoder.decode_events(transaction.events, transaction.metadata)
abi_decoded_calls: DecodedCall = ethtx.decoders.abi_decoder.decode_calls(transaction.root_call, transaction.metadata)
abi_decoded_transfers: List[DecodedTransfer] = ethtx.decoders.abi_decoder.decode_transfers(abi_decoded_calls, abi_decoded_events)
abi_decoded_balances: List[DecodedBalance] = ethtx.decoders.abi_decoder.decode_balances(abi_decoded_transfers)

# decode a single event
raw_event: Event = transaction.events[3]
abi_decoded_event: DecodedEvent = ethtx.decoders.abi_decoder.decode_event(raw_event, transaction.metadata)

# decode a single call
raw_call: Call = transaction.root_call.subcalls[3].subcalls[2]
abi_decoded_call: DecodedCall = ethtx.decoders.abi_decoder.decode_call(raw_call, transaction.metadata)
  1. Semantic decoding:
from ethtx.models.decoded_model import DecodedTransactionMetadata

# get proxies used in the transaction
proxies = ethtx.decoders.get_proxies(transaction.root_call)

# semantically decode transaction components
decoded_metadata: DecodedTransactionMetadata = ethtx.decoders.semantic_decoder.decode_metadata(block.metadata, transaction.metadata)
decoded_events: List[DecodedEvent] = ethtx.decoders.semantic_decoder.decode_events(abi_decoded_events, decoded_metadata, token_proxies)
decoded_calls: Call = ethtx.decoders.semantic_decoder.decode_calls(abi_decoded_calls, decoded_metadata, proxies)
decoded_transfers: List[DecodedTransfer] = ethtx.decoders.semantic_decoder.decode_transfers(abi_decoded_transfers)
decoded_balances: List[DecodedBalance] = ethtx.decoders.semantic_decoder.decode_balances(abi_decoded_balances)

# semantically decode a single event
decoded_event: DecodedEvent = ethtx.decoders.semantic_decoder.decode_event(abi_decoded_events[0], decoded_metadata, proxies)
# semantically decode a single call
decoded_call: Call = ethtx.decoders.semantic_decoder.decode_call(abi_decoded_calls.subcalls[2].subcalls[0], decoded_metadata, proxies)
Comments
  • No module named 'ethtx.semantics'

    No module named 'ethtx.semantics'

    Windows 10. Python version 3.10.4. Latest version for ethtx is installed with pip install ethtx.

    I created a file test.py with the following content:

    from ethtx import EthTx, EthTxConfig
    from ethtx.models.decoded_model import DecodedTransaction
    
    ethtx_config = EthTxConfig(
        etherscan_api_key="my etherscan API key",  ##Etherscan API key,
        web3nodes={
            "mainnet": {
                "hook": "my Infura url",  # multiple nodes supported, separate them with comma
                "poa": False  # represented by bool value
            }
        },
        default_chain="mainnet",
        etherscan_urls={"mainnet": "https://api.etherscan.io/api" }
    )
    
    ethtx = EthTx.initialize(ethtx_config)
    transaction: DecodedTransaction = ethtx.decoders.decode_transaction(
        '0x50051e0a6f216ab9484c2080001c7e12d5138250acee1f4b7c725b8fb6bb922d')
    

    Once I try to execute the file with py test.py, I get the following error:

    Traceback (most recent call last):
      File "C:\Users\Utente\Desktop\ethtx\test.py", line 1, in <module>
        from ethtx import EthTx, EthTxConfig
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\__init__.py", line 13, in <module>
        from .ethtx import EthTx, EthTxConfig
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\ethtx.py", line 18, in <module>
        from .decoders.abi.decoder import ABIDecoder
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\decoders\abi\decoder.py", line 32, in <module>
        from .abc import IABIDecoder
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\decoders\abi\abc.py", line 25, in <module>
        from ethtx.providers.semantic_providers import SemanticsRepository
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\providers\semantic_providers\__init__.py", line 15, in <module>
        from .repository import SemanticsRepository
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\providers\semantic_providers\repository.py", line 30, in <module>
        from ethtx.semantics.protocols_router import amend_contract_semantics
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\semantics\protocols_router.py", line 21, in <module>
        def amend_contract_semantics(semantics: ContractSemantics, router_=Router()):
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\semantics\router.py", line 30, in __new__
        return cls._get_semantics()
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\site-packages\ethtx\semantics\router.py", line 52, in _get_semantics
        imported_module = importlib.import_module(
      File "C:\Users\Utente\AppData\Local\Programs\Python\Python310\lib\importlib\__init__.py", line 126, in import_module
        return _bootstrap._gcd_import(name[level:], package, level)
    ModuleNotFoundError: No module named 'ethtx.semanticsC:\\Users\\Utente\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\ethtx\\semantics\\base'
    

    What's the issue? Thanks for the help.

    opened by yuripaoloni 10
  • from_address is none

    from_address is none

    When I run get start script,I get error: Traceback (most recent call last): File "Code/python/Blockchain/eth_trans_decode.py", line 23, in transaction: DecodedTransaction = ethtx.decoders.decode_transaction( File "/opt/anaconda3/envs/py3.8/lib/python3.8/site-packages/ethtx/ethtx.py", line 67, in decode_transaction return self._decoder_service.decode_transaction(chain_id, tx_hash) File "/opt/anaconda3/envs/py3.8/lib/python3.8/site-packages/ethtx/decoders/decoder_service.py", line 47, in decode_transaction transaction = self.web3provider.get_full_transaction( File "/opt/anaconda3/envs/py3.8/lib/python3.8/site-packages/ethtx/providers/web3_provider.py", line 466, in get_full_transaction w3calltree = self.get_calls(tx_hash, chain_id) File "/opt/anaconda3/envs/py3.8/lib/python3.8/site-packages/ethtx/providers/web3_provider.py", line 255, in get_calls return self._create_call_from_debug_trace_tx( File "/opt/anaconda3/envs/py3.8/lib/python3.8/site-packages/ethtx/providers/web3_provider.py", line 490, in _create_call_from_debug_trace_tx main_parent = W3CallTree(tx_hash=tx_hash, chain_id=chain_id, **w3input) File "pydantic/main.py", line 406, in pydantic.main.BaseModel.init pydantic.error_wrappers.ValidationError: 2 validation errors for W3CallTree type field required (type=value_error.missing) from_address none is not an allowed value (type=type_error.none.not_allowed)

    When debug, I found from_address is set to none in ethtx/providers/web3_provider.py , 479 line.

    I have no idea why? And I print the debug_transaction response , it dose not contain from_address , either.

    bug 
    opened by wulasite 10
  • OverflowError: integer division result too large for a float in calls.py L194

    OverflowError: integer division result too large for a float in calls.py L194

    When I install and run starter demo, I put a tx hash : 0x4f378f91bf16a26cff104aea5eceaa66c72d128dade84ddb619d40426f9780a0 and got an error report like the output below:

    Traceback (most recent call last):
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/decoder.py", line 171, in _decode_transaction
        full_decoded_transaction.calls = self.decode_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/decoder.py", line 68, in decode_calls
        return ABICallsDecoder(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 55, in decode
        calls_tree = self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 237, in _decode_nested_calls
        self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 237, in _decode_nested_calls
        self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 237, in _decode_nested_calls
        self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 224, in _decode_nested_calls
        decoded = self.decode_call(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 194, in decode_call
        value=call.call_value / 10**18,
    OverflowError: integer division result too large for a float
    Traceback (most recent call last):
      File "ethtxdemo.py", line 18, in <module>
        transaction: DecodedTransaction = ethtx.decoders.decode_transaction('0x4f378f91bf16a26cff104aea5eceaa66c72d128dade84ddb619d40426f9780a0')
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/ethtx.py", line 67, in decode_transaction
        return self._decoder_service.decode_transaction(chain_id, tx_hash)
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/decoder_service.py", line 63, in decode_transaction
        abi_decoded_tx = self.abi_decoder.decode_transaction(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/decoder.py", line 54, in decode_transaction
        full_decoded_transaction = self._decode_transaction(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/decoder.py", line 180, in _decode_transaction
        raise e
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/decoder.py", line 171, in _decode_transaction
        full_decoded_transaction.calls = self.decode_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/decoder.py", line 68, in decode_calls
        return ABICallsDecoder(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 55, in decode
        calls_tree = self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 237, in _decode_nested_calls
        self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 237, in _decode_nested_calls
        self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 237, in _decode_nested_calls
        self._decode_nested_calls(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 224, in _decode_nested_calls
        decoded = self.decode_call(
      File "/home/ubuntu/.local/lib/python3.8/site-packages/ethtx/decoders/abi/calls.py", line 194, in decode_call
        value=call.call_value / 10**18,
    OverflowError: integer division result too large for a float
    

    I think maybe L194

    value=call.call_value / 10**18,
    

    could be replaced by

    value=Decimal(call.call_value) / 10**18,
    

    and a import should also be added.

    I will push a PR soon

    bug 
    opened by hitdavid 9
  • Getting started script not working

    Getting started script not working

    Just discovered this repo. Really interested to test it out.

    However, when I tried to run the 'Getting started1' script in README, I came across a few errors:

    Error 1:

    File "test.py", line 6
        mongo_database = "mongomock://localhost"
        ^
    SyntaxError: invalid syntax
    

    I got passed this by adding commas before each inline comment. Then I got another error:

    Error 2:

    Traceback (most recent call last):
      File "test.py", line 4, in <module>
        ethtx_config = EthTxConfig(
    TypeError: __init__() got an unexpected keyword argument 'mongo_database'
    

    I looked into ethtx.py and didn't see any mongo_database attribute. So I commented the mongo_database line out. Then I got another error:

    Error 3:

    Traceback (most recent call last):
      File "test.py", line 18, in <module>
        ethtx = EthTx.initialize(ethtx_config)
      File "/home/lsa/anaconda3/lib/python3.8/site-packages/ethtx/ethtx.py", line 122, in initialize
        repository = MongoSemanticsDatabase(db=mongo_client.get_database())
      File "/home/lsa/anaconda3/lib/python3.8/site-packages/mongomock/mongo_client.py", line 133, in get_database
        db = self.get_default_database(
      File "/home/lsa/anaconda3/lib/python3.8/site-packages/mongomock/mongo_client.py", line 151, in get_default_database
        raise ConfigurationError('No default database name defined or provided.')
    pymongo.errors.ConfigurationError: No default database name defined or provided.
    

    Can you advise if I am doing something incorrectly? Thanks.

    bug documentation 
    opened by 0x1355 5
  • Account balances not adding up for Eth for USDT transaction on UniswapV3

    Account balances not adding up for Eth for USDT transaction on UniswapV3

    So it seems that there is some bug with how multicalls are being handled.

    here https://etherscan.io/tx/0x093cdad6594e26543fdec775ddb99c6a761bace9512332a5aec50b38b39e92d9

    You see that I have received $126 USDT for .1 eth.

    However then the transaction is analyzed it does not seem to reflect that: https://ethtx.info/mainnet/0x093cdad6594e26543fdec775ddb99c6a761bace9512332a5aec50b38b39e92d9/

    Other tool seems to handle this better https://phalcon.blocksec.com/tx/eth/0x093cdad6594e26543fdec775ddb99c6a761bace9512332a5aec50b38b39e92d9.

    Anyone know if this is a bug... or am i just misinterpreting the result.

    bug 
    opened by lemiesz 4
  • gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>

    gunicorn.errors.HaltServer:

    Version:0.3.1

    ethtx_ce_1       | [2021-10-15 13:09:57 +0000] [7] [INFO] Starting gunicorn 20.1.0
    ethtx_ce_1       | [2021-10-15 13:09:57 +0000] [7] [INFO] Listening at: http://0.0.0.0:5000 (7)
    ethtx_ce_1       | [2021-10-15 13:09:57 +0000] [7] [INFO] Using worker: sync
    ethtx_ce_1       | [2021-10-15 13:09:57 +0000] [14] [INFO] Booting worker with pid: 14
    ethtx_ce_1       | [2021-10-15 13:09:57 +0000] [15] [INFO] Booting worker with pid: 15
    ethtx_ce_1       | [2021-10-15 13:09:57 +0000] [16] [INFO] Booting worker with pid: 16
    ethtx_ce_1       | [2021-10-15 13:09:57 +0000] [17] [INFO] Booting worker with pid: 17
    ethtx_ce_1       | [2021-10-15 13:09:58 +0000] [14] [ERROR] Exception in worker process
    ethtx_ce_1       | Traceback (most recent call last):
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
    ethtx_ce_1       |     worker.init_process()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 134, in init_process
    ethtx_ce_1       |     self.load_wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
    ethtx_ce_1       |     self.wsgi = self.app.wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/base.py", line 67, in wsgi
    ethtx_ce_1       |     self.callable = self.load()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
    ethtx_ce_1       |     return self.load_wsgiapp()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
    ethtx_ce_1       |     return util.import_app(self.app_uri)
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/util.py", line 359, in import_app
    ethtx_ce_1       |     mod = importlib.import_module(module)
    ethtx_ce_1       |   File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
    ethtx_ce_1       |     return _bootstrap._gcd_import(name[level:], package, level)
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 991, in _find_and_load
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap_external>", line 843, in exec_module
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
    ethtx_ce_1       |   File "/app/wsgi.py", line 23, in <module>
    ethtx_ce_1       |     ethtx_config = EthTxConfig(
    ethtx_ce_1       | TypeError: __init__() got an unexpected keyword argument 'mongo_database'
    ethtx_ce_1       | [2021-10-15 13:09:58 +0000] [14] [INFO] Worker exiting (pid: 14)
    ethtx_ce_1       | [2021-10-15 13:09:58 +0000] [15] [ERROR] Exception in worker process
    ethtx_ce_1       | Traceback (most recent call last):
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
    ethtx_ce_1       |     worker.init_process()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 134, in init_process
    ethtx_ce_1       |     self.load_wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
    ethtx_ce_1       |     self.wsgi = self.app.wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/base.py", line 67, in wsgi
    ethtx_ce_1       |     self.callable = self.load()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
    ethtx_ce_1       |     return self.load_wsgiapp()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
    ethtx_ce_1       |     return util.import_app(self.app_uri)
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/util.py", line 359, in import_app
    ethtx_ce_1       |     mod = importlib.import_module(module)
    ethtx_ce_1       |   File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
    ethtx_ce_1       |     return _bootstrap._gcd_import(name[level:], package, level)
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 991, in _find_and_load
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap_external>", line 843, in exec_module
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
    ethtx_ce_1       |   File "/app/wsgi.py", line 23, in <module>
    ethtx_ce_1       |     ethtx_config = EthTxConfig(
    ethtx_ce_1       | TypeError: __init__() got an unexpected keyword argument 'mongo_database'
    ethtx_ce_1       | [2021-10-15 13:09:58 +0000] [15] [INFO] Worker exiting (pid: 15)
    ethtx_ce_1       | [2021-10-15 13:09:59 +0000] [7] [WARNING] Worker with pid 15 was terminated due to signal 15
    ethtx_ce_1       | [2021-10-15 13:09:59 +0000] [16] [ERROR] Exception in worker process
    ethtx_ce_1       | Traceback (most recent call last):
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
    ethtx_ce_1       |     worker.init_process()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 134, in init_process
    ethtx_ce_1       |     self.load_wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
    ethtx_ce_1       |     self.wsgi = self.app.wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/base.py", line 67, in wsgi
    ethtx_ce_1       |     self.callable = self.load()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
    ethtx_ce_1       |     return self.load_wsgiapp()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
    ethtx_ce_1       |     return util.import_app(self.app_uri)
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/util.py", line 359, in import_app
    ethtx_ce_1       |     mod = importlib.import_module(module)
    ethtx_ce_1       |   File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
    ethtx_ce_1       |     return _bootstrap._gcd_import(name[level:], package, level)
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 991, in _find_and_load
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap_external>", line 843, in exec_module
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
    ethtx_ce_1       |   File "/app/wsgi.py", line 23, in <module>
    ethtx_ce_1       |     ethtx_config = EthTxConfig(
    ethtx_ce_1       | TypeError: __init__() got an unexpected keyword argument 'mongo_database'
    ethtx_ce_1       | [2021-10-15 13:09:59 +0000] [16] [INFO] Worker exiting (pid: 16)
    ethtx_ce_1       | [2021-10-15 13:09:59 +0000] [17] [ERROR] Exception in worker process
    ethtx_ce_1       | Traceback (most recent call last):
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
    ethtx_ce_1       |     worker.init_process()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 134, in init_process
    ethtx_ce_1       |     self.load_wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
    ethtx_ce_1       |     self.wsgi = self.app.wsgi()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/base.py", line 67, in wsgi
    ethtx_ce_1       |     self.callable = self.load()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
    ethtx_ce_1       |     return self.load_wsgiapp()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
    ethtx_ce_1       |     return util.import_app(self.app_uri)
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/util.py", line 359, in import_app
    ethtx_ce_1       |     mod = importlib.import_module(module)
    ethtx_ce_1       |   File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
    ethtx_ce_1       |     return _bootstrap._gcd_import(name[level:], package, level)
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 991, in _find_and_load
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
    ethtx_ce_1       |   File "<frozen importlib._bootstrap_external>", line 843, in exec_module
    ethtx_ce_1       |   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
    ethtx_ce_1       |   File "/app/wsgi.py", line 23, in <module>
    ethtx_ce_1       |     ethtx_config = EthTxConfig(
    ethtx_ce_1       | TypeError: __init__() got an unexpected keyword argument 'mongo_database'
    ethtx_ce_1       | [2021-10-15 13:09:59 +0000] [17] [INFO] Worker exiting (pid: 17)
    ethtx_ce_1       | Traceback (most recent call last):
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 209, in run
    ethtx_ce_1       |     self.sleep()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 357, in sleep
    ethtx_ce_1       |     ready = select.select([self.PIPE[0]], [], [], 1.0)
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 242, in handle_chld
    ethtx_ce_1       |     self.reap_workers()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 525, in reap_workers
    ethtx_ce_1       |     raise HaltServer(reason, self.WORKER_BOOT_ERROR)
    ethtx_ce_1       | gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>
    ethtx_ce_1       | 
    ethtx_ce_1       | During handling of the above exception, another exception occurred:
    ethtx_ce_1       | 
    ethtx_ce_1       | Traceback (most recent call last):
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/bin/gunicorn", line 8, in <module>
    ethtx_ce_1       |     sys.exit(run())
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 67, in run
    ethtx_ce_1       |     WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/base.py", line 231, in run
    ethtx_ce_1       |     super().run()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/app/base.py", line 72, in run
    ethtx_ce_1       |     Arbiter(self).run()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 229, in run
    ethtx_ce_1       |     self.halt(reason=inst.reason, exit_status=inst.exit_status)
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 342, in halt
    ethtx_ce_1       |     self.stop()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 393, in stop
    ethtx_ce_1       |     time.sleep(0.1)
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 242, in handle_chld
    ethtx_ce_1       |     self.reap_workers()
    ethtx_ce_1       |   File "/root/.local/share/virtualenvs/app-4PlAip0Q/lib/python3.8/site-packages/gunicorn/arbiter.py", line 525, in reap_workers
    ethtx_ce_1       |     raise HaltServer(reason, self.WORKER_BOOT_ERROR)
    ethtx_ce_1       | gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>
    ethtx_ce_ethtx_ce_1 exited with code 1
    
    opened by z3roTo0ne 3
  • Fix loss of precision decimal numbers

    Fix loss of precision decimal numbers

    Fixed the issue of loss of precision in the decimal representation of large floats. Converted float to Decimals and changed the precision of the context to 256 i.e. getcontext().prec = 256. Eventually returns the string representation of the Decimal object.

    This fixes the issue of scientific notation representation of floats both in Argument value field and also other fields in models from decoded_model.py

    opened by dudzicz 2
  • ENS lookup bug

    ENS lookup bug

    I was comparing ethtx output to other similar tools and found a case where ethtx resolves an incorrect ENS address.

    In ethtx.info, the offerer value is babywhale.eth. Clicking on babywhale.eth in ethtx uses the address 0x8af6e15ed513b5b73573f58158b1b0bbd5085ec7. But babywhale.eth on etherscan resolves to 0x9bf2eb61a3beb7afb1213029f3f1ae3e08ede755 https://ethtx.info/mainnet/0x665c4db0b7dcf44eb2ead3d455b12bfb244de9a2d3239d756ca3fbdbf21817bb/

    Meanwhile, two other transaction explorers (which don't support ENS resolution) show the value 0x8af6e15ed513b5b73573f58158b1b0bbd5085ec7 for the offerer https://phalcon.blocksec.com/tx/eth/0x665c4db0b7dcf44eb2ead3d455b12bfb244de9a2d3239d756ca3fbdbf21817bb https://tx.eth.samczsun.com/ethereum/0x665c4db0b7dcf44eb2ead3d455b12bfb244de9a2d3239d756ca3fbdbf21817bb

    0x8af6e15ed513b5b73573f58158b1b0bbd5085ec7 does hold two ENS addresses, but babywhale.eth is not one of them, so the wrong ENS address is getting displayed.

    bug 
    opened by engn33r 1
  • More flexible dependencies, no cursor timeout, cache 4bytes

    More flexible dependencies, no cursor timeout, cache 4bytes

    • Make more flexible deps - ethtx is easier to install in other apps
    • No timeout for mongo cursor - some collections may sometimes require more time to search
    • Cache 4bytes resposne, if some transactions have a lot of guessed functions/events, it definitely speeds up ethtx!
    • Fix README mongo string
    opened by kchojn 1
  • Port and standardization models with `pydantic`

    Port and standardization models with `pydantic`

    • all models now use pydantic
    • fix types in models
    • removed jsonpickle
    • extend function end event models
    • changing the order of methods in DecoderService
    • update requirements
    • add tests
    • fix bug with empty args from 4byte
    • update Readme
    • strip node URL from env
    opened by kchojn 1
  • Bug in tx data decoding (token transfers & account balances sections)

    Bug in tx data decoding (token transfers & account balances sections)

    Referring to this tx on etherscan, address 0x8c3FA50473065f1D90f186cA8ba1Aa76Aee409Bb is receiving 0.01 eth but as per the tx on ethtx.info the address is sending out the same amount.

    The emitted events show it as a donation sent event which would indicate the aforementioned address should be the recipient of the amount rather than the one sending it, and hence, I'm inclined to believe that the decoding on etherscan is correct and ethtx's isn't.

    I tried to make some more sense of the data and these points might help with debugging the issue: 0x8c3f.. is a gnosis safe multisig. Ethtx shows 0x8c3f.. is sending the eth to 0x34cf.., this recipient is actually the gnosis safe mastercopy contract. I think this is where the problem lies. this mastercopy be receiving the funds from the multisig doesn't seem right to me

    bug 
    opened by shreyjain711 2
  • Account balances broken for specific transaction

    Account balances broken for specific transaction

    opened by franz101 0
  • feat: erc1155 support

    feat: erc1155 support

    Tested with tx: 0x9848cc10366cf7dde89861a6ae5e1166e7d1edb6cd245c2592679848959f168c Decoded tx (removed block and tx metadata):

    events=[DecodedEvent(chain_id='mainnet', tx_hash='0x9848cc10366cf7dde89861a6ae5e1166e7d1edb6cd245c2592679848959f168c', timestamp=datetime.datetime(2021, 12, 13, 12, 16, 50), contract=AddressInfo(address='0x348fc118bcc65a92dc033a951af153d14d945312', name='Mintvial', badge='receiver'), index=261, call_id=None, event_signature='0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', event_name='TransferSingle', parameters=[Argument(name='operator', type='address', value=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender')), Argument(name='from', type='address', value=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender')), Argument(name='to', type='address', value=AddressInfo(address='0x0000000000000000000000000000000000000000', name='0x0000000000000000000000000000000000000000', badge=None)), Argument(name='id', type='uint256', value=913), Argument(name='value', type='uint256', value=1)], event_guessed=False), DecodedEvent(chain_id='mainnet', tx_hash='0x9848cc10366cf7dde89861a6ae5e1166e7d1edb6cd245c2592679848959f168c', timestamp=datetime.datetime(2021, 12, 13, 12, 16, 50), contract=AddressInfo(address='0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b', name='CloneX', badge=None), index=262, call_id=None, event_signature='0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', event_name='Transfer', parameters=[Argument(name='from', type='address', value=AddressInfo(address='0x0000000000000000000000000000000000000000', name='0x0000000000000000000000000000000000000000', badge=None)), Argument(name='to', type='address', value=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender')), Argument(name='tokenId', type='nft', value={'address': '0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b?a=14609#inventory', 'name': 'NFT 14609'})], event_guessed=False), DecodedEvent(chain_id='mainnet', tx_hash='0x9848cc10366cf7dde89861a6ae5e1166e7d1edb6cd245c2592679848959f168c', timestamp=datetime.datetime(2021, 12, 13, 12, 16, 50), contract=AddressInfo(address='0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b', name='CloneX', badge=None), index=263, call_id=None, event_signature='0x2ce54db2c0bc64dc675f5fae90636ec2f0c88dbd8e7c6a19c9caca9193741b15', event_name='CloneXRevealed', parameters=[Argument(name='tokenId', type='uint256', value=14609), Argument(name='fileId', type='string', value='13192')], event_guessed=False)] calls=DecodedCall(chain_id='mainnet', timestamp=datetime.datetime(2021, 12, 13, 12, 16, 50), tx_hash='0x9848cc10366cf7dde89861a6ae5e1166e7d1edb6cd245c2592679848959f168c', call_id='', call_type='call', from_address=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender'), to_address=AddressInfo(address='0x348fc118bcc65a92dc033a951af153d14d945312', name='Mintvial', badge='receiver'), value=0.0, function_signature='0x5f6be614', function_name='migrateToken', arguments=[Argument(name='id', type='uint256', value=913)], outputs=[Argument(name='', type='uint256', value=14609)], gas_used=164970, error=None, status=True, indent=0, subcalls=[DecodedCall(chain_id='mainnet', timestamp=datetime.datetime(2021, 12, 13, 12, 16, 50), tx_hash='0x9848cc10366cf7dde89861a6ae5e1166e7d1edb6cd245c2592679848959f168c', call_id='0', call_type='call', from_address=AddressInfo(address='0x348fc118bcc65a92dc033a951af153d14d945312', name='Mintvial', badge='receiver'), to_address=AddressInfo(address='0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b', name='CloneX', badge=None), value=0.0, function_signature='0x937f2608', function_name='mintTransfer', arguments=[Argument(name='to', type='address', value=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender'))], outputs=[Argument(name='', type='uint256', value=14609)], gas_used=148545, error=None, status=True, indent=1, subcalls=[DecodedCall(chain_id='mainnet', timestamp=datetime.datetime(2021, 12, 13, 12, 16, 50), tx_hash='0x9848cc10366cf7dde89861a6ae5e1166e7d1edb6cd245c2592679848959f168c', call_id='0_0000', call_type='staticcall', from_address=AddressInfo(address='0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b', name='CloneX', badge=None), to_address=AddressInfo(address='0xffc4dbeace02c578cc189004c0ad25eeb8d8ba3f', name='CloneXRandomizer', badge=None), value=0.0, function_signature='0x14ff5ea3', function_name='getTokenId', arguments=[Argument(name='tokenId', type='uint256', value=14609)], outputs=[Argument(name='', type='string', value='13192')], gas_used=8473, error=None, status=True, indent=2, subcalls=[], function_guessed=False)], function_guessed=False)], function_guessed=False) transfers=[DecodedTransfer(from_address=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender'), to_address=AddressInfo(address='0x0000000000000000000000000000000000000000', name='0x0000000000000000000000000000000000000000', badge=None), token_address='0x348fc118bcc65a92dc033a951af153d14d945312?a=913#inventory', token_symbol='NFT 913', token_standard='ERC1155', value='1.0000'), DecodedTransfer(from_address=AddressInfo(address='0x0000000000000000000000000000000000000000', name='0x0000000000000000000000000000000000000000', badge=None), to_address=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender'), token_address='0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b?a=14609#inventory', token_symbol='NFT 14609', token_standard='ERC721', value='1.0000')] balances=[DecodedBalance(holder=AddressInfo(address='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', name='0x9e7c078f8da492d80d567705d0dd12dbfffd65ea', badge='sender'), tokens=[{'token_address': '0x348fc118bcc65a92dc033a951af153d14d945312?a=913#inventory', 'token_symbol': 'NFT 913', 'token_standard': 'ERC1155', 'balance': '-1.0000'}, {'token_address': '0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b?a=14609#inventory', 'token_symbol': 'NFT 14609', 'token_standard': 'ERC721', 'balance': '1.0000'}])] status=True
    

    Fixes #125

    feature 
    opened by 0xbhoori 0
Releases(0.3.21)
Allows you to canibalize methods from classes effectively implementing trait-oriented programming

About This package enables code reuse in non-inheritance way from existing classes, effectively implementing traits-oriented programming pattern. Stor

1 Dec 13, 2021
Lock files using python and cmd

Python_Lock_Files Lock files using python and cmd license feel free to do whatever you want to with these files, i dont take any responsibility tho, u

1 Nov 01, 2021
Python script to get some stats on nodes in a Blender material nodetree

Python script to get some stats on nodes in a Blender material nodetree. It counts the nodes, the node types and the max deep level for group nodes.

Alek Mugnozzo 2 Sep 03, 2022
A small utility that sorts your files.

FileSorter A small utility that sorts your files. TODO: Scan directory to find files(thanks @corruptmemry for this!) Split extensions to determine fil

2 Jun 16, 2022
A tool written in python to generate basic repo files from github

A tool written in python to generate basic repo files from github

Riley 7 Dec 02, 2021
Dynamic key remapper for Wayland Window System, especially for Sway

wayremap Dynamic keyboard remapper for Wayland. It works on both X Window Manager and Wayland, but focused on Wayland as it intercepts evdev input and

Kay Gosho 50 Nov 29, 2022
cssOrganizer - organize a css file by grouping them into categories

This python project was created to scan through a CSS file and produce a more organized CSS file by grouping related CSS Properties within selectors. Created in my spare time for fun and my own utili

Andrew Espindola 0 Aug 31, 2022
This script allows you to retrieve all functions / variables names of a Python code, and the variables values.

Memory Extractor This script allows you to retrieve all functions / variables names of a Python code, and the variables values. How to use it ? The si

Venax 2 Dec 26, 2021
A python module for extract domains

A python module for extract domains

Fayas Noushad 4 Aug 10, 2022
Find version automatically based on git tags and commit messages.

GIT-CONVENTIONAL-VERSION Find version automatically based on git tags and commit messages. The tool is very specific in its function, so it is very fl

0 Nov 07, 2021
A simple tool to move and rename Nvidia Share recordings to a more sensible format.

A simple tool to move and rename Nvidia Share recordings to a more sensible format.

Jasper Rebane 8 Dec 23, 2022
A thing to simplify listening for PG notifications with asyncpg

asyncpg-listen This library simplifies usage of listen/notify with asyncpg: Handles loss of a connection Simplifies notifications processing from mult

ANNA 18 Dec 23, 2022
Conveniently measures the time of your loops, contexts and functions.

Conveniently measures the time of your loops, contexts and functions.

Maciej J Mikulski 79 Nov 15, 2022
Factoral Methods using two different method

Factoral-Methods-using-two-different-method Here, I am finding the factorial of a number by using two different method. The first method is by using f

Sachin Vinayak Dabhade 4 Sep 24, 2021
Pass arguments by reference—in Python!

byref Pass arguments by reference—in Python! byrefis a decorator that allows Python functions to declare reference parameters, with similar semantics

9 Feb 10, 2022
Simple Python tool that generates a pseudo-random password with numbers, letters, and special characters in accordance with password policy best practices.

Simple Python tool that generates a pseudo-random password with numbers, letters, and special characters in accordance with password policy best practices.

Joe Helle 7 Mar 25, 2022
general-phylomoji: a phylogenetic tree of emoji

general-phylomoji: a phylogenetic tree of emoji

2 Dec 11, 2021
Simple script to export contacts from telegram into vCard file

Telegram Contacts Exporter Simple script to export contacts from telegram into vCard file Getting Started Prerequisites You must to put your Telegram

Pere Antoni 1 Oct 17, 2021
Script to autocompound 3commas BO:SO based on user provided risk factor

3commas_compounder Script to autocompound 3commas BO:SO based on user provided risk factor Setup Step 1 git clone this repo into your working director

0 Feb 24, 2022
kawadi is a versatile tool that used as a form of weapon and is used to cut, shape and split wood.

kawadi kawadi (કવાડિ in Gujarati) (Axe in English) is a versatile tool that used as a form of weapon and is used to cut, shape and split wood. kawadi

Jay Vala 2 Jan 10, 2022