I am a software developer by trade, and after two years of exploring cryptogames, I finally decided I would try to develop my own game.
This post is not intended to describe the game I am developing (I'll do that in later posts, though it's loosely based on the concept I wrote about previously. But in this post, I wanted to talk about trying to transition from traditional development to blockchain development.
I built a proof-of-concept of my game as a Windows console app using C# and .Net 6 so I could work out all of the mechanics of the game and test-run it myself. I was able to develop the concept for the game and play it myself, and so far I have enjoyed playing it. It's not an action-packed game, but a game where you stake assets that produce resources that can then be combined together and sent on quests, which have a chance of success. If you succeed, you get rewards.
I really thought it would be an easy transition, but it has proven to be more challenging than I first expected. Nothing insurmountable, yet, but as soon as I got started, I ran into issues.
Of course, the first thing that needs to be done is to log in a user using their wallet account. For my game, I am using third-party wallet providers to provide the login so I don't have to store users' private keys. I assumed that there would be ready-to-use plugins for each of the major wallet providers (Anchor, Cloud Wallet, Wombat, etc.). Actually, I had assumed that there would be a single plugin that would work across multiple wallet providers. Unfortunately, I was not able to find anything I could use.
I did stumble across @wharfkit, which looked like exactly what I needed, but I was not able to get it integrated in my project. There is some sample code available on the side, but no explanation of how to integrate that code into my app. Wharfkit is available via Node.js and npm, and I have only very limited experience with Node.js, so I'm probably missing something that is obvious to most developers. But the sample code uses ES6 import statements to import the Node.js modules, which suggested to me that this code was intended for the browser. But of course, by the time we get to the browser, our client is not aware of what Node.js modules are installed, and therefore cannot import them. I tried to copy the modules from the node_modules folder into a client-side folder, but the Node.js modules for @wharfkit require other modules that are not available on the client. I tried to run this code on the backend by changing the import statements into require() statements, but those caused my app to crash because the modules being referenced are looking for the browser's localStorage object, which does not exist on the back-end. I'm sure I'm missing something obvious, but I have yet to find any way to make this work.
I tried using webpack to bundle the @wharfkit Node.js modules and their dependencies into a single JavaScript file that I could access via the client. When added this output to my login page using a <script> tag, I was unable to access any of the @wharfkit functions. When I tried to import this module into a <script type="module"> tag on my login page, I was unable to access any of the @wharfkit exports. Again, I'm sure that I am missing something important, because I'm not familiar with webpack. But since I don't know what it is that I don't know, I'm having a lot of trouble finding information to help me get up to speed quickly.
Eventually, I was able to create a login page for my dApp using Anchor-Link. This repository included links to some pre-bundled scripts that can be used in the browser, so that's what I did. I was able to make this work using an account on the Wax Testnet and the Anchor wallet app. But, once I got this to actually work, I ran into another problem. I really wanted to have my application check on the server side to see if the user was logged in or not and deliver a different page depending on whether the user was logged in. But since I could only run the Anchor-Link plugin on the client page running in the browser, I had to wait until the page was rendered before I could check to see if the user had an active login session.
To solve this (and this is probably not a good solution, but it was the only thing I found that worked), I created two separate web pages (one for logged in users, and one for not-logged in users). I am using express and ejs to render web pages in my application, so in my app.get('/') route, I use an if/then statement to check the session variables for an accountName value. If it was found, the logged-in-user page is rendered, and if not, the not-logged-in-user page is rendered. This meant that the same URL would serve two different pages. Again, this is probably not a good solution, but it is working for now.
Let's start with the not-logged-in-user page. On this page, I included references to the Anchor-Link bundled scripts, and then called them in my code like this:
$(document).ready(function() {
$('#LoginAnchor').on('click', async function(e) {
e.preventDefault();
const transport = new AnchorLinkBrowserTransport()
const link = new AnchorLink({
transport,
chains: [
{
chainId: '<%- blockchain_id %>',
nodeUrl: '<%- blockchain_url %>',
},
],
verifyProofs: true
});
// Perform the login, which returns the users identity
const identity = await link.login('MyGame');
// Write the player's identity to the server session
$.ajax({
url: '/setaccount/' + identity.account.account_name,
success: function(data) {
window.location.reload();
},
error: function(request, error) {
alert(error);
}
});
});
});
When the user clicks on a button with the ID "LoginAnchor," the Anchor-Link plugin can be used to generate and display an Anchor login prompt on the page. Note that I am passing the blockchain_id and blockchain_url as variables from the server because I am using the dotenv Node.js module to allow me to use private ENV variables that can be different depending on where my website is running and aren't ever displayed in my code.
After the user is logged in to the site, I am using JQuery AJAX to call an endpoint in my application that will set the player's account name in the accountName session variable and then refresh the page. When the page is refreshed, the server again checked to see if there was a session variable called accountName. This time, the variable exists, and the logged-in-user page is rendered. On this page, I am again referencing the Anchor-Link bundled scripts. But this time, I am trying to restore an existing session from the user's Anchor wallet.
$(document).ready(function() {
const transport = new AnchorLinkBrowserTransport()
const link = new AnchorLink({
transport,
chains: [
{
chainId: '<%- blockchain_id %>',
nodeUrl: '<%- blockchain_url %>',
},
],
});
link.restoreSession('MyGame').then((session) => {
if (session) {
$('#playerName').text(session.auth);
}
else {
window.location.href('/logout');
}
});
});
This time, instead of calling the login Anchor-Link function, I am calling the restoreSession function and using the information I received to fill out the #playerName element on my page. If no session is found, I simply redirect the page to /logout, which will destroy the session and redirect the user to the / (home) page. This page, in turn, will re-check the session's accountName (which no longer exists) and render the logged-out-user page.
This is a bit convoluted, I realize, but it does seem to work. When I started working on developing my own game using the blockchain, I never really thought about the fact that the login had to happen on the client side (because it requires user interaction to authorize the wallet verification), but that I would also want to know about the session on the server side (to determine whether to render a game page or a login page).
I suppose I could completely ignore the wallet's session on the server side and just include a script on every page that tries to reload the player's session from their wallet provider and, if unsuccessful, redirects them to a login page. But I worry that doing so might be too much load on the server, since I basically have to render the whole game page and then, if the session does not exist, go back and render a different page.
In any case, this is working well enough for me to proceed. For now, I am just going to use Anchor-Link to verify player's sessions. I am considering implementing at least the Cloud Wallet login as well using the WaxJS Node.js script, which also provides a way to get a user's account information using the Cloud Wallet's login functionality. But I didn't immediately see a way to use this to restore a session on subsequent page loads, so I haven't added this functionality yet.
I know I'm off to a bad start, and I have a lot more to learn than I thought I did. I will continue to investigate and try to find better ways to integrate cloud wallets with my application. I thought this would be the easiest part of developing a cryptogame, and indeed it might turn out to be. But I didn't expect it to be this hard to grasp.
Wish me luck as I continue to hammer away at this!