# The Game Wager Elements
Make sure you have all you need before proceeding:
- You understand the concepts of modules), keepers, and Protobuf.
- Have Go installed.
- The checkers blockchain codebase up to game expiry handling. You can get there by following the previous steps or checking out the relevant version (opens new window).
With the introduction of a game expiry in the previous section and others you have now addressed the cases when two players start a game and finish it or let it expire.
In this section you will add an extra layer to a game with wagers or stakes. Your application already includes all the necessary modules. This section relies on the bank
module in particular.
Players choose to wager money or not, and the winner gets both wagers. The forfeiter loses their wager. To reduce complexity start by letting players wager in the staking token of your application.
# New information
Add this wager value to the StoredGame
's Protobuf definition:
You can let players choose the wager they want by adding a dedicated field in the message to create a game, in proto/checkers/tx.proto
:
To have Starport and Protobuf recompile these two files, you can use:
Now add a helper function to StoredGame
using the Cosmos SDK Coin
in full_game.go
. It encapsulates information about the wager:
Where sdk.DefaultBondDenom
is most likely "stake"
.
# Saving the wager
Time to make sure that the new field is saved in the storage and it is part of the creation event.
First, define a new event key as a constant:
Next set the actual value in the new
StoredGame
as it is instantiated in the create game handler:And in the event:
Also modify the constructor among the interface definition of
MsgCreateGame
inx/checkers/types/message_create_game.go
to avoid surprises:
# Declaring expectations
Alone, the Wager
field does not make players pay the wager or receive rewards. You need to add the handling actions. These handling actions must ask the bank
module to perform the required token transfers. For that your keeper needs to ask for a bank
instance during setup.
Remember the only way to have access to a capability with the object-capability model of the Cosmos SDK, is to be given the reference to an instance which already has this capability.
Implement payment handling by having your keeper hold wagers in escrow while the game is being played. The bank
module has functions to transfer tokens from any account to your module and vice-versa.
It is best practice to to declare an interface that narrowly declares the functions from other modules that you expect for your module. The conventional file for these declarations is x/checkers/types/expected_keepers.go
.
The bank
module is capable of a lot but all you need here are two of the bank
's functions. So you redeclare the functions like so:
These two functions must exactly match the functions declared in the bank
's keeper.go file (opens new window). Copy the file. Any object with these two functions is a BankKeeper
.
# Obtaining the capability
With your requirements declared it is time to make sure your keeper receives a reference to a bank keeper. First add a BankKeeper
to your keeper in x/checkers/keeper/keeper.go
:
This BankKeeper
is your newly declared narrow interface. Do not forget to adjust the constructor accordingly:
Finally you need to update where the constructor is called and pass a proper instance of bank keeper. This happens in app/app.go
:
This app.BankKeeper
is a full bank
keeper that also conforms to your BankKeeper
interface because you mastered that copy and paste move.
# Preparing expected errors
There are several new error situations which you can enumerate with new variables:
# Money handling steps
With the bank
now in your keeper it is time to have your keeper handle the money. Keep this concern in its own file as the keeper is reused on a play, reject, and forfeit.
Create the new file x/checkers/keeper/wager_handler.go
and add three functions to collect a wager, refund a wager, and pay winnings:
The Must
prefix in the function means that the transaction either takes place or a panic is issued. If a player cannot pay the wager, it is a user-side error and the user must be informed of a failed transaction. If the module cannot pay, it means the escrow account has failed. This error is much more serious. An invariant has been violated and the whole application must be terminated.
Now it is time to set up collecting a wager, paying winnings, and refunding a wager:
Collecting wagers happens on a player's first move. Therefore, differentiate between players:
Get the address for the black player:
Try to transfer into the escrow:
Then do the same for the red player.
Paying winnings takes place when the game has a declared winner. So first get the winner. "No winner" is not an acceptable situation in this
MustPayWinnings
. The caller of the function must know:Then get the winnings to pay:
You double the wager only if the red player has also played and therefore both players have paid their wagers. Then pay the winner:
Finally, refunding wagers takes place when the game has partially started, i.e. only one party has paid, or when the game ends in a draw. In this narrow case of
MustRefundWager
:Refund the black player when there has been a single move:
If the module cannot pay, then it is a panic as the escrow has failed.
# Insert wager handling
With the desired steps defined in the wager handling functions, it is time to invoke them at the right places in the message handlers.
When a player plays for the first time:
When a player wins as a result of a move:
When a player rejects a game:
When a game expires and there is a forfeit. Make sure to only refund or pay full winnings when applicable. The logic needs to be adjusted:
# Next up
You can skip ahead and see how you can integrate foreign tokens via the use of IBC. Or take a look at the next section to prevent spam and reward validators proportional to their effort in your checkers blockchain.