# Integrate CosmJS and Keplr
Make sure you have all you need before proceeding:
- You understand the concepts of CosmJS.
- You have the checkers CosmJS codebase up to the external GUI. If not, follow the previous steps or go ahead and clone and checkout this branch (opens new window) to get the version needed for this tutorial.
In the previous sections:
- You created the objects, and the messages and clients that allow you to interface any GUI with your checkers blockchain.
- You learned how to start a running checkers blockchain, in Docker or on your local computer.
- You imported an external checkers GUI to use.
Now, you must integrate the two together:
- Adjust the React app to be able to package CosmJS.
- Work on the CosmJS integration.
For the CosmJS integration, you will:
- Work with the GUI's data structures.
- Fetch all games from the blockchain and display them (without pagination).
- Integrate with Keplr for browser-based players.
- Create a new game.
- Fetch a single game to be played.
- Play on the game, making single moves and double moves.
This exercise assumes that:
You are running your checkers blockchain with:
And have the following in
# Prepare the integration with the checkers blockchain
As always, when moving code to a browser, adjustments are needed.
# Prepare Webpack
Your GUI uses React v18, which uses Webpack v5. Therefore you need to adjust Webpack's configuration (opens new window) to handle some elements (see also the CosmJS documentation). To modify the Webpack configuration in a non-ejected React app, use
react-app-rewired (opens new window) as explained here (opens new window):
Install the new package:
Add a new
You can also pass along the
RPC_URLas an environment variable, as seen in the code block above.
Change the run targets to use
Be careful not to put
See a previous section for how to set
process.env.RPC_URL. It also assumes that you have an RPC endpoint that runs the checkers blockchain, as explained in the previous section.
# GUI data structures
The checkers GUI uses different data structures, which you must understand to convert them correctly and with less effort.
name: string(opens new window) field which can be used as the player's address.
board: number | null(opens new window) field. You must do a conversion from
b*b*...to this type. And at some point, you must ensure that the alignments are correct.
turn: number(opens new window), which can be mapped to
idfield. Instead the GUI developer identified games by their index in an array, which is not adequate for this case. You must add an
IGameInfo. This is saved as a
stringin the blockchain but in practice is a number, which the GUI code expects as game index. Add:
To avoid compilation errors on
index:elsewhere, temporarily add
index?: number. Remember to come back to it and remove the
# Board converter
In order to correctly convert a blockchain board into a GUI board, you want to see how a GUI board is coded. In
components/Game/Board/Board.tsx, add a
console.log(props.squares) (opens new window). Let React recompile, open the browser console, and create a game. You should see printed:
1 represents player 1, i.e. black pieces. Compare that to how a game is saved in the blockchain:
To convert the board, create a new file
src/types/checkers/board.ts and code as follows:
# Game converter
Next you convert a
StoredGame into the
IGameInfo of the GUI:
- You use Cosmos addresses instead of names.
- You put today in the creation date because it is not stored in
- You set the
lastplayed date to deadline minus expiry duration (an adequate solution).
- The possibility of "AI" is not important.
Could an AI player and the blockchain mix together? If the AI has access to a private key that lets it send transactions, it would be a bona fide player. This could be implemented with backend scripts running on a server. See checkers backend scripts for an example of backend scripts for a different use-case.
# Obtain a client
For your React components to communicate with the blockchain they need to have access to a
StargateClient, and at some point to a
SigningStargateClient too. Among the information they need is the RPC URL.
# Pass RPC parameters
The RPC URL is already saved into
process.env.RPC_URL thanks to
react-app-rewired. However, it is better to reduce your components' reliance on
process.env. A simple way to give the RPC URL to the components is for them to:
- Receive an
rpcUrl: stringin the properties.
- Keep a
StargateClientin the component's state that would be instantiated lazily.
With this setup, only
index.tsx would use
For instance, prepare access to
MenuContainer.tsx by adding it to the following:
The properties of
The properties of the component call stack, first in
You have to create the missing
If your compiler still believes that
string | undefined, you may append an
!to force it to
Whenever another component needs the
rpcUrl in this tutorial, adapt and reproduce these steps as necessary.
# Create the
Whenever you need a
StargateClient in a component, you can repeat the following chain of actions for the component in question. For instance, to prepare the client in the state of
Add the field in the state:
Initialize it to
undefinedin the constructor:
Add a method that implements the conditional instantiation:
This creates it only once by saving the client in the state.
CheckersSigningStargateClient is more involved, as you will see later on.
First, make use of what you already prepared. Why choose to start with
MenuContainer? Because that is where all the games are listed.
# Show all games
To show all games, you only need the read-only
StargateClient. There is no need to worry about private keys for now. Where do you query for the games?
# All games container
src/components/Menu/MenuContainer.tsx for the
componentDidMount method, which accesses the browser storage for saved games. You can replace the storage logic with the following steps:
- Obtain a
- Obtain the blockchain games (without pagination).
- Convert the blockchain games to the format the component expects.
- Save them in
saved: IGameInfoof the component's state.
Before adding lines of code in the component, a good next step is to add a new function to
CheckersStargateClient to handle the call and the conversion, or to add a helper function that takes a client as a parameter.
# A specific GUI method
In the future you may want to reuse the
CheckersStargateClient code in another GUI or for backend scripts. To avoid polluting the code, and to avoid a less-elegant helper where you need to pass a Stargate client as a parameter, add an extension method (opens new window) to
In a new
Declare the new extension method that extends your
Add its implementation:
Note this does not care about pagination or games beyond the first 20. This purely-GUI detail is left as an exercise.
# All games call
Add an empty
importfor the extension method file, otherwise the method is not known nor compiled:
asyncand replace the storage code with a call to the new method:
npm start and you should see the list of games in your running chain.
Remember that this exercise assumes that you are running a checkers chain as described at the beginning of this section.
If your list is empty, you can quickly create a game with:
# Individual game menu container
Each game is now listed as a
src/components/Menu/MenuItem.tsx (opens new window). However, in
index of the game is taken from its index in the games array, which is not what you want here. The list needs to use the proper
index of each game. In
Menu.tsx make this change:
If the compiler complains, temporarily add
! (as in
game.index!) if you previously added the
# Show one game
What happens when you click on a game's Resume Game? It opens a page of the form
http://localhost:3000/play/1. The component is
src/components/Game/GameContainer.tsx, from which you have to pick the game information from the blockchain. This process begins as with
- Pass a
rpcUrl(opens new window) along the chain to
GameContainerWrapper(opens new window) then the
GameContainer(opens new window).
- Add a
client: CheckersStargateClient | undefined(opens new window) state field, initialize it (opens new window), and add a lazy instantiation method (opens new window).
GameContainer, you see that
componentDidMount gets all the games from storage then sets the state with the information. Instead, as you did for all games, you have to take the game from the blockchain.
extensions-gui.tsxdeclare another extension method to
CheckersStargateClientdedicated to getting the game as expected by the GUI:
GameContainer.tsxadd an empty import to the extension methods:
componentDidMountmethod, and move the game loading lines to their own
loadGamereplace the storage actions with a fetch from the blockchain:
setStatecall so that
- You force
truesince it is always saved in the blockchain.
game.indexto state is unimportant because it is actually passed in
- By having a separate
loadGame, you can call it again if you know the game has changed.
- It is important that this component fetches the game from the blockchain on its own, because the game page
http://.../play/1could be opened out of nowhere and may not have access to the list of games. Moreover, it may be entirely missing from the list.
- You force
npm start and you should now see your game.
To quickly make a move on your created game with an id of
1, you can run:
Refresh the page to confirm the change.
# Integrate with Keplr
So far you have only made it possible to show the state of games and of the blockchain. This allows your users to poke around without unnecessarily asking them to connect their wallet and thereby disclose their address. However, to create a game or play in one, users need to make transactions. This is where you need to make integration with the Keplr wallet possible.
Install the necessary packages:
# Identify the checkers blockchain
Keplr will need information to differentiate your checkers blockchain from the other chains. You are also going to inform it about your REST API.
Adjust the associated type declaration:
Adjust its passing through Webpack:
With this, prepare your checkers blockchain info in a new
Note the use of
chainId value has to match exactly that returned by
client.getChainId(), or the transaction signer will balk. The
ChainInfo object is copied from the one you used for Theta in the first steps with Keplr section.
# Prepare a signing client
Just as components that need a
CheckersStargateClient keep one in their state, components that need a
SigningCheckersStargateClient keep one in their state too. Some components may have both.
A component may have both
SigningCheckersStargateClient. That is not a problem. In fact it may benefit your project, because with a simple
CheckersStargateClient you can let your users poke around first, and build their trust, before asking them to connect their Keplr account.
The steps to take are the same for each such component.
For instance, in
rpcUrl: string(opens new window) to the props, passed along
Menu.tsx(opens new window) which also needs it as a new prop (opens new window), in turn passed from
MenuContainer.tsx(opens new window).
Add a signing client to the component's state, as well as the potential address of the Keplr user:
Do not forget to initialize them to nothing in the constructor:
The address obtained from Keplr is saved in
creator, because it is accessible from the
OfflineSignerbut not from the
Add a tuple type to return both of them:
This is done because the
setStatefunction does not ensure the state is updated immediately after it has been called, so the lazy instantiation method has to return both.
Inform TypeScript of the Keplr
Add a function that obtains the signing client and signer's address (a.k.a. future transaction
creator) by setting up Keplr and connecting to it:
Setting up Keplr is idempotent (opens new window), so repeating these operations more than once is harmless. You may want to separate these actions into more defined methods at a later optimization stage.
Note too that a default gas price is passed in, so that you can use
"auto"when sending a transaction.
Your component is now ready to send transactions to the blockchain. Why choose
NewGameModal.tsx as the example? Because this is where a new game is created.
# Create a new game
This modal window pops up when you click on New Game:
The player names look ready-made to take the player's blockchain addresses.
Examine the code, and focus on
src/components/Menu/NewGameModal/NewGameModal.tsx. Thanks to the previous preparation with
CheckersSigningStargateClient it is ready to send transactions:
extensions-gui.tsdeclare an extension method to your
CheckersSigningStargateClientthat encapsulates knowledge about how to get the newly created game index out of the events:
Now define it:
Keep in mind:
- For the sake of simplicity, a possible wager is completely omitted.
getCreatedGameIdis defined in a previous section.
NewGameModal.tsxadd an empty import of the extension method so that it is compiled in:
Since you are going to paste addresses into the name field, make sure that the GUI does not truncate them. In
NewGameModal.tsx, change the declaration of
handleSubmitand make it
handleSubmit, avert the need to save to local storage and call your create game extension method:
Next send the player directly to the newly created game:
render()change the React link around the
Buttonto a regular
divso that window redirection appears smooth:
You have now added game creation to your GUI.
If you do not yet know your Keplr address on the checkers network, you will have to test in two passes. To test properly, you need to:
Run the initialization code by pretending to create a game. This makes Keplr prompt you to accept adding the
checkersnetwork and accessing your account. Accept both, but optionally reject the prompt to accept a transaction if your balance is zero.
Select Checkers in Keplr. Make a note of your address, for instance
Use the faucet you started at the beginning of this section to put enough tokens in your Keplr Checkers account.
"1000000stake"will satisfy by a 10x margin.
Now start again to actually create a game. Accept the transaction, and you are redirected to the game page. This time the content of the game is from the blockchain.
# Play a move
GameContainer.tsx there are
saveGame functions. In a blockchain context,
saveGame is irrelevant, as on each move done with a transaction the game will be automatically saved in the blockchain. So add
index: -1 in the game to be saved to get past the compilation error.
makeMove to demonstrate the format it uses:
Now play a move with the current interface. Move your first black piece:
In the blockchain code, this is
fromX: 1, fromY: 2, toX: 2, toY: 3. However, the GUI prints:
Evidently for each position X and Y are flipped. You can encapsulate this knowledge in a helper function:
This is likely a familiar situation. Go back to the board and do as if your piece was moving twice. This time, the GUI prints an array with three positions:
This may not be a legal move but it proves that the GUI lets you make more than one move per turn. You can use this feature to send a transaction with more than one move.
Do some more preparation:
extensions-gui.tsdeclare an extension method to
CheckersStargateClientto check whether the move is valid, with parameters as they are given in the GUI components:
Now define it:
Note that due to the limits of the
canPlayGuiMovefunction, you can only test the first move of a multi-move turn. That is, it uses only
positionsof a potentially longer move. You cannot just test the next moves anyway because it will be rejected for sure. For it to be passed, the board would have to be updated first in the blockchain state.
Declare another extension method, this time for
CheckersSigningStargateClient, to actually make the move with parameters as they are given in the GUI components:
It returns the captured pieces. Now define it:
Note how it maps from the positions array except for the last position. This is to take the moves out of the positions.
With this done:
GameContainerwhat you did in
- Keep a signing client and
creator(opens new window) in the component's state.
- Initialize (opens new window) it in the constructor.
- Add a tuple (opens new window).
- Add a method to lazily instantiate it (opens new window).
- Keep a signing client and
Change the declaration of
makeMoveto make it
makeMovemake sure that the move is likely to be accepted immediately after the existing code that extracts the move. It uses the read-only
StargateClient, which allows players to look around without being asked to disclose their address:
The move test is made with the read-only client. This is so that your users can poke around without connecting for as long as possible.
With this partial assurance, you can make an actual move:
The assurance is partial because what was tested with
canPlayGuiMoveis whether a player of a certain color can make a move or not. When making the move, it takes the Keplr address without cross-checking whether this is indeed the address of the colored player that tested a move. This could be improved either at the cost of another call to get the game, or better by adding the black and red addresses in the component's status.
The remaining code of the
makeMovemethod can be deleted.
Finish with a reload of the game to show its new state:
There is a potentially hard-to-reproduce-in-production race condition bug here. The
loadGameis done immediately after the transaction has completed. However, depending on the implementation of the RPC endpoint, the
loadGamecalls may hit two different servers on the backend. In some instances, the server that answers your
loadGamemay not have fully updated its store and may in fact serve you the old version of your game.
As your GUI matures, you may want to show the expected state of the game before you eventually show its finalized state. Sometimes you may want to show the expected state of the game even before the transaction has completed, and add visual cues hinting at the fact that it is a provisional state.
The same can happen when creating a game, where the second server may return
nullif it has not been updated yet.
Now you can play test in the GUI. Make sure to put your Keplr address as the black player, so that you start with this one. You will need a second account on Keplr with which to play red, otherwise you must play red from the command line. Alternatively, put the same Keplr address for both black and red.
Either way, it is now possible to play the game from the GUI. Congratulations!
If you started the chain in Docker, when you are done you can stop the containers with:
# Further exercise ideas
- Add pagination to the game list.
- Implement typical GUI features, like disabling buttons when their action should be unavailable, or adding a countdown to the forfeit deadline.
- Implement a Web socket to listen to changes. That would be useful when there are two players who cannot communicate otherwise (instead of polling).
To summarize, this section has explored:
- How to prepare for and then integrate CosmJS and Keplr into the GUI of your checkers blockchain, including how to adjust the React app to be able to package CosmJS.
- How to integrate CosmJS, including working with the GUI's data structures, fetching games from the blockchain and displaying them, integrating with Keplr for browser-based players, creating a new game, and fetching a single game to be played.