Filters

# Build Your Module

You will integrate your module with the minimal chain that you cloned previously. To facilitate integration and speed up feedback, you will direct the Go package manager to look for this dependency on a local disk. The simplest way is to put the module folder into a folder next to the minimal chain's folder:

Copy $ mkdir checkers-minimal

The ls command should give you:

Copy chain-minimal checkers-minimal minimal-module-example

You can now define the module:

Copy $ cd checkers-minimal $ touch go.mod

In the new file, you define your module name and the Go version:

Copy module github.com/alice/checkers go 1.21

# Protobuf files

On top of the regular Protobuf files, like message and query types, your checkers module type will also be identified with a Protobuf file. You have chosen the package github.com/alice/checkers, so go ahead and create the necessary folders:

Copy $ mkdir -p proto/alice/checkers/v1 $ mkdir -p proto/alice/checkers/module/v1

# Module proto

You define your module in a new proto/alice/checkers/module/v1/module.proto, taking inspiration from minimal-module-example:

Copy syntax = "proto3"; package alice.checkers.module.v1; import "cosmos/app/v1alpha1/module.proto"; // Module is the app config object of the module. // Learn more: https://docs.cosmos.network/main/building-modules/depinject message Module { option (cosmos.app.v1alpha1.module) = { go_import : "github.com/alice/checkers" }; // authority defines the custom module authority. // if not set, defaults to the governance module. string authority = 1; } proto alice ... v1 module.proto View source

Note how the package is alice.checkers.module.v1 and the message name is Module. This means that later on, when you integrate the module in your chain, you will identify it with:

Copy alice.checkers.module.v1.Module

# Necessary Protobuf files

The compilation of your Protobuf files will be done with the scripts/protocgen.sh script, which you can copy from minimal-module-example:

Copy $ mkdir scripts $ cp ../minimal-module-example/scripts/protocgen.sh ./scripts
Copy #!/usr/bin/env bash set -e echo "Generating gogo proto code" cd proto proto_dirs=$(find . -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) for dir in $proto_dirs; do for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do # this regex checks if a proto file has its go_package set to github.com/alice/checkers/api/... # gogo proto files SHOULD ONLY be generated if this is false # you don't want gogo proto to run for proto files which are natively built for google.golang.org/protobuf if grep -q "option go_package" "$file" && grep -H -o -c 'option go_package.*github.com/alice/checkers/api' "$file" | grep -q ':0$'; then buf generate --template buf.gen.gogo.yaml $file fi done done echo "Generating pulsar proto code" buf generate --template buf.gen.pulsar.yaml cd .. cp -r github.com/alice/checkers/* ./ rm -rf api && mkdir api mv alice/checkers/* ./api rm -rf github.com alice scripts protocgen.sh View source

Note how it mentions two files: buf.gen.gogo.yaml and buf.gen.pulsar.yaml. Go ahead and copy them from minimal-module-example. In fact, also copy buf.yaml and the version lock file to avoid surprises:

Copy $ cp ../minimal-module-example/proto/buf* ./proto

To make your life easier, minimal-module-example also provides a make target to compile all the Protobuf files. Copy the whole Makefile:

Copy $ cp ../minimal-module-example/Makefile .
Copy #!/usr/bin/make -f DOCKER := $(shell which docker) ################# ### Build ### ################# test: @echo "--> Running tests" go test -v ./... test-integration: @echo "--> Running integration tests" cd integration; go test -v ./... .PHONY: test test-integration ################## ### Protobuf ### ################## protoVer=0.14.0 protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) proto-all: proto-format proto-lint proto-gen proto-gen: @echo "Generating protobuf files..." @$(protoImage) sh ./scripts/protocgen.sh @go mod tidy proto-format: @$(protoImage) find ./ -name "*.proto" -exec clang-format -i {} \; proto-lint: @$(protoImage) buf lint proto/ --error-format=json .PHONY: proto-all proto-gen proto-format proto-lint ################# ### Linting ### ################# golangci_lint_cmd=golangci-lint golangci_version=v1.51.2 lint: @echo "--> Running linter" @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) @$(golangci_lint_cmd) run ./... --timeout 15m lint-fix: @echo "--> Running linter and fixing issues" @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) @$(golangci_lint_cmd) run ./... --fix --timeout 15m .PHONY: lint lint-fix Makefile View source

Note how it uses the ghcr.io/cosmos/proto-builder:0.14.0 (opens new window) Docker image here to run the protocgen.sh script. This helps with making sure all necessary software is available.

# First Protobuf compilation

Before you run the compilation, because you have not yet defined any Protobuf files for messages, you can comment out this protocgen.sh line:

