mukan-ignite/docs/versioned_docs/version-v0.25/guide/05-scavenge/07-keeper.md
Mukan Erkin Törük 26b204bd04
Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

7.8 KiB

sidebar_position
7

Keeper

Keepers are a Cosmos SDK abstraction whose role is to manage access to the subset of the state defined by various modules.

Create scavenge

Make the required changes in the x/scavenge/keeper/msg_server_submit_scavenge.go file so the create scavenge method can manage the following:

  • Check that a scavenge with a given solution hash doesn't exist
  • Send tokens from the scavenge creator account to a module account
  • Write the scavenge to the store
// x/scavenge/keeper/msg_server_submit_scavenge.go

package keeper

import (
	"context"

	sdk "github.com/cosmos/cosmos-sdk/types"
	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
	"github.com/tendermint/tendermint/crypto"

	"scavenge/x/scavenge/types"
)

func (k msgServer) SubmitScavenge(goCtx context.Context, msg *types.MsgSubmitScavenge) (*types.MsgSubmitScavengeResponse, error) {
	// get context that contains information about the environment, such as block height
	ctx := sdk.UnwrapSDKContext(goCtx)

	// create a new scavenge from the data in the MsgSubmitScavenge message
	var scavenge = types.Scavenge{
		Index:        msg.SolutionHash,
		Description:  msg.Description,
		SolutionHash: msg.SolutionHash,
		Reward:       msg.Reward,
	}

	// try getting a scavenge from the store using the solution hash as the key
	_, isFound := k.GetScavenge(ctx, scavenge.SolutionHash)

	// return an error if a scavenge already exists in the store
	if isFound {
		return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Scavenge with that solution hash already exists")
	}

	// get address of the Scavenge module account
	moduleAcct := sdk.AccAddress(crypto.AddressHash([]byte(types.ModuleName)))

	// convert the message creator address from a string into sdk.AccAddress
	scavenger, err := sdk.AccAddressFromBech32(msg.Creator)
	if err != nil {
		panic(err)
	}

	// convert tokens from string into sdk.Coins
	reward, err := sdk.ParseCoinsNormalized(scavenge.Reward)
	if err != nil {
		panic(err)
	}

	// send tokens from the scavenge creator to the module account
	sdkError := k.bankKeeper.SendCoins(ctx, scavenger, moduleAcct, reward)
	if sdkError != nil {
		return nil, sdkError
	}

	// write the scavenge to the store
	k.SetScavenge(ctx, scavenge)
	return &types.MsgSubmitScavengeResponse{}, nil
}

Notice the use of moduleAcct. This account is not controlled by a public key pair, but is a reference to an account that is owned by this actual module. moduleAcct is used to hold the bounty reward that is attached to a scavenge until that scavenge has been solved, at which point the bounty is paid to the account who solved the scavenge.

SubmitScavenge uses the SendCoins method from the bank module. When you scaffolded the scavenge module, you used --dep bank to specify a dependency between the scavenge and bank modules. This dependency automatically created an expected_keepers.go file with a BankKeeper interface.

To use the BankKeeper interface in the keeper methods of the scavenge module, add SendCoins to the x/scavenge/types/expected_keepers.go file:

// x/scavenge/types/expected_keepers.go

package types

import (
	sdk "github.com/cosmos/cosmos-sdk/types"
)

type BankKeeper interface {
	SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
}

Commit Solution

Make the required changes in the x/scavenge/keeper/msg_server_commit_solution.go file so the commit solution method can manage the following:

  • Check that commit with a given hash doesn't exist in the store
  • Write a new commit to the store
// x/scavenge/keeper/msg_server_commit_solution.go

package keeper

import (
	"context"

	sdk "github.com/cosmos/cosmos-sdk/types"
	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

	"scavenge/x/scavenge/types"
)

