Skip to main content
Version: Next



Learn how to configure light client modules and create clients using core IBC and the 02-client submodule.

A last step to finish the development of the light client, is to implement the AppModuleBasic interface to allow it to be added to the chain's app.go alongside other light client types the chain enables.

Finally, a succinct rundown is given of the remaining steps to make the light client operational, getting the light client type passed through governance and creating the clients.

Configuring a light client module

An IBC light client module must implement the AppModuleBasic interface in order to register its concrete types against the core IBC interfaces defined in modules/core/exported. This is accomplished via the RegisterInterfaces method which provides the light client module with the opportunity to register codec types using the chain's InterfaceRegistry. Please refer to the 07-tendermint codec registration.

The AppModuleBasic interface may also be leveraged to install custom CLI handlers for light client module users. Light client modules can safely no-op for interface methods which it does not wish to implement.

Please refer to the core IBC documentation for how to configure additional light client modules alongside 07-tendermint in app.go.

See below for an example of the 07-tendermint implementation of AppModuleBasic.

var _ module.AppModuleBasic = AppModuleBasic{}

// AppModuleBasic defines the basic application module used by the tendermint light client.
// Only the RegisterInterfaces function needs to be implemented. All other function perform
// a no-op.
type AppModuleBasic struct{}

// Name returns the tendermint module name.
func (AppModuleBasic) Name() string {
return ModuleName

// RegisterLegacyAminoCodec performs a no-op. The Tendermint client does not support amino.
func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {}

// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC
// to unmarshal tendermint light client types.
func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {

// DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client.
func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
return nil

// ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client.
func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error {
return nil

// RegisterGRPCGatewayRoutes performs a no-op.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {}

// GetTxCmd performs a no-op. Please see the 02-client cli commands.
func (AppModuleBasic) GetTxCmd() *cobra.Command {
return nil

// GetQueryCmd performs a no-op. Please see the 02-client cli commands.
func (AppModuleBasic) GetQueryCmd() *cobra.Command {
return nil

Creating clients

A client is created by executing a new MsgCreateClient transaction composed with a valid ClientState and initial ConsensusState encoded as protobuf Anys. Generally, this is performed by an off-chain process known as an IBC relayer however, this is not a strict requirement.

See below for a list of IBC relayer implementations:

Stateless checks are performed within the ValidateBasic method of MsgCreateClient.

// MsgCreateClient defines a message to create an IBC client
message MsgCreateClient {
option (gogoproto.goproto_getters) = false;

// light client state
google.protobuf.Any client_state = 1 [(gogoproto.moretags) = "yaml:\"client_state\""];
// consensus state associated with the client that corresponds to a given
// height.
google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""];
// signer address
string signer = 3;

Leveraging protobuf Any encoding allows core IBC to unpack both the ClientState and ConsensusState into their respective interface types registered previously using the light client module's RegisterInterfaces method.

Within the 02-client submodule, the ClientState is then initialized with its own isolated key-value store, namespaced using a unique client identifier.

In order to successfully create an IBC client using a new client type, it must be supported. Light client support in IBC is gated by on-chain governance. The allow list may be updated by submitting a new governance proposal to update the 02-client parameter AllowedClients.

See below for example:

%s tx gov submit-proposal <path/to/proposal.json> --from <key_or_address>

where proposal.json contains:

"title": "IBC Clients Param Change",
"summary": "Update allowed clients",
"messages": [
"@type": "/ibc.core.client.v1.MsgUpdateParams",
"signer": "cosmos1...", // The gov module account address
"params": {
"allowed_clients": ["06-solomachine", "07-tendermint", "0x-new-client"]
"metadata": "AQ==",
"deposit": "100stake"

If the AllowedClients list contains a single element that is equal to the wildcard "*", then all client types are allowed and it is thus not necessary to submit a governance proposal to update the parameter.