From f5001d5bc739921533fe8ea26c035178ba3c8b4e Mon Sep 17 00:00:00 2001
From: Kobs <sergeik30@gmail.com>
Date: Fri, 20 Dec 2024 10:31:13 +0100
Subject: [PATCH] KIP-87:Ethereum RPC Abstraction

---
 app/app.go                           |  13 +-
 proto/kira/ethereum/ethereum.proto   |  21 +
 proto/kira/ethereum/genesis.proto    |   8 +
 proto/kira/ethereum/query.proto      |  26 +
 proto/kira/ethereum/tx.proto         |  31 +
 x/ethereum/client/cli/query.go       |  16 +
 x/ethereum/client/cli/tx.go          |  50 ++
 x/ethereum/handler.go                |  27 +
 x/ethereum/keeper/ethereum.go        |  28 +
 x/ethereum/keeper/grpc_query.go      |  25 +
 x/ethereum/keeper/keeper.go          |  37 ++
 x/ethereum/keeper/msg_server.go      | 157 +++++
 x/ethereum/module.go                 | 121 ++++
 x/ethereum/types/codec.go            |  32 ++
 x/ethereum/types/errors.go           |  10 +
 x/ethereum/types/ethereum.pb.go      | 819 +++++++++++++++++++++++++++
 x/ethereum/types/expected_keepers.go |  16 +
 x/ethereum/types/genesis.go          |   6 +
 x/ethereum/types/genesis.pb.go       | 263 +++++++++
 x/ethereum/types/keys.go             |  10 +
 x/ethereum/types/msg.go              |  33 ++
 x/ethereum/types/query.pb.go         | 596 +++++++++++++++++++
 x/ethereum/types/query.pb.gw.go      | 189 +++++++
 x/ethereum/types/tx.pb.go            | 576 +++++++++++++++++++
 24 files changed, 3108 insertions(+), 2 deletions(-)
 create mode 100644 proto/kira/ethereum/ethereum.proto
 create mode 100644 proto/kira/ethereum/genesis.proto
 create mode 100644 proto/kira/ethereum/query.proto
 create mode 100644 proto/kira/ethereum/tx.proto
 create mode 100644 x/ethereum/client/cli/query.go
 create mode 100644 x/ethereum/client/cli/tx.go
 create mode 100644 x/ethereum/handler.go
 create mode 100644 x/ethereum/keeper/ethereum.go
 create mode 100644 x/ethereum/keeper/grpc_query.go
 create mode 100644 x/ethereum/keeper/keeper.go
 create mode 100644 x/ethereum/keeper/msg_server.go
 create mode 100644 x/ethereum/module.go
 create mode 100644 x/ethereum/types/codec.go
 create mode 100644 x/ethereum/types/errors.go
 create mode 100644 x/ethereum/types/ethereum.pb.go
 create mode 100644 x/ethereum/types/expected_keepers.go
 create mode 100644 x/ethereum/types/genesis.go
 create mode 100644 x/ethereum/types/genesis.pb.go
 create mode 100644 x/ethereum/types/keys.go
 create mode 100644 x/ethereum/types/msg.go
 create mode 100644 x/ethereum/types/query.pb.go
 create mode 100644 x/ethereum/types/query.pb.gw.go
 create mode 100644 x/ethereum/types/tx.pb.go

