Creating the Backend for your Plugin
Welcome to the second tutorial in our series of launching an HTML5 game in the ARK Desktop Wallet! This series is aimed at developers of all skill levels. The goal of this series is to be able to convert an HTML5 game to work as a fully functioning plugin within the ARK Desktop Wallet. The first set of tutorials will explain how to interact with the ARK blockchain in a standalone HTML5 environment before finally jumping over into the ARK Desktop Wallet.
We left off last time with a self-contained Construct 3 app that lets us make sure a supplied address is valid on the ARK Public Network. Now it’s time to evolve this to work with any ARK-powered network of our choice and start building the back-end of our game.
For the rest of this tutorial series, we’re going to focus on building the back-end of our game and using Connect 4, a turn-based blockchain game as our ongoing example and template. We will let a player host a game by deciding their wager, and once a challenger matches it, the game will begin. We’ll use a standard 7 x 6 board and each player will take their turn to pick a column and insert their disc from 1 to 7. If a player lines up 4 discs either vertically, horizontally or diagonally, they’ll win the prize; if the game ends in a tie, both players receive their entry fee back. For simplicity, we will utilize ARK’s Smartbridge field to pick our chosen column on the board from 1 to 7. If you would like to take a more advanced approach, you can consider making custom transactions with GTI on your own bridgechain to make this game truly decentralized.
While this all may seem daunting at first, we will be making this as simple as possible. All of these tasks will be broken down into manageable sections over the next few tutorials. In this following section, we will look at how to send and receive transactions within the game.
Information
WAIT: Before we go any further, check the ARK Core Documentation to understand how to create your first Core module. Follow the guide and use the starter module template project. It will be the base for the rest of the tutorial.
Now that you’re set up with your skeleton module, let’s list our objectives for what we want our Core plugin to do by the end of this tutorial. We’re going to:
- Set up a WebSocket server so our Construct 3 app and Core plugin can talk to each other;
- Send the network version (which we talked about in the last tutorial) whenever the app connects to the server, so it knows the address format to validate;
- Generate a new ARK address to receive transactions;
- Listen for incoming transactions sent to that address.
Let’s devote some time to each of the sections above. All of our code will be written to defaults.ts and manager.ts so open these in your favorite text editor and let’s begin!
Setting up a WebSocket server
First, we want to set up a WebSocket server. We will use WebSockets because they allow for easy bidirectional communication between our plugin and Construct 3 app. WebSockets communicate with a host and a port, and to begin we will define these in defaults.ts. Replace the contents of that file with the following:
1export const defaults = {2 enabled: true,3 host: "0.0.0.0",4 port: 100005};
We use host 0.0.0.0 which means we’ll accept connections to any of the IP addresses on our server, and we’ll listen on port number 10000. Save that file and close it, as all our remaining changes will be made to manager.ts.
To do this we first import the ws dependency by adding the following line to the top of our file:
1import * as WebSocket from "ws";
Now we want to start our WebSocket server when our plugin starts. You might have already spotted the start method in manager.ts which is where we’ll start our server:
1public start(options: any) {2 this.logger.info("Initialization of dApp");3}
Let’s change this to start our WebSocket server and print a more descriptive message:
1public start(options: any) {2 try {3 const server = new WebSocket.Server({ host: options.host, port: options.port });4 this.logger.info(`Connect 4 WebSocket server listening on ws://${options.host}:${options.port}`);5 6 } catch (error) {7 this.logger.error(`Connect 4 WebSocket server could not start: ${error.message}`);8 }9}
What does this do? It starts a WebSocket server using the host and port values we earlier defined in defaults.ts and prints a message in our logs to confirm that the server started successfully, or, if it failed, it will explain what went wrong.
Sending the network version
We want to send the network version to any client that accesses our WebSocket as soon as they connect. To do this we listen for a connection event from the WebSocket server, which fires when a new connection is established, and then we send the network version to the new connection. Add the following code after the this.logger.info line:
1const config = app.getConfig();2const networkData = JSON.stringify({ networkVersion: config.get("network.pubKeyHash") });3 4server.on("connection", websocket => {5 websocket.send(networkData);6});
At this point, you can run yarn set up to build our plugin, restart your Core relay, and check the logs. You should see something like this:
11|ark-relay | [2020-02-17 19:00:00.000] INFO : Connect 4 WebSocket server listening on ws://0.0.0.0:10000
We can check that the WebSocket is working by heading over to http://www.websocket.org/echo.html and entering ws://X.X.X.X:10000 (where X.X.X.X is the IP address of your server running our Core plugin) and clicking Connect. If you followed the steps above, you should see the network version appear in the Log box:
Generating a new ARK address to receive transactions
We need our plugin to generate a new address to receive wagers and process the Smartbridge messages to update our game state. To do this, our Construct 3 app will send a message {action: “new”} to the server which will trigger this action. Our Core plugin needs to listen for this message and act upon it, so we’ll add the following code immediately after the websocket.send(networkData) line above:
1websocket.on("message", message => { 2 try { 3 const data = JSON.parse(message.toString()); 4 if (data.action === "new") { 5 const { address, passphrase } = this.generateAddress(); 6 this.addresses[address] = passphrase; 7 // @ts-ignore 8 websocket.address = address; 9 websocket.send(JSON.stringify({ address }));10 }11 } catch (error) {12 this.logger.error(error.stack);13 }14});
You’ll see we use a new method, generateAddress, so we have to write the code for that too. Paste this below the end of our start method and before the stop method:
1private generateAddress() {2 const passphrase = generateMnemonic();3 const address = Identities.Address.fromPassphrase(passphrase);4 5 return { address, passphrase };6}
We’ll also need to add a couple of imports to the top of our code to access these new methods:
1import { generateMnemonic } from "bip39";2import { Identities } from "@arkecosystem/crypto";3 4// Lastly we add a new addresses private variable immediately above // the private readonly logger line:5private addresses = {};
This is quite a lot of code so let’s break it down. We’re listening for any incoming messages over the WebSocket that contain {action: “new”} signaling that our Construct 3 app wants to generate a new address for a new game. This calls upon our new generateAddress method to internally produce a new wallet passphrase and convert it to an address. It is now stored in the addresses object which keeps track of all addresses created during this session than will be used to listen for incoming transactions.
Listen for incoming transactions
The final part of our Core plugin for this part of the tutorial will listen for incoming transactions. If the receiving address matches one of the addresses generated earlier, we’ll send its data back to our Construct 3 app via the WebSocket.
This chunk of code will go immediately above the second catch line in our start method:
1const emitter = app.resolvePlugin("event-emitter"); 2emitter.on(ApplicationEvents.TransactionApplied, transaction => { 3 if (this.addresses[transaction.recipientId]) { 4 for (const websocket of server.clients) { 5 // @ts-ignore 6 if (websocket.readyState === WebSocket.OPEN && websocket.address === transaction.recipientId) { 7 websocket.send(JSON.stringify({ transaction })); 8 } 9 }10 }11});
To finish, we just need one final import at the top of the code for the ApplicationEvents we used:
1import { ApplicationEvents } from "@arkecosystem/core-event-emitter";
At this point, we are done with our Core plugin for this part of the tutorial! Compile it with yarn setup, restart Core and head over to Construct 3 where we’ll interact with our WebSocket. Open the project we created in the last tutorial, switch to Layout 1, right-click and Insert New Object. Add a WebSocket. Repeat the process, adding a Browser object, a JSON object, and another Button which will be used to trigger the address generation process.
Flip to our Event Sheet 1 and add a new event. Drill down to System and then On start of layout. We want to add an action for the WebSocket to Connect. Enter the URL in the format wss://X.X.X.X:10000/ where X.X.X.X is your Core IP. Note this requires an HTTPS connection, so you may need to use a free service such as CloudFlare or use your own SSL certificate with a reverse proxy such as Nginx.
Add another event, this time listening for the Websocket -> On Text Message event. For the corresponding action, choose JSON -> Parse. Enter WebSocket.MessageText for the JSON string.
Now we want to add a few sub-events within the Websocket -> On Text Message event to process the WebSocket data we receive. Choose JSON and then Has Key. Enter “transaction” then add a new action. This will be fired when we receive a new transaction. For now, to prove it works, we’re just going to display the transaction data in an alert box in the browser. Choose Browser then Alert. Enter JSON.GetAsBeautifiedString(“transaction”).
Add another sub-event and choose JSON and then Has Key. Enter “networkVersion” this time. This will be used to set the network version so our app validates addresses properly depending on the network of the peer we’re connected to. Our corresponding action should be System -> Set Value. Make sure the Variable is networkVersion and set the value to JSON.Get(“networkVersion”).
Now we should check for a newly generated address, show it to the user and allow the user to send a transaction via the Desktop Wallet. In order to proceed, we must add another sub-event. It’s JSON -> Has Key, again. Enter “address” and add a new Browser action. Choose Alert and enter Click “OK” to send a transaction to JSON.Get(“address”). Add another action for this event, this time for Browser -> Go to URL. For the URL we enter “ark:” & JSON.Get(“address”) which will automatically open the Desktop Wallet and pre-fill a transaction to be sent to the newly generated address.
Finally, we’re going to wire up our new Button to trigger this address generation action. Add an event, choosing Button2 (as it’s our second button) then On clicked. We’re going to add an action to send a message over the WebSocket to trigger the address creation process, so choose WebSocket then Send Text. The text we’ll send is “**{”“action”“: ““new””}“.**
Now we’re done! Our event sheet should look similar to the following:
If you’re running the Core plugin on the ARK Development Network, you should find that it only accepts Devnet addresses now. By clicking the newly added button, you should receive a message giving you a newly generated address, which then opens the ARK Desktop Wallet to automatically send a transaction to that address:
Now if we send a transaction to that address, our Construct 3 app automatically receives it and shows us the data:
Congratulations on reaching the end of the tutorial! It might not seem like much, but we’ve achieved a lot! We’ve set up our bare-bones Core plugin, made a WebSocket server and now we know how to generate new addresses, send and receive transactions between our app and the network and dynamically validate addresses based on the network of the peer.
Next Steps
That’s it for Part 2 of this tutorial series. In our next session, we will make the game work with a betting system and create a lobby to start games and see existing ones!
If you become stuck at any point make sure to consult our documents on our Core Developer Docs. In addition, you can reach out via our contact form.