From 4501bbccdc8c99532372698d4f48bc66f7de2843 Mon Sep 17 00:00:00 2001
From: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Date: Thu, 22 Oct 2020 22:39:51 +0200
Subject: [PATCH] rpc, evm: refactor (#588)

* stargate: refactor

* remove evm CLI

* rpc: refactor

* more fixes

* fixes fixes fixes

* changelog

* refactor according to namespaces

* fix

* lint

* remove export logic

* fix rpc test

* godoc
---
 CHANGELOG.md                                  |    8 +
 cmd/ethermintcli/main.go                      |    2 +-
 rpc/apis.go                                   |   37 +-
 rpc/args/send_tx.go                           |   22 -
 rpc/backend.go                                |  328 -----
 rpc/backend/backend.go                        |  259 ++++
 rpc/cmd.go                                    |   18 +
 rpc/config.go                                 |   43 +-
 rpc/doc.go                                    |   10 +
 rpc/eth_api.go                                | 1070 -----------------
 rpc/namespaces/eth/api.go                     |  890 ++++++++++++++
 .../eth/filters/api.go}                       |   59 +-
 .../eth/filters}/filter_system.go             |   62 +-
 rpc/{ => namespaces/eth/filters}/filters.go   |   96 +-
 rpc/namespaces/eth/filters/subscription.go    |   71 ++
 rpc/namespaces/eth/filters/utils.go           |  102 ++
 rpc/{net_api.go => namespaces/net/api.go}     |   16 +-
 .../personal/api.go}                          |  143 +--
 rpc/{web3_api.go => namespaces/web3/api.go}   |   10 +-
 rpc/{ => types}/addrlock.go                   |    2 +-
 rpc/{types.go => types/block.go}              |   15 +-
 rpc/types/types.go                            |   85 ++
 rpc/types/utils.go                            |  191 +++
 rpc/websockets.go                             |  542 ---------
 rpc/websockets/pubsub_api.go                  |  309 +++++
 rpc/websockets/server.go                      |  219 ++++
 rpc/websockets/types.go                       |   45 +
 tests/rpc_test.go                             |   77 +-
 x/evm/client/cli/query.go                     |   12 +-
 x/evm/client/cli/tx.go                        |  164 ---
 x/evm/client/cli/utils.go                     |   16 -
 x/evm/client/cli/utils_test.go                |   21 -
 x/evm/module.go                               |    3 +-
 x/faucet/client/cli/query.go                  |    8 +-
 x/faucet/client/cli/tx.go                     |    8 +-
 x/faucet/client/rest/tx.go                    |   20 +-
 36 files changed, 2396 insertions(+), 2587 deletions(-)
 delete mode 100644 rpc/args/send_tx.go
 delete mode 100644 rpc/backend.go
 create mode 100644 rpc/backend/backend.go
 create mode 100644 rpc/cmd.go
 create mode 100644 rpc/doc.go
 delete mode 100644 rpc/eth_api.go
 create mode 100644 rpc/namespaces/eth/api.go
 rename rpc/{filter_api.go => namespaces/eth/filters/api.go} (90%)
 rename rpc/{ => namespaces/eth/filters}/filter_system.go (88%)
 rename rpc/{ => namespaces/eth/filters}/filters.go (63%)
 create mode 100644 rpc/namespaces/eth/filters/subscription.go
 create mode 100644 rpc/namespaces/eth/filters/utils.go
 rename rpc/{net_api.go => namespaces/net/api.go} (53%)
 rename rpc/{personal_api.go => namespaces/personal/api.go} (56%)
 rename rpc/{web3_api.go => namespaces/web3/api.go} (68%)
 rename rpc/{ => types}/addrlock.go (98%)
 rename rpc/{types.go => types/block.go} (82%)
 create mode 100644 rpc/types/types.go
 create mode 100644 rpc/types/utils.go
 delete mode 100644 rpc/websockets.go
 create mode 100644 rpc/websockets/pubsub_api.go
 create mode 100644 rpc/websockets/server.go
 create mode 100644 rpc/websockets/types.go
 delete mode 100644 x/evm/client/cli/tx.go

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53f55c2c..dcd70e29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,14 @@ Ref: https://keepachangelog.com/en/1.0.0/
 * (crypto) [\#559](https://github.com/cosmos/ethermint/pull/559) Refactored crypto package in preparation for the SDK's Stargate release:
   * `crypto.PubKeySecp256k1` and `crypto.PrivKeySecp256k1` are now `ethsecp256k1.PubKey` and `ethsecp256k1.PrivKey`, respectively
   * Moved SDK `SigningAlgo` implementation for Ethermint's Secp256k1 key to `crypto/hd` package.
+* (rpc) [\#588](https://github.com/cosmos/ethermint/pull/588) The `rpc` package has been refactored to account for the separation of each
+corresponding Ethereum API namespace:
+  * `rpc/namespaces/eth`: `eth` namespace. Exposes the `PublicEthereumAPI` and the `PublicFilterAPI`.
+  * `rpc/namespaces/personal`: `personal` namespace. Exposes the `PrivateAccountAPI`.
+  * `rpc/namespaces/net`: `net` namespace. Exposes the `PublicNetAPI`.
+  * `rpc/namespaces/web3`: `web3` namespace. Exposes the `PublicWeb3API`.
+
+* (evm) [\#588](https://github.com/cosmos/ethermint/pull/588) The EVM transaction CLI has been removed in favor of the JSON-RPC.
 
 ### Bug Fixes
 
diff --git a/cmd/ethermintcli/main.go b/cmd/ethermintcli/main.go
index cded7573..8a92cee4 100644
--- a/cmd/ethermintcli/main.go
+++ b/cmd/ethermintcli/main.go
@@ -67,7 +67,7 @@ func main() {
 		queryCmd(cdc),
 		txCmd(cdc),
 		client.ValidateChainID(
-			rpc.EmintServeCmd(cdc),
+			rpc.ServeCmd(cdc),
 		),
 		flags.LineBreak,
 		client.KeyCommands(),
diff --git a/rpc/apis.go b/rpc/apis.go
index 37884428..b38dcaa4 100644
--- a/rpc/apis.go
+++ b/rpc/apis.go
@@ -1,5 +1,3 @@
-// Package rpc contains RPC handler methods and utilities to start
-// Ethermint's Web3-compatibly JSON-RPC server.
 package rpc
 
 import (
@@ -8,6 +6,13 @@ import (
 	"github.com/cosmos/cosmos-sdk/client/context"
 
 	"github.com/cosmos/ethermint/crypto/ethsecp256k1"
+	"github.com/cosmos/ethermint/rpc/backend"
+	"github.com/cosmos/ethermint/rpc/namespaces/eth"
+	"github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
+	"github.com/cosmos/ethermint/rpc/namespaces/net"
+	"github.com/cosmos/ethermint/rpc/namespaces/personal"
+	"github.com/cosmos/ethermint/rpc/namespaces/web3"
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
 )
 
 // RPC namespaces and API version
@@ -20,17 +25,17 @@ const (
 	apiVersion = "1.0"
 )
 
-// GetRPCAPIs returns the list of all APIs
-func GetRPCAPIs(cliCtx context.CLIContext, keys []ethsecp256k1.PrivKey) []rpc.API {
-	nonceLock := new(AddrLocker)
-	backend := NewEthermintBackend(cliCtx)
-	ethAPI := NewPublicEthAPI(cliCtx, backend, nonceLock, keys)
+// GetAPIs returns the list of all APIs from the Ethereum namespaces
+func GetAPIs(clientCtx context.CLIContext, keys ...ethsecp256k1.PrivKey) []rpc.API {
+	nonceLock := new(rpctypes.AddrLocker)
+	backend := backend.New(clientCtx)
+	ethAPI := eth.NewAPI(clientCtx, backend, nonceLock, keys...)
 
 	return []rpc.API{
 		{
 			Namespace: Web3Namespace,
 			Version:   apiVersion,
-			Service:   NewPublicWeb3API(),
+			Service:   web3.NewAPI(),
 			Public:    true,
 		},
 		{
@@ -39,22 +44,22 @@ func GetRPCAPIs(cliCtx context.CLIContext, keys []ethsecp256k1.PrivKey) []rpc.AP
 			Service:   ethAPI,
 			Public:    true,
 		},
-		{
-			Namespace: PersonalNamespace,
-			Version:   apiVersion,
-			Service:   NewPersonalEthAPI(ethAPI),
-			Public:    false,
-		},
 		{
 			Namespace: EthNamespace,
 			Version:   apiVersion,
-			Service:   NewPublicFilterAPI(cliCtx, backend),
+			Service:   filters.NewAPI(clientCtx, backend),
 			Public:    true,
 		},
+		{
+			Namespace: PersonalNamespace,
+			Version:   apiVersion,
+			Service:   personal.NewAPI(ethAPI),
+			Public:    false,
+		},
 		{
 			Namespace: NetNamespace,
 			Version:   apiVersion,
-			Service:   NewPublicNetAPI(cliCtx),
+			Service:   net.NewAPI(clientCtx),
 			Public:    true,
 		},
 	}
diff --git a/rpc/args/send_tx.go b/rpc/args/send_tx.go
deleted file mode 100644
index 7515dbb5..00000000
--- a/rpc/args/send_tx.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package args
-
-import (
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
-// Duplicate struct definition since geth struct is in internal package
-// Ref: https://github.com/ethereum/go-ethereum/blob/release/1.9/internal/ethapi/api.go#L1346
-type SendTxArgs struct {
-	From     common.Address  `json:"from"`
-	To       *common.Address `json:"to"`
-	Gas      *hexutil.Uint64 `json:"gas"`
-	GasPrice *hexutil.Big    `json:"gasPrice"`
-	Value    *hexutil.Big    `json:"value"`
-	Nonce    *hexutil.Uint64 `json:"nonce"`
-	// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
-	// newer name and should be preferred by clients.
-	Data  *hexutil.Bytes `json:"data"`
-	Input *hexutil.Bytes `json:"input"`
-}
diff --git a/rpc/backend.go b/rpc/backend.go
deleted file mode 100644
index 1c5800a1..00000000
--- a/rpc/backend.go
+++ /dev/null
@@ -1,328 +0,0 @@
-package rpc
-
-import (
-	"fmt"
-	"math/big"
-	"os"
-	"strconv"
-
-	"github.com/tendermint/tendermint/libs/log"
-	tmtypes "github.com/tendermint/tendermint/types"
-
-	evmtypes "github.com/cosmos/ethermint/x/evm/types"
-
-	"github.com/cosmos/cosmos-sdk/client/context"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-	ethtypes "github.com/ethereum/go-ethereum/core/types"
-)
-
-// Backend implements the functionality needed to filter changes.
-// Implemented by EthermintBackend.
-type Backend interface {
-	// Used by block filter; also used for polling
-	BlockNumber() (hexutil.Uint64, error)
-	HeaderByNumber(blockNum BlockNumber) (*ethtypes.Header, error)
-	HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
-	GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error)
-	GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
-	getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error)
-	getGasLimit() (int64, error)
-	// returns the logs of a given block
-	GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
-
-	// Used by pending transaction filter
-	PendingTransactions() ([]*Transaction, error)
-
-	// Used by log filter
-	GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
-	BloomStatus() (uint64, uint64)
-}
-
-var _ Backend = (*EthermintBackend)(nil)
-
-// EthermintBackend implements the Backend interface
-type EthermintBackend struct {
-	cliCtx   context.CLIContext
-	logger   log.Logger
-	gasLimit int64
-}
-
-// NewEthermintBackend creates a new EthermintBackend instance
-func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend {
-	return &EthermintBackend{
-		cliCtx:   cliCtx,
-		logger:   log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc"),
-		gasLimit: int64(^uint32(0)),
-	}
-}
-
-// BlockNumber returns the current block number.
-func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
-	res, height, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", evmtypes.ModuleName), nil)
-	if err != nil {
-		return hexutil.Uint64(0), err
-	}
-
-	var out evmtypes.QueryResBlockNumber
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
-
-	e.cliCtx.WithHeight(height)
-	return hexutil.Uint64(out.Number), nil
-}
-
-// GetBlockByNumber returns the block identified by number.
-func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) {
-	value := blockNum.Int64()
-	return e.getEthBlockByNumber(value, fullTx)
-}
-
-// GetBlockByHash returns the block identified by hash.
-func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
-	res, height, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
-	if err != nil {
-		return nil, err
-	}
-
-	var out evmtypes.QueryResBlockNumber
-	if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
-		return nil, err
-	}
-
-	e.cliCtx = e.cliCtx.WithHeight(height)
-	return e.getEthBlockByNumber(out.Number, fullTx)
-}
-
-// HeaderByNumber returns the block header identified by height.
-func (e *EthermintBackend) HeaderByNumber(blockNum BlockNumber) (*ethtypes.Header, error) {
-	return e.getBlockHeader(blockNum.Int64())
-}
-
-// HeaderByHash returns the block header identified by hash.
-func (e *EthermintBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
-	res, height, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
-	if err != nil {
-		return nil, err
-	}
-	var out evmtypes.QueryResBlockNumber
-	if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
-		return nil, err
-	}
-
-	e.cliCtx = e.cliCtx.WithHeight(height)
-	return e.getBlockHeader(out.Number)
-}
-
-func (e *EthermintBackend) getBlockHeader(height int64) (*ethtypes.Header, error) {
-	if height <= 0 {
-		// get latest block height
-		num, err := e.BlockNumber()
-		if err != nil {
-			return nil, err
-		}
-
-		height = int64(num)
-	}
-
-	block, err := e.cliCtx.Client.Block(&height)
-	if err != nil {
-		return nil, err
-	}
-
-	res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryBloom, strconv.FormatInt(height, 10)))
-	if err != nil {
-		return nil, err
-	}
-
-	var bloomRes evmtypes.QueryBloomFilter
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
-
-	ethHeader := EthHeaderFromTendermint(block.Block.Header)
-	ethHeader.Bloom = bloomRes.Bloom
-
-	return ethHeader, nil
-}
-
-func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) {
-	// Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014)
-	var blkNumPtr *int64
-	if height != 0 {
-		blkNumPtr = &height
-	}
-
-	block, err := e.cliCtx.Client.Block(blkNumPtr)
-	if err != nil {
-		return nil, err
-	}
-	header := block.Block.Header
-
-	gasLimit, err := e.getGasLimit()
-	if err != nil {
-		return nil, err
-	}
-
-	var (
-		gasUsed      *big.Int
-		transactions []common.Hash
-	)
-
-	if fullTx {
-		// Populate full transaction data
-		transactions, gasUsed, err = convertTransactionsToRPC(
-			e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height),
-		)
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		// TODO: Gas used not saved and cannot be calculated by hashes
-		// Return slice of transaction hashes
-		transactions = make([]common.Hash, len(block.Block.Txs))
-		for i, tx := range block.Block.Txs {
-			transactions[i] = common.BytesToHash(tx.Hash())
-		}
-	}
-
-	res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryBloom, strconv.FormatInt(block.Block.Height, 10)))
-	if err != nil {
-		return nil, err
-	}
-
-	var out evmtypes.QueryBloomFilter
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
-	return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil
-}
-
-// getGasLimit returns the gas limit per block set in genesis
-func (e *EthermintBackend) getGasLimit() (int64, error) {
-	// Retrieve from gasLimit variable cache
-	if e.gasLimit != -1 {
-		return e.gasLimit, nil
-	}
-
-	// Query genesis block if hasn't been retrieved yet
-	genesis, err := e.cliCtx.Client.Genesis()
-	if err != nil {
-		return 0, err
-	}
-
-	// Save value to gasLimit cached value
-	gasLimit := genesis.Genesis.ConsensusParams.Block.MaxGas
-	if gasLimit == -1 {
-		// Sets gas limit to max uint32 to not error with javascript dev tooling
-		// This -1 value indicating no block gas limit is set to max uint64 with geth hexutils
-		// which errors certain javascript dev tooling which only supports up to 53 bits
-		gasLimit = int64(^uint32(0))
-	}
-	e.gasLimit = gasLimit
-	return gasLimit, nil
-}
-
-// GetTransactionLogs returns the logs given a transaction hash.
-// It returns an error if there's an encoding error.
-// If no logs are found for the tx hash, the error is nil.
-func (e *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
-	ctx := e.cliCtx
-
-	res, height, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, txHash.Hex()), nil)
-	if err != nil {
-		return nil, err
-	}
-
-	out := new(evmtypes.QueryETHLogs)
-	if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
-		return nil, err
-	}
-
-	e.cliCtx = e.cliCtx.WithHeight(height)
-	return out.Logs, nil
-}
-
-// PendingTransactions returns the transactions that are in the transaction pool
-// and have a from address that is one of the accounts this node manages.
-func (e *EthermintBackend) PendingTransactions() ([]*Transaction, error) {
-	pendingTxs, err := e.cliCtx.Client.UnconfirmedTxs(100)
-	if err != nil {
-		return nil, err
-	}
-
-	transactions := make([]*Transaction, pendingTxs.Count)
-	for _, tx := range pendingTxs.Txs {
-		ethTx, err := bytesToEthTx(e.cliCtx, tx)
-		if err != nil {
-			return nil, err
-		}
-
-		// * Should check signer and reference against accounts the node manages in future
-		rpcTx, err := newRPCTransaction(*ethTx, common.BytesToHash(tx.Hash()), common.Hash{}, nil, 0)
-		if err != nil {
-			return nil, err
-		}
-
-		transactions = append(transactions, rpcTx)
-	}
-
-	return transactions, nil
-}
-
-// GetLogs returns all the logs from all the ethreum transactions in a block.
-func (e *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) {
-	res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
-	if err != nil {
-		return nil, err
-	}
-
-	var out evmtypes.QueryResBlockNumber
-	if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
-		return nil, err
-	}
-
-	block, err := e.cliCtx.Client.Block(&out.Number)
-	if err != nil {
-		return nil, err
-	}
-
-	var blockLogs = [][]*ethtypes.Log{}
-	for _, tx := range block.Block.Txs {
-		// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
-		res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, common.BytesToHash(tx.Hash()).Hex()), nil)
-		if err != nil {
-			continue
-		}
-
-		out := new(evmtypes.QueryETHLogs)
-		if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
-			return nil, err
-		}
-
-		blockLogs = append(blockLogs, out.Logs)
-	}
-
-	return blockLogs, nil
-}
-
-// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
-// by the chain indexer.
-func (e *EthermintBackend) BloomStatus() (uint64, uint64) {
-	return 4096, 0
-}
-
-// EthHeaderFromTendermint is an util function that returns an Ethereum Header
-// from a tendermint Header.
-func EthHeaderFromTendermint(header tmtypes.Header) *ethtypes.Header {
-	return &ethtypes.Header{
-		ParentHash:  common.BytesToHash(header.LastBlockID.Hash.Bytes()),
-		UncleHash:   common.Hash{},
-		Coinbase:    common.Address{},
-		Root:        common.BytesToHash(header.AppHash),
-		TxHash:      common.BytesToHash(header.DataHash),
-		ReceiptHash: common.Hash{},
-		Difficulty:  nil,
-		Number:      big.NewInt(header.Height),
-		Time:        uint64(header.Time.Unix()),
-		Extra:       nil,
-		MixDigest:   common.Hash{},
-		Nonce:       ethtypes.BlockNonce{},
-	}
-}
diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go
new file mode 100644
index 00000000..99d71174
--- /dev/null
+++ b/rpc/backend/backend.go
@@ -0,0 +1,259 @@
+package backend
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/tendermint/tendermint/libs/log"
+
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
+	evmtypes "github.com/cosmos/ethermint/x/evm/types"
+
+	clientcontext "github.com/cosmos/cosmos-sdk/client/context"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	ethtypes "github.com/ethereum/go-ethereum/core/types"
+)
+
+// Backend implements the functionality needed to filter changes.
+// Implemented by EthermintBackend.
+type Backend interface {
+	// Used by block filter; also used for polling
+	BlockNumber() (hexutil.Uint64, error)
+	HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error)
+	HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
+	GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
+	GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
+
+	// returns the logs of a given block
+	GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
+
+	// Used by pending transaction filter
+	PendingTransactions() ([]*rpctypes.Transaction, error)
+
+	// Used by log filter
+	GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
+	BloomStatus() (uint64, uint64)
+}
+
+var _ Backend = (*EthermintBackend)(nil)
+
+// EthermintBackend implements the Backend interface
+type EthermintBackend struct {
+	ctx       context.Context
+	clientCtx clientcontext.CLIContext
+	logger    log.Logger
+	gasLimit  int64
+}
+
+// New creates a new EthermintBackend instance
+func New(clientCtx clientcontext.CLIContext) *EthermintBackend {
+	return &EthermintBackend{
+		ctx:       context.Background(),
+		clientCtx: clientCtx,
+		logger:    log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc"),
+		gasLimit:  int64(^uint32(0)),
+	}
+}
+
+// BlockNumber returns the current block number.
+func (b *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
+	// NOTE: using 0 as min and max height returns the blockchain info up to the latest block.
+	info, err := b.clientCtx.Client.BlockchainInfo(0, 0)
+	if err != nil {
+		return hexutil.Uint64(0), err
+	}
+
+	return hexutil.Uint64(info.LastHeight), nil
+}
+
+// GetBlockByNumber returns the block identified by number.
+func (b *EthermintBackend) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
+	height := blockNum.Int64()
+	if height <= 0 {
+		// get latest block height
+		num, err := b.BlockNumber()
+		if err != nil {
+			return nil, err
+		}
+
+		height = int64(num)
+	}
+
+	resBlock, err := b.clientCtx.Client.Block(&height)
+	if err != nil {
+		return nil, err
+	}
+
+	return rpctypes.EthBlockFromTendermint(b.clientCtx, resBlock.Block)
+}
+
+// GetBlockByHash returns the block identified by hash.
+func (b *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
+	res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
+	if err != nil {
+		return nil, err
+	}
+
+	var out evmtypes.QueryResBlockNumber
+	if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
+		return nil, err
+	}
+
+	resBlock, err := b.clientCtx.Client.Block(&out.Number)
+	if err != nil {
+		return nil, err
+	}
+
+	return rpctypes.EthBlockFromTendermint(b.clientCtx, resBlock.Block)
+}
+
+// HeaderByNumber returns the block header identified by height.
+func (b *EthermintBackend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error) {
+	height := blockNum.Int64()
+	if height <= 0 {
+		// get latest block height
+		num, err := b.BlockNumber()
+		if err != nil {
+			return nil, err
+		}
+
+		height = int64(num)
+	}
+
+	resBlock, err := b.clientCtx.Client.Block(&height)
+	if err != nil {
+		return nil, err
+	}
+
+	res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%d", evmtypes.ModuleName, evmtypes.QueryBloom, resBlock.Block.Height))
+	if err != nil {
+		return nil, err
+	}
+
+	var bloomRes evmtypes.QueryBloomFilter
+	b.clientCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
+
+	ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header)
+	ethHeader.Bloom = bloomRes.Bloom
+	return ethHeader, nil
+}
+
+// HeaderByHash returns the block header identified by hash.
+func (b *EthermintBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
+	res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
+	if err != nil {
+		return nil, err
+	}
+
+	var out evmtypes.QueryResBlockNumber
+	if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
+		return nil, err
+	}
+
+	resBlock, err := b.clientCtx.Client.Block(&out.Number)
+	if err != nil {
+		return nil, err
+	}
+
+	res, _, err = b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%d", evmtypes.ModuleName, evmtypes.QueryBloom, resBlock.Block.Height))
+	if err != nil {
+		return nil, err
+	}
+
+	var bloomRes evmtypes.QueryBloomFilter
+	b.clientCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
+
+	ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header)
+	ethHeader.Bloom = bloomRes.Bloom
+	return ethHeader, nil
+}
+
+// GetTransactionLogs returns the logs given a transaction hash.
+// It returns an error if there's an encoding error.
+// If no logs are found for the tx hash, the error is nil.
+func (b *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
+	res, _, err := b.clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, txHash.String()), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	out := new(evmtypes.QueryETHLogs)
+	if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
+		return nil, err
+	}
+
+	return out.Logs, nil
+}
+
+// PendingTransactions returns the transactions that are in the transaction pool
+// and have a from address that is one of the accounts this node manages.
+func (b *EthermintBackend) PendingTransactions() ([]*rpctypes.Transaction, error) {
+	pendingTxs, err := b.clientCtx.Client.UnconfirmedTxs(1000)
+	if err != nil {
+		return nil, err
+	}
+
+	transactions := make([]*rpctypes.Transaction, pendingTxs.Count)
+	for _, tx := range pendingTxs.Txs {
+		ethTx, err := rpctypes.RawTxToEthTx(b.clientCtx, tx)
+		if err != nil {
+			// ignore non Ethermint EVM transactions
+			continue
+		}
+
+		// TODO: check signer and reference against accounts the node manages
+		rpcTx, err := rpctypes.NewTransaction(ethTx, common.BytesToHash(tx.Hash()), common.Hash{}, 0, 0)
+		if err != nil {
+			return nil, err
+		}
+
+		transactions = append(transactions, rpcTx)
+	}
+
+	return transactions, nil
+}
+
+// GetLogs returns all the logs from all the ethereum transactions in a block.
+func (b *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) {
+	res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
+	if err != nil {
+		return nil, err
+	}
+
+	var out evmtypes.QueryResBlockNumber
+	if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
+		return nil, err
+	}
+
+	block, err := b.clientCtx.Client.Block(&out.Number)
+	if err != nil {
+		return nil, err
+	}
+
+	var blockLogs = [][]*ethtypes.Log{}
+	for _, tx := range block.Block.Txs {
+		// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
+		res, _, err := b.clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, common.BytesToHash(tx.Hash()).String()), nil)
+		if err != nil {
+			continue
+		}
+
+		out := new(evmtypes.QueryETHLogs)
+		if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
+			return nil, err
+		}
+
+		blockLogs = append(blockLogs, out.Logs)
+	}
+
+	return blockLogs, nil
+}
+
+// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
+// by the chain indexer.
+func (b *EthermintBackend) BloomStatus() (uint64, uint64) {
+	return 4096, 0
+}
diff --git a/rpc/cmd.go b/rpc/cmd.go
new file mode 100644
index 00000000..bfd4663b
--- /dev/null
+++ b/rpc/cmd.go
@@ -0,0 +1,18 @@
+package rpc
+
+import (
+	"github.com/cosmos/cosmos-sdk/client/flags"
+	"github.com/cosmos/cosmos-sdk/client/lcd"
+	"github.com/cosmos/cosmos-sdk/codec"
+	"github.com/spf13/cobra"
+)
+
+// ServeCmd creates a CLI command to start Cosmos REST server with web3 RPC API and
+// Cosmos rest-server endpoints
+func ServeCmd(cdc *codec.Codec) *cobra.Command {
+	cmd := lcd.ServeCommand(cdc, RegisterRoutes)
+	cmd.Flags().String(flagUnlockKey, "", "Select a key to unlock on the RPC server")
+	cmd.Flags().String(flagWebsocket, "8546", "websocket port to listen to")
+	cmd.Flags().StringP(flags.FlagBroadcastMode, "b", flags.BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
+	return cmd
+}
diff --git a/rpc/config.go b/rpc/config.go
index 7133c15d..12125449 100644
--- a/rpc/config.go
+++ b/rpc/config.go
@@ -6,14 +6,12 @@ import (
 	"os"
 	"strings"
 
-	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 
 	"github.com/cosmos/cosmos-sdk/client"
 	"github.com/cosmos/cosmos-sdk/client/flags"
 	"github.com/cosmos/cosmos-sdk/client/input"
 	"github.com/cosmos/cosmos-sdk/client/lcd"
-	"github.com/cosmos/cosmos-sdk/codec"
 	"github.com/cosmos/cosmos-sdk/crypto/keys"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
@@ -21,6 +19,7 @@ import (
 	"github.com/cosmos/ethermint/app"
 	"github.com/cosmos/ethermint/crypto/ethsecp256k1"
 	"github.com/cosmos/ethermint/crypto/hd"
+	"github.com/cosmos/ethermint/rpc/websockets"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
@@ -29,20 +28,10 @@ const (
 	flagWebsocket = "wsport"
 )
 
-// EmintServeCmd creates a CLI command to start Cosmos REST server with web3 RPC API and
-// Cosmos rest-server endpoints
-func EmintServeCmd(cdc *codec.Codec) *cobra.Command {
-	cmd := lcd.ServeCommand(cdc, registerRoutes)
-	cmd.Flags().String(flagUnlockKey, "", "Select a key to unlock on the RPC server")
-	cmd.Flags().String(flagWebsocket, "8546", "websocket port to listen to")
-	cmd.Flags().StringP(flags.FlagBroadcastMode, "b", flags.BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
-	return cmd
-}
-
-// registerRoutes creates a new server and registers the `/rpc` endpoint.
+// RegisterRoutes creates a new server and registers the `/rpc` endpoint.
 // Rpc calls are enabled based on their associated module (eg. "eth").
-func registerRoutes(rs *lcd.RestServer) {
-	s := rpc.NewServer()
+func RegisterRoutes(rs *lcd.RestServer) {
+	server := rpc.NewServer()
 	accountName := viper.GetString(flagUnlockKey)
 	accountNames := strings.Split(accountName, ",")
 
@@ -71,26 +60,18 @@ func registerRoutes(rs *lcd.RestServer) {
 		}
 	}
 
-	apis := GetRPCAPIs(rs.CliCtx, privkeys)
+	apis := GetAPIs(rs.CliCtx, privkeys...)
 
-	// TODO: Allow cli to configure modules https://github.com/cosmos/ethermint/issues/74
-	whitelist := make(map[string]bool)
-
-	// Register all the APIs exposed by the services
+	// Register all the APIs exposed by the namespace services
+	// TODO: handle allowlist and private APIs
 	for _, api := range apis {
-		if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
-			if err := s.RegisterName(api.Namespace, api.Service); err != nil {
-				panic(err)
-			}
-		} else if !api.Public { // TODO: how to handle private apis? should only accept local calls
-			if err := s.RegisterName(api.Namespace, api.Service); err != nil {
-				panic(err)
-			}
+		if err := server.RegisterName(api.Namespace, api.Service); err != nil {
+			panic(err)
 		}
 	}
 
 	// Web3 RPC API route
-	rs.Mux.HandleFunc("/", s.ServeHTTP).Methods("POST", "OPTIONS")
+	rs.Mux.HandleFunc("/", server.ServeHTTP).Methods("POST", "OPTIONS")
 
 	// Register all other Cosmos routes
 	client.RegisterRoutes(rs.CliCtx, rs.Mux)
@@ -99,8 +80,8 @@ func registerRoutes(rs *lcd.RestServer) {
 
 	// start websockets server
 	websocketAddr := viper.GetString(flagWebsocket)
-	ws := newWebsocketsServer(rs.CliCtx, websocketAddr)
-	ws.start()
+	ws := websockets.NewServer(rs.CliCtx, websocketAddr)
+	ws.Start()
 }
 
 func unlockKeyFromNameAndPassphrase(accountNames []string, passphrase string) ([]ethsecp256k1.PrivKey, error) {
diff --git a/rpc/doc.go b/rpc/doc.go
new file mode 100644
index 00000000..bea7dea7
--- /dev/null
+++ b/rpc/doc.go
@@ -0,0 +1,10 @@
+// Package rpc contains RPC handler methods, namespaces and utilities to start
+// Ethermint's Web3-compatible JSON-RPC server.
+//
+// The list of available namespaces are:
+//
+// * `rpc/namespaces/eth`: `eth` namespace. Exposes the `PublicEthereumAPI` and the `PublicFilterAPI`.
+// * `rpc/namespaces/personal`: `personal` namespace. Exposes the `PrivateAccountAPI`.
+// * `rpc/namespaces/net`: `net` namespace. Exposes the `PublicNetAPI`.
+// * `rpc/namespaces/web3`: `web3` namespace. Exposes the `PublicWeb3API`
+package rpc
diff --git a/rpc/eth_api.go b/rpc/eth_api.go
deleted file mode 100644
index 6c602df8..00000000
--- a/rpc/eth_api.go
+++ /dev/null
@@ -1,1070 +0,0 @@
-package rpc
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"math/big"
-	"os"
-	"sync"
-
-	"github.com/spf13/viper"
-
-	"github.com/cosmos/ethermint/crypto/ethsecp256k1"
-	"github.com/cosmos/ethermint/crypto/hd"
-	params "github.com/cosmos/ethermint/rpc/args"
-	ethermint "github.com/cosmos/ethermint/types"
-	"github.com/cosmos/ethermint/utils"
-	"github.com/cosmos/ethermint/version"
-	evmtypes "github.com/cosmos/ethermint/x/evm/types"
-
-	abci "github.com/tendermint/tendermint/abci/types"
-	"github.com/tendermint/tendermint/crypto/merkle"
-	tmbytes "github.com/tendermint/tendermint/libs/bytes"
-	"github.com/tendermint/tendermint/libs/log"
-	"github.com/tendermint/tendermint/rpc/client"
-	tmtypes "github.com/tendermint/tendermint/types"
-
-	"github.com/ethereum/go-ethereum/accounts/keystore"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-	ethtypes "github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/rlp"
-
-	"github.com/cosmos/cosmos-sdk/client/context"
-	"github.com/cosmos/cosmos-sdk/client/flags"
-	"github.com/cosmos/cosmos-sdk/crypto/keys"
-	sdk "github.com/cosmos/cosmos-sdk/types"
-	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
-	"github.com/cosmos/cosmos-sdk/x/auth"
-	authclient "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
-	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
-)
-
-// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
-type PublicEthAPI struct {
-	cliCtx       context.CLIContext
-	chainIDEpoch *big.Int
-	logger       log.Logger
-	backend      Backend
-	keys         []ethsecp256k1.PrivKey // unlocked keys
-	nonceLock    *AddrLocker
-	keybaseLock  sync.Mutex
-}
-
-// NewPublicEthAPI creates an instance of the public ETH Web3 API.
-func NewPublicEthAPI(cliCtx context.CLIContext, backend Backend, nonceLock *AddrLocker,
-	key []ethsecp256k1.PrivKey) *PublicEthAPI {
-
-	epoch, err := ethermint.ParseChainID(cliCtx.ChainID)
-	if err != nil {
-		panic(err)
-	}
-
-	api := &PublicEthAPI{
-		cliCtx:       cliCtx,
-		chainIDEpoch: epoch,
-		logger:       log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc"),
-		backend:      backend,
-		keys:         key,
-		nonceLock:    nonceLock,
-	}
-
-	if err := api.getKeybaseInfo(); err != nil {
-		api.logger.Error("failed to get keybase info", "error", err)
-	}
-
-	return api
-}
-
-func (e *PublicEthAPI) getKeybaseInfo() error {
-	e.keybaseLock.Lock()
-	defer e.keybaseLock.Unlock()
-
-	if e.cliCtx.Keybase == nil {
-		keybase, err := keys.NewKeyring(
-			sdk.KeyringServiceName(),
-			viper.GetString(flags.FlagKeyringBackend),
-			viper.GetString(flags.FlagHome),
-			e.cliCtx.Input,
-			hd.EthSecp256k1Options()...,
-		)
-		if err != nil {
-			return err
-		}
-
-		e.cliCtx.Keybase = keybase
-	}
-
-	return nil
-}
-
-// ProtocolVersion returns the supported Ethereum protocol version.
-func (e *PublicEthAPI) ProtocolVersion() hexutil.Uint {
-	e.logger.Debug("eth_protocolVersion")
-	return hexutil.Uint(version.ProtocolVersion)
-}
-
-// ChainId returns the chain's identifier in hex format
-func (e *PublicEthAPI) ChainId() (hexutil.Uint, error) { // nolint
-	e.logger.Debug("eth_chainId")
-	return hexutil.Uint(uint(e.chainIDEpoch.Uint64())), nil
-}
-
-// Syncing returns whether or not the current node is syncing with other peers. Returns false if not, or a struct
-// outlining the state of the sync if it is.
-func (e *PublicEthAPI) Syncing() (interface{}, error) {
-	e.logger.Debug("eth_syncing")
-
-	status, err := e.cliCtx.Client.Status()
-	if err != nil {
-		return false, err
-	}
-
-	if !status.SyncInfo.CatchingUp {
-		return false, nil
-	}
-
-	return map[string]interface{}{
-		// "startingBlock": nil, // NA
-		"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
-		// "highestBlock":  nil, // NA
-		// "pulledStates":  nil, // NA
-		// "knownStates":   nil, // NA
-	}, nil
-}
-
-// Coinbase is the address that staking rewards will be send to (alias for Etherbase).
-func (e *PublicEthAPI) Coinbase() (common.Address, error) {
-	e.logger.Debug("eth_coinbase")
-
-	node, err := e.cliCtx.GetNode()
-	if err != nil {
-		return common.Address{}, err
-	}
-
-	status, err := node.Status()
-	if err != nil {
-		return common.Address{}, err
-	}
-
-	return common.BytesToAddress(status.ValidatorInfo.Address.Bytes()), nil
-}
-
-// Mining returns whether or not this node is currently mining. Always false.
-func (e *PublicEthAPI) Mining() bool {
-	e.logger.Debug("eth_mining")
-	return false
-}
-
-// Hashrate returns the current node's hashrate. Always 0.
-func (e *PublicEthAPI) Hashrate() hexutil.Uint64 {
-	e.logger.Debug("eth_hashrate")
-	return 0
-}
-
-// GasPrice returns the current gas price based on Ethermint's gas price oracle.
-func (e *PublicEthAPI) GasPrice() *hexutil.Big {
-	e.logger.Debug("eth_gasPrice")
-	out := big.NewInt(0)
-	return (*hexutil.Big)(out)
-}
-
-// Accounts returns the list of accounts available to this node.
-func (e *PublicEthAPI) Accounts() ([]common.Address, error) {
-	e.logger.Debug("eth_accounts")
-	e.keybaseLock.Lock()
-
-	addresses := make([]common.Address, 0) // return [] instead of nil if empty
-
-	keybase, err := keys.NewKeyring(
-		sdk.KeyringServiceName(),
-		viper.GetString(flags.FlagKeyringBackend),
-		viper.GetString(flags.FlagHome),
-		e.cliCtx.Input,
-		hd.EthSecp256k1Options()...,
-	)
-	if err != nil {
-		return addresses, err
-	}
-
-	infos, err := keybase.List()
-	if err != nil {
-		return addresses, err
-	}
-
-	e.keybaseLock.Unlock()
-
-	for _, info := range infos {
-		addressBytes := info.GetPubKey().Address().Bytes()
-		addresses = append(addresses, common.BytesToAddress(addressBytes))
-	}
-
-	return addresses, nil
-}
-
-// BlockNumber returns the current block number.
-func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) {
-	e.logger.Debug("eth_blockNumber")
-	return e.backend.BlockNumber()
-}
-
-// GetBalance returns the provided account's balance up to the provided block number.
-func (e *PublicEthAPI) GetBalance(address common.Address, blockNum BlockNumber) (*hexutil.Big, error) {
-	e.logger.Debug("eth_getBalance", "address", address, "block number", blockNum)
-	ctx := e.cliCtx.WithHeight(blockNum.Int64())
-	res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/balance/%s", evmtypes.ModuleName, address.Hex()), nil)
-	if err != nil {
-		return nil, err
-	}
-
-	var out evmtypes.QueryResBalance
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
-	val, err := utils.UnmarshalBigInt(out.Balance)
-	if err != nil {
-		return nil, err
-	}
-
-	return (*hexutil.Big)(val), nil
-}
-
-// GetStorageAt returns the contract storage at the given address, block number, and key.
-func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum BlockNumber) (hexutil.Bytes, error) {
-	e.logger.Debug("eth_getStorageAt", "address", address, "key", key, "block number", blockNum)
-	ctx := e.cliCtx.WithHeight(blockNum.Int64())
-	res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", evmtypes.ModuleName, address.Hex(), key), nil)
-	if err != nil {
-		return nil, err
-	}
-
-	var out evmtypes.QueryResStorage
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
-	return out.Value, nil
-}
-
-// GetTransactionCount returns the number of transactions at the given address up to the given block number.
-func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum BlockNumber) (*hexutil.Uint64, error) {
-	e.logger.Debug("eth_getTransactionCount", "address", address, "block number", blockNum)
-	ctx := e.cliCtx.WithHeight(blockNum.Int64())
-
-	// Get nonce (sequence) from account
-	from := sdk.AccAddress(address.Bytes())
-	accRet := authtypes.NewAccountRetriever(ctx)
-
-	err := accRet.EnsureExists(from)
-	if err != nil {
-		// account doesn't exist yet, return 0
-		n := hexutil.Uint64(0)
-		return &n, nil
-	}
-
-	_, nonce, err := accRet.GetAccountNumberSequence(from)
-	if err != nil {
-		return nil, err
-	}
-
-	n := hexutil.Uint64(nonce)
-	return &n, nil
-}
-
-// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
-func (e *PublicEthAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
-	e.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash)
-	res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
-	if err != nil {
-		// Return nil if block does not exist
-		return nil
-	}
-
-	var out evmtypes.QueryResBlockNumber
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
-	return e.getBlockTransactionCountByNumber(out.Number)
-}
-
-// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
-func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum BlockNumber) *hexutil.Uint {
-	e.logger.Debug("eth_getBlockTransactionCountByNumber", "block number", blockNum)
-	height := blockNum.Int64()
-	return e.getBlockTransactionCountByNumber(height)
-}
-
-func (e *PublicEthAPI) getBlockTransactionCountByNumber(number int64) *hexutil.Uint {
-	block, err := e.cliCtx.Client.Block(&number)
-	if err != nil {
-		// Return nil if block doesn't exist
-		return nil
-	}
-
-	n := hexutil.Uint(len(block.Block.Txs))
-	return &n
-}
-
-// GetUncleCountByBlockHash returns the number of uncles in the block idenfied by hash. Always zero.
-func (e *PublicEthAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint {
-	return 0
-}
-
-// GetUncleCountByBlockNumber returns the number of uncles in the block idenfied by number. Always zero.
-func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum BlockNumber) hexutil.Uint {
-	return 0
-}
-
-// GetCode returns the contract code at the given address and block number.
-func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber) (hexutil.Bytes, error) {
-	e.logger.Debug("eth_getCode", "address", address, "block number", blockNumber)
-	ctx := e.cliCtx.WithHeight(blockNumber.Int64())
-	res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryCode, address.Hex()), nil)
-	if err != nil {
-		return nil, err
-	}
-
-	var out evmtypes.QueryResCode
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
-	return out.Code, nil
-}
-
-// GetTransactionLogs returns the logs given a transaction hash.
-func (e *PublicEthAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
-	e.logger.Debug("eth_getTransactionLogs", "hash", txHash)
-	return e.backend.GetTransactionLogs(txHash)
-}
-
-// ExportAccount exports an account's balance, code, and storage at the given block number
-// TODO: deprecate this once the export genesis command works
-func (e *PublicEthAPI) ExportAccount(address common.Address, blockNumber BlockNumber) (string, error) {
-	e.logger.Debug("eth_exportAccount", "address", address, "block number", blockNumber)
-	ctx := e.cliCtx.WithHeight(blockNumber.Int64())
-
-	res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryExportAccount, address.Hex()), nil)
-	if err != nil {
-		return "", err
-	}
-
-	return string(res), nil
-}
-
-func checkKeyInKeyring(keys []ethsecp256k1.PrivKey, address common.Address) (key ethsecp256k1.PrivKey, exist bool) {
-	if len(keys) > 0 {
-		for _, key := range keys {
-			if bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
-				return key, true
-			}
-		}
-	}
-	return nil, false
-}
-
-// Sign signs the provided data using the private key of address via Geth's signature standard.
-func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
-	e.logger.Debug("eth_sign", "address", address, "data", data)
-	// TODO: Change this functionality to find an unlocked account by address
-
-	key, exist := checkKeyInKeyring(e.keys, address)
-	if !exist {
-		return nil, keystore.ErrLocked
-	}
-
-	// Sign the requested hash with the wallet
-	signature, err := key.Sign(data)
-	if err == nil {
-		signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
-	}
-
-	return signature, err
-}
-
-// SendTransaction sends an Ethereum transaction.
-func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, error) {
-	e.logger.Debug("eth_sendTransaction", "args", args)
-	// TODO: Change this functionality to find an unlocked account by address
-
-	for _, key := range e.keys {
-		e.logger.Debug("eth_sendTransaction", "key", fmt.Sprintf("0x%x", key.PubKey().Address().Bytes()))
-	}
-
-	key, exist := checkKeyInKeyring(e.keys, args.From)
-	if !exist {
-		e.logger.Debug("failed to find key in keyring", "key", args.From)
-		return common.Hash{}, keystore.ErrLocked
-	}
-
-	// Mutex lock the address' nonce to avoid assigning it to multiple requests
-	if args.Nonce == nil {
-		e.nonceLock.LockAddr(args.From)
-		defer e.nonceLock.UnlockAddr(args.From)
-	}
-
-	// Assemble transaction from fields
-	tx, err := e.generateFromArgs(args)
-	if err != nil {
-		e.logger.Debug("failed to generate tx", "error", err)
-		return common.Hash{}, err
-	}
-
-	// ChainID must be set as flag to send transaction
-	chainID := viper.GetString(flags.FlagChainID)
-	// parse the chainID from a string to a base-10 integer
-	chainIDEpoch, err := ethermint.ParseChainID(chainID)
-	if err != nil {
-		return common.Hash{}, err
-	}
-
-	// Sign transaction
-	if err := tx.Sign(chainIDEpoch, key.ToECDSA()); err != nil {
-		e.logger.Debug("failed to sign tx", "error", err)
-		return common.Hash{}, err
-	}
-
-	// Encode transaction by default Tx encoder
-	txEncoder := authclient.GetTxEncoder(e.cliCtx.Codec)
-	txBytes, err := txEncoder(tx)
-	if err != nil {
-		return common.Hash{}, err
-	}
-
-	// Broadcast transaction in sync mode (default)
-	res, err := e.cliCtx.BroadcastTx(txBytes)
-	// If error is encountered on the node, the broadcast will not return an error
-	if err != nil {
-		return common.Hash{}, err
-	}
-
-	// Return transaction hash
-	return common.HexToHash(res.TxHash), nil
-}
-
-// SendRawTransaction send a raw Ethereum transaction.
-func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
-	e.logger.Debug("eth_sendRawTransaction", "data", data)
-	tx := new(evmtypes.MsgEthereumTx)
-
-	// RLP decode raw transaction bytes
-	if err := rlp.DecodeBytes(data, tx); err != nil {
-		// Return nil is for when gasLimit overflows uint64
-		return common.Hash{}, nil
-	}
-
-	// Encode transaction by default Tx encoder
-	txEncoder := authclient.GetTxEncoder(e.cliCtx.Codec)
-	txBytes, err := txEncoder(tx)
-	if err != nil {
-		return common.Hash{}, err
-	}
-
-	// TODO: Possibly log the contract creation address (if recipient address is nil) or tx data
-	// If error is encountered on the node, the broadcast will not return an error
-	res, err := e.cliCtx.BroadcastTx(txBytes)
-	if err != nil {
-		return common.Hash{}, err
-	}
-
-	// Return transaction hash
-	return common.HexToHash(res.TxHash), nil
-}
-
-// CallArgs represents the arguments for a call.
-type CallArgs struct {
-	From     *common.Address `json:"from"`
-	To       *common.Address `json:"to"`
-	Gas      *hexutil.Uint64 `json:"gas"`
-	GasPrice *hexutil.Big    `json:"gasPrice"`
-	Value    *hexutil.Big    `json:"value"`
-	Data     *hexutil.Bytes  `json:"data"`
-}
-
-// Call performs a raw contract call.
-func (e *PublicEthAPI) Call(args CallArgs, blockNr BlockNumber, _ *map[common.Address]account) (hexutil.Bytes, error) {
-	e.logger.Debug("eth_call", "args", args, "block number", blockNr)
-	simRes, err := e.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit))
-	if err != nil {
-		return []byte{}, err
-	}
-
-	data, err := evmtypes.DecodeResultData(simRes.Result.Data)
-	if err != nil {
-		return []byte{}, err
-	}
-
-	return (hexutil.Bytes)(data.Ret), nil
-}
-
-// account indicates the overriding fields of account during the execution of
-// a message call.
-// Note, state and stateDiff can't be specified at the same time. If state is
-// set, message execution will only use the data in the given state. Otherwise
-// if statDiff is set, all diff will be applied first and then execute the call
-// message.
-type account struct {
-	Nonce     *hexutil.Uint64              `json:"nonce"`
-	Code      *hexutil.Bytes               `json:"code"`
-	Balance   **hexutil.Big                `json:"balance"`
-	State     *map[common.Hash]common.Hash `json:"state"`
-	StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
-}
-
-// DoCall performs a simulated call operation through the evmtypes. It returns the
-// estimated gas used on the operation or an error if fails.
-func (e *PublicEthAPI) doCall(
-	args CallArgs, blockNr BlockNumber, globalGasCap *big.Int,
-) (*sdk.SimulationResponse, error) {
-	// Set height for historical queries
-	ctx := e.cliCtx
-
-	if blockNr.Int64() != 0 {
-		ctx = e.cliCtx.WithHeight(blockNr.Int64())
-	}
-
-	// Set sender address or use a default if none specified
-	var addr common.Address
-
-	if args.From == nil {
-		addrs, err := e.Accounts()
-		if err == nil && len(addrs) > 0 {
-			addr = addrs[0]
-		}
-	} else {
-		addr = *args.From
-	}
-
-	// Set default gas & gas price if none were set
-	// Change this to uint64(math.MaxUint64 / 2) if gas cap can be configured
-	gas := uint64(ethermint.DefaultRPCGasLimit)
-	if args.Gas != nil {
-		gas = uint64(*args.Gas)
-	}
-	if globalGasCap != nil && globalGasCap.Uint64() < gas {
-		e.logger.Debug("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
-		gas = globalGasCap.Uint64()
-	}
-
-	// Set gas price using default or parameter if passed in
-	gasPrice := new(big.Int).SetUint64(ethermint.DefaultGasPrice)
-	if args.GasPrice != nil {
-		gasPrice = args.GasPrice.ToInt()
-	}
-
-	// Set value for transaction
-	value := new(big.Int)
-	if args.Value != nil {
-		value = args.Value.ToInt()
-	}
-
-	// Set Data if provided
-	var data []byte
-	if args.Data != nil {
-		data = []byte(*args.Data)
-	}
-
-	// Set destination address for call
-	var toAddr sdk.AccAddress
-	if args.To != nil {
-		toAddr = sdk.AccAddress(args.To.Bytes())
-	}
-
-	// Create new call message
-	msg := evmtypes.NewMsgEthermint(0, &toAddr, sdk.NewIntFromBigInt(value), gas,
-		sdk.NewIntFromBigInt(gasPrice), data, sdk.AccAddress(addr.Bytes()))
-
-	if err := msg.ValidateBasic(); err != nil {
-		return nil, err
-	}
-
-	// Generate tx to be used to simulate (signature isn't needed)
-	var stdSig authtypes.StdSignature
-	tx := authtypes.NewStdTx([]sdk.Msg{msg}, authtypes.StdFee{}, []authtypes.StdSignature{stdSig}, "")
-
-	txEncoder := authclient.GetTxEncoder(ctx.Codec)
-	txBytes, err := txEncoder(tx)
-	if err != nil {
-		return nil, err
-	}
-
-	// Transaction simulation through query
-	res, _, err := ctx.QueryWithData("app/simulate", txBytes)
-	if err != nil {
-		return nil, err
-	}
-
-	var simResponse sdk.SimulationResponse
-	if err := ctx.Codec.UnmarshalBinaryBare(res, &simResponse); err != nil {
-		return nil, err
-	}
-
-	return &simResponse, nil
-}
-
-// EstimateGas returns an estimate of gas usage for the given smart contract call.
-// It adds 1,000 gas to the returned value instead of using the gas adjustment
-// param from the SDK.
-func (e *PublicEthAPI) EstimateGas(args CallArgs) (hexutil.Uint64, error) {
-	e.logger.Debug("eth_estimateGas", "args", args)
-	simResponse, err := e.doCall(args, 0, big.NewInt(ethermint.DefaultRPCGasLimit))
-	if err != nil {
-		return 0, err
-	}
-
-	// TODO: change 1000 buffer for more accurate buffer (eg: SDK's gasAdjusted)
-	estimatedGas := simResponse.GasInfo.GasUsed
-	gas := estimatedGas + 1000
-
-	return hexutil.Uint64(gas), nil
-}
-
-// GetBlockByHash returns the block identified by hash.
-func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
-	e.logger.Debug("eth_getBlockByHash", "hash", hash, "full", fullTx)
-	return e.backend.GetBlockByHash(hash, fullTx)
-}
-
-// GetBlockByNumber returns the block identified by number.
-func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) {
-	e.logger.Debug("eth_getBlockByNumber", "number", blockNum, "full", fullTx)
-	return e.backend.GetBlockByNumber(blockNum, fullTx)
-}
-
-func formatBlock(
-	header tmtypes.Header, size int, gasLimit int64,
-	gasUsed *big.Int, transactions interface{}, bloom ethtypes.Bloom,
-) map[string]interface{} {
-	if bytes.Equal(header.DataHash, []byte{}) {
-		header.DataHash = tmbytes.HexBytes(common.Hash{}.Bytes())
-	}
-
-	return map[string]interface{}{
-		"number":           hexutil.Uint64(header.Height),
-		"hash":             hexutil.Bytes(header.Hash()),
-		"parentHash":       hexutil.Bytes(header.LastBlockID.Hash),
-		"nonce":            hexutil.Uint64(0), // PoW specific
-		"sha3Uncles":       common.Hash{},     // No uncles in Tendermint
-		"logsBloom":        bloom,
-		"transactionsRoot": hexutil.Bytes(header.DataHash),
-		"stateRoot":        hexutil.Bytes(header.AppHash),
-		"miner":            common.Address{},
-		"mixHash":          common.Hash{},
-		"difficulty":       0,
-		"totalDifficulty":  0,
-		"extraData":        hexutil.Uint64(0),
-		"size":             hexutil.Uint64(size),
-		"gasLimit":         hexutil.Uint64(gasLimit), // Static gas limit
-		"gasUsed":          (*hexutil.Big)(gasUsed),
-		"timestamp":        hexutil.Uint64(header.Time.Unix()),
-		"transactions":     transactions.([]common.Hash),
-		"uncles":           []string{},
-		"receiptsRoot":     common.Hash{},
-	}
-}
-
-func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]common.Hash, *big.Int, error) {
-	transactions := make([]common.Hash, len(txs))
-	gasUsed := big.NewInt(0)
-
-	for i, tx := range txs {
-		ethTx, err := bytesToEthTx(cliCtx, tx)
-		if err != nil {
-			// continue to next transaction in case it's not a MsgEthereumTx
-			continue
-		}
-		// TODO: Remove gas usage calculation if saving gasUsed per block
-		gasUsed.Add(gasUsed, ethTx.Fee())
-		tx, err := newRPCTransaction(*ethTx, common.BytesToHash(tx.Hash()), blockHash, &height, uint64(i))
-		if err != nil {
-			return nil, nil, err
-		}
-		transactions[i] = tx.Hash
-	}
-
-	return transactions, gasUsed, nil
-}
-
-// Transaction represents a transaction returned to RPC clients.
-type Transaction struct {
-	BlockHash        *common.Hash    `json:"blockHash"`
-	BlockNumber      *hexutil.Big    `json:"blockNumber"`
-	From             common.Address  `json:"from"`
-	Gas              hexutil.Uint64  `json:"gas"`
-	GasPrice         *hexutil.Big    `json:"gasPrice"`
-	Hash             common.Hash     `json:"hash"`
-	Input            hexutil.Bytes   `json:"input"`
-	Nonce            hexutil.Uint64  `json:"nonce"`
-	To               *common.Address `json:"to"`
-	TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
-	Value            *hexutil.Big    `json:"value"`
-	V                *hexutil.Big    `json:"v"`
-	R                *hexutil.Big    `json:"r"`
-	S                *hexutil.Big    `json:"s"`
-}
-
-func bytesToEthTx(cliCtx context.CLIContext, bz []byte) (*evmtypes.MsgEthereumTx, error) {
-	var stdTx sdk.Tx
-	// TODO: switch to UnmarshalBinaryBare on SDK v0.40.0
-	err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(bz, &stdTx)
-	if err != nil {
-		return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
-	}
-
-	ethTx, ok := stdTx.(evmtypes.MsgEthereumTx)
-	if !ok {
-		return nil, fmt.Errorf("invalid transaction type %T, expected MsgEthereumTx", stdTx)
-	}
-	return &ethTx, nil
-}
-
-// newRPCTransaction returns a transaction that will serialize to the RPC
-// representation, with the given location metadata set (if available).
-func newRPCTransaction(tx evmtypes.MsgEthereumTx, txHash, blockHash common.Hash, blockNumber *uint64, index uint64) (*Transaction, error) {
-	// Verify signature and retrieve sender address
-	from, err := tx.VerifySig(tx.ChainID())
-	if err != nil {
-		return nil, err
-	}
-
-	result := Transaction{
-		From:     from,
-		Gas:      hexutil.Uint64(tx.Data.GasLimit),
-		GasPrice: (*hexutil.Big)(tx.Data.Price),
-		Hash:     txHash,
-		Input:    hexutil.Bytes(tx.Data.Payload),
-		Nonce:    hexutil.Uint64(tx.Data.AccountNonce),
-		To:       tx.To(),
-		Value:    (*hexutil.Big)(tx.Data.Amount),
-		V:        (*hexutil.Big)(tx.Data.V),
-		R:        (*hexutil.Big)(tx.Data.R),
-		S:        (*hexutil.Big)(tx.Data.S),
-	}
-
-	if blockHash != (common.Hash{}) {
-		result.BlockHash = &blockHash
-		result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(*blockNumber))
-		result.TransactionIndex = (*hexutil.Uint64)(&index)
-	}
-
-	return &result, nil
-}
-
-// GetTransactionByHash returns the transaction identified by hash.
-func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*Transaction, error) {
-	e.logger.Debug("eth_getTransactionByHash", "hash", hash)
-	tx, err := e.cliCtx.Client.Tx(hash.Bytes(), false)
-	if err != nil {
-		// Return nil for transaction when not found
-		return nil, nil
-	}
-
-	// Can either cache or just leave this out if not necessary
-	block, err := e.cliCtx.Client.Block(&tx.Height)
-	if err != nil {
-		return nil, err
-	}
-	blockHash := common.BytesToHash(block.Block.Header.Hash())
-
-	ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx)
-	if err != nil {
-		return nil, err
-	}
-
-	height := uint64(tx.Height)
-	return newRPCTransaction(*ethTx, common.BytesToHash(tx.Tx.Hash()), blockHash, &height, uint64(tx.Index))
-}
-
-// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
-func (e *PublicEthAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*Transaction, error) {
-	e.logger.Debug("eth_getTransactionByHashAndIndex", "hash", hash, "index", idx)
-	res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
-	if err != nil {
-		return nil, err
-	}
-
-	var out evmtypes.QueryResBlockNumber
-	e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
-	return e.getTransactionByBlockNumberAndIndex(out.Number, idx)
-}
-
-// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
-func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum BlockNumber, idx hexutil.Uint) (*Transaction, error) {
-	e.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
-	value := blockNum.Int64()
-	return e.getTransactionByBlockNumberAndIndex(value, idx)
-}
-
-func (e *PublicEthAPI) getTransactionByBlockNumberAndIndex(number int64, idx hexutil.Uint) (*Transaction, error) {
-	block, err := e.cliCtx.Client.Block(&number)
-	if err != nil {
-		return nil, err
-	}
-	header := block.Block.Header
-
-	txs := block.Block.Txs
-	if uint64(idx) >= uint64(len(txs)) {
-		return nil, nil
-	}
-	ethTx, err := bytesToEthTx(e.cliCtx, txs[idx])
-	if err != nil {
-		return nil, err
-	}
-
-	height := uint64(header.Height)
-	return newRPCTransaction(*ethTx, common.BytesToHash(txs[idx].Hash()), common.BytesToHash(header.Hash()), &height, uint64(idx))
-}
-
-// GetTransactionReceipt returns the transaction receipt identified by hash.
-func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
-	e.logger.Debug("eth_getTransactionReceipt", "hash", hash)
-	tx, err := e.cliCtx.Client.Tx(hash.Bytes(), false)
-	if err != nil {
-		// Return nil for transaction when not found
-		return nil, nil
-	}
-
-	// Query block for consensus hash
-	block, err := e.cliCtx.Client.Block(&tx.Height)
-	if err != nil {
-		return nil, err
-	}
-	blockHash := common.BytesToHash(block.Block.Header.Hash())
-
-	// Convert tx bytes to eth transaction
-	ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx)
-	if err != nil {
-		return nil, err
-	}
-
-	from, err := ethTx.VerifySig(ethTx.ChainID())
-	if err != nil {
-		return nil, err
-	}
-
-	// Set status codes based on tx result
-	var status hexutil.Uint
-	if tx.TxResult.IsOK() {
-		status = hexutil.Uint(1)
-	} else {
-		status = hexutil.Uint(0)
-	}
-
-	txData := tx.TxResult.GetData()
-
-	data, err := evmtypes.DecodeResultData(txData)
-	if err != nil {
-		status = 0 // transaction failed
-	}
-
-	if data.Logs == nil {
-		data.Logs = []*ethtypes.Log{}
-	}
-
-	receipt := map[string]interface{}{
-		// Consensus fields: These fields are defined by the Yellow Paper
-		"status":            status,
-		"cumulativeGasUsed": nil, // ignore until needed
-		"logsBloom":         data.Bloom,
-		"logs":              data.Logs,
-
-		// Implementation fields: These fields are added by geth when processing a transaction.
-		// They are stored in the chain database.
-		"transactionHash": hash,
-		"contractAddress": data.ContractAddress,
-		"gasUsed":         hexutil.Uint64(tx.TxResult.GasUsed),
-
-		// Inclusion information: These fields provide information about the inclusion of the
-		// transaction corresponding to this receipt.
-		"blockHash":        blockHash,
-		"blockNumber":      hexutil.Uint64(tx.Height),
-		"transactionIndex": hexutil.Uint64(tx.Index),
-
-		// sender and receiver (contract or EOA) addreses
-		"from": from,
-		"to":   ethTx.To(),
-	}
-
-	return receipt, nil
-}
-
-// PendingTransactions returns the transactions that are in the transaction pool
-// and have a from address that is one of the accounts this node manages.
-func (e *PublicEthAPI) PendingTransactions() ([]*Transaction, error) {
-	e.logger.Debug("eth_getPendingTransactions")
-	return e.backend.PendingTransactions()
-}
-
-// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil.
-func (e *PublicEthAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} {
-	return nil
-}
-
-// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil.
-func (e *PublicEthAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} {
-	return nil
-}
-
-// Copied the Account and StorageResult types since they are registered under an
-// internal pkg on geth.
-
-// AccountResult struct for account proof
-type AccountResult struct {
-	Address      common.Address  `json:"address"`
-	AccountProof []string        `json:"accountProof"`
-	Balance      *hexutil.Big    `json:"balance"`
-	CodeHash     common.Hash     `json:"codeHash"`
-	Nonce        hexutil.Uint64  `json:"nonce"`
-	StorageHash  common.Hash     `json:"storageHash"`
-	StorageProof []StorageResult `json:"storageProof"`
-}
-
-// StorageResult defines the format for storage proof return
-type StorageResult struct {
-	Key   string       `json:"key"`
-	Value *hexutil.Big `json:"value"`
-	Proof []string     `json:"proof"`
-}
-
-// GetProof returns an account object with proof and any storage proofs
-func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, block BlockNumber) (*AccountResult, error) {
-	e.logger.Debug("eth_getProof", "address", address, "keys", storageKeys, "number", block)
-	e.cliCtx = e.cliCtx.WithHeight(int64(block))
-	path := fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryAccount, address.Hex())
-
-	// query eth account at block height
-	resBz, _, err := e.cliCtx.Query(path)
-	if err != nil {
-		return nil, err
-	}
-
-	var account evmtypes.QueryResAccount
-	e.cliCtx.Codec.MustUnmarshalJSON(resBz, &account)
-
-	storageProofs := make([]StorageResult, len(storageKeys))
-	opts := client.ABCIQueryOptions{Height: int64(block), Prove: true}
-	for i, k := range storageKeys {
-		// Get value for key
-		vPath := fmt.Sprintf("custom/%s/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryStorage, address, k)
-		vRes, err := e.cliCtx.Client.ABCIQueryWithOptions(vPath, nil, opts)
-		if err != nil {
-			return nil, err
-		}
-
-		var value evmtypes.QueryResStorage
-		e.cliCtx.Codec.MustUnmarshalJSON(vRes.Response.GetValue(), &value)
-
-		// check for proof
-		proof := vRes.Response.GetProof()
-		proofStr := new(merkle.Proof).String()
-		if proof != nil {
-			proofStr = proof.String()
-		}
-
-		storageProofs[i] = StorageResult{
-			Key:   k,
-			Value: (*hexutil.Big)(common.BytesToHash(value.Value).Big()),
-			Proof: []string{proofStr},
-		}
-	}
-
-	req := abci.RequestQuery{
-		Path:   fmt.Sprintf("store/%s/key", auth.StoreKey),
-		Data:   auth.AddressStoreKey(sdk.AccAddress(address.Bytes())),
-		Height: int64(block),
-		Prove:  true,
-	}
-
-	res, err := e.cliCtx.QueryABCI(req)
-	if err != nil {
-		return nil, err
-	}
-
-	// check for proof
-	accountProof := res.GetProof()
-	accProofStr := new(merkle.Proof).String()
-	if accountProof != nil {
-		accProofStr = accountProof.String()
-	}
-
-	return &AccountResult{
-		Address:      address,
-		AccountProof: []string{accProofStr},
-		Balance:      (*hexutil.Big)(utils.MustUnmarshalBigInt(account.Balance)),
-		CodeHash:     common.BytesToHash(account.CodeHash),
-		Nonce:        hexutil.Uint64(account.Nonce),
-		StorageHash:  common.Hash{}, // Ethermint doesn't have a storage hash
-		StorageProof: storageProofs,
-	}, nil
-}
-
-// generateFromArgs populates tx message with args (used in RPC API)
-func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (*evmtypes.MsgEthereumTx, error) {
-	var (
-		nonce    uint64
-		gasLimit uint64
-		err      error
-	)
-
-	amount := (*big.Int)(args.Value)
-	gasPrice := (*big.Int)(args.GasPrice)
-
-	if args.GasPrice == nil {
-
-		// Set default gas price
-		// TODO: Change to min gas price from context once available through server/daemon
-		gasPrice = big.NewInt(ethermint.DefaultGasPrice)
-	}
-
-	if args.Nonce == nil {
-		// Get nonce (sequence) from account
-		from := sdk.AccAddress(args.From.Bytes())
-		accRet := authtypes.NewAccountRetriever(e.cliCtx)
-
-		if e.cliCtx.Keybase == nil {
-			return nil, fmt.Errorf("cliCtx.Keybase is nil")
-		}
-
-		err = accRet.EnsureExists(from)
-		if err != nil {
-			// account doesn't exist
-			return nil, fmt.Errorf("nonexistent account %s: %s", args.From.Hex(), err)
-		}
-
-		_, nonce, err = accRet.GetAccountNumberSequence(from)
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		nonce = (uint64)(*args.Nonce)
-	}
-
-	if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
-		return nil, errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
-	}
-
-	// Sets input to either Input or Data, if both are set and not equal error above returns
-	var input []byte
-	if args.Input != nil {
-		input = *args.Input
-	} else if args.Data != nil {
-		input = *args.Data
-	}
-
-	if args.To == nil && len(input) == 0 {
-		// Contract creation
-		return nil, fmt.Errorf("contract creation without any data provided")
-	}
-
-	if args.Gas == nil {
-		callArgs := CallArgs{
-			From:     &args.From,
-			To:       args.To,
-			Gas:      args.Gas,
-			GasPrice: args.GasPrice,
-			Value:    args.Value,
-			Data:     args.Data,
-		}
-		gl, err := e.EstimateGas(callArgs)
-		if err != nil {
-			return nil, err
-		}
-		gasLimit = uint64(gl)
-	} else {
-		gasLimit = (uint64)(*args.Gas)
-	}
-	msg := evmtypes.NewMsgEthereumTx(nonce, args.To, amount, gasLimit, gasPrice, input)
-
-	return &msg, nil
-}
diff --git a/rpc/namespaces/eth/api.go b/rpc/namespaces/eth/api.go
new file mode 100644
index 00000000..71af7b6c
--- /dev/null
+++ b/rpc/namespaces/eth/api.go
@@ -0,0 +1,890 @@
+package eth
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"math/big"
+	"os"
+	"sync"
+
+	"github.com/spf13/viper"
+
+	"github.com/cosmos/ethermint/crypto/ethsecp256k1"
+	"github.com/cosmos/ethermint/crypto/hd"
+	"github.com/cosmos/ethermint/rpc/backend"
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
+	ethermint "github.com/cosmos/ethermint/types"
+	"github.com/cosmos/ethermint/utils"
+	"github.com/cosmos/ethermint/version"
+	evmtypes "github.com/cosmos/ethermint/x/evm/types"
+
+	abci "github.com/tendermint/tendermint/abci/types"
+	"github.com/tendermint/tendermint/crypto/merkle"
+	"github.com/tendermint/tendermint/libs/log"
+	"github.com/tendermint/tendermint/rpc/client"
+	tmtypes "github.com/tendermint/tendermint/types"
+
+	"github.com/ethereum/go-ethereum/accounts/keystore"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	ethtypes "github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/rlp"
+
+	clientcontext "github.com/cosmos/cosmos-sdk/client/context"
+	"github.com/cosmos/cosmos-sdk/client/flags"
+	"github.com/cosmos/cosmos-sdk/crypto/keys"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/x/auth"
+	authclient "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
+	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+)
+
+// PublicEthereumAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
+type PublicEthereumAPI struct {
+	ctx          context.Context
+	clientCtx    clientcontext.CLIContext
+	chainIDEpoch *big.Int
+	logger       log.Logger
+	backend      backend.Backend
+	keys         []ethsecp256k1.PrivKey // unlocked keys
+	nonceLock    *rpctypes.AddrLocker
+	keyringLock  sync.Mutex
+}
+
+// NewAPI creates an instance of the public ETH Web3 API.
+func NewAPI(
+	clientCtx clientcontext.CLIContext, backend backend.Backend, nonceLock *rpctypes.AddrLocker,
+	keys ...ethsecp256k1.PrivKey,
+) *PublicEthereumAPI {
+
+	epoch, err := ethermint.ParseChainID(clientCtx.ChainID)
+	if err != nil {
+		panic(err)
+	}
+
+	api := &PublicEthereumAPI{
+		ctx:          context.Background(),
+		clientCtx:    clientCtx,
+		chainIDEpoch: epoch,
+		logger:       log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc", "namespace", "eth"),
+		backend:      backend,
+		keys:         keys,
+		nonceLock:    nonceLock,
+	}
+
+	if err := api.GetKeyringInfo(); err != nil {
+		api.logger.Error("failed to get keybase info", "error", err)
+	}
+
+	return api
+}
+
+// GetKeyringInfo checks if the keyring is present on the client context. If not, it creates a new
+// instance and sets it to the client context for later usage.
+func (api *PublicEthereumAPI) GetKeyringInfo() error {
+	api.keyringLock.Lock()
+	defer api.keyringLock.Unlock()
+
+	if api.clientCtx.Keybase != nil {
+		return nil
+	}
+
+	keybase, err := keys.NewKeyring(
+		sdk.KeyringServiceName(),
+		viper.GetString(flags.FlagKeyringBackend),
+		viper.GetString(flags.FlagHome),
+		api.clientCtx.Input,
+		hd.EthSecp256k1Options()...,
+	)
+	if err != nil {
+		return err
+	}
+
+	api.clientCtx.Keybase = keybase
+	return nil
+}
+
+// ClientCtx returns the Cosmos SDK client context.
+func (api *PublicEthereumAPI) ClientCtx() clientcontext.CLIContext {
+	return api.clientCtx
+}
+
+// GetKeys returns the Cosmos SDK client context.
+func (api *PublicEthereumAPI) GetKeys() []ethsecp256k1.PrivKey {
+	return api.keys
+}
+
+// SetKeys sets the given key slice to the set of private keys
+func (api *PublicEthereumAPI) SetKeys(keys []ethsecp256k1.PrivKey) {
+	api.keys = keys
+}
+
+// ProtocolVersion returns the supported Ethereum protocol version.
+func (api *PublicEthereumAPI) ProtocolVersion() hexutil.Uint {
+	api.logger.Debug("eth_protocolVersion")
+	return hexutil.Uint(version.ProtocolVersion)
+}
+
+// ChainId returns the chain's identifier in hex format
+func (api *PublicEthereumAPI) ChainId() (hexutil.Uint, error) { // nolint
+	api.logger.Debug("eth_chainId")
+	return hexutil.Uint(uint(api.chainIDEpoch.Uint64())), nil
+}
+
+// Syncing returns whether or not the current node is syncing with other peers. Returns false if not, or a struct
+// outlining the state of the sync if it is.
+func (api *PublicEthereumAPI) Syncing() (interface{}, error) {
+	api.logger.Debug("eth_syncing")
+
+	status, err := api.clientCtx.Client.Status()
+	if err != nil {
+		return false, err
+	}
+
+	if !status.SyncInfo.CatchingUp {
+		return false, nil
+	}
+
+	return map[string]interface{}{
+		// "startingBlock": nil, // NA
+		"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
+		// "highestBlock":  nil, // NA
+		// "pulledStates":  nil, // NA
+		// "knownStates":   nil, // NA
+	}, nil
+}
+
+// Coinbase is the address that staking rewards will be send to (alias for Etherbase).
+func (api *PublicEthereumAPI) Coinbase() (common.Address, error) {
+	api.logger.Debug("eth_coinbase")
+
+	node, err := api.clientCtx.GetNode()
+	if err != nil {
+		return common.Address{}, err
+	}
+
+	status, err := node.Status()
+	if err != nil {
+		return common.Address{}, err
+	}
+
+	return common.BytesToAddress(status.ValidatorInfo.Address.Bytes()), nil
+}
+
+// Mining returns whether or not this node is currently mining. Always false.
+func (api *PublicEthereumAPI) Mining() bool {
+	api.logger.Debug("eth_mining")
+	return false
+}
+
+// Hashrate returns the current node's hashrate. Always 0.
+func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 {
+	api.logger.Debug("eth_hashrate")
+	return 0
+}
+
+// GasPrice returns the current gas price based on Ethermint's gas price oracle.
+func (api *PublicEthereumAPI) GasPrice() *hexutil.Big {
+	api.logger.Debug("eth_gasPrice")
+	out := big.NewInt(0)
+	return (*hexutil.Big)(out)
+}
+
+// Accounts returns the list of accounts available to this node.
+func (api *PublicEthereumAPI) Accounts() ([]common.Address, error) {
+	api.logger.Debug("eth_accounts")
+	api.keyringLock.Lock()
+
+	addresses := make([]common.Address, 0) // return [] instead of nil if empty
+
+	infos, err := api.clientCtx.Keybase.List()
+	if err != nil {
+		return addresses, err
+	}
+
+	api.keyringLock.Unlock()
+
+	for _, info := range infos {
+		addressBytes := info.GetPubKey().Address().Bytes()
+		addresses = append(addresses, common.BytesToAddress(addressBytes))
+	}
+
+	return addresses, nil
+}
+
+// rpctypes.BlockNumber returns the current block number.
+func (api *PublicEthereumAPI) BlockNumber() (hexutil.Uint64, error) {
+	api.logger.Debug("eth_blockNumber")
+	return api.backend.BlockNumber()
+}
+
+// GetBalance returns the provided account's balance up to the provided block number.
+func (api *PublicEthereumAPI) GetBalance(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Big, error) {
+	api.logger.Debug("eth_getBalance", "address", address, "block number", blockNum)
+	clientCtx := api.clientCtx.WithHeight(blockNum.Int64())
+	res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/balance/%s", evmtypes.ModuleName, address.Hex()), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var out evmtypes.QueryResBalance
+	api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
+	val, err := utils.UnmarshalBigInt(out.Balance)
+	if err != nil {
+		return nil, err
+	}
+
+	return (*hexutil.Big)(val), nil
+}
+
+// GetStorageAt returns the contract storage at the given address, block number, and key.
+func (api *PublicEthereumAPI) GetStorageAt(address common.Address, key string, blockNum rpctypes.BlockNumber) (hexutil.Bytes, error) {
+	api.logger.Debug("eth_getStorageAt", "address", address, "key", key, "block number", blockNum)
+	clientCtx := api.clientCtx.WithHeight(blockNum.Int64())
+	res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", evmtypes.ModuleName, address.Hex(), key), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var out evmtypes.QueryResStorage
+	api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
+	return out.Value, nil
+}
+
+// GetTransactionCount returns the number of transactions at the given address up to the given block number.
+func (api *PublicEthereumAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) {
+	api.logger.Debug("eth_getTransactionCount", "address", address, "block number", blockNum)
+	clientCtx := api.clientCtx.WithHeight(blockNum.Int64())
+
+	// Get nonce (sequence) from account
+	from := sdk.AccAddress(address.Bytes())
+	accRet := authtypes.NewAccountRetriever(clientCtx)
+
+	err := accRet.EnsureExists(from)
+	if err != nil {
+		// account doesn't exist yet, return 0
+		n := hexutil.Uint64(0)
+		return &n, nil
+	}
+
+	_, nonce, err := accRet.GetAccountNumberSequence(from)
+	if err != nil {
+		return nil, err
+	}
+
+	n := hexutil.Uint64(nonce)
+	return &n, nil
+}
+
+// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
+func (api *PublicEthereumAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
+	api.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash)
+	res, _, err := api.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
+	if err != nil {
+		return nil
+	}
+
+	var out evmtypes.QueryResBlockNumber
+	if err := api.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
+		return nil
+	}
+
+	resBlock, err := api.clientCtx.Client.Block(&out.Number)
+	if err != nil {
+		return nil
+	}
+
+	n := hexutil.Uint(len(resBlock.Block.Txs))
+	return &n
+}
+
+// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
+func (api *PublicEthereumAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
+	api.logger.Debug("eth_getBlockTransactionCountByNumber", "block number", blockNum)
+	height := blockNum.Int64()
+	resBlock, err := api.clientCtx.Client.Block(&height)
+	if err != nil {
+		return nil
+	}
+
+	n := hexutil.Uint(len(resBlock.Block.Txs))
+	return &n
+}
+
+// GetUncleCountByBlockHash returns the number of uncles in the block idenfied by hash. Always zero.
+func (api *PublicEthereumAPI) GetUncleCountByBlockHash(_ common.Hash) hexutil.Uint {
+	return 0
+}
+
+// GetUncleCountByBlockNumber returns the number of uncles in the block idenfied by number. Always zero.
+func (api *PublicEthereumAPI) GetUncleCountByBlockNumber(_ rpctypes.BlockNumber) hexutil.Uint {
+	return 0
+}
+
+// GetCode returns the contract code at the given address and block number.
+func (api *PublicEthereumAPI) GetCode(address common.Address, blockNumber rpctypes.BlockNumber) (hexutil.Bytes, error) {
+	api.logger.Debug("eth_getCode", "address", address, "block number", blockNumber)
+	clientCtx := api.clientCtx.WithHeight(blockNumber.Int64())
+	res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryCode, address.Hex()), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var out evmtypes.QueryResCode
+	api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
+	return out.Code, nil
+}
+
+// GetTransactionLogs returns the logs given a transaction hash.
+func (api *PublicEthereumAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
+	api.logger.Debug("eth_getTransactionLogs", "hash", txHash)
+	return api.backend.GetTransactionLogs(txHash)
+}
+
+// Sign signs the provided data using the private key of address via Geth's signature standard.
+func (api *PublicEthereumAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
+	api.logger.Debug("eth_sign", "address", address, "data", data)
+	// TODO: Change this functionality to find an unlocked account by address
+
+	key, exist := rpctypes.GetKeyByAddress(api.keys, address)
+	if !exist {
+		return nil, keystore.ErrLocked
+	}
+
+	// Sign the requested hash with the wallet
+	signature, err := key.Sign(data)
+	if err != nil {
+		return nil, err
+	}
+
+	signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
+	return signature, nil
+}
+
+// SendTransaction sends an Ethereum transaction.
+func (api *PublicEthereumAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, error) {
+	api.logger.Debug("eth_sendTransaction", "args", args)
+	// TODO: Change this functionality to find an unlocked account by address
+
+	key, exist := rpctypes.GetKeyByAddress(api.keys, args.From)
+	if !exist {
+		api.logger.Debug("failed to find key in keyring", "key", args.From)
+		return common.Hash{}, keystore.ErrLocked
+	}
+
+	// Mutex lock the address' nonce to avoid assigning it to multiple requests
+	if args.Nonce == nil {
+		api.nonceLock.LockAddr(args.From)
+		defer api.nonceLock.UnlockAddr(args.From)
+	}
+
+	// Assemble transaction from fields
+	tx, err := api.generateFromArgs(args)
+	if err != nil {
+		api.logger.Debug("failed to generate tx", "error", err)
+		return common.Hash{}, err
+	}
+
+	// Sign transaction
+	if err := tx.Sign(api.chainIDEpoch, key.ToECDSA()); err != nil {
+		api.logger.Debug("failed to sign tx", "error", err)
+		return common.Hash{}, err
+	}
+
+	// Encode transaction by default Tx encoder
+	txEncoder := authclient.GetTxEncoder(api.clientCtx.Codec)
+	txBytes, err := txEncoder(tx)
+	if err != nil {
+		return common.Hash{}, err
+	}
+
+	// Broadcast transaction in sync mode (default)
+	// NOTE: If error is encountered on the node, the broadcast will not return an error
+	res, err := api.clientCtx.BroadcastTx(txBytes)
+	if err != nil {
+		return common.Hash{}, err
+	}
+
+	// Return transaction hash
+	return common.HexToHash(res.TxHash), nil
+}
+
+// SendRawTransaction send a raw Ethereum transaction.
+func (api *PublicEthereumAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
+	api.logger.Debug("eth_sendRawTransaction", "data", data)
+	tx := new(evmtypes.MsgEthereumTx)
+
+	// RLP decode raw transaction bytes
+	if err := rlp.DecodeBytes(data, tx); err != nil {
+		// Return nil is for when gasLimit overflows uint64
+		return common.Hash{}, nil
+	}
+
+	// Encode transaction by default Tx encoder
+	txEncoder := authclient.GetTxEncoder(api.clientCtx.Codec)
+	txBytes, err := txEncoder(tx)
+	if err != nil {
+		return common.Hash{}, err
+	}
+
+	// TODO: Possibly log the contract creation address (if recipient address is nil) or tx data
+	// If error is encountered on the node, the broadcast will not return an error
+	res, err := api.clientCtx.BroadcastTx(txBytes)
+	if err != nil {
+		return common.Hash{}, err
+	}
+
+	// Return transaction hash
+	return common.HexToHash(res.TxHash), nil
+}
+
+// Call performs a raw contract call.
+func (api *PublicEthereumAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *map[common.Address]rpctypes.Account) (hexutil.Bytes, error) {
+	api.logger.Debug("eth_call", "args", args, "block number", blockNr)
+	simRes, err := api.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit))
+	if err != nil {
+		return []byte{}, err
+	}
+
+	data, err := evmtypes.DecodeResultData(simRes.Result.Data)
+	if err != nil {
+		return []byte{}, err
+	}
+
+	return (hexutil.Bytes)(data.Ret), nil
+}
+
+// DoCall performs a simulated call operation through the evmtypes. It returns the
+// estimated gas used on the operation or an error if fails.
+func (api *PublicEthereumAPI) doCall(
+	args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, globalGasCap *big.Int,
+) (*sdk.SimulationResponse, error) {
+	// Set height for historical queries
+	clientCtx := api.clientCtx
+
+	if blockNr.Int64() != 0 {
+		clientCtx = api.clientCtx.WithHeight(blockNr.Int64())
+	}
+
+	// Set sender address or use a default if none specified
+	var addr common.Address
+
+	if args.From == nil {
+		addrs, err := api.Accounts()
+		if err == nil && len(addrs) > 0 {
+			addr = addrs[0]
+		}
+	} else {
+		addr = *args.From
+	}
+
+	// Set default gas & gas price if none were set
+	// Change this to uint64(math.MaxUint64 / 2) if gas cap can be configured
+	gas := uint64(ethermint.DefaultRPCGasLimit)
+	if args.Gas != nil {
+		gas = uint64(*args.Gas)
+	}
+	if globalGasCap != nil && globalGasCap.Uint64() < gas {
+		api.logger.Debug("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
+		gas = globalGasCap.Uint64()
+	}
+
+	// Set gas price using default or parameter if passed in
+	gasPrice := new(big.Int).SetUint64(ethermint.DefaultGasPrice)
+	if args.GasPrice != nil {
+		gasPrice = args.GasPrice.ToInt()
+	}
+
+	// Set value for transaction
+	value := new(big.Int)
+	if args.Value != nil {
+		value = args.Value.ToInt()
+	}
+
+	// Set Data if provided
+	var data []byte
+	if args.Data != nil {
+		data = []byte(*args.Data)
+	}
+
+	// Set destination address for call
+	var toAddr sdk.AccAddress
+	if args.To != nil {
+		toAddr = sdk.AccAddress(args.To.Bytes())
+	}
+
+	// Create new call message
+	msg := evmtypes.NewMsgEthermint(0, &toAddr, sdk.NewIntFromBigInt(value), gas,
+		sdk.NewIntFromBigInt(gasPrice), data, sdk.AccAddress(addr.Bytes()))
+
+	if err := msg.ValidateBasic(); err != nil {
+		return nil, err
+	}
+
+	// Generate tx to be used to simulate (signature isn't needed)
+	var stdSig authtypes.StdSignature
+	tx := authtypes.NewStdTx([]sdk.Msg{msg}, authtypes.StdFee{}, []authtypes.StdSignature{stdSig}, "")
+
+	txEncoder := authclient.GetTxEncoder(clientCtx.Codec)
+	txBytes, err := txEncoder(tx)
+	if err != nil {
+		return nil, err
+	}
+
+	// Transaction simulation through query
+	res, _, err := clientCtx.QueryWithData("app/simulate", txBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	var simResponse sdk.SimulationResponse
+	if err := clientCtx.Codec.UnmarshalBinaryBare(res, &simResponse); err != nil {
+		return nil, err
+	}
+
+	return &simResponse, nil
+}
+
+// EstimateGas returns an estimate of gas usage for the given smart contract call.
+// It adds 1,000 gas to the returned value instead of using the gas adjustment
+// param from the SDK.
+func (api *PublicEthereumAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint64, error) {
+	api.logger.Debug("eth_estimateGas", "args", args)
+	simResponse, err := api.doCall(args, 0, big.NewInt(ethermint.DefaultRPCGasLimit))
+	if err != nil {
+		return 0, err
+	}
+
+	// TODO: change 1000 buffer for more accurate buffer (eg: SDK's gasAdjusted)
+	estimatedGas := simResponse.GasInfo.GasUsed
+	gas := estimatedGas + 1000
+
+	return hexutil.Uint64(gas), nil
+}
+
+// GetBlockByHash returns the block identified by hash.
+func (api *PublicEthereumAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
+	api.logger.Debug("eth_getBlockByHash", "hash", hash, "full", fullTx)
+	return api.backend.GetBlockByHash(hash, fullTx)
+}
+
+// GetBlockByNumber returns the block identified by number.
+func (api *PublicEthereumAPI) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
+	api.logger.Debug("eth_getBlockByNumber", "number", blockNum, "full", fullTx)
+	return api.backend.GetBlockByNumber(blockNum, fullTx)
+}
+
+// GetTransactionByHash returns the transaction identified by hash.
+func (api *PublicEthereumAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.Transaction, error) {
+	api.logger.Debug("eth_getTransactionByHash", "hash", hash)
+	tx, err := api.clientCtx.Client.Tx(hash.Bytes(), false)
+	if err != nil {
+		// Return nil for transaction when not found
+		return nil, nil
+	}
+
+	// Can either cache or just leave this out if not necessary
+	block, err := api.clientCtx.Client.Block(&tx.Height)
+	if err != nil {
+		return nil, err
+	}
+
+	blockHash := common.BytesToHash(block.Block.Header.Hash())
+
+	ethTx, err := rpctypes.RawTxToEthTx(api.clientCtx, tx.Tx)
+	if err != nil {
+		return nil, err
+	}
+
+	height := uint64(tx.Height)
+	return rpctypes.NewTransaction(ethTx, common.BytesToHash(tx.Tx.Hash()), blockHash, height, uint64(tx.Index))
+}
+
+// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
+func (api *PublicEthereumAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.Transaction, error) {
+	api.logger.Debug("eth_getTransactionByHashAndIndex", "hash", hash, "index", idx)
+	res, _, err := api.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
+	if err != nil {
+		return nil, err
+	}
+
+	var out evmtypes.QueryResBlockNumber
+	api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
+
+	resBlock, err := api.clientCtx.Client.Block(&out.Number)
+	if err != nil {
+		return nil, err
+	}
+
+	return api.getTransactionByBlockAndIndex(resBlock.Block, idx)
+}
+
+// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
+func (api *PublicEthereumAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.Transaction, error) {
+	api.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
+	height := blockNum.Int64()
+	resBlock, err := api.clientCtx.Client.Block(&height)
+	if err != nil {
+		return nil, err
+	}
+
+	return api.getTransactionByBlockAndIndex(resBlock.Block, idx)
+}
+
+func (api *PublicEthereumAPI) getTransactionByBlockAndIndex(block *tmtypes.Block, idx hexutil.Uint) (*rpctypes.Transaction, error) {
+	// return if index out of bounds
+	if uint64(idx) >= uint64(len(block.Txs)) {
+		return nil, nil
+	}
+
+	ethTx, err := rpctypes.RawTxToEthTx(api.clientCtx, block.Txs[idx])
+	if err != nil {
+		// return nil error if the transaction is not a MsgEthereumTx
+		return nil, nil
+	}
+
+	height := uint64(block.Height)
+	txHash := common.BytesToHash(block.Txs[idx].Hash())
+	blockHash := common.BytesToHash(block.Header.Hash())
+	return rpctypes.NewTransaction(ethTx, txHash, blockHash, height, uint64(idx))
+}
+
+// GetTransactionReceipt returns the transaction receipt identified by hash.
+func (api *PublicEthereumAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
+	api.logger.Debug("eth_getTransactionReceipt", "hash", hash)
+	tx, err := api.clientCtx.Client.Tx(hash.Bytes(), false)
+	if err != nil {
+		// Return nil for transaction when not found
+		return nil, nil
+	}
+
+	// Query block for consensus hash
+	block, err := api.clientCtx.Client.Block(&tx.Height)
+	if err != nil {
+		return nil, err
+	}
+
+	blockHash := common.BytesToHash(block.Block.Header.Hash())
+
+	// Convert tx bytes to eth transaction
+	ethTx, err := rpctypes.RawTxToEthTx(api.clientCtx, tx.Tx)
+	if err != nil {
+		return nil, err
+	}
+
+	from, err := ethTx.VerifySig(ethTx.ChainID())
+	if err != nil {
+		return nil, err
+	}
+
+	// Set status codes based on tx result
+	var status hexutil.Uint
+	if tx.TxResult.IsOK() {
+		status = hexutil.Uint(1)
+	} else {
+		status = hexutil.Uint(0)
+	}
+
+	txData := tx.TxResult.GetData()
+
+	data, err := evmtypes.DecodeResultData(txData)
+	if err != nil {
+		status = 0 // transaction failed
+	}
+
+	if len(data.Logs) == 0 {
+		data.Logs = []*ethtypes.Log{}
+	}
+
+	receipt := map[string]interface{}{
+		// Consensus fields: These fields are defined by the Yellow Paper
+		"status":            status,
+		"cumulativeGasUsed": nil, // ignore until needed
+		"logsBloom":         data.Bloom,
+		"logs":              data.Logs,
+
+		// Implementation fields: These fields are added by geth when processing a transaction.
+		// They are stored in the chain database.
+		"transactionHash": hash,
+		"contractAddress": data.ContractAddress,
+		"gasUsed":         hexutil.Uint64(tx.TxResult.GasUsed),
+
+		// Inclusion information: These fields provide information about the inclusion of the
+		// transaction corresponding to this receipt.
+		"blockHash":        blockHash,
+		"blockNumber":      hexutil.Uint64(tx.Height),
+		"transactionIndex": hexutil.Uint64(tx.Index),
+
+		// sender and receiver (contract or EOA) addreses
+		"from": from,
+		"to":   ethTx.To(),
+	}
+
+	return receipt, nil
+}
+
+// PendingTransactions returns the transactions that are in the transaction pool
+// and have a from address that is one of the accounts this node manages.
+func (api *PublicEthereumAPI) PendingTransactions() ([]*rpctypes.Transaction, error) {
+	api.logger.Debug("eth_getPendingTransactions")
+	return api.backend.PendingTransactions()
+}
+
+// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil.
+func (api *PublicEthereumAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} {
+	return nil
+}
+
+// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil.
+func (api *PublicEthereumAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} {
+	return nil
+}
+
+// GetProof returns an account object with proof and any storage proofs
+func (api *PublicEthereumAPI) GetProof(address common.Address, storageKeys []string, block rpctypes.BlockNumber) (*rpctypes.AccountResult, error) {
+	api.logger.Debug("eth_getProof", "address", address, "keys", storageKeys, "number", block)
+
+	clientCtx := api.clientCtx.WithHeight(int64(block))
+	path := fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryAccount, address.Hex())
+
+	// query eth account at block height
+	resBz, _, err := clientCtx.Query(path)
+	if err != nil {
+		return nil, err
+	}
+
+	var account evmtypes.QueryResAccount
+	clientCtx.Codec.MustUnmarshalJSON(resBz, &account)
+
+	storageProofs := make([]rpctypes.StorageResult, len(storageKeys))
+	opts := client.ABCIQueryOptions{Height: int64(block), Prove: true}
+	for i, k := range storageKeys {
+		// Get value for key
+		vPath := fmt.Sprintf("custom/%s/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryStorage, address, k)
+		vRes, err := api.clientCtx.Client.ABCIQueryWithOptions(vPath, nil, opts)
+		if err != nil {
+			return nil, err
+		}
+
+		var value evmtypes.QueryResStorage
+		clientCtx.Codec.MustUnmarshalJSON(vRes.Response.GetValue(), &value)
+
+		// check for proof
+		proof := vRes.Response.GetProof()
+		proofStr := new(merkle.Proof).String()
+		if proof != nil {
+			proofStr = proof.String()
+		}
+
+		storageProofs[i] = rpctypes.StorageResult{
+			Key:   k,
+			Value: (*hexutil.Big)(common.BytesToHash(value.Value).Big()),
+			Proof: []string{proofStr},
+		}
+	}
+
+	req := abci.RequestQuery{
+		Path:   fmt.Sprintf("store/%s/key", auth.StoreKey),
+		Data:   auth.AddressStoreKey(sdk.AccAddress(address.Bytes())),
+		Height: int64(block),
+		Prove:  true,
+	}
+
+	res, err := clientCtx.QueryABCI(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// check for proof
+	accountProof := res.GetProof()
+	accProofStr := new(merkle.Proof).String()
+	if accountProof != nil {
+		accProofStr = accountProof.String()
+	}
+
+	return &rpctypes.AccountResult{
+		Address:      address,
+		AccountProof: []string{accProofStr},
+		Balance:      (*hexutil.Big)(utils.MustUnmarshalBigInt(account.Balance)),
+		CodeHash:     common.BytesToHash(account.CodeHash),
+		Nonce:        hexutil.Uint64(account.Nonce),
+		StorageHash:  common.Hash{}, // Ethermint doesn't have a storage hash
+		StorageProof: storageProofs,
+	}, nil
+}
+
+// generateFromArgs populates tx message with args (used in RPC API)
+func (api *PublicEthereumAPI) generateFromArgs(args rpctypes.SendTxArgs) (*evmtypes.MsgEthereumTx, error) {
+	var (
+		nonce    uint64
+		gasLimit uint64
+		err      error
+	)
+
+	amount := (*big.Int)(args.Value)
+	gasPrice := (*big.Int)(args.GasPrice)
+
+	if args.GasPrice == nil {
+
+		// Set default gas price
+		// TODO: Change to min gas price from context once available through server/daemon
+		gasPrice = big.NewInt(ethermint.DefaultGasPrice)
+	}
+
+	if args.Nonce == nil {
+		// Get nonce (sequence) from account
+		from := sdk.AccAddress(args.From.Bytes())
+		accRet := authtypes.NewAccountRetriever(api.clientCtx)
+
+		if api.clientCtx.Keybase == nil {
+			return nil, fmt.Errorf("clientCtx.Keybase is nil")
+		}
+
+		_, nonce, err = accRet.GetAccountNumberSequence(from)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		nonce = (uint64)(*args.Nonce)
+	}
+
+	if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
+		return nil, errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
+	}
+
+	// Sets input to either Input or Data, if both are set and not equal error above returns
+	var input []byte
+	if args.Input != nil {
+		input = *args.Input
+	} else if args.Data != nil {
+		input = *args.Data
+	}
+
+	if args.To == nil && len(input) == 0 {
+		// Contract creation
+		return nil, fmt.Errorf("contract creation without any data provided")
+	}
+
+	if args.Gas == nil {
+		callArgs := rpctypes.CallArgs{
+			From:     &args.From,
+			To:       args.To,
+			Gas:      args.Gas,
+			GasPrice: args.GasPrice,
+			Value:    args.Value,
+			Data:     args.Data,
+		}
+		gl, err := api.EstimateGas(callArgs)
+		if err != nil {
+			return nil, err
+		}
+		gasLimit = uint64(gl)
+	} else {
+		gasLimit = (uint64)(*args.Gas)
+	}
+	msg := evmtypes.NewMsgEthereumTx(nonce, args.To, amount, gasLimit, gasPrice, input)
+
+	return &msg, nil
+}
diff --git a/rpc/filter_api.go b/rpc/namespaces/eth/filters/api.go
similarity index 90%
rename from rpc/filter_api.go
rename to rpc/namespaces/eth/filters/api.go
index 6d2fdc84..9e701763 100644
--- a/rpc/filter_api.go
+++ b/rpc/namespaces/eth/filters/api.go
@@ -1,4 +1,4 @@
-package rpc
+package filters
 
 import (
 	"context"
@@ -16,13 +16,14 @@ import (
 
 	clientcontext "github.com/cosmos/cosmos-sdk/client/context"
 
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
 	evmtypes "github.com/cosmos/ethermint/x/evm/types"
 )
 
-// FiltersBackend defines the methods requided by the PublicFilterAPI backend
-type FiltersBackend interface {
-	GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error)
-	HeaderByNumber(blockNr BlockNumber) (*ethtypes.Header, error)
+// Backend defines the methods requided by the PublicFilterAPI backend
+type Backend interface {
+	GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
+	HeaderByNumber(blockNr rpctypes.BlockNumber) (*ethtypes.Header, error)
 	HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
 	GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
 
@@ -47,26 +48,26 @@ type filter struct {
 // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
 // information related to the Ethereum protocol such as blocks, transactions and logs.
 type PublicFilterAPI struct {
-	cliCtx    clientcontext.CLIContext
-	backend   FiltersBackend
+	clientCtx clientcontext.CLIContext
+	backend   Backend
 	events    *EventSystem
 	filtersMu sync.Mutex
 	filters   map[rpc.ID]*filter
 }
 
-// NewPublicFilterAPI returns a new PublicFilterAPI instance.
-func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend FiltersBackend) *PublicFilterAPI {
+// NewAPI returns a new PublicFilterAPI instance.
+func NewAPI(clientCtx clientcontext.CLIContext, backend Backend) *PublicFilterAPI {
 	// start the client to subscribe to Tendermint events
-	err := cliCtx.Client.Start()
+	err := clientCtx.Client.Start()
 	if err != nil {
 		panic(err)
 	}
 
 	api := &PublicFilterAPI{
-		cliCtx:  cliCtx,
-		backend: backend,
-		filters: make(map[rpc.ID]*filter),
-		events:  NewEventSystem(cliCtx.Client),
+		clientCtx: clientCtx,
+		backend:   backend,
+		filters:   make(map[rpc.ID]*filter),
+		events:    NewEventSystem(clientCtx.Client),
 	}
 
 	go api.timeoutLoop()
@@ -209,7 +210,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
 			select {
 			case ev := <-headersCh:
 				data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
-				header := EthHeaderFromTendermint(data.Header)
+				header := rpctypes.EthHeaderFromTendermint(data.Header)
 				api.filtersMu.Lock()
 				if f, found := api.filters[headerSub.ID()]; found {
 					f.hashes = append(f.hashes, header.Hash())
@@ -255,7 +256,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er
 					return
 				}
 
-				header := EthHeaderFromTendermint(data.Header)
+				header := rpctypes.EthHeaderFromTendermint(data.Header)
 				err = notifier.Notify(rpcSub.ID, header)
 				if err != nil {
 					headersSub.err <- err
@@ -317,7 +318,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
 					return
 				}
 
-				logs := filterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
+				logs := FilterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
 
 				for _, log := range logs {
 					err = notifier.Notify(rpcSub.ID, log)
@@ -386,7 +387,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
 					return
 				}
 
-				logs := filterLogs(resultData.Logs, criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics)
+				logs := FilterLogs(resultData.Logs, criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics)
 
 				api.filtersMu.Lock()
 				if f, found := api.filters[filterID]; found {
@@ -407,7 +408,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
 
 // GetLogs returns logs matching the given argument that are stored within the state.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
+// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getLogs
 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) {
 	var filter *Filter
 	if crit.BlockHash != nil {
@@ -433,7 +434,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit
 		return nil, err
 	}
 
-	return returnLogs(logs), err
+	return returnLogs(logs), nil
 }
 
 // UninstallFilter removes the filter with the given filter id.
@@ -533,21 +534,3 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
 		return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ)
 	}
 }
-
-// returnHashes is a helper that will return an empty hash array case the given hash array is nil,
-// otherwise the given hashes array is returned.
-func returnHashes(hashes []common.Hash) []common.Hash {
-	if hashes == nil {
-		return []common.Hash{}
-	}
-	return hashes
-}
-
-// returnLogs is a helper that will return an empty log array in case the given logs array is nil,
-// otherwise the given logs array is returned.
-func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log {
-	if logs == nil {
-		return []*ethtypes.Log{}
-	}
-	return logs
-}
diff --git a/rpc/filter_system.go b/rpc/namespaces/eth/filters/filter_system.go
similarity index 88%
rename from rpc/filter_system.go
rename to rpc/namespaces/eth/filters/filter_system.go
index 00e7aa96..4f308e78 100644
--- a/rpc/filter_system.go
+++ b/rpc/namespaces/eth/filters/filter_system.go
@@ -1,9 +1,8 @@
-package rpc
+package filters
 
 import (
 	"context"
 	"fmt"
-	"log"
 	"time"
 
 	tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
@@ -18,6 +17,7 @@ import (
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
 	evmtypes "github.com/cosmos/ethermint/x/evm/types"
 )
 
@@ -262,7 +262,7 @@ func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) {
 		return
 	}
 	for _, f := range es.index[filters.LogsSubscription] {
-		matchedLogs := filterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
+		matchedLogs := FilterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
 		if len(matchedLogs) > 0 {
 			f.logs <- matchedLogs
 		}
@@ -279,7 +279,7 @@ func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) {
 func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) {
 	data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
 	for _, f := range es.index[filters.BlocksSubscription] {
-		f.headers <- EthHeaderFromTendermint(data.Header)
+		f.headers <- rpctypes.EthHeaderFromTendermint(data.Header)
 	}
 	// TODO: light client
 }
@@ -377,57 +377,3 @@ func (es *EventSystem) eventLoop() {
 	}
 	// }()
 }
-
-// Subscription defines a wrapper for the private subscription
-type Subscription struct {
-	id        rpc.ID
-	typ       filters.Type
-	event     string
-	created   time.Time
-	logsCrit  filters.FilterCriteria
-	logs      chan []*ethtypes.Log
-	hashes    chan []common.Hash
-	headers   chan *ethtypes.Header
-	installed chan struct{} // closed when the filter is installed
-	eventCh   <-chan coretypes.ResultEvent
-	err       chan error
-}
-
-// ID returns the underlying subscription RPC identifier.
-func (s Subscription) ID() rpc.ID {
-	return s.id
-}
-
-// Unsubscribe to the current subscription from Tendermint Websocket. It sends an error to the
-// subscription error channel if unsubscription fails.
-func (s *Subscription) Unsubscribe(es *EventSystem) {
-	if err := es.client.Unsubscribe(es.ctx, string(s.ID()), s.event); err != nil {
-		s.err <- err
-	}
-
-	go func() {
-		defer func() {
-			log.Println("successfully unsubscribed to event", s.event)
-		}()
-
-	uninstallLoop:
-		for {
-			// write uninstall request and consume logs/hashes. This prevents
-			// the eventLoop broadcast method to deadlock when writing to the
-			// filter event channel while the subscription loop is waiting for
-			// this method to return (and thus not reading these events).
-			select {
-			case es.uninstall <- s:
-				break uninstallLoop
-			case <-s.logs:
-			case <-s.hashes:
-			case <-s.headers:
-			}
-		}
-	}()
-}
-
-// Err returns the error channel
-func (s *Subscription) Err() <-chan error {
-	return s.err
-}
diff --git a/rpc/filters.go b/rpc/namespaces/eth/filters/filters.go
similarity index 63%
rename from rpc/filters.go
rename to rpc/namespaces/eth/filters/filters.go
index 2a94040d..c361ef63 100644
--- a/rpc/filters.go
+++ b/rpc/namespaces/eth/filters/filters.go
@@ -1,4 +1,4 @@
-package rpc
+package filters
 
 import (
 	"context"
@@ -9,25 +9,27 @@ import (
 	"github.com/ethereum/go-ethereum/core/bloombits"
 	ethtypes "github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/eth/filters"
+
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
 )
 
 // Filter can be used to retrieve and filter logs.
 type Filter struct {
-	backend  FiltersBackend
+	backend  Backend
 	criteria filters.FilterCriteria
 	matcher  *bloombits.Matcher
 }
 
 // NewBlockFilter creates a new filter which directly inspects the contents of
 // a block to figure out whether it is interesting or not.
-func NewBlockFilter(backend FiltersBackend, criteria filters.FilterCriteria) *Filter {
+func NewBlockFilter(backend Backend, criteria filters.FilterCriteria) *Filter {
 	// Create a generic filter and convert it into a block filter
 	return newFilter(backend, criteria, nil)
 }
 
 // NewRangeFilter creates a new filter which uses a bloom filter on blocks to
 // figure out whether a particular block is interesting or not.
-func NewRangeFilter(backend FiltersBackend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
+func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
 	// Flatten the address and topic filter clauses into a single bloombits filter
 	// system. Since the bloombits are not positional, nil topics are permitted,
 	// which get flattened into a nil byte slice.
@@ -62,7 +64,7 @@ func NewRangeFilter(backend FiltersBackend, begin, end int64, addresses []common
 }
 
 // newFilter returns a new Filter
-func newFilter(backend FiltersBackend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter {
+func newFilter(backend Backend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter {
 	return &Filter{
 		backend:  backend,
 		criteria: criteria,
@@ -89,7 +91,7 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) {
 	}
 
 	// Figure out the limits of the filter range
-	header, err := f.backend.HeaderByNumber(LatestBlockNumber)
+	header, err := f.backend.HeaderByNumber(rpctypes.LatestBlockNumber)
 	if err != nil {
 		return nil, err
 	}
@@ -107,7 +109,7 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) {
 	}
 
 	for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ {
-		block, err := f.backend.GetBlockByNumber(BlockNumber(i), true)
+		block, err := f.backend.GetBlockByNumber(rpctypes.BlockNumber(i), true)
 		if err != nil {
 			return logs, err
 		}
@@ -139,7 +141,7 @@ func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) {
 	for _, logs := range logsList {
 		unfiltered = append(unfiltered, logs...)
 	}
-	logs := filterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics)
+	logs := FilterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics)
 	if len(logs) == 0 {
 		return []*ethtypes.Log{}, nil
 	}
@@ -162,81 +164,5 @@ func (f *Filter) checkMatches(transactions []common.Hash) []*ethtypes.Log {
 		unfiltered = append(unfiltered, logs...)
 	}
 
-	return filterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics)
-}
-
-// filterLogs creates a slice of logs matching the given criteria.
-// [] -> anything
-// [A] -> A in first position of log topics, anything after
-// [null, B] -> anything in first position, B in second position
-// [A, B] -> A in first position and B in second position
-// [[A, B], [A, B]] -> A or B in first position, A or B in second position
-func filterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log {
-	var ret []*ethtypes.Log
-Logs:
-	for _, log := range logs {
-		if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
-			continue
-		}
-		if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
-			continue
-		}
-		if len(addresses) > 0 && !includes(addresses, log.Address) {
-			continue
-		}
-		// If the to filtered topics is greater than the amount of topics in logs, skip.
-		if len(topics) > len(log.Topics) {
-			continue
-		}
-		for i, sub := range topics {
-			match := len(sub) == 0 // empty rule set == wildcard
-			for _, topic := range sub {
-				if log.Topics[i] == topic {
-					match = true
-					break
-				}
-			}
-			if !match {
-				continue Logs
-			}
-		}
-		ret = append(ret, log)
-	}
-	return ret
-}
-
-func includes(addresses []common.Address, a common.Address) bool {
-	for _, addr := range addresses {
-		if addr == a {
-			return true
-		}
-	}
-
-	return false
-}
-
-func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
-	var included bool
-	if len(addresses) > 0 {
-		for _, addr := range addresses {
-			if ethtypes.BloomLookup(bloom, addr) {
-				included = true
-				break
-			}
-		}
-		if !included {
-			return false
-		}
-	}
-
-	for _, sub := range topics {
-		included = len(sub) == 0 // empty rule set == wildcard
-		for _, topic := range sub {
-			if ethtypes.BloomLookup(bloom, topic) {
-				included = true
-				break
-			}
-		}
-	}
-	return included
+	return FilterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics)
 }
diff --git a/rpc/namespaces/eth/filters/subscription.go b/rpc/namespaces/eth/filters/subscription.go
new file mode 100644
index 00000000..ee09f6eb
--- /dev/null
+++ b/rpc/namespaces/eth/filters/subscription.go
@@ -0,0 +1,71 @@
+package filters
+
+import (
+	"log"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	ethtypes "github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/eth/filters"
+	"github.com/ethereum/go-ethereum/rpc"
+	coretypes "github.com/tendermint/tendermint/rpc/core/types"
+)
+
+// Subscription defines a wrapper for the private subscription
+type Subscription struct {
+	id        rpc.ID
+	typ       filters.Type
+	event     string
+	created   time.Time
+	logsCrit  filters.FilterCriteria
+	logs      chan []*ethtypes.Log
+	hashes    chan []common.Hash
+	headers   chan *ethtypes.Header
+	installed chan struct{} // closed when the filter is installed
+	eventCh   <-chan coretypes.ResultEvent
+	err       chan error
+}
+
+// ID returns the underlying subscription RPC identifier.
+func (s Subscription) ID() rpc.ID {
+	return s.id
+}
+
+// Unsubscribe to the current subscription from Tendermint Websocket. It sends an error to the
+// subscription error channel if unsubscription fails.
+func (s *Subscription) Unsubscribe(es *EventSystem) {
+	if err := es.client.Unsubscribe(es.ctx, string(s.ID()), s.event); err != nil {
+		s.err <- err
+	}
+
+	go func() {
+		defer func() {
+			log.Println("successfully unsubscribed to event", s.event)
+		}()
+
+	uninstallLoop:
+		for {
+			// write uninstall request and consume logs/hashes. This prevents
+			// the eventLoop broadcast method to deadlock when writing to the
+			// filter event channel while the subscription loop is waiting for
+			// this method to return (and thus not reading these events).
+			select {
+			case es.uninstall <- s:
+				break uninstallLoop
+			case <-s.logs:
+			case <-s.hashes:
+			case <-s.headers:
+			}
+		}
+	}()
+}
+
+// Err returns the error channel
+func (s *Subscription) Err() <-chan error {
+	return s.err
+}
+
+// Event returns the tendermint result event channel
+func (s *Subscription) Event() <-chan coretypes.ResultEvent {
+	return s.eventCh
+}
diff --git a/rpc/namespaces/eth/filters/utils.go b/rpc/namespaces/eth/filters/utils.go
new file mode 100644
index 00000000..fd65181e
--- /dev/null
+++ b/rpc/namespaces/eth/filters/utils.go
@@ -0,0 +1,102 @@
+package filters
+
+import (
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	ethtypes "github.com/ethereum/go-ethereum/core/types"
+)
+
+// filterLogs creates a slice of logs matching the given criteria.
+// [] -> anything
+// [A] -> A in first position of log topics, anything after
+// [null, B] -> anything in first position, B in second position
+// [A, B] -> A in first position and B in second position
+// [[A, B], [A, B]] -> A or B in first position, A or B in second position
+func FilterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log {
+	var ret []*ethtypes.Log
+Logs:
+	for _, log := range logs {
+		if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
+			continue
+		}
+		if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
+			continue
+		}
+		if len(addresses) > 0 && !includes(addresses, log.Address) {
+			continue
+		}
+		// If the to filtered topics is greater than the amount of topics in logs, skip.
+		if len(topics) > len(log.Topics) {
+			continue
+		}
+		for i, sub := range topics {
+			match := len(sub) == 0 // empty rule set == wildcard
+			for _, topic := range sub {
+				if log.Topics[i] == topic {
+					match = true
+					break
+				}
+			}
+			if !match {
+				continue Logs
+			}
+		}
+		ret = append(ret, log)
+	}
+	return ret
+}
+
+func includes(addresses []common.Address, a common.Address) bool {
+	for _, addr := range addresses {
+		if addr == a {
+			return true
+		}
+	}
+
+	return false
+}
+
+func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
+	var included bool
+	if len(addresses) > 0 {
+		for _, addr := range addresses {
+			if ethtypes.BloomLookup(bloom, addr) {
+				included = true
+				break
+			}
+		}
+		if !included {
+			return false
+		}
+	}
+
+	for _, sub := range topics {
+		included = len(sub) == 0 // empty rule set == wildcard
+		for _, topic := range sub {
+			if ethtypes.BloomLookup(bloom, topic) {
+				included = true
+				break
+			}
+		}
+	}
+	return included
+}
+
+// returnHashes is a helper that will return an empty hash array case the given hash array is nil,
+// otherwise the given hashes array is returned.
+func returnHashes(hashes []common.Hash) []common.Hash {
+	if hashes == nil {
+		return []common.Hash{}
+	}
+	return hashes
+}
+
+// returnLogs is a helper that will return an empty log array in case the given logs array is nil,
+// otherwise the given logs array is returned.
+func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log {
+	if logs == nil {
+		return []*ethtypes.Log{}
+	}
+	return logs
+}
diff --git a/rpc/net_api.go b/rpc/namespaces/net/api.go
similarity index 53%
rename from rpc/net_api.go
rename to rpc/namespaces/net/api.go
index 2d065930..64d4c528 100644
--- a/rpc/net_api.go
+++ b/rpc/namespaces/net/api.go
@@ -1,12 +1,9 @@
-package rpc
+package net
 
 import (
 	"fmt"
 
-	"github.com/spf13/viper"
-
 	"github.com/cosmos/cosmos-sdk/client/context"
-	"github.com/cosmos/cosmos-sdk/client/flags"
 	ethermint "github.com/cosmos/ethermint/types"
 )
 