diff --git a/app/app.go b/app/app.go
index 229e887a..6ce98c33 100644
--- a/app/app.go
+++ b/app/app.go
@@ -20,6 +20,9 @@ import (
 	"github.com/KiraCore/sekai/x/distributor"
 	distributorkeeper "github.com/KiraCore/sekai/x/distributor/keeper"
 	distributortypes "github.com/KiraCore/sekai/x/distributor/types"
+	"github.com/KiraCore/sekai/x/ethereum"
+	ethereumkeeper "github.com/KiraCore/sekai/x/ethereum/keeper"
+	ethereumtypes "github.com/KiraCore/sekai/x/ethereum/types"
 	"github.com/KiraCore/sekai/x/evidence"
 	evidencekeeper "github.com/KiraCore/sekai/x/evidence/keeper"
 	evidencetypes "github.com/KiraCore/sekai/x/evidence/types"
@@ -128,6 +131,7 @@ var (
 		collectives.AppModuleBasic{},
 		layer2.AppModuleBasic{},
 		consensus.AppModuleBasic{},
+		ethereum.AppModuleBasic{},
 	)
 
 	// module account permissions
@@ -184,6 +188,7 @@ type SekaiApp struct {
 	CollectivesKeeper     collectiveskeeper.Keeper
 	Layer2Keeper          layer2keeper.Keeper
 	ConsensusParamsKeeper consensusparamkeeper.Keeper
+	EthereumKeeper        ethereumkeeper.Keeper
 
 	// Module Manager
 	mm *module.Manager
@@ -237,6 +242,7 @@ func NewInitApp(
 		collectivestypes.ModuleName,
 		layer2types.StoreKey,
 		consensusparamtypes.StoreKey,
+		ethereumtypes.StoreKey,
 	)
 	tKeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
 
@@ -364,6 +370,7 @@ func NewInitApp(
 		app.DistrKeeper,
 		app.TokensKeeper,
 	)
+	app.EthereumKeeper = ethereumkeeper.NewKeeper(keys[ethereumtypes.StoreKey], appCodec, app.CustomGovKeeper, app.BankKeeper)
 
 	proposalRouter := govtypes.NewProposalRouter(
 		[]govtypes.ProposalHandler{
@@ -439,6 +446,7 @@ func NewInitApp(
 		collectives.NewAppModule(app.CollectivesKeeper),
 		layer2.NewAppModule(app.Layer2Keeper),
 		consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper),
+		ethereum.NewAppModule(app.EthereumKeeper, app.CustomGovKeeper, app.BankKeeper),
 	)
 
 	// During begin block slashing happens after distr.BeginBlocker so that
@@ -451,8 +459,7 @@ func NewInitApp(
 		evidencetypes.ModuleName, stakingtypes.ModuleName,
 		spendingtypes.ModuleName, ubitypes.ModuleName,
 		distributortypes.ModuleName, multistakingtypes.ModuleName, custodytypes.ModuleName,
-		baskettypes.ModuleName,
-		distributortypes.ModuleName, multistakingtypes.ModuleName, custodytypes.ModuleName,
+		ethereumtypes.ModuleName,
 		baskettypes.ModuleName,
 		collectivestypes.ModuleName,
 		layer2types.ModuleName,
@@ -467,6 +474,7 @@ func NewInitApp(
 		feeprocessingtypes.ModuleName,
 		spendingtypes.ModuleName, ubitypes.ModuleName,
 		distributortypes.ModuleName, multistakingtypes.ModuleName, custodytypes.ModuleName,
+		ethereumtypes.ModuleName,
 		baskettypes.ModuleName,
 		collectivestypes.ModuleName,
 		layer2types.ModuleName,
@@ -495,6 +503,7 @@ func NewInitApp(
 		paramstypes.ModuleName,
 		distributortypes.ModuleName,
 		custodytypes.ModuleName,
+		ethereumtypes.ModuleName,
 		multistakingtypes.ModuleName,
 		baskettypes.ModuleName,
 		collectivestypes.ModuleName,
diff --git a/proto/kira/ethereum/ethereum.proto b/proto/kira/ethereum/ethereum.proto
new file mode 100644
index 00000000..5edc499f
--- /dev/null
+++ b/proto/kira/ethereum/ethereum.proto
@@ -0,0 +1,21 @@
+syntax = "proto3";
+package kira.ethereum;
+
+import "gogoproto/gogo.proto";
+import "google/protobuf/any.proto";
+
+option go_package = "github.com/KiraCore/sekai/x/ethereum/types";
+
+message EVMTx {
+  string From     = 1;
+  string To       = 2;
+  string Value    = 3;
+  string Gas      = 4;
+  string GasPrice = 5;
+  string Nonce    = 6;
+  string Data     = 7;
+  int64  ChainId  = 8;
+  string V        = 9;
+  string R        = 10;
+  string S        = 11;
+}
diff --git a/proto/kira/ethereum/genesis.proto b/proto/kira/ethereum/genesis.proto
new file mode 100644
index 00000000..a1c86958
--- /dev/null
+++ b/proto/kira/ethereum/genesis.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+package kira.ethereum;
+
+import "kira/ethereum/ethereum.proto";
+
+option go_package = "github.com/KiraCore/sekai/x/ethereum/types";
+
+message GenesisState { }
diff --git a/proto/kira/ethereum/query.proto b/proto/kira/ethereum/query.proto
new file mode 100644
index 00000000..5bd6006a
--- /dev/null
+++ b/proto/kira/ethereum/query.proto
@@ -0,0 +1,26 @@
+syntax = "proto3";
+package kira.ethereum;
+
+import "gogoproto/gogo.proto";
+import "google/api/annotations.proto";
+import "kira/ethereum/ethereum.proto";
+import "kira/ethereum/tx.proto";
+
+option go_package = "github.com/KiraCore/sekai/x/ethereum/types";
+
+service Query {
+  rpc RelayByAddress (RelayByAddressRequest) returns (RelayByAddressResponse) {
+    option (google.api.http).get = "/kira/ethereum/relay/{addr}";
+  }
+}
+
+message RelayByAddressRequest {
+  bytes addr = 1 [
+    (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress",
+    (gogoproto.moretags) = "yaml:\"addr\""
+  ];
+}
+
+message RelayByAddressResponse {
+  MsgRelay msg_relay = 1;
+}
\ No newline at end of file
diff --git a/proto/kira/ethereum/tx.proto b/proto/kira/ethereum/tx.proto
new file mode 100644
index 00000000..99b1b025
--- /dev/null
+++ b/proto/kira/ethereum/tx.proto
@@ -0,0 +1,31 @@
+syntax = "proto3";
+package kira.ethereum;
+
+import "gogoproto/gogo.proto";
+import "cosmos/base/v1beta1/coin.proto";
+import "cosmos/bank/v1beta1/bank.proto";
+import "cosmos_proto/cosmos.proto";
+import "cosmos/msg/v1/msg.proto";
+import "amino/amino.proto";
+import "kira/ethereum/ethereum.proto";
+
+option go_package = "github.com/KiraCore/sekai/x/ethereum/types";
+
+// Msg defines the ethereum Msg service.
+service Msg {
+  rpc Relay(MsgRelay) returns (MsgRelayResponse);
+}
+
+message MsgRelay {
+  option (gogoproto.equal)           = false;
+  option (gogoproto.goproto_getters) = false;
+
+  bytes address = 1 [
+    (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress",
+    (gogoproto.moretags) = "yaml:\"address\""
+  ];
+
+  string data = 2;
+}
+
+message MsgRelayResponse {}
\ No newline at end of file
diff --git a/x/ethereum/client/cli/query.go b/x/ethereum/client/cli/query.go
new file mode 100644
index 00000000..e6cf5689
--- /dev/null
+++ b/x/ethereum/client/cli/query.go
@@ -0,0 +1,16 @@
+package cli
+
+import (
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	"github.com/spf13/cobra"
+)
+
+// NewQueryCmd returns a root CLI command handler for all x/ethereum transaction commands.
+func NewQueryCmd() *cobra.Command {
+	queryCmd := &cobra.Command{
+		Use:   types.RouterKey,
+		Short: "query commands for the ethereum module",
+	}
+
+	return queryCmd
+}
diff --git a/x/ethereum/client/cli/tx.go b/x/ethereum/client/cli/tx.go
new file mode 100644
index 00000000..1519dd20
--- /dev/null
+++ b/x/ethereum/client/cli/tx.go
@@ -0,0 +1,50 @@
+package cli
+
+import (
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	"github.com/cosmos/cosmos-sdk/client"
+	"github.com/cosmos/cosmos-sdk/client/flags"
+	"github.com/cosmos/cosmos-sdk/client/tx"
+	"github.com/spf13/cobra"
+)
+
+// NewTxCmd returns a root CLI command handler for all x/ethereum transaction commands.
+func NewTxCmd() *cobra.Command {
+	txCmd := &cobra.Command{
+		Use:                        types.ModuleName,
+		Short:                      "ethereum sub commands",
+		DisableFlagParsing:         true,
+		SuggestionsMinimumDistance: 2,
+		RunE:                       client.ValidateCmd,
+	}
+
+	txCmd.AddCommand(GetTxRelay())
+
+	return txCmd
+}
+
+func GetTxRelay() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "relay",
+		Short: "New relay from ETHEREUM to COSMOS",
+		Args:  cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			clientCtx, err := client.GetClientTxContext(cmd)
+			if err != nil {
+				return err
+			}
+
+			msg := types.NewMsgRelay(
+				clientCtx.FromAddress,
+				args[0],
+			)
+
+			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
+		},
+	}
+
+	flags.AddTxFlagsToCmd(cmd)
+	cmd.MarkFlagRequired(flags.FlagFrom)
+
+	return cmd
+}
diff --git a/x/ethereum/handler.go b/x/ethereum/handler.go
new file mode 100644
index 00000000..fe7b6cd6
--- /dev/null
+++ b/x/ethereum/handler.go
@@ -0,0 +1,27 @@
+package ethereum
+
+import (
+	"github.com/KiraCore/sekai/x/ethereum/keeper"
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/types/errors"
+)
+
+// NewHandler returns new instance of handler
+func NewHandler(ck keeper.Keeper, cgk types.CustomGovKeeper, bk types.BankKeeper) sdk.Handler {
+	msgServer := keeper.NewMsgServerImpl(ck, cgk, bk)
+
+	return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
+		ctx = ctx.WithEventManager(sdk.NewEventManager())
+
+		switch msg := msg.(type) {
+		case *types.MsgRelay:
+			{
+				res, err := msgServer.Relay(sdk.WrapSDKContext(ctx), msg)
+				return sdk.WrapServiceResult(ctx, res, err)
+			}
+		default:
+			return nil, errors.Wrapf(errors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg)
+		}
+	}
+}
diff --git a/x/ethereum/keeper/ethereum.go b/x/ethereum/keeper/ethereum.go
new file mode 100644
index 00000000..82d2c47f
--- /dev/null
+++ b/x/ethereum/keeper/ethereum.go
@@ -0,0 +1,28 @@
+package keeper
+
+import (
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	"github.com/cosmos/cosmos-sdk/store/prefix"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+func (k Keeper) SetRelay(ctx sdk.Context, record *types.MsgRelay) {
+	store := ctx.KVStore(k.storeKey)
+	key := append([]byte(types.PrefixKeyRelay), record.Address...)
+
+	store.Set(key, k.cdc.MustMarshal(record))
+}
+
+func (k Keeper) GetRelayByAddress(ctx sdk.Context, address sdk.AccAddress) *types.MsgRelay {
+	prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.PrefixKeyRelay))
+	bz := prefixStore.Get(address)
+
+	if bz == nil {
+		return nil
+	}
+
+	info := new(types.MsgRelay)
+	k.cdc.MustUnmarshal(bz, info)
+
+	return info
+}
diff --git a/x/ethereum/keeper/grpc_query.go b/x/ethereum/keeper/grpc_query.go
new file mode 100644
index 00000000..63227cc8
--- /dev/null
+++ b/x/ethereum/keeper/grpc_query.go
@@ -0,0 +1,25 @@
+package keeper
+
+import (
+	"context"
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type Querier struct {
+	keeper Keeper
+}
+
+func NewQuerier(keeper Keeper) types.QueryServer {
+	return &Querier{keeper: keeper}
+}
+
+var _ types.QueryServer = Querier{}
+
+func (q Querier) RelayByAddress(goCtx context.Context, request *types.RelayByAddressRequest) (*types.RelayByAddressResponse, error) {
+	ctx := sdk.UnwrapSDKContext(goCtx)
+
+	return &types.RelayByAddressResponse{
+		MsgRelay: q.keeper.GetRelayByAddress(ctx, request.Addr),
+	}, nil
+}
diff --git a/x/ethereum/keeper/keeper.go b/x/ethereum/keeper/keeper.go
new file mode 100644
index 00000000..0bca5b9e
--- /dev/null
+++ b/x/ethereum/keeper/keeper.go
@@ -0,0 +1,37 @@
+package keeper
+
+import (
+	appparams "github.com/KiraCore/sekai/app/params"
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	"github.com/cometbft/cometbft/libs/log"
+	"github.com/cosmos/cosmos-sdk/codec"
+	storetypes "github.com/cosmos/cosmos-sdk/store/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// Keeper is for managing token module
+type Keeper struct {
+	cdc      codec.BinaryCodec
+	storeKey storetypes.StoreKey
+	gk       types.CustomGovKeeper
+	bk       types.BankKeeper
+}
+
+// NewKeeper returns instance of a keeper
+func NewKeeper(storeKey storetypes.StoreKey, cdc codec.BinaryCodec, gk types.CustomGovKeeper, bk types.BankKeeper) Keeper {
+	return Keeper{
+		cdc:      cdc,
+		storeKey: storeKey,
+		gk:       gk,
+		bk:       bk,
+	}
+}
+
+// DefaultDenom returns the denom that is basically used for fee payment
+func (k Keeper) DefaultDenom(ctx sdk.Context) string {
+	return appparams.DefaultDenom
+}
+
+func (k Keeper) Logger(ctx sdk.Context) log.Logger {
+	return ctx.Logger().With("module", "x/"+types.ModuleName)
+}
diff --git a/x/ethereum/keeper/msg_server.go b/x/ethereum/keeper/msg_server.go
new file mode 100644
index 00000000..d794d061
--- /dev/null
+++ b/x/ethereum/keeper/msg_server.go
@@ -0,0 +1,157 @@
+package keeper
+
+import (
+	"bytes"
+	"context"
+	"cosmossdk.io/errors"
+	"encoding/hex"
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	"github.com/armon/go-metrics"
+	"github.com/cosmos/cosmos-sdk/telemetry"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+	types2 "github.com/cosmos/cosmos-sdk/x/bank/types"
+	"github.com/cosmos/gogoproto/proto"
+	"github.com/ethereum/go-ethereum/common"
+	ethtypes "github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/crypto"
+	"math/big"
+	"strconv"
+)
+
+type msgServer struct {
+	keeper Keeper
+	cgk    types.CustomGovKeeper
+	bk     types.BankKeeper
+}
+
+// NewMsgServerImpl returns an implementation of the bank MsgServer interface
+// for the provided Keeper.
+func NewMsgServerImpl(keeper Keeper, cgk types.CustomGovKeeper, bk types.BankKeeper) types.MsgServer {
+	return &msgServer{
+		keeper: keeper,
+		cgk:    cgk,
+		bk:     bk,
+	}
+}
+
+var _ types.MsgServer = msgServer{}
+
+func (m msgServer) Relay(goCtx context.Context, relay *types.MsgRelay) (*types.MsgRelayResponse, error) {
+	ctx := sdk.UnwrapSDKContext(goCtx)
+
+	txData, err := hex.DecodeString(relay.Data)
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+
+	var tx = new(types.EVMTx)
+	err = proto.Unmarshal(txData, tx)
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+
+	ethFrom := common.HexToAddress(tx.From)
+	ethTo := common.HexToAddress(tx.To)
+
+	value := new(big.Int)
+	value.SetString(tx.Value, 10)
+
+	gas, err := strconv.Atoi(tx.Gas)
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+
+	gasPrice := new(big.Int)
+	gasPrice.SetString(tx.GasPrice, 10)
+
+	nonce, err := strconv.Atoi(tx.Nonce)
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+
+	data, _ := hex.DecodeString(tx.Data[2:])
+	chainID := big.NewInt(int64(tx.ChainId))
+
+	rBytes, err := hex.DecodeString(tx.R[2:])
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+	sBytes, err := hex.DecodeString(tx.S[2:])
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+	vBytes, err := hex.DecodeString(tx.V[2:])
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+
+	ntx := ethtypes.NewTx(&ethtypes.LegacyTx{
+		Nonce:    uint64(nonce),
+		To:       &ethTo,
+		Value:    value,
+		Gas:      uint64(gas),
+		GasPrice: gasPrice,
+		Data:     data,
+	})
+
+	signer := ethtypes.NewEIP155Signer(chainID)
+	hash := signer.Hash(ntx)
+
+	sig := append(rBytes, append(sBytes, vBytes...)...)
+	pubKey, err := crypto.SigToPub(hash.Bytes(), sig)
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+
+	recoveredAddress := crypto.PubkeyToAddress(*pubKey)
+
+	if !bytes.Equal(recoveredAddress.Bytes(), ethFrom.Bytes()) {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, "Recovered address does not equal sender")
+	}
+
+	var msg = new(types2.MsgSend)
+	err = proto.Unmarshal([]byte(tx.Data), msg)
+	if err != nil {
+		return &types.MsgRelayResponse{}, errors.Wrap(types.ErrEthTxNotValid, err.Error())
+	}
+
+	if err := m.bk.IsSendEnabledCoins(ctx, msg.Amount...); err != nil {
+		return nil, err
+	}
+
+	from, err := sdk.AccAddressFromBech32(msg.FromAddress)
+	if err != nil {
+		return nil, err
+	}
+
+	to, err := sdk.AccAddressFromBech32(msg.ToAddress)
+	if err != nil {
+		return nil, err
+	}
+
+	if m.bk.BlockedAddr(to) {
+		return nil, errors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", msg.ToAddress)
+	}
+
+	err = m.bk.SendCoins(ctx, from, to, msg.Amount)
+	if err != nil {
+		return nil, err
+	}
+
+	defer func() {
+		for _, a := range msg.Amount {
+			if a.Amount.IsInt64() {
+				telemetry.SetGaugeWithLabels(
+					[]string{"tx", "msg", "send"},
+					float32(a.Amount.Int64()),
+					[]metrics.Label{telemetry.NewLabel("denom", a.Denom)},
+				)
+			}
+		}
+	}()
+
+	m.keeper.SetRelay(ctx, relay)
+
+	return &types.MsgRelayResponse{}, nil
+}
diff --git a/x/ethereum/module.go b/x/ethereum/module.go
new file mode 100644
index 00000000..854639dc
--- /dev/null
+++ b/x/ethereum/module.go
@@ -0,0 +1,121 @@
+package ethereum
+
+import (
+	"context"
+	"encoding/json"
+
+	"github.com/KiraCore/sekai/x/ethereum/client/cli"
+	"github.com/KiraCore/sekai/x/ethereum/keeper"
+	"github.com/KiraCore/sekai/x/ethereum/types"
+	abci "github.com/cometbft/cometbft/abci/types"
+	"github.com/cosmos/cosmos-sdk/client"
+	"github.com/cosmos/cosmos-sdk/codec"
+	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/types/module"
+	"github.com/gorilla/mux"
+	"github.com/grpc-ecosystem/grpc-gateway/runtime"
+	"github.com/spf13/cobra"
+)
+
+var (
+	_ module.AppModule      = AppModule{}
+	_ module.AppModuleBasic = AppModuleBasic{}
+)
+
+type AppModuleBasic struct{}
+
+func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {}
+
+func (b AppModuleBasic) Name() string {
+	return types.ModuleName
+}
+
+func (b AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
+	types.RegisterInterfaces(registry)
+}
+
+func (b AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
+	return json.RawMessage("{}")
+}
+
+func (b AppModuleBasic) ValidateGenesis(marshaler codec.JSONCodec, config client.TxEncodingConfig, message json.RawMessage) error {
+	return nil
+}
+
+func (b AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, router *mux.Router) {}
+
+func (b AppModuleBasic) RegisterGRPCRoutes(clientCtx client.Context, serveMux *runtime.ServeMux) {
+	_ = types.RegisterQueryHandlerClient(context.Background(), serveMux, types.NewQueryClient(clientCtx))
+}
+
+func (b AppModuleBasic) RegisterLegacyAminoCodec(amino *codec.LegacyAmino) {
+	types.RegisterCodec(amino)
+}
+
+func (b AppModuleBasic) GetTxCmd() *cobra.Command {
+	return cli.NewTxCmd()
+}
+
+func (b AppModuleBasic) GetQueryCmd() *cobra.Command {
+	return cli.NewQueryCmd()
+}
+
+type AppModule struct {
+	AppModuleBasic
+	ethereumKeeper  keeper.Keeper
+	customGovKeeper types.CustomGovKeeper
+	bankKeeper      types.BankKeeper
+}
+
+func (am AppModule) RegisterServices(cfg module.Configurator) {
+	types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.ethereumKeeper, am.customGovKeeper, am.bankKeeper))
+	querier := keeper.NewQuerier(am.ethereumKeeper)
+	types.RegisterQueryServer(cfg.QueryServer(), querier)
+}
+
+func (am AppModule) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
+	types.RegisterInterfaces(registry)
+}
+
+func (am AppModule) InitGenesis(
+	ctx sdk.Context,
+	cdc codec.JSONCodec,
+	data json.RawMessage,
+) []abci.ValidatorUpdate {
+	return nil
+}
+
+func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
+	return nil
+}
+
+func (AppModule) ConsensusVersion() uint64 { return 1 }
+
+func (am AppModule) RegisterInvariants(registry sdk.InvariantRegistry) {}
+
+func (am AppModule) QuerierRoute() string {
+	return types.QuerierRoute
+}
+
+func (am AppModule) BeginBlock(clientCtx sdk.Context, block abci.RequestBeginBlock) {}
+
+func (am AppModule) EndBlock(ctx sdk.Context, block abci.RequestEndBlock) []abci.ValidatorUpdate {
+	return nil
+}
+
+func (am AppModule) Name() string {
+	return types.ModuleName
+}
+
+func (am AppModule) CheckTx() string {
+	return types.ModuleName
+}
+
+func NewAppModule(keeper keeper.Keeper, customGovKeeper types.CustomGovKeeper, bankKeeper types.BankKeeper) AppModule {
+	return AppModule{
+		ethereumKeeper:  keeper,
+		customGovKeeper: customGovKeeper,
+		bankKeeper:      bankKeeper,
+	}
+}
diff --git a/x/ethereum/types/codec.go b/x/ethereum/types/codec.go
new file mode 100644
index 00000000..e820b173
--- /dev/null
+++ b/x/ethereum/types/codec.go
@@ -0,0 +1,32 @@
+package types
+
+import (
+	"github.com/cosmos/cosmos-sdk/codec"
+	"github.com/cosmos/cosmos-sdk/codec/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/types/msgservice"
+)
+
+// RegisterCodec register codec and metadata
+func RegisterCodec(cdc *codec.LegacyAmino) {
+	cdc.RegisterConcrete(&MsgRelay{}, "kiraHub/MsgRelay", nil)
+}
+
+// RegisterInterfaces register Msg and structs
+func RegisterInterfaces(registry types.InterfaceRegistry) {
+	registry.RegisterImplementations((*sdk.Msg)(nil),
+		&MsgRelay{},
+	)
+
+	msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
+}
+
+var (
+	amino     = codec.NewLegacyAmino()
+	ModuleCdc = codec.NewAminoCodec(amino)
+)
+
+func init() {
+	RegisterCodec(amino)
+	amino.Seal()
+}
diff --git a/x/ethereum/types/errors.go b/x/ethereum/types/errors.go
new file mode 100644
index 00000000..91cadbae
--- /dev/null
+++ b/x/ethereum/types/errors.go
@@ -0,0 +1,10 @@
+package types
+
+import (
+	"github.com/cosmos/cosmos-sdk/types/errors"
+)
+
+// errors
+var (
+	ErrEthTxNotValid = errors.Register(ModuleName, 2, "ETH tx not valid")
+)
diff --git a/x/ethereum/types/ethereum.pb.go b/x/ethereum/types/ethereum.pb.go
new file mode 100644
index 00000000..26687030
--- /dev/null
+++ b/x/ethereum/types/ethereum.pb.go
@@ -0,0 +1,819 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: kira/ethereum/ethereum.proto
+
+package types
+
+import (
+	fmt "fmt"
+	_ "github.com/cosmos/cosmos-sdk/codec/types"
+	_ "github.com/cosmos/gogoproto/gogoproto"
+	proto "github.com/cosmos/gogoproto/proto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type EVMTx struct {
+	From     string `protobuf:"bytes,1,opt,name=From,proto3" json:"From,omitempty"`
+	To       string `protobuf:"bytes,2,opt,name=To,proto3" json:"To,omitempty"`
+	Value    string `protobuf:"bytes,3,opt,name=Value,proto3" json:"Value,omitempty"`
+	Gas      string `protobuf:"bytes,4,opt,name=Gas,proto3" json:"Gas,omitempty"`
+	GasPrice string `protobuf:"bytes,5,opt,name=GasPrice,proto3" json:"GasPrice,omitempty"`
+	Nonce    string `protobuf:"bytes,6,opt,name=Nonce,proto3" json:"Nonce,omitempty"`
+	Data     string `protobuf:"bytes,7,opt,name=Data,proto3" json:"Data,omitempty"`
+	ChainId  int64  `protobuf:"varint,8,opt,name=ChainId,proto3" json:"ChainId,omitempty"`
+	V        string `protobuf:"bytes,9,opt,name=V,proto3" json:"V,omitempty"`
+	R        string `protobuf:"bytes,10,opt,name=R,proto3" json:"R,omitempty"`
+	S        string `protobuf:"bytes,11,opt,name=S,proto3" json:"S,omitempty"`
+}
+
+func (m *EVMTx) Reset()         { *m = EVMTx{} }
+func (m *EVMTx) String() string { return proto.CompactTextString(m) }
+func (*EVMTx) ProtoMessage()    {}
+func (*EVMTx) Descriptor() ([]byte, []int) {
+	return fileDescriptor_4ec492796f7b88e4, []int{0}
+}
+func (m *EVMTx) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *EVMTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_EVMTx.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *EVMTx) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EVMTx.Merge(m, src)
+}
+func (m *EVMTx) XXX_Size() int {
+	return m.Size()
+}
+func (m *EVMTx) XXX_DiscardUnknown() {
+	xxx_messageInfo_EVMTx.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EVMTx proto.InternalMessageInfo
+
+func (m *EVMTx) GetFrom() string {
+	if m != nil {
+		return m.From
+	}
+	return ""
+}
+
+func (m *EVMTx) GetTo() string {
+	if m != nil {
+		return m.To
+	}
+	return ""
+}
+
+func (m *EVMTx) GetValue() string {
+	if m != nil {
+		return m.Value
+	}
+	return ""
+}
+
+func (m *EVMTx) GetGas() string {
+	if m != nil {
+		return m.Gas
+	}
+	return ""
+}
+
+func (m *EVMTx) GetGasPrice() string {
+	if m != nil {
+		return m.GasPrice
+	}
+	return ""
+}
+
+func (m *EVMTx) GetNonce() string {
+	if m != nil {
+		return m.Nonce
+	}
+	return ""
+}
+
+func (m *EVMTx) GetData() string {
+	if m != nil {
+		return m.Data
+	}
+	return ""
+}
+
+func (m *EVMTx) GetChainId() int64 {
+	if m != nil {
+		return m.ChainId
+	}
+	return 0
+}
+
+func (m *EVMTx) GetV() string {
+	if m != nil {
+		return m.V
+	}
+	return ""
+}
+
+func (m *EVMTx) GetR() string {
+	if m != nil {
+		return m.R
+	}
+	return ""
+}
+
+func (m *EVMTx) GetS() string {
+	if m != nil {
+		return m.S
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*EVMTx)(nil), "kira.ethereum.EVMTx")
+}
+
+func init() { proto.RegisterFile("kira/ethereum/ethereum.proto", fileDescriptor_4ec492796f7b88e4) }
+
+var fileDescriptor_4ec492796f7b88e4 = []byte{
+	// 299 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0xcd, 0x4a, 0x03, 0x31,
+	0x10, 0xc7, 0x9b, 0x7e, 0x37, 0x7e, 0x20, 0xa1, 0x87, 0xb1, 0x48, 0x28, 0x9e, 0x8a, 0x87, 0xe6,
+	0xe0, 0x1b, 0xd8, 0x6a, 0x11, 0x51, 0x64, 0x5b, 0xf6, 0xe0, 0x2d, 0xad, 0x71, 0x1b, 0xda, 0xdd,
+	0x29, 0xd9, 0x5d, 0x68, 0xdf, 0xc2, 0xc7, 0xf2, 0xd8, 0xa3, 0x17, 0x41, 0x76, 0x5f, 0x44, 0x92,
+	0xd0, 0x7a, 0xfb, 0xff, 0x7e, 0xff, 0x61, 0x06, 0x86, 0x5e, 0xad, 0xb4, 0x91, 0x42, 0x65, 0x4b,
+	0x65, 0x54, 0x1e, 0x1f, 0xc3, 0x70, 0x63, 0x30, 0x43, 0x76, 0x66, 0xdb, 0xe1, 0x41, 0xf6, 0xba,
+	0x11, 0x46, 0xe8, 0x1a, 0x61, 0x93, 0x1f, 0xea, 0x5d, 0x46, 0x88, 0xd1, 0x5a, 0x09, 0x47, 0xf3,
+	0xfc, 0x43, 0xc8, 0x64, 0xe7, 0xab, 0xeb, 0x1f, 0x42, 0x1b, 0xf7, 0xe1, 0xf3, 0x6c, 0xcb, 0x18,
+	0xad, 0x3f, 0x18, 0x8c, 0x81, 0xf4, 0xc9, 0xa0, 0x13, 0xb8, 0xcc, 0xce, 0x69, 0x75, 0x86, 0x50,
+	0x75, 0xa6, 0x3a, 0x43, 0xd6, 0xa5, 0x8d, 0x50, 0xae, 0x73, 0x05, 0x35, 0xa7, 0x3c, 0xb0, 0x0b,
+	0x5a, 0x9b, 0xc8, 0x14, 0xea, 0xce, 0xd9, 0xc8, 0x7a, 0xb4, 0x3d, 0x91, 0xe9, 0xab, 0xd1, 0x0b,
+	0x05, 0x0d, 0xa7, 0x8f, 0x6c, 0x77, 0xbc, 0x60, 0xb2, 0x50, 0xd0, 0xf4, 0x3b, 0x1c, 0xd8, 0xeb,
+	0x63, 0x99, 0x49, 0x68, 0xf9, 0xeb, 0x36, 0x33, 0xa0, 0xad, 0xd1, 0x52, 0xea, 0xe4, 0xf1, 0x1d,
+	0xda, 0x7d, 0x32, 0xa8, 0x05, 0x07, 0x64, 0xa7, 0x94, 0x84, 0xd0, 0x71, 0xa3, 0x24, 0xb4, 0x14,
+	0x00, 0xf5, 0x14, 0x58, 0x9a, 0xc2, 0x89, 0xa7, 0xe9, 0xdd, 0xf8, 0xab, 0xe0, 0x64, 0x5f, 0x70,
+	0xf2, 0x5b, 0x70, 0xf2, 0x59, 0xf2, 0xca, 0xbe, 0xe4, 0x95, 0xef, 0x92, 0x57, 0xde, 0x6e, 0x22,
+	0x9d, 0x2d, 0xf3, 0xf9, 0x70, 0x81, 0xb1, 0x78, 0xd2, 0x46, 0x8e, 0xd0, 0x28, 0x91, 0xaa, 0x95,
+	0xd4, 0x62, 0xfb, 0xff, 0xee, 0x6c, 0xb7, 0x51, 0xe9, 0xbc, 0xe9, 0x9e, 0x75, 0xfb, 0x17, 0x00,
+	0x00, 0xff, 0xff, 0x2b, 0x88, 0x2d, 0xe3, 0x8c, 0x01, 0x00, 0x00,
+}
+
+func (m *EVMTx) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *EVMTx) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *EVMTx) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.S) > 0 {
+		i -= len(m.S)
+		copy(dAtA[i:], m.S)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.S)))
+		i--
+		dAtA[i] = 0x5a
+	}
+	if len(m.R) > 0 {
+		i -= len(m.R)
+		copy(dAtA[i:], m.R)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.R)))
+		i--
+		dAtA[i] = 0x52
+	}
+	if len(m.V) > 0 {
+		i -= len(m.V)
+		copy(dAtA[i:], m.V)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.V)))
+		i--
+		dAtA[i] = 0x4a
+	}
+	if m.ChainId != 0 {
+		i = encodeVarintEthereum(dAtA, i, uint64(m.ChainId))
+		i--
+		dAtA[i] = 0x40
+	}
+	if len(m.Data) > 0 {
+		i -= len(m.Data)
+		copy(dAtA[i:], m.Data)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.Data)))
+		i--
+		dAtA[i] = 0x3a
+	}
+	if len(m.Nonce) > 0 {
+		i -= len(m.Nonce)
+		copy(dAtA[i:], m.Nonce)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.Nonce)))
+		i--
+		dAtA[i] = 0x32
+	}
+	if len(m.GasPrice) > 0 {
+		i -= len(m.GasPrice)
+		copy(dAtA[i:], m.GasPrice)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.GasPrice)))
+		i--
+		dAtA[i] = 0x2a
+	}
+	if len(m.Gas) > 0 {
+		i -= len(m.Gas)
+		copy(dAtA[i:], m.Gas)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.Gas)))
+		i--
+		dAtA[i] = 0x22
+	}
+	if len(m.Value) > 0 {
+		i -= len(m.Value)
+		copy(dAtA[i:], m.Value)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.Value)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if len(m.To) > 0 {
+		i -= len(m.To)
+		copy(dAtA[i:], m.To)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.To)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.From) > 0 {
+		i -= len(m.From)
+		copy(dAtA[i:], m.From)
+		i = encodeVarintEthereum(dAtA, i, uint64(len(m.From)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintEthereum(dAtA []byte, offset int, v uint64) int {
+	offset -= sovEthereum(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *EVMTx) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.From)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.To)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.Value)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.Gas)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.GasPrice)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.Nonce)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.Data)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	if m.ChainId != 0 {
+		n += 1 + sovEthereum(uint64(m.ChainId))
+	}
+	l = len(m.V)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.R)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	l = len(m.S)
+	if l > 0 {
+		n += 1 + l + sovEthereum(uint64(l))
+	}
+	return n
+}
+
+func sovEthereum(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozEthereum(x uint64) (n int) {
+	return sovEthereum(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *EVMTx) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowEthereum
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: EVMTx: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: EVMTx: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field From", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.From = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field To", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.To = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Value = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Gas", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Gas = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 5:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field GasPrice", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.GasPrice = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 6:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Nonce = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 7:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Data = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 8:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType)
+			}
+			m.ChainId = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ChainId |= int64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 9:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field V", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.V = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 10:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field R", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.R = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 11:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field S", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.S = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipEthereum(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthEthereum
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipEthereum(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowEthereum
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowEthereum
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthEthereum
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupEthereum
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthEthereum
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthEthereum        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowEthereum          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupEthereum = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/x/ethereum/types/expected_keepers.go b/x/ethereum/types/expected_keepers.go
new file mode 100644
index 00000000..d28b8e45
--- /dev/null
+++ b/x/ethereum/types/expected_keepers.go
@@ -0,0 +1,16 @@
+package types
+
+import (
+	govtypes "github.com/KiraCore/sekai/x/gov/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+type CustomGovKeeper interface {
+	GetNetworkProperties(ctx sdk.Context) *govtypes.NetworkProperties
+}
+
+type BankKeeper interface {
+	SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
+	IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error
+	BlockedAddr(addr sdk.AccAddress) bool
+}
diff --git a/x/ethereum/types/genesis.go b/x/ethereum/types/genesis.go
new file mode 100644
index 00000000..ce94a2f7
--- /dev/null
+++ b/x/ethereum/types/genesis.go
@@ -0,0 +1,6 @@
+package types
+
+// DefaultGenesis returns the default CustomGo genesis state
+func DefaultGenesis() *GenesisState {
+	return &GenesisState{}
+}
diff --git a/x/ethereum/types/genesis.pb.go b/x/ethereum/types/genesis.pb.go
new file mode 100644
index 00000000..4b60091f
--- /dev/null
+++ b/x/ethereum/types/genesis.pb.go
@@ -0,0 +1,263 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: kira/ethereum/genesis.proto
+
+package types
+
+import (
+	fmt "fmt"
+	proto "github.com/cosmos/gogoproto/proto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type GenesisState struct {
+}
+
+func (m *GenesisState) Reset()         { *m = GenesisState{} }
+func (m *GenesisState) String() string { return proto.CompactTextString(m) }
+func (*GenesisState) ProtoMessage()    {}
+func (*GenesisState) Descriptor() ([]byte, []int) {
+	return fileDescriptor_538ae6538e22bf0f, []int{0}
+}
+func (m *GenesisState) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *GenesisState) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_GenesisState.Merge(m, src)
+}
+func (m *GenesisState) XXX_Size() int {
+	return m.Size()
+}
+func (m *GenesisState) XXX_DiscardUnknown() {
+	xxx_messageInfo_GenesisState.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GenesisState proto.InternalMessageInfo
+
+func init() {
+	proto.RegisterType((*GenesisState)(nil), "kira.ethereum.GenesisState")
+}
+
+func init() { proto.RegisterFile("kira/ethereum/genesis.proto", fileDescriptor_538ae6538e22bf0f) }
+
+var fileDescriptor_538ae6538e22bf0f = []byte{
+	// 148 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xce, 0xce, 0x2c, 0x4a,
+	0xd4, 0x4f, 0x2d, 0xc9, 0x48, 0x2d, 0x4a, 0x2d, 0xcd, 0xd5, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce,
+	0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x05, 0x49, 0xea, 0xc1, 0x24, 0xa5, 0x64,
+	0x50, 0xd5, 0xc2, 0x18, 0x10, 0xc5, 0x4a, 0x7c, 0x5c, 0x3c, 0xee, 0x10, 0xdd, 0xc1, 0x25, 0x89,
+	0x25, 0xa9, 0x4e, 0x2e, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c,
+	0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, 0x95,
+	0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0xef, 0x9d, 0x59, 0x94, 0xe8, 0x9c,
+	0x5f, 0x94, 0xaa, 0x5f, 0x9c, 0x9a, 0x9d, 0x98, 0xa9, 0x5f, 0x81, 0x30, 0xbe, 0xa4, 0xb2, 0x20,
+	0xb5, 0x38, 0x89, 0x0d, 0x6c, 0xb8, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x3b, 0xc3, 0x16,
+	0xa8, 0x00, 0x00, 0x00,
+}
+
+func (m *GenesisState) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int {
+	offset -= sovGenesis(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *GenesisState) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	return n
+}
+
+func sovGenesis(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozGenesis(x uint64) (n int) {
+	return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *GenesisState) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenesis
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: GenesisState: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenesis(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthGenesis
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipGenesis(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowGenesis
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowGenesis
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowGenesis
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthGenesis
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupGenesis
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthGenesis
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthGenesis        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowGenesis          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/x/ethereum/types/keys.go b/x/ethereum/types/keys.go
new file mode 100644
index 00000000..c429f4ed
--- /dev/null
+++ b/x/ethereum/types/keys.go
@@ -0,0 +1,10 @@
+package types
+
+var (
+	ModuleName   = "ethereum"
+	RouterKey    = ModuleName
+	QuerierRoute = ModuleName
+	StoreKey     = ModuleName
+
+	PrefixKeyRelay = "relay_prefix_"
+)
diff --git a/x/ethereum/types/msg.go b/x/ethereum/types/msg.go
new file mode 100644
index 00000000..6dc22327
--- /dev/null
+++ b/x/ethereum/types/msg.go
@@ -0,0 +1,33 @@
+package types
+
+import (
+	"github.com/KiraCore/sekai/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+func NewMsgRelay(addr sdk.AccAddress, data string) *MsgRelay {
+	return &MsgRelay{addr, data}
+}
+
+func (m *MsgRelay) Route() string {
+	return ModuleName
+}
+
+func (m *MsgRelay) Type() string {
+	return types.MsgTypeCreateCustody
+}
+
+func (m *MsgRelay) ValidateBasic() error {
+	return nil
+}
+
+func (m *MsgRelay) GetSignBytes() []byte {
+	bz := ModuleCdc.MustMarshalJSON(m)
+	return sdk.MustSortJSON(bz)
+}
+
+func (m *MsgRelay) GetSigners() []sdk.AccAddress {
+	return []sdk.AccAddress{
+		m.Address,
+	}
+}
diff --git a/x/ethereum/types/query.pb.go b/x/ethereum/types/query.pb.go
new file mode 100644
index 00000000..0c17fc47
--- /dev/null
+++ b/x/ethereum/types/query.pb.go
@@ -0,0 +1,596 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: kira/ethereum/query.proto
+
+package types
+
+import (
+	context "context"
+	fmt "fmt"
+	github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
+	_ "github.com/cosmos/gogoproto/gogoproto"
+	grpc1 "github.com/cosmos/gogoproto/grpc"
+	proto "github.com/cosmos/gogoproto/proto"
+	_ "google.golang.org/genproto/googleapis/api/annotations"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type RelayByAddressRequest struct {
+	Addr github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,1,opt,name=addr,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"addr,omitempty" yaml:"addr"`
+}
+
+func (m *RelayByAddressRequest) Reset()         { *m = RelayByAddressRequest{} }
+func (m *RelayByAddressRequest) String() string { return proto.CompactTextString(m) }
+func (*RelayByAddressRequest) ProtoMessage()    {}
+func (*RelayByAddressRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_cfdbf4c942f97f7e, []int{0}
+}
+func (m *RelayByAddressRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *RelayByAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_RelayByAddressRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *RelayByAddressRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RelayByAddressRequest.Merge(m, src)
+}
+func (m *RelayByAddressRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *RelayByAddressRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_RelayByAddressRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RelayByAddressRequest proto.InternalMessageInfo
+
+func (m *RelayByAddressRequest) GetAddr() github_com_cosmos_cosmos_sdk_types.AccAddress {
+	if m != nil {
+		return m.Addr
+	}
+	return nil
+}
+
+type RelayByAddressResponse struct {
+	MsgRelay *MsgRelay `protobuf:"bytes,1,opt,name=msg_relay,json=msgRelay,proto3" json:"msg_relay,omitempty"`
+}
+
+func (m *RelayByAddressResponse) Reset()         { *m = RelayByAddressResponse{} }
+func (m *RelayByAddressResponse) String() string { return proto.CompactTextString(m) }
+func (*RelayByAddressResponse) ProtoMessage()    {}
+func (*RelayByAddressResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_cfdbf4c942f97f7e, []int{1}
+}
+func (m *RelayByAddressResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *RelayByAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_RelayByAddressResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *RelayByAddressResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RelayByAddressResponse.Merge(m, src)
+}
+func (m *RelayByAddressResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *RelayByAddressResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_RelayByAddressResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RelayByAddressResponse proto.InternalMessageInfo
+
+func (m *RelayByAddressResponse) GetMsgRelay() *MsgRelay {
+	if m != nil {
+		return m.MsgRelay
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*RelayByAddressRequest)(nil), "kira.ethereum.RelayByAddressRequest")
+	proto.RegisterType((*RelayByAddressResponse)(nil), "kira.ethereum.RelayByAddressResponse")
+}
+
+func init() { proto.RegisterFile("kira/ethereum/query.proto", fileDescriptor_cfdbf4c942f97f7e) }
+
+var fileDescriptor_cfdbf4c942f97f7e = []byte{
+	// 355 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0xce, 0x2c, 0x4a,
+	0xd4, 0x4f, 0x2d, 0xc9, 0x48, 0x2d, 0x4a, 0x2d, 0xcd, 0xd5, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4,
+	0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x05, 0x49, 0xe9, 0xc1, 0xa4, 0xa4, 0x44, 0xd2, 0xf3,
+	0xd3, 0xf3, 0xc1, 0x32, 0xfa, 0x20, 0x16, 0x44, 0x91, 0x94, 0x4c, 0x7a, 0x7e, 0x7e, 0x7a, 0x4e,
+	0xaa, 0x7e, 0x62, 0x41, 0xa6, 0x7e, 0x62, 0x5e, 0x5e, 0x7e, 0x49, 0x62, 0x49, 0x66, 0x7e, 0x5e,
+	0x31, 0x4c, 0x16, 0xd5, 0x74, 0x18, 0x03, 0x2a, 0x2b, 0x86, 0x2a, 0x5b, 0x52, 0x01, 0x11, 0x57,
+	0xca, 0xe5, 0x12, 0x0d, 0x4a, 0xcd, 0x49, 0xac, 0x74, 0xaa, 0x74, 0x4c, 0x49, 0x29, 0x4a, 0x2d,
+	0x2e, 0x0e, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x0a, 0xe1, 0x62, 0x49, 0x4c, 0x49, 0x29,
+	0x92, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x71, 0x72, 0xf8, 0x74, 0x4f, 0x9e, 0xbb, 0x32, 0x31, 0x37,
+	0xc7, 0x4a, 0x09, 0x24, 0xaa, 0xf4, 0xeb, 0x9e, 0xbc, 0x6e, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92,
+	0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x31, 0x94, 0xd2, 0x2d, 0x4e, 0xc9,
+	0xd6, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x73, 0x4c, 0x4e, 0x86, 0x19, 0x0b, 0x36, 0x4d, 0xc9,
+	0x8f, 0x4b, 0x0c, 0xdd, 0xba, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x13, 0x2e, 0xce, 0xdc,
+	0xe2, 0xf4, 0xf8, 0x22, 0x90, 0x2c, 0xd8, 0x52, 0x6e, 0x23, 0x71, 0x3d, 0x94, 0x50, 0xd1, 0xf3,
+	0x2d, 0x4e, 0x07, 0x6b, 0x0e, 0xe2, 0xc8, 0x85, 0xb2, 0x8c, 0x7a, 0x18, 0xb9, 0x58, 0x03, 0x41,
+	0xe1, 0x28, 0xd4, 0xc4, 0xc8, 0xc5, 0x87, 0x6a, 0xb4, 0x90, 0x0a, 0x9a, 0x7e, 0xac, 0x1e, 0x95,
+	0x52, 0x25, 0xa0, 0x0a, 0xe2, 0x3e, 0x25, 0xe5, 0xa6, 0xcb, 0x4f, 0x26, 0x33, 0xc9, 0x0a, 0x49,
+	0xeb, 0xa3, 0x86, 0x24, 0xd8, 0xc1, 0xfa, 0xd5, 0x20, 0xdf, 0xd5, 0x3a, 0xb9, 0x9c, 0x78, 0x24,
+	0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78,
+	0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x16, 0x52, 0x68, 0x79, 0x67, 0x16, 0x25, 0x3a,
+	0xe7, 0x17, 0xa5, 0xea, 0x17, 0xa7, 0x66, 0x27, 0x66, 0xea, 0x57, 0x20, 0x45, 0x0b, 0x28, 0xd4,
+	0x92, 0xd8, 0xc0, 0x51, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x37, 0x16, 0xba, 0xf5, 0x30,
+	0x02, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// QueryClient is the client API for Query service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type QueryClient interface {
+	RelayByAddress(ctx context.Context, in *RelayByAddressRequest, opts ...grpc.CallOption) (*RelayByAddressResponse, error)
+}
+
+type queryClient struct {
+	cc grpc1.ClientConn
+}
+
+func NewQueryClient(cc grpc1.ClientConn) QueryClient {
+	return &queryClient{cc}
+}
+
+func (c *queryClient) RelayByAddress(ctx context.Context, in *RelayByAddressRequest, opts ...grpc.CallOption) (*RelayByAddressResponse, error) {
+	out := new(RelayByAddressResponse)
+	err := c.cc.Invoke(ctx, "/kira.ethereum.Query/RelayByAddress", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// QueryServer is the server API for Query service.
+type QueryServer interface {
+	RelayByAddress(context.Context, *RelayByAddressRequest) (*RelayByAddressResponse, error)
+}
+
+// UnimplementedQueryServer can be embedded to have forward compatible implementations.
+type UnimplementedQueryServer struct {
+}
+
+func (*UnimplementedQueryServer) RelayByAddress(ctx context.Context, req *RelayByAddressRequest) (*RelayByAddressResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method RelayByAddress not implemented")
+}
+
+func RegisterQueryServer(s grpc1.Server, srv QueryServer) {
+	s.RegisterService(&_Query_serviceDesc, srv)
+}
+
+func _Query_RelayByAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(RelayByAddressRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QueryServer).RelayByAddress(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/kira.ethereum.Query/RelayByAddress",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QueryServer).RelayByAddress(ctx, req.(*RelayByAddressRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _Query_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "kira.ethereum.Query",
+	HandlerType: (*QueryServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "RelayByAddress",
+			Handler:    _Query_RelayByAddress_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "kira/ethereum/query.proto",
+}
+
+func (m *RelayByAddressRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *RelayByAddressRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *RelayByAddressRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Addr) > 0 {
+		i -= len(m.Addr)
+		copy(dAtA[i:], m.Addr)
+		i = encodeVarintQuery(dAtA, i, uint64(len(m.Addr)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *RelayByAddressResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *RelayByAddressResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *RelayByAddressResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.MsgRelay != nil {
+		{
+			size, err := m.MsgRelay.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintQuery(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintQuery(dAtA []byte, offset int, v uint64) int {
+	offset -= sovQuery(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *RelayByAddressRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Addr)
+	if l > 0 {
+		n += 1 + l + sovQuery(uint64(l))
+	}
+	return n
+}
+
+func (m *RelayByAddressResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.MsgRelay != nil {
+		l = m.MsgRelay.Size()
+		n += 1 + l + sovQuery(uint64(l))
+	}
+	return n
+}
+
+func sovQuery(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozQuery(x uint64) (n int) {
+	return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *RelayByAddressRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQuery
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: RelayByAddressRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: RelayByAddressRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Addr", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Addr = append(m.Addr[:0], dAtA[iNdEx:postIndex]...)
+			if m.Addr == nil {
+				m.Addr = []byte{}
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQuery(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *RelayByAddressResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQuery
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: RelayByAddressResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: RelayByAddressResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field MsgRelay", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.MsgRelay == nil {
+				m.MsgRelay = &MsgRelay{}
+			}
+			if err := m.MsgRelay.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQuery(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipQuery(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowQuery
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthQuery
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupQuery
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthQuery
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthQuery        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowQuery          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/x/ethereum/types/query.pb.gw.go b/x/ethereum/types/query.pb.gw.go
new file mode 100644
index 00000000..7fae3827
--- /dev/null
+++ b/x/ethereum/types/query.pb.gw.go
@@ -0,0 +1,189 @@
+// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
+// source: kira/ethereum/query.proto
+
+/*
+Package types is a reverse proxy.
+
+It translates gRPC into RESTful JSON APIs.
+*/
+package types
+
+import (
+	"context"
+	"io"
+	"net/http"
+
+	"github.com/golang/protobuf/descriptor"
+	"github.com/golang/protobuf/proto"
+	"github.com/grpc-ecosystem/grpc-gateway/runtime"
+	"github.com/grpc-ecosystem/grpc-gateway/utilities"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/grpclog"
+	"google.golang.org/grpc/metadata"
+	"google.golang.org/grpc/status"
+)
+
+// Suppress "imported and not used" errors
+var _ codes.Code
+var _ io.Reader
+var _ status.Status
+var _ = runtime.String
+var _ = utilities.NewDoubleArray
+var _ = descriptor.ForMessage
+var _ = metadata.Join
+
+func request_Query_RelayByAddress_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq RelayByAddressRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["addr"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "addr")
+	}
+
+	protoReq.Addr, err = runtime.Bytes(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "addr", err)
+	}
+
+	msg, err := client.RelayByAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_Query_RelayByAddress_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq RelayByAddressRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["addr"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "addr")
+	}
+
+	protoReq.Addr, err = runtime.Bytes(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "addr", err)
+	}
+
+	msg, err := server.RelayByAddress(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
+// RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
+// UnaryRPC     :call QueryServer directly.
+// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
+// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead.
+func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error {
+
+	mux.Handle("GET", pattern_Query_RelayByAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		var stream runtime.ServerTransportStream
+		ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_Query_RelayByAddress_0(rctx, inboundMarshaler, server, req, pathParams)
+		md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_RelayByAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
+	return nil
+}
+
+// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but
+// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
+func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
+	conn, err := grpc.Dial(endpoint, opts...)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if err != nil {
+			if cerr := conn.Close(); cerr != nil {
+				grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
+			}
+			return
+		}
+		go func() {
+			<-ctx.Done()
+			if cerr := conn.Close(); cerr != nil {
+				grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
+			}
+		}()
+	}()
+
+	return RegisterQueryHandler(ctx, mux, conn)
+}
+
+// RegisterQueryHandler registers the http handlers for service Query to "mux".
+// The handlers forward requests to the grpc endpoint over "conn".
+func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
+	return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn))
+}
+
+// RegisterQueryHandlerClient registers the http handlers for service Query
+// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient".
+// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient"
+// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
+// "QueryClient" to call the correct interceptors.
+func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error {
+
+	mux.Handle("GET", pattern_Query_RelayByAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_Query_RelayByAddress_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_RelayByAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
+	return nil
+}
+
+var (
+	pattern_Query_RelayByAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"kira", "ethereum", "relay", "addr"}, "", runtime.AssumeColonVerbOpt(false)))
+)
+
+var (
+	forward_Query_RelayByAddress_0 = runtime.ForwardResponseMessage
+)
diff --git a/x/ethereum/types/tx.pb.go b/x/ethereum/types/tx.pb.go
new file mode 100644
index 00000000..9a20f468
--- /dev/null
+++ b/x/ethereum/types/tx.pb.go
@@ -0,0 +1,576 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: kira/ethereum/tx.proto
+
+package types
+
+import (
+	context "context"
+	fmt "fmt"
+	_ "github.com/cosmos/cosmos-proto"
+	_ "github.com/cosmos/cosmos-sdk/types"
+	github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
+	_ "github.com/cosmos/cosmos-sdk/types/msgservice"
+	_ "github.com/cosmos/cosmos-sdk/types/tx/amino"
+	_ "github.com/cosmos/cosmos-sdk/x/bank/types"
+	_ "github.com/cosmos/gogoproto/gogoproto"
+	grpc1 "github.com/cosmos/gogoproto/grpc"
+	proto "github.com/cosmos/gogoproto/proto"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type MsgRelay struct {
+	Address github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,1,opt,name=address,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"address,omitempty" yaml:"address"`
+	Data    string                                        `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (m *MsgRelay) Reset()         { *m = MsgRelay{} }
+func (m *MsgRelay) String() string { return proto.CompactTextString(m) }
+func (*MsgRelay) ProtoMessage()    {}
+func (*MsgRelay) Descriptor() ([]byte, []int) {
+	return fileDescriptor_db51acef94d7c763, []int{0}
+}
+func (m *MsgRelay) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *MsgRelay) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_MsgRelay.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *MsgRelay) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MsgRelay.Merge(m, src)
+}
+func (m *MsgRelay) XXX_Size() int {
+	return m.Size()
+}
+func (m *MsgRelay) XXX_DiscardUnknown() {
+	xxx_messageInfo_MsgRelay.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MsgRelay proto.InternalMessageInfo
+
+type MsgRelayResponse struct {
+}
+
+func (m *MsgRelayResponse) Reset()         { *m = MsgRelayResponse{} }
+func (m *MsgRelayResponse) String() string { return proto.CompactTextString(m) }
+func (*MsgRelayResponse) ProtoMessage()    {}
+func (*MsgRelayResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_db51acef94d7c763, []int{1}
+}
+func (m *MsgRelayResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *MsgRelayResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_MsgRelayResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *MsgRelayResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MsgRelayResponse.Merge(m, src)
+}
+func (m *MsgRelayResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *MsgRelayResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_MsgRelayResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MsgRelayResponse proto.InternalMessageInfo
+
+func init() {
+	proto.RegisterType((*MsgRelay)(nil), "kira.ethereum.MsgRelay")
+	proto.RegisterType((*MsgRelayResponse)(nil), "kira.ethereum.MsgRelayResponse")
+}
+
+func init() { proto.RegisterFile("kira/ethereum/tx.proto", fileDescriptor_db51acef94d7c763) }
+
+var fileDescriptor_db51acef94d7c763 = []byte{
+	// 349 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xbf, 0x4b, 0xc3, 0x40,
+	0x14, 0xc7, 0x13, 0x7f, 0xd6, 0x43, 0x45, 0x83, 0xd8, 0x5a, 0xe4, 0x52, 0x32, 0x15, 0xa1, 0x39,
+	0xaa, 0x5b, 0xb7, 0xb6, 0x0e, 0x82, 0x74, 0xc9, 0x28, 0x88, 0x5c, 0x92, 0x23, 0x0d, 0x69, 0x72,
+	0x25, 0xef, 0x5a, 0xda, 0xbf, 0x40, 0x47, 0xff, 0x84, 0xfe, 0x39, 0x8e, 0x1d, 0x9d, 0x8a, 0xb4,
+	0x8b, 0xb3, 0xa3, 0x93, 0x24, 0x77, 0x91, 0x16, 0x71, 0xc9, 0xfb, 0xf1, 0xc9, 0x7b, 0x7c, 0xbf,
+	0xef, 0xd0, 0x79, 0x14, 0xa6, 0x94, 0x30, 0xd1, 0x67, 0x29, 0x1b, 0xc5, 0x44, 0x4c, 0xec, 0x61,
+	0xca, 0x05, 0x37, 0x8e, 0xb2, 0xbe, 0x5d, 0xf4, 0xab, 0x67, 0x01, 0x0f, 0x78, 0x4e, 0x48, 0x96,
+	0xc9, 0x9f, 0xaa, 0xd8, 0xe3, 0x10, 0x73, 0x20, 0x2e, 0x05, 0x46, 0xc6, 0x4d, 0x97, 0x09, 0xda,
+	0x24, 0x1e, 0x0f, 0x93, 0x3f, 0x3c, 0x89, 0x7e, 0x79, 0x56, 0x28, 0x7e, 0x21, 0xf9, 0x93, 0x5c,
+	0x2c, 0x0b, 0x85, 0xca, 0x6a, 0x34, 0x86, 0x80, 0x8c, 0x9b, 0x59, 0x50, 0xe0, 0x94, 0xc6, 0x61,
+	0xc2, 0x49, 0xfe, 0x55, 0xad, 0xcb, 0x4d, 0x0f, 0x45, 0x22, 0xa9, 0xf5, 0xac, 0xa3, 0x52, 0x0f,
+	0x02, 0x87, 0x0d, 0xe8, 0xd4, 0x78, 0x44, 0xfb, 0xd4, 0xf7, 0x53, 0x06, 0x50, 0xd1, 0x6b, 0x7a,
+	0xfd, 0xb0, 0xd3, 0xfd, 0x5a, 0x98, 0xc7, 0x53, 0x1a, 0x0f, 0x5a, 0x96, 0x02, 0xd6, 0xf7, 0xc2,
+	0x6c, 0x04, 0xa1, 0xe8, 0x8f, 0x5c, 0xdb, 0xe3, 0xb1, 0x92, 0xa5, 0x42, 0x03, 0xfc, 0x88, 0x88,
+	0xe9, 0x90, 0x81, 0xdd, 0xf6, 0xbc, 0xb6, 0x9c, 0x70, 0x8a, 0x9d, 0x86, 0x81, 0x76, 0x7c, 0x2a,
+	0x68, 0x65, 0xab, 0xa6, 0xd7, 0x0f, 0x9c, 0x3c, 0x6f, 0x95, 0x5e, 0x66, 0xa6, 0xf6, 0x39, 0x33,
+	0x35, 0xcb, 0x40, 0x27, 0x85, 0x10, 0x87, 0xc1, 0x90, 0x27, 0xc0, 0xae, 0xef, 0xd0, 0x76, 0x0f,
+	0x02, 0xa3, 0x8d, 0x76, 0xa5, 0xc0, 0xb2, 0xbd, 0x71, 0x78, 0xbb, 0x18, 0xa8, 0x9a, 0xff, 0x80,
+	0x62, 0x53, 0xe7, 0xf6, 0x6d, 0x89, 0xf5, 0xf9, 0x12, 0xeb, 0x1f, 0x4b, 0xac, 0xbf, 0xae, 0xb0,
+	0x36, 0x5f, 0x61, 0xed, 0x7d, 0x85, 0xb5, 0x87, 0xab, 0x35, 0x37, 0xf7, 0x61, 0x4a, 0xbb, 0x3c,
+	0x65, 0x04, 0x58, 0x44, 0x43, 0x32, 0x59, 0x7b, 0xfa, 0xcc, 0x95, 0xbb, 0x97, 0x1f, 0xed, 0xe6,
+	0x27, 0x00, 0x00, 0xff, 0xff, 0x18, 0xb0, 0x86, 0xc7, 0x18, 0x02, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// MsgClient is the client API for Msg service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type MsgClient interface {
+	Relay(ctx context.Context, in *MsgRelay, opts ...grpc.CallOption) (*MsgRelayResponse, error)
+}
+
+type msgClient struct {
+	cc grpc1.ClientConn
+}
+
+func NewMsgClient(cc grpc1.ClientConn) MsgClient {
+	return &msgClient{cc}
+}
+
+func (c *msgClient) Relay(ctx context.Context, in *MsgRelay, opts ...grpc.CallOption) (*MsgRelayResponse, error) {
+	out := new(MsgRelayResponse)
+	err := c.cc.Invoke(ctx, "/kira.ethereum.Msg/Relay", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// MsgServer is the server API for Msg service.
+type MsgServer interface {
+	Relay(context.Context, *MsgRelay) (*MsgRelayResponse, error)
+}
+
+// UnimplementedMsgServer can be embedded to have forward compatible implementations.
+type UnimplementedMsgServer struct {
+}
+
+func (*UnimplementedMsgServer) Relay(ctx context.Context, req *MsgRelay) (*MsgRelayResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Relay not implemented")
+}
+
+func RegisterMsgServer(s grpc1.Server, srv MsgServer) {
+	s.RegisterService(&_Msg_serviceDesc, srv)
+}
+
+func _Msg_Relay_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(MsgRelay)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(MsgServer).Relay(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/kira.ethereum.Msg/Relay",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(MsgServer).Relay(ctx, req.(*MsgRelay))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _Msg_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "kira.ethereum.Msg",
+	HandlerType: (*MsgServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Relay",
+			Handler:    _Msg_Relay_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "kira/ethereum/tx.proto",
+}
+
+func (m *MsgRelay) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *MsgRelay) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MsgRelay) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Data) > 0 {
+		i -= len(m.Data)
+		copy(dAtA[i:], m.Data)
+		i = encodeVarintTx(dAtA, i, uint64(len(m.Data)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.Address) > 0 {
+		i -= len(m.Address)
+		copy(dAtA[i:], m.Address)
+		i = encodeVarintTx(dAtA, i, uint64(len(m.Address)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *MsgRelayResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *MsgRelayResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MsgRelayResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintTx(dAtA []byte, offset int, v uint64) int {
+	offset -= sovTx(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *MsgRelay) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Address)
+	if l > 0 {
+		n += 1 + l + sovTx(uint64(l))
+	}
+	l = len(m.Data)
+	if l > 0 {
+		n += 1 + l + sovTx(uint64(l))
+	}
+	return n
+}
+
+func (m *MsgRelayResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	return n
+}
+
+func sovTx(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozTx(x uint64) (n int) {
+	return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *MsgRelay) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowTx
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: MsgRelay: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: MsgRelay: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTx
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthTx
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthTx
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Address = append(m.Address[:0], dAtA[iNdEx:postIndex]...)
+			if m.Address == nil {
+				m.Address = []byte{}
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowTx
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthTx
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthTx
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Data = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipTx(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthTx
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *MsgRelayResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowTx
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: MsgRelayResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: MsgRelayResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		default:
+			iNdEx = preIndex
+			skippy, err := skipTx(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthTx
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipTx(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowTx
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowTx
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowTx
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthTx
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupTx
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthTx
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthTx        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowTx          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group")
+)