func (k msgServer) CommitSolution(goCtx context.Context, msg *types.MsgCommitSolution) (*types.MsgCommitSolutionResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)

	// create a new commit from the information in the MsgCommitSolution message
	var commit = types.Commit{
		Index:                 msg.SolutionScavengerHash,
		SolutionHash:          msg.SolutionHash,
		SolutionScavengerHash: msg.SolutionScavengerHash,
	}

	// try getting a commit from the store using the solution+scavenger hash as the key
	_, isFound := k.GetCommit(ctx, commit.SolutionScavengerHash)

	// return an error if a commit already exists in the store
	if isFound {
		return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Commit with that hash already exists")
	}

	// write commit to the store
	k.SetCommit(ctx, commit)
	return &types.MsgCommitSolutionResponse{}, nil
}

Reveal Solution

Make the required changes in the x/scavenge/keeper/msg_server_reveal_solution.go file so the reveal solution method can manage the following:

  • Check that a commit with a given hash exists in the store
  • Check that a scavenge with a given solution hash exists in the store
  • Check that the scavenge hasn't already been solved
  • Send tokens from the module account to the account that revealed the correct anwer
  • Write the updated scavenge to the store
// x/scavenge/keeper/msg_server_reveal_solution.go

package keeper

import (
	"context"
	"crypto/sha256"
	"encoding/hex"

	sdk "github.com/cosmos/cosmos-sdk/types"
	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
	"github.com/tendermint/tendermint/crypto"

	"scavenge/x/scavenge/types"
)

func (k msgServer) RevealSolution(goCtx context.Context, msg *types.MsgRevealSolution) (*types.MsgRevealSolutionResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)

	// concatenate a solution and a scavenger address and convert it to bytes
	var solutionScavengerBytes = []byte(msg.Solution + msg.Creator)

	// find the hash of solution and address
	var solutionScavengerHash = sha256.Sum256(solutionScavengerBytes)

	// convert the hash to a string
	var solutionScavengerHashString = hex.EncodeToString(solutionScavengerHash[:])

	// try getting a commit using the hash of solution and address
	_, isFound := k.GetCommit(ctx, solutionScavengerHashString)

	// return an error if a commit doesn't exist
	if !isFound {
		return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Commit with that hash doesn't exists")
	}

	// find a hash of the solution
	var solutionHash = sha256.Sum256([]byte(msg.Solution))

	// encode the solution hash to string
	var solutionHashString = hex.EncodeToString(solutionHash[:])
	var scavenge types.Scavenge

	// get a scavenge from the stre using the solution hash
	scavenge, isFound = k.GetScavenge(ctx, solutionHashString)

	// return an error if the solution doesn't exist
	if !isFound {
		return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Scavenge with that solution hash doesn't exists")
	}

	// check that the scavenger property contains a valid address
	_, err := sdk.AccAddressFromBech32(scavenge.Scavenger)

	// return an error if a scavenge has already been solved
	if err == nil {
		return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Scavenge has already been solved")
	}

	// save the scavebger address to the scavenge
	scavenge.Scavenger = msg.Creator

	// save the correct solution to the scavenge
	scavenge.Solution = msg.Solution

	// get address of the module account
	moduleAcct := sdk.AccAddress(crypto.AddressHash([]byte(types.ModuleName)))

	// convert scavenger address from string to sdk.AccAddress
	scavenger, err := sdk.AccAddressFromBech32(scavenge.Scavenger)
	if err != nil {
		panic(err)
	}

	// parse tokens from a string to sdk.Coins
	reward, err := sdk.ParseCoinsNormalized(scavenge.Reward)
	if err != nil {
		panic(err)
	}

	// send tokens from a module account to the scavenger
	sdkError := k.bankKeeper.SendCoins(ctx, moduleAcct, scavenger, reward)
	if sdkError != nil {
		return nil, sdkError
	}

	// save the updated scavenge to the store
	k.SetScavenge(ctx, scavenge)
	return &types.MsgRevealSolutionResponse{}, nil
}