Skip to content
This repository was archived by the owner on Apr 10, 2026. It is now read-only.

Commit d9927fe

Browse files
authored
Merge pull request #92 from cosmos/aayushijain23/add-new-msg-type-delete
Implement new DeleteName Message Type
2 parents 9edc26f + a702f54 commit d9927fe

22 files changed

Lines changed: 418 additions & 34 deletions

tutorial/README.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,23 @@ Then, just follow along! The first step describes the design of your application
6464
1. [Design](./app-design.md) the application.
6565
2. Begin the implementation of your application in [`./app.go`](./app-init.md).
6666
3. Start building your module by defining some basic [`Types`](types.md).
67-
4. Define the keys needed for your module [`key`](./key.md)
68-
5. Create the main core of the module using the [`Keeper`](./keeper.md).
69-
6. Define state transitions through [`Msgs` and `Handlers`](./msgs-handlers.md).
67+
4. Define the keys needed for your module [`key`](./key.md).
68+
5. Define errors that are custom to your module [`Errors`](./errors.md).
69+
6. Create the main core of the module using the [`Keeper`](./keeper.md).
70+
7. Define state transitions through [`Msgs` and `Handlers`](./msgs-handlers.md).
7071
- [`SetName`](./set-name.md)
7172
- [`BuyName`](./buy-name.md)
72-
7. Make views on your state machine with [`Queriers`](./queriers.md).
73-
8. Create the [`alias file`](./alias.md)
74-
9. Register your types in the encoding format using [`sdk.Codec`](./codec.md).
75-
10. Create [CLI interactions for your module](./cli.md).
76-
11. Create [HTTP routes for clients to access your nameservice](./rest.md).
77-
12. Implement the [AppModule interface](./module.md)
78-
13. Configure your [Genesis state](./genesis.md).
79-
14. Import your module and [finish building your application](./app-complete.md)!
80-
15. Create the [`nsd` and `nscli` entry points](./entrypoint.md) to your application.
81-
16. Setup [dependency management using `go.mod`](./gomod.md).
82-
17. [Build and run](./build-run.md) the example.
83-
18. [Run REST routes](./run-rest.md).
73+
8. Make views on your state machine with [`Queriers`](./queriers.md).
74+
9. Create the [`alias file`](./alias.md)
75+
10. Register your types in the encoding format using [`sdk.Codec`](./codec.md).
76+
11. Create [CLI interactions for your module](./cli.md).
77+
12. Create [HTTP routes for clients to access your nameservice](./rest.md).
78+
13. Implement the [AppModule interface](./module.md)
79+
14. Configure your [Genesis state](./genesis.md).
80+
15. Import your module and [finish building your application](./app-complete.md)!
81+
16. Create the [`nsd` and `nscli` entry points](./entrypoint.md) to your application.
82+
17. Setup [dependency management using `go.mod`](./gomod.md).
83+
18. [Build and run](./build-run.md) the example.
84+
19. [Run REST routes](./run-rest.md).
8485

8586
## [Click here](./app-design.md) to get started with the tutorial!

