Now it is time to define the Msg
for buying names and add it to the ./x/nameservice/msgs.go
file. This code is very similar to SetName
:
// MsgBuyName defines the BuyName message
type MsgBuyName struct {
NameID string
Bid sdk.Coins
Buyer sdk.AccAddress
}
// NewMsgBuyName is the constructor function for MsgBuyName
func NewMsgBuyName(name string, bid sdk.Coins, buyer sdk.AccAddress) MsgBuyName {
return MsgBuyName{
NameID: name,
Bid: bid,
Buyer: buyer,
}
}
// Type Implements Msg.
func (msg MsgBuyName) Type() string { return "nameservice" }
// Name Implements Msg.
func (msg MsgBuyName) Name() string { return "buy_name" }
// ValidateBasic Implements Msg.
func (msg MsgBuyName) ValidateBasic() sdk.Error {
if msg.Buyer.Empty() {
return sdk.ErrInvalidAddress(msg.Buyer.String())
}
if len(msg.NameID) == 0 {
return sdk.ErrUnknownRequest("Name cannot be empty")
}
if !msg.Bid.IsPositive() {
return sdk.ErrInsufficientCoins("Bids must be positive")
}
return nil
}
// GetSignBytes Implements Msg.
func (msg MsgBuyName) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(b)
}
// GetSigners Implements Msg.
func (msg MsgBuyName) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Buyer}
}
Next, in the ./x/nameservice/handler.go
file, add the MsgBuyName
handler to the module router:
// NewHandler returns a handler for "nameservice" type messages.
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgSetName:
return handleMsgSetName(ctx, keeper, msg)
case MsgBuyName:
return handleMsgBuyName(ctx, keeper, msg)
default:
errMsg := fmt.Sprintf("Unrecognized nameservice Msg type: %v", reflect.TypeOf(msg).Name())
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
Finally, define the actual handler
function which performs the state transitions triggered by the message. Keep in mind that at this point that the message has had its ValidateBasic
function run so there has been some input verification. However ValidateBasic
should not have to query the state. Validation logic that is dependent on network state (i.e. address balances) should be performed in the handler
function.
// Handle MsgBuyName
func handleMsgBuyName(ctx sdk.Context, keeper Keeper, msg MsgBuyName) sdk.Result {
if keeper.GetPrice(ctx, msg.NameID).IsGTE(msg.Bid) { // Checks if the the bid price is greater than the price paid by the current owner
return sdk.ErrInsufficientCoins("Bid not high enough").Result() // If not, throw an error
}
if keeper.HasOwner(ctx, msg.NameID) {
_, err := keeper.coinKeeper.SendCoins(ctx, msg.Buyer, keeper.GetOwner(ctx, msg.NameID), msg.Bid)
if err != nil {
return sdk.ErrInsufficientCoins("Buyer does not have enough coins").Result()
}
} else {
_, _, err := keeper.coinKeeper.SubtractCoins(ctx, msg.Buyer, msg.Bid) // If so, deduct the Bid amount from the sender
if err != nil {
return sdk.ErrInsufficientCoins("Buyer does not have enough coins").Result()
}
}
keeper.SetOwner(ctx, msg.NameID, msg.Buyer)
keeper.SetPrice(ctx, msg.NameID, msg.Bid)
return sdk.Result{}
}
First check to make sure that the bid is higher than the current price. Then check to see whether the name already has an owner. If it does, the former owner will get transferred the money from the Buyer
.
If there is no owner, our nameservice
"burns" (sends to an unrecoverable address) the coins from the Buyer
.
If either SubtractCoins
or SendCoins
returns a non-nil error, the handler throws an error, reverting the state transition. Otherwise, using the getters and setters defined on the Keeper
earlier, set the buyer to the new owner and set the new price to be the current bid before exiting.
NOTE: This handler uses functions from the
coinKeeper
to perform currency operations. If your application is performing currency operations you man want to take a look at the godocs for this module.