Repository: lidofinance/lido-python-sdk (language: python)

Issue:
Format Python code with psf/black push
There appear to be some python formatting errors in a56fcfd0384a2dfcf0b058442fe48ff2ea580d54. This pull request
uses the [psf/black](https://github.com/psf/black) formatter to fix these issues.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml
index b309c95..d885ba8 100644
--- a/.github/workflows/formatting.yml
+++ b/.github/workflows/formatting.yml
@@ -1,4 +1,4 @@
-              name: formating
+name: formating
 
 on:
   push:
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index fbf30c4..1f330d3 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -20,7 +20,7 @@ jobs:
       max-parallel: 4
       fail-fast: false
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: [3.7, 3.8, 3.9]
 
     steps:
     - uses: actions/checkout@v1
diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml
index 1f5148e..6a91058 100644
--- a/.github/workflows/osx.yml
+++ b/.github/workflows/osx.yml
@@ -20,7 +20,7 @@ jobs:
       max-parallel: 4
       fail-fast: false
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: [3.7, 3.8, 3.9]
 
     steps:
     - uses: actions/checkout@v1
@@ -38,10 +38,6 @@ jobs:
         python -m pip install --upgrade pip
         pip install -r requirements.txt
         pip install -r requirements-dev.txt
-    - name: pre-commit Checkers and Formatters
-      run: |
-        pre-commit install
-        pre-commit run --all-files
     - name: Test with pytest
       run: |
         pytest .
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 7f29623..218f634 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -20,7 +20,7 @@ jobs:
       max-parallel: 4
       fail-fast: false
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: [3.7, 3.8, 3.9]
 
     steps:
     - uses: actions/checkout@v1
@@ -38,10 +38,6 @@ jobs:
         python -m pip install --upgrade pip
         pip install -r requirements.txt
         pip install -r requirements-dev.txt
-    - name: pre-commit Checkers and Formatters
-      run: |
-        pre-commit install
-        pre-commit run --all-files
     - name: Test with pytest
       run: |
         pytest .
diff --git a/README.md b/README.md
index 6be212c..911adc2 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,75 @@
 This repo provides a scratch of a Python project. Its purpose is to serve as starting point for
 the development of a Python project based on a minimal working structure.
 
+## Installation
+This library is abailable on PyPi:
+
+```bash
+pip install lido-sdk
+```
+
+## Main Features
+
+### Multicall Function Calls
+
+Instead of making network requests one-by-one, this library combines many requests into one RPC call. It uses [banteg/multicall.py](https://github.com/banteg/multicall.py), a Python wrapper for [makerdao/multicall](https://github.com/makerdao/multicall).
+
+### Multiprocess Signature Validations
+
+When using `validate_keys_multi()`, this library spreads processing of key signature validations to all system cores.
+
+### Automatic Testnet / Mainnet Switching
+
+Depending on which network is configured in web3 object, a set of contracts will be used. Even an appropriate ABI will be loaded for the chain automatically.
+
+## Helpers Provided (IN PROGRESS)
+
+- lido.get_operators_data() -> operator_data - load node operator data
+
+- lido.get_operators_keys(operator_data) -> operator_data - fetches and adds keys to operator_data
+- lido.validate_keys_mono(operator_data, strict = False) -> operator_data - validates keys in single process and adds validation results to operator_data
+- lido.validate_keys_multi(operator_data, strict = False) -> operator_data - validates keys in multiple processes and adds validation results to operator_data, requires a main function (see example)
+- lido.validate_key_list_multi(operator_data, strict = False) -> [] - same as validate_keys_multi(), but returns a list of invalid keys
+- lido.validate_key(chain_id, key, withdrawal_credentials) -> Boolean - low-level validation function, doesn't check for correct
+  chain_id and withdrawal_credentials for a Lido deployment. For most use-cases use validate_keys_multi or validate_key_list_multi instead
+- lido.find_duplicates(operator_data) -> operator_data - finds duplicate keys and adds results to operator_data
+
+- lido.fetch_and_validate() -> operator_data - combines fetching operator data and running all validations on it - useful when you would be running all validations on data anyway
+
+- lido.get_stats() -> stats - fetches various constants from Lido contract, but you can even pass a list of functions to fetch eg get_stats(["isStopped"])
+
+You can mix and match these functions, but make sure to use get_operators_data() first.
+
+## Notes
+
+1. Signature validation will be skipped if its results are already present in operator_data. This way you can safely load validation results from cache and add `["valid_signature"] = Boolean` to already checked keys.
+
+2. Signature validation functions are accounting for previous withdrawal credentials by default. However, if you are building a fresh key validator it is vitally important to enable strict mode by setting strict argument of the functions to `True`. This way new keys with old withdrawal credentials won't pass validation.
+
+## Running an example script
+
+The example script uses web3.auto, so use a RPC provider url as an environment variable to run it:
+
+`WEB3_PROVIDER_URI=https://eth-mainnet.provider.xx example.py`
+
+See `example.py` for a complete example, make sure to use a main function and a script entry point check when using validate_keys_multi() or fetch_and_validate().
+
+## Options
+
+If you are testing a new deployment of Lido, you can override addresses and ABIs with constructor of Lido object. Also you can configure the maximum number of calls agregated to one multicall:
+
+```python
+lido = Lido(
+    w3,
+    lido_address=LIDO_ADDRESS,
+    registry_address=REGISTRY_ADDRESS,
+    lido_abi_path=LIDO_ABI, # the file-path to the contract's ABI
+    registry_abi_path=REGISTRY_ABI, # the file-path to the contract's ABI
+    max_multicall=MAX_MULTICALL,
+)
+```
+
+
 ## Development
 Clone project:
 ```bash
diff --git a/lido/contract/__init__.py b/lido/contract/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lido/contract/abi/Lido.json b/lido/contract/abi/Lido.json
new file mode 100644
index 0000000..be9578e
--- /dev/null
+++ b/lido/contract/abi/Lido.json
@@ -0,0 +1,1268 @@
+[
+  {
+    "constant": false,
+    "inputs": [],
+    "name": "resume",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [],
+    "name": "stop",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "hasInitialized",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_spender",
+        "type": "address"
+      },
+      {
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "depositContract",
+        "type": "address"
+      },
+      {
+        "name": "_oracle",
+        "type": "address"
+      },
+      {
+        "name": "_operators",
+        "type": "address"
+      },
+      {
+        "name": "_treasury",
+        "type": "address"
+      },
+      {
+        "name": "_insuranceFund",
+        "type": "address"
+      }
+    ],
+    "name": "initialize",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getInsuranceFund",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_ethAmount",
+        "type": "uint256"
+      }
+    ],
+    "name": "getSharesByPooledEth",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_sender",
+        "type": "address"
+      },
+      {
+        "name": "_recipient",
+        "type": "address"
+      },
+      {
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getOperators",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_script",
+        "type": "bytes"
+      }
+    ],
+    "name": "getEVMScriptExecutor",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getRecoveryVault",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "DEPOSIT_SIZE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getTotalPooledEther",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "PAUSE_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_spender",
+        "type": "address"
+      },
+      {
+        "name": "_addedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "increaseAllowance",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getTreasury",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SET_ORACLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "isStopped",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "MANAGE_WITHDRAWAL_KEY",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getBufferedEther",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SIGNATURE_LENGTH",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getWithdrawalCredentials",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_account",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getFeeDistribution",
+    "outputs": [
+      {
+        "name": "treasuryFeeBasisPoints",
+        "type": "uint16"
+      },
+      {
+        "name": "insuranceFeeBasisPoints",
+        "type": "uint16"
+      },
+      {
+        "name": "operatorsFeeBasisPoints",
+        "type": "uint16"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_sharesAmount",
+        "type": "uint256"
+      }
+    ],
+    "name": "getPooledEthByShares",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_oracle",
+        "type": "address"
+      }
+    ],
+    "name": "setOracle",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "token",
+        "type": "address"
+      }
+    ],
+    "name": "allowRecoverability",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "appId",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getOracle",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getInitializationBlock",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_treasuryFeeBasisPoints",
+        "type": "uint16"
+      },
+      {
+        "name": "_insuranceFeeBasisPoints",
+        "type": "uint16"
+      },
+      {
+        "name": "_operatorsFeeBasisPoints",
+        "type": "uint16"
+      }
+    ],
+    "name": "setFeeDistribution",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_feeBasisPoints",
+        "type": "uint16"
+      }
+    ],
+    "name": "setFee",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_maxDeposits",
+        "type": "uint256"
+      }
+    ],
+    "name": "depositBufferedEther",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "MANAGE_FEE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_token",
+        "type": "address"
+      }
+    ],
+    "name": "transferToVault",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SET_TREASURY",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_sender",
+        "type": "address"
+      },
+      {
+        "name": "_role",
+        "type": "bytes32"
+      },
+      {
+        "name": "_params",
+        "type": "uint256[]"
+      }
+    ],
+    "name": "canPerform",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_referral",
+        "type": "address"
+      }
+    ],
+    "name": "submit",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": true,
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "WITHDRAWAL_CREDENTIALS_LENGTH",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_spender",
+        "type": "address"
+      },
+      {
+        "name": "_subtractedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "decreaseAllowance",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getEVMScriptRegistry",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "PUBKEY_LENGTH",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "name": "_pubkeyHash",
+        "type": "bytes32"
+      }
+    ],
+    "name": "withdraw",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_recipient",
+        "type": "address"
+      },
+      {
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transfer",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getDepositContract",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getBeaconStat",
+    "outputs": [
+      {
+        "name": "depositedValidators",
+        "type": "uint256"
+      },
+      {
+        "name": "beaconValidators",
+        "type": "uint256"
+      },
+      {
+        "name": "beaconBalance",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "BURN_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_insuranceFund",
+        "type": "address"
+      }
+    ],
+    "name": "setInsuranceFund",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getFee",
+    "outputs": [
+      {
+        "name": "feeBasisPoints",
+        "type": "uint16"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SET_INSURANCE_FUND",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "kernel",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getTotalShares",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_owner",
+        "type": "address"
+      },
+      {
+        "name": "_spender",
+        "type": "address"
+      }
+    ],
+    "name": "allowance",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "isPetrified",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_withdrawalCredentials",
+        "type": "bytes32"
+      }
+    ],
+    "name": "setWithdrawalCredentials",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [],
+    "name": "depositBufferedEther",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_account",
+        "type": "address"
+      },
+      {
+        "name": "_sharesAmount",
+        "type": "uint256"
+      }
+    ],
+    "name": "burnShares",
+    "outputs": [
+      {
+        "name": "newTotalShares",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_treasury",
+        "type": "address"
+      }
+    ],
+    "name": "setTreasury",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_beaconValidators",
+        "type": "uint256"
+      },
+      {
+        "name": "_beaconBalance",
+        "type": "uint256"
+      }
+    ],
+    "name": "pushBeacon",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_account",
+        "type": "address"
+      }
+    ],
+    "name": "sharesOf",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "payable": true,
+    "stateMutability": "payable",
+    "type": "fallback"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "executor",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "script",
+        "type": "bytes"
+      },
+      {
+        "indexed": false,
+        "name": "input",
+        "type": "bytes"
+      },
+      {
+        "indexed": false,
+        "name": "returnData",
+        "type": "bytes"
+      }
+    ],
+    "name": "ScriptResult",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "vault",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "name": "token",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "RecoverToVault",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [],
+    "name": "Stopped",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [],
+    "name": "Resumed",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "name": "feeBasisPoints",
+        "type": "uint16"
+      }
+    ],
+    "name": "FeeSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "name": "treasuryFeeBasisPoints",
+        "type": "uint16"
+      },
+      {
+        "indexed": false,
+        "name": "insuranceFeeBasisPoints",
+        "type": "uint16"
+      },
+      {
+        "indexed": false,
+        "name": "operatorsFeeBasisPoints",
+        "type": "uint16"
+      }
+    ],
+    "name": "FeeDistributionSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "name": "withdrawalCredentials",
+        "type": "bytes32"
+      }
+    ],
+    "name": "WithdrawalCredentialsSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "amount",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "referral",
+        "type": "address"
+      }
+    ],
+    "name": "Submitted",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "Unbuffered",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "tokenAmount",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "sentFromBuffer",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "name": "pubkeyHash",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "name": "etherAmount",
+        "type": "uint256"
+      }
+    ],
+    "name": "Withdrawal",
+    "type": "event"
+  }
+]
diff --git a/lido/contract/abi/NodeOperatorsRegistry.json b/lido/contract/abi/NodeOperatorsRegistry.json
new file mode 100644
index 0000000..a83fb5c
--- /dev/null
+++ b/lido/contract/abi/NodeOperatorsRegistry.json
@@ -0,0 +1,918 @@
+[
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "hasInitialized",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_name",
+        "type": "string"
+      },
+      {
+        "name": "_rewardAddress",
+        "type": "address"
+      },
+      {
+        "name": "_stakingLimit",
+        "type": "uint64"
+      }
+    ],
+    "name": "addNodeOperator",
+    "outputs": [
+      {
+        "name": "id",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_operator_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_quantity",
+        "type": "uint256"
+      },
+      {
+        "name": "_pubkeys",
+        "type": "bytes"
+      },
+      {
+        "name": "_signatures",
+        "type": "bytes"
+      }
+    ],
+    "name": "addSigningKeys",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_script",
+        "type": "bytes"
+      }
+    ],
+    "name": "getEVMScriptExecutor",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getRecoveryVault",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_numKeys",
+        "type": "uint256"
+      }
+    ],
+    "name": "assignNextSigningKeys",
+    "outputs": [
+      {
+        "name": "pubkeys",
+        "type": "bytes"
+      },
+      {
+        "name": "signatures",
+        "type": "bytes"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SIGNATURE_LENGTH",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SET_NODE_OPERATOR_ADDRESS_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_name",
+        "type": "string"
+      }
+    ],
+    "name": "setNodeOperatorName",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_totalRewardShares",
+        "type": "uint256"
+      }
+    ],
+    "name": "getRewardsDistribution",
+    "outputs": [
+      {
+        "name": "recipients",
+        "type": "address[]"
+      },
+      {
+        "name": "shares",
+        "type": "uint256[]"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_active",
+        "type": "bool"
+      }
+    ],
+    "name": "setNodeOperatorActive",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SET_NODE_OPERATOR_NAME_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_operator_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_index",
+        "type": "uint256"
+      }
+    ],
+    "name": "removeSigningKey",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "ADD_NODE_OPERATOR_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "token",
+        "type": "address"
+      }
+    ],
+    "name": "allowRecoverability",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_operator_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_quantity",
+        "type": "uint256"
+      },
+      {
+        "name": "_pubkeys",
+        "type": "bytes"
+      },
+      {
+        "name": "_signatures",
+        "type": "bytes"
+      }
+    ],
+    "name": "addSigningKeysOperatorBH",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "appId",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getActiveNodeOperatorsCount",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getInitializationBlock",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_operator_id",
+        "type": "uint256"
+      }
+    ],
+    "name": "getUnusedSigningKeyCount",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_rewardAddress",
+        "type": "address"
+      }
+    ],
+    "name": "setNodeOperatorRewardAddress",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_fullInfo",
+        "type": "bool"
+      }
+    ],
+    "name": "getNodeOperator",
+    "outputs": [
+      {
+        "name": "active",
+        "type": "bool"
+      },
+      {
+        "name": "name",
+        "type": "string"
+      },
+      {
+        "name": "rewardAddress",
+        "type": "address"
+      },
+      {
+        "name": "stakingLimit",
+        "type": "uint64"
+      },
+      {
+        "name": "stoppedValidators",
+        "type": "uint64"
+      },
+      {
+        "name": "totalSigningKeys",
+        "type": "uint64"
+      },
+      {
+        "name": "usedSigningKeys",
+        "type": "uint64"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_token",
+        "type": "address"
+      }
+    ],
+    "name": "transferToVault",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_sender",
+        "type": "address"
+      },
+      {
+        "name": "_role",
+        "type": "bytes32"
+      },
+      {
+        "name": "_params",
+        "type": "uint256[]"
+      }
+    ],
+    "name": "canPerform",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getEVMScriptRegistry",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "PUBKEY_LENGTH",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getNodeOperatorsCount",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_stakingLimit",
+        "type": "uint64"
+      }
+    ],
+    "name": "setNodeOperatorStakingLimit",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_operator_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_index",
+        "type": "uint256"
+      }
+    ],
+    "name": "getSigningKey",
+    "outputs": [
+      {
+        "name": "key",
+        "type": "bytes"
+      },
+      {
+        "name": "depositSignature",
+        "type": "bytes"
+      },
+      {
+        "name": "used",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_stoppedIncrement",
+        "type": "uint64"
+      }
+    ],
+    "name": "reportStoppedValidators",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_lido",
+        "type": "address"
+      }
+    ],
+    "name": "initialize",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "REPORT_STOPPED_VALIDATORS_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "kernel",
+    "outputs": [
+      {
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SET_NODE_OPERATOR_ACTIVE_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "SET_NODE_OPERATOR_LIMIT_ROLE",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_operator_id",
+        "type": "uint256"
+      }
+    ],
+    "name": "getTotalSigningKeyCount",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "isPetrified",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_operator_id",
+        "type": "uint256"
+      },
+      {
+        "name": "_index",
+        "type": "uint256"
+      }
+    ],
+    "name": "removeSigningKeyOperatorBH",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "MANAGE_SIGNING_KEYS",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [],
+    "name": "trimUnusedKeys",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "executor",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "script",
+        "type": "bytes"
+      },
+      {
+        "indexed": false,
+        "name": "input",
+        "type": "bytes"
+      },
+      {
+        "indexed": false,
+        "name": "returnData",
+        "type": "bytes"
+      }
+    ],
+    "name": "ScriptResult",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "vault",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "name": "token",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "RecoverToVault",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "name": "id",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "name",
+        "type": "string"
+      },
+      {
+        "indexed": false,
+        "name": "rewardAddress",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "stakingLimit",
+        "type": "uint64"
+      }
+    ],
+    "name": "NodeOperatorAdded",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "id",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "active",
+        "type": "bool"
+      }
+    ],
+    "name": "NodeOperatorActiveSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "id",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "name",
+        "type": "string"
+      }
+    ],
+    "name": "NodeOperatorNameSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "id",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "rewardAddress",
+        "type": "address"
+      }
+    ],
+    "name": "NodeOperatorRewardAddressSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "id",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "stakingLimit",
+        "type": "uint64"
+      }
+    ],
+    "name": "NodeOperatorStakingLimitSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "id",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "totalStopped",
+        "type": "uint64"
+      }
+    ],
+    "name": "NodeOperatorTotalStoppedValidatorsReported",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "operatorId",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "pubkey",
+        "type": "bytes"
+      }
+    ],
+    "name": "SigningKeyAdded",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "operatorId",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "name": "pubkey",
+        "type": "bytes"
+      }
+    ],
+    "name": "SigningKeyRemoved",
+    "type": "event"
+  }
+]
diff --git a/lido/contract/contracts.py b/lido/contract/contracts.py
new file mode 100644
index 0000000..89643fd
--- /dev/null
+++ b/lido/contract/contracts.py
@@ -0,0 +1,21 @@
+import json
+import os
+from enum import Enum
+
+from web3 import Web3
+from web3.contract import Contract
+
+
+def _get_contract_abi(contract_name: str):
+    script_dir = os.path.dirname(__file__)
+
+    return json.load(open(os.path.join(script_dir, "abi", contract_name)))
+
+
+class ContractABI(Enum):
+    LIDO = _get_contract_abi("abi/Lido.json")
+    NODE_OPERATORS_REGISTRY = _get_contract_abi("abi/NodeOperatorsRegistry.json")
+
+
+def load_contract(w3: Web3, address: str, contract_abi: str) -> Contract:
+    return w3.eth.contract(address=address, abi=contract_abi)
diff --git a/lido/multicall/__init__.py b/lido/multicall/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lido/multicall/multicall.py b/lido/multicall/multicall.py
new file mode 100644
index 0000000..a70e019
--- /dev/null
+++ b/lido/multicall/multicall.py
@@ -0,0 +1,21 @@
+from multicall import Call, Multicall as DefaultMulticall
+
+from lido.network.address import MULTICALL_ADDRESSES
+
+
+class Multicall(DefaultMulticall):
+    def __call__(self):
+        """Overwrite call to use our MULTICALL_ADDRESSES"""
+        aggregate = Call(
+            MULTICALL_ADDRESSES[self.w3.eth.chainId],
+            "aggregate((address,bytes)[])(uint256,bytes[])",
+            returns=None,
+            _w3=self.w3,
+            block_id=self.block_id,
+        )
+        args = [[[call.target, call.data] for call in self.calls]]
+        block, outputs = aggregate(args)
+        result = {}
+        for call, output in zip(self.calls, outputs):
+            result.update(call.decode_output(output))
+        return result
diff --git a/lido/network/__init__.py b/lido/network/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lido/network/address.py b/lido/network/address.py
new file mode 100644
index 0000000..bf02003
--- /dev/null
+++ b/lido/network/address.py
@@ -0,0 +1,20 @@
+LIDO_ADDRESSES = {
+    Network.MAINNET: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
+    Network.Görli: "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F",
+    Network.Ropsten: "0xd40EefCFaB888C9159a61221def03bF77773FC19",
+}
+
+NODE_OPS_ADDRESSES = {
+    Network.MAINNET: "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5",
+    Network.Görli: "0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320",
+    Network.Ropsten: "0x32c6f34F3920E8c0074241619c02be2fB722a68d",
+}
+
+MULTICALL_ADDRESSES = {
+    Network.Mainnet: "0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441",
+    Network.Kovan: "0x2cc8688C5f75E365aaEEb4ea8D6a480405A48D2A",
+    Network.Rinkeby: "0x42Ad527de7d4e9d9d011aC45B31D8551f8Fe9821",
+    Network.Görli: "0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e",
+    Network.xDai: "0xb5b692a88BDFc81ca69dcB1d924f59f0413A602a",
+    Network.Ropsten: "0x53c43764255c17bd724f74c4ef150724ac50a3ed",
+}
diff --git a/lido/network/type.py b/lido/network/type.py
new file mode 100644
index 0000000..aa1410e
--- /dev/null
+++ b/lido/network/type.py
@@ -0,0 +1,10 @@
+from enum import IntEnum
+
+
+class Network(IntEnum):
+    Mainnet = 1
+    Kovan = 42
+    Rinkeby = 4
+    Görli = 5
+    xDai = 100
+    Ropsten = 3
diff --git a/lido/network/withdrawal_credentials.py b/lido/network/withdrawal_credentials.py
new file mode 100644
index 0000000..0a6c3fe
--- /dev/null
+++ b/lido/network/withdrawal_credentials.py
@@ -0,0 +1,14 @@
+from multicall.constants import Network
+
+
+# Existing withdrawal credentials on the chain
+# Will be filtered for unique values
+# Will be used as a fallback for used keys or in strict mode when validating signatures
+WITHDRAWAL_CREDENTIALS = {
+    Network.Mainnet: [
+        "0x009690e5d4472c7c0dbdf490425d89862535d2a52fb686333f3a0a9ff5d2125e"
+    ],
+    Network.Görli: [
+        "0x00040517ce98f81070cea20e35610a3ae23a45f0883b0b035afc5717cc2e833e"
+    ],
+}
diff --git a/lido/operator/__init__.py b/lido/operator/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lido/operator/operator.py b/lido/operator/operator.py
new file mode 100644
index 0000000..6fe0aa2
--- /dev/null
+++ b/lido/operator/operator.py
@@ -0,0 +1,69 @@
+import typing as t
+import logging
+
+from multicall import Call, Multicall
+
+logger = logging.getLogger(__name__)
+
+
+def get_operators_data(
+    w3,
+    registry_address: str,
+    registry_abi_path: str,
+) -> t.List[t.Dict]:
+    """Fetch information for each node operator
+
+    Example output:
+    [{
+        'id': 0,
+        'active': True,
+        'name': 'Staking Facilities',
+        'rewardAddress': '0xdd4bc51496dc93a0c47008e820e0d80745476f22',
+        'stakingLimit': 2040,
+        'stoppedValidators': 0,
+        'totalSigningKeys': 2500,
+        'usedSigningKeys': 2000
+    }...]
+    """
+
+    operators_n = Call(w3, registry_address, "getNodeOperatorsCount()(uint256)")()
+    logger.debug(f"{operators_n=}")
+    if operators_n == 0:
+        logger.warning(f"no operators")  # fixme assert if not test env
+        return []
+    assert operators_n < 1_000_000, "too big operators_n"
+
+    calls = Multicall(
+        w3,
+        [
+            Call(
+                w3,
+                registry_address,
+                [
+                    "getNodeOperator(uint256,bool)(bool,string,address,uint64,uint64,uint64,uint64)",
+                    i,
+                    True,
+                ],
+                [[i, None]],
+            )
+            for i in range(operators_n)
+        ],
+    )()
+
+    calls_list = list(calls.values())
+
+    # Adding index as first data of operator
+    calls_with_indeces = [[i] + list(item) for i, item in enumerate(calls_list)]
+
+    # Getting function data from contract ABI
+    function_abi = next(
+        x
+        for x in get_contract(w3, address=registry_address, path=registry_abi_path).abi
+        if x["name"] == "getNodeOperator"
+    )
+
+    # Adding "id" and the rest of output name keys
+    op_keys = ["id"] + [x["name"] for x in function_abi["outputs"]]
+    operators = [dict(zip(op_keys, op)) for op in calls_with_indeces]
+
+    return operators
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 8ca57bf..9006cd9 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,5 +1,5 @@
 pytest==6.2.4
-pre-commit==2.14.0
 pytest-cov==2.12.1
 codecov==2.1.12
 sphinx_rtd_theme==0.5.2
+eth-tester==0.5.0b4
diff --git a/requirements.txt b/requirements.txt
index e69de29..e528397 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+ssz==0.2.4
+web3==5.23.0
+multicall==0.1.2
diff --git a/setup.py b/setup.py
index 14298e7..1ba9e59 100644
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@
 URL = "https://github.com/lidofinance/lido-validator-python"
 EMAIL = "info@lido.fi"
 AUTHOR = "Lido"
-REQUIRES_PYTHON = ">=3.6,<4"
+REQUIRES_PYTHON = ">=3.7,<4"
 VERSION = "0.1.0"
 
 # The rest you shouldn't have to touch too much :)
@@ -105,6 +105,9 @@ def run(self):
         "License :: OSI Approved :: MIT License",
         "Development Status :: 2 - Pre-Alpha",
         "Programming Language :: Python :: 3 :: Only",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
     ],
     # $ setup.py publish support.
     cmdclass={


Test patch:
diff --git a/tests/test_foo.py b/tests/test_foo.py
new file mode 100644
index 0000000..8f2d73c
--- /dev/null
+++ b/tests/test_foo.py
@@ -0,0 +1,2 @@
+def test_foo():
+    pass