tutorial/alias.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ const (
2828

2929
```go
3030
var (
31-
NewMsgBuyName = types.NewMsgBuyName
32-
NewMsgSetName = types.NewMsgSetName
33-
NewWhois = types.NewWhois
34-
ModuleCdc = types.ModuleCdc
35-
RegisterCodec = types.RegisterCodec
31+
NewMsgBuyName = types.NewMsgBuyName
32+
NewMsgSetName = types.NewMsgSetName
33+
NewMsgDeleteName = types.NewMsgDeleteName
34+
NewWhois = types.NewWhois
35+
ModuleCdc = types.ModuleCdc
36+
RegisterCodec = types.RegisterCodec
3637
)
3738
```
3839

@@ -42,6 +43,7 @@ var (
4243
type (
4344
MsgSetName = types.MsgSetName
4445
MsgBuyName = types.MsgBuyName
46+
MsgDeleteName = types.MsgDeleteName
4547
QueryResResolve = types.QueryResResolve
4648
QueryResNames = types.QueryResNames
4749
Whois = types.Whois

tutorial/build-run.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ nscli query nameservice whois jack.id
9595

9696
# Alice buys name from jack
9797
nscli tx nameservice buy-name jack.id 10nametoken --from alice
98+
99+
# Alice decides to delete the name she just bought from jack
100+
nscli tx nameservice delete-name jack.id --from alice
101+
102+
# Try out a whois query against the name you just deleted
103+
nscli query nameservice whois jack.id
104+
# > {"value":"","owner":"","price":[{"denom":"nametoken","amount":"1"}]}
98105
```
99106

100107
### Congratulations, you have built a Cosmos SDK application! This tutorial is now complete. If you want to see how to run the same commands using the REST server [click here](run-rest.md).

tutorial/buy-name.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,5 @@ If either `SubtractCoins` or `SendCoins` returns a non-nil error, the handler th
104104

105105
> _*NOTE*_: This handler uses functions from the `coinKeeper` to perform currency operations. If your application is performing currency operations you may want to take a look at the [godocs for this module](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#BaseKeeper) to see what functions it exposes.
106106
107-
### Now that you have your `Msgs` and `Handlers` defined it's time to learn about making the data from these transactions [available for querying](queriers.md)!
107+
### Great, now owners can `BuyName`s! But what if they don't want the name any longer? Your module needs a way for users to delete names! Let us define [define the `DeleteName` message](./delete-name.md).
108+

tutorial/cli.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command {
149149
nameserviceTxCmd.AddCommand(client.PostCommands(
150150
GetCmdBuyName(cdc),
151151
GetCmdSetName(cdc),
152+
GetCmdDeleteName(cdc),
152153
)...)
153154

154155
return nameserviceTxCmd
@@ -208,6 +209,26 @@ func GetCmdSetName(cdc *codec.Codec) *cobra.Command {
208209
}
209210
}
210211

212+
// GetCmdDeleteName is the CLI command for sending a DeleteName transaction
213+
func GetCmdDeleteName(cdc *codec.Codec) *cobra.Command {
214+
return &cobra.Command{
215+
Use: "delete-name [name]",
216+
Short: "Delete the name that you own",
217+
Args: cobra.ExactArgs(1),
218+
RunE: func(cmd *cobra.Command, args []string) error {
219+
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)
220+
221+
txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
222+
223+
msg := types.NewMsgDeleteName(args[0], cliCtx.GetFromAddress())
224+
if err := msg.ValidateBasic(); err != nil {
225+
return err
226+
}
227+
228+
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
229+
},
230+
}
231+
}
211232
```
212233

213234
Notes on the above code:

tutorial/codec.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Codec File
22

3-
To [register your types with Amino](https://github.com/tendermint/go-amino#registering-types) so that they can be encoded/decoded, there is a bit of code that needs to be placed in `./x/nameservice/types/codec.go`. Any interface you create and any struct that implements an interface needs to be declared in the `RegisterCodec` function. In this module the two `Msg` implementations (`SetName` and `BuyName`) need to be registered, but your `Whois` query return type does not. In addition, we define a module specific codec for use later.
3+
To [register your types with Amino](https://github.com/tendermint/go-amino#registering-types) so that they can be encoded/decoded, there is a bit of code that needs to be placed in `./x/nameservice/types/codec.go`. Any interface you create and any struct that implements an interface needs to be declared in the `RegisterCodec` function. In this module the three `Msg` implementations (`SetName`, `BuyName` and `DeleteName`) need to be registered, but your `Whois` query return type does not. In addition, we define a module specific codec for use later.
44

55
```go
66
package types
@@ -19,6 +19,7 @@ func init() {
1919
func RegisterCodec(cdc *codec.Codec) {
2020
cdc.RegisterConcrete(MsgSetName{}, "nameservice/SetName", nil)
2121
cdc.RegisterConcrete(MsgBuyName{}, "nameservice/BuyName", nil)
22+
cdc.RegisterConcrete(MsgDeleteName{}, "nameservice/DeleteName", nil)
2223
}
2324
```
2425

tutorial/delete-name.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Delete Name
2+
3+
## Msg
4+
5+
Now it is time to define the `Msg` for deleting names and add it to the `./x/nameservice/types/msgs.go` file. This code is very similar to `SetName`:
6+
7+
```go
8+
// MsgDeleteName defines a DeleteName message
9+
type MsgDeleteName struct {
10+
Name string `json:"name"`
11+
Owner sdk.AccAddress `json:"owner"`
12+
}
13+
14+
// NewMsgDeleteName is a constructor function for MsgDeleteName
15+
func NewMsgDeleteName(name string, owner sdk.AccAddress) MsgDeleteName {
16+
return MsgDeleteName{
17+
Name: name,
18+
Owner: owner,
19+
}
20+
}
21+
22+
// Route should return the name of the module
23+
func (msg MsgDeleteName) Route() string { return RouterKey }
24+
25+
// Type should return the action
26+
func (msg MsgDeleteName) Type() string { return "delete_name" }
27+
28+
// ValidateBasic runs stateless checks on the message
29+
func (msg MsgDeleteName) ValidateBasic() sdk.Error {
30+
if msg.Owner.Empty() {
31+
return sdk.ErrInvalidAddress(msg.Owner.String())
32+
}
33+
if len(msg.Name) == 0 {
34+
return sdk.ErrUnknownRequest("Name cannot be empty")
35+
}
36+
return nil
37+
}
38+
39+
// GetSignBytes encodes the message for signing
40+
func (msg MsgDeleteName) GetSignBytes() []byte {
41+
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
42+
}
43+
44+
// GetSigners defines whose signature is required
45+
func (msg MsgDeleteName) GetSigners() []sdk.AccAddress {
46+
return []sdk.AccAddress{msg.Owner}
47+
}
48+
```
49+
50+
Next, in the `./x/nameservice/handler.go` file, add the `MsgDeleteName` handler to the module router:
51+
52+
```go
53+
// NewHandler returns a handler for "nameservice" type messages.
54+
func NewHandler(keeper Keeper) sdk.Handler {
55+
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
56+
switch msg := msg.(type) {
57+
case MsgSetName:
58+
return handleMsgSetName(ctx, keeper, msg)
59+
case MsgBuyName:
60+
return handleMsgBuyName(ctx, keeper, msg)
61+
case MsgDeleteName:
62+
return handleMsgDeleteName(ctx, keeper, msg)
63+
default:
64+
errMsg := fmt.Sprintf("Unrecognized nameservice Msg type: %v", msg.Type())
65+
return sdk.ErrUnknownRequest(errMsg).Result()
66+
}
67+
}
68+
}
69+
```
70+
71+
Finally, define the `DeleteName` `handler` function which performs the state transitions triggered by the message. Keep in mind that at this point the message has had its `ValidateBasic` function run so there has been some input verification. However, `ValidateBasic` cannot query application state. Validation logic that is dependent on network state (e.g. account balances) should be performed in the `handler` function.
72+
73+
```go
74+
// Handle a message to delete name
75+
func handleMsgDeleteName(ctx sdk.Context, keeper Keeper, msg MsgDeleteName) sdk.Result {
76+
if !keeper.IsNamePresent(ctx, msg.Name) {
77+
return types.ErrNameDoesNotExist(types.DefaultCodespace).Result()
78+
}
79+
if !msg.Owner.Equals(keeper.GetOwner(ctx, msg.Name)) {
80+
return sdk.ErrUnauthorized("Incorrect Owner").Result()
81+
}
82+
keeper.DeleteWhois(ctx, msg.Name)
83+
return sdk.Result{}
84+
}
85+
```
86+
87+
First check to see if the name currently exists in the store. If not, throw an error and return that to the user. Then check to see if the `Msg` sender is actually the owner of the name (`keeper.GetOwner`). If so, they can delete the name by calling the function on the `Keeper`. If not, throw an error and return that to the user.
88+
89+
### Now that you have your `Msgs` and `Handlers` defined it's time to learn about making the data from these transactions [available for querying](queriers.md)!

tutorial/errors.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Errors
2+
3+
Start by creating a errors.go file within the types folder. Within your errors.go file, define errors that are custom to your module along with their codes.
4+
5+
```go
6+
package types
7+
8+
import (
9+
sdk "github.com/cosmos/cosmos-sdk/types"
10+
)
11+
12+
const (
13+
DefaultCodespace sdk.CodespaceType = ModuleName
14+
15+
CodeNameDoesNotExist sdk.CodeType = 101
16+
)
17+
```
18+
19+
Now it's time to add the corresponding method that'll be called at the time of error handling. For instance, let's say we try to delete a name that is not present in the store. In this case, an error should be thrown as the name does not exist.
20+
21+
```go
22+
func ErrNameDoesNotExist(codespace sdk.CodespaceType) sdk.Error {
23+
return sdk.NewError(codespace, CodeNameDoesNotExist, "Name does not exist")
24+
}
25+
```
26+
We'll see later on in the tutorial where this method is called.
27+
28+
### Now we move on to writing the [Keeper for the module](./keeper.md).
29+
30+

tutorial/genesis.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package nameservice
99

1010
import (
1111
"fmt"
12-
1312
sdk "github.com/cosmos/cosmos-sdk/types"
1413
abci "github.com/tendermint/tendermint/abci/types"
1514
)

tutorial/keeper.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package nameservice
1212
import (
1313
"github.com/cosmos/cosmos-sdk/codec"
1414
"github.com/cosmos/cosmos-sdk/x/bank"
15+
"github.com/cosmos/sdk-application-tutorial/x/nameservice/types"
1516

1617
sdk "github.com/cosmos/cosmos-sdk/types"
1718
)
@@ -66,7 +67,7 @@ Next, add a method to resolve the names (i.e. look up the `Whois` for the `name`
6667
// Gets the entire Whois metadata struct for a name
6768
func (k Keeper) GetWhois(ctx sdk.Context, name string) Whois {
6869
store := ctx.KVStore(k.storeKey)
69-
if !store.Has([]byte(name)) {
70+
if !k.IsNamePresent(ctx, name) {
7071
return NewWhois()
7172
}
7273
bz := store.Get([]byte(name))
@@ -80,6 +81,17 @@ Here, like in the `SetName` method, first access the store using the `StoreKey`.
8081

8182
If a name currently does not exist in the store, it returns a new Whois, which has the minimumPrice initialized in it.
8283

84+
Next, add a method to delete the names:
85+
86+
```go
87+
// Deletes the entire Whois metadata struct for a name
88+
func (k Keeper) DeleteWhois(ctx sdk.Context, name string) {
89+
store := ctx.KVStore(k.storeKey)
90+
store.Delete([]byte(name))
91+
}
92+
```
93+
Here, like in the `SetName` method, first access the store using the `StoreKey`. Next, we delete the name from the store using its `.Delete([]byte)` method. As the store only takes `[]byte`, the `name` string is casted to `[]byte` while passing it as a function parameter.
94+
8395
Now, we add functions for getting specific parameters from the store based on the name. However, instead of rewriting the store getters and setters, we reuse the `GetWhois` and `SetWhois` functions. For example, to set a field, first we grab the whole Whois data, update our specific field, and put the new version back into the store.
8496

8597
```go
@@ -123,6 +135,12 @@ func (k Keeper) SetPrice(ctx sdk.Context, name string, price sdk.Coins) {
123135
whois.Price = price
124136
k.SetWhois(ctx, name, whois)
125137
}
138+
139+
// Check if the name is present in the store or not
140+
func (k Keeper) IsNamePresent(ctx sdk.Context, name string) bool {
141+
store := ctx.KVStore(k.storeKey)
142+
return store.Has([]byte(name))
143+
}
126144
```
127145

128146
The SDK also includes a feature called an `sdk.Iterator`, which returns an iterator over all the `<Key, Value>` pairs in a specific spot in a store.

0 commit comments

Comments
 (0)