Repository: multiversx/mx-sdk-py-cli (language: python)

Issue:
Prepare version 1.0.18
This PR updates `CHANGELOG.md` and `setup.py`.
Bug in _prepare_contract [counter tutorial]
Deploying the counter smart contract as described in the tutorial ([here](https://docs.elrond.com/developers/tutorials/counter/#deploy-the-contract-on-the-testnet)) with the following command:

```
erdpy --verbose contract deploy --project=mycounter --pem="alice.pem" --gas-limit=5000000 --proxy="https://testnet-gateway.elrond.com" --outfile="counter.json" --recall-nonce --send
```
yields the error : 

```
  File "/<HOME>/anaconda3/envs/elrond/bin/erdpy", line 33, in <module>
    sys.exit(load_entry_point('erdpy==1.0.17', 'console_scripts', 'erdpy')())
  File "/<HOME>/anaconda3/envs/elrond/lib/python3.8/site-packages/erdpy-1.0.17-py3.8.egg/erdpy/cli.py", line 32, in main
    _do_main()
  File "/<HOME>/anaconda3/envs/elrond/lib/python3.8/site-packages/erdpy-1.0.17-py3.8.egg/erdpy/cli.py", line 56, in _do_main
    args.func(args)
  File "/<HOME>/anaconda3/envs/elrond/lib/python3.8/site-packages/erdpy-1.0.17-py3.8.egg/erdpy/cli_contracts.py", line 185, in deploy
    contract = _prepare_contract(args)
  File "/<HOME>/anaconda3/envs/elrond/lib/python3.8/site-packages/erdpy-1.0.17-py3.8.egg/erdpy/cli_contracts.py", line 224, in _prepare_contract
    if len(args.bytecode):
TypeError: object of type 'NoneType' has no len()
```



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/erdpy-mypy.yml b/.github/workflows/erdpy-mypy.yml
new file mode 100644
index 00000000..c2afd3e7
--- /dev/null
+++ b/.github/workflows/erdpy-mypy.yml
@@ -0,0 +1,14 @@
+name: mypy reviewdog check
+on: [pull_request]
+jobs:
+  mypy:
+    name: runner / mypy
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install Python dependencies
+        uses: py-actions/py-dependency-install@v2
+      - uses: tsuyoshicho/action-mypy@v3
+        with:
+          github_token: ${{ secrets.github_token }}
+          filter_mode: nofilter
diff --git a/erdpy/CHANGELOG.md b/erdpy/CHANGELOG.md
index ba70c780..bb5d2f01 100644
--- a/erdpy/CHANGELOG.md
+++ b/erdpy/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes will be documented in this file.
 
 Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
 
+## [1.0.18] - 14.09.2021
+ - Load a local `erdpy.json` file when running `erdpy` commands, containing default values for CLI options per project
+ - Bugfix by [MWFIAE](https://github.com/MWFIAE): correctly verify the value of the `--bytecode` argument
+ - Bugfix by [MWFIAE](https://github.com/MWFIAE): `QueryResult` objects are now properly JSON-serializable
+ - Add more output information after building and deploying contracts
+ - Improve error reporting to standard output
+ - Enable `mypy` checking as a GitHub action
+ - Add and fix more type hints for `mypy`
+
 ## [1.0.16] - 27.08.2021
  - Merge branch `legolas-addons`
  - Fix `erdpy testnet` to work with recent changes in `elrond-go`
@@ -22,7 +31,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
  - Add --wait-result flag for erdpy tx new cli command.
 
 ## [1.0.10] - 25.02.2021
- - [Fixed a bug in the testnet setup process when creting the config for the proxy app #213](https://github.com/ElrondNetwork/elrond-sdk/pull/213) 
+ - [Fixed a bug in the testnet setup process when creating the config for the proxy app #213](https://github.com/ElrondNetwork/elrond-sdk/pull/213) 
 
 ## [1.0.9] - 29.01.2021
  -  [Multiple config templates #152](https://github.com/ElrondNetwork/elrond-sdk/pull/152). One can now `switch` between different **configuration** profiles.
@@ -149,7 +158,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
  - Refactoring and design improvements
 
 
-**Note that below, the change log is in chronological order. Will be soon updated to be in inverse chronological order (how it should have been in the first place).**
+**Note that below, the changelog is in chronological order. It will be soon updated to be in inverse chronological order (how it should have been in the first place).**
 
 ## [0.0.4]
 
@@ -498,7 +507,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
  - Removed .py SC interaction samples, moved to `sc-examples` `sc-examples-rs` repositories, so that they become available in Elrond IDE.
  - Removed some deprecated code.
  - `mypy`-related refactoring.
- - Fix accounts CLI. Trucate data for "account get-transactions"
+ - Fix accounts CLI. Truncate data for "account get-transactions".
 
 ## [0.7.1] - 13.07.2020
 
diff --git a/erdpy/_version.py b/erdpy/_version.py
index 85100980..441304cd 100644
--- a/erdpy/_version.py
+++ b/erdpy/_version.py
@@ -1,1 +1,1 @@
-__version__ = "1.0.17"
+__version__ = "1.0.18"
diff --git a/erdpy/accounts.py b/erdpy/accounts.py
index 9538dd38..4131295b 100644
--- a/erdpy/accounts.py
+++ b/erdpy/accounts.py
@@ -133,7 +133,7 @@ def __repr__(self):
         return self.bech32()
 
     @classmethod
-    def zero(cls):
+    def zero(cls) -> 'Address':
         return Address("0" * 64)
 
 
diff --git a/erdpy/cli.py b/erdpy/cli.py
index 0ceb37d2..7e1f6ea8 100644
--- a/erdpy/cli.py
+++ b/erdpy/cli.py
@@ -21,7 +21,7 @@
 import erdpy.cli_wallet
 import erdpy.cli_delagation
 import erdpy.cli_dns
-from erdpy import errors, scope
+from erdpy import config, errors, scope
 from erdpy._version import __version__
 
 logger = logging.getLogger("cli")
@@ -32,18 +32,20 @@ def main():
         _do_main()
     except errors.KnownError as err:
         logger.critical(err.get_pretty())
-        sys.exit(1)
+        return 1
     except KeyboardInterrupt:
         print("erdpy process killed by user.")
-        sys.exit(1)
+        return 1
+    return 0
 
 
 def _do_main():
     logging.basicConfig(level=logging.INFO)
     scope.initialize()
 
-    parser = setup_parser()
-    args = parser.parse_args()
+    argv_with_config_args = config.add_config_args(sys.argv[1:])
+    parser = setup_parser(argv_with_config_args)
+    args = parser.parse_args(argv_with_config_args)
 
     if args.verbose:
         logging.basicConfig(level=logging.DEBUG)
@@ -56,7 +58,7 @@ def _do_main():
         args.func(args)
 
 
-def setup_parser():
+def setup_parser(args: List[str] = sys.argv[1:]):
     parser = ArgumentParser(
         prog="erdpy",
         usage="erdpy [-h] [-v] [--verbose] COMMAND-GROUP [-h] COMMAND ...",
@@ -82,23 +84,23 @@ def setup_parser():
     subparsers = parser.add_subparsers()
     commands: List[Any] = []
 
-    commands.append(erdpy.cli_contracts.setup_parser(subparsers))
-    commands.append(erdpy.cli_transactions.setup_parser(subparsers))
-    commands.append(erdpy.cli_validators.setup_parser(subparsers))
+    commands.append(erdpy.cli_contracts.setup_parser(args, subparsers))
+    commands.append(erdpy.cli_transactions.setup_parser(args, subparsers))
+    commands.append(erdpy.cli_validators.setup_parser(args, subparsers))
     commands.append(erdpy.cli_accounts.setup_parser(subparsers))
     commands.append(erdpy.cli_ledger.setup_parser(subparsers))
-    commands.append(erdpy.cli_wallet.setup_parser(subparsers))
+    commands.append(erdpy.cli_wallet.setup_parser(args, subparsers))
     commands.append(erdpy.cli_network.setup_parser(subparsers))
     commands.append(erdpy.cli_cost.setup_parser(subparsers))
-    commands.append(erdpy.cli_dispatcher.setup_parser(subparsers))
+    commands.append(erdpy.cli_dispatcher.setup_parser(args, subparsers))
     commands.append(erdpy.cli_blockatlas.setup_parser(subparsers))
     commands.append(erdpy.cli_deps.setup_parser(subparsers))
     commands.append(erdpy.cli_config.setup_parser(subparsers))
     commands.append(erdpy.cli_block.setup_parser(subparsers))
-    commands.append(erdpy.cli_testnet.setup_parser(subparsers))
+    commands.append(erdpy.cli_testnet.setup_parser(args, subparsers))
     commands.append(erdpy.cli_data.setup_parser(subparsers))
-    commands.append(erdpy.cli_delagation.setup_parser(subparsers))
-    commands.append(erdpy.cli_dns.setup_parser(subparsers))
+    commands.append(erdpy.cli_delagation.setup_parser(args, subparsers))
+    commands.append(erdpy.cli_dns.setup_parser(args, subparsers))
 
     parser.epilog = """
 ----------------------
@@ -112,4 +114,5 @@ def setup_parser():
 
 
 if __name__ == "__main__":
-    main()
+    ret = main()
+    sys.exit(ret)
diff --git a/erdpy/cli_contracts.py b/erdpy/cli_contracts.py
index 5744bd4a..558d26a8 100644
--- a/erdpy/cli_contracts.py
+++ b/erdpy/cli_contracts.py
@@ -1,6 +1,6 @@
 import logging
 import os
-from typing import Any
+from typing import Any, List
 
 from pathlib import Path
 
@@ -14,7 +14,7 @@
 logger = logging.getLogger("cli.contracts")
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(subparsers, "contract", "Build, deploy and interact with Smart Contracts")
     subparsers = parser.add_subparsers()
 
@@ -50,9 +50,9 @@ def setup_parser(subparsers: Any) -> Any:
     _add_project_or_bytecode_arg(sub)
     _add_metadata_arg(sub)
     cli_shared.add_outfile_arg(sub)
-    cli_shared.add_wallet_args(sub)
+    cli_shared.add_wallet_args(args, sub)
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_tx_args(sub, with_receiver=False, with_data=False)
+    cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
     _add_arguments_arg(sub)
     sub.add_argument("--wait-result", action="store_true", default=False,
                      help="signal to wait for the transaction result - only valid if --send is set")
@@ -65,9 +65,9 @@ def setup_parser(subparsers: Any) -> Any:
     sub = cli_shared.add_command_subparser(subparsers, "contract", "call", "Interact with a Smart Contract (execute function).")
     _add_contract_arg(sub)
     cli_shared.add_outfile_arg(sub)
-    cli_shared.add_wallet_args(sub)
+    cli_shared.add_wallet_args(args, sub)
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_tx_args(sub, with_receiver=False, with_data=False)
+    cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
     _add_function_arg(sub)
     _add_arguments_arg(sub)
     sub.add_argument("--wait-result", action="store_true", default=False,
@@ -83,9 +83,9 @@ def setup_parser(subparsers: Any) -> Any:
     cli_shared.add_outfile_arg(sub)
     _add_project_or_bytecode_arg(sub)
     _add_metadata_arg(sub)
-    cli_shared.add_wallet_args(sub)
+    cli_shared.add_wallet_args(args, sub)
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_tx_args(sub, with_receiver=False, with_data=False)
+    cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
     _add_arguments_arg(sub)
     sub.add_argument("--wait-result", action="store_true", default=False,
                      help="signal to wait for the transaction result - only valid if --send is set")
@@ -115,7 +115,7 @@ def _add_project_or_bytecode_arg(sub: Any):
     group.add_argument("--project", default=os.getcwd(),
                        help="🗀 the project directory (default: current directory)")
     group.add_argument("--bytecode", type=str,
-                      help="the file containing the WASM bytecode")
+                       help="the file containing the WASM bytecode")
 
 
 def _add_contract_arg(sub: Any):
@@ -187,6 +187,7 @@ def deploy(args: Any):
 
     tx = contract.deploy(sender, arguments, gas_price, gas_limit, value, chain, version)
     logger.info("Contract address: %s", contract.address)
+    utils.log_explorer_contract_address(chain, contract.address)
 
     result = None
     try:
@@ -221,7 +222,7 @@ def dump_tx_and_result(tx: Any, result: Any, args: Any):
 
 
 def _prepare_contract(args: Any) -> SmartContract:
-    if len(args.bytecode):
+    if args.bytecode and len(args.bytecode):
         bytecode = utils.read_binary_file(Path(args.bytecode)).hex()
     else:
         project = load_project(args.project)
diff --git a/erdpy/cli_delagation.py b/erdpy/cli_delagation.py
index 401a555c..6d389841 100644
--- a/erdpy/cli_delagation.py
+++ b/erdpy/cli_delagation.py
@@ -1,6 +1,6 @@
 import binascii
 import sys
-from typing import Any
+from typing import Any, List
 
 from erdpy import cli_shared, errors, utils
 from erdpy.accounts import Address
@@ -9,7 +9,7 @@
 from erdpy.transactions import do_prepare_transaction
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(subparsers, "staking-provider", "Staking provider omnitool")
     subparsers = parser.add_subparsers()
 
@@ -17,7 +17,7 @@ def setup_parser(subparsers: Any) -> Any:
     sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "create-new-delegation-contract",
                                            "Create a new delegation system smart contract, transferred value must be"
                                            "greater than baseIssuingCost + min deposit value")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.add_argument("--total-delegation-cap", required=True, help="the total delegation contract capacity")
     sub.add_argument("--service-fee", required=True, help="the delegation contract service fee")
     sub.set_defaults(func=do_create_delegation_contract)
@@ -37,7 +37,7 @@ def setup_parser(subparsers: Any) -> Any:
     sub.add_argument("--validators-file", required=True, help="a JSON file describing the Nodes")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
     sub.add_argument("--using-delegation-manager", action="store_true", required=False, help="whether delegation contract was created using the Delegation Manager")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=add_new_nodes)
 
     # remove nodes
@@ -45,7 +45,7 @@ def setup_parser(subparsers: Any) -> Any:
                                            "Remove nodes must be called by the contract owner")
     sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=remove_nodes)
 
     # stake nodes
@@ -53,7 +53,7 @@ def setup_parser(subparsers: Any) -> Any:
                                            "Stake nodes must be called by the contract owner")
     sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=stake_nodes)
 
     # unbond nodes
@@ -61,7 +61,7 @@ def setup_parser(subparsers: Any) -> Any:
                                            "Unbond nodes must be called by the contract owner")
     sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=unbond_nodes)
 
     # unstake nodes
@@ -69,7 +69,7 @@ def setup_parser(subparsers: Any) -> Any:
                                            "Unstake nodes must be called by the contract owner")
     sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=unstake_nodes)
 
     # unjail nodes
@@ -77,7 +77,7 @@ def setup_parser(subparsers: Any) -> Any:
                                            "Unjail nodes must be called by the contract owner")
     sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=unjail_nodes)
 
     # change service fee
@@ -85,7 +85,7 @@ def setup_parser(subparsers: Any) -> Any:
                                            "Change service fee must be called by the contract owner")
     sub.add_argument("--service-fee", required=True, help="new service fee value")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=change_service_fee)
 
     # modify total delegation cap
@@ -93,31 +93,31 @@ def setup_parser(subparsers: Any) -> Any:
                                            "Modify delegation cap must be called by the contract owner")
     sub.add_argument("--delegation-cap", required=True, help="new delegation contract capacity")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=modify_delegation_cap)
 
     # set automatic activation
     sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "automatic-activation",
                                            "Automatic activation must be called by the contract owner")
 
-    sub.add_argument("--set", action="store_true", required=not (utils.is_arg_present("--unset", sys.argv)),
+    sub.add_argument("--set", action="store_true", required=not (utils.is_arg_present(args, "--unset")),
                      help="set automatic activation True")
-    sub.add_argument("--unset", action="store_true", required=not (utils.is_arg_present("--set", sys.argv)),
+    sub.add_argument("--unset", action="store_true", required=not (utils.is_arg_present(args, "--set")),
                      help="set automatic activation False")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=automatic_activation)
 
     # set redelegate cap
     sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "redelegate-cap",
                                            "Redelegate cap must be called by the contract owner")
 
-    sub.add_argument("--set", action="store_true", required=not (utils.is_arg_present("--unset", sys.argv)),
+    sub.add_argument("--set", action="store_true", required=not (utils.is_arg_present(args, "--unset")),
                      help="set redelegate cap True")
-    sub.add_argument("--unset", action="store_true", required=not (utils.is_arg_present("--set", sys.argv)),
+    sub.add_argument("--unset", action="store_true", required=not (utils.is_arg_present(args, "--set")),
                      help="set redelegate cap False")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=redelegate_cap)
 
     # set metadata
@@ -128,14 +128,14 @@ def setup_parser(subparsers: Any) -> Any:
     sub.add_argument("--website", required=True, help="website field in staking provider metadata")
     sub.add_argument("--identifier", required=True, help="identifier field in staking provider metadata")
     sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=set_metadata)
 
 
-def _add_common_arguments(sub: Any):
+def _add_common_arguments(args: List[str], sub: Any):
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_wallet_args(sub)
-    cli_shared.add_tx_args(sub, with_receiver=False, with_data=False, with_estimate_gas=True)
+    cli_shared.add_wallet_args(args, sub)
+    cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True)
     cli_shared.add_broadcast_args(sub, relay=False)
     cli_shared.add_outfile_arg(sub, what="signed transaction, hash")
 
diff --git a/erdpy/cli_dispatcher.py b/erdpy/cli_dispatcher.py
index e10747cd..bffe7e94 100644
--- a/erdpy/cli_dispatcher.py
+++ b/erdpy/cli_dispatcher.py
@@ -1,28 +1,28 @@
 from erdpy.dispatcher.transactions.queue import TransactionQueue
 import logging
-from typing import Any
+from typing import Any, List
 
 from erdpy import cli_shared
 
 logger = logging.getLogger("cli.dispatcher")
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(subparsers, "dispatcher", "Enqueue transactions, then bulk dispatch them")
     subparsers = parser.add_subparsers()
 
     sub = cli_shared.add_command_subparser(subparsers, "dispatcher", "enqueue", "Enqueue a transaction")
-    cli_shared.add_tx_args(sub, with_nonce=False)
+    cli_shared.add_tx_args(args, sub, with_nonce=False)
     sub.set_defaults(func=enqueue_transaction)
 
     sub = cli_shared.add_command_subparser(subparsers, "dispatcher", "dispatch", "Dispatch queued transactions")
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_wallet_args(sub)
+    cli_shared.add_wallet_args(args, sub)
     sub.set_defaults(func=dispatch_transactions)
 
     sub = cli_shared.add_command_subparser(subparsers, "dispatcher", "dispatch-continuously", "Continuously dispatch queued transactions")
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_wallet_args(sub)
+    cli_shared.add_wallet_args(args, sub)
     sub.add_argument("--interval", required=True, help="the interval to retrieve transactions from the queue, in seconds")
     sub.set_defaults(func=dispatch_transactions_continuously)
 
diff --git a/erdpy/cli_dns.py b/erdpy/cli_dns.py
index 8c3d0a1b..69090a01 100644
--- a/erdpy/cli_dns.py
+++ b/erdpy/cli_dns.py
@@ -1,4 +1,4 @@
-from typing import Any
+from typing import Any, List
 from prettytable import PrettyTable
 
 from erdpy import cli_shared
@@ -7,16 +7,16 @@
 from erdpy.proxy.core import ElrondProxy
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(subparsers, "dns", "Operations related to the Domain Name Service")
     subparsers = parser.add_subparsers()
 
     sub = cli_shared.add_command_subparser(subparsers, "dns", "register", "Send a register transaction to the appropriate DNS contract from given user and with given name")
     cli_shared.add_outfile_arg(sub)
     cli_shared.add_broadcast_args(sub, relay=True)
-    cli_shared.add_wallet_args(sub)
+    cli_shared.add_wallet_args(args, sub)
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_tx_args(sub, with_receiver=False, with_data=False)
+    cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
     sub.add_argument("--name", help="the name to register")
     sub.set_defaults(func=register)
 
diff --git a/erdpy/cli_shared.py b/erdpy/cli_shared.py
index f268b58f..d9604d83 100644
--- a/erdpy/cli_shared.py
+++ b/erdpy/cli_shared.py
@@ -1,9 +1,8 @@
 import argparse
 import ast
-import string
 import sys
 from argparse import FileType
-from typing import Any, List, Text
+from typing import Any, List, Text, cast
 
 from erdpy import config, errors, scope, utils
 from erdpy.accounts import Account
@@ -50,9 +49,9 @@ def add_command_subparser(subparsers: Any, group: str, command: str, description
     )
 
 
-def add_tx_args(sub: Any, with_nonce: bool = True, with_receiver: bool = True, with_data: bool = True, with_estimate_gas: bool = False):
+def add_tx_args(args: List[str], sub: Any, with_nonce: bool = True, with_receiver: bool = True, with_data: bool = True, with_estimate_gas: bool = False):
     if with_nonce:
-        sub.add_argument("--nonce", type=int, required=not("--recall-nonce" in sys.argv), help="# the nonce for the transaction")
+        sub.add_argument("--nonce", type=int, required=not("--recall-nonce" in args), help="# the nonce for the transaction")
         sub.add_argument("--recall-nonce", action="store_true", default=False, help="⭮ whether to recall the nonce when creating the transaction (default: %(default)s)")
 
     if with_receiver:
@@ -60,7 +59,7 @@ def add_tx_args(sub: Any, with_nonce: bool = True, with_receiver: bool = True, w
         sub.add_argument("--receiver-username", required=False, help="🖄 the username of the receiver")
 
     sub.add_argument("--gas-price", default=config.DEFAULT_GAS_PRICE, help="⛽ the gas price (default: %(default)d)")
-    sub.add_argument("--gas-limit", required=not("--estimate-gas" in sys.argv), help="⛽ the gas limit")
+    sub.add_argument("--gas-limit", required=not("--estimate-gas" in args), help="⛽ the gas limit")
     if with_estimate_gas:
         sub.add_argument("--estimate-gas", action="store_true", default=False, help="⛽ whether to estimate the gas limit (default: %(default)d)")
 
@@ -71,15 +70,15 @@ def add_tx_args(sub: Any, with_nonce: bool = True, with_receiver: bool = True, w
 
     sub.add_argument("--chain", default=scope.get_chain_id(), help="the chain identifier (default: %(default)s)")
     sub.add_argument("--version", type=int, default=scope.get_tx_version(), help="the transaction version (default: %(default)s)")
-    sub.add_argument("--options", type=int, default=0,  help="the transaction options (default: 0)")
+    sub.add_argument("--options", type=int, default=0, help="the transaction options (default: 0)")
 
 
-def add_wallet_args(sub: Any):
-    sub.add_argument("--pem", required=check_if_sign_method_required("--pem"), help="🔑 the PEM file, if keyfile not provided")
+def add_wallet_args(args: List[str], sub: Any):
+    sub.add_argument("--pem", required=check_if_sign_method_required(args, "--pem"), help="🔑 the PEM file, if keyfile not provided")
     sub.add_argument("--pem-index", default=0, help="🔑 the index in the PEM file (default: %(default)s)")
-    sub.add_argument("--keyfile", required=check_if_sign_method_required("--keyfile"), help="🔑 a JSON keyfile, if PEM not provided")
-    sub.add_argument("--passfile", required=(utils.is_arg_present("--keyfile", sys.argv)), help="🔑 a file containing keyfile's password, if keyfile provided")
-    sub.add_argument("--ledger", action="store_true", required=check_if_sign_method_required("--ledger"), default=False, help="🔐 bool flag for signing transaction using ledger")
+    sub.add_argument("--keyfile", required=check_if_sign_method_required(args, "--keyfile"), help="🔑 a JSON keyfile, if PEM not provided")
+    sub.add_argument("--passfile", required=(utils.is_arg_present(args, "--keyfile")), help="🔑 a file containing keyfile's password, if keyfile provided")
+    sub.add_argument("--ledger", action="store_true", required=check_if_sign_method_required(args, "--ledger"), default=False, help="🔐 bool flag for signing transaction using ledger")
     sub.add_argument("--ledger-account-index", type=int, default=0, help="🔐 the index of the account when using Ledger")
     sub.add_argument("--ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger")
     sub.add_argument("--sender-username", required=False, help="🖄 the username of the sender")
@@ -106,7 +105,7 @@ def add_omit_fields_arg(sub: Any):
 def parse_omit_fields_arg(args: Any) -> List[str]:
     literal = args.omit_fields
     parsed = ast.literal_eval(literal)
-    return parsed
+    return cast(List[str], parsed)
 
 
 def prepare_nonce_in_args(args: Any):
@@ -149,7 +148,7 @@ def send_or_simulate(tx: Transaction, args: Any):
         utils.dump_out_json(response)
 
 
-def check_if_sign_method_required(checked_method: string) -> bool:
+def check_if_sign_method_required(args: List[str], checked_method: str) -> bool:
     methods = ["--pem", "--keyfile", "--ledger"]
     rest_of_methods = []
     for method in methods:
@@ -157,7 +156,7 @@ def check_if_sign_method_required(checked_method: string) -> bool:
             rest_of_methods.append(method)
 
     for method in rest_of_methods:
-        if utils.is_arg_present(method, sys.argv):
+        if utils.is_arg_present(args, method):
             return False
 
     return True
diff --git a/erdpy/cli_transactions.py b/erdpy/cli_transactions.py
index fcffc576..af597405 100644
--- a/erdpy/cli_transactions.py
+++ b/erdpy/cli_transactions.py
@@ -1,5 +1,5 @@
 from argparse import FileType
-from typing import Any
+from typing import Any, List
 
 from erdpy import cli_shared, utils
 from erdpy.ledger.ledger_functions import do_get_ledger_address
@@ -7,12 +7,12 @@
 from erdpy.transactions import Transaction, do_prepare_transaction
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(subparsers, "tx", "Create and broadcast Transactions")
     subparsers = parser.add_subparsers()
 
     sub = cli_shared.add_command_subparser(subparsers, "tx", "new", "Create a new transaction")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     cli_shared.add_outfile_arg(sub, what="signed transaction, hash")
     cli_shared.add_broadcast_args(sub, relay=True)
     cli_shared.add_proxy_arg(sub)
@@ -40,9 +40,9 @@ def setup_parser(subparsers: Any) -> Any:
     return subparsers
 
 
-def _add_common_arguments(sub: Any):
-    cli_shared.add_wallet_args(sub)
-    cli_shared.add_tx_args(sub)
+def _add_common_arguments(args: List[str], sub: Any):
+    cli_shared.add_wallet_args(args, sub)
+    cli_shared.add_tx_args(args, sub)
     sub.add_argument("--data-file", type=FileType("r"), default=None, help="a file containing transaction data")
 
 
diff --git a/erdpy/cli_validators.py b/erdpy/cli_validators.py
index f6c274c2..99dac174 100644
--- a/erdpy/cli_validators.py
+++ b/erdpy/cli_validators.py
@@ -1,53 +1,53 @@
 import sys
-from typing import Any
+from typing import Any, List
 
 from erdpy import cli_shared, validators, utils
 from erdpy.transactions import do_prepare_transaction
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(subparsers, "validator", "Stake, UnStake, UnBond, Unjail and other "
                                                                      "actions useful for "
                                                                      "Validators")
     subparsers = parser.add_subparsers()
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "stake", "Stake value into the Network")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.add_argument("--reward-address", default="", help="the reward address")
-    sub.add_argument("--validators-file", required=not (utils.is_arg_present("--top-up", sys.argv)),
+    sub.add_argument("--validators-file", required=not (utils.is_arg_present(args, "--top-up")),
                      help="a JSON file describing the Nodes")
     sub.add_argument("--top-up", action="store_true", default=False,
-                     required=not (utils.is_arg_present("--validators-file", sys.argv)), help="Stake value for top up")
+                     required=not (utils.is_arg_present(args, "--validators-file")), help="Stake value for top up")
     sub.set_defaults(func=do_stake)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "unstake", "Unstake value")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     _add_nodes_arg(sub)
     sub.set_defaults(func=do_unstake)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "unjail", "Unjail a Validator Node")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     _add_nodes_arg(sub)
     sub.set_defaults(func=do_unjail)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "unbond", "Unbond tokens for a bls key")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     _add_nodes_arg(sub)
     sub.set_defaults(func=do_unbond)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "change-reward-address",
                                            "Change the reward address")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.add_argument("--reward-address", required=True, help="the new reward address")
     sub.set_defaults(func=change_reward_address)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "claim", "Claim rewards")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=do_claim)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "unstake-nodes", "Unstake-nodes will unstake "
                                                                                      "nodes for provided bls keys")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     _add_nodes_arg(sub)
     sub.set_defaults(func=do_unstake_nodes)
 
@@ -56,29 +56,29 @@ def setup_parser(subparsers: Any) -> Any:
                                                                                       "greater than the existing "
                                                                                       "topUp value, it will unStake "
                                                                                       "one or several nodes)")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.add_argument("--unstake-value", default=0, help="the unstake value")
     sub.set_defaults(func=do_unstake_tokens)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "unbond-nodes", "It will unBond nodes")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     _add_nodes_arg(sub)
     sub.set_defaults(func=do_unbond_nodes)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "unbond-tokens", "It will unBond tokens, if "
                                                                                      "provided value is bigger that "
                                                                                      "topUp value will unBond nodes")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.add_argument("--unbond-value", default=0, help="the unbond value")
     sub.set_defaults(func=do_unbond_tokens)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "clean-registered-data", "Deletes duplicated keys "
                                                                                              "from registered data")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     sub.set_defaults(func=do_clean_registered_data)
 
     sub = cli_shared.add_command_subparser(subparsers, "validator", "restake-unstaked-nodes", "It will reStake UnStaked nodes")
-    _add_common_arguments(sub)
+    _add_common_arguments(args, sub)
     _add_nodes_arg(sub)
     sub.set_defaults(func=do_restake_unstaked_nodes)
 
@@ -86,10 +86,10 @@ def setup_parser(subparsers: Any) -> Any:
     return subparsers
 
 
-def _add_common_arguments(sub: Any):
+def _add_common_arguments(args: List[str], sub: Any):
     cli_shared.add_proxy_arg(sub)
-    cli_shared.add_wallet_args(sub)
-    cli_shared.add_tx_args(sub, with_receiver=False, with_data=False, with_estimate_gas=True)
+    cli_shared.add_wallet_args(args, sub)
+    cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True)
     cli_shared.add_broadcast_args(sub, relay=False)
     cli_shared.add_outfile_arg(sub, what="signed transaction, hash")
 
diff --git a/erdpy/cli_wallet.py b/erdpy/cli_wallet.py
index e00eefd8..b7fcf968 100644
--- a/erdpy/cli_wallet.py
+++ b/erdpy/cli_wallet.py
@@ -1,5 +1,5 @@
 import logging
-from typing import Any
+from typing import Any, List
 
 from erdpy import cli_shared, wallet
 from erdpy.accounts import Account, Address
@@ -8,7 +8,7 @@
 logger = logging.getLogger("cli.wallet")
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(
         subparsers,
         "wallet",
diff --git a/erdpy/config.py b/erdpy/config.py
index 65852de0..8b5bb2c7 100644
--- a/erdpy/config.py
+++ b/erdpy/config.py
@@ -1,10 +1,13 @@
+from itertools import chain
+from logging import Logger
 import os.path
-from typing import Any, Dict
+from typing import Any, Dict, List
 
 from erdpy import errors, utils
 
 ROOT_FOLDER_NAME = "elrondsdk"
-CONFIG_PATH = os.path.expanduser("~/elrondsdk/erdpy.json")
+LOCAL_CONFIG_PATH = os.path.join(os.getcwd(), "erdpy.json")
+GLOBAL_CONFIG_PATH = os.path.expanduser("~/elrondsdk/erdpy.json")
 
 DEFAULT_GAS_PRICE = 1000000000
 GAS_PER_DATA_BYTE = 1500
@@ -161,11 +164,66 @@ def get_defaults() -> Dict[str, Any]:
     }
 
 
+def resolve_config_path() -> str:
+    if os.path.isfile(LOCAL_CONFIG_PATH):
+        return LOCAL_CONFIG_PATH
+    return GLOBAL_CONFIG_PATH
+
+
 def read_file() -> Dict[str, Any]:
-    if not os.path.isfile(CONFIG_PATH):
-        return dict()
-    return utils.read_json_file(CONFIG_PATH)
+    config_path = resolve_config_path()
+    if os.path.isfile(config_path):
+        return utils.read_json_file(config_path)
+    return dict()
 
 
 def write_file(data: Dict[str, Any]):
-    utils.write_json_file(CONFIG_PATH, data)
+    config_path = resolve_config_path()
+    utils.write_json_file(config_path, data)
+
+
+def add_config_args(argv: List[str]) -> List[str]:
+    try:
+        command, subcommand, *_ = argv
+    except ValueError:
+        return argv
+
+    config = read_file()
+
+    try:
+        config_args = config[command][subcommand]
+    except KeyError:
+        return argv
+
+    final_args = determine_final_args(argv, config_args)
+    print(f"Found extra arguments in erdpy.json. Final arguments: {final_args}")
+    return final_args
+
+
+def determine_final_args(argv: List[str], config_args: Dict[str, Any]) -> List[str]:
+    extra_args = []
+    for key, value in config_args.items():
+        key_arg = f'--{key}'
+        # arguments from the command line override the config
+        if key_arg in argv:
+            continue
+        if any(arg.startswith(f"{key_arg}=") for arg in argv):
+            continue
+        extra_args.append(key_arg)
+        if value is True:
+            continue
+        if isinstance(value, List):
+            for item in value:
+                extra_args.append(str(item))
+        else:
+            extra_args.append(str(value))
+
+    # the verbose flag is an exception since it has to go before the command and subcommand
+    # eg. erdpy --verbose contract deploy
+    verbose_flag = '--verbose'
+    pre_args = []
+    if verbose_flag in extra_args:
+        extra_args.remove(verbose_flag)
+        pre_args = [verbose_flag]
+
+    return pre_args + argv + extra_args
diff --git a/erdpy/contracts.py b/erdpy/contracts.py
index d65f483f..90bed1d9 100644
--- a/erdpy/contracts.py
+++ b/erdpy/contracts.py
@@ -8,13 +8,14 @@
 from erdpy.accounts import Account, Address
 from erdpy.interfaces import IElrondProxy
 from erdpy.transactions import Transaction
+from erdpy.utils import Object
 
 logger = logging.getLogger("contracts")
 
 HEX_PREFIX = "0X"
 
 
-class QueryResult:
+class QueryResult(Object):
     def __init__(self, as_base64: str, as_hex: str, as_number: int):
         self.base64 = as_base64
         self.hex = as_hex
diff --git a/erdpy/dispatcher/transactions/README.md b/erdpy/dispatcher/transactions/README.md
index e8f15e3a..6c6d6c84 100644
--- a/erdpy/dispatcher/transactions/README.md
+++ b/erdpy/dispatcher/transactions/README.md
@@ -33,4 +33,4 @@ erdpy dispatcher clean
 ## Roadmap
 
 - Use a database as the queue backend
-- Implement a mechanism to check which transactions was executed successfully (we need to use api route `transaction/:hash` and `transaction/:hash/status` )
+- Implement a mechanism to check which transactions were executed successfully (we need to use API route `transaction/:hash` and `transaction/:hash/status` )
diff --git a/erdpy/dns.py b/erdpy/dns.py
index b1452bf1..5d94ed18 100644
--- a/erdpy/dns.py
+++ b/erdpy/dns.py
@@ -62,7 +62,7 @@ def compute_all_dns_addresses() -> List[Address]:
     return addresses
 
 
-def name_hash(name: str) -> Address:
+def name_hash(name: str) -> bytes:
     return keccak.new(digest_bits=256).update(str.encode(name)).digest()
 
 
diff --git a/erdpy/environments.py b/erdpy/environments.py
index 5b30b055..99b0a9ca 100644
--- a/erdpy/environments.py
+++ b/erdpy/environments.py
@@ -15,10 +15,10 @@ def __init__(self):
     def run_flow(self):
         raise NotImplementedError()
 
-    def deploy_contract(self, contract, owner, arguments, gas_price, gas_limit, chain, version):
+    def deploy_contract(self, contract: SmartContract, owner, arguments, gas_price, gas_limit, value, chain, version):
         raise NotImplementedError()
 
-    def execute_contract(self, contract, caller, function, arguments, gas_price, gas_limit, chain, version):
+    def execute_contract(self, contract: SmartContract, caller, function, arguments, gas_price, gas_limit, value, chain, version):
         raise NotImplementedError()
 
     def query_contract(self, contract, function, arguments=None):
diff --git a/erdpy/projects/core.py b/erdpy/projects/core.py
index 111d9f15..0c16dac5 100644
--- a/erdpy/projects/core.py
+++ b/erdpy/projects/core.py
@@ -2,6 +2,7 @@
 import logging
 from os import path
 from typing import Any, Dict
+from pathlib import Path
 
 from erdpy import errors, utils, guards
 from erdpy.projects import shared
@@ -36,8 +37,10 @@ def build_project(directory: str, options: Dict[str, Any]):
 
     guards.is_directory(directory)
     project = load_project(directory)
-    project.build(options)
+    output_wasm_file = project.build(options)
     logger.info("Build ran.")
+    relative_wasm_path = output_wasm_file.relative_to(Path.cwd())
+    logger.info(f"WASM file generated: {relative_wasm_path}")
 
 
 def clean_project(directory: str):
diff --git a/erdpy/projects/project_base.py b/erdpy/projects/project_base.py
index 31308140..59e48732 100644
--- a/erdpy/projects/project_base.py
+++ b/erdpy/projects/project_base.py
@@ -18,12 +18,12 @@ def __init__(self, directory: Union[Path, str]):
         self.path = Path(directory).expanduser().resolve()
         self.directory = str(self.path)
 
-    def build(self, options: Union[Dict[str, Any], None] = None):
+    def build(self, options: Union[Dict[str, Any], None] = None) -> Path:
         self.options = options or dict()
         self.debug = self.options.get("debug", False)
         self._ensure_dependencies_installed()
         self.perform_build()
-        self._do_after_build()
+        return self._do_after_build()
 
     def clean(self):
         utils.remove_folder(self._get_output_folder())
@@ -61,14 +61,15 @@ def find_file_in_folder(self, folder, pattern):
         file = path.join(folder, files[0])
         return Path(file).resolve()
 
-    def _do_after_build(self) -> None:
+    def _do_after_build(self) -> Path:
         raise NotImplementedError()
 
-    def _copy_to_output(self, source: str, destination: str = None):
+    def _copy_to_output(self, source: str, destination: str = None) -> Path:
         output_folder = self._get_output_folder()
         utils.ensure_folder(output_folder)
         destination = path.join(output_folder, destination) if destination else output_folder
-        shutil.copy(source, destination)
+        output_wasm_file = shutil.copy(source, destination)
+        return Path(output_wasm_file)
 
     def _get_output_folder(self):
         return path.join(self.directory, "output")
diff --git a/erdpy/projects/project_clang.py b/erdpy/projects/project_clang.py
index b57f8e01..01647c94 100644
--- a/erdpy/projects/project_clang.py
+++ b/erdpy/projects/project_clang.py
@@ -98,8 +98,8 @@ def do_wasm(self):
 
         myprocess.run_process(args)
 
-    def _do_after_build(self):
-        self._copy_to_output(self.file_output)
+    def _do_after_build(self) -> Path:
+        output_wasm_file = self._copy_to_output(self.file_output)
         self.file_output.unlink()
         self.file_ll.unlink()
         self.file_o.unlink()
@@ -108,6 +108,7 @@ def _do_after_build(self):
                 ll_file.unlink()
             except FileNotFoundError:
                 pass
+        return output_wasm_file
 
     def _get_llvm_path(self):
         return dependencies.get_module_directory('llvm')
diff --git a/erdpy/projects/project_cpp.py b/erdpy/projects/project_cpp.py
index 3bc0b423..1580abd7 100644
--- a/erdpy/projects/project_cpp.py
+++ b/erdpy/projects/project_cpp.py
@@ -1,5 +1,6 @@
 import logging
 import os
+from pathlib import Path
 import subprocess
 from os import path
 
@@ -79,12 +80,13 @@ def _do_wasm(self):
 
         myprocess.run_process(args)
 
-    def _do_after_build(self):
+    def _do_after_build(self) -> Path:
         source_file = self.find_file_globally("*.cpp")
-        self._copy_to_output(source_file.with_suffix(".wasm"))
+        output_wasm_file = self._copy_to_output(source_file.with_suffix(".wasm"))
         os.remove(source_file.with_suffix(".wasm"))
         os.remove(source_file.with_suffix(".ll"))
         os.remove(source_file.with_suffix(".o"))
+        return output_wasm_file
 
     def _get_llvm_path(self):
         return dependencies.get_module_directory("llvm")
diff --git a/erdpy/projects/project_rust.py b/erdpy/projects/project_rust.py
index 9d30b601..d742207b 100644
--- a/erdpy/projects/project_rust.py
+++ b/erdpy/projects/project_rust.py
@@ -87,7 +87,7 @@ def _get_abi_filepath(self):
     def _get_abi_folder(self):
         return Path(self.directory, "abi")
 
-    def _do_after_build(self):
+    def _do_after_build(self) -> Path:
         original_name = self.cargo_file.package_name
         wasm_base_name = self.cargo_file.package_name.replace("-", "_")
         wasm_file = Path(self._get_output_folder(), f"{wasm_base_name}_wasm.wasm").resolve()
@@ -102,6 +102,8 @@ def _do_after_build(self):
             abi_file_renamed = Path(self._get_output_folder(), f"{original_name}.abi.json")
             shutil.move(abi_file, abi_file_renamed)
 
+        return wasm_file_renamed_path
+
     def get_dependencies(self):
         return ["rust"]
 
diff --git a/erdpy/proxy/core.py b/erdpy/proxy/core.py
index 9e5aa35c..f8f51f9f 100644
--- a/erdpy/proxy/core.py
+++ b/erdpy/proxy/core.py
@@ -3,7 +3,7 @@
 from typing import Any, List, Tuple, cast, Dict
 
 from erdpy.accounts import Address
-from erdpy.interfaces import IAddress
+from erdpy.interfaces import IAddress, IElrondProxy
 from erdpy.proxy.http_facade import do_get, do_post
 from erdpy.proxy.messages import NetworkConfig
 
@@ -13,7 +13,7 @@
 logger = logging.getLogger("proxy")
 
 
-class ElrondProxy:
+class ElrondProxy(IElrondProxy):
     def __init__(self, url: str):
         self.url = url
 
diff --git a/erdpy/transactions.py b/erdpy/transactions.py
index 5f6cdb63..71d27d62 100644
--- a/erdpy/transactions.py
+++ b/erdpy/transactions.py
@@ -115,6 +115,7 @@ def send(self, proxy: IElrondProxy):
         dictionary = self.to_dictionary()
         self.hash = proxy.send_transaction(dictionary)
         logger.info(f"Hash: {self.hash}")
+        utils.log_explorer_transaction(self.chainID, self.hash)
         return self.hash
 
     def send_wait_result(self, proxy, timeout):
diff --git a/erdpy/utils.py b/erdpy/utils.py
index e9c477f0..e3092818 100644
--- a/erdpy/utils.py
+++ b/erdpy/utils.py
@@ -9,7 +9,7 @@
 import tarfile
 import zipfile
 from pathlib import Path
-from typing import Any, List, Union, Optional, cast, IO, Dict
+from typing import Any, AnyStr, List, Union, Optional, cast, IO, Dict
 
 import toml
 
@@ -78,30 +78,32 @@ def read_lines(file: str):
 # TODO delete this function, it is too generic
 # TODO find usages in legolas
 def read_file(f: Any, binary: bool = False) -> Union[str, bytes]:
-    try:
-        if isinstance(f, str) or isinstance(f, pathlib.PosixPath):
-            path = Path(f)
-            if binary:
-                return read_binary_file(path)
-            return read_text_file(path)
-
-        file = cast(IO, f)
-        result = file.read()
-        assert isinstance(result, str) or isinstance(result, bytes)
-        return result
+    if isinstance(f, str) or isinstance(f, pathlib.PosixPath):
+        path = Path(f)
+        if binary:
+            return read_binary_file(path)
+        return read_text_file(path)
 
-    except Exception as err:
-        raise errors.BadFile(f, err)
+    file = cast(IO, f)
+    result = file.read()
+    assert isinstance(result, str) or isinstance(result, bytes)
+    return result
 
 
 def read_binary_file(path: Path) -> bytes:
-    with open(path, 'rb') as binary_file:
-        return binary_file.read()
+    try:
+        with open(path, 'rb') as binary_file:
+            return binary_file.read()
+    except Exception as err:
+        raise errors.BadFile(str(path), err) from None
 
 
 def read_text_file(path: Path) -> str:
-    with open(path, 'r') as text_file:
-        return text_file.read()
+    try:
+        with open(path, 'r') as text_file:
+            return text_file.read()
+    except Exception as err:
+        raise errors.BadFile(str(path), err) from None
 
 
 def write_file(f: Any, text: str):
@@ -195,7 +197,7 @@ def as_object(data: Object) -> Object:
     return data
 
 
-def is_arg_present(key: str, args: List[str]) -> bool:
+def is_arg_present(args: List[str], key: str) -> bool:
     for arg in args:
         if arg.find("--data") != -1:
             continue
@@ -230,3 +232,24 @@ def breakpoint():
     print("Waiting for debugger attach")
     debugpy.wait_for_client()
     debugpy.breakpoint()
+
+
+def log_explorer(chain, name, path, details):
+    networks = {
+        "1": ("Elrond Mainnet Explorer", "https://explorer.elrond.com"),
+        "T": ("Elrond Testnet Explorer", "https://testnet-explorer.elrond.com"),
+        "D": ("Elrond Devnet Explorer", "https://devnet-explorer.elrond.com"),
+    }
+    try:
+        explorer_name, explorer_url = networks[chain]
+        logger.info(f"View this {name} in the {explorer_name}: {explorer_url}/{path}/{details}")
+    except KeyError:
+        return
+
+
+def log_explorer_contract_address(chain, address):
+    log_explorer(chain, "contract address", "accounts", address)
+
+
+def log_explorer_transaction(chain, transaction_hash):
+    log_explorer(chain, "transaction", "transactions", transaction_hash)
diff --git a/requirements.txt b/requirements.txt
index 0ce880d1..fc0043ae 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,15 @@
 toml>=0.10.2
+types-toml
 bottle~=0.12.18
 requests~=2.24.0
+types-requests
 pynacl
 pycryptodomex
 cryptography>=3.2
 prettytable
+types-prettytable
 ledgercomm
 
+pytest
+
 setuptools
diff --git a/setup.py b/setup.py
index 930546d3..e3d22d16 100644
--- a/setup.py
+++ b/setup.py
@@ -3,11 +3,11 @@
 with open("README.md", "r") as fh:
     long_description = "https://github.com/ElrondNetwork/elrond-sdk-erdpy"
 
-VERSION = "1.0.17"
+VERSION = "1.0.18"
 
 try:
     with open('./erdpy/_version.py', 'wt') as versionfile:
-        versionfile.write(f'__version__ = "{VERSION}"')
+        versionfile.write(f'__version__ = "{VERSION}"\n')
 except FileNotFoundError:
     pass
 
@@ -38,9 +38,8 @@
     zip_safe=False,
     keywords=["Elrond"],
     classifiers=[
-        "Programming Language :: Python :: 3.6",
-        "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
         "License :: OSI Approved :: GNU General Public License (GPL)",
         "Operating System :: POSIX :: Linux",
         "Intended Audience :: Developers",
@@ -51,5 +50,5 @@
             "erdpy=erdpy.cli:main",
         ],
     },
-    python_requires=">=3.6"
+    python_requires=">=3.8"
 )


Test patch:
diff --git a/erdpy/cli_testnet.py b/erdpy/cli_testnet.py
index 5ba2c8e9..9b8f0181 100644
--- a/erdpy/cli_testnet.py
+++ b/erdpy/cli_testnet.py
@@ -1,12 +1,12 @@
 import logging
-from typing import Any
+from typing import Any, List
 
 from erdpy import cli_shared, testnet
 
 logger = logging.getLogger("cli.testnet")
 
 
-def setup_parser(subparsers: Any) -> Any:
+def setup_parser(args: List[str], subparsers: Any) -> Any:
     parser = cli_shared.add_group_subparser(
         subparsers,
         "testnet",
diff --git a/erdpy/tests/__init__.py b/erdpy/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/erdpy/tests/test_playground_proxy.py b/erdpy/tests/test_playground_proxy.py
index 7d3e4a39..d1dc07b4 100644
--- a/erdpy/tests/test_playground_proxy.py
+++ b/erdpy/tests/test_playground_proxy.py
@@ -4,7 +4,7 @@
 
 
 class TestPlaygroundElrondProxy(unittest.TestCase):
-    @unittest.skip
+    @unittest.skip('manual run only')
     def test_do_request(self):
         # use a valid proxy address
         url = "http://localhost:8001"
@@ -15,7 +15,7 @@ def test_do_request(self):
         print(response.json())
         self.assertTrue(self, response is not None)
 
-    @unittest.skip
+    @unittest.skip('manual run only')
     def test_do_request_node_status(self):
         # use a valid proxy address
         url = "http://localhost:8001"
diff --git a/erdpy/tests/test_solidity.py b/erdpy/tests/test_solidity.py
index 9ad9dd3d..663c3981 100644
--- a/erdpy/tests/test_solidity.py
+++ b/erdpy/tests/test_solidity.py
@@ -12,7 +12,7 @@ class ProjectSolidityTestCase(utils.ProjectTestCase):
     def setUp(self):
         super().setUp()
 
-    @unittest.skip
+    @unittest.skip('manual run only')
     def test_solidity_hello(self):
         _, contract = self.build("solidity_hello")
 
@@ -23,7 +23,7 @@ def myflow():
 
         self.environment.run_flow(myflow)
 
-    @unittest.skip
+    @unittest.skip('manual run only')
     def test_solidity_soll_001(self):
         _, contract = self.build("solidity_soll_001")
 
@@ -38,7 +38,7 @@ def add(a, b):
 
         self.environment.run_flow(myflow)
 
-    @unittest.skip
+    @unittest.skip('manual run only')
     def test_solidity_soll_003(self):
         _, contract = self.build("solidity_soll_003")