diff --git a/README.md b/README.md
index 6d7e8fc..bb6a9f4 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 ## Oracle
 
-Oracles are responsible for voting on the new ETH2 rewards for the StakeWise sETH2 tokens holders and calculating Merkle
+Oracles are responsible for voting on the new rewards for the StakeWise staked tokens holders and calculating Merkle
 root and proofs for the additional token distributions through the
 [Merkle Distributor](https://github.com/stakewise/contracts/blob/master/contracts/merkles/MerkleDistributor.sol)
 contract.
@@ -31,57 +31,37 @@ supports [ETH2 Beacon Node API specification](https://ethereum.github.io/beacon-
 - [Teku](https://launchpad.ethereum.org/en/teku)
 - [Infura](https://infura.io/docs/eth2) (hosted)
 
-### Usage
+### Oracle Usage
 
-1. Move to `deploy` directory
+1. Move to `deploy/<network>` directory
 
 ```shell script
-cd deploy
+cd deploy/mainnet
 ```
 
-2. Create an edit environment file (see `Oracle Settings` below)
+2. Create an edit environment file
 
 ```shell script
 cp .env.example .env
 ```
 
-3. Enable `pushover` alerts in `configs/alertmanager.yml`
+3. Enable `pushover` alerts in `deploy/configs/alertmanager.yml`
 
-4. Run with [docker-compose](https://docs.docker.com/compose/)
+   1. Register an account on [pushover](https://pushover.net/).
+   2. Create an [Application/API Token](https://pushover.net/apps/build).
+   3. Add `User Key` and `API Token` to `deploy/configs/alertmanager.yml` file.
+
+4. Run with [docker-compose](https://docs.docker.com/compose/). The docker-compose version must be **v1.27.0+**.
 
 ```shell script
-docker-compose -f docker-compose.yml up -d
+COMPOSE_PROFILES=lighthouse docker-compose up -d
 ```
 
-### Oracle Settings
-
-| Variable                | Description                                                                        | Required | Default                                                                 |
-|-------------------------|------------------------------------------------------------------------------------|----------|-------------------------------------------------------------------------|
-| NETWORK                 | The network that the oracle is currently operating on. Choices are goerli, mainnet | No       | mainnet                                                                 |
-| ENABLE_HEALTH_SERVER    | Defines whether to enable health server                                            | No       | True                                                                    |
-| HEALTH_SERVER_PORT      | The port where the health server will run                                          | No       | 8080                                                                    |
-| HEALTH_SERVER_HOST      | The host where the health server will run                                          | No       | 127.0.0.1                                                               |
-| IPFS_PIN_ENDPOINTS      | The IPFS endpoint where the rewards will be uploaded                               | No       | /dns/ipfs.infura.io/tcp/5001/https                                      |
-| IPFS_FETCH_ENDPOINTS    | The IPFS endpoints from where the rewards will be fetched                          | No       | https://gateway.pinata.cloud,http://cloudflare-ipfs.com,https://ipfs.io |
-| IPFS_PINATA_API_KEY     | The Pinata API key for uploading reward proofs for the redundancy                  | No       | -                                                                       |
-| IPFS_PINATA_SECRET_KEY  | The Pinata Secret key for uploading reward proofs for the redundancy               | No       | -                                                                       |
-| ETH2_ENDPOINT           | The ETH2 node endpoint                                                             | No       | http://localhost:3501                                                   |
-| ETH2_CLIENT             | The ETH2 client used. Choices are prysm, lighthouse, teku.                         | No       | prysm                                                                   |
-| ORACLE_PRIVATE_KEY      | The ETH1 private key of the oracle                                                 | Yes      | -                                                                       |
-| AWS_ACCESS_KEY_ID       | The AWS access key used to make the oracle vote public                             | Yes      | -                                                                       |
-| AWS_SECRET_ACCESS_KEY   | The AWS secret access key used to make the oracle vote public                      | Yes      | -                                                                       |
-| STAKEWISE_SUBGRAPH_URL  | The StakeWise subgraph URL                                                         | No       | https://api.thegraph.com/subgraphs/name/stakewise/stakewise-mainnet     |
-| UNISWAP_V3_SUBGRAPH_URL | The Uniswap V3 subgraph URL                                                        | No       | https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-mainnet    |
-| RARI_FUSE_SUBGRAPH_URL  | Rari Capital Fuse subgraph URL                                                     | No       | https://api.thegraph.com/subgraphs/name/stakewise/rari-fuse-mainnet     |
-| ETHEREUM_SUBGRAPH_URL   | The Ethereum subgraph URL                                                          | No       | https://api.thegraph.com/subgraphs/name/stakewise/ethereum-mainnet      |
-| ORACLE_PROCESS_INTERVAL | How long to wait before processing again (in seconds)                              | No       | 180                                                                     |
-| CONFIRMATION_BLOCKS     | The required number of confirmation blocks used to fetch the data                  | No       | 15                                                                      |
-| LOG_LEVEL               | The log level of the oracle                                                        | No       | INFO                                                                    |
-
 ## Keeper
 
 Keeper is an oracle that aggregates votes that were submitted by all the oracles and submits the update transaction.
 The keeper does not require any additional role, and can be executed by any of the oracles.
+It helps save the gas cost and stability as there is no need for every oracle to submit vote.
 
 ### Dependencies
 
@@ -91,49 +71,24 @@ The ETH1 node is used to submit the transactions on chain. Any of the ETH1 clien
 
 - [Go-ethereum](https://github.com/ethereum/go-ethereum)
 - [OpenEthereum](https://github.com/openethereum/openethereum)
+- [Nethermind](https://github.com/NethermindEth/nethermind)
 - [Infura](https://infura.io/docs/eth2) (hosted)
 - [Alchemy](https://www.alchemy.com/) (hosted)
 
-### Usage
-
-1. Move to `deploy` directory
-
-```shell script
-cd deploy
-```
+### Keeper Usage
 
-2. Create an edit environment file (see `Keeper Settings` below)
+1. Make sure keeper has enough balance to submit the transactions
 
-```shell script
-cp .env.example .env
-```
+2. Go through the [oracle usage](#oracle-usage) steps above
 
-3. Enable `pushover` alerts in `configs/alertmanager.yml`
+3. Configure keeper section in the `deploy/<network>/.env` file
 
 4. Uncomment `keeper` sections in the following files:
-   * configs/rules.yml
-   * configs/prometheus.yml
-   * configs/rules.yml
-   * docker-compose.yml
+   * `deploy/configs/prometheus.yml`
+   * `deploy/configs/rules.yml`
 
-5. Run with [docker-compose](https://docs.docker.com/compose/)
+5. Run with [docker-compose](https://docs.docker.com/compose/). The docker-compose version must be **v1.27.0+**.
 
 ```shell script
-docker-compose -f docker-compose.yml up -d
+COMPOSE_PROFILES=lighthouse,keeper docker-compose up -d
 ```
-
-### Keeper Settings
-
-| Variable                | Description                                                                        | Required | Default   |
-|-------------------------|------------------------------------------------------------------------------------|----------|-----------|
-| NETWORK                 | The network that the keeper is currently operating on. Choices are goerli, mainnet | No       | mainnet   |
-| WEB3_ENDPOINT           | The endpoint of the ETH1 node.                                                     | Yes      | -         |
-| MAX_FEE_PER_GAS_GWEI    | The max fee per gas keeper is willing to pay. Specified in GWEI.                   |  No      | 150       |
-| ENABLE_HEALTH_SERVER    | Defines whether to enable health server                                            | No       | True      |
-| HEALTH_SERVER_PORT      | The port where the health server will run                                          | No       | 8080      |
-| HEALTH_SERVER_HOST      | The host where the health server will run                                          | No       | 127.0.0.1 |
-| ORACLE_PRIVATE_KEY      | The ETH1 private key of the oracle                                                 | Yes      | -         |
-| KEEPER_PROCESS_INTERVAL | How long to wait before processing again (in seconds)                              | No       | 180       |
-| CONFIRMATION_BLOCKS     | The required number of confirmation blocks used to fetch the data                  | No       | 15        |
-| KEEPER_MIN_BALANCE_WEI  | The minimum balance keeper must have for votes submission                          | No       | 0.1 ETH   |
-| LOG_LEVEL               | The log level of the keeper                                                        | No       | INFO      |
diff --git a/deploy/.env.example b/deploy/.env.example
deleted file mode 100644
index 4d64b78..0000000
--- a/deploy/.env.example
+++ /dev/null
@@ -1,43 +0,0 @@
-# mainnet or goerli
-NETWORK=mainnet
-ENABLE_HEALTH_SERVER=true
-HEALTH_SERVER_PORT=8080
-HEALTH_SERVER_HOST=0.0.0.0
-ORACLE_PRIVATE_KEY=0x<private_key>
-
-# ORACLE
-IPFS_PINATA_API_KEY=""
-IPFS_PINATA_SECRET_KEY=""
-ETH2_ENDPOINT=http://lighthouse:5052
-# lighthouse, prysm or teku
-ETH2_CLIENT=lighthouse
-AWS_ACCESS_KEY_ID=""
-AWS_SECRET_ACCESS_KEY=""
-
-# Uncomment below lines if use self-hosted graph node
-# STAKEWISE_SUBGRAPH_URL=http://graph-node:8000/subgraphs/name/stakewise/stakewise
-# UNISWAP_V3_SUBGRAPH_URL=http://graph-node:8000/subgraphs/name/stakewise/uniswap-v3
-# ETHEREUM_SUBGRAPH_URL=http://graph-node:8000/subgraphs/name/stakewise/ethereum
-# RARI_FUSE_SUBGRAPH_URL=http://graph-node:8000/subgraphs/name/stakewise/rari-fuse
-
-# KEEPER
-WEB3_ENDPOINT=""
-MAX_FEE_PER_GAS_GWEI=150
-
-# GRAPH
-postgres_host=postgres
-postgres_user=graph
-postgres_pass=strong-password
-postgres_db=graph-node
-ipfs=ipfs:5001
-ethereum=mainnet:http://geth:8545
-GRAPH_LOG=info
-GRAPH_NODE_URL=http://graph-node:8020
-IPFS_URL=http://ipfs:5001
-IPFS_PROFILE=server
-IPFS_FD_MAX=8192
-
-# POSTGRESQL
-POSTGRES_DB=graph-node
-POSTGRES_USER=graph
-POSTGRES_PASSWORD=strong-password
diff --git a/deploy/README.md b/deploy/README.md
deleted file mode 100644
index a5a5add..0000000
--- a/deploy/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Deploy instruction
-
-The deployment directory contains a set of docker-compose files for deploying `oracle` and `keeper` (by default, only `oracle` will be deployed). Oracle requires eth2 node, graph node and ipfs as dependencies, if you do not use cloud solutions, you can deploy self-hosted dependencies:
-
-```console
-$ COMPOSE_PROFILES=oracle,geth,prysm,graph docker-compose up -d
-```
-
-If you want to run the keeper service:
-
-1. Uncomment `keeper` service in `docker-compose.yml` file.
-1. Uncomment `keeper` job in `configs/prometheus.yml` file.
-1. Uncomment `keeper` rule in `configs/rules.yml` file.
-
-## Monitoring
-
-If you want to receive notifications on one of your devices:
-
-1. Register an account on [pushover](https://pushover.net/).
-1. Create an [Application/API Token](https://pushover.net/apps/build).
-1. Add `User Key` and `API Token` to `configs/alertmanager.yml` file.
-1. Restart `docker-compose`.
diff --git a/deploy/configs/genesis.ssz b/deploy/configs/genesis.ssz
new file mode 100644
index 0000000..3f03867
Binary files /dev/null and b/deploy/configs/genesis.ssz differ
diff --git a/deploy/gnosis/.env.example b/deploy/gnosis/.env.example
new file mode 100644
index 0000000..91be9a6
--- /dev/null
+++ b/deploy/gnosis/.env.example
@@ -0,0 +1,79 @@
+##########
+# Oracle #
+##########
+LOG_LEVEL=INFO
+ENABLED_NETWORKS=gnosis
+ENABLE_HEALTH_SERVER=true
+HEALTH_SERVER_PORT=8080
+HEALTH_SERVER_HOST=0.0.0.0
+
+# Remove ",/dns/ipfs/tcp/5001/http" if you don't use "ipfs" profile
+IPFS_PIN_ENDPOINTS=/dns/ipfs.infura.io/tcp/5001/https,/dns/ipfs/tcp/5001/http
+
+# Optionally pin merkle proofs to the pinata service for redundancy
+IPFS_PINATA_API_KEY=<pinata_api_key>
+IPFS_PINATA_SECRET_KEY=<pinata_secret_key>
+
+# Change https://api.thegraph.com to http://graph-node:8000 if running local graph node
+GNOSIS_STAKEWISE_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/stakewise-gnosis
+GNOSIS_ETHEREUM_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/ethereum-gnosis
+
+# Ethereum private key
+# NB! You must use a different private key for every network
+GNOSIS_ORACLE_PRIVATE_KEY=0x<private_key>
+
+# ETH2 (consensus) client endpoint
+# Change if running an external ETH2 node
+GNOSIS_ETH2_ENDPOINT=http://eth2-node:5052
+
+# AWS bucket to publish oracle votes to
+GNOSIS_AWS_ACCESS_KEY_ID=<access_id>
+GNOSIS_AWS_SECRET_ACCESS_KEY=<secret_key>
+GNOSIS_AWS_BUCKET_NAME=oracle-votes-gnosis
+GNOSIS_AWS_REGION=us-east-2
+
+##########
+# Keeper #
+##########
+# Change if running an external ETH1 node
+GNOSIS_KEEPER_ETH1_ENDPOINT=http://eth1-node:8545
+# Use https://eth-converter.com/ to calculate
+GNOSIS_KEEPER_MIN_BALANCE_WEI=1000000000000000000
+GNOSIS_KEEPER_MAX_FEE_PER_GAS_GWEI=150
+
+########
+# IPFS #
+########
+IPFS_URL=http://ipfs:5001
+IPFS_PROFILE=server
+IPFS_FD_MAX=8192
+
+##############
+# Graph Node #
+##############
+GRAPH_LOG=info
+GRAPH_NODE_URL=http://graph-node:8020
+# Change if running remote IPFS node
+ipfs=ipfs:5001
+# Change if running an external ETH1 node
+# NB! If syncing graph node from scratch archive node must be used.
+# It can be switched to fast-sync node once fully synced.
+ethereum=xdai:http://eth1-node:8545
+# Postgres DB settings for graph node
+postgres_host=postgres
+postgres_user=graph
+postgres_pass=strong-password
+postgres_db=graph-node
+
+############
+# Postgres #
+############
+# postgres is used by local graph node
+POSTGRES_DB=graph-node
+POSTGRES_USER=graph
+POSTGRES_PASSWORD=strong-password
+
+#############
+# ETH2 NODE #
+#############
+ETH1_ENDPOINT=http://eth1-node:8545
diff --git a/deploy/gnosis/docker-compose.yml b/deploy/gnosis/docker-compose.yml
new file mode 100644
index 0000000..afbff74
--- /dev/null
+++ b/deploy/gnosis/docker-compose.yml
@@ -0,0 +1,177 @@
+version: "3.9"
+
+volumes:
+  prometheus:
+    driver: local
+  alertmanager:
+    driver: local
+  postgres:
+    driver: local
+  ipfs:
+    driver: local
+  openethereum:
+    driver: local
+  nethermind:
+    driver: local
+  lighthouse:
+    driver: local
+
+networks:
+  gnosis:
+    name: gnosis
+    driver: bridge
+
+services:
+  oracle:
+    container_name: oracle_gnosis
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.2.0
+    restart: always
+    entrypoint: ["python"]
+    command: ["oracle/oracle/main.py"]
+    env_file: [".env"]
+    profiles: ["oracle"]
+    networks:
+      - gnosis
+
+  keeper:
+    container_name: keeper_gnosis
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.2.0
+    restart: always
+    entrypoint: ["python"]
+    command: ["oracle/keeper/main.py"]
+    env_file: [".env"]
+    profiles: ["keeper"]
+    networks:
+      - gnosis
+
+  prometheus:
+    container_name: prometheus_gnosis
+    image: bitnami/prometheus:2
+    restart: always
+    env_file: [".env"]
+    volumes:
+      - prometheus:/opt/bitnami/prometheus/data
+      - ../configs/prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml
+      - ../configs/rules.yml:/opt/bitnami/prometheus/conf/rules.yml
+    networks:
+      - gnosis
+
+  alertmanager:
+    container_name: alertmanager_gnosis
+    image: bitnami/alertmanager:0
+    restart: always
+    env_file: [".env"]
+    volumes:
+      - alertmanager:/opt/bitnami/alertmanager/data
+      - ../configs/alertmanager.yml:/opt/bitnami/alertmanager/conf/config.yml
+    depends_on: ["prometheus"]
+    networks:
+      - gnosis
+
+  graph-node:
+    container_name: graph_node_gnosis
+    image: graphprotocol/graph-node:v0.25.0
+    restart: always
+    env_file: [".env"]
+    depends_on: ["postgres","ipfs"]
+    profiles: ["graph"]
+    networks:
+      - gnosis
+
+  postgres:
+    container_name: postgres_gnosis
+    image: postgres:14-alpine
+    restart: always
+    command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"]
+    env_file: [".env"]
+    volumes: ["postgres:/var/lib/postgresql/data"]
+    profiles: ["graph"]
+    networks:
+      - gnosis
+
+  subgraphs:
+    container_name: subgraphs_gnosis
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/subgraphs:v1.0.8
+    command: >
+      /bin/sh -c "until nc -vz graph-node 8020; do echo 'Waiting graph-node'; sleep 2; done
+      && yarn build:${NETWORK}
+      && yarn create:local
+      && yarn deploy:local"
+    env_file: [".env"]
+    restart: "no"
+    depends_on: ["graph-node","ipfs"]
+    profiles: ["graph"]
+    networks:
+      - gnosis
+
+  ipfs:
+    container_name: ipfs_gnosis
+    image: ipfs/go-ipfs:v0.10.0
+    restart: always
+    env_file: [".env"]
+    ulimits:
+      nofile:
+        soft: 8192
+        hard: 8192
+    volumes: ["ipfs:/data/ipfs","../configs/ipfs-entrypoint.sh:/usr/local/bin/start_ipfs"]
+    profiles: ["ipfs"]
+    networks:
+      - gnosis
+
+  openethereum:
+    container_name: openethereum_gnosis
+    image: openethereum/openethereum:v3.3.3
+    restart: always
+    command:
+      - --chain=xdai
+      - --jsonrpc-interface=all
+      - --jsonrpc-hosts=all
+      - --jsonrpc-port=8545
+      - --min-peers=50
+      - --max-peers=100
+    volumes: ["openethereum:/home/openethereum"]
+    profiles: ["openethereum"]
+    networks:
+      gnosis:
+        aliases:
+          - eth1-node
+
+
+  nethermind:
+    container_name: nethermind_gnosis
+    image: nethermind/nethermind:1.12.4
+    restart: always
+    command:
+      - --config=xdai
+      - --datadir=/data/nethermind
+      - --JsonRpc.Enabled=true
+      - --JsonRpc.EnabledModules=Eth,Subscribe,Trace,TxPool,Web3,Personal,Proof,Net,Parity,Health
+      - --JsonRpc.Host=0.0.0.0
+      - --JsonRpc.Port=8545
+    volumes: ["nethermind:/data"]
+    profiles: ["nethermind"]
+    networks:
+      gnosis:
+        aliases:
+          - eth1-node
+
+  lighthouse:
+    container_name: lighthouse_gnosis
+    image: sigp/lighthouse:v2.1.2
+    restart: always
+    command:
+      - lighthouse
+      - --network
+      - gnosis
+      - beacon
+      - --http
+      - --http-address=0.0.0.0
+      - --http-port=5052
+      - --eth1-endpoints
+      - $ETH1_ENDPOINT
+    volumes: ["lighthouse:/root/.lighthouse"]
+    profiles: ["lighthouse"]
+    networks:
+      gnosis:
+        aliases:
+          - eth2-node
diff --git a/deploy/goerli/.env.example b/deploy/goerli/.env.example
new file mode 100644
index 0000000..cae0b5e
--- /dev/null
+++ b/deploy/goerli/.env.example
@@ -0,0 +1,80 @@
+##########
+# Oracle #
+##########
+LOG_LEVEL=INFO
+ENABLED_NETWORKS=eth_goerli
+ENABLE_HEALTH_SERVER=true
+HEALTH_SERVER_PORT=8080
+HEALTH_SERVER_HOST=0.0.0.0
+
+# Remove ",/dns/ipfs/tcp/5001/http" if you don't use "ipfs" profile
+IPFS_PIN_ENDPOINTS=/dns/ipfs.infura.io/tcp/5001/https,/dns/ipfs/tcp/5001/http
+
+# Optionally pin merkle proofs to the pinata service for redundancy
+IPFS_PINATA_API_KEY=<pinata_api_key>
+IPFS_PINATA_SECRET_KEY=<pinata_secret_key>
+
+# Change https://api.thegraph.com to http://graph-node:8000 if running local graph node
+ETH_GOERLI_STAKEWISE_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/stakewise-goerli
+ETH_GOERLI_ETHEREUM_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/ethereum-goerli
+ETH_GOERLI_UNISWAP_V3_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-goerli
+
+# Ethereum private key
+# NB! You must use a different private key for every network
+ETH_GOERLI_ORACLE_PRIVATE_KEY=0x<private_key>
+
+# ETH2 (consensus) client endpoint
+# Change if running an external ETH2 node
+ETH_GOERLI_ETH2_ENDPOINT=http://eth2-node:5052
+
+# AWS bucket to publish oracle votes to
+ETH_GOERLI_AWS_ACCESS_KEY_ID=<access_id>
+ETH_GOERLI_AWS_SECRET_ACCESS_KEY=<secret_key>
+ETH_GOERLI_AWS_BUCKET_NAME=oracle-votes-goerli
+ETH_GOERLI_AWS_REGION=eu-central-1
+
+##########
+# Keeper #
+##########
+# Change if running an external ETH1 node
+ETH_GOERLI_KEEPER_ETH1_ENDPOINT=http://eth1-node:8545
+# Use https://eth-converter.com/ to calculate
+ETH_GOERLI_KEEPER_MIN_BALANCE_WEI=100000000000000000
+ETH_GOERLI_KEEPER_MAX_FEE_PER_GAS_GWEI=150
+
+########
+# IPFS #
+########
+IPFS_URL=http://ipfs:5001
+IPFS_PROFILE=server
+IPFS_FD_MAX=8192
+
+##############
+# Graph Node #
+##############
+GRAPH_LOG=info
+GRAPH_NODE_URL=http://graph-node:8020
+# Change if running remote IPFS node
+ipfs=ipfs:5001
+# Change if running an external ETH1 node
+# NB! If syncing graph node from scratch archive node must be used.
+# It can be switched to fast-sync node once fully synced.
+ethereum=goerli:http://eth1-node:8545
+# Postgres DB settings for graph node
+postgres_host=postgres
+postgres_user=graph
+postgres_pass=strong-password
+postgres_db=graph-node
+
+############
+# Postgres #
+############
+# postgres is used by local graph node
+POSTGRES_DB=graph-node
+POSTGRES_USER=graph
+POSTGRES_PASSWORD=strong-password
+
+#############
+# ETH2 NODE #
+#############
+ETH1_ENDPOINT=http://eth1-node:8545
diff --git a/deploy/goerli/docker-compose.yml b/deploy/goerli/docker-compose.yml
new file mode 100644
index 0000000..1954b8a
--- /dev/null
+++ b/deploy/goerli/docker-compose.yml
@@ -0,0 +1,221 @@
+version: "3.9"
+
+volumes:
+  prometheus:
+    driver: local
+  alertmanager:
+    driver: local
+  postgres:
+    driver: local
+  ipfs:
+    driver: local
+  geth:
+    driver: local
+  erigon:
+    driver: local
+  prysm:
+    driver: local
+  lighthouse:
+    driver: local
+
+networks:
+  goerli:
+    name: goerli
+    driver: bridge
+
+services:
+  oracle:
+    container_name: oracle_goerli
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.2.0
+    restart: always
+    entrypoint: ["python"]
+    command: ["oracle/oracle/main.py"]
+    env_file: [".env"]
+    profiles: ["oracle"]
+    networks:
+      - goerli
+
+  keeper:
+    container_name: keeper_goerli
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.2.0
+    restart: always
+    entrypoint: ["python"]
+    command: ["oracle/keeper/main.py"]
+    env_file: [".env"]
+    profiles: ["keeper"]
+    networks:
+      - goerli
+
+  prometheus:
+    container_name: prometheus_goerli
+    image: bitnami/prometheus:2
+    restart: always
+    env_file: [".env"]
+    volumes:
+      - prometheus:/opt/bitnami/prometheus/data
+      - ../configs/prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml
+      - ../configs/rules.yml:/opt/bitnami/prometheus/conf/rules.yml
+    networks:
+      - goerli
+
+  alertmanager:
+    container_name: alertmanager_goerli
+    image: bitnami/alertmanager:0
+    restart: always
+    env_file: [".env"]
+    volumes:
+      - alertmanager:/opt/bitnami/alertmanager/data
+      - ../configs/alertmanager.yml:/opt/bitnami/alertmanager/conf/config.yml
+    depends_on: ["prometheus"]
+    networks:
+      - goerli
+
+  graph-node:
+    container_name: graph_node_goerli
+    image: graphprotocol/graph-node:v0.25.0
+    restart: always
+    env_file: [".env"]
+    depends_on: ["postgres","ipfs"]
+    profiles: ["graph"]
+    networks:
+      - goerli
+
+  postgres:
+    container_name: postgres_goerli
+    image: postgres:14-alpine
+    restart: always
+    command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"]
+    env_file: [".env"]
+    volumes: ["postgres:/var/lib/postgresql/data"]
+    profiles: ["graph"]
+    networks:
+      - goerli
+
+  subgraphs:
+    container_name: subgraphs_goerli
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/subgraphs:v1.0.8
+    command: >
+      /bin/sh -c "until nc -vz graph-node 8020; do echo 'Waiting graph-node'; sleep 2; done
+      && yarn build:${NETWORK}
+      && yarn create:local
+      && yarn deploy:local"
+    env_file: [".env"]
+    restart: "no"
+    depends_on: ["graph-node","ipfs"]
+    profiles: ["graph"]
+    networks:
+      - goerli
+
+  ipfs:
+    container_name: ipfs_goerli
+    image: ipfs/go-ipfs:v0.10.0
+    restart: always
+    env_file: [".env"]
+    ulimits:
+      nofile:
+        soft: 8192
+        hard: 8192
+    volumes: ["ipfs:/data/ipfs","./configs/ipfs-entrypoint.sh:/usr/local/bin/start_ipfs"]
+    profiles: ["ipfs"]
+    networks:
+      - goerli
+
+  geth:
+    container_name: geth_goerli
+    image: ethereum/client-go:v1.10.15
+    restart: always
+    command:
+      - --goerli
+      - --syncmode=full
+      - --http
+      - --http.addr=0.0.0.0
+      - --http.vhosts=*
+      - --http.api=web3,eth,net
+      - --datadir=/data/ethereum
+      - --ethash.dagdir=/data/ethereum/.ethash
+      - --ipcdisable
+    volumes: ["geth:/data"]
+    profiles: ["geth"]
+    networks:
+      goerli:
+        aliases:
+          - eth1-node
+
+  erigon:
+    container_name: erigon_goerli
+    image: thorax/erigon:v2022.01.03
+    restart: always
+    command:
+      - erigon
+      - --chain=goerli
+      - --private.api.addr=0.0.0.0:9090
+      - --maxpeers=100
+      - --datadir=/home/erigon/.local/share/erigon
+      - --batchSize=512M
+      - --prune.r.before=11184524
+      - --prune=htc
+    volumes: ["erigon:/home/erigon/.local/share/erigon"]
+    profiles: ["erigon"]
+    networks:
+      - goerli
+
+  erigon-rpcdaemon:
+    container_name: erigon-rpcdaemon_goerli
+    image: thorax/erigon:v2022.01.03
+    restart: always
+    command:
+      - rpcdaemon
+      - --private.api.addr=erigon:9090
+      - --http.addr=0.0.0.0
+      - --http.vhosts=*
+      - --http.corsdomain=*
+      - --http.api=eth,erigon,web3,net,txpool
+      - --ws
+    depends_on: ["erigon"]
+    profiles: ["erigon"]
+    networks:
+      goerli:
+        aliases:
+          - eth1-node
+
+  prysm:
+    container_name: prysm_prater
+    image: gcr.io/prysmaticlabs/prysm/beacon-chain:v2.0.6
+    restart: always
+    command:
+      - --prater
+      - --genesis-state=/data/genesis.ssz
+      - --datadir=/data
+      - --rpc-host=0.0.0.0
+      - --rpc-port=5052
+      - --monitoring-host=0.0.0.0
+      - --http-web3provider=$ETH1_ENDPOINT
+      - --slots-per-archive-point=64
+      - --accept-terms-of-use
+    volumes: ["prysm:/data","../configs/genesis.ssz:/data/gensis.ssz"]
+    profiles: ["prysm"]
+    networks:
+      goerli:
+        aliases:
+          - eth2-node
+
+  lighthouse:
+    container_name: lighthouse_prater
+    image: sigp/lighthouse:v2.1.2
+    restart: always
+    command:
+      - lighthouse
+      - --network
+      - prater
+      - beacon
+      - --http
+      - --http-address=0.0.0.0
+      - --http-port=5052
+      - --eth1-endpoints
+      - $ETH1_ENDPOINT
+    volumes: ["lighthouse:/root/.lighthouse"]
+    profiles: ["lighthouse"]
+    networks:
+      goerli:
+        aliases:
+          - eth2-node
diff --git a/deploy/mainnet/.env.example b/deploy/mainnet/.env.example
new file mode 100644
index 0000000..ac19d79
--- /dev/null
+++ b/deploy/mainnet/.env.example
@@ -0,0 +1,81 @@
+##########
+# Oracle #
+##########
+LOG_LEVEL=INFO
+ENABLED_NETWORKS=eth_mainnet
+ENABLE_HEALTH_SERVER=true
+HEALTH_SERVER_PORT=8080
+HEALTH_SERVER_HOST=0.0.0.0
+
+# Remove ",/dns/ipfs/tcp/5001/http" if you don't use "ipfs" profile
+IPFS_PIN_ENDPOINTS=/dns/ipfs.infura.io/tcp/5001/https,/dns/ipfs/tcp/5001/http
+
+# Optionally pin merkle proofs to the pinata service for redundancy
+IPFS_PINATA_API_KEY=<pinata_api_key>
+IPFS_PINATA_SECRET_KEY=<pinata_secret_key>
+
+# Change https://api.thegraph.com to http://graph-node:8000 if running local graph node
+ETH_MAINNET_STAKEWISE_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/stakewise-mainnet
+ETH_MAINNET_ETHEREUM_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/ethereum-mainnet
+ETH_MAINNET_UNISWAP_V3_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-mainnet
+ETH_MAINNET_RARI_FUSE_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/stakewise/rari-fuse-mainnet
+
+# Ethereum private key
+# NB! You must use a different private key for every network
+ETH_MAINNET_ORACLE_PRIVATE_KEY=0x<private_key>
+
+# ETH2 (consensus) client endpoint
+# Change if running an external ETH2 node
+ETH_MAINNET_ETH2_ENDPOINT=http://eth2-node:5052
+
+# AWS bucket to publish oracle votes to
+ETH_MAINNET_AWS_ACCESS_KEY_ID=<access_id>
+ETH_MAINNET_AWS_SECRET_ACCESS_KEY=<secret_key>
+ETH_MAINNET_AWS_BUCKET_NAME=oracle-votes-mainnet
+ETH_MAINNET_AWS_REGION=eu-central-1
+
+##########
+# Keeper #
+##########
+# Change if running an external ETH1 node
+ETH_MAINNET_KEEPER_ETH1_ENDPOINT=http://eth1-node:8545
+# Use https://eth-converter.com/ to calculate
+ETH_MAINNET_KEEPER_MIN_BALANCE_WEI=100000000000000000
+ETH_MAINNET_KEEPER_MAX_FEE_PER_GAS_GWEI=150
+
+########
+# IPFS #
+########
+IPFS_URL=http://ipfs:5001
+IPFS_PROFILE=server
+IPFS_FD_MAX=8192
+
+##############
+# Graph Node #
+##############
+GRAPH_LOG=info
+GRAPH_NODE_URL=http://graph-node:8020
+# Change if running remote IPFS node
+ipfs=ipfs:5001
+# Change if running an external ETH1 node
+# NB! If syncing graph node from scratch archive node must be used.
+# It can be switched to fast-sync node once fully synced.
+ethereum=mainnet:http://eth1-node:8545
+# Postgres DB settings for graph node
+postgres_host=postgres
+postgres_user=graph
+postgres_pass=strong-password
+postgres_db=graph-node
+
+############
+# Postgres #
+############
+# postgres is used by local graph node
+POSTGRES_DB=graph-node
+POSTGRES_USER=graph
+POSTGRES_PASSWORD=strong-password
+
+#############
+# ETH2 NODE #
+#############
+ETH1_ENDPOINT=http://eth1-node:8545
diff --git a/deploy/docker-compose.yml b/deploy/mainnet/docker-compose.yml
similarity index 69%
rename from deploy/docker-compose.yml
rename to deploy/mainnet/docker-compose.yml
index 0efdba1..6fe464f 100644
--- a/deploy/docker-compose.yml
+++ b/deploy/mainnet/docker-compose.yml
@@ -18,65 +18,81 @@ volumes:
   lighthouse:
     driver: local
 
+networks:
+  mainnet:
+    name: mainnet
+    driver: bridge
+
 services:
   oracle:
-    container_name: oracle
-    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.1.2
+    container_name: oracle_mainnet
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.2.0
     restart: always
     entrypoint: ["python"]
     command: ["oracle/oracle/main.py"]
     env_file: [".env"]
     profiles: ["oracle"]
+    networks:
+      - mainnet
 
   keeper:
-    container_name: keeper
-    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.1.2
+    container_name: keeper_mainnet
+    image: europe-west4-docker.pkg.dev/stakewiselabs/public/oracle:v2.2.0
     restart: always
     entrypoint: ["python"]
     command: ["oracle/keeper/main.py"]
     env_file: [".env"]
     profiles: ["keeper"]
+    networks:
+      - mainnet
 
   prometheus:
-    container_name: prometheus
+    container_name: prometheus_mainnet
     image: bitnami/prometheus:2
     restart: always
     env_file: [".env"]
     volumes:
       - prometheus:/opt/bitnami/prometheus/data
-      - ./configs/prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml
-      - ./configs/rules.yml:/opt/bitnami/prometheus/conf/rules.yml
+      - ../configs/prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml
+      - ../configs/rules.yml:/opt/bitnami/prometheus/conf/rules.yml
+    networks:
+      - mainnet
 
   alertmanager:
-    container_name: alertmanager
+    container_name: alertmanager_mainnet
     image: bitnami/alertmanager:0
     restart: always
-    ports: ["127.0.0.1:9093:9093"]
     env_file: [".env"]
     volumes:
       - alertmanager:/opt/bitnami/alertmanager/data
-      - ./configs/alertmanager.yml:/opt/bitnami/alertmanager/conf/config.yml
+      - ../configs/alertmanager.yml:/opt/bitnami/alertmanager/conf/config.yml
     depends_on: ["prometheus"]
+    networks:
+      - mainnet
 
   graph-node:
-    container_name: graph-node
+    container_name: graph-node_mainnet
     image: graphprotocol/graph-node:v0.25.0
     restart: always
     env_file: [".env"]
     depends_on: ["postgres","ipfs"]
     profiles: ["graph"]
+    networks:
+      - mainnet
 
   postgres:
-    container_name: postgres
+    container_name: postgres_mainnet
     image: postgres:14-alpine
     restart: always
     command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"]
     env_file: [".env"]
     volumes: ["postgres:/var/lib/postgresql/data"]
     profiles: ["graph"]
+    networks:
+      - mainnet
 
   subgraphs:
-    container_name: subgraphs
+    container_name: subgraphs_mainnet
     image: europe-west4-docker.pkg.dev/stakewiselabs/public/subgraphs:v1.0.8
     command: >
       /bin/sh -c "until nc -vz graph-node 8020; do echo 'Waiting graph-node'; sleep 2; done
@@ -87,9 +103,11 @@ services:
     restart: "no"
     depends_on: ["graph-node","ipfs"]
     profiles: ["graph"]
+    networks:
+      - mainnet
 
   ipfs:
-    container_name: ipfs
+    container_name: ipfs_mainnet
     image: ipfs/go-ipfs:v0.10.0
     restart: always
     env_file: [".env"]
@@ -98,10 +116,12 @@ services:
         soft: 8192
         hard: 8192
     volumes: ["ipfs:/data/ipfs","./configs/ipfs-entrypoint.sh:/usr/local/bin/start_ipfs"]
-    profiles: ["oracle"]
+    profiles: ["ipfs"]
+    networks:
+      - mainnet
 
   geth:
-    container_name: geth
+    container_name: geth_mainnet
     image: ethereum/client-go:v1.10.15
     restart: always
     command:
@@ -115,9 +135,11 @@ services:
       - --ipcdisable
     volumes: ["geth:/data"]
     profiles: ["geth"]
+    networks:
+      - mainnet
 
   erigon:
-    container_name: erigon
+    container_name: erigon_mainnet
     image: thorax/erigon:v2022.01.03
     restart: always
     command:
@@ -130,9 +152,13 @@ services:
       - --prune=htc
     volumes: ["erigon:/home/erigon/.local/share/erigon"]
     profiles: ["erigon"]
+    networks:
+      mainnet:
+        aliases:
+          - eth1-node
 
   erigon-rpcdaemon:
-    container_name: erigon-rpcdaemon
+    container_name: erigon-rpcdaemon_mainnet
     image: thorax/erigon:v2022.01.03
     restart: always
     command:
@@ -145,24 +171,33 @@ services:
       - --ws
     depends_on: ["erigon"]
     profiles: ["erigon"]
+    networks:
+      mainnet:
+        aliases:
+          - eth1-node
 
   prysm:
-    container_name: prysm
-    image: gcr.io/prysmaticlabs/prysm/beacon-chain:v2.0.5
+    container_name: prysm_mainnet
+    image: gcr.io/prysmaticlabs/prysm/beacon-chain:v2.0.6
     restart: always
     command:
       - --datadir=/data
       - --rpc-host=0.0.0.0
+      - --rpc-port=5052
       - --monitoring-host=0.0.0.0
-      - --http-web3provider=http://geth:8545
+      - --http-web3provider=$ETH1_ENDPOINT
       - --slots-per-archive-point=64
       - --accept-terms-of-use
     volumes: ["prysm:/data"]
     profiles: ["prysm"]
+    networks:
+      mainnet:
+        aliases:
+          - eth2-node
 
   lighthouse:
-    container_name: lighthouse
-    image: sigp/lighthouse:v2.1.1
+    container_name: lighthouse_mainnet
+    image: sigp/lighthouse:v2.1.2
     restart: always
     command:
       - lighthouse
@@ -170,9 +205,13 @@ services:
       - mainnet
       - beacon
       - --http
-      - --http-address
-      - 0.0.0.0
+      - --http-address=0.0.0.0
+      - --http-port=5052
       - --eth1-endpoints
-      - http://geth:8545
+      - $ETH1_ENDPOINT
     volumes: ["lighthouse:/root/.lighthouse"]
     profiles: ["lighthouse"]
+    networks:
+      mainnet:
+        aliases:
+          - eth2-node
diff --git a/oracle/common/__init__.py b/oracle/common/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/oracle/common/settings.py b/oracle/common/settings.py
deleted file mode 100644
index dbf8b2e..0000000
--- a/oracle/common/settings.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from decouple import Choices, config
-
-LOG_LEVEL = config("LOG_LEVEL", default="INFO")
-
-REWARD_VOTE_FILENAME = "reward-vote.json"
-DISTRIBUTOR_VOTE_FILENAME = "distributor-vote.json"
-VALIDATOR_VOTE_FILENAME = "validator-vote.json"
-TEST_VOTE_FILENAME = "test-vote.json"
-
-# supported networks
-MAINNET = "mainnet"
-GOERLI = "goerli"
-GNOSIS = "gnosis"
-# TODO: enable gnosis once supported
-NETWORK = config(
-    "NETWORK",
-    default=MAINNET,
-    cast=Choices([MAINNET, GOERLI], cast=lambda net: net.lower()),
-)
-
-if NETWORK == MAINNET:
-    AWS_S3_BUCKET_NAME = config("AWS_S3_BUCKET_NAME", default="oracle-votes-mainnet")
-elif NETWORK == GNOSIS:
-    AWS_S3_BUCKET_NAME = config("AWS_S3_BUCKET_NAME", default="oracle-votes-gnosis")
-elif NETWORK == GOERLI:
-    AWS_S3_BUCKET_NAME = config("AWS_S3_BUCKET_NAME", default="oracle-votes-goerli")
-
-AWS_S3_REGION = config("AWS_S3_REGION", default="eu-central-1")
-
-# health server settings
-ENABLE_HEALTH_SERVER = config("ENABLE_HEALTH_SERVER", default=False, cast=bool)
-HEALTH_SERVER_PORT = config("HEALTH_SERVER_PORT", default=8080, cast=int)
-HEALTH_SERVER_HOST = config("HEALTH_SERVER_HOST", default="127.0.0.1", cast=str)
-
-# required confirmation blocks
-CONFIRMATION_BLOCKS: int = config("CONFIRMATION_BLOCKS", default=15, cast=int)
diff --git a/oracle/common/health_server.py b/oracle/health_server.py
similarity index 86%
rename from oracle/common/health_server.py
rename to oracle/health_server.py
index 8251cdb..e4f7a2e 100644
--- a/oracle/common/health_server.py
+++ b/oracle/health_server.py
@@ -2,7 +2,7 @@
 
 from aiohttp import web
 
-from oracle.common.settings import HEALTH_SERVER_HOST, HEALTH_SERVER_PORT
+from oracle.settings import HEALTH_SERVER_HOST, HEALTH_SERVER_PORT
 
 
 def start_health_server(runner):
diff --git a/oracle/keeper/clients.py b/oracle/keeper/clients.py
index 0f3bf93..20934dd 100644
--- a/oracle/keeper/clients.py
+++ b/oracle/keeper/clients.py
@@ -1,41 +1,49 @@
 import logging
+from typing import Dict
 
-import boto3
 from web3 import Web3
 from web3.middleware import construct_sign_and_send_raw_middleware, geth_poa_middleware
 
-from oracle.keeper.settings import GOERLI, NETWORK, ORACLE_PRIVATE_KEY, WEB3_ENDPOINT
+from oracle.networks import NETWORKS
+from oracle.settings import ENABLED_NETWORKS
 
 logger = logging.getLogger(__name__)
 
 
-def get_web3_client() -> Web3:
+def get_web3_client(network: str) -> Web3:
     """Returns instance of the Web3 client."""
+    network_config = NETWORKS[network]
+    endpoint = network_config["KEEPER_ETH1_ENDPOINT"]
 
     # Prefer WS over HTTP
-    if WEB3_ENDPOINT.startswith("ws"):
-        w3 = Web3(Web3.WebsocketProvider(WEB3_ENDPOINT, websocket_timeout=60))
-        logger.warning(f"Web3 websocket endpoint={WEB3_ENDPOINT}")
-    elif WEB3_ENDPOINT.startswith("http"):
-        w3 = Web3(Web3.HTTPProvider(WEB3_ENDPOINT))
-        logger.warning(f"Web3 HTTP endpoint={WEB3_ENDPOINT}")
+    if endpoint.startswith("ws"):
+        w3 = Web3(Web3.WebsocketProvider(endpoint, websocket_timeout=60))
+        logger.warning(f"[{network}] Web3 websocket endpoint={endpoint}")
+    elif endpoint.startswith("http"):
+        w3 = Web3(Web3.HTTPProvider(endpoint))
+        logger.warning(f"[{network}] Web3 HTTP endpoint={endpoint}")
     else:
-        w3 = Web3(Web3.IPCProvider(WEB3_ENDPOINT))
-        logger.warning(f"Web3 HTTP endpoint={WEB3_ENDPOINT}")
+        w3 = Web3(Web3.IPCProvider(endpoint))
+        logger.warning(f"[{network}] Web3 HTTP endpoint={endpoint}")
 
-    if NETWORK == GOERLI:
+    if network_config["IS_POA"]:
         w3.middleware_onion.inject(geth_poa_middleware, layer=0)
-        logger.warning("Injected POA middleware")
+        logger.warning(f"[{network}] Injected POA middleware")
 
-    account = w3.eth.account.from_key(ORACLE_PRIVATE_KEY)
+    account = w3.eth.account.from_key(network_config["ORACLE_PRIVATE_KEY"])
     w3.middleware_onion.add(construct_sign_and_send_raw_middleware(account))
-    logger.warning("Injected middleware for capturing transactions and sending as raw")
+    logger.warning(
+        f"[{network}] Injected middleware for capturing transactions and sending as raw"
+    )
 
     w3.eth.default_account = account.address
-    logger.info(f"Configured default account {w3.eth.default_account}")
+    logger.info(f"[{network}] Configured default account {w3.eth.default_account}")
 
     return w3
 
 
-s3_client = boto3.client("s3")
-web3_client = get_web3_client()
+def get_web3_clients() -> Dict[str, Web3]:
+    web3_clients = {}
+    for network in ENABLED_NETWORKS:
+        web3_clients[network] = get_web3_client(network)
+    return web3_clients
diff --git a/oracle/keeper/contracts.py b/oracle/keeper/contracts.py
index 8f6ed60..2749d47 100644
--- a/oracle/keeper/contracts.py
+++ b/oracle/keeper/contracts.py
@@ -1,13 +1,16 @@
+from typing import Dict
+
+from web3 import Web3
 from web3.contract import Contract
 
-from oracle.keeper.clients import web3_client
-from oracle.keeper.settings import MULTICALL_CONTRACT_ADDRESS, ORACLES_CONTRACT_ADDRESS
+from oracle.networks import NETWORKS
+from oracle.settings import ENABLED_NETWORKS
 
 
-def get_multicall_contract() -> Contract:
+def get_multicall_contract(w3_client: Web3, network: str) -> Contract:
     """:returns instance of `Multicall` contract."""
-    return web3_client.eth.contract(
-        address=MULTICALL_CONTRACT_ADDRESS,
+    return w3_client.eth.contract(
+        address=NETWORKS[network]["MULTICALL_CONTRACT_ADDRESS"],
         abi=[
             {
                 "constant": False,
@@ -34,10 +37,10 @@ def get_multicall_contract() -> Contract:
     )
 
 
-def get_oracles_contract() -> Contract:
+def get_oracles_contract(web3_client: Web3, network: str) -> Contract:
     """:returns instance of `Oracles` contract."""
     return web3_client.eth.contract(
-        address=ORACLES_CONTRACT_ADDRESS,
+        address=NETWORKS[network]["ORACLES_CONTRACT_ADDRESS"],
         abi=[
             {
                 "inputs": [],
@@ -193,5 +196,19 @@ def get_oracles_contract() -> Contract:
     )
 
 
-multicall_contract = get_multicall_contract()
-oracles_contract = get_oracles_contract()
+def get_multicall_contracts(web3_clients: Dict[str, Web3]) -> Dict[str, Contract]:
+    multicall_contracts = {}
+    for network in ENABLED_NETWORKS:
+        web3_client = web3_clients[network]
+        multicall_contracts[network] = get_multicall_contract(web3_client, network)
+
+    return multicall_contracts
+
+
+def get_oracles_contracts(web3_clients: Dict[str, Web3]) -> Dict[str, Contract]:
+    oracles_contracts = {}
+    for network in ENABLED_NETWORKS:
+        web3_client = web3_clients[network]
+        oracles_contracts[network] = get_oracles_contract(web3_client, network)
+
+    return oracles_contracts
diff --git a/oracle/keeper/health_server.py b/oracle/keeper/health_server.py
index 0644074..1431b15 100644
--- a/oracle/keeper/health_server.py
+++ b/oracle/keeper/health_server.py
@@ -1,8 +1,9 @@
 from aiohttp import web
 
-from oracle.keeper.contracts import get_oracles_contract
-from oracle.keeper.settings import KEEPER_MIN_BALANCE_WEI
+from oracle.keeper.clients import get_web3_clients
+from oracle.keeper.contracts import get_multicall_contracts, get_oracles_contracts
 from oracle.keeper.utils import get_keeper_params, get_oracles_votes
+from oracle.networks import NETWORKS
 
 keeper_routes = web.RouteTableDef()
 
@@ -10,27 +11,33 @@
 @keeper_routes.get("/")
 async def health(request):
     try:
-        oracles = get_oracles_contract()
-        oracle = oracles.web3.eth.default_account
-
-        # Check ETH1 node connection and oracle is part of the set
-        assert oracles.functions.isOracle(oracles.web3.eth.default_account).call()
-
-        # Check oracle has enough balance
-        balance = oracles.web3.eth.get_balance(oracle)
-        assert balance > KEEPER_MIN_BALANCE_WEI
-
-        # Can fetch oracle votes and is not paused
-        params = get_keeper_params()
-        if params.paused:
-            return web.Response(text="keeper 0")
-
-        # Can resolve and fetch latest votes of the oracles
-        get_oracles_votes(
-            rewards_nonce=params.rewards_nonce,
-            validators_nonce=params.validators_nonce,
-            oracles=params.oracles,
-        )
+        web3_clients = get_web3_clients()
+        oracles_contracts = get_oracles_contracts(web3_clients)
+        multicall_contracts = get_multicall_contracts(web3_clients)
+        for network, web3_client in web3_clients.items():
+            oracles_contract = oracles_contracts[network]
+            multicall_contract = multicall_contracts[network]
+
+            # Check ETH1 node connection and oracle is part of the set
+            oracle_account = web3_client.eth.default_account
+            assert oracles_contract.functions.isOracle(oracle_account).call()
+
+            # Check oracle has enough balance
+            balance = web3_client.eth.get_balance(oracle_account)
+            assert balance > NETWORKS[network]["KEEPER_MIN_BALANCE"]
+
+            # Can fetch oracle votes and is not paused
+            params = get_keeper_params(oracles_contract, multicall_contract)
+            if params.paused:
+                return web.Response(text="keeper 0")
+
+            # Can resolve and fetch recent votes of the oracles
+            get_oracles_votes(
+                network=network,
+                rewards_nonce=params.rewards_nonce,
+                validators_nonce=params.validators_nonce,
+                oracles=params.oracles,
+            )
 
         return web.Response(text="keeper 1")
     except:  # noqa: E722
diff --git a/oracle/keeper/main.py b/oracle/keeper/main.py
index bfe4c8f..a24063d 100644
--- a/oracle/keeper/main.py
+++ b/oracle/keeper/main.py
@@ -1,14 +1,20 @@
 import logging
-import signal
 import threading
 import time
-from typing import Any
 
-from oracle.common.health_server import create_health_server_runner, start_health_server
-from oracle.common.settings import ENABLE_HEALTH_SERVER, LOG_LEVEL
+from oracle.health_server import create_health_server_runner, start_health_server
+from oracle.keeper.clients import get_web3_clients
+from oracle.keeper.contracts import get_multicall_contracts, get_oracles_contracts
 from oracle.keeper.health_server import keeper_routes
-from oracle.keeper.settings import KEEPER_PROCESS_INTERVAL
 from oracle.keeper.utils import get_keeper_params, submit_votes
+from oracle.settings import (
+    ENABLE_HEALTH_SERVER,
+    ENABLED_NETWORKS,
+    HEALTH_SERVER_PORT,
+    KEEPER_PROCESS_INTERVAL,
+    LOG_LEVEL,
+)
+from oracle.utils import InterruptHandler
 
 logging.basicConfig(
     format="%(asctime)s %(levelname)-8s %(message)s",
@@ -20,39 +26,29 @@
 logger = logging.getLogger(__name__)
 
 
-class InterruptHandler:
-    """
-    Tracks SIGINT and SIGTERM signals.
-    https://stackoverflow.com/a/31464349
-    """
-
-    exit = False
-
-    def __init__(self) -> None:
-        signal.signal(signal.SIGINT, self.exit_gracefully)
-        signal.signal(signal.SIGTERM, self.exit_gracefully)
-
-    # noinspection PyUnusedLocal
-    def exit_gracefully(self, signum: int, frame: Any) -> None:
-        logger.info(f"Received interrupt signal {signum}, exiting...")
-        self.exit = True
-
-
 def main() -> None:
     # wait for interrupt
     interrupt_handler = InterruptHandler()
+    web3_clients = get_web3_clients()
+    multicall_contracts = get_multicall_contracts(web3_clients)
+    oracles_contracts = get_oracles_contracts(web3_clients)
 
     while not interrupt_handler.exit:
         # Fetch current nonces of the validators, rewards and the total number of oracles
-        params = get_keeper_params()
-        if params.paused:
-            time.sleep(KEEPER_PROCESS_INTERVAL)
-            continue
+        for network in ENABLED_NETWORKS:
+            web3_client = web3_clients[network]
+            multicall_contract = multicall_contracts[network]
+            oracles_contract = oracles_contracts[network]
 
-        # If nonces match the current for the majority, submit the transactions
-        submit_votes(params)
+            params = get_keeper_params(oracles_contract, multicall_contract)
+            if params.paused:
+                time.sleep(KEEPER_PROCESS_INTERVAL)
+                continue
 
-        time.sleep(KEEPER_PROCESS_INTERVAL)
+            # If nonces match the current for the majority, submit the transactions
+            submit_votes(network, web3_client, oracles_contract, params)
+
+            time.sleep(KEEPER_PROCESS_INTERVAL)
 
 
 if __name__ == "__main__":
@@ -62,5 +58,8 @@ def main() -> None:
             args=(create_health_server_runner(keeper_routes),),
             daemon=True,
         )
+        logger.info(
+            f"Starting monitoring server at http://{ENABLE_HEALTH_SERVER}:{HEALTH_SERVER_PORT}"
+        )
         t.start()
     main()
diff --git a/oracle/keeper/settings.py b/oracle/keeper/settings.py
deleted file mode 100644
index 73aafe3..0000000
--- a/oracle/keeper/settings.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from decouple import config
-from web3 import Web3
-
-from oracle.common.settings import GOERLI, MAINNET, NETWORK
-
-WEB3_ENDPOINT = config("WEB3_ENDPOINT")
-
-ORACLE_PRIVATE_KEY = config("ORACLE_PRIVATE_KEY")
-
-KEEPER_PROCESS_INTERVAL = config("KEEPER_PROCESS_INTERVAL", default=10, cast=int)
-
-KEEPER_MIN_BALANCE_WEI = config(
-    "KEEPER_MIN_BALANCE_WEI", default=Web3.toWei(0.1, "ether"), cast=int
-)
-
-TRANSACTION_TIMEOUT = config("TRANSACTION_TIMEOUT", default=900, cast=int)
-
-MAX_FEE_PER_GAS = config(
-    "MAX_FEE_PER_GAS_GWEI", default=150, cast=lambda x: Web3.toWei(x, "gwei")
-)
-
-if NETWORK == MAINNET:
-    ORACLES_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x8a887282E67ff41d36C0b7537eAB035291461AcD"
-    )
-    MULTICALL_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441"
-    )
-elif NETWORK == GOERLI:
-    ORACLES_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x531b9D9cb268E88D53A87890699bbe31326A6f08"
-    )
-    MULTICALL_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e"
-    )
diff --git a/oracle/keeper/utils.py b/oracle/keeper/utils.py
index c9c70ee..d92f285 100644
--- a/oracle/keeper/utils.py
+++ b/oracle/keeper/utils.py
@@ -4,41 +4,36 @@
 from typing import List
 
 import backoff
-import boto3
 import requests
 from eth_account.messages import encode_defunct
 from eth_typing import BlockNumber, ChecksumAddress, HexStr
 from hexbytes import HexBytes
 from web3 import Web3
-from web3.contract import ContractFunction
+from web3.contract import Contract, ContractFunction
 from web3.types import TxParams
 
-from oracle.common.settings import (
-    AWS_S3_BUCKET_NAME,
-    AWS_S3_REGION,
+from oracle.keeper.typings import OraclesVotes, Parameters
+from oracle.networks import NETWORKS
+from oracle.oracle.distributor.types import DistributorVote
+from oracle.oracle.rewards.types import RewardVote
+from oracle.oracle.validators.types import ValidatorVote
+from oracle.settings import (
     CONFIRMATION_BLOCKS,
     DISTRIBUTOR_VOTE_FILENAME,
     REWARD_VOTE_FILENAME,
+    TRANSACTION_TIMEOUT,
     VALIDATOR_VOTE_FILENAME,
 )
-from oracle.keeper.clients import web3_client
-from oracle.keeper.contracts import multicall_contract, oracles_contract
-from oracle.keeper.settings import MAX_FEE_PER_GAS, TRANSACTION_TIMEOUT
-from oracle.keeper.typings import OraclesVotes, Parameters
-from oracle.oracle.distributor.types import DistributorVote
-from oracle.oracle.rewards.types import RewardVote
-from oracle.oracle.validators.types import ValidatorVote
 
 logger = logging.getLogger(__name__)
-s3_client = boto3.client(
-    "s3",
-)
 
 ORACLE_ROLE = Web3.solidityKeccak(["string"], ["ORACLE_ROLE"])
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
-def get_keeper_params() -> Parameters:
+def get_keeper_params(
+    oracles_contract: Contract, multicall_contract: Contract
+) -> Parameters:
     """Returns keeper params for checking whether to submit the votes."""
     calls = [
         {
@@ -90,7 +85,7 @@ def get_keeper_params() -> Parameters:
 
 
 def validate_vote_signature(
-    encoded_data: bytes, account: ChecksumAddress, signature: HexStr
+    web3_client: Web3, encoded_data: bytes, account: ChecksumAddress, signature: HexStr
 ) -> bool:
     """Checks whether vote was signed by specific Ethereum account."""
     try:
@@ -108,7 +103,9 @@ def validate_vote_signature(
     return True
 
 
-def check_reward_vote(vote: RewardVote, oracle: ChecksumAddress) -> bool:
+def check_reward_vote(
+    web3_client: Web3, vote: RewardVote, oracle: ChecksumAddress
+) -> bool:
     """Checks whether oracle's reward vote is correct."""
     try:
         encoded_data: bytes = web3_client.codec.encode_abi(
@@ -119,24 +116,32 @@ def check_reward_vote(vote: RewardVote, oracle: ChecksumAddress) -> bool:
                 int(vote["total_rewards"]),
             ],
         )
-        return validate_vote_signature(encoded_data, oracle, vote["signature"])
+        return validate_vote_signature(
+            web3_client, encoded_data, oracle, vote["signature"]
+        )
     except:  # noqa: E722
         return False
 
 
-def check_distributor_vote(vote: DistributorVote, oracle: ChecksumAddress) -> bool:
+def check_distributor_vote(
+    web3_client: Web3, vote: DistributorVote, oracle: ChecksumAddress
+) -> bool:
     """Checks whether oracle's distributor vote is correct."""
     try:
         encoded_data: bytes = web3_client.codec.encode_abi(
             ["uint256", "string", "bytes32"],
             [int(vote["nonce"]), vote["merkle_proofs"], vote["merkle_root"]],
         )
-        return validate_vote_signature(encoded_data, oracle, vote["signature"])
+        return validate_vote_signature(
+            web3_client, encoded_data, oracle, vote["signature"]
+        )
     except:  # noqa: E722
         return False
 
 
-def check_validator_vote(vote: ValidatorVote, oracle: ChecksumAddress) -> bool:
+def check_validator_vote(
+    web3_client: Web3, vote: ValidatorVote, oracle: ChecksumAddress
+) -> bool:
     """Checks whether oracle's validator vote is correct."""
     try:
         encoded_data: bytes = web3_client.codec.encode_abi(
@@ -148,16 +153,24 @@ def check_validator_vote(vote: ValidatorVote, oracle: ChecksumAddress) -> bool:
                 vote["validators_deposit_root"],
             ],
         )
-        return validate_vote_signature(encoded_data, oracle, vote["signature"])
+        return validate_vote_signature(
+            web3_client, encoded_data, oracle, vote["signature"]
+        )
     except:  # noqa: E722
         return False
 
 
 def get_oracles_votes(
-    rewards_nonce: int, validators_nonce: int, oracles: List[ChecksumAddress]
+    network: str,
+    rewards_nonce: int,
+    validators_nonce: int,
+    oracles: List[ChecksumAddress],
 ) -> OraclesVotes:
     """Fetches oracle votes that match current nonces."""
     votes = OraclesVotes(rewards=[], distributor=[], validator=[])
+    network_config = NETWORKS[network]
+    aws_bucket_name = network_config["AWS_BUCKET_NAME"]
+    aws_region = network_config["AWS_REGION"]
 
     for oracle in oracles:
         for arr, filename, correct_nonce, vote_checker in [
@@ -179,7 +192,7 @@ def get_oracles_votes(
             bucket_key = f"{oracle}/{filename}"
             try:
                 response = requests.get(
-                    f"https://{AWS_S3_BUCKET_NAME}.s3.{AWS_S3_REGION}.amazonaws.com/{bucket_key}"
+                    f"https://{aws_bucket_name}.s3.{aws_region}.amazonaws.com/{bucket_key}"
                 )
                 response.raise_for_status()
                 vote = response.json()
@@ -187,7 +200,7 @@ def get_oracles_votes(
                     continue
                 if not vote_checker(vote, oracle):
                     logger.warning(
-                        f"Oracle {oracle} has submitted incorrect vote at {bucket_key}"
+                        f"[{network}] Oracle {oracle} has submitted incorrect vote at {bucket_key}"
                     )
                     continue
 
@@ -203,7 +216,7 @@ def can_submit(signatures_count: int, total_oracles: int) -> bool:
     return signatures_count * 3 > total_oracles * 2
 
 
-def wait_for_transaction(tx_hash: HexBytes) -> None:
+def wait_for_transaction(network: str, web3_client: Web3, tx_hash: HexBytes) -> None:
     """Waits for transaction to be confirmed."""
     receipt = web3_client.eth.wait_for_transaction_receipt(
         transaction_hash=tx_hash, timeout=TRANSACTION_TIMEOUT, poll_latency=5
@@ -212,7 +225,7 @@ def wait_for_transaction(tx_hash: HexBytes) -> None:
     current_block: BlockNumber = web3_client.eth.block_number
     while confirmation_block > current_block:
         logger.info(
-            f"Waiting for {confirmation_block - current_block} confirmation blocks..."
+            f"[{network}] Waiting for {confirmation_block - current_block} confirmation blocks..."
         )
         time.sleep(15)
 
@@ -221,10 +234,12 @@ def wait_for_transaction(tx_hash: HexBytes) -> None:
         current_block = web3_client.eth.block_number
 
 
-def get_transaction_params() -> TxParams:
+def get_transaction_params(network: str, web3_client: Web3) -> TxParams:
+    network_config = NETWORKS[network]
+    max_fee_per_gas = network_config["KEEPER_MAX_FEE_PER_GAS"]
     account_nonce = web3_client.eth.getTransactionCount(web3_client.eth.default_account)
     latest_block = web3_client.eth.get_block("latest")
-    max_priority_fee = min(web3_client.eth.max_priority_fee, MAX_FEE_PER_GAS)
+    max_priority_fee = min(web3_client.eth.max_priority_fee, max_fee_per_gas)
 
     base_fee = latest_block["baseFeePerGas"]
     priority_fee = int(str(max_priority_fee), 16)
@@ -233,12 +248,14 @@ def get_transaction_params() -> TxParams:
     return TxParams(
         nonce=account_nonce,
         maxPriorityFeePerGas=max_priority_fee,
-        maxFeePerGas=hex(min(max_fee_per_gas, MAX_FEE_PER_GAS)),
+        maxFeePerGas=hex(min(max_fee_per_gas, max_fee_per_gas)),
     )
 
 
-def submit_update(function_call: ContractFunction) -> None:
-    tx_params = get_transaction_params()
+def submit_update(
+    network: str, web3_client: Web3, function_call: ContractFunction
+) -> None:
+    tx_params = get_transaction_params(network, web3_client)
     estimated_gas = function_call.estimateGas(tx_params)
 
     # add 10% margin to the estimated gas
@@ -246,15 +263,18 @@ def submit_update(function_call: ContractFunction) -> None:
 
     # execute transaction
     tx_hash = function_call.transact(tx_params)
-    logger.info(f"Submitted transaction: {Web3.toHex(tx_hash)}")
-    wait_for_transaction(tx_hash)
+    logger.info(f"[{network}] Submitted transaction: {Web3.toHex(tx_hash)}")
+    wait_for_transaction(network, web3_client, tx_hash)
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
-def submit_votes(params: Parameters) -> None:
+def submit_votes(
+    network: str, web3_client: Web3, oracles_contract: Contract, params: Parameters
+) -> None:
     """Submits aggregated votes in case they have majority."""
     # resolve and fetch the latest votes of the oracles for validators and rewards
     votes = get_oracles_votes(
+        network=network,
         rewards_nonce=params.rewards_nonce,
         validators_nonce=params.validators_nonce,
         oracles=params.oracles,
@@ -283,16 +303,18 @@ def submit_votes(params: Parameters) -> None:
             i += 1
 
         logger.info(
-            f"Submitting total rewards update:"
+            f"[{network}] Submitting total rewards update:"
             f' rewards={Web3.fromWei(int(total_rewards), "ether")},'
             f" activated validators={activated_validators}"
         )
         submit_update(
+            network,
+            web3_client,
             oracles_contract.functions.submitRewards(
                 int(total_rewards), int(activated_validators), signatures
             ),
         )
-        logger.info("Total rewards has been successfully updated")
+        logger.info(f"[{network}] Total rewards has been successfully updated")
 
     counter = Counter(
         [(vote["merkle_root"], vote["merkle_proofs"]) for vote in votes.distributor]
@@ -312,14 +334,16 @@ def submit_votes(params: Parameters) -> None:
             i += 1
 
         logger.info(
-            f"Submitting distributor update: merkle root={merkle_root}, merkle proofs={merkle_proofs}"
+            f"[{network}] Submitting distributor update: merkle root={merkle_root}, merkle proofs={merkle_proofs}"
         )
         submit_update(
+            network,
+            web3_client,
             oracles_contract.functions.submitMerkleRoot(
                 merkle_root, merkle_proofs, signatures
             ),
         )
-        logger.info("Merkle Distributor has been successfully updated")
+        logger.info(f"[{network}] Merkle Distributor has been successfully updated")
 
     counter = Counter(
         [
@@ -353,12 +377,14 @@ def submit_votes(params: Parameters) -> None:
             )
         )
         logger.info(
-            f"Submitting validator registration: "
+            f"[{network}] Submitting validator registration: "
             f"operator={operator}, "
             f"public key={public_key}, "
             f"validator deposit root={validators_deposit_root}"
         )
         submit_update(
+            network,
+            web3_client,
             oracles_contract.functions.registerValidator(
                 dict(
                     operator=validator_vote["operator"],
@@ -372,4 +398,6 @@ def submit_votes(params: Parameters) -> None:
                 signatures,
             ),
         )
-        logger.info("Validator registration has been successfully submitted")
+        logger.info(
+            f"[{network}] Validator registration has been successfully submitted"
+        )
diff --git a/oracle/networks.py b/oracle/networks.py
new file mode 100644
index 0000000..5579796
--- /dev/null
+++ b/oracle/networks.py
@@ -0,0 +1,223 @@
+from datetime import timedelta
+
+from decouple import config
+from eth_typing import HexStr
+from web3 import Web3
+
+ETHEREUM_MAINNET = "eth_mainnet"
+ETHEREUM_GOERLI = "eth_goerli"
+GNOSIS_CHAIN = "gnosis"
+
+ETHEREUM_MAINNET_UPPER = ETHEREUM_MAINNET.upper()
+ETHEREUM_GOERLI_UPPER = ETHEREUM_GOERLI.upper()
+GNOSIS_CHAIN_UPPER = GNOSIS_CHAIN.upper()
+
+NETWORKS = {
+    ETHEREUM_MAINNET: dict(
+        STAKEWISE_SUBGRAPH_URL=config(
+            f"{ETHEREUM_MAINNET_UPPER}_STAKEWISE_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/stakewise-mainnet",
+        ),
+        ETHEREUM_SUBGRAPH_URL=config(
+            f"{ETHEREUM_MAINNET_UPPER}_ETHEREUM_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/ethereum-mainnet",
+        ),
+        UNISWAP_V3_SUBGRAPH_URL=config(
+            f"{ETHEREUM_MAINNET_UPPER}_UNISWAP_V3_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-mainnet",
+        ),
+        RARI_FUSE_SUBGRAPH_URL=config(
+            f"{ETHEREUM_MAINNET_UPPER}_RARI_FUSE_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/rari-fuse-mainnet",
+        ),
+        ETH2_ENDPOINT=config(f"{ETHEREUM_MAINNET_UPPER}_ETH2_ENDPOINT", default=""),
+        SLOTS_PER_EPOCH=32,
+        SECONDS_PER_SLOT=12,
+        ORACLES_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x8a887282E67ff41d36C0b7537eAB035291461AcD"
+        ),
+        MULTICALL_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441"
+        ),
+        SWISE_TOKEN_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x48C3399719B582dD63eB5AADf12A40B4C3f52FA2"
+        ),
+        REWARD_TOKEN_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x20BC832ca081b91433ff6c17f85701B6e92486c5"
+        ),
+        STAKED_TOKEN_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0xFe2e637202056d30016725477c5da089Ab0A043A"
+        ),
+        DISTRIBUTOR_FALLBACK_ADDRESS=Web3.toChecksumAddress(
+            "0x144a98cb1CdBb23610501fE6108858D9B7D24934"
+        ),
+        RARI_FUSE_POOL_ADDRESSES=[
+            Web3.toChecksumAddress("0x18F49849D20Bc04059FE9d775df9a38Cd1f5eC9F"),
+            Web3.toChecksumAddress("0x83d534Ab1d4002249B0E6d22410b62CF31978Ca2"),
+        ],
+        WITHDRAWAL_CREDENTIALS=HexStr(
+            "0x0100000000000000000000002296e122c1a20fca3cac3371357bdad3be0df079"
+        ),
+        ORACLE_PRIVATE_KEY=config(
+            f"{ETHEREUM_MAINNET_UPPER}_ORACLE_PRIVATE_KEY", default=""
+        ),
+        AWS_BUCKET_NAME=config(
+            f"{ETHEREUM_MAINNET_UPPER}_AWS_BUCKET_NAME", default="oracle-votes-mainnet"
+        ),
+        AWS_REGION=config(
+            f"{ETHEREUM_MAINNET_UPPER}_AWS_REGION", default="eu-central-1"
+        ),
+        AWS_ACCESS_KEY_ID=config(
+            f"{ETHEREUM_MAINNET_UPPER}_AWS_ACCESS_KEY_ID", default=""
+        ),
+        AWS_SECRET_ACCESS_KEY=config(
+            f"{ETHEREUM_MAINNET_UPPER}_AWS_SECRET_ACCESS_KEY", default=""
+        ),
+        KEEPER_ETH1_ENDPOINT=config(
+            f"{ETHEREUM_MAINNET_UPPER}_KEEPER_ETH1_ENDPOINT", default=""
+        ),
+        KEEPER_MIN_BALANCE=config(
+            f"{ETHEREUM_MAINNET_UPPER}_KEEPER_MIN_BALANCE_WEI",
+            default=Web3.toWei(0.1, "ether"),
+            cast=int,
+        ),
+        KEEPER_MAX_FEE_PER_GAS=config(
+            f"{ETHEREUM_MAINNET_UPPER}_KEEPER_MAX_FEE_PER_GAS_GWEI",
+            default=150,
+            cast=lambda x: Web3.toWei(x, "gwei"),
+        ),
+        SYNC_PERIOD=timedelta(days=1),
+        IS_POA=False,
+        DEPOSIT_TOKEN_SYMBOL="ETH",
+    ),
+    ETHEREUM_GOERLI: dict(
+        STAKEWISE_SUBGRAPH_URL=config(
+            f"{ETHEREUM_GOERLI_UPPER}_STAKEWISE_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/stakewise-goerli",
+        ),
+        ETHEREUM_SUBGRAPH_URL=config(
+            f"{ETHEREUM_GOERLI_UPPER}_ETHEREUM_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/ethereum-goerli",
+        ),
+        UNISWAP_V3_SUBGRAPH_URL=config(
+            f"{ETHEREUM_GOERLI_UPPER}_UNISWAP_V3_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-goerli",
+        ),
+        # TODO: update once rari fuse pools is deployed to goerli chain
+        RARI_FUSE_SUBGRAPH_URL=config(
+            f"{ETHEREUM_GOERLI_UPPER}_RARI_FUSE_SUBGRAPH_URL", default=""
+        ),
+        ETH2_ENDPOINT=config(f"{ETHEREUM_GOERLI_UPPER}_ETH2_ENDPOINT", default=""),
+        SLOTS_PER_EPOCH=32,
+        SECONDS_PER_SLOT=12,
+        ORACLES_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x531b9D9cb268E88D53A87890699bbe31326A6f08"
+        ),
+        MULTICALL_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e"
+        ),
+        SWISE_TOKEN_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x0e2497aACec2755d831E4AFDEA25B4ef1B823855"
+        ),
+        REWARD_TOKEN_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x826f88d423440c305D9096cC1581Ae751eFCAfB0"
+        ),
+        STAKED_TOKEN_CONTRACT_ADDRESS=Web3.toChecksumAddress(
+            "0x221D9812823DBAb0F1fB40b0D294D9875980Ac19"
+        ),
+        DISTRIBUTOR_FALLBACK_ADDRESS=Web3.toChecksumAddress(
+            "0x1867c96601bc5fE24F685d112314B8F3Fe228D5A"
+        ),
+        RARI_FUSE_POOL_ADDRESSES=[],
+        WITHDRAWAL_CREDENTIALS=HexStr(
+            "0x010000000000000000000000040f15c6b5bfc5f324ecab5864c38d4e1eef4218"
+        ),
+        ORACLE_PRIVATE_KEY=config(
+            f"{ETHEREUM_GOERLI_UPPER}_ORACLE_PRIVATE_KEY", default=""
+        ),
+        AWS_BUCKET_NAME=config(
+            f"{ETHEREUM_GOERLI_UPPER}_AWS_BUCKET_NAME", default="oracle-votes-goerli"
+        ),
+        AWS_REGION=config(
+            f"{ETHEREUM_GOERLI_UPPER}_AWS_REGION", default="eu-central-1"
+        ),
+        AWS_ACCESS_KEY_ID=config(
+            f"{ETHEREUM_GOERLI_UPPER}_AWS_ACCESS_KEY_ID", default=""
+        ),
+        AWS_SECRET_ACCESS_KEY=config(
+            f"{ETHEREUM_GOERLI_UPPER}_AWS_SECRET_ACCESS_KEY", default=""
+        ),
+        KEEPER_ETH1_ENDPOINT=config(
+            f"{ETHEREUM_GOERLI_UPPER}_KEEPER_ETH1_ENDPOINT", default=""
+        ),
+        KEEPER_MIN_BALANCE=config(
+            f"{ETHEREUM_GOERLI_UPPER}_KEEPER_MIN_BALANCE_WEI",
+            default=Web3.toWei(0.1, "ether"),
+            cast=int,
+        ),
+        KEEPER_MAX_FEE_PER_GAS=config(
+            f"{ETHEREUM_GOERLI_UPPER}_KEEPER_MAX_FEE_PER_GAS_GWEI",
+            default=150,
+            cast=lambda x: Web3.toWei(x, "gwei"),
+        ),
+        SYNC_PERIOD=timedelta(hours=1),
+        IS_POA=True,
+        DEPOSIT_TOKEN_SYMBOL="ETH",
+    ),
+    GNOSIS_CHAIN: dict(
+        STAKEWISE_SUBGRAPH_URL=config(
+            f"{GNOSIS_CHAIN_UPPER}_STAKEWISE_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/stakewise-gnosis",
+        ),
+        ETHEREUM_SUBGRAPH_URL=config(
+            f"{GNOSIS_CHAIN_UPPER}_ETHEREUM_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/ethereum-gnosis",
+        ),
+        UNISWAP_V3_SUBGRAPH_URL=config(
+            f"{GNOSIS_CHAIN_UPPER}_UNISWAP_V3_SUBGRAPH_URL",
+            default="https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-gnosis",
+        ),
+        # TODO: update once rari fuse pools is deployed to gnosis chain
+        RARI_FUSE_SUBGRAPH_URL=config(
+            f"{GNOSIS_CHAIN_UPPER}_RARI_FUSE_SUBGRAPH_URL", default=""
+        ),
+        ETH2_ENDPOINT=config(f"{GNOSIS_CHAIN_UPPER}_ETH2_ENDPOINT", default=""),
+        SLOTS_PER_EPOCH=16,
+        SECONDS_PER_SLOT=5,
+        ORACLES_CONTRACT_ADDRESS="",
+        MULTICALL_CONTRACT_ADDRESS="",
+        SWISE_TOKEN_CONTRACT_ADDRESS="",
+        REWARD_TOKEN_CONTRACT_ADDRESS="",
+        STAKED_TOKEN_CONTRACT_ADDRESS="",
+        DISTRIBUTOR_FALLBACK_ADDRESS="",
+        RARI_FUSE_POOL_ADDRESSES=[],
+        WITHDRAWAL_CREDENTIALS="",
+        ORACLE_PRIVATE_KEY=config(
+            f"{GNOSIS_CHAIN_UPPER}_ORACLE_PRIVATE_KEY", default=""
+        ),
+        AWS_BUCKET_NAME=config(
+            f"{GNOSIS_CHAIN_UPPER}_AWS_BUCKET_NAME", default="oracle-votes-gnosis"
+        ),
+        AWS_REGION=config(f"{GNOSIS_CHAIN_UPPER}_AWS_REGION", default=""),
+        AWS_ACCESS_KEY_ID=config(f"{GNOSIS_CHAIN_UPPER}_AWS_ACCESS_KEY_ID", default=""),
+        AWS_SECRET_ACCESS_KEY=config(
+            f"{GNOSIS_CHAIN_UPPER}_AWS_SECRET_ACCESS_KEY", default=""
+        ),
+        KEEPER_ETH1_ENDPOINT=config(
+            f"{GNOSIS_CHAIN_UPPER}_KEEPER_ETH1_ENDPOINT", default=""
+        ),
+        KEEPER_MIN_BALANCE=config(
+            f"{GNOSIS_CHAIN_UPPER}_KEEPER_MIN_BALANCE_WEI",
+            default=Web3.toWei(1, "ether"),
+            cast=int,
+        ),
+        KEEPER_MAX_FEE_PER_GAS=config(
+            f"{GNOSIS_CHAIN_UPPER}_KEEPER_MAX_FEE_PER_GAS_GWEI",
+            default=150,
+            cast=lambda x: Web3.toWei(x, "gwei"),
+        ),
+        SYNC_PERIOD=timedelta(days=1),
+        IS_POA=True,
+        DEPOSIT_TOKEN_SYMBOL="mGNO",
+    ),
+}
diff --git a/oracle/oracle/clients.py b/oracle/oracle/clients.py
index cc9158a..ca1c757 100644
--- a/oracle/oracle/clients.py
+++ b/oracle/oracle/clients.py
@@ -2,29 +2,14 @@
 from typing import Any, Dict, List, Union
 
 import backoff
-import boto3
 import ipfshttpclient
 from aiohttp import ClientSession
 from gql import Client
 from gql.transport.aiohttp import AIOHTTPTransport
 from graphql import DocumentNode
 
-from oracle.oracle.settings import (
-    AWS_ACCESS_KEY_ID,
-    AWS_SECRET_ACCESS_KEY,
-    ETHEREUM_SUBGRAPH_URL,
-    IPFS_FETCH_ENDPOINTS,
-    IPFS_PIN_ENDPOINTS,
-    RARI_FUSE_SUBGRAPH_URL,
-    STAKEWISE_SUBGRAPH_URL,
-    UNISWAP_V3_SUBGRAPH_URL,
-)
-
-s3_client = boto3.client(
-    "s3",
-    aws_access_key_id=AWS_ACCESS_KEY_ID,
-    aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
-)
+from oracle.networks import NETWORKS
+from oracle.settings import IPFS_FETCH_ENDPOINTS, IPFS_PIN_ENDPOINTS
 
 gql_logger = logging.getLogger("gql_logger")
 gql_handler = logging.StreamHandler()
@@ -36,35 +21,45 @@
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=300, logger=gql_logger)
-async def execute_sw_gql_query(query: DocumentNode, variables: Dict) -> Dict:
+async def execute_sw_gql_query(
+    network: str, query: DocumentNode, variables: Dict
+) -> Dict:
     """Executes GraphQL query."""
-    transport = AIOHTTPTransport(url=STAKEWISE_SUBGRAPH_URL)
+    subgraph_url = NETWORKS[network]["STAKEWISE_SUBGRAPH_URL"]
+    transport = AIOHTTPTransport(url=subgraph_url)
     async with Client(transport=transport, execute_timeout=EXECUTE_TIMEOUT) as session:
         return await session.execute(query, variable_values=variables)
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=300, logger=gql_logger)
-async def execute_uniswap_v3_gql_query(query: DocumentNode, variables: Dict) -> Dict:
+async def execute_uniswap_v3_gql_query(
+    network: str, query: DocumentNode, variables: Dict
+) -> Dict:
     """Executes GraphQL query."""
-    transport = AIOHTTPTransport(url=UNISWAP_V3_SUBGRAPH_URL)
+    subgraph_url = NETWORKS[network]["UNISWAP_V3_SUBGRAPH_URL"]
+    transport = AIOHTTPTransport(url=subgraph_url)
     async with Client(transport=transport, execute_timeout=EXECUTE_TIMEOUT) as session:
         return await session.execute(query, variable_values=variables)
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=300, logger=gql_logger)
-async def execute_ethereum_gql_query(query: DocumentNode, variables: Dict) -> Dict:
+async def execute_ethereum_gql_query(
+    network: str, query: DocumentNode, variables: Dict
+) -> Dict:
     """Executes GraphQL query."""
-    transport = AIOHTTPTransport(url=ETHEREUM_SUBGRAPH_URL)
+    subgraph_url = NETWORKS[network]["ETHEREUM_SUBGRAPH_URL"]
+    transport = AIOHTTPTransport(url=subgraph_url)
     async with Client(transport=transport, execute_timeout=EXECUTE_TIMEOUT) as session:
         return await session.execute(query, variable_values=variables)
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=300, logger=gql_logger)
 async def execute_rari_fuse_pools_gql_query(
-    query: DocumentNode, variables: Dict
+    network: str, query: DocumentNode, variables: Dict
 ) -> Dict:
     """Executes GraphQL query."""
-    transport = AIOHTTPTransport(url=RARI_FUSE_SUBGRAPH_URL)
+    subgraph_url = NETWORKS[network]["RARI_FUSE_SUBGRAPH_URL"]
+    transport = AIOHTTPTransport(url=subgraph_url)
     async with Client(transport=transport, execute_timeout=EXECUTE_TIMEOUT) as session:
         return await session.execute(query, variable_values=variables)
 
diff --git a/oracle/oracle/distributor/controller.py b/oracle/oracle/distributor/controller.py
index 3086bd3..f717a1d 100644
--- a/oracle/oracle/distributor/controller.py
+++ b/oracle/oracle/distributor/controller.py
@@ -5,10 +5,10 @@
 from eth_typing import HexStr
 from web3 import Web3
 
-from oracle.common.settings import DISTRIBUTOR_VOTE_FILENAME
+from oracle.networks import NETWORKS
+from oracle.settings import DISTRIBUTOR_VOTE_FILENAME
 
 from ..eth1 import submit_vote
-from ..settings import DISTRIBUTOR_FALLBACK_ADDRESS, REWARD_TOKEN_CONTRACT_ADDRESS
 from .eth1 import (
     get_disabled_stakers_reward_token_distributions,
     get_distributor_claimed_accounts,
@@ -30,9 +30,16 @@
 class DistributorController(object):
     """Updates merkle root and submits proofs to the IPFS."""
 
-    def __init__(self, oracle: LocalAccount) -> None:
+    def __init__(self, network: str, oracle: LocalAccount) -> None:
         self.last_to_block = None
+        self.network = network
         self.oracle = oracle
+        self.distributor_fallback_address = NETWORKS[network][
+            "DISTRIBUTOR_FALLBACK_ADDRESS"
+        ]
+        self.reward_token_contract_address = NETWORKS[network][
+            "REWARD_TOKEN_CONTRACT_ADDRESS"
+        ]
 
     async def process(self, voting_params: DistributorVotingParameters) -> None:
         """Submits vote for the new merkle root and merkle proofs to the IPFS."""
@@ -50,14 +57,16 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
             return
 
         logger.info(
-            f"Voting for Merkle Distributor rewards: from block={from_block}, to block={to_block}"
+            f"[{self.network}] Voting for Merkle Distributor rewards: from block={from_block}, to block={to_block}"
         )
 
         # fetch active periodic allocations
         active_allocations = await get_periodic_allocations(
-            from_block=from_block, to_block=to_block
+            network=self.network, from_block=from_block, to_block=to_block
+        )
+        uniswap_v3_pools = await get_uniswap_v3_pools(
+            network=self.network, block_number=to_block
         )
-        uniswap_v3_pools = await get_uniswap_v3_pools(to_block)
 
         # fetch uni v3 distributions
         all_distributions = await get_uniswap_v3_distributions(
@@ -70,6 +79,7 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
         # fetch disabled stakers distributions
         disabled_stakers_distributions = (
             await get_disabled_stakers_reward_token_distributions(
+                network=self.network,
                 distributor_reward=voting_params["distributor_reward"],
                 from_block=from_block,
                 to_block=to_block,
@@ -81,7 +91,9 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
         last_merkle_proofs = voting_params["last_merkle_proofs"]
         if w3.toInt(hexstr=last_merkle_root) and last_merkle_proofs:
             # fetch accounts that have claimed since last merkle root update
-            claimed_accounts = await get_distributor_claimed_accounts(last_merkle_root)
+            claimed_accounts = await get_distributor_claimed_accounts(
+                network=self.network, merkle_root=last_merkle_root
+            )
 
             # calculate unclaimed rewards
             unclaimed_rewards = await get_unclaimed_balances(
@@ -95,6 +107,7 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
         tasks = []
         for dist in all_distributions:
             distributor_rewards = DistributorRewards(
+                network=self.network,
                 uniswap_v3_pools=uniswap_v3_pools,
                 from_block=dist["from_block"],
                 to_block=dist["to_block"],
@@ -107,7 +120,11 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
             tasks.append(task)
 
         # process one time rewards
-        tasks.append(get_one_time_rewards(from_block=from_block, to_block=to_block))
+        tasks.append(
+            get_one_time_rewards(
+                network=self.network, from_block=from_block, to_block=to_block
+            )
+        )
 
         # merge results
         results = await asyncio.gather(*tasks)
@@ -117,15 +134,21 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
 
         protocol_reward = voting_params["protocol_reward"]
         operators_rewards, left_reward = await get_operators_rewards(
-            from_block=from_block, to_block=to_block, total_reward=protocol_reward
+            network=self.network,
+            from_block=from_block,
+            to_block=to_block,
+            total_reward=protocol_reward,
         )
         partners_rewards, left_reward = await get_partners_rewards(
-            from_block=from_block, to_block=to_block, total_reward=left_reward
+            network=self.network,
+            from_block=from_block,
+            to_block=to_block,
+            total_reward=left_reward,
         )
         if left_reward > 0:
             fallback_rewards: Rewards = {
-                DISTRIBUTOR_FALLBACK_ADDRESS: {
-                    REWARD_TOKEN_CONTRACT_ADDRESS: str(left_reward)
+                self.distributor_fallback_address: {
+                    self.reward_token_contract_address: str(left_reward)
                 }
             }
             final_rewards = DistributorRewards.merge_rewards(
@@ -144,10 +167,10 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
 
         # calculate merkle root
         merkle_root, claims = calculate_merkle_root(final_rewards)
-        logger.info(f"Generated new merkle root: {merkle_root}")
+        logger.info(f"[{self.network}] Generated new merkle root: {merkle_root}")
 
         claims_link = await upload_claims(claims)
-        logger.info(f"Claims uploaded to: {claims_link}")
+        logger.info(f"[{self.network}] Claims uploaded to: {claims_link}")
 
         # submit vote
         encoded_data: bytes = w3.codec.encode_abi(
@@ -161,11 +184,14 @@ async def process(self, voting_params: DistributorVotingParameters) -> None:
             merkle_proofs=claims_link,
         )
         submit_vote(
+            network=self.network,
             oracle=self.oracle,
             encoded_data=encoded_data,
             vote=vote,
             name=DISTRIBUTOR_VOTE_FILENAME,
         )
-        logger.info("Distributor vote has been successfully submitted")
+        logger.info(
+            f"[{self.network}] Distributor vote has been successfully submitted"
+        )
 
         self.last_to_block = to_block
diff --git a/oracle/oracle/distributor/eth1.py b/oracle/oracle/distributor/eth1.py
index 1370902..226e43e 100644
--- a/oracle/oracle/distributor/eth1.py
+++ b/oracle/oracle/distributor/eth1.py
@@ -15,12 +15,8 @@
     PARTNERS_QUERY,
     PERIODIC_DISTRIBUTIONS_QUERY,
 )
-from oracle.oracle.settings import (
-    DISTRIBUTOR_FALLBACK_ADDRESS,
-    REWARD_TOKEN_CONTRACT_ADDRESS,
-    STAKED_TOKEN_CONTRACT_ADDRESS,
-)
 
+from ...networks import NETWORKS
 from .ipfs import get_one_time_rewards_allocations
 from .rewards import DistributorRewards
 from .types import (
@@ -36,11 +32,12 @@
 
 
 async def get_periodic_allocations(
-    from_block: BlockNumber, to_block: BlockNumber
+    network: str, from_block: BlockNumber, to_block: BlockNumber
 ) -> TokenAllocations:
     """Fetches periodic allocations."""
     last_id = ""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=PERIODIC_DISTRIBUTIONS_QUERY,
         variables=dict(from_block=from_block, to_block=to_block, last_id=last_id),
     )
@@ -51,6 +48,7 @@ async def get_periodic_allocations(
     while len(distributions_chunk) >= 1000:
         last_id = distributions_chunk[-1]["id"]
         result: Dict = await execute_sw_gql_query(
+            network=network,
             query=PERIODIC_DISTRIBUTIONS_QUERY,
             variables=dict(from_block=from_block, to_block=to_block, last_id=last_id),
         )
@@ -80,14 +78,21 @@ async def get_periodic_allocations(
 
 
 async def get_disabled_stakers_reward_token_distributions(
-    distributor_reward: Wei, from_block: BlockNumber, to_block: BlockNumber
+    network: str,
+    distributor_reward: Wei,
+    from_block: BlockNumber,
+    to_block: BlockNumber,
 ) -> Distributions:
     """Fetches disabled stakers reward token distributions based on their staked token balances."""
     if distributor_reward <= 0:
         return []
 
+    reward_token_address = NETWORKS[network]["REWARD_TOKEN_CONTRACT_ADDRESS"]
+    staked_token_address = NETWORKS[network]["STAKED_TOKEN_CONTRACT_ADDRESS"]
+
     last_id = ""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=DISABLED_STAKER_ACCOUNTS_QUERY,
         variables=dict(block_number=to_block, last_id=last_id),
     )
@@ -98,6 +103,7 @@ async def get_disabled_stakers_reward_token_distributions(
     while len(stakers_chunk) >= 1000:
         last_id = stakers_chunk[-1]["id"]
         result: Dict = await execute_sw_gql_query(
+            network=network,
             query=DISABLED_STAKER_ACCOUNTS_QUERY,
             variables=dict(block_number=to_block, last_id=last_id),
         )
@@ -144,8 +150,8 @@ async def get_disabled_stakers_reward_token_distributions(
             contract=staker_address,
             from_block=from_block,
             to_block=to_block,
-            uni_v3_token=STAKED_TOKEN_CONTRACT_ADDRESS,
-            reward_token=REWARD_TOKEN_CONTRACT_ADDRESS,
+            uni_v3_token=staked_token_address,
+            reward_token=reward_token_address,
             reward=reward,
         )
         distributions.append(distribution)
@@ -154,10 +160,13 @@ async def get_disabled_stakers_reward_token_distributions(
     return distributions
 
 
-async def get_distributor_claimed_accounts(merkle_root: HexStr) -> ClaimedAccounts:
+async def get_distributor_claimed_accounts(
+    network: str, merkle_root: HexStr
+) -> ClaimedAccounts:
     """Fetches addresses that have claimed their tokens from the `MerkleDistributor` contract."""
     last_id = ""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=DISTRIBUTOR_CLAIMED_ACCOUNTS_QUERY,
         variables=dict(merkle_root=merkle_root, last_id=last_id),
     )
@@ -168,6 +177,7 @@ async def get_distributor_claimed_accounts(merkle_root: HexStr) -> ClaimedAccoun
     while len(claims_chunk) >= 1000:
         last_id = claims_chunk[-1]["id"]
         result: Dict = await execute_sw_gql_query(
+            network=network,
             query=DISTRIBUTOR_CLAIMED_ACCOUNTS_QUERY,
             variables=dict(merkle_root=merkle_root, last_id=last_id),
         )
@@ -178,18 +188,21 @@ async def get_distributor_claimed_accounts(merkle_root: HexStr) -> ClaimedAccoun
 
 
 async def get_operators_rewards(
+    network: str,
     from_block: BlockNumber,
     to_block: BlockNumber,
     total_reward: Wei,
 ) -> Tuple[Rewards, Wei]:
     """Fetches operators rewards."""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=OPERATORS_REWARDS_QUERY,
         variables=dict(
             block_number=to_block,
         ),
     )
     operators = result["operators"]
+    reward_token_address = NETWORKS[network]["REWARD_TOKEN_CONTRACT_ADDRESS"]
 
     # process operators
     points: Dict[ChecksumAddress, int] = {}
@@ -234,23 +247,25 @@ async def get_operators_rewards(
         total_reward=operators_reward,
         points=points,
         total_points=total_points,
-        reward_token=REWARD_TOKEN_CONTRACT_ADDRESS,
+        reward_token=reward_token_address,
     )
 
     return rewards, Wei(total_reward - operators_reward)
 
 
 async def get_partners_rewards(
-    from_block: BlockNumber, to_block: BlockNumber, total_reward: Wei
+    network: str, from_block: BlockNumber, to_block: BlockNumber, total_reward: Wei
 ) -> Tuple[Rewards, Wei]:
     """Fetches partners rewards."""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=PARTNERS_QUERY,
         variables=dict(
             block_number=to_block,
         ),
     )
     partners = result["partners"]
+    reward_token_address = NETWORKS[network]["REWARD_TOKEN_CONTRACT_ADDRESS"]
 
     # process partners
     points: Dict[ChecksumAddress, int] = {}
@@ -295,7 +310,7 @@ async def get_partners_rewards(
         total_reward=partners_reward,
         points=points,
         total_points=total_points,
-        reward_token=REWARD_TOKEN_CONTRACT_ADDRESS,
+        reward_token=reward_token_address,
     )
 
     return rewards, Wei(total_reward - partners_reward)
@@ -335,11 +350,14 @@ def calculate_points_based_rewards(
 
 
 async def get_one_time_rewards(
-    from_block: BlockNumber, to_block: BlockNumber
+    network: str, from_block: BlockNumber, to_block: BlockNumber
 ) -> Rewards:
     """Fetches one time rewards."""
+    distributor_fallback_address = NETWORKS[network]["DISTRIBUTOR_FALLBACK_ADDRESS"]
+
     last_id = ""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=ONE_TIME_DISTRIBUTIONS_QUERY,
         variables=dict(from_block=from_block, to_block=to_block, last_id=last_id),
     )
@@ -350,6 +368,7 @@ async def get_one_time_rewards(
     while len(distributions_chunk) >= 1000:
         last_id = distributions_chunk[-1]["id"]
         result: Dict = await execute_sw_gql_query(
+            network=network,
             query=ONE_TIME_DISTRIBUTIONS_QUERY,
             variables=dict(from_block=from_block, to_block=to_block, last_id=last_id),
         )
@@ -380,18 +399,18 @@ async def get_one_time_rewards(
 
             if total_amount != distributed_amount:
                 logger.warning(
-                    f'Failed to process one time distribution: {distribution["id"]}. Invalid rewards.'
+                    f'[{network}] Failed to process one time distribution: {distribution["id"]}. Invalid rewards.'
                 )
                 rewards: Rewards = {
-                    DISTRIBUTOR_FALLBACK_ADDRESS: {token: str(total_amount)}
+                    distributor_fallback_address: {token: str(total_amount)}
                 }
         except Exception as e:
             logger.error(e)
             logger.warning(
-                f'Failed to process one time distribution: {distribution["id"]}. Exception occurred.'
+                f'[{network}] Failed to process one time distribution: {distribution["id"]}. Exception occurred.'
             )
             rewards: Rewards = {
-                DISTRIBUTOR_FALLBACK_ADDRESS: {token: str(total_amount)}
+                distributor_fallback_address: {token: str(total_amount)}
             }
 
         final_rewards = DistributorRewards.merge_rewards(final_rewards, rewards)
diff --git a/oracle/oracle/distributor/ipfs.py b/oracle/oracle/distributor/ipfs.py
index d056727..6222fc3 100644
--- a/oracle/oracle/distributor/ipfs.py
+++ b/oracle/oracle/distributor/ipfs.py
@@ -7,14 +7,14 @@
 from aiohttp import ClientSession
 from eth_typing import ChecksumAddress
 
-from oracle.oracle.settings import (
+from oracle.oracle.clients import ipfs_fetch
+from oracle.settings import (
     IPFS_PIN_ENDPOINTS,
     IPFS_PINATA_API_KEY,
     IPFS_PINATA_PIN_ENDPOINT,
     IPFS_PINATA_SECRET_KEY,
 )
 
-from ..clients import ipfs_fetch
 from .types import ClaimedAccounts, Claims, Rewards
 
 logger = logging.getLogger(__name__)
diff --git a/oracle/oracle/distributor/rari.py b/oracle/oracle/distributor/rari.py
index 87c471c..78a9d15 100644
--- a/oracle/oracle/distributor/rari.py
+++ b/oracle/oracle/distributor/rari.py
@@ -5,18 +5,22 @@
 from web3 import Web3
 
 from oracle.oracle.clients import execute_rari_fuse_pools_gql_query
+from oracle.oracle.graphql_queries import RARI_FUSE_POOLS_CTOKENS_QUERY
 
-from ..graphql_queries import RARI_FUSE_POOLS_CTOKENS_QUERY
 from .types import Balances
 
 
 async def get_rari_fuse_liquidity_points(
-    ctoken_address: ChecksumAddress, from_block: BlockNumber, to_block: BlockNumber
+    network: str,
+    ctoken_address: ChecksumAddress,
+    from_block: BlockNumber,
+    to_block: BlockNumber,
 ) -> Balances:
     """Fetches Rari Fuse pool accounts balances."""
     lowered_ctoken_address = ctoken_address.lower()
     last_id = ""
     result: Dict = await execute_rari_fuse_pools_gql_query(
+        network=network,
         query=RARI_FUSE_POOLS_CTOKENS_QUERY,
         variables=dict(
             ctoken_address=lowered_ctoken_address,
@@ -31,6 +35,7 @@ async def get_rari_fuse_liquidity_points(
     while len(positions_chunk) >= 1000:
         last_id = positions_chunk[-1]["id"]
         result: Dict = await execute_rari_fuse_pools_gql_query(
+            network=network,
             query=RARI_FUSE_POOLS_CTOKENS_QUERY,
             variables=dict(
                 ctoken_address=lowered_ctoken_address,
diff --git a/oracle/oracle/distributor/rewards.py b/oracle/oracle/distributor/rewards.py
index fae3027..c4b8d3d 100644
--- a/oracle/oracle/distributor/rewards.py
+++ b/oracle/oracle/distributor/rewards.py
@@ -5,13 +5,7 @@
 from ens.constants import EMPTY_ADDR_HEX
 from eth_typing import BlockNumber, ChecksumAddress
 
-from oracle.oracle.settings import (
-    DISTRIBUTOR_FALLBACK_ADDRESS,
-    RARI_FUSE_POOL_ADDRESSES,
-    REWARD_TOKEN_CONTRACT_ADDRESS,
-    STAKED_TOKEN_CONTRACT_ADDRESS,
-    SWISE_TOKEN_CONTRACT_ADDRESS,
-)
+from oracle.networks import NETWORKS
 
 from .rari import get_rari_fuse_liquidity_points
 from .types import Balances, Rewards, UniswapV3Pools
@@ -27,12 +21,27 @@
 class DistributorRewards(object):
     def __init__(
         self,
+        network: str,
         uniswap_v3_pools: UniswapV3Pools,
         from_block: BlockNumber,
         to_block: BlockNumber,
         reward_token: ChecksumAddress,
         uni_v3_token: ChecksumAddress,
     ) -> None:
+        self.network = network
+        self.rari_fuse_pool_addresses = NETWORKS[network]["RARI_FUSE_POOL_ADDRESSES"]
+        self.distributor_fallback_address = NETWORKS[network][
+            "DISTRIBUTOR_FALLBACK_ADDRESS"
+        ]
+        self.staked_token_contract_address = NETWORKS[network][
+            "STAKED_TOKEN_CONTRACT_ADDRESS"
+        ]
+        self.reward_token_contract_address = NETWORKS[network][
+            "REWARD_TOKEN_CONTRACT_ADDRESS"
+        ]
+        self.swise_token_contract_address = NETWORKS[network][
+            "SWISE_TOKEN_CONTRACT_ADDRESS"
+        ]
         self.uni_v3_staked_token_pools = uniswap_v3_pools["staked_token_pools"]
         self.uni_v3_reward_token_pools = uniswap_v3_pools["reward_token_pools"]
         self.uni_v3_swise_pools = uniswap_v3_pools["swise_pools"]
@@ -48,8 +57,8 @@ def is_supported_contract(self, contract_address: ChecksumAddress) -> bool:
         """Checks whether the provided contract address is supported."""
         return (
             contract_address in self.uni_v3_pools
-            or contract_address == SWISE_TOKEN_CONTRACT_ADDRESS
-            or contract_address in RARI_FUSE_POOL_ADDRESSES
+            or contract_address == self.swise_token_contract_address
+            or contract_address in self.rari_fuse_pool_addresses
         )
 
     @staticmethod
@@ -96,7 +105,7 @@ async def get_rewards(
         rewards: Rewards = {}
         self.add_value(
             rewards=rewards,
-            to=DISTRIBUTOR_FALLBACK_ADDRESS,
+            to=self.distributor_fallback_address,
             reward_token=self.reward_token,
             amount=reward,
         )
@@ -106,37 +115,42 @@ async def get_rewards(
     async def get_balances(self, contract_address: ChecksumAddress) -> Balances:
         """Fetches balances and total supply of the contract."""
         if (
-            self.uni_v3_token == STAKED_TOKEN_CONTRACT_ADDRESS
+            self.uni_v3_token == self.staked_token_contract_address
             and contract_address in self.uni_v3_staked_token_pools
         ):
             logger.info(
-                f"Fetching Uniswap V3 staked token balances: pool={contract_address}"
+                f"[{self.network}] Fetching Uniswap V3 staked token balances: pool={contract_address}"
             )
             return await get_uniswap_v3_single_token_balances(
+                network=self.network,
                 pool_address=contract_address,
-                token=STAKED_TOKEN_CONTRACT_ADDRESS,
+                token=self.staked_token_contract_address,
                 block_number=self.to_block,
             )
         elif (
-            self.uni_v3_token == REWARD_TOKEN_CONTRACT_ADDRESS
+            self.uni_v3_token == self.reward_token_contract_address
             and contract_address in self.uni_v3_reward_token_pools
         ):
             logger.info(
-                f"Fetching Uniswap V3 reward token balances: pool={contract_address}"
+                f"[{self.network}] Fetching Uniswap V3 reward token balances: pool={contract_address}"
             )
             return await get_uniswap_v3_single_token_balances(
+                network=self.network,
                 pool_address=contract_address,
-                token=REWARD_TOKEN_CONTRACT_ADDRESS,
+                token=self.reward_token_contract_address,
                 block_number=self.to_block,
             )
         elif (
-            self.uni_v3_token == SWISE_TOKEN_CONTRACT_ADDRESS
+            self.uni_v3_token == self.swise_token_contract_address
             and contract_address in self.uni_v3_swise_pools
         ):
-            logger.info(f"Fetching Uniswap V3 SWISE balances: pool={contract_address}")
+            logger.info(
+                f"[{self.network}] Fetching Uniswap V3 SWISE balances: pool={contract_address}"
+            )
             return await get_uniswap_v3_single_token_balances(
+                network=self.network,
                 pool_address=contract_address,
-                token=SWISE_TOKEN_CONTRACT_ADDRESS,
+                token=self.swise_token_contract_address,
                 block_number=self.to_block,
             )
         elif (
@@ -144,9 +158,10 @@ async def get_balances(self, contract_address: ChecksumAddress) -> Balances:
             and contract_address in self.uni_v3_swise_pools
         ):
             logger.info(
-                f"Fetching Uniswap V3 full range liquidity points: pool={contract_address}"
+                f"[{self.network}] Fetching Uniswap V3 full range liquidity points: pool={contract_address}"
             )
             return await get_uniswap_v3_range_liquidity_points(
+                network=self.network,
                 tick_lower=-887220,
                 tick_upper=887220,
                 pool_address=contract_address,
@@ -154,24 +169,26 @@ async def get_balances(self, contract_address: ChecksumAddress) -> Balances:
             )
         elif contract_address in self.uni_v3_pools:
             logger.info(
-                f"Fetching Uniswap V3 liquidity points: pool={contract_address}"
+                f"[{self.network}] Fetching Uniswap V3 liquidity points: pool={contract_address}"
             )
             return await get_uniswap_v3_liquidity_points(
+                network=self.network,
                 pool_address=contract_address,
                 block_number=self.to_block,
             )
-        elif contract_address in RARI_FUSE_POOL_ADDRESSES:
+        elif contract_address in self.rari_fuse_pool_addresses:
             logger.info(
-                f"Fetching Rari Fuse Pool liquidity points: pool={contract_address}"
+                f"[{self.network}] Fetching Rari Fuse Pool liquidity points: pool={contract_address}"
             )
             return await get_rari_fuse_liquidity_points(
+                network=self.network,
                 ctoken_address=contract_address,
                 from_block=self.from_block,
                 to_block=self.to_block,
             )
 
         raise ValueError(
-            f"Cannot get balances for unsupported contract address {contract_address}"
+            f"[{self.network}] Cannot get balances for unsupported contract address {contract_address}"
         )
 
     async def _get_rewards(
@@ -189,7 +206,7 @@ async def _get_rewards(
             # no recipients for the rewards -> assign reward to the rescue address
             self.add_value(
                 rewards=rewards,
-                to=DISTRIBUTOR_FALLBACK_ADDRESS,
+                to=self.distributor_fallback_address,
                 reward_token=self.reward_token,
                 amount=total_reward,
             )
@@ -215,7 +232,7 @@ async def _get_rewards(
                 # failed to assign reward -> return it to rescue address
                 self.add_value(
                     rewards=rewards,
-                    to=DISTRIBUTOR_FALLBACK_ADDRESS,
+                    to=self.distributor_fallback_address,
                     reward_token=self.reward_token,
                     amount=account_reward,
                 )
diff --git a/oracle/oracle/distributor/uniswap_v3.py b/oracle/oracle/distributor/uniswap_v3.py
index c5baf1a..383c7c1 100644
--- a/oracle/oracle/distributor/uniswap_v3.py
+++ b/oracle/oracle/distributor/uniswap_v3.py
@@ -6,6 +6,7 @@
 from eth_typing import BlockNumber, ChecksumAddress
 from web3 import Web3
 
+from oracle.networks import NETWORKS
 from oracle.oracle.clients import execute_uniswap_v3_gql_query
 from oracle.oracle.graphql_queries import (
     UNISWAP_V3_CURRENT_TICK_POSITIONS_QUERY,
@@ -14,11 +15,6 @@
     UNISWAP_V3_POSITIONS_QUERY,
     UNISWAP_V3_RANGE_POSITIONS_QUERY,
 )
-from oracle.oracle.settings import (
-    REWARD_TOKEN_CONTRACT_ADDRESS,
-    STAKED_TOKEN_CONTRACT_ADDRESS,
-    SWISE_TOKEN_CONTRACT_ADDRESS,
-)
 
 from .types import (
     Balances,
@@ -38,10 +34,14 @@
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
-async def get_uniswap_v3_pools(block_number: BlockNumber) -> UniswapV3Pools:
+async def get_uniswap_v3_pools(
+    network: str, block_number: BlockNumber
+) -> UniswapV3Pools:
     """Fetches Uniswap V3 pools."""
+    network_config = NETWORKS[network]
     last_id = ""
     result: Dict = await execute_uniswap_v3_gql_query(
+        network=network,
         query=UNISWAP_V3_POOLS_QUERY,
         variables=dict(block_number=block_number, last_id=last_id),
     )
@@ -52,6 +52,7 @@ async def get_uniswap_v3_pools(block_number: BlockNumber) -> UniswapV3Pools:
     while len(pools_chunk) >= 1000:
         last_id = pools_chunk[-1]["id"]
         result: Dict = await execute_uniswap_v3_gql_query(
+            network=network,
             query=UNISWAP_V3_POOLS_QUERY,
             variables=dict(block_number=block_number, last_id=last_id),
         )
@@ -68,11 +69,11 @@ async def get_uniswap_v3_pools(block_number: BlockNumber) -> UniswapV3Pools:
         pool_token0 = Web3.toChecksumAddress(pool["token0"])
         pool_token1 = Web3.toChecksumAddress(pool["token1"])
         for pool_token in [pool_token0, pool_token1]:
-            if pool_token == STAKED_TOKEN_CONTRACT_ADDRESS:
+            if pool_token == network_config["STAKED_TOKEN_CONTRACT_ADDRESS"]:
                 uni_v3_pools["staked_token_pools"].add(pool_address)
-            elif pool_token == REWARD_TOKEN_CONTRACT_ADDRESS:
+            elif pool_token == network_config["REWARD_TOKEN_CONTRACT_ADDRESS"]:
                 uni_v3_pools["reward_token_pools"].add(pool_address)
-            elif pool_token == SWISE_TOKEN_CONTRACT_ADDRESS:
+            elif pool_token == network_config["SWISE_TOKEN_CONTRACT_ADDRESS"]:
                 uni_v3_pools["swise_pools"].add(pool_address)
 
     return uni_v3_pools
@@ -151,11 +152,12 @@ async def get_uniswap_v3_distributions(
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
 async def get_uniswap_v3_liquidity_points(
-    pool_address: ChecksumAddress, block_number: BlockNumber
+    network: str, pool_address: ChecksumAddress, block_number: BlockNumber
 ) -> Balances:
     """Fetches users' liquidity points of the Uniswap V3 pool in the current tick."""
     lowered_pool_address = pool_address.lower()
     result: Dict = await execute_uniswap_v3_gql_query(
+        network=network,
         query=UNISWAP_V3_POOL_QUERY,
         variables=dict(block_number=block_number, pool_address=lowered_pool_address),
     )
@@ -171,6 +173,7 @@ async def get_uniswap_v3_liquidity_points(
 
     last_id = ""
     result: Dict = await execute_uniswap_v3_gql_query(
+        network=network,
         query=UNISWAP_V3_CURRENT_TICK_POSITIONS_QUERY,
         variables=dict(
             block_number=block_number,
@@ -186,6 +189,7 @@ async def get_uniswap_v3_liquidity_points(
     while len(positions_chunk) >= 1000:
         last_id = positions_chunk[-1]["id"]
         result: Dict = await execute_uniswap_v3_gql_query(
+            network=network,
             query=UNISWAP_V3_CURRENT_TICK_POSITIONS_QUERY,
             variables=dict(
                 block_number=block_number,
@@ -218,6 +222,7 @@ async def get_uniswap_v3_liquidity_points(
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
 async def get_uniswap_v3_range_liquidity_points(
+    network: str,
     tick_lower: int,
     tick_upper: int,
     pool_address: ChecksumAddress,
@@ -228,6 +233,7 @@ async def get_uniswap_v3_range_liquidity_points(
 
     last_id = ""
     result: Dict = await execute_uniswap_v3_gql_query(
+        network=network,
         query=UNISWAP_V3_RANGE_POSITIONS_QUERY,
         variables=dict(
             block_number=block_number,
@@ -244,6 +250,7 @@ async def get_uniswap_v3_range_liquidity_points(
     while len(positions_chunk) >= 1000:
         last_id = positions_chunk[-1]["id"]
         result: Dict = await execute_uniswap_v3_gql_query(
+            network=network,
             query=UNISWAP_V3_RANGE_POSITIONS_QUERY,
             variables=dict(
                 block_number=block_number,
@@ -277,11 +284,15 @@ async def get_uniswap_v3_range_liquidity_points(
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
 async def get_uniswap_v3_single_token_balances(
-    pool_address: ChecksumAddress, token: ChecksumAddress, block_number: BlockNumber
+    network: str,
+    pool_address: ChecksumAddress,
+    token: ChecksumAddress,
+    block_number: BlockNumber,
 ) -> Balances:
     """Fetches users' single token balances of the Uniswap V3 pair across all the ticks."""
     lowered_pool_address = pool_address.lower()
     result: Dict = await execute_uniswap_v3_gql_query(
+        network=network,
         query=UNISWAP_V3_POOL_QUERY,
         variables=dict(block_number=block_number, pool_address=lowered_pool_address),
     )
@@ -305,6 +316,7 @@ async def get_uniswap_v3_single_token_balances(
 
     last_id = ""
     result: Dict = await execute_uniswap_v3_gql_query(
+        network=network,
         query=UNISWAP_V3_POSITIONS_QUERY,
         variables=dict(
             block_number=block_number,
@@ -321,6 +333,7 @@ async def get_uniswap_v3_single_token_balances(
     while len(positions_chunk) >= 1000:
         last_id = positions_chunk[-1]["id"]
         result: Dict = await execute_uniswap_v3_gql_query(
+            network=network,
             query=UNISWAP_V3_POSITIONS_QUERY,
             variables=dict(
                 block_number=block_number,
diff --git a/oracle/oracle/eth1.py b/oracle/oracle/eth1.py
index e4ee809..0941385 100644
--- a/oracle/oracle/eth1.py
+++ b/oracle/oracle/eth1.py
@@ -1,23 +1,24 @@
 import json
 import logging
-from typing import Dict, List, TypedDict, Union
+from typing import Dict, TypedDict, Union
 
 import backoff
+import boto3
 from eth_account.messages import encode_defunct
 from eth_account.signers.local import LocalAccount
 from web3 import Web3
 from web3.types import BlockNumber, Timestamp, Wei
 
-from oracle.common.settings import AWS_S3_BUCKET_NAME, CONFIRMATION_BLOCKS
-
-from .clients import execute_ethereum_gql_query, execute_sw_gql_query, s3_client
-from .distributor.types import DistributorVote, DistributorVotingParameters
-from .graphql_queries import (
+from oracle.networks import NETWORKS
+from oracle.oracle.clients import execute_ethereum_gql_query, execute_sw_gql_query
+from oracle.oracle.graphql_queries import (
     FINALIZED_BLOCK_QUERY,
     LATEST_BLOCK_QUERY,
-    ORACLE_QUERY,
     VOTING_PARAMETERS_QUERY,
 )
+from oracle.settings import CONFIRMATION_BLOCKS
+
+from .distributor.types import DistributorVote, DistributorVotingParameters
 from .rewards.types import RewardsVotingParameters, RewardVote
 from .validators.types import ValidatorVote
 
@@ -34,9 +35,10 @@ class VotingParameters(TypedDict):
     distributor: DistributorVotingParameters
 
 
-async def get_finalized_block() -> Block:
+async def get_finalized_block(network: str) -> Block:
     """Gets the finalized block number and its timestamp."""
     result: Dict = await execute_ethereum_gql_query(
+        network=network,
         query=FINALIZED_BLOCK_QUERY,
         variables=dict(
             confirmation_blocks=CONFIRMATION_BLOCKS,
@@ -48,9 +50,10 @@ async def get_finalized_block() -> Block:
     )
 
 
-async def get_latest_block() -> Block:
+async def get_latest_block(network: str) -> Block:
     """Gets the latest block number and its timestamp."""
     result: Dict = await execute_ethereum_gql_query(
+        network=network,
         query=LATEST_BLOCK_QUERY,
         variables=dict(
             confirmation_blocks=CONFIRMATION_BLOCKS,
@@ -62,9 +65,12 @@ async def get_latest_block() -> Block:
     )
 
 
-async def get_voting_parameters(block_number: BlockNumber) -> VotingParameters:
+async def get_voting_parameters(
+    network: str, block_number: BlockNumber
+) -> VotingParameters:
     """Fetches rewards voting parameters."""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=VOTING_PARAMETERS_QUERY,
         variables=dict(
             block_number=block_number,
@@ -92,34 +98,22 @@ async def get_voting_parameters(block_number: BlockNumber) -> VotingParameters:
     return VotingParameters(rewards=rewards, distributor=distributor)
 
 
-async def check_oracle_account(oracle: LocalAccount) -> None:
-    """Checks whether oracle is part of the oracles set."""
-    oracle_lowered_address = oracle.address.lower()
-    result: List = (
-        await execute_sw_gql_query(
-            query=ORACLE_QUERY,
-            variables=dict(
-                oracle_address=oracle_lowered_address,
-            ),
-        )
-    ).get("oracles", [])
-    if result and result[0].get("id", "") == oracle_lowered_address:
-        logger.info(f"Oracle {oracle.address} is part of the oracles set")
-    else:
-        logger.warning(
-            f"NB! Oracle {oracle.address} is not part of the oracles set."
-            f" Please create DAO proposal to include it."
-        )
-
-
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
 def submit_vote(
+    network: str,
     oracle: LocalAccount,
     encoded_data: bytes,
     vote: Union[RewardVote, DistributorVote, ValidatorVote],
     name: str,
 ) -> None:
     """Submits vote to the votes' aggregator."""
+    network_config = NETWORKS[network]
+    aws_bucket_name = network_config["AWS_BUCKET_NAME"]
+    s3_client = boto3.client(
+        "s3",
+        aws_access_key_id=network_config["AWS_ACCESS_KEY_ID"],
+        aws_secret_access_key=network_config["AWS_SECRET_ACCESS_KEY"],
+    )
     # generate candidate ID
     candidate_id: bytes = Web3.keccak(primitive=encoded_data)
     message = encode_defunct(primitive=candidate_id)
@@ -129,11 +123,9 @@ def submit_vote(
     # TODO: support more aggregators (GCP, Azure, etc.)
     bucket_key = f"{oracle.address}/{name}"
     s3_client.put_object(
-        Bucket=AWS_S3_BUCKET_NAME,
+        Bucket=aws_bucket_name,
         Key=bucket_key,
         Body=json.dumps(vote),
         ACL="public-read",
     )
-    s3_client.get_waiter("object_exists").wait(
-        Bucket=AWS_S3_BUCKET_NAME, Key=bucket_key
-    )
+    s3_client.get_waiter("object_exists").wait(Bucket=aws_bucket_name, Key=bucket_key)
diff --git a/oracle/oracle/health_server.py b/oracle/oracle/health_server.py
index edf8fbc..ed6782e 100644
--- a/oracle/oracle/health_server.py
+++ b/oracle/oracle/health_server.py
@@ -4,6 +4,7 @@
 
 from oracle.oracle.clients import ipfs_fetch
 from oracle.oracle.eth1 import get_finalized_block, get_voting_parameters
+from oracle.settings import ENABLED_NETWORKS
 
 logger = logging.getLogger(__name__)
 oracle_routes = web.RouteTableDef()
@@ -13,13 +14,14 @@
 async def health(request):
     try:
         # check graphQL connection
-        finalized_block = await get_finalized_block()
-        current_block_number = finalized_block["block_number"]
-        voting_params = await get_voting_parameters(current_block_number)
-        last_merkle_proofs = voting_params["distributor"]["last_merkle_proofs"]
-
-        # check IPFS connection
-        await ipfs_fetch(last_merkle_proofs)
+        for network in ENABLED_NETWORKS:
+            finalized_block = await get_finalized_block(network)
+            current_block_number = finalized_block["block_number"]
+            voting_params = await get_voting_parameters(network, current_block_number)
+            last_merkle_proofs = voting_params["distributor"]["last_merkle_proofs"]
+
+            # check IPFS connection
+            await ipfs_fetch(last_merkle_proofs)
 
         return web.Response(text="oracle 1")
     except Exception as e:  # noqa: E722
diff --git a/oracle/oracle/main.py b/oracle/oracle/main.py
index 25c5443..f960b01 100644
--- a/oracle/oracle/main.py
+++ b/oracle/oracle/main.py
@@ -1,34 +1,29 @@
 import asyncio
 import logging
-import signal
 import threading
-from typing import Any
+from typing import Dict
 from urllib.parse import urlparse
 
 import aiohttp
-from eth_account import Account
 from eth_account.signers.local import LocalAccount
 
-from oracle.common.health_server import create_health_server_runner, start_health_server
-from oracle.common.settings import ENABLE_HEALTH_SERVER, LOG_LEVEL, TEST_VOTE_FILENAME
+from oracle.health_server import create_health_server_runner, start_health_server
+from oracle.networks import NETWORKS
 from oracle.oracle.distributor.controller import DistributorController
-from oracle.oracle.eth1 import (
-    check_oracle_account,
-    get_finalized_block,
-    get_voting_parameters,
-    submit_vote,
-)
+from oracle.oracle.eth1 import get_finalized_block, get_voting_parameters, submit_vote
 from oracle.oracle.health_server import oracle_routes
 from oracle.oracle.rewards.controller import RewardsController
 from oracle.oracle.rewards.eth2 import get_finality_checkpoints, get_genesis
-from oracle.oracle.settings import (
-    ETH2_CLIENT,
-    ETH2_ENDPOINT,
-    ETHEREUM_SUBGRAPH_URL,
-    ORACLE_PRIVATE_KEY,
+from oracle.oracle.validators.controller import ValidatorsController
+from oracle.settings import (
+    ENABLE_HEALTH_SERVER,
+    ENABLED_NETWORKS,
+    HEALTH_SERVER_PORT,
+    LOG_LEVEL,
     ORACLE_PROCESS_INTERVAL,
+    TEST_VOTE_FILENAME,
 )
-from oracle.oracle.validators.controller import ValidatorsController
+from oracle.utils import InterruptHandler, get_oracle_accounts
 
 logging.basicConfig(
     format="%(asctime)s %(levelname)-8s %(message)s",
@@ -41,89 +36,86 @@
 logger = logging.getLogger(__name__)
 
 
-class InterruptHandler:
-    """
-    Tracks SIGINT and SIGTERM signals.
-    https://stackoverflow.com/a/31464349
-    """
-
-    exit = False
-
-    def __init__(self) -> None:
-        signal.signal(signal.SIGINT, self.exit_gracefully)
-        signal.signal(signal.SIGTERM, self.exit_gracefully)
-
-    # noinspection PyUnusedLocal
-    def exit_gracefully(self, signum: int, frame: Any) -> None:
-        logger.info(f"Received interrupt signal {signum}, exiting...")
-        self.exit = True
-
-
 async def main() -> None:
-    oracle: LocalAccount = Account.from_key(ORACLE_PRIVATE_KEY)
+    oracle_accounts: Dict[str, LocalAccount] = await get_oracle_accounts()
 
     # try submitting test vote
-    # noinspection PyTypeChecker
-    submit_vote(
-        oracle=oracle,
-        encoded_data=b"test data",
-        vote={"name": "test vote"},
-        name=TEST_VOTE_FILENAME,
-    )
+    for network, oracle in oracle_accounts.items():
+        logger.info(f"[{network}] Submitting test vote for account {oracle.address}...")
+        # noinspection PyTypeChecker
+        submit_vote(
+            network=network,
+            oracle=oracle,
+            encoded_data=b"test data",
+            vote={"name": "test vote"},
+            name=TEST_VOTE_FILENAME,
+        )
 
     # check stakewise graphql connection
-    logger.info("Checking connection to graph node...")
-    await get_finalized_block()
-    parsed_uri = "{uri.scheme}://{uri.netloc}".format(
-        uri=urlparse(ETHEREUM_SUBGRAPH_URL)
-    )
-    logger.info(f"Connected to graph node at {parsed_uri}")
+    for network in ENABLED_NETWORKS:
+        network_config = NETWORKS[network]
+        logger.info(f"[{network}] Checking connection to graph node...")
+        await get_finalized_block(network)
+        parsed_uri = "{uri.scheme}://{uri.netloc}".format(
+            uri=urlparse(network_config["ETHEREUM_SUBGRAPH_URL"])
+        )
+        logger.info(f"[{network}] Connected to graph node at {parsed_uri}")
 
     # aiohttp session
     session = aiohttp.ClientSession()
 
     # check ETH2 API connection
-    logger.info("Checking connection to ETH2 node...")
-    await get_finality_checkpoints(session)
-    parsed_uri = "{uri.scheme}://{uri.netloc}".format(uri=urlparse(ETH2_ENDPOINT))
-    logger.info(f"Connected to {ETH2_CLIENT} node at {parsed_uri}")
-
-    # check whether oracle is part of the oracles set
-    await check_oracle_account(oracle)
+    for network in ENABLED_NETWORKS:
+        network_config = NETWORKS[network]
+        logger.info(f"[{network}] Checking connection to ETH2 node...")
+        await get_finality_checkpoints(network, session)
+        parsed_uri = "{uri.scheme}://{uri.netloc}".format(
+            uri=urlparse(network_config["ETH2_ENDPOINT"])
+        )
+        logger.info(f"[{network}] Connected to ETH2 node at {parsed_uri}")
 
     # wait for interrupt
     interrupt_handler = InterruptHandler()
 
     # fetch ETH2 genesis
-    genesis = await get_genesis(session)
-
-    rewards_controller = RewardsController(
-        aiohttp_session=session,
-        genesis_timestamp=int(genesis["genesis_time"]),
-        oracle=oracle,
-    )
-    distributor_controller = DistributorController(oracle)
-    validators_controller = ValidatorsController(oracle)
+    controllers = []
+    for network in ENABLED_NETWORKS:
+        genesis = await get_genesis(network, session)
+        oracle = oracle_accounts[network]
+        rewards_controller = RewardsController(
+            network=network,
+            aiohttp_session=session,
+            genesis_timestamp=int(genesis["genesis_time"]),
+            oracle=oracle,
+        )
+        distributor_controller = DistributorController(network, oracle)
+        validators_controller = ValidatorsController(network, oracle)
+        controllers.append(
+            (network, rewards_controller, distributor_controller, validators_controller)
+        )
 
     while not interrupt_handler.exit:
-        # fetch current finalized ETH1 block data
-        finalized_block = await get_finalized_block()
-        current_block_number = finalized_block["block_number"]
-        current_timestamp = finalized_block["timestamp"]
-        voting_parameters = await get_voting_parameters(current_block_number)
-
-        await asyncio.gather(
-            # check and update staking rewards
-            rewards_controller.process(
-                voting_params=voting_parameters["rewards"],
-                current_block_number=current_block_number,
-                current_timestamp=current_timestamp,
-            ),
-            # check and update merkle distributor
-            distributor_controller.process(voting_parameters["distributor"]),
-            # process validators registration
-            validators_controller.process(),
-        )
+        for (network, rewards_ctrl, distributor_ctrl, validators_ctrl) in controllers:
+            # fetch current finalized ETH1 block data
+            finalized_block = await get_finalized_block(network)
+            current_block_number = finalized_block["block_number"]
+            current_timestamp = finalized_block["timestamp"]
+            voting_parameters = await get_voting_parameters(
+                network, current_block_number
+            )
+            await asyncio.gather(
+                # check and update staking rewards
+                rewards_ctrl.process(
+                    voting_params=voting_parameters["rewards"],
+                    current_block_number=current_block_number,
+                    current_timestamp=current_timestamp,
+                ),
+                # check and update merkle distributor
+                distributor_ctrl.process(voting_parameters["distributor"]),
+                # process validators registration
+                validators_ctrl.process(),
+            )
+
         # wait until next processing time
         await asyncio.sleep(ORACLE_PROCESS_INTERVAL)
 
@@ -137,5 +129,8 @@ async def main() -> None:
             args=(create_health_server_runner(oracle_routes),),
             daemon=True,
         )
+        logger.info(
+            f"Starting monitoring server at http://{ENABLE_HEALTH_SERVER}:{HEALTH_SERVER_PORT}"
+        )
         t.start()
     asyncio.run(main())
diff --git a/oracle/oracle/rewards/controller.py b/oracle/oracle/rewards/controller.py
index 2ddd6cc..2943d38 100644
--- a/oracle/oracle/rewards/controller.py
+++ b/oracle/oracle/rewards/controller.py
@@ -9,15 +9,13 @@
 from web3 import Web3
 from web3.types import Timestamp, Wei
 
-from oracle.common.settings import REWARD_VOTE_FILENAME
+from oracle.networks import NETWORKS
 from oracle.oracle.eth1 import submit_vote
+from oracle.settings import REWARD_VOTE_FILENAME
 
-from ..settings import STAKED_TOKEN_SYMBOL, SYNC_PERIOD
 from .eth1 import get_registered_validators_public_keys
 from .eth2 import (
     PENDING_STATUSES,
-    SECONDS_PER_EPOCH,
-    SLOTS_PER_EPOCH,
     ValidatorStatus,
     get_finality_checkpoints,
     get_validators,
@@ -28,22 +26,12 @@
 w3 = Web3()
 
 
-def format_ether(value: Union[str, int, Wei], sign=STAKED_TOKEN_SYMBOL) -> str:
-    """Converts Wei value."""
-    _value = int(value)
-    if _value < 0:
-        formatted_value = f'-{Web3.fromWei(abs(_value), "ether")}'
-    else:
-        formatted_value = f'{Web3.fromWei(_value, "ether")}'
-
-    return f"{formatted_value} {sign}" if sign else formatted_value
-
-
 class RewardsController(object):
     """Updates total rewards and activated validators number."""
 
     def __init__(
         self,
+        network: str,
         aiohttp_session: ClientSession,
         genesis_timestamp: int,
         oracle: LocalAccount,
@@ -52,6 +40,13 @@ def __init__(
         self.aiohttp_session = aiohttp_session
         self.genesis_timestamp = genesis_timestamp
         self.oracle = oracle
+        self.network = network
+        self.sync_period = NETWORKS[network]["SYNC_PERIOD"]
+        self.slots_per_epoch = NETWORKS[network]["SLOTS_PER_EPOCH"]
+        self.seconds_per_epoch = (
+            self.slots_per_epoch * NETWORKS[network]["SECONDS_PER_SLOT"]
+        )
+        self.deposit_token_symbol = NETWORKS[network]["DEPOSIT_TOKEN_SYMBOL"]
         self.last_vote_total_rewards = None
 
     async def process(
@@ -65,17 +60,19 @@ async def process(
         last_update_time = datetime.utcfromtimestamp(
             voting_params["rewards_updated_at_timestamp"]
         )
-        next_update_time: datetime = last_update_time + SYNC_PERIOD
+        next_update_time: datetime = last_update_time + self.sync_period
         current_time: datetime = datetime.utcfromtimestamp(current_timestamp)
-        while next_update_time + SYNC_PERIOD <= current_time:
-            next_update_time += SYNC_PERIOD
+        while next_update_time + self.sync_period <= current_time:
+            next_update_time += self.sync_period
 
         # skip submitting vote if too early or vote has been already submitted
         if next_update_time > current_time:
             return
 
         # fetch pool validator BLS public keys
-        public_keys = await get_registered_validators_public_keys(current_block_number)
+        public_keys = await get_registered_validators_public_keys(
+            self.network, current_block_number
+        )
 
         # calculate current ETH2 epoch
         update_timestamp = int(
@@ -83,26 +80,31 @@ async def process(
         )
         update_epoch: int = (
             update_timestamp - self.genesis_timestamp
-        ) // SECONDS_PER_EPOCH
+        ) // self.seconds_per_epoch
 
         logger.info(
-            f"Voting for new total rewards with parameters:"
+            f"[{self.network}] Voting for new total rewards with parameters:"
             f" timestamp={update_timestamp}, epoch={update_epoch}"
         )
 
         # wait for the epoch to get finalized
-        checkpoints = await get_finality_checkpoints(self.aiohttp_session)
+        checkpoints = await get_finality_checkpoints(self.network, self.aiohttp_session)
         while update_epoch > int(checkpoints["finalized"]["epoch"]):
-            logger.info(f"Waiting for the epoch {update_epoch} to finalize...")
+            logger.info(
+                f"[{self.network}] Waiting for the epoch {update_epoch} to finalize..."
+            )
             await asyncio.sleep(360)
-            checkpoints = await get_finality_checkpoints(self.aiohttp_session)
+            checkpoints = await get_finality_checkpoints(
+                self.network, self.aiohttp_session
+            )
 
-        state_id = str(update_epoch * SLOTS_PER_EPOCH)
+        state_id = str(update_epoch * self.slots_per_epoch)
         total_rewards: Wei = Wei(0)
         activated_validators = 0
         # fetch balances in chunks of 100 keys
         for i in range(0, len(public_keys), 100):
             validators = await get_validators(
+                network=self.network,
                 session=self.aiohttp_session,
                 public_keys=public_keys[i : i + 100],
                 state_id=state_id,
@@ -116,26 +118,24 @@ async def process(
                     Web3.toWei(validator["balance"], "gwei") - self.deposit_amount
                 )
 
-        pretty_total_rewards = format_ether(total_rewards)
-        log_msg = f"Retrieved pool validator rewards: total={pretty_total_rewards}"
-
-        if self.last_vote_total_rewards is not None:
-            log_msg += f", since last vote={format_ether((total_rewards - self.last_vote_total_rewards))}"
-        logger.info(log_msg)
+        pretty_total_rewards = self.format_ether(total_rewards)
+        logger.info(
+            f"[{self.network}] Retrieved pool validator rewards: total={pretty_total_rewards}"
+        )
 
         if total_rewards < voting_params["total_rewards"]:
             # rewards were reduced -> don't mint new ones
             logger.warning(
-                f"Total rewards decreased since the previous update:"
+                f"[{self.network}] Total rewards decreased since the previous update:"
                 f" current={pretty_total_rewards},"
-                f' previous={format_ether(voting_params["total_rewards"])}'
+                f' previous={self.format_ether(voting_params["total_rewards"])}'
             )
             total_rewards = voting_params["total_rewards"]
-            pretty_total_rewards = format_ether(total_rewards)
+            pretty_total_rewards = self.format_ether(total_rewards)
 
         # submit vote
         logger.info(
-            f"Submitting rewards vote:"
+            f"[{self.network}] Submitting rewards vote:"
             f" nonce={voting_params['rewards_nonce']},"
             f" total rewards={pretty_total_rewards},"
             f" activated validators={activated_validators}"
@@ -153,11 +153,22 @@ async def process(
             total_rewards=str(total_rewards),
         )
         submit_vote(
+            network=self.network,
             oracle=self.oracle,
             encoded_data=encoded_data,
             vote=vote,
             name=REWARD_VOTE_FILENAME,
         )
-        logger.info("Rewards vote has been successfully submitted")
+        logger.info(f"[{self.network}] Rewards vote has been successfully submitted")
 
         self.last_vote_total_rewards = total_rewards
+
+    def format_ether(self, value: Union[str, int, Wei]) -> str:
+        """Converts Wei value."""
+        _value = int(value)
+        if _value < 0:
+            formatted_value = f'-{Web3.fromWei(abs(_value), "ether")}'
+        else:
+            formatted_value = f'{Web3.fromWei(_value, "ether")}'
+
+        return f"{formatted_value} {self.deposit_token_symbol}"
diff --git a/oracle/oracle/rewards/eth1.py b/oracle/oracle/rewards/eth1.py
index 10a2b2a..5c8b128 100644
--- a/oracle/oracle/rewards/eth1.py
+++ b/oracle/oracle/rewards/eth1.py
@@ -9,11 +9,13 @@
 
 
 async def get_registered_validators_public_keys(
+    network: str,
     block_number: BlockNumber,
 ) -> RegisteredValidatorsPublicKeys:
     """Fetches pool validators public keys."""
     last_id = ""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=REGISTERED_VALIDATORS_QUERY,
         variables=dict(block_number=block_number, last_id=last_id),
     )
@@ -24,6 +26,7 @@ async def get_registered_validators_public_keys(
     while len(validators_chunk) >= 1000:
         last_id = validators_chunk[-1]["id"]
         result: Dict = await execute_sw_gql_query(
+            network=network,
             query=REGISTERED_VALIDATORS_QUERY,
             variables=dict(block_number=block_number, last_id=last_id),
         )
diff --git a/oracle/oracle/rewards/eth2.py b/oracle/oracle/rewards/eth2.py
index 7e8dfc5..22603a7 100644
--- a/oracle/oracle/rewards/eth2.py
+++ b/oracle/oracle/rewards/eth2.py
@@ -5,13 +5,7 @@
 from aiohttp import ClientSession
 from eth_typing import HexStr
 
-from oracle.oracle.settings import (
-    ETH2_CLIENT,
-    ETH2_ENDPOINT,
-    GNOSIS,
-    LIGHTHOUSE,
-    NETWORK,
-)
+from oracle.networks import NETWORKS
 
 
 class ValidatorStatus(Enum):
@@ -30,22 +24,14 @@ class ValidatorStatus(Enum):
 
 PENDING_STATUSES = [ValidatorStatus.PENDING_INITIALIZED, ValidatorStatus.PENDING_QUEUED]
 
-if NETWORK == GNOSIS:
-    SLOTS_PER_EPOCH = 16
-    SECONDS_PER_SLOT = 5
-else:
-    SLOTS_PER_EPOCH = 32
-    SECONDS_PER_SLOT = 12
-
-SECONDS_PER_EPOCH = SECONDS_PER_SLOT * SLOTS_PER_EPOCH
-
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
 async def get_finality_checkpoints(
-    session: ClientSession, state_id: str = "head"
+    network: str, session: ClientSession, state_id: str = "head"
 ) -> Dict:
     """Fetches finality checkpoints."""
-    endpoint = f"{ETH2_ENDPOINT}/eth/v1/beacon/states/{state_id}/finality_checkpoints"
+    network_config = NETWORKS[network]
+    endpoint = f"{network_config['ETH2_ENDPOINT']}/eth/v1/beacon/states/{state_id}/finality_checkpoints"
     async with session.get(endpoint) as response:
         response.raise_for_status()
         return (await response.json())["data"]
@@ -53,17 +39,17 @@ async def get_finality_checkpoints(
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
 async def get_validators(
-    session: ClientSession, public_keys: List[HexStr], state_id: str = "head"
+    network: str,
+    session: ClientSession,
+    public_keys: List[HexStr],
+    state_id: str = "head",
 ) -> List[Dict]:
     """Fetches validators."""
     if not public_keys:
         return []
 
-    # TODO: remove once https://github.com/gnosischain/gbc-lighthouse updated to 2.1.1
-    if ETH2_CLIENT == LIGHTHOUSE:
-        endpoint = f"{ETH2_ENDPOINT}/eth/v1/beacon/states/{state_id}/validators?id={','.join(public_keys)}"
-    else:
-        endpoint = f"{ETH2_ENDPOINT}/eth/v1/beacon/states/{state_id}/validators?id={'&id='.join(public_keys)}"
+    _endpoint = NETWORKS[network]["ETH2_ENDPOINT"]
+    endpoint = f"{_endpoint}/eth/v1/beacon/states/{state_id}/validators?id={'&id='.join(public_keys)}"
 
     async with session.get(endpoint) as response:
         response.raise_for_status()
@@ -71,9 +57,10 @@ async def get_validators(
 
 
 @backoff.on_exception(backoff.expo, Exception, max_time=900)
-async def get_genesis(session: ClientSession) -> Dict:
+async def get_genesis(network: str, session: ClientSession) -> Dict:
     """Fetches beacon chain genesis."""
-    endpoint = f"{ETH2_ENDPOINT}/eth/v1/beacon/genesis"
+    network_config = NETWORKS[network]
+    endpoint = f"{network_config['ETH2_ENDPOINT']}/eth/v1/beacon/genesis"
     async with session.get(endpoint) as response:
         response.raise_for_status()
         return (await response.json())["data"]
diff --git a/oracle/oracle/settings.py b/oracle/oracle/settings.py
deleted file mode 100644
index 988970a..0000000
--- a/oracle/oracle/settings.py
+++ /dev/null
@@ -1,144 +0,0 @@
-from datetime import timedelta
-
-from decouple import Choices, Csv, config
-from eth_typing import HexStr
-from web3 import Web3
-
-from oracle.common.settings import GNOSIS, GOERLI, MAINNET, NETWORK
-
-IPFS_PIN_ENDPOINTS = config(
-    "IPFS_PIN_ENDPOINTS",
-    cast=Csv(),
-    default="/dns/ipfs.infura.io/tcp/5001/https,/dns/ipfs/tcp/5001/http",
-)
-IPFS_FETCH_ENDPOINTS = config(
-    "IPFS_FETCH_ENDPOINTS",
-    cast=Csv(),
-    default="https://gateway.pinata.cloud,http://cloudflare-ipfs.com,https://ipfs.io",
-)
-
-# extra pins to pinata for redundancy
-IPFS_PINATA_PIN_ENDPOINT = config(
-    "IPFS_PINATA_ENDPOINT", default="https://api.pinata.cloud/pinning/pinJSONToIPFS"
-)
-IPFS_PINATA_API_KEY = config("IPFS_PINATA_API_KEY", default="")
-IPFS_PINATA_SECRET_KEY = config(
-    "IPFS_PINATA_SECRET_KEY",
-    default="",
-)
-
-# ETH2 settings
-ETH2_ENDPOINT = config("ETH2_ENDPOINT", default="http://localhost:3501")
-
-# TODO: remove once https://github.com/gnosischain/gbc-lighthouse updated to 2.1.1
-LIGHTHOUSE = "lighthouse"
-PRYSM = "prysm"
-TEKU = "teku"
-ETH2_CLIENT = config(
-    "ETH2_CLIENT",
-    default=PRYSM,
-    cast=Choices([LIGHTHOUSE, PRYSM, TEKU], cast=lambda client: client.lower()),
-)
-
-# credentials
-ORACLE_PRIVATE_KEY = config("ORACLE_PRIVATE_KEY", default="")
-
-# S3 credentials
-AWS_ACCESS_KEY_ID = config("AWS_ACCESS_KEY_ID", default="")
-AWS_SECRET_ACCESS_KEY = config("AWS_SECRET_ACCESS_KEY", default="")
-
-ORACLE_PROCESS_INTERVAL = config("ORACLE_PROCESS_INTERVAL", default=10, cast=int)
-
-if NETWORK == MAINNET:
-    SYNC_PERIOD = timedelta(days=1)
-    SWISE_TOKEN_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x48C3399719B582dD63eB5AADf12A40B4C3f52FA2"
-    )
-    REWARD_TOKEN_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x20BC832ca081b91433ff6c17f85701B6e92486c5"
-    )
-    STAKED_TOKEN_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0xFe2e637202056d30016725477c5da089Ab0A043A"
-    )
-    DISTRIBUTOR_FALLBACK_ADDRESS = Web3.toChecksumAddress(
-        "0x144a98cb1CdBb23610501fE6108858D9B7D24934"
-    )
-    RARI_FUSE_POOL_ADDRESSES = [
-        Web3.toChecksumAddress("0x18F49849D20Bc04059FE9d775df9a38Cd1f5eC9F"),
-        Web3.toChecksumAddress("0x83d534Ab1d4002249B0E6d22410b62CF31978Ca2"),
-    ]
-    WITHDRAWAL_CREDENTIALS: HexStr = HexStr(
-        "0x0100000000000000000000002296e122c1a20fca3cac3371357bdad3be0df079"
-    )
-    STAKEWISE_SUBGRAPH_URL = config(
-        "STAKEWISE_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/stakewise-mainnet",
-    )
-    UNISWAP_V3_SUBGRAPH_URL = config(
-        "UNISWAP_V3_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-mainnet",
-    )
-    ETHEREUM_SUBGRAPH_URL = config(
-        "ETHEREUM_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/ethereum-mainnet",
-    )
-    RARI_FUSE_SUBGRAPH_URL = config(
-        "RARI_FUSE_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/rari-fuse-mainnet",
-    )
-    STAKED_TOKEN_SYMBOL = "ETH"
-# TODO: fix addresses once gnosis deployed
-elif NETWORK == GNOSIS:
-    SYNC_PERIOD = timedelta(days=1)
-    SWISE_TOKEN_CONTRACT_ADDRESS = ""
-    REWARD_TOKEN_CONTRACT_ADDRESS = ""
-    STAKED_TOKEN_CONTRACT_ADDRESS = ""
-    DISTRIBUTOR_FALLBACK_ADDRESS = ""
-    RARI_FUSE_POOL_ADDRESSES = []
-    WITHDRAWAL_CREDENTIALS: HexStr = HexStr("")
-    STAKEWISE_SUBGRAPH_URL = config(
-        "STAKEWISE_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/stakewise-gnosis",
-    )
-    # TODO: update once uniswap v3 is deployed to gnosis chain
-    UNISWAP_V3_SUBGRAPH_URL = config("UNISWAP_V3_SUBGRAPH_URL", default="")
-    ETHEREUM_SUBGRAPH_URL = config(
-        "ETHEREUM_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/ethereum-gnosis",
-    )
-    # TODO: update once rari fuse pools is deployed to gnosis chain
-    RARI_FUSE_SUBGRAPH_URL = config("RARI_FUSE_SUBGRAPH_URL", default="")
-    STAKED_TOKEN_SYMBOL = "mGNO"
-elif NETWORK == GOERLI:
-    SYNC_PERIOD = timedelta(hours=1)
-    SWISE_TOKEN_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x0e2497aACec2755d831E4AFDEA25B4ef1B823855"
-    )
-    REWARD_TOKEN_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x826f88d423440c305D9096cC1581Ae751eFCAfB0"
-    )
-    STAKED_TOKEN_CONTRACT_ADDRESS = Web3.toChecksumAddress(
-        "0x221D9812823DBAb0F1fB40b0D294D9875980Ac19"
-    )
-    DISTRIBUTOR_FALLBACK_ADDRESS = Web3.toChecksumAddress(
-        "0x1867c96601bc5fE24F685d112314B8F3Fe228D5A"
-    )
-    RARI_FUSE_POOL_ADDRESSES = []
-    WITHDRAWAL_CREDENTIALS: HexStr = HexStr(
-        "0x010000000000000000000000040f15c6b5bfc5f324ecab5864c38d4e1eef4218"
-    )
-    STAKEWISE_SUBGRAPH_URL = config(
-        "STAKEWISE_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/stakewise-goerli",
-    )
-    UNISWAP_V3_SUBGRAPH_URL = config(
-        "UNISWAP_V3_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/uniswap-v3-goerli",
-    )
-    ETHEREUM_SUBGRAPH_URL = config(
-        "ETHEREUM_SUBGRAPH_URL",
-        default="https://api.thegraph.com/subgraphs/name/stakewise/ethereum-goerli",
-    )
-    # TODO: update once rari fuse pools is deployed to goerli chain
-    RARI_FUSE_SUBGRAPH_URL = config("RARI_FUSE_SUBGRAPH_URL", default="")
-    STAKED_TOKEN_SYMBOL = "ETH"
diff --git a/oracle/oracle/validators/controller.py b/oracle/oracle/validators/controller.py
index 9908d1c..cbc59c4 100644
--- a/oracle/oracle/validators/controller.py
+++ b/oracle/oracle/validators/controller.py
@@ -6,9 +6,9 @@
 from web3 import Web3
 from web3.types import Wei
 
-from oracle.common.settings import VALIDATOR_VOTE_FILENAME
+from oracle.oracle.eth1 import submit_vote
+from oracle.settings import VALIDATOR_VOTE_FILENAME
 
-from ..eth1 import submit_vote
 from .eth1 import (
     get_validators_deposit_root,
     get_voting_parameters,
@@ -24,7 +24,8 @@
 class ValidatorsController(object):
     """Submits new validators registrations to the IPFS."""
 
-    def __init__(self, oracle: LocalAccount) -> None:
+    def __init__(self, network: str, oracle: LocalAccount) -> None:
+        self.network = network
         self.validator_deposit: Wei = Web3.toWei(32, "ether")
         self.last_vote_public_key = None
         self.last_vote_validators_deposit_root = None
@@ -32,24 +33,30 @@ def __init__(self, oracle: LocalAccount) -> None:
 
     async def process(self) -> None:
         """Process validators registration."""
-        voting_params = await get_voting_parameters()
+        voting_params = await get_voting_parameters(self.network)
         latest_block_number = voting_params["latest_block_number"]
         pool_balance = voting_params["pool_balance"]
         if pool_balance < self.validator_deposit:
             # not enough balance to register next validator
             return
 
-        while not (await has_synced_block(latest_block_number)):
+        while not (await has_synced_block(self.network, latest_block_number)):
             await asyncio.sleep(5)
 
         # select next validator
         # TODO: implement scoring system based on the operators performance
-        validator_deposit_data = await select_validator(latest_block_number)
+        validator_deposit_data = await select_validator(
+            self.network, latest_block_number
+        )
         if validator_deposit_data is None:
-            logger.warning("Failed to find the next validator to register")
+            logger.warning(
+                f"[{self.network}] Failed to find the next validator to register"
+            )
             return
 
-        validators_deposit_root = await get_validators_deposit_root(latest_block_number)
+        validators_deposit_root = await get_validators_deposit_root(
+            self.network, latest_block_number
+        )
         public_key = validator_deposit_data["public_key"]
         if (
             self.last_vote_validators_deposit_root == validators_deposit_root
@@ -72,16 +79,17 @@ async def process(self) -> None:
             **validator_deposit_data,
         )
         logger.info(
-            f"Voting for the next validator: operator={operator}, public key={public_key}"
+            f"[{self.network}] Voting for the next validator: operator={operator}, public key={public_key}"
         )
 
         submit_vote(
+            network=self.network,
             oracle=self.oracle,
             encoded_data=encoded_data,
             vote=vote,
             name=VALIDATOR_VOTE_FILENAME,
         )
-        logger.info("Submitted validator registration vote")
+        logger.info(f"[{self.network}] Submitted validator registration vote")
 
         # skip voting for the same validator and validators deposit root in the next check
         self.last_vote_public_key = public_key
diff --git a/oracle/oracle/validators/eth1.py b/oracle/oracle/validators/eth1.py
index 2d4fbfd..64206b2 100644
--- a/oracle/oracle/validators/eth1.py
+++ b/oracle/oracle/validators/eth1.py
@@ -20,9 +20,10 @@
 from .types import ValidatorDepositData, ValidatorVotingParameters
 
 
-async def get_voting_parameters() -> ValidatorVotingParameters:
+async def get_voting_parameters(network: str) -> ValidatorVotingParameters:
     """Fetches validator voting parameters."""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=VALIDATOR_VOTING_PARAMETERS_QUERY,
         variables={},
     )
@@ -37,10 +38,12 @@ async def get_voting_parameters() -> ValidatorVotingParameters:
 
 
 async def select_validator(
+    network: str,
     block_number: BlockNumber,
 ) -> Union[None, ValidatorDepositData]:
     """Selects the next validator to register."""
     result: Dict = await execute_sw_gql_query(
+        network=network,
         query=OPERATORS_QUERY,
         variables=dict(block_number=block_number),
     )
@@ -60,7 +63,7 @@ async def select_validator(
 
         selected_deposit_data = deposit_datum[deposit_data_index]
         can_register = await can_register_validator(
-            block_number, selected_deposit_data["public_key"]
+            network, block_number, selected_deposit_data["public_key"]
         )
         while deposit_data_index < max_deposit_data_index and not can_register:
             # the edge case when the validator was registered in previous merkle root
@@ -68,7 +71,7 @@ async def select_validator(
             deposit_data_index += 1
             selected_deposit_data = deposit_datum[deposit_data_index]
             can_register = await can_register_validator(
-                block_number, selected_deposit_data["public_key"]
+                network, block_number, selected_deposit_data["public_key"]
             )
 
         if can_register:
@@ -82,9 +85,12 @@ async def select_validator(
             )
 
 
-async def can_register_validator(block_number: BlockNumber, public_key: HexStr) -> bool:
+async def can_register_validator(
+    network: str, block_number: BlockNumber, public_key: HexStr
+) -> bool:
     """Checks whether it's safe to register the validator."""
     result: Dict = await execute_ethereum_gql_query(
+        network=network,
         query=VALIDATOR_REGISTRATIONS_QUERY,
         variables=dict(block_number=block_number, public_key=public_key),
     )
@@ -93,8 +99,9 @@ async def can_register_validator(block_number: BlockNumber, public_key: HexStr)
     return len(registrations) == 0
 
 
-async def has_synced_block(block_number: BlockNumber) -> bool:
+async def has_synced_block(network: str, block_number: BlockNumber) -> bool:
     result: Dict = await execute_ethereum_gql_query(
+        network=network,
         query=VALIDATOR_REGISTRATIONS_SYNC_BLOCK_QUERY,
         variables={},
     )
@@ -103,9 +110,12 @@ async def has_synced_block(block_number: BlockNumber) -> bool:
     return block_number <= BlockNumber(int(meta["block"]["number"]))
 
 
-async def get_validators_deposit_root(block_number: BlockNumber) -> HexStr:
+async def get_validators_deposit_root(
+    network: str, block_number: BlockNumber
+) -> HexStr:
     """Fetches validators deposit root for protecting against operator submitting deposit prior to registration."""
     result: Dict = await execute_ethereum_gql_query(
+        network=network,
         query=VALIDATOR_REGISTRATIONS_LATEST_INDEX_QUERY,
         variables=dict(block_number=block_number),
     )
diff --git a/oracle/settings.py b/oracle/settings.py
new file mode 100644
index 0000000..f4e8a91
--- /dev/null
+++ b/oracle/settings.py
@@ -0,0 +1,54 @@
+from decouple import Csv, config
+
+from oracle.networks import ETHEREUM_MAINNET
+
+# common
+LOG_LEVEL = config("LOG_LEVEL", default="INFO")
+
+ENABLED_NETWORKS = config(
+    "ENABLED_NETWORKS",
+    default=ETHEREUM_MAINNET,
+    cast=Csv(),
+)
+
+REWARD_VOTE_FILENAME = "reward-vote.json"
+DISTRIBUTOR_VOTE_FILENAME = "distributor-vote.json"
+VALIDATOR_VOTE_FILENAME = "validator-vote.json"
+TEST_VOTE_FILENAME = "test-vote.json"
+
+# health server settings
+ENABLE_HEALTH_SERVER = config("ENABLE_HEALTH_SERVER", default=False, cast=bool)
+HEALTH_SERVER_PORT = config("HEALTH_SERVER_PORT", default=8080, cast=int)
+HEALTH_SERVER_HOST = config("HEALTH_SERVER_HOST", default="127.0.0.1", cast=str)
+
+# required confirmation blocks
+CONFIRMATION_BLOCKS: int = config("CONFIRMATION_BLOCKS", default=15, cast=int)
+
+# oracle
+ORACLE_PROCESS_INTERVAL = config("ORACLE_PROCESS_INTERVAL", default=10, cast=int)
+
+IPFS_PIN_ENDPOINTS = config(
+    "IPFS_PIN_ENDPOINTS",
+    cast=Csv(),
+    default="/dns/ipfs.infura.io/tcp/5001/https,/dns/ipfs/tcp/5001/http",
+)
+IPFS_FETCH_ENDPOINTS = config(
+    "IPFS_FETCH_ENDPOINTS",
+    cast=Csv(),
+    default="https://gateway.pinata.cloud,http://cloudflare-ipfs.com,https://ipfs.io",
+)
+
+# extra pins to pinata for redundancy
+IPFS_PINATA_PIN_ENDPOINT = config(
+    "IPFS_PINATA_ENDPOINT", default="https://api.pinata.cloud/pinning/pinJSONToIPFS"
+)
+IPFS_PINATA_API_KEY = config("IPFS_PINATA_API_KEY", default="")
+IPFS_PINATA_SECRET_KEY = config(
+    "IPFS_PINATA_SECRET_KEY",
+    default="",
+)
+
+# keeper
+KEEPER_PROCESS_INTERVAL = config("KEEPER_PROCESS_INTERVAL", default=10, cast=int)
+
+TRANSACTION_TIMEOUT = config("TRANSACTION_TIMEOUT", default=900, cast=int)
diff --git a/oracle/utils.py b/oracle/utils.py
new file mode 100644
index 0000000..501bd63
--- /dev/null
+++ b/oracle/utils.py
@@ -0,0 +1,63 @@
+import logging
+import signal
+from typing import Any, Dict, List
+
+from eth_account import Account
+from eth_account.signers.local import LocalAccount
+
+from oracle.networks import NETWORKS
+from oracle.oracle.clients import execute_sw_gql_query
+from oracle.oracle.graphql_queries import ORACLE_QUERY
+from oracle.settings import ENABLED_NETWORKS
+
+logger = logging.getLogger(__name__)
+
+
+class InterruptHandler:
+    """
+    Tracks SIGINT and SIGTERM signals.
+    https://stackoverflow.com/a/31464349
+    """
+
+    exit = False
+
+    def __init__(self) -> None:
+        signal.signal(signal.SIGINT, self.exit_gracefully)
+        signal.signal(signal.SIGTERM, self.exit_gracefully)
+
+    # noinspection PyUnusedLocal
+    def exit_gracefully(self, signum: int, frame: Any) -> None:
+        logger.info(f"Received interrupt signal {signum}, exiting...")
+        self.exit = True
+
+
+async def check_oracle_account(network: str, oracle: LocalAccount) -> None:
+    """Checks whether oracle is part of the oracles set."""
+    oracle_lowered_address = oracle.address.lower()
+    result: List = (
+        await execute_sw_gql_query(
+            network=network,
+            query=ORACLE_QUERY,
+            variables=dict(
+                oracle_address=oracle_lowered_address,
+            ),
+        )
+    ).get("oracles", [])
+    if result and result[0].get("id", "") == oracle_lowered_address:
+        logger.info(f"[{network}] Oracle {oracle.address} is part of the oracles set")
+    else:
+        logger.warning(
+            f"[{network}] NB! Oracle {oracle.address} is not part of the oracles set."
+            f" Please create DAO proposal to include it."
+        )
+
+
+async def get_oracle_accounts() -> Dict[str, LocalAccount]:
+    """Create oracle and verify oracle accounts."""
+    oracle_accounts = {}
+    for network in ENABLED_NETWORKS:
+        oracle = Account.from_key(NETWORKS[network]["ORACLE_PRIVATE_KEY"])
+        oracle_accounts[network] = oracle
+        await check_oracle_account(network, oracle)
+
+    return oracle_accounts