@@ -15,11 +12,10 @@ type PublicNetAPI struct {
 	networkVersion uint64
 }
 
-// NewPublicNetAPI creates an instance of the public Net Web3 API.
-func NewPublicNetAPI(_ context.CLIContext) *PublicNetAPI {
-	chainID := viper.GetString(flags.FlagChainID)
+// NewAPI creates an instance of the public Net Web3 API.
+func NewAPI(clientCtx context.CLIContext) *PublicNetAPI {
 	// parse the chainID from a integer string
-	chainIDEpoch, err := ethermint.ParseChainID(chainID)
+	chainIDEpoch, err := ethermint.ParseChainID(clientCtx.ChainID)
 	if err != nil {
 		panic(err)
 	}
@@ -30,6 +26,6 @@ func NewPublicNetAPI(_ context.CLIContext) *PublicNetAPI {
 }
 
 // Version returns the current ethereum protocol version.
-func (s *PublicNetAPI) Version() string {
-	return fmt.Sprintf("%d", s.networkVersion)
+func (api *PublicNetAPI) Version() string {
+	return fmt.Sprintf("%d", api.networkVersion)
 }
diff --git a/rpc/personal_api.go b/rpc/namespaces/personal/api.go
similarity index 56%
rename from rpc/personal_api.go
rename to rpc/namespaces/personal/api.go
index d3bcfcb3..f78d7409 100644
--- a/rpc/personal_api.go
+++ b/rpc/namespaces/personal/api.go
@@ -1,4 +1,4 @@
-package rpc
+package personal
 
 import (
 	"bytes"
@@ -7,12 +7,10 @@ import (
 	"os"
 	"time"
 
-	"github.com/spf13/viper"
+	"github.com/tendermint/tendermint/libs/log"
 
-	"github.com/cosmos/cosmos-sdk/client/flags"
 	"github.com/cosmos/cosmos-sdk/crypto/keys"
 	"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
-	sdk "github.com/cosmos/cosmos-sdk/types"
 
 	"github.com/ethereum/go-ethereum/accounts"
 	"github.com/ethereum/go-ethereum/common"
@@ -21,58 +19,43 @@ import (
 
 	"github.com/cosmos/ethermint/crypto/ethsecp256k1"
 	"github.com/cosmos/ethermint/crypto/hd"
-	params "github.com/cosmos/ethermint/rpc/args"
+	"github.com/cosmos/ethermint/rpc/namespaces/eth"
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
 )
 
-// PersonalEthAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
-type PersonalEthAPI struct {
-	ethAPI   *PublicEthAPI
+// PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
+type PrivateAccountAPI struct {
+	ethAPI   *eth.PublicEthereumAPI
+	logger   log.Logger
 	keyInfos []keys.Info // all keys, both locked and unlocked. unlocked keys are stored in ethAPI.keys
 }
 
-// NewPersonalEthAPI creates an instance of the public Personal Eth API.
-func NewPersonalEthAPI(ethAPI *PublicEthAPI) *PersonalEthAPI {
-	api := &PersonalEthAPI{
+// NewAPI creates an instance of the public Personal Eth API.
+func NewAPI(ethAPI *eth.PublicEthereumAPI) *PrivateAccountAPI {
+	api := &PrivateAccountAPI{
 		ethAPI: ethAPI,
+		logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc", "namespace", "personal"),
 	}
 
-	infos, err := api.getKeybaseInfo()
+	err := api.ethAPI.GetKeyringInfo()
 	if err != nil {
 		return api
 	}
 
-	api.keyInfos = infos
-	return api
-}
-
-func (e *PersonalEthAPI) getKeybaseInfo() ([]keys.Info, error) {
-	e.ethAPI.keybaseLock.Lock()
-	defer e.ethAPI.keybaseLock.Unlock()
-
-	if e.ethAPI.cliCtx.Keybase == nil {
-		keybase, err := keys.NewKeyring(
-			sdk.KeyringServiceName(),
-			viper.GetString(flags.FlagKeyringBackend),
-			viper.GetString(flags.FlagHome),
-			e.ethAPI.cliCtx.Input,
-			hd.EthSecp256k1Options()...,
-		)
-		if err != nil {
-			return nil, err
-		}
-
-		e.ethAPI.cliCtx.Keybase = keybase
+	api.keyInfos, err = api.ethAPI.ClientCtx().Keybase.List()
+	if err != nil {
+		return api
 	}
 
-	return e.ethAPI.cliCtx.Keybase.List()
+	return api
 }
 
 // ImportRawKey armors and encrypts a given raw hex encoded ECDSA key and stores it into the key directory.
 // The name of the key will have the format "personal_<length-keys>", where <length-keys> is the total number of
 // keys stored on the keyring.
 // NOTE: The key will be both armored and encrypted using the same passphrase.
-func (e *PersonalEthAPI) ImportRawKey(privkey, password string) (common.Address, error) {
-	e.ethAPI.logger.Debug("personal_importRawKey")
+func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Address, error) {
+	api.logger.Debug("personal_importRawKey")
 	priv, err := crypto.HexToECDSA(privkey)
 	if err != nil {
 		return common.Address{}, err
@@ -83,33 +66,33 @@ func (e *PersonalEthAPI) ImportRawKey(privkey, password string) (common.Address,
 	armor := mintkey.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
 
 	// ignore error as we only care about the length of the list
-	list, _ := e.ethAPI.cliCtx.Keybase.List()
+	list, _ := api.ethAPI.ClientCtx().Keybase.List()
 	privKeyName := fmt.Sprintf("personal_%d", len(list))
 
-	if err := e.ethAPI.cliCtx.Keybase.ImportPrivKey(privKeyName, armor, password); err != nil {
+	if err := api.ethAPI.ClientCtx().Keybase.ImportPrivKey(privKeyName, armor, password); err != nil {
 		return common.Address{}, err
 	}
 
 	addr := common.BytesToAddress(privKey.PubKey().Address().Bytes())
 
-	info, err := e.ethAPI.cliCtx.Keybase.Get(privKeyName)
+	info, err := api.ethAPI.ClientCtx().Keybase.Get(privKeyName)
 	if err != nil {
 		return common.Address{}, err
 	}
 
 	// append key and info to be able to lock and list the account
-	//e.ethAPI.keys = append(e.ethAPI.keys, privKey)
-	e.keyInfos = append(e.keyInfos, info)
-	e.ethAPI.logger.Info("key successfully imported", "name", privKeyName, "address", addr.String())
+	//api.ethAPI.keys = append(api.ethAPI.keys, privKey)
+	api.keyInfos = append(api.keyInfos, info)
+	api.logger.Info("key successfully imported", "name", privKeyName, "address", addr.String())
 
 	return addr, nil
 }
 
 // ListAccounts will return a list of addresses for accounts this node manages.
-func (e *PersonalEthAPI) ListAccounts() ([]common.Address, error) {
-	e.ethAPI.logger.Debug("personal_listAccounts")
+func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
+	api.logger.Debug("personal_listAccounts")
 	addrs := []common.Address{}
-	for _, info := range e.keyInfos {
+	for _, info := range api.keyInfos {
 		addressBytes := info.GetPubKey().Address().Bytes()
 		addrs = append(addrs, common.BytesToAddress(addressBytes))
 	}
@@ -119,20 +102,21 @@ func (e *PersonalEthAPI) ListAccounts() ([]common.Address, error) {
 
 // LockAccount will lock the account associated with the given address when it's unlocked.
 // It removes the key corresponding to the given address from the API's local keys.
-func (e *PersonalEthAPI) LockAccount(address common.Address) bool {
-	e.ethAPI.logger.Debug("personal_lockAccount", "address", address.String())
+func (api *PrivateAccountAPI) LockAccount(address common.Address) bool {
+	api.logger.Debug("personal_lockAccount", "address", address.String())
 
-	for i, key := range e.ethAPI.keys {
+	keys := api.ethAPI.GetKeys()
+	for i, key := range keys {
 		if !bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
 			continue
 		}
 
-		tmp := make([]ethsecp256k1.PrivKey, len(e.ethAPI.keys)-1)
-		copy(tmp[:i], e.ethAPI.keys[:i])
-		copy(tmp[i:], e.ethAPI.keys[i+1:])
-		e.ethAPI.keys = tmp
+		tmp := make([]ethsecp256k1.PrivKey, len(keys)-1)
+		copy(tmp[:i], keys[:i])
+		copy(tmp[i:], keys[i+1:])
+		api.ethAPI.SetKeys(tmp)
 
-		e.ethAPI.logger.Debug("account unlocked", "address", address.String())
+		api.logger.Debug("account unlocked", "address", address.String())
 		return true
 	}
 
@@ -140,25 +124,21 @@ func (e *PersonalEthAPI) LockAccount(address common.Address) bool {
 }
 
 // NewAccount will create a new account and returns the address for the new account.
-func (e *PersonalEthAPI) NewAccount(password string) (common.Address, error) {
-	e.ethAPI.logger.Debug("personal_newAccount")
-	_, err := e.getKeybaseInfo()
-	if err != nil {
-		return common.Address{}, err
-	}
+func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
+	api.logger.Debug("personal_newAccount")
 
 	name := "key_" + time.Now().UTC().Format(time.RFC3339)
-	info, _, err := e.ethAPI.cliCtx.Keybase.CreateMnemonic(name, keys.English, password, hd.EthSecp256k1)
+	info, _, err := api.ethAPI.ClientCtx().Keybase.CreateMnemonic(name, keys.English, password, hd.EthSecp256k1)
 	if err != nil {
 		return common.Address{}, err
 	}
 
-	e.keyInfos = append(e.keyInfos, info)
+	api.keyInfos = append(api.keyInfos, info)
 
 	addr := common.BytesToAddress(info.GetPubKey().Address().Bytes())
-	e.ethAPI.logger.Info("Your new key was generated", "address", addr.String())
-	e.ethAPI.logger.Info("Please backup your key file!", "path", os.Getenv("HOME")+"/.ethermintcli/"+name)
-	e.ethAPI.logger.Info("Please remember your password!")
+	api.logger.Info("Your new key was generated", "address", addr.String())
+	api.logger.Info("Please backup your key file!", "path", os.Getenv("HOME")+"/.ethermintd/"+name)
+	api.logger.Info("Please remember your password!")
 	return addr, nil
 }
 
@@ -166,13 +146,13 @@ func (e *PersonalEthAPI) NewAccount(password string) (common.Address, error) {
 // the given password for duration seconds. If duration is nil it will use a
 // default of 300 seconds. It returns an indication if the account was unlocked.
 // It exports the private key corresponding to the given address from the keyring and stores it in the API's local keys.
-func (e *PersonalEthAPI) UnlockAccount(_ context.Context, addr common.Address, password string, _ *uint64) (bool, error) { // nolint: interfacer
-	e.ethAPI.logger.Debug("personal_unlockAccount", "address", addr.String())
+func (api *PrivateAccountAPI) UnlockAccount(_ context.Context, addr common.Address, password string, _ *uint64) (bool, error) { // nolint: interfacer
+	api.logger.Debug("personal_unlockAccount", "address", addr.String())
 	// TODO: use duration
 
 	var keyInfo keys.Info
 
-	for _, info := range e.keyInfos {
+	for _, info := range api.keyInfos {
 		addressBytes := info.GetPubKey().Address().Bytes()
 		if bytes.Equal(addressBytes, addr[:]) {
 			keyInfo = info
@@ -184,31 +164,26 @@ func (e *PersonalEthAPI) UnlockAccount(_ context.Context, addr common.Address, p
 		return false, fmt.Errorf("cannot find key with given address %s", addr.String())
 	}
 
-	// exporting private key only works on local keys
-	if keyInfo.GetType() != keys.TypeLocal {
-		return false, fmt.Errorf("key type must be %s, got %s", keys.TypeLedger.String(), keyInfo.GetType().String())
-	}
-
-	privKey, err := e.ethAPI.cliCtx.Keybase.ExportPrivateKeyObject(keyInfo.GetName(), password)
+	privKey, err := api.ethAPI.ClientCtx().Keybase.ExportPrivateKeyObject(keyInfo.GetName(), password)
 	if err != nil {
 		return false, err
 	}
 
-	emintKey, ok := privKey.(ethsecp256k1.PrivKey)
+	ethermintPrivKey, ok := privKey.(ethsecp256k1.PrivKey)
 	if !ok {
-		return false, fmt.Errorf("invalid private key type: %T", privKey)
+		return false, fmt.Errorf("invalid private key type %T, expected %T", privKey, &ethsecp256k1.PrivKey{})
 	}
 
-	e.ethAPI.keys = append(e.ethAPI.keys, emintKey)
-	e.ethAPI.logger.Debug("account unlocked", "address", addr.String())
+	api.ethAPI.SetKeys(append(api.ethAPI.GetKeys(), ethermintPrivKey))
+	api.logger.Debug("account unlocked", "address", addr.String())
 	return true, nil
 }
 
 // SendTransaction will create a transaction from the given arguments and
 // tries to sign it with the key associated with args.To. If the given password isn't
 // able to decrypt the key it fails.
-func (e *PersonalEthAPI) SendTransaction(_ context.Context, args params.SendTxArgs, _ string) (common.Hash, error) {
-	return e.ethAPI.SendTransaction(args)
+func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args rpctypes.SendTxArgs, _ string) (common.Hash, error) {
+	return api.ethAPI.SendTransaction(args)
 }
 
 // Sign calculates an Ethereum ECDSA signature for:
@@ -220,10 +195,10 @@ func (e *PersonalEthAPI) SendTransaction(_ context.Context, args params.SendTxAr
 // The key used to calculate the signature is decrypted with the given password.
 //
 // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
-func (e *PersonalEthAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
-	e.ethAPI.logger.Debug("personal_sign", "data", data, "address", addr.String())
+func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
+	api.logger.Debug("personal_sign", "data", data, "address", addr.String())
 
-	key, ok := checkKeyInKeyring(e.ethAPI.keys, addr)
+	key, ok := rpctypes.GetKeyByAddress(api.ethAPI.GetKeys(), addr)
 	if !ok {
 		return nil, fmt.Errorf("cannot find key with address %s", addr.String())
 	}
@@ -247,8 +222,8 @@ func (e *PersonalEthAPI) Sign(_ context.Context, data hexutil.Bytes, addr common
 // the V value must be 27 or 28 for legacy reasons.
 //
 // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecove
-func (e *PersonalEthAPI) EcRecover(_ context.Context, data, sig hexutil.Bytes) (common.Address, error) {
-	e.ethAPI.logger.Debug("personal_ecRecover", "data", data, "sig", sig)
+func (api *PrivateAccountAPI) EcRecover(_ context.Context, data, sig hexutil.Bytes) (common.Address, error) {
+	api.logger.Debug("personal_ecRecover", "data", data, "sig", sig)
 
 	if len(sig) != crypto.SignatureLength {
 		return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
diff --git a/rpc/web3_api.go b/rpc/namespaces/web3/api.go
similarity index 68%
rename from rpc/web3_api.go
rename to rpc/namespaces/web3/api.go
index edff98fd..24885692 100644
--- a/rpc/web3_api.go
+++ b/rpc/namespaces/web3/api.go
@@ -1,4 +1,4 @@
-package rpc
+package web3
 
 import (
 	"github.com/cosmos/ethermint/version"
@@ -10,17 +10,17 @@ import (
 // PublicWeb3API is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec.
 type PublicWeb3API struct{}
 
-// NewPublicWeb3API creates an instance of the Web3 API.
-func NewPublicWeb3API() *PublicWeb3API {
+// New creates an instance of the Web3 API.
+func NewAPI() *PublicWeb3API {
 	return &PublicWeb3API{}
 }
 
 // ClientVersion returns the client version in the Web3 user agent format.
-func (a *PublicWeb3API) ClientVersion() string {
+func (PublicWeb3API) ClientVersion() string {
 	return version.ClientVersion()
 }
 
 // Sha3 returns the keccak-256 hash of the passed-in input.
-func (a *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
+func (PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
 	return crypto.Keccak256(input)
 }
diff --git a/rpc/addrlock.go b/rpc/types/addrlock.go
similarity index 98%
rename from rpc/addrlock.go
rename to rpc/types/addrlock.go
index 51f6b9c3..e512c03c 100644
--- a/rpc/addrlock.go
+++ b/rpc/types/addrlock.go
@@ -1,4 +1,4 @@
-package rpc
+package types
 
 import (
 	"sync"
diff --git a/rpc/types.go b/rpc/types/block.go
similarity index 82%
rename from rpc/types.go
rename to rpc/types/block.go
index 935947a2..0951da25 100644
--- a/rpc/types.go
+++ b/rpc/types/block.go
@@ -1,4 +1,4 @@
-package rpc
+package types
 
 import (
 	"fmt"
@@ -63,5 +63,16 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
 
 // Int64 converts block number to primitive type
 func (bn BlockNumber) Int64() int64 {
-	return (int64)(bn)
+	return int64(bn)
+}
+
+// TmHeight is a util function used for the Tendermint RPC client. It returns
+// nil if the block number is "latest". Otherwise, it returns the pointer of the
+// int64 value of the height.
+func (bn BlockNumber) TmHeight() *int64 {
+	if bn == LatestBlockNumber {
+		return nil
+	}
+	height := bn.Int64()
+	return &height
 }
diff --git a/rpc/types/types.go b/rpc/types/types.go
new file mode 100644
index 00000000..72bf22d7
--- /dev/null
+++ b/rpc/types/types.go
@@ -0,0 +1,85 @@
+package types
+
+import (
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+// Copied the Account and StorageResult types since they are registered under an
+// internal pkg on geth.
+
+// AccountResult struct for account proof
+type AccountResult struct {
+	Address      common.Address  `json:"address"`
+	AccountProof []string        `json:"accountProof"`
+	Balance      *hexutil.Big    `json:"balance"`
+	CodeHash     common.Hash     `json:"codeHash"`
+	Nonce        hexutil.Uint64  `json:"nonce"`
+	StorageHash  common.Hash     `json:"storageHash"`
+	StorageProof []StorageResult `json:"storageProof"`
+}
+
+// StorageResult defines the format for storage proof return
+type StorageResult struct {
+	Key   string       `json:"key"`
+	Value *hexutil.Big `json:"value"`
+	Proof []string     `json:"proof"`
+}
+
+// Transaction represents a transaction returned to RPC clients.
+type Transaction struct {
+	BlockHash        *common.Hash    `json:"blockHash"`
+	BlockNumber      *hexutil.Big    `json:"blockNumber"`
+	From             common.Address  `json:"from"`
+	Gas              hexutil.Uint64  `json:"gas"`
+	GasPrice         *hexutil.Big    `json:"gasPrice"`
+	Hash             common.Hash     `json:"hash"`
+	Input            hexutil.Bytes   `json:"input"`
+	Nonce            hexutil.Uint64  `json:"nonce"`
+	To               *common.Address `json:"to"`
+	TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
+	Value            *hexutil.Big    `json:"value"`
+	V                *hexutil.Big    `json:"v"`
+	R                *hexutil.Big    `json:"r"`
+	S                *hexutil.Big    `json:"s"`
+}
+
+// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
+// Duplicate struct definition since geth struct is in internal package
+// Ref: https://github.com/ethereum/go-ethereum/blob/release/1.9/internal/ethapi/api.go#L1346
+type SendTxArgs struct {
+	From     common.Address  `json:"from"`
+	To       *common.Address `json:"to"`
+	Gas      *hexutil.Uint64 `json:"gas"`
+	GasPrice *hexutil.Big    `json:"gasPrice"`
+	Value    *hexutil.Big    `json:"value"`
+	Nonce    *hexutil.Uint64 `json:"nonce"`
+	// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
+	// newer name and should be preferred by clients.
+	Data  *hexutil.Bytes `json:"data"`
+	Input *hexutil.Bytes `json:"input"`
+}
+
+// CallArgs represents the arguments for a call.
+type CallArgs struct {
+	From     *common.Address `json:"from"`
+	To       *common.Address `json:"to"`
+	Gas      *hexutil.Uint64 `json:"gas"`
+	GasPrice *hexutil.Big    `json:"gasPrice"`
+	Value    *hexutil.Big    `json:"value"`
+	Data     *hexutil.Bytes  `json:"data"`
+}
+
+// Account indicates the overriding fields of account during the execution of
+// a message call.
+// NOTE: state and stateDiff can't be specified at the same time. If state is
+// set, message execution will only use the data in the given state. Otherwise
+// if statDiff is set, all diff will be applied first and then execute the call
+// message.
+type Account struct {
+	Nonce     *hexutil.Uint64              `json:"nonce"`
+	Code      *hexutil.Bytes               `json:"code"`
+	Balance   **hexutil.Big                `json:"balance"`
+	State     *map[common.Hash]common.Hash `json:"state"`
+	StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
+}
diff --git a/rpc/types/utils.go b/rpc/types/utils.go
new file mode 100644
index 00000000..e1bbaec7
--- /dev/null
+++ b/rpc/types/utils.go
@@ -0,0 +1,191 @@
+package types
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"math/big"
+
+	tmbytes "github.com/tendermint/tendermint/libs/bytes"
+	tmtypes "github.com/tendermint/tendermint/types"
+
+	clientcontext "github.com/cosmos/cosmos-sdk/client/context"
+	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+
+	"github.com/cosmos/ethermint/crypto/ethsecp256k1"
+	evmtypes "github.com/cosmos/ethermint/x/evm/types"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	ethtypes "github.com/ethereum/go-ethereum/core/types"
+)
+
+// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
+func RawTxToEthTx(clientCtx clientcontext.CLIContext, bz []byte) (*evmtypes.MsgEthereumTx, error) {
+	tx, err := evmtypes.TxDecoder(clientCtx.Codec)(bz)
+	if err != nil {
+		return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
+	}
+
+	ethTx, ok := tx.(evmtypes.MsgEthereumTx)
+	if !ok {
+		return nil, fmt.Errorf("invalid transaction type %T, expected %T", tx, &evmtypes.MsgEthereumTx{})
+	}
+	return &ethTx, nil
+}
+
+// NewTransaction returns a transaction that will serialize to the RPC
+// representation, with the given location metadata set (if available).
+func NewTransaction(tx *evmtypes.MsgEthereumTx, txHash, blockHash common.Hash, blockNumber, index uint64) (*Transaction, error) {
+	// Verify signature and retrieve sender address
+	from, err := tx.VerifySig(tx.ChainID())
+	if err != nil {
+		return nil, err
+	}
+
+	rpcTx := &Transaction{
+		From:     from,
+		Gas:      hexutil.Uint64(tx.Data.GasLimit),
+		GasPrice: (*hexutil.Big)(tx.Data.Price),
+		Hash:     txHash,
+		Input:    hexutil.Bytes(tx.Data.Payload),
+		Nonce:    hexutil.Uint64(tx.Data.AccountNonce),
+		To:       tx.To(),
+		Value:    (*hexutil.Big)(tx.Data.Amount),
+		V:        (*hexutil.Big)(tx.Data.V),
+		R:        (*hexutil.Big)(tx.Data.R),
+		S:        (*hexutil.Big)(tx.Data.S),
+	}
+
+	if blockHash != (common.Hash{}) {
+		rpcTx.BlockHash = &blockHash
+		rpcTx.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
+		rpcTx.TransactionIndex = (*hexutil.Uint64)(&index)
+	}
+
+	return rpcTx, nil
+}
+
+// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum blockfrom a given Tendermint block.
+func EthBlockFromTendermint(clientCtx clientcontext.CLIContext, block *tmtypes.Block) (map[string]interface{}, error) {
+	gasLimit, err := BlockMaxGasFromConsensusParams(context.Background(), clientCtx)
+	if err != nil {
+		return nil, err
+	}
+
+	transactions, gasUsed, err := EthTransactionsFromTendermint(clientCtx, block.Txs)
+	if err != nil {
+		return nil, err
+	}
+
+	res, _, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s/%d", evmtypes.ModuleName, evmtypes.QueryBloom, block.Height))
+	if err != nil {
+		return nil, err
+	}
+
+	var bloomRes evmtypes.QueryBloomFilter
+	clientCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
+
+	bloom := bloomRes.Bloom
+
+	return formatBlock(block.Header, block.Size(), gasLimit, gasUsed, transactions, bloom), nil
+}
+
+// EthHeaderFromTendermint is an util function that returns an Ethereum Header
+// from a tendermint Header.
+func EthHeaderFromTendermint(header tmtypes.Header) *ethtypes.Header {
+	return &ethtypes.Header{
+		ParentHash:  common.BytesToHash(header.LastBlockID.Hash.Bytes()),
+		UncleHash:   common.Hash{},
+		Coinbase:    common.Address{},
+		Root:        common.BytesToHash(header.AppHash),
+		TxHash:      common.BytesToHash(header.DataHash),
+		ReceiptHash: common.Hash{},
+		Difficulty:  nil,
+		Number:      big.NewInt(header.Height),
+		Time:        uint64(header.Time.Unix()),
+		Extra:       nil,
+		MixDigest:   common.Hash{},
+		Nonce:       ethtypes.BlockNonce{},
+	}
+}
+
+// EthTransactionsFromTendermint returns a slice of ethereum transaction hashes and the total gas usage from a set of
+// tendermint block transactions.
+func EthTransactionsFromTendermint(clientCtx clientcontext.CLIContext, txs []tmtypes.Tx) ([]common.Hash, *big.Int, error) {
+	transactionHashes := []common.Hash{}
+	gasUsed := big.NewInt(0)
+
+	for _, tx := range txs {
+		ethTx, err := RawTxToEthTx(clientCtx, tx)
+		if err != nil {
+			// continue to next transaction in case it's not a MsgEthereumTx
+			continue
+		}
+		// TODO: Remove gas usage calculation if saving gasUsed per block
+		gasUsed.Add(gasUsed, ethTx.Fee())
+		transactionHashes = append(transactionHashes, common.BytesToHash(tx.Hash()))
+	}
+
+	return transactionHashes, gasUsed, nil
+}
+
+// BlockMaxGasFromConsensusParams returns the gas limit for the latest block from the chain consensus params.
+func BlockMaxGasFromConsensusParams(_ context.Context, clientCtx clientcontext.CLIContext) (int64, error) {
+	resConsParams, err := clientCtx.Client.ConsensusParams(nil)
+	if err != nil {
+		return 0, err
+	}
+
+	gasLimit := resConsParams.ConsensusParams.Block.MaxGas
+	if gasLimit == -1 {
+		// Sets gas limit to max uint32 to not error with javascript dev tooling
+		// This -1 value indicating no block gas limit is set to max uint64 with geth hexutils
+		// which errors certain javascript dev tooling which only supports up to 53 bits
+		gasLimit = int64(^uint32(0))
+	}
+
+	return gasLimit, nil
+}
+
+func formatBlock(
+	header tmtypes.Header, size int, gasLimit int64,
+	gasUsed *big.Int, transactions interface{}, bloom ethtypes.Bloom,
+) map[string]interface{} {
+	if len(header.DataHash) == 0 {
+		header.DataHash = tmbytes.HexBytes(common.Hash{}.Bytes())
+	}
+
+	return map[string]interface{}{
+		"number":           hexutil.Uint64(header.Height),
+		"hash":             hexutil.Bytes(header.Hash()),
+		"parentHash":       hexutil.Bytes(header.LastBlockID.Hash),
+		"nonce":            hexutil.Uint64(0), // PoW specific
+		"sha3Uncles":       common.Hash{},     // No uncles in Tendermint
+		"logsBloom":        bloom,
+		"transactionsRoot": hexutil.Bytes(header.DataHash),
+		"stateRoot":        hexutil.Bytes(header.AppHash),
+		"miner":            common.Address{},
+		"mixHash":          common.Hash{},
+		"difficulty":       0,
+		"totalDifficulty":  0,
+		"extraData":        hexutil.Uint64(0),
+		"size":             hexutil.Uint64(size),
+		"gasLimit":         hexutil.Uint64(gasLimit), // Static gas limit
+		"gasUsed":          (*hexutil.Big)(gasUsed),
+		"timestamp":        hexutil.Uint64(header.Time.Unix()),
+		"transactions":     transactions.([]common.Hash),
+		"uncles":           []string{},
+		"receiptsRoot":     common.Hash{},
+	}
+}
+
+// GetKeyByAddress returns the private key matching the given address. If not found it returns false.
+func GetKeyByAddress(keys []ethsecp256k1.PrivKey, address common.Address) (key *ethsecp256k1.PrivKey, exist bool) {
+	for _, key := range keys {
+		if bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
+			return &key, true
+		}
+	}
+	return nil, false
+}
diff --git a/rpc/websockets.go b/rpc/websockets.go
deleted file mode 100644
index 26117824..00000000
--- a/rpc/websockets.go
+++ /dev/null
@@ -1,542 +0,0 @@
-package rpc
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"math/big"
-	"net"
-	"net/http"
-	"os"
-	"strings"
-	"sync"
-
-	"github.com/gorilla/mux"
-	"github.com/gorilla/websocket"
-	"github.com/spf13/viper"
-
-	"github.com/tendermint/tendermint/libs/log"
-	coretypes "github.com/tendermint/tendermint/rpc/core/types"
-	tmtypes "github.com/tendermint/tendermint/types"
-
-	evmtypes "github.com/cosmos/ethermint/x/evm/types"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/eth/filters"
-	"github.com/ethereum/go-ethereum/rpc"
-
-	context "github.com/cosmos/cosmos-sdk/client/context"
-)
-
-type SubscriptionResponseJSON struct {
-	Jsonrpc string      `json:"jsonrpc"`
-	Result  interface{} `json:"result"`
-	ID      float64     `json:"id"`
-}
-
-type SubscriptionNotification struct {
-	Jsonrpc string              `json:"jsonrpc"`
-	Method  string              `json:"method"`
-	Params  *SubscriptionResult `json:"params"`
-}
-
-type SubscriptionResult struct {
-	Subscription rpc.ID      `json:"subscription"`
-	Result       interface{} `json:"result"`
-}
-
-type ErrorResponseJSON struct {
-	Jsonrpc string            `json:"jsonrpc"`
-	Error   *ErrorMessageJSON `json:"error"`
-	ID      *big.Int          `json:"id"`
-}
-
-type ErrorMessageJSON struct {
-	Code    *big.Int `json:"code"`
-	Message string   `json:"message"`
-}
-
-type websocketsServer struct {
-	rpcAddr string // listen address of rest-server
-	wsAddr  string // listen address of ws server
-	api     *pubSubAPI
-	logger  log.Logger
-}
-
-func newWebsocketsServer(cliCtx context.CLIContext, wsAddr string) *websocketsServer {
-	return &websocketsServer{
-		rpcAddr: viper.GetString("laddr"),
-		wsAddr:  wsAddr,
-		api:     newPubSubAPI(cliCtx),
-		logger:  log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-server"),
-	}
-}
-
-func (s *websocketsServer) start() {
-	ws := mux.NewRouter()
-	ws.Handle("/", s)
-
-	go func() {
-		err := http.ListenAndServe(fmt.Sprintf(":%s", s.wsAddr), ws)
-		if err != nil {
-			s.logger.Error("http error:", err)
-		}
-	}()
-}
-
-func (s *websocketsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	var upgrader = websocket.Upgrader{
-		CheckOrigin: func(r *http.Request) bool {
-			return true
-		},
-	}
-
-	wsConn, err := upgrader.Upgrade(w, r, nil)
-	if err != nil {
-		s.logger.Error("websocket upgrade failed; error:", err)
-		return
-	}
-
-	s.readLoop(wsConn)
-}
-
-func (s *websocketsServer) sendErrResponse(conn *websocket.Conn, msg string) {
-	res := &ErrorResponseJSON{
-		Jsonrpc: "2.0",
-		Error: &ErrorMessageJSON{
-			Code:    big.NewInt(-32600),
-			Message: msg,
-		},
-		ID: nil,
-	}
-	err := conn.WriteJSON(res)
-	if err != nil {
-		s.logger.Error("websocket failed write message", "error", err)
-	}
-}
-
-func (s *websocketsServer) readLoop(wsConn *websocket.Conn) {
-	for {
-		_, mb, err := wsConn.ReadMessage()
-		if err != nil {
-			_ = wsConn.Close()
-			s.logger.Error("failed to read message; error", err)
-			return
-		}
-
-		var msg map[string]interface{}
-		err = json.Unmarshal(mb, &msg)
-		if err != nil {
-			s.sendErrResponse(wsConn, "invalid request")
-			continue
-		}
-
-		// check if method == eth_subscribe or eth_unsubscribe
-		method := msg["method"]
-		if method.(string) == "eth_subscribe" {
-			params := msg["params"].([]interface{})
-			if len(params) == 0 {
-				s.sendErrResponse(wsConn, "invalid parameters")
-				continue
-			}
-
-			id, err := s.api.subscribe(wsConn, params)
-			if err != nil {
-				s.sendErrResponse(wsConn, err.Error())
-				continue
-			}
-
-			res := &SubscriptionResponseJSON{
-				Jsonrpc: "2.0",
-				ID:      1,
-				Result:  id,
-			}
-
-			err = wsConn.WriteJSON(res)
-			if err != nil {
-				s.logger.Error("failed to write json response", err)
-				continue
-			}
-
-			continue
-		} else if method.(string) == "eth_unsubscribe" {
-			ids, ok := msg["params"].([]interface{})
-			if _, idok := ids[0].(string); !ok || !idok {
-				s.sendErrResponse(wsConn, "invalid parameters")
-				continue
-			}
-
-			ok = s.api.unsubscribe(rpc.ID(ids[0].(string)))
-			res := &SubscriptionResponseJSON{
-				Jsonrpc: "2.0",
-				ID:      1,
-				Result:  ok,
-			}
-
-			err = wsConn.WriteJSON(res)
-			if err != nil {
-				s.logger.Error("failed to write json response", err)
-				continue
-			}
-
-			continue
-		}
-
-		// otherwise, call the usual rpc server to respond
-		err = s.tcpGetAndSendResponse(wsConn, mb)
-		if err != nil {
-			s.sendErrResponse(wsConn, err.Error())
-		}
-	}
-}
-
-// tcpGetAndSendResponse connects to the rest-server over tcp, posts a JSON-RPC request, and sends the response
-// to the client over websockets
-func (s *websocketsServer) tcpGetAndSendResponse(conn *websocket.Conn, mb []byte) error {
-	addr := strings.Split(s.rpcAddr, "tcp://")
-	if len(addr) != 2 {
-		return fmt.Errorf("invalid laddr %s", s.rpcAddr)
-	}
-
-	tcpConn, err := net.Dial("tcp", addr[1])
-	if err != nil {
-		return fmt.Errorf("cannot connect to %s; %s", s.rpcAddr, err)
-	}
-
-	buf := &bytes.Buffer{}
-	_, err = buf.Write(mb)
-	if err != nil {
-		return fmt.Errorf("failed to write message; %s", err)
-	}
-
-	req, err := http.NewRequest("POST", s.rpcAddr, buf)
-	if err != nil {
-		return fmt.Errorf("failed to request; %s", err)
-	}
-
-	req.Header.Set("Content-Type", "application/json;")
-	err = req.Write(tcpConn)
-	if err != nil {
-		return fmt.Errorf("failed to write to rest-server; %s", err)
-	}
-
-	respBytes, err := ioutil.ReadAll(tcpConn)
-	if err != nil {
-		return fmt.Errorf("error reading response from rest-server; %s", err)
-	}
-
-	respbuf := &bytes.Buffer{}
-	respbuf.Write(respBytes)
-	resp, err := http.ReadResponse(bufio.NewReader(respbuf), req)
-	if err != nil {
-		return fmt.Errorf("could not read response; %s", err)
-	}
-
-	defer resp.Body.Close()
-
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return fmt.Errorf("could not read body from response; %s", err)
-	}
-
-	var wsSend interface{}
-	err = json.Unmarshal(body, &wsSend)
-	if err != nil {
-		return fmt.Errorf("failed to unmarshal rest-server response; %s", err)
-	}
-
-	return conn.WriteJSON(wsSend)
-}
-
-type wsSubscription struct {
-	sub          *Subscription
-	unsubscribed chan struct{} // closed when unsubscribing
-	conn         *websocket.Conn
-}
-
-// pubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec
-type pubSubAPI struct {
-	cliCtx    context.CLIContext
-	events    *EventSystem
-	filtersMu sync.Mutex
-	filters   map[rpc.ID]*wsSubscription
-	logger    log.Logger
-}
-
-// newPubSubAPI creates an instance of the ethereum PubSub API.
-func newPubSubAPI(cliCtx context.CLIContext) *pubSubAPI {
-	return &pubSubAPI{
-		cliCtx:  cliCtx,
-		events:  NewEventSystem(cliCtx.Client),
-		filters: make(map[rpc.ID]*wsSubscription),
-		logger:  log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-client"),
-	}
-}
-
-func (api *pubSubAPI) subscribe(conn *websocket.Conn, params []interface{}) (rpc.ID, error) {
-	method, ok := params[0].(string)
-	if !ok {
-		return "0", fmt.Errorf("invalid parameters")
-	}
-
-	switch method {
-	case "newHeads":
-		// TODO: handle extra params
-		return api.subscribeNewHeads(conn)
-	case "logs":
-		if len(params) > 1 {
-			return api.subscribeLogs(conn, params[1])
-		}
-
-		return api.subscribeLogs(conn, nil)
-	case "newPendingTransactions":
-		return api.subscribePendingTransactions(conn)
-	case "syncing":
-		return api.subscribeSyncing(conn)
-	default:
-		return "0", fmt.Errorf("unsupported method %s", method)
-	}
-}
-
-func (api *pubSubAPI) unsubscribe(id rpc.ID) bool {
-	api.filtersMu.Lock()
-	defer api.filtersMu.Unlock()
-
-	if api.filters[id] == nil {
-		return false
-	}
-
-	close(api.filters[id].unsubscribed)
-	delete(api.filters, id)
-	return true
-}
-
-func (api *pubSubAPI) subscribeNewHeads(conn *websocket.Conn) (rpc.ID, error) {
-	sub, _, err := api.events.SubscribeNewHeads()
-	if err != nil {
-		return "", fmt.Errorf("error creating block filter: %s", err.Error())
-	}
-
-	unsubscribed := make(chan struct{})
-	api.filtersMu.Lock()
-	api.filters[sub.ID()] = &wsSubscription{
-		sub:          sub,
-		conn:         conn,
-		unsubscribed: unsubscribed,
-	}
-	api.filtersMu.Unlock()
-
-	go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
-		for {
-			select {
-			case event := <-headersCh:
-				data, _ := event.Data.(tmtypes.EventDataNewBlockHeader)
-				header := EthHeaderFromTendermint(data.Header)
-
-				api.filtersMu.Lock()
-				if f, found := api.filters[sub.ID()]; found {
-					// write to ws conn
-					res := &SubscriptionNotification{
-						Jsonrpc: "2.0",
-						Method:  "eth_subscription",
-						Params: &SubscriptionResult{
-							Subscription: sub.ID(),
-							Result:       header,
-						},
-					}
-
-					err = f.conn.WriteJSON(res)
-					if err != nil {
-						api.logger.Error("error writing header")
-					}
-				}
-				api.filtersMu.Unlock()
-			case <-errCh:
-				api.filtersMu.Lock()
-				delete(api.filters, sub.ID())
-				api.filtersMu.Unlock()
-				return
-			case <-unsubscribed:
-				return
-			}
-		}
-	}(sub.eventCh, sub.Err())
-
-	return sub.ID(), nil
-}
-
-func (api *pubSubAPI) subscribeLogs(conn *websocket.Conn, extra interface{}) (rpc.ID, error) {
-	crit := filters.FilterCriteria{}
-
-	if extra != nil {
-		params, ok := extra.(map[string]interface{})
-		if !ok {
-			return "", fmt.Errorf("invalid criteria")
-		}
-
-		if params["address"] != nil {
-			address, ok := params["address"].(string)
-			addresses, sok := params["address"].([]interface{})
-			if !ok && !sok {
-				return "", fmt.Errorf("invalid address; must be address or array of addresses")
-			}
-
-			if ok {
-				crit.Addresses = []common.Address{common.HexToAddress(address)}
-			}
-
-			if sok {
-				crit.Addresses = []common.Address{}
-				for _, addr := range addresses {
-					address, ok := addr.(string)
-					if !ok {
-						return "", fmt.Errorf("invalid address")
-					}
-
-					crit.Addresses = append(crit.Addresses, common.HexToAddress(address))
-				}
-			}
-		}
-
-		if params["topics"] != nil {
-			topics, ok := params["topics"].([]interface{})
-			if !ok {
-				return "", fmt.Errorf("invalid topics")
-			}
-
-			crit.Topics = [][]common.Hash{}
-			for _, topic := range topics {
-				tstr, ok := topic.(string)
-				if !ok {
-					return "", fmt.Errorf("invalid topics")
-				}
-
-				h := common.HexToHash(tstr)
-				crit.Topics = append(crit.Topics, []common.Hash{h})
-			}
-		}
-	}
-
-	sub, _, err := api.events.SubscribeLogs(crit)
-	if err != nil {
-		return rpc.ID(""), err
-	}
-
-	unsubscribed := make(chan struct{})
-	api.filtersMu.Lock()
-	api.filters[sub.ID()] = &wsSubscription{
-		sub:          sub,
-		conn:         conn,
-		unsubscribed: unsubscribed,
-	}
-	api.filtersMu.Unlock()
-
-	go func(ch <-chan coretypes.ResultEvent, errCh <-chan error) {
-		for {
-			select {
-			case event := <-ch:
-				dataTx, ok := event.Data.(tmtypes.EventDataTx)
-				if !ok {
-					err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data)
-					return
-				}
-
-				var resultData evmtypes.ResultData
-				resultData, err = evmtypes.DecodeResultData(dataTx.TxResult.Result.Data)
-				if err != nil {
-					return
-				}
-
-				logs := filterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
-
-				api.filtersMu.Lock()
-				if f, found := api.filters[sub.ID()]; found {
-					// write to ws conn
-					res := &SubscriptionNotification{
-						Jsonrpc: "2.0",
-						Method:  "eth_subscription",
-						Params: &SubscriptionResult{
-							Subscription: sub.ID(),
-							Result:       logs,
-						},
-					}
-
-					err = f.conn.WriteJSON(res)
-				}
-				api.filtersMu.Unlock()
-
-				if err != nil {
-					err = fmt.Errorf("failed to write header: %w", err)
-					return
-				}
-			case <-errCh:
-				api.filtersMu.Lock()
-				delete(api.filters, sub.ID())
-				api.filtersMu.Unlock()
-				return
-			case <-unsubscribed:
-				return
-			}
-		}
-	}(sub.eventCh, sub.Err())
-
-	return sub.ID(), nil
-}
-
-func (api *pubSubAPI) subscribePendingTransactions(conn *websocket.Conn) (rpc.ID, error) {
-	sub, _, err := api.events.SubscribePendingTxs()
-	if err != nil {
-		return "", fmt.Errorf("error creating block filter: %s", err.Error())
-	}
-
-	unsubscribed := make(chan struct{})
-	api.filtersMu.Lock()
-	api.filters[sub.ID()] = &wsSubscription{
-		sub:          sub,
-		conn:         conn,
-		unsubscribed: unsubscribed,
-	}
-	api.filtersMu.Unlock()
-
-	go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
-		for {
-			select {
-			case ev := <-txsCh:
-				data, _ := ev.Data.(tmtypes.EventDataTx)
-				txHash := common.BytesToHash(data.Tx.Hash())
-
-				api.filtersMu.Lock()
-				if f, found := api.filters[sub.ID()]; found {
-					// write to ws conn
-					res := &SubscriptionNotification{
-						Jsonrpc: "2.0",
-						Method:  "eth_subscription",
-						Params: &SubscriptionResult{
-							Subscription: sub.ID(),
-							Result:       txHash,
-						},
-					}
-
-					err = f.conn.WriteJSON(res)
-				}
-				api.filtersMu.Unlock()
-
-				if err != nil {
-					err = fmt.Errorf("failed to write header: %w", err)
-					return
-				}
-			case <-errCh:
-				api.filtersMu.Lock()
-				delete(api.filters, sub.ID())
-				api.filtersMu.Unlock()
-			}
-		}
-	}(sub.eventCh, sub.Err())
-
-	return sub.ID(), nil
-}
-
-func (api *pubSubAPI) subscribeSyncing(conn *websocket.Conn) (rpc.ID, error) {
-	return "", nil
-}
diff --git a/rpc/websockets/pubsub_api.go b/rpc/websockets/pubsub_api.go
new file mode 100644
index 00000000..266fce40
--- /dev/null
+++ b/rpc/websockets/pubsub_api.go
@@ -0,0 +1,309 @@
+package websockets
+
+import (
+	"fmt"
+	"os"
+	"sync"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/tendermint/tendermint/libs/log"
+	coretypes "github.com/tendermint/tendermint/rpc/core/types"
+	tmtypes "github.com/tendermint/tendermint/types"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/eth/filters"
+	"github.com/ethereum/go-ethereum/rpc"
+
+	context "github.com/cosmos/cosmos-sdk/client/context"
+
+	rpcfilters "github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
+	evmtypes "github.com/cosmos/ethermint/x/evm/types"
+)
+
+// PubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec
+type PubSubAPI struct {
+	clientCtx context.CLIContext
+	events    *rpcfilters.EventSystem
+	filtersMu sync.Mutex
+	filters   map[rpc.ID]*wsSubscription
+	logger    log.Logger
+}
+
+// NewAPI creates an instance of the ethereum PubSub API.
+func NewAPI(clientCtx context.CLIContext) *PubSubAPI {
+	return &PubSubAPI{
+		clientCtx: clientCtx,
+		events:    rpcfilters.NewEventSystem(clientCtx.Client),
+		filters:   make(map[rpc.ID]*wsSubscription),
+		logger:    log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-client"),
+	}
+}
+
+func (api *PubSubAPI) subscribe(conn *websocket.Conn, params []interface{}) (rpc.ID, error) {
+	method, ok := params[0].(string)
+	if !ok {
+		return "0", fmt.Errorf("invalid parameters")
+	}
+
+	switch method {
+	case "newHeads":
+		// TODO: handle extra params
+		return api.subscribeNewHeads(conn)
+	case "logs":
+		if len(params) > 1 {
+			return api.subscribeLogs(conn, params[1])
+		}
+
+		return api.subscribeLogs(conn, nil)
+	case "newPendingTransactions":
+		return api.subscribePendingTransactions(conn)
+	case "syncing":
+		return api.subscribeSyncing(conn)
+	default:
+		return "0", fmt.Errorf("unsupported method %s", method)
+	}
+}
+
+func (api *PubSubAPI) unsubscribe(id rpc.ID) bool {
+	api.filtersMu.Lock()
+	defer api.filtersMu.Unlock()
+
+	if api.filters[id] == nil {
+		return false
+	}
+
+	close(api.filters[id].unsubscribed)
+	delete(api.filters, id)
+	return true
+}
+
+func (api *PubSubAPI) subscribeNewHeads(conn *websocket.Conn) (rpc.ID, error) {
+	sub, _, err := api.events.SubscribeNewHeads()
+	if err != nil {
+		return "", fmt.Errorf("error creating block filter: %s", err.Error())
+	}
+
+	unsubscribed := make(chan struct{})
+	api.filtersMu.Lock()
+	api.filters[sub.ID()] = &wsSubscription{
+		sub:          sub,
+		conn:         conn,
+		unsubscribed: unsubscribed,
+	}
+	api.filtersMu.Unlock()
+
+	go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
+		for {
+			select {
+			case event := <-headersCh:
+				data, _ := event.Data.(tmtypes.EventDataNewBlockHeader)
+				header := rpctypes.EthHeaderFromTendermint(data.Header)
+
+				api.filtersMu.Lock()
+				if f, found := api.filters[sub.ID()]; found {
+					// write to ws conn
+					res := &SubscriptionNotification{
+						Jsonrpc: "2.0",
+						Method:  "eth_subscription",
+						Params: &SubscriptionResult{
+							Subscription: sub.ID(),
+							Result:       header,
+						},
+					}
+
+					err = f.conn.WriteJSON(res)
+					if err != nil {
+						api.logger.Error("error writing header")
+					}
+				}
+				api.filtersMu.Unlock()
+			case <-errCh:
+				api.filtersMu.Lock()
+				delete(api.filters, sub.ID())
+				api.filtersMu.Unlock()
+				return
+			case <-unsubscribed:
+				return
+			}
+		}
+	}(sub.Event(), sub.Err())
+
+	return sub.ID(), nil
+}
+
+func (api *PubSubAPI) subscribeLogs(conn *websocket.Conn, extra interface{}) (rpc.ID, error) {
+	crit := filters.FilterCriteria{}
+
+	if extra != nil {
+		params, ok := extra.(map[string]interface{})
+		if !ok {
+			return "", fmt.Errorf("invalid criteria")
+		}
+
+		if params["address"] != nil {
+			address, ok := params["address"].(string)
+			addresses, sok := params["address"].([]interface{})
+			if !ok && !sok {
+				return "", fmt.Errorf("invalid address; must be address or array of addresses")
+			}
+
+			if ok {
+				crit.Addresses = []common.Address{common.HexToAddress(address)}
+			}
+
+			if sok {
+				crit.Addresses = []common.Address{}
+				for _, addr := range addresses {
+					address, ok := addr.(string)
+					if !ok {
+						return "", fmt.Errorf("invalid address")
+					}
+
+					crit.Addresses = append(crit.Addresses, common.HexToAddress(address))
+				}
+			}
+		}
+
+		if params["topics"] != nil {
+			topics, ok := params["topics"].([]interface{})
+			if !ok {
+				return "", fmt.Errorf("invalid topics")
+			}
+
+			crit.Topics = [][]common.Hash{}
+			for _, topic := range topics {
+				tstr, ok := topic.(string)
+				if !ok {
+					return "", fmt.Errorf("invalid topics")
+				}
+
+				h := common.HexToHash(tstr)
+				crit.Topics = append(crit.Topics, []common.Hash{h})
+			}
+		}
+	}
+
+	sub, _, err := api.events.SubscribeLogs(crit)
+	if err != nil {
+		return rpc.ID(""), err
+	}
+
+	unsubscribed := make(chan struct{})
+	api.filtersMu.Lock()
+	api.filters[sub.ID()] = &wsSubscription{
+		sub:          sub,
+		conn:         conn,
+		unsubscribed: unsubscribed,
+	}
+	api.filtersMu.Unlock()
+
+	go func(ch <-chan coretypes.ResultEvent, errCh <-chan error) {
+		for {
+			select {
+			case event := <-ch:
+				dataTx, ok := event.Data.(tmtypes.EventDataTx)
+				if !ok {
+					err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data)
+					return
+				}
+
+				var resultData evmtypes.ResultData
+				resultData, err = evmtypes.DecodeResultData(dataTx.TxResult.Result.Data)
+				if err != nil {
+					return
+				}
+
+				logs := rpcfilters.FilterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
+
+				api.filtersMu.Lock()
+				if f, found := api.filters[sub.ID()]; found {
+					// write to ws conn
+					res := &SubscriptionNotification{
+						Jsonrpc: "2.0",
+						Method:  "eth_subscription",
+						Params: &SubscriptionResult{
+							Subscription: sub.ID(),
+							Result:       logs,
+						},
+					}
+
+					err = f.conn.WriteJSON(res)
+				}
+				api.filtersMu.Unlock()
+
+				if err != nil {
+					err = fmt.Errorf("failed to write header: %w", err)
+					return
+				}
+			case <-errCh:
+				api.filtersMu.Lock()
+				delete(api.filters, sub.ID())
+				api.filtersMu.Unlock()
+				return
+			case <-unsubscribed:
+				return
+			}
+		}
+	}(sub.Event(), sub.Err())
+
+	return sub.ID(), nil
+}
+
+func (api *PubSubAPI) subscribePendingTransactions(conn *websocket.Conn) (rpc.ID, error) {
+	sub, _, err := api.events.SubscribePendingTxs()
+	if err != nil {
+		return "", fmt.Errorf("error creating block filter: %s", err.Error())
+	}
+
+	unsubscribed := make(chan struct{})
+	api.filtersMu.Lock()
+	api.filters[sub.ID()] = &wsSubscription{
+		sub:          sub,
+		conn:         conn,
+		unsubscribed: unsubscribed,
+	}
+	api.filtersMu.Unlock()
+
+	go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
+		for {
+			select {
+			case ev := <-txsCh:
+				data, _ := ev.Data.(tmtypes.EventDataTx)
+				txHash := common.BytesToHash(data.Tx.Hash())
+
+				api.filtersMu.Lock()
+				if f, found := api.filters[sub.ID()]; found {
+					// write to ws conn
+					res := &SubscriptionNotification{
+						Jsonrpc: "2.0",
+						Method:  "eth_subscription",
+						Params: &SubscriptionResult{
+							Subscription: sub.ID(),
+							Result:       txHash,
+						},
+					}
+
+					err = f.conn.WriteJSON(res)
+				}
+				api.filtersMu.Unlock()
+
+				if err != nil {
+					err = fmt.Errorf("failed to write header: %w", err)
+					return
+				}
+			case <-errCh:
+				api.filtersMu.Lock()
+				delete(api.filters, sub.ID())
+				api.filtersMu.Unlock()
+			}
+		}
+	}(sub.Event(), sub.Err())
+
+	return sub.ID(), nil
+}
+
+func (api *PubSubAPI) subscribeSyncing(conn *websocket.Conn) (rpc.ID, error) {
+	return "", nil
+}
diff --git a/rpc/websockets/server.go b/rpc/websockets/server.go
new file mode 100644
index 00000000..25102afb
--- /dev/null
+++ b/rpc/websockets/server.go
@@ -0,0 +1,219 @@
+package websockets
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"math/big"
+	"net"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/gorilla/mux"
+	"github.com/gorilla/websocket"
+	"github.com/spf13/viper"
+
+	"github.com/tendermint/tendermint/libs/log"
+
+	"github.com/ethereum/go-ethereum/rpc"
+
+	context "github.com/cosmos/cosmos-sdk/client/context"
+)
+
+// Server defines a server that handles Ethereum websockets.
+type Server struct {
+	rpcAddr string // listen address of rest-server
+	wsAddr  string // listen address of ws server
+	api     *PubSubAPI
+	logger  log.Logger
+}
+
+// NewServer creates a new websocket server instance.
+func NewServer(clientCtx context.CLIContext, wsAddr string) *Server {
+	return &Server{
+		rpcAddr: viper.GetString("laddr"),
+		wsAddr:  wsAddr,
+		api:     NewAPI(clientCtx),
+		logger:  log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-server"),
+	}
+}
+
+// Start runs the websocket server
+func (s *Server) Start() {
+	ws := mux.NewRouter()
+	ws.Handle("/", s)
+
+	go func() {
+		err := http.ListenAndServe(fmt.Sprintf(":%s", s.wsAddr), ws)
+		if err != nil {
+			s.logger.Error("http error:", err)
+		}
+	}()
+}
+
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	var upgrader = websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+
+	wsConn, err := upgrader.Upgrade(w, r, nil)
+	if err != nil {
+		s.logger.Error("websocket upgrade failed; error:", err)
+		return
+	}
+
+	s.readLoop(wsConn)
+}
+
+func (s *Server) sendErrResponse(conn *websocket.Conn, msg string) {
+	res := &ErrorResponseJSON{
+		Jsonrpc: "2.0",
+		Error: &ErrorMessageJSON{
+			Code:    big.NewInt(-32600),
+			Message: msg,
+		},
+		ID: nil,
+	}
+	err := conn.WriteJSON(res)
+	if err != nil {
+		s.logger.Error("websocket failed write message", "error", err)
+	}
+}
+
+func (s *Server) readLoop(wsConn *websocket.Conn) {
+	for {
+		_, mb, err := wsConn.ReadMessage()
+		if err != nil {
+			_ = wsConn.Close()
+			s.logger.Error("failed to read message; error", err)
+			return
+		}
+
+		var msg map[string]interface{}
+		err = json.Unmarshal(mb, &msg)
+		if err != nil {
+			s.sendErrResponse(wsConn, "invalid request")
+			continue
+		}
+
+		// check if method == eth_subscribe or eth_unsubscribe
+		method := msg["method"]
+		if method.(string) == "eth_subscribe" {
+			params := msg["params"].([]interface{})
+			if len(params) == 0 {
+				s.sendErrResponse(wsConn, "invalid parameters")
+				continue
+			}
+
+			id, err := s.api.subscribe(wsConn, params)
+			if err != nil {
+				s.sendErrResponse(wsConn, err.Error())
+				continue
+			}
+
+			res := &SubscriptionResponseJSON{
+				Jsonrpc: "2.0",
+				ID:      1,
+				Result:  id,
+			}
+
+			err = wsConn.WriteJSON(res)
+			if err != nil {
+				s.logger.Error("failed to write json response", err)
+				continue
+			}
+
+			continue
+		} else if method.(string) == "eth_unsubscribe" {
+			ids, ok := msg["params"].([]interface{})
+			if _, idok := ids[0].(string); !ok || !idok {
+				s.sendErrResponse(wsConn, "invalid parameters")
+				continue
+			}
+
+			ok = s.api.unsubscribe(rpc.ID(ids[0].(string)))
+			res := &SubscriptionResponseJSON{
+				Jsonrpc: "2.0",
+				ID:      1,
+				Result:  ok,
+			}
+
+			err = wsConn.WriteJSON(res)
+			if err != nil {
+				s.logger.Error("failed to write json response", err)
+				continue
+			}
+
+			continue
+		}
+
+		// otherwise, call the usual rpc server to respond
+		err = s.tcpGetAndSendResponse(wsConn, mb)
+		if err != nil {
+			s.sendErrResponse(wsConn, err.Error())
+		}
+	}
+}
+
+// tcpGetAndSendResponse connects to the rest-server over tcp, posts a JSON-RPC request, and sends the response
+// to the client over websockets
+func (s *Server) tcpGetAndSendResponse(conn *websocket.Conn, mb []byte) error {
+	addr := strings.Split(s.rpcAddr, "tcp://")
+	if len(addr) != 2 {
+		return fmt.Errorf("invalid laddr %s", s.rpcAddr)
+	}
+
+	tcpConn, err := net.Dial("tcp", addr[1])
+	if err != nil {
+		return fmt.Errorf("cannot connect to %s; %s", s.rpcAddr, err)
+	}
+
+	buf := &bytes.Buffer{}
+	_, err = buf.Write(mb)
+	if err != nil {
+		return fmt.Errorf("failed to write message; %s", err)
+	}
+
+	req, err := http.NewRequest("POST", s.rpcAddr, buf)
+	if err != nil {
+		return fmt.Errorf("failed to request; %s", err)
+	}
+
+	req.Header.Set("Content-Type", "application/json;")
+	err = req.Write(tcpConn)
+	if err != nil {
+		return fmt.Errorf("failed to write to rest-server; %s", err)
+	}
+
+	respBytes, err := ioutil.ReadAll(tcpConn)
+	if err != nil {
+		return fmt.Errorf("error reading response from rest-server; %s", err)
+	}
+
+	respbuf := &bytes.Buffer{}
+	respbuf.Write(respBytes)
+	resp, err := http.ReadResponse(bufio.NewReader(respbuf), req)
+	if err != nil {
+		return fmt.Errorf("could not read response; %s", err)
+	}
+
+	defer resp.Body.Close()
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return fmt.Errorf("could not read body from response; %s", err)
+	}
+
+	var wsSend interface{}
+	err = json.Unmarshal(body, &wsSend)
+	if err != nil {
+		return fmt.Errorf("failed to unmarshal rest-server response; %s", err)
+	}
+
+	return conn.WriteJSON(wsSend)
+}
diff --git a/rpc/websockets/types.go b/rpc/websockets/types.go
new file mode 100644
index 00000000..ca826641
--- /dev/null
+++ b/rpc/websockets/types.go
@@ -0,0 +1,45 @@
+package websockets
+
+import (
+	"math/big"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/ethereum/go-ethereum/rpc"
+
+	rpcfilters "github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
+)
+
+type SubscriptionResponseJSON struct {
+	Jsonrpc string      `json:"jsonrpc"`
+	Result  interface{} `json:"result"`
+	ID      float64     `json:"id"`
+}
+
+type SubscriptionNotification struct {
+	Jsonrpc string              `json:"jsonrpc"`
+	Method  string              `json:"method"`
+	Params  *SubscriptionResult `json:"params"`
+}
+
+type SubscriptionResult struct {
+	Subscription rpc.ID      `json:"subscription"`
+	Result       interface{} `json:"result"`
+}
+
+type ErrorResponseJSON struct {
+	Jsonrpc string            `json:"jsonrpc"`
+	Error   *ErrorMessageJSON `json:"error"`
+	ID      *big.Int          `json:"id"`
+}
+
+type ErrorMessageJSON struct {
+	Code    *big.Int `json:"code"`
+	Message string   `json:"message"`
+}
+
+type wsSubscription struct {
+	sub          *rpcfilters.Subscription
+	unsubscribed chan struct{} // closed when unsubscribing
+	conn         *websocket.Conn
+}
diff --git a/tests/rpc_test.go b/tests/rpc_test.go
index 28bf81d6..fe3ae290 100644
--- a/tests/rpc_test.go
+++ b/tests/rpc_test.go
@@ -14,7 +14,6 @@ import (
 	"math/big"
 	"net/http"
 	"os"
-	"strings"
 	"testing"
 	"time"
 
@@ -24,9 +23,8 @@ import (
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	ethtypes "github.com/ethereum/go-ethereum/core/types"
 
-	"github.com/cosmos/ethermint/rpc"
+	rpctypes "github.com/cosmos/ethermint/rpc/types"
 	"github.com/cosmos/ethermint/version"
-	"github.com/cosmos/ethermint/x/evm/types"
 )
 
 const (
@@ -191,7 +189,14 @@ func TestEth_GetLogs_NoLogs(t *testing.T) {
 	param := make([]map[string][]string, 1)
 	param[0] = make(map[string][]string)
 	param[0]["topics"] = []string{}
-	call(t, "eth_getLogs", param)
+	rpcRes := call(t, "eth_getLogs", param)
+	require.NotNil(t, rpcRes)
+	require.Nil(t, rpcRes.Error)
+
+	var logs []*ethtypes.Log
+	err := json.Unmarshal(rpcRes.Result, &logs)
+	require.NoError(t, err)
+	require.NotEmpty(t, logs)
 }
 
 func TestEth_GetLogs_Topics_AB(t *testing.T) {
@@ -332,7 +337,7 @@ func TestEth_GetProof(t *testing.T) {
 	rpcRes := call(t, "eth_getProof", params)
 	require.NotNil(t, rpcRes)
 
-	var accRes rpc.AccountResult
+	var accRes rpctypes.AccountResult
 	err := json.Unmarshal(rpcRes.Result, &accRes)
 	require.NoError(t, err)
 	require.NotEmpty(t, accRes.AccountProof)
@@ -779,68 +784,6 @@ func TestEth_EstimateGas_ContractDeployment(t *testing.T) {
 	require.Equal(t, "0x1c2c4", gas.String())
 }
 
-func TestEth_ExportAccount(t *testing.T) {
-	param := []string{}
-	param = append(param, "0x1122334455667788990011223344556677889901")
-	param = append(param, "latest")
-	rpcRes := call(t, "eth_exportAccount", param)
-
-	var res string
-	err := json.Unmarshal(rpcRes.Result, &res)
-	require.NoError(t, err)
-
-	var account types.GenesisAccount
-	err = json.Unmarshal([]byte(res), &account)
-	require.NoError(t, err)
-
-	require.Equal(t, "0x1122334455667788990011223344556677889901", account.Address.Hex())
-	require.Equal(t, big.NewInt(0), account.Balance)
-	require.Equal(t, hexutil.Bytes(nil), account.Code)
-	require.Equal(t, types.Storage(nil), account.Storage)
-}
-
-func TestEth_ExportAccount_WithStorage(t *testing.T) {
-	hash := deployTestContractWithFunction(t)
-	receipt := waitForReceipt(t, hash)
-	addr := receipt["contractAddress"].(string)
-
-	// call function to set storage
-	calldata := "0xeb8ac92100000000000000000000000000000000000000000000000000000000000000630000000000000000000000000000000000000000000000000000000000000000"
-
-	param := make([]map[string]string, 1)
-	param[0] = make(map[string]string)
-	param[0]["from"] = "0x" + fmt.Sprintf("%x", from)
-	param[0]["to"] = addr
-	param[0]["data"] = calldata
-	rpcRes := call(t, "eth_sendTransaction", param)
-
-	var txhash hexutil.Bytes
-	err := json.Unmarshal(rpcRes.Result, &txhash)
-	require.NoError(t, err)
-	waitForReceipt(t, txhash)
-
-	// get exported account
-	eap := []string{}
-	eap = append(eap, addr)
-	eap = append(eap, "latest")
-	rpcRes = call(t, "eth_exportAccount", eap)
-
-	var res string
-	err = json.Unmarshal(rpcRes.Result, &res)
-	require.NoError(t, err)
-
-	var account types.GenesisAccount
-	err = json.Unmarshal([]byte(res), &account)
-	require.NoError(t, err)
-
-	// deployed bytecode
-	bytecode := "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032"
-	require.Equal(t, addr, strings.ToLower(account.Address.Hex()))
-	require.Equal(t, big.NewInt(0), account.Balance)
-	require.Equal(t, bytecode, account.Code.String())
-	require.NotEqual(t, types.Storage(nil), account.Storage)
-}
-
 func TestEth_GetBlockByNumber(t *testing.T) {
 	param := []interface{}{"0x1", false}
 	rpcRes := call(t, "eth_getBlockByNumber", param)
diff --git a/x/evm/client/cli/query.go b/x/evm/client/cli/query.go
index f274a20e..0a73e993 100644
--- a/x/evm/client/cli/query.go
+++ b/x/evm/client/cli/query.go
@@ -37,7 +37,7 @@ func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command {
 		Short: "Gets storage for an account at a given key",
 		Args:  cobra.ExactArgs(2),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			cliCtx := context.NewCLIContext().WithCodec(cdc)
+			clientCtx := context.NewCLIContext().WithCodec(cdc)
 
 			account, err := accountToHex(args[0])
 			if err != nil {
@@ -46,7 +46,7 @@ func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command {
 
 			key := formatKeyToHash(args[1])
 
-			res, _, err := cliCtx.Query(
+			res, _, err := clientCtx.Query(
 				fmt.Sprintf("custom/%s/storage/%s/%s", queryRoute, account, key))
 
 			if err != nil {
@@ -54,7 +54,7 @@ func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command {
 			}
 			var out types.QueryResStorage
 			cdc.MustUnmarshalJSON(res, &out)
-			return cliCtx.PrintOutput(out)
+			return clientCtx.PrintOutput(out)
 		},
 	}
 }
@@ -66,14 +66,14 @@ func GetCmdGetCode(queryRoute string, cdc *codec.Codec) *cobra.Command {
 		Short: "Gets code from an account",
 		Args:  cobra.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			cliCtx := context.NewCLIContext().WithCodec(cdc)
+			clientCtx := context.NewCLIContext().WithCodec(cdc)
 
 			account, err := accountToHex(args[0])
 			if err != nil {
 				return errors.Wrap(err, "could not parse account address")
 			}
 
-			res, _, err := cliCtx.Query(
+			res, _, err := clientCtx.Query(
 				fmt.Sprintf("custom/%s/code/%s", queryRoute, account))
 
 			if err != nil {
@@ -82,7 +82,7 @@ func GetCmdGetCode(queryRoute string, cdc *codec.Codec) *cobra.Command {
 
 			var out types.QueryResCode
 			cdc.MustUnmarshalJSON(res, &out)
-			return cliCtx.PrintOutput(out)
+			return clientCtx.PrintOutput(out)
 		},
 	}
 }
diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go
deleted file mode 100644
index 84258ac6..00000000
--- a/x/evm/client/cli/tx.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package cli
-
-import (
-	"bufio"
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/pkg/errors"
-	"github.com/spf13/cobra"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-	ethcrypto "github.com/ethereum/go-ethereum/crypto"
-
-	"github.com/cosmos/cosmos-sdk/client"
-	"github.com/cosmos/cosmos-sdk/client/context"
-	"github.com/cosmos/cosmos-sdk/client/flags"
-	"github.com/cosmos/cosmos-sdk/codec"
-	sdk "github.com/cosmos/cosmos-sdk/types"
-	"github.com/cosmos/cosmos-sdk/x/auth"
-	authclient "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
-	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
-
-	emint "github.com/cosmos/ethermint/types"
-	"github.com/cosmos/ethermint/x/evm/types"
-)
-
-// GetTxCmd defines the CLI commands regarding evm module transactions
-func GetTxCmd(cdc *codec.Codec) *cobra.Command {
-	evmTxCmd := &cobra.Command{
-		Use:                        types.ModuleName,
-		Short:                      "EVM transaction subcommands",
-		DisableFlagParsing:         true,
-		SuggestionsMinimumDistance: 2,
-		RunE:                       client.ValidateCmd,
-	}
-
-	evmTxCmd.AddCommand(flags.PostCommands(
-		GetCmdSendTx(cdc),
-		GetCmdGenCreateTx(cdc),
-	)...)
-
-	return evmTxCmd
-}
-
-// GetCmdSendTx generates an Ethermint transaction (excludes create operations)
-func GetCmdSendTx(cdc *codec.Codec) *cobra.Command {
-	return &cobra.Command{
-		Use:   "send [to_address] [amount (in aphotons)] [<data>]",
-		Short: "send transaction to address (call operations included)",
-		Args:  cobra.RangeArgs(2, 3),
-		RunE: func(cmd *cobra.Command, args []string) error {
-			cliCtx := context.NewCLIContext().WithCodec(cdc)
-			inBuf := bufio.NewReader(cmd.InOrStdin())
-
-			txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
-
-			toAddr, err := cosmosAddressFromArg(args[0])
-			if err != nil {
-				return errors.Wrap(err, "must provide a valid Bech32 address for to_address")
-			}
-
-			// Ambiguously decode amount from any base
-			amount, err := strconv.ParseInt(args[1], 0, 64)
-			if err != nil {
-				return err
-			}
-
-			var data []byte
-			if len(args) > 2 {
-				payload := args[2]
-				if !strings.HasPrefix(payload, "0x") {
-					payload = "0x" + payload
-				}
-
-				data, err = hexutil.Decode(payload)
-				if err != nil {
-					return err
-				}
-			}
-
-			from := cliCtx.GetFromAddress()
-
-			_, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(from)
-			if err != nil {
-				return errors.Wrap(err, "Could not retrieve account sequence")
-			}
-
-			// TODO: Potentially allow overriding of gas price and gas limit
-			msg := types.NewMsgEthermint(seq, &toAddr, sdk.NewInt(amount), txBldr.Gas(),
-				sdk.NewInt(emint.DefaultGasPrice), data, from)
-
-			err = msg.ValidateBasic()
-			if err != nil {
-				return err
-			}
-
-			return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
-		},
-	}
-}
-
-// GetCmdGenCreateTx generates an Ethermint transaction (excludes create operations)
-func GetCmdGenCreateTx(cdc *codec.Codec) *cobra.Command {
-	return &cobra.Command{
-		Use:   "create [contract bytecode] [<amount (in aphotons)>]",
-		Short: "create contract through the evm using compiled bytecode",
-		Args:  cobra.RangeArgs(1, 2),
-		RunE: func(cmd *cobra.Command, args []string) error {
-			cliCtx := context.NewCLIContext().WithCodec(cdc)
-			inBuf := bufio.NewReader(cmd.InOrStdin())
-
-			txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
-
-			payload := args[0]
-			if !strings.HasPrefix(payload, "0x") {
-				payload = "0x" + payload
-			}
-
-			data, err := hexutil.Decode(payload)
-			if err != nil {
-				return err
-			}
-
-			var amount int64
-			if len(args) > 1 {
-				// Ambiguously decode amount from any base
-				amount, err = strconv.ParseInt(args[1], 0, 64)
-				if err != nil {
-					return errors.Wrap(err, "invalid amount")
-				}
-			}
-
-			from := cliCtx.GetFromAddress()
-
-			_, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(from)
-			if err != nil {
-				return errors.Wrap(err, "Could not retrieve account sequence")
-			}
-
-			// TODO: Potentially allow overriding of gas price and gas limit
-			msg := types.NewMsgEthermint(seq, nil, sdk.NewInt(amount), txBldr.Gas(),
-				sdk.NewInt(emint.DefaultGasPrice), data, from)
-
-			err = msg.ValidateBasic()
-			if err != nil {
-				return err
-			}
-
-			if err = authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}); err != nil {
-				return err
-			}
-
-			contractAddr := ethcrypto.CreateAddress(common.BytesToAddress(from.Bytes()), seq)
-			fmt.Printf(
-				"Contract will be deployed to: \nHex: %s\nCosmos Address: %s\n",
-				contractAddr.Hex(),
-				sdk.AccAddress(contractAddr.Bytes()),
-			)
-			return nil
-		},
-	}
-}
diff --git a/x/evm/client/cli/utils.go b/x/evm/client/cli/utils.go
index 6114246c..7277cdae 100644
--- a/x/evm/client/cli/utils.go
+++ b/x/evm/client/cli/utils.go
@@ -45,19 +45,3 @@ func formatKeyToHash(key string) string {
 
 	return ethkey.Hex()
 }
-
-func cosmosAddressFromArg(addr string) (sdk.AccAddress, error) {
-	if strings.HasPrefix(addr, sdk.GetConfig().GetBech32AccountAddrPrefix()) {
-		// Check to see if address is Cosmos bech32 formatted
-		toAddr, err := sdk.AccAddressFromBech32(addr)
-		if err != nil {
-			return nil, errors.Wrap(err, "invalid bech32 formatted address")
-		}
-		return toAddr, nil
-	}
-
-	// Strip 0x prefix if exists
-	addr = strings.TrimPrefix(addr, "0x")
-
-	return sdk.AccAddressFromHex(addr)
-}
diff --git a/x/evm/client/cli/utils_test.go b/x/evm/client/cli/utils_test.go
index 8ddb3102..ffe18a5a 100644
--- a/x/evm/client/cli/utils_test.go
+++ b/x/evm/client/cli/utils_test.go
@@ -61,24 +61,3 @@ func TestCosmosToEthereumTypes(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, hexString, ethDecoded)
 }
-
-func TestAddressToCosmosAddress(t *testing.T) {
-	baseAddr, err := sdk.AccAddressFromHex("6A98D72760f7bbA69d62Ed6F48278451251948E7")
-	require.NoError(t, err)
-
-	// Test cosmos string back to address
-	cosmosFormatted, err := cosmosAddressFromArg(baseAddr.String())
-	require.NoError(t, err)
-	require.Equal(t, baseAddr, cosmosFormatted)
-
-	// Test account address from Ethereum address
-	ethAddr := common.BytesToAddress(baseAddr.Bytes())
-	ethFormatted, err := cosmosAddressFromArg(ethAddr.Hex())
-	require.NoError(t, err)
-	require.Equal(t, baseAddr, ethFormatted)
-
-	// Test encoding without the 0x prefix
-	ethFormatted, err = cosmosAddressFromArg(ethAddr.Hex()[2:])
-	require.NoError(t, err)
-	require.Equal(t, baseAddr, ethFormatted)
-}
diff --git a/x/evm/module.go b/x/evm/module.go
index 6b5de639..28d09bc9 100644
--- a/x/evm/module.go
+++ b/x/evm/module.go
@@ -52,7 +52,6 @@ func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
 
 // RegisterRESTRoutes Registers rest routes
 func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
-	//rpc.RegisterRoutes(ctx, rtr, StoreKey)
 }
 
 // GetQueryCmd Gets the root query command of this module
@@ -62,7 +61,7 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
 
 // GetTxCmd Gets the root tx command of this module
 func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
-	return cli.GetTxCmd(cdc)
+	return nil
 }
 
 //____________________________________________________________________________
diff --git a/x/faucet/client/cli/query.go b/x/faucet/client/cli/query.go
index 0a928c7c..65725ecb 100644
--- a/x/faucet/client/cli/query.go
+++ b/x/faucet/client/cli/query.go
@@ -37,17 +37,17 @@ func GetCmdFunded(cdc *codec.Codec) *cobra.Command {
 		Short: "Gets storage for an account at a given key",
 		Args:  cobra.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			cliCtx := context.NewCLIContext().WithCodec(cdc)
+			clientCtx := context.NewCLIContext().WithCodec(cdc)
 
-			res, height, err := cliCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
+			res, height, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
 			if err != nil {
 				return err
 			}
 
 			var out sdk.Coins
 			cdc.MustUnmarshalJSON(res, &out)
-			cliCtx = cliCtx.WithHeight(height)
-			return cliCtx.PrintOutput(out)
+			clientCtx = clientCtx.WithHeight(height)
+			return clientCtx.PrintOutput(out)
 		},
 	}
 }
diff --git a/x/faucet/client/cli/tx.go b/x/faucet/client/cli/tx.go
index 2faf648c..d3382005 100644
--- a/x/faucet/client/cli/tx.go
+++ b/x/faucet/client/cli/tx.go
@@ -41,7 +41,7 @@ func GetCmdRequest(cdc *codec.Codec) *cobra.Command {
 		Args:  cobra.RangeArgs(1, 2),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			inBuf := bufio.NewReader(cmd.InOrStdin())
-			cliCtx := context.NewCLIContext().WithCodec(cdc)
+			clientCtx := context.NewCLIContext().WithCodec(cdc)
 			txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
 
 			amount, err := sdk.ParseCoins(args[0])
@@ -51,7 +51,7 @@ func GetCmdRequest(cdc *codec.Codec) *cobra.Command {
 
 			var recipient sdk.AccAddress
 			if len(args) == 1 {
-				recipient = cliCtx.GetFromAddress()
+				recipient = clientCtx.GetFromAddress()
 			} else {
 				recipient, err = sdk.AccAddressFromBech32(args[1])
 			}
@@ -60,12 +60,12 @@ func GetCmdRequest(cdc *codec.Codec) *cobra.Command {
 				return err
 			}
 
-			msg := types.NewMsgFund(amount, cliCtx.GetFromAddress(), recipient)
+			msg := types.NewMsgFund(amount, clientCtx.GetFromAddress(), recipient)
 			if err := msg.ValidateBasic(); err != nil {
 				return err
 			}
 
-			return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
+			return authclient.GenerateOrBroadcastMsgs(clientCtx, txBldr, []sdk.Msg{msg})
 		},
 	}
 }
diff --git a/x/faucet/client/rest/tx.go b/x/faucet/client/rest/tx.go
index f475bf96..dd634694 100644
--- a/x/faucet/client/rest/tx.go
+++ b/x/faucet/client/rest/tx.go
@@ -15,9 +15,9 @@ import (
 )
 
 // RegisterRoutes register REST endpoints for the faucet module
-func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
-	r.HandleFunc(fmt.Sprintf("/%s/request", types.ModuleName), requestHandler(cliCtx)).Methods("POST")
-	r.HandleFunc(fmt.Sprintf("/%s/funded", types.ModuleName), fundedHandlerFn(cliCtx)).Methods("GET")
+func RegisterRoutes(clientCtx context.CLIContext, r *mux.Router) {
+	r.HandleFunc(fmt.Sprintf("/%s/request", types.ModuleName), requestHandler(clientCtx)).Methods("POST")
+	r.HandleFunc(fmt.Sprintf("/%s/funded", types.ModuleName), fundedHandlerFn(clientCtx)).Methods("GET")
 }
 
 // PostRequestBody defines fund request's body.
@@ -27,10 +27,10 @@ type PostRequestBody struct {
 	Recipient string       `json:"receipient" yaml:"receipient"`
 }
 
-func requestHandler(cliCtx context.CLIContext) http.HandlerFunc {
+func requestHandler(clientCtx context.CLIContext) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var req PostRequestBody
-		if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
+		if !rest.ReadRESTReq(w, r, clientCtx.Codec, &req) {
 			rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
 			return
 		}
@@ -65,19 +65,19 @@ func requestHandler(cliCtx context.CLIContext) http.HandlerFunc {
 			return
 		}
 
-		authclient.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
+		authclient.WriteGenerateStdTxResponse(w, clientCtx, baseReq, []sdk.Msg{msg})
 	}
 }
 
-func fundedHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
+func fundedHandlerFn(clientCtx context.CLIContext) http.HandlerFunc {
 	return func(w http.ResponseWriter, _ *http.Request) {
-		res, height, err := cliCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
+		res, height, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
 		if err != nil {
 			rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
 			return
 		}
 
-		cliCtx = cliCtx.WithHeight(height)
-		rest.PostProcessResponse(w, cliCtx, res)
+		clientCtx = clientCtx.WithHeight(height)
+		rest.PostProcessResponse(w, clientCtx, res)
 	}
 }