Copy - cp -r github.com/alice/checkers/* ./ + # cp -r github.com/alice/checkers/* ./ View source

Now run the compilation:

Copy $ make proto-gen

This creates a new api/module/v1/module.pulsar.go (opens new window) file and updates your go.mod. It also creates a new go.sum.

Now you need a couple more Protobuf files.

# Minimum Protobuf objects

You are not defining your games, messages, and queries just yet. However, you already define the facts that your module:

  • Has a genesis, which has a type.
  • Uses params, which also have a type.

Go ahead and define them in a new proto/alice/checkers/v1/types.proto, taking inspiration from minimal-module-example:

Copy syntax = "proto3"; package alice.checkers.v1; option go_package = "github.com/alice/checkers"; import "cosmos_proto/cosmos.proto"; import "gogoproto/gogo.proto"; // Params defines the parameters of the module. message Params {} // GenesisState is the state that must be provided at genesis. message GenesisState { // params defines all the parameters of the module. Params params = 1 [ (gogoproto.nullable) = false ]; } proto alice ... v1 types.proto View source

Because you added a new Protobuf file, you need to compile it too. Uncomment the cp line in protocgen.sh:

Copy - # cp -r github.com/alice/checkers/* ./ + cp -r github.com/alice/checkers/* ./

And run it:

Copy $ make proto-gen

If the process tells you that it downloads a Cosmos SDK version different from 0.50.1, make sure to manually edit your go.mod after the fact.

Copy - github.com/cosmos/cosmos-sdk v0.47.5 + github.com/cosmos/cosmos-sdk v0.50.1 go.mod View source

Next, tidy up the dependencies:

Copy $ go mod tidy

The script has created two new files: api/v1/types.pulsar.go and types.pb.go.

Your module is not viable yet. You need to define it and at least have it conform to the interface expected by an app, in this case chain-minimal.

# Module interface

Once again, you can take inspiration from minimal-module-example.

# General files

Prepare the basics around codec, params, genesis, and module name. You can keep them in the root folder:


The files have a lot of missing dependencies, so go ahead and, once more, run:

Copy $ go mod tidy

See the previous troubleshoot if it updates to a cosmos-sdk other than v0.50.1.

With this done, you can move to defining some necessary functions of the module's keeper.

# Keeper files

Again minimal-module-example is a good guide. Create the keeper folder:

Copy $ mkdir keeper

In it, you define what the keeper holds, its genesis actions, and eventually its message and query servers:


You may need to run go mod tidy again.

At the moment you have not defined any message or query types, so you do not bother with defining the corresponding servers.

# Module files

To be correctly called, the module needs to conform to the chain-minimal expectations. You define functions for the module, the dependency injection, and accessorily to open the possibility to add CLI commands. Create the module folder:

Copy $ mkdir module

In it, again copying from minimal-module-example:


Once more, you need to run:

Copy $ go mod tidy

Your checkers module is not prepared as a standalone module. It is time to integrate it into chain-minimal.

# Integrate checkers in chain-minimal

You now have to direct chain-minimal to integrate your checkers module. Your checkers module is known with the github.com/alice/checkers package, and located in a parallel folder.

Direct chain-minimal to the right dependency, so it does not look for the module on github.com. Go to your go.mod and add:

Copy ... replace ( + github.com/alice/checkers => ../checkers-minimal/ // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. ... go.mod View source

Now, you can define the module in the app/app.yaml file in two locations:


What remains is to update app/app.go, where you place your checkers keeper at the right locations:

Copy import( ... + checkerskeeper "github.com/alice/checkers/keeper" "github.com/cosmos/cosmos-sdk/baseapp" ... _ "cosmossdk.io/api/cosmos/tx/config/v1" // import for side-effects + _ "github.com/alice/checkers/module" // import for side-effects _ "github.com/cosmos/cosmos-sdk/x/auth" // import for side-effects ... ) ... type MiniApp struct { ... ConsensusParamsKeeper consensuskeeper.Keeper + CheckersKeeper checkerskeeper.Keeper ... } ... func NewMiniApp( ... ) (*MiniApp, error) { ... if err := depinject.Inject( ... &app.ConsensusParamsKeeper, + &app.CheckersKeeper, ); err != nil { return nil, err } ... } ... app app.go View source

And that's about it. depinject and the rest take care of initialization and runtime.

The side-effects comments refer to the init() function of Go packages. If you omit the new empty import call, the corresponding init function will not be called. Because of that, your alice.checkers.module.v1.Module Protobuf objects will not be known, which can lead to an unpleasant runtime error such as unable to resolve "alice.checkers.module.v1.Module".

The missing object is only detected at runtime because its value is in app.yaml.

# Run your checkers chain

Just like you did in the previous section, you compile the minimal chain, re-initialize, and start it. You need to re-initialize because your genesis has changed:

Copy $ go mod tidy $ make install $ make init $ minid start

There you have it: your minimal chain with a checkers module is running. After stopping it with CTRL-C, confirm that the checkers module was correctly integrated by calling up:

In there, you can find:

Copy { ... "app_state: { ... + "checkers": { + "params": {} + }, ... } }

This is as expected.

# Up next

Now that you have integrated your empty checkers module, it is time to make it interesting.