Creating a turn based game with NeoContract, ASP.NET Core, SignalR and AngularJS

Zdravko Georgiev
25 min readSep 21, 2018

--

Greetings, stranger!

We are venturing in uncharted territories today so grab a drink and get yourself comfortable. We are going to explore and see what the NEO platform can offer us as .NET developers and what are some potential ways to employ this new technology called the blockchain.

This tutorial assumes little to no experience with NEO or any blockchain technology as a whole, altho some basic knowledge such as knowing the answer to questions like “What is a block?” and “What is a transaction?”, would certainly be helpful. It requires you to have at least a little experience with ASP.NET Core. Experience with SignalR is helpful as we will spend little time explaining how that actually works. We are going to create a front end client using AngularJS, but you can use any client-side javascript framework of your choice (or any other type of client — mobile, desktop..). We are going to see how to use the neon-js library in order to send transactions to the Neo network from our html apps. As expected most of our attention is going to be on smart contract development, smart contract execution and smart contract interfacing.

Here’s our game plan:

  • Setup a development environment for Smart Contract development for the Neo platform using C#
  • Setup our own private Neo network using a simple docker container
  • Connect to our private net using the neo-gui software
  • Use the neo-gui to deploy and invoke a Smart Contract
  • Integrate a Neo node inside an ASP.NET application
  • Interface with the Neo blockchain thorough our local node
  • Leverage websockets to create a simple web based game that uses the Neo blockchain for it’s backend logic and data storage

The final source code can be found here. It might be a good idea to clone the repo and have the code available locally while going through the tutorial.

Disclaimer: This article was written as a submission to the CityOfZion dapps competition #3. It is still incomplete, but I am publishing it due to the deadline closing tonight. Expect improvements and additions in the coming days and weeks. If you find incorrectness, have suggestions or any questions, please do not hesitate to post a comment below or contact me on the Neo discord server directly. My alias there is @insomnia#5785 . I don’t expect too many people actually reaching out, so you can count on your questions being answered.

Section 1. Development Environment

This tutorial assumes you have no prior experience developing smart contracts for NEO so we are going to start with the very basics. Every journey into a new technology should start with a proper IDE setup.

Visual Studio 2017

For us as .NET developers, the following first step is most likely not necessary, but if you don’t already have it installed, go ahead download and install Visual Studio 2017. Make sure you have the “.NET Core cross-platform development” toolset installed. If you already have VS2017, but don’t have the .NET Core cross-platform toolkit, you can add just that.

NeoContract plugin

The first Neo specific thing that you need to get is the NeoContract plugin. This basically adds a Neo project template to your ide. You can install it by opening Visual Studio, going to the Tools menu -> Extensions and Updates -> Online section and search for “neocontract”.

Now that you have it installed you can create new Neo projects. We are going to see how to do that in a second, but first we also need to get the .NET neo compiler.

Neo compiler

One of the great things about Neo is that it has compilers for many different languages. Today however, we are particularly interested in it’s .NET compiler since we are going to write our smart contract in C#.

To get the .NET compiler, go to the official repository on github and clone it locally. Open the solution with Visual Studio and Publish the neon project. You can do that by right clicking on the project name and choosing “Publish…” from the menu.

After the project is published we need to add the output directory to our execution path. Specifically we need to be able to execute the neon.exe that was just compiled. Neon.exe is the tool that takes our .NET .dll files and creates an .avm file that the Neo Virtual Machine can execute. After publishing you can find neon.exe inside of the “bin\Release\PublishOutput” folder. This is the folder that you need to add to your PATH variable.

Creating and Building a Neo Project

Now we can happily create our first NEO project. You will find the NeoContract project template under Visual C# like so:

The default NeoContract template adds several files to our project — Contract1.cs — this is our smart contract file. Neo.ConvertTask.dll — this library knows how to use the neo compiler. build .tasks — this file ensures that Neo.ConvertTask.dll will run after we build the project.

Now we can build the project like we do normally. If everything goes well, we will receive a success message in the Output window:

Now that we have our smart contract compiled in the form of an .avm file, we can deploy it on the network! Or … maybe not. Publishing a smart contract on the main network costs 500 GAS — not a negligible price at all! Let’s set up our own private network where we can have all the gas in the world and do our tests there instead.

Section 2: NEO Private Net

It is a good idea to set up our own network and use that for development and debugging. It’s much better to use our own private network instead of the public TestNet, because we don’t have to rely on testnet GAS each time when we want to deploy a contract. By the way, if you need gas for the TestNet, you can go to the Neo discord, there is a channel for testnet gas requests. You can ask there, but remember: testnet is intended only for the final tests of your smart contract, before you deploy it to the main net. For development and testing we use private net. Fortunately, the good people from CityOfZion have made it extremely easy for us to spin up our own private net. Have you used docker before? No? Well, I haven’t used it before either, but let me tell you — it is super easy. If you don’t have it already go ahead and install it. There is a release for all major operating systems. The installation of docker is straightforward for the most part, unless your windows is “Home edition”, or doesn’t support Hyper-V for some other reason. In this case you would have to take an additional step like I had to do. You need to install an alternative of Hyper-V like VirtualBox, or something else. Look for it on the internet. Installing docker is out of the scope of this article, but there is plenty of information out there on how to do that.

Once you have docker installed, simply head over to this repo and clone it. Or better yet simply type

git clone git@github.com:slipo/neo-scan-docker.git

in your favorite command line tool. Now you have everything that you need to start your private net. Just navigate to the root of the repo with your command line and type

docker-compose up

Execution of this command is going to take some time, so you can use that to go and edit your hosts file (if you don’t know how, search for “how to edit hosts file”). Go ahead and add this line:

127.0.0.1 neo-privnet

If you don’t know what the hosts file is, it acts sort of like a local DNS — it is used to map a connection between an IP addresses and domain names.

When the docker-compose command finishes, you would have your private network up and running. But oh wait, it gets even better! The above command would not only download and start the private net container, but a few other things as well. It starts a container running a local version of NeoScan the most popular neo blockchain explorer. Your own NeoScan comes configured out of the box to read data from your private net and can be reached at http://localhost:4000.

And what about our private network, what is it exactly. Well it consists of 4 virtual nodes, each of them running a neo-cli software, configured to communicate and form consensus with each other. It is a rather deserted place this network, but it’s a must if you want to be able to do any kind of smart contract development and testing.

One of the ways we can test our nodes…

<give instructions on JSON Rpc>

The official tool provided by the Neo team for interaction with the Neo network, is the neo-gui. A windows forms application that can be used as a wallet to send and receive currency, but has a lot more functionality built into it. Most importantly for us — it can deploy and invoke smart contracts. You can download the latest release from its github repo, or simply clone and build the project yourself. It’s handy having the whole source code of neo-gui, because it allows you to launch the application in debug mode from visual studio and go through the execution step by step to see what’s going on if you need to.

By default when you download or clone the neo-gui project it is setup to connect to the main network. We want to be able to use it with our private network so here is what we need to do. Each neo node (neo-gui is a full node, remember) needs to be configured with two json files in order to know how to communicate with the rest of the network. Remember those, as we will use them later, when we run our own node, integrated in our ASP.NET Core server. The two json files are:

Config.json

config.json

Paths: Which folders to you to save the blockchain and notifications from smart contract executions

Paths.Chain: Blockchain data

Paths.Notifications: Notifications data

P2P: In Neo there are two ways to participate in the network: Through bare a TCP port or through a WebSocket

P2P.Port: This is the port to use for your bare TCP connections

P2P.WsPort: This is the port that the node will use to connect through WebSockets

RPC: Settings for your node’s RPC endpoint

RPC.Port: The port on which to accept json RPC requests

RPC.SslCert: If you want to use https with your RPC endpoint

RPC.SslCertPassword: Password for the above certificate

DataDirectoryPath: We store the blockchain data here

protocol.json

protocol.json

StandbyValidators — An array containing a list of the public keys of all validators. That is how when you receive a block you can tell if it was legit. It was legit, if it was signed by the private key corresponding to one of those public keys

SeedList — An array of the addresses of the seed nodes, the initial nodes to which you connect. To connect to your private net you need to have the four addresses that you see in the image above

SystemFee — This section is used to set the costs of certain types of transactions within the network. This only applies to consensus nodes. Not interesting for us at this moment

Magic — Unique identifier of the network
AddressVersion — Not sure about that one

Copy and paste those two config files in the folder of your neo-gui (you may save the old files, or rename them to .mainnet or something if you want to use them later). This will instruct your neo-gui to connect to your private network. Now that we have this done, let’s deploy our “hello world” smart contract.

Start your neo-gui and make sure it is connected to your private network. Open your wallet. From the menus select Advanced -> Deploy Contract. In the popup dialog window, click on the Load button and browse to your smart contract .avm file. You need to fill all of the metadata fields, altho there is no validation on them whatsoever, so for debugging purposes you can enter whatever you want in those fields. If your smart contract will use Storage (the hello world contract uses storage even tho the whole contract is only one line long). Click on Deploy. In the new dialog window click Test and then Invoke. Now your contract is deployed on the network.

<Give instructions on invoking smart contract>

Section 3: Local Node inside an ASP.NET application

In this section of the series, we are going to see how we can integrate a node and participate actively in the Neo network, from our existing ASP.NET applications. There is surprisingly little setup that we need to do.

First we need to copy the json config files that we used before with our neo-gui in our project root. Then we need to make sure that those files are always copied to our output directory on project build.

Second, we need to initialize our local node! We have created a NeoConfig service, that will manage our local node. We register the service as a singleton in our ConfigureServices() method at startup and then we inject and init it in the Configure() method.

We have added two lines at the bottom. We create a NeoConfig and we initialize it. Let’s peek at what’s inside NeoConfig.

The init method is quite slim. First we register our blockchain, by providing it the directory path where we want the blockchain data to be stored. Finally we call localNode.Start() using the settings from our config.json file that we saw before.

Let’s look at those NeoSettings again and how do we get them:

NeoSettings.cs

In its static constructor we simply build a configuration using our config.json file that we took a look at earlier, then we initialize the Default settings by reading its property values from the json file. Now we can use the NeoSettings.Default static property from anywhere within the application. You can just copy and paste that in your .NET apps and you would have done most of the work needed to run a node.

Important

One last thing that we need to do before we can have a local node inside our ASP.NET server is, we need to get leveldb. LevelDB is a fast key-value storage library written at Google that provides an ordered mapping from string keys to string values.

On linux:

yum install leveldb-devel
On windows:

Clone and build this repo, then copy libleveldb.dll to the working directory of your ASP.NET app. Note — when building leveldb, the project file settings must be changed from static library (lib) to dynamic linked library (dll).

And that’s it. Our Local Node is now ready to take part in the network. We can have real time access to blockchain data on our server. How cool is that? We can use locally all of the API’s that the NEO blockchain provides us and the one that is most interesting to us today is the Notifications API. This provides us the ability to subscribe to any events raised by an execution of a smart contract, meaning that we can have a callback execute on our server, each time a certain event is raised within a given smart contract. Using this API and SignalR, we can leverage websockets to create a real time communication between a smart contract execution and clients listening on our server. Of course we still need to wait for the block to come in, in order for the transaction to be executed on our node, but that’s as real time as it gets with about any blockchain at this point :)

Section 4: Interfacing with the blockchain

Let’s have a brief first look tho, on what APIs do we get from Neo. What data can we ask for straight away, now that we have a local node? We can ask for what the current height of the blockchain is for example, like so: Neo.Blockchain.GetHeight(). Or perhaps you might want to get a specific block data like so: Neo.Blockchain.GetBlock(), for that you would need to supply a block Height or a block Hash as a parameter.

The Neo Api consists of tens of methods so we are not going to go through them all here, but you can take a look at the full list here. And the best thing about that list is that it’s constantly growing, with the new releases new methods are being added. The project is evolving.

< more examples >

Section 5: Bulls and Cows

This is where the fun part starts. We are finally ready to get digging into the real juicy stuff. Now that we have our own local instance of the blockchain on our server and we know how to start interfacing with it, we can leverage that real time feedback from the blockchain in order to make a turn based game, the game would have it’s logic on the blockchain. Neo’s block-time is between 15 and 20 seconds. That time is not exactly perfect for a seamless gameplay of a fast paced game like the one that we are going to create today, but this example illustrates the technical abilities and limitations very well.

We are going to use the SignalR technology by microsoft, which provides us a useful abstraction around websockets to use within an ASP.NET app. We are going to create a simple js client app that is going to talk to our server using the said websockets. Our web client will also know how to sign and send transactions to the blockchain. For that we are going to use the neon-js open source library. Let’s look at our

Communication channels

Client to Server and Server to Client: Client and server can stay in sync and communicate in real-time thanks to websockets and SignalR

Client to Smart Contract: Clients execute the smart contract by sending invocation transactions to the Neo network

Smart Contract to Server: Smart contract talk to the nodes that are executing them via runtime notifications

Now let’s have a look at what the dataflow of our Join Game operation looks like

  1. Client app asks the game server to join the waiting for game queue. If the queue is empty the client would have to wait for someone else to join, if there is already someone in the queue, a game starts immediately
  2. After the game server finds a match, it generates a Game Id and returns it to both clients
  3. Both clients sign and send transactions to the blockchain, signifying that they want to join a game with this Game Id
  4. The Smart Contract is invoked by both transactions and communicates through notifications to the game server the results of those executions
  5. The game server uses SignalR to communicate in real time with both clients about any changes of the game sate recorded on the blockchain

Using this approach, our server can push smart contract execution data to its subscribers in real time. In addition to that, Neo has a perfect finality, meaning that each block is final and doesn’t need to be confirmed, unlike blockchain platforms that use Proof of Work consensus algorithms like Bitcoin or Ethereum. Therefore once you get the block you know it’s final and can move on with the business logic, for example give turn to the next player.

Game Rules

Let me lay out very quickly the rules of the game that we are going to implement today. It is called “Bulls and Cows” and it is something that I used to play with my classmates back in primary school. It goes like this: When the game begins both players have to pick their secret number — a number consisting of four digits. After that the guessing starts and both players try to guess their opponent’s number. Each guess is scored in “bulls” and “cows”. One “bull” point is when the guess number has one overlapping digit as the secret number, meaning both the secret number and the guess number have the same digit at the same position. One “cow” point is when they have the same digit, but on different positions. So after each guess, the player receives a certain number of Bulls and Cows score. The game goes on until one player guesses the secret number of his opponent, or in other words — until one player makes a “four bulls” guess.

Section 6: Creating the Smart Contract

Our Smart Contract is the heart of our game logic. It is this untouchable piece of mathematical rules, that once defined and published on the blockchain, could never be changed or tampered with. This is where we need to be the most careful to not make any mistakes, because bugs in smart contracts can be costly.

We are going to split our smart contract in multiple files, not because our game logic is too large for one file, it isn’t. We are going to do it to explore what we might have to do in case we are in a real world scenario, where our business logic can be a hundred times larger.

Important

Please take note that in order for your smart contract to work if you split it into multiple files, all files that contain logic need to inherit from Neo.SmartContract.Framework.SmartContract.

Entry point

The entry point of our smart contract is inside GameContract.cs, which looks like this:

GameContract.cs

Just like in a console app, the Main() method is where the execution starts. The Main() method can have as many arguments as we want, but a well established convention within the community is to always have 2 parameters, in all our smart contracts. First parameter is a string usually called operation and the second parameter params object[] usually called args. This way we can have flexible number of parameters without needing to have methods with different signatures. Then in the Main() method we determine which method of our GameLogic class to execute. We use if else statement to do that. We can also use a switch statement, but watch out! Switch statements with more than 6 cases don’t compile properly by the neo compiler. This is caused by some optimizations used by the c# compiler when compiling switch statements, but that is not so important now. Just remember to avoid using switch statements with more than 6 cases in a Neo smart contract.

If you take a closer look at our Main() method above and you know how ASP.NET works in detail, this what we got here sort of does the work of our Routing and a ModelBinding modules in ASP.NET. This of course is a very very primitive version, but I think the concept is similar. We can of course just put all the logic in the Main() method and it should work. It’s just not easily maintainable that way and as we said, bugs in smart contracts are costly so we better come up with the highest quality code practices possible.

Game Logic

Ok let’s move on. Let’s take a look at the GameLogic.JoinGame() method. This is going to handle all of our invocations with operation “join”. We accept two parameters a gameId — this was generated by the Game Server and address, this is the address requesting to join the game. If all validations pass, information that a player from this address, joined a game with this Id, would be recorded inside of the smart contract’s storage. Let’s take a look:

GameLogic.cs

Woah, quite a lot is going on here so we are going to move slowly. Looking at the code above, the first thing you might be asking yourself is: “What does Runtime.CheckWitness() do?”. Well that’s a handy little API, provided by the Neo Runtime, which produces a true/false result and takes as a parameter an address in the form of a byte array. Basically it checks if the provided address is the address that the current invocation transaction is coming from. It is a very common authentication technique to assure that the caller is “who he says he is”, so make sure to remember that one.

So what do we do if CheckWitness() returns false? Well, we should stop the execution of our smart contract so we return false, but what is this Runtime.Notify() call before the return statement. That in fact is our outgoing channel for communicating with the node that actually executes the contract. Wait a second, “the node that actually executes the contract” what does that really mean? Well, when an invocation transaction is sent out on the network and gets included within a block, that block is propagated to all nodes on the network, including the node in our game server, this means that the smart contract would be executed on all nodes of the network, including our own. In the next section, where we build out our Game Server, we will see how to subscribe for those notifications, through an event handler. We can specify a callback to be executed, each time a smart contract is invoked on the blockchain and we have access to a collection of all notifications. Every call to Runtime.Notify() creates a notification that gets added to that collection. For now just take note that the results of those calls to to Runtime.Notify() create a notification that gets propagated to all nodes. In the next section we will see how that looks from the perspective of a node.

One other thing worth noting and something that you might have noticed yourself already, about our Runtime.Notify() calls is the pattern that we use. We always have a constant string as the first parameter. The Runtime.Notify() method takes a params object[] state as a parameter, so there can be as many parameters as we want and from any type that we want. We use the first parameter to be able to identify what kind of data are we sending, or in other words, what event are we raising. We are using strings defined as constants in another assembly that we are referencing into our Smart Contract project. We are separating those constants from the actual smart contract, so that we can refer to that separate project from our server as well. We are going to take advantage of that when we get to the next section where we will subscribe for those notifications.

That params object[] array that is passed to the Runtime.Notify() calls, gets serialized to a byte array and can be accessed as a property of the notification. In the next section we will see how to deserialize and access that data. Some serious magic is going on there, but let’s finish our outlook on the smart contract first.

Game Storage

Moving on with our GameLogic.JoinGame() code we see that if the call to Runtime.CheckWitness() returns true, we try to see if existing game with this Id already exists in the storage. Our GameStorage class handles our data access operations. Basically a repository type of class. It’s GetGame() method returns an object of type Game, a very simple data transfer object looking like so:

Game.cs

The [Serializable] attribute is important, because it allows for this class to be serialized as byte array, by the Neo runtime. FirstPlayer and SecondPlayer properties hold the players’ addresses in byte array format. The game Id is stored as a byte array as well. The players’ secret numbers are stored as a string and the GameState enum looks like this

Those are our data models — not too complicated. The GameStorage class has two very simple operations SaveGame() and GetGame(). Take a look:

GameStorage.cs

Game objects are being serialized as byte arrays and saved into storage mapped by unique keys. The smart contract storage in Neo is a simple key value store. All of the storage operations, take a StorageContext type of object as the first parameter. This is because we have the ability to access the Storages of other smart contracts if we have access, so we could provide their context instead. In case you are building a complex application, you might want to have the logic split between several contracts that talk to each other. But that’s too advanced for now. Let’s take a look at the KeysFactoy and wrap it with the smart contract development. The game server is waiting for us and it is just as fun.

The KeysFactory is basically the place where we get our unique keys, by which we access the records in our storage.

KeysFactory.cs

A single method ? Well it does look like an overkill at this point, but just imagine if you have to save tens of types of objects inside the storage. Also some keys can more more complex than others, for example in the method above, we only use the unique gameId and a string constant (“GameId”) in this case, but in other instances we might have keys that are composed by two or three unique Ids. And we all know that good separation is always nice, so our keys are coming from the KeysFactory :)

But let’s get back to where we were in our GameLogic.JoinGame() method.

GameLogic.cs

We can only join a game if it’s state is WaitingForPlayers, so it if isn’t we send a notification about the failure and return false. If the game is in correct state and the first player’s address is empty, we set the FirstPlayer property of our game object to the address parameter, we save the game with it’s updated info and we send a notification, before returning true to indicate a successful invocation. If the game already has a first player, we check to see if there is room for one more, if yes — again we save the current address to the SecondPlayer property of the game, but we also set the state to SelectingNumbers, because the game is now full. Now the game server will pick up on the notifications and use SignalR to communicate to the two players, that the game state was changed on the blockchain and it’s time to select their secret numbers.

We are not going to look at the other two operations — GameLogic.SelectNumber() and GameLogic.GuessNumber(), where we select our secret number and try to guess our opponents’ number respectively. You can take a look at the final source code to see those, there’s nothing smart contract specific that was used there and that was not covered in this section.

Section 7: Listening for Smart Contract Notifications

In this section we are going to configure our ASP.NET app that already is hosting a Neo full node to listen for smart contract invocations and more specifically for invocations of our game contract.

Again there is surprisingly little that we have to do in order to get a callback to execute with each contract invocation. We have to subscribe to the following event

LevelDBBlockchain.ApplicationExecuted

This event will be raised for each smart contract invocation. It’s event handler must have the following signature (object sender, ApplicationExecutedEventArgs e) where ApplicationExecutedEventArgs is the object of interest. It’s property ExecutionResults holds a collection of ApplicationExecutionResult items that represent information about each smart contract execution that took place. We can filter that collection to only contain executions that have at least one notification whos ScriptHash property matches our game contract’s ScriptHash. Now that we have filtered out and only have a collection of the executions featuring notifications coming from our game contract, we can take a closer look at what each execution is telling us.

Let’s see what our NotificationsHandler service looks like:

NotificationsHandler.cs

We register the NotificationsHandler class as a Singleton service in our ConfigureServices() method and we initialize it in the Configure() method.

Basically what we do is, in the Init() method we attach a callback method to be executed each time the ApplicationExecuted event fires. This is the event, as we said, that fires for each Smart Contract execution. We pass the filtered NotifyEventArgs to the ProcessNotifications() method which looks like so:

NotificationsHandler.cs

Now we have some serious magic going on in here so we are going to move slower. Let’s review the code. On the first line of each iteration, we first get the notification type as a string variable. This is the first argument that we passed to the Runtime.Notify() method, remember? GetNotificationType() is an extension method we created for the NotifyEventArgs class. It goes like this:

NotificationsExtensions.cs

The NotifyEventArgs object has a property State which holds the values that we passed to the Runtime.Notify() call. Those values are not in a very easy to use format and therefore the sorcery.

First we get the value as bytes with the GetByteArray() call, then we need to turn those bytes into a hex string and after that we transform it into a regular string. And voila, we have our notification type.

Moving on with our Process() method, now that we have determined what is the notification type, we can take the appropriate action. When we have decided in which case of the ef else statement to go depending on the notification type, the next thing that we do is we get the reset of the values that were passed to the Runtime.Notify() call and we get those values in a nicely mapped and easy to use object, using the generic GetNotification<T>() extension method:

NotificationsExtensions.cs

We skip the first item in the state, because that was our notification type, we get the rest of the items and transform them into an instance of one of our notification types using the CreateObject<T>() extension method. Even more magic:

CollectionGenericExtensions.cs

If you know reflection you should be able to read the code above relatively easily. If you’ve never heard or used reflection it’s going to be hard. We basically use the template type that was used when invoking the method to create a default instance of it and get it’s properties using reflection. Then we loop through the stack items that are passed as arguments and we get the type of each property that was defined on the type on that position (again with reflection) and then we try to convert the stack item byte array value, into the type of the property and finally we set that property value to be equal to the parsed one. Phew, that was a mouthful. For this to work of course, the order in which properties are defined on the notification class, has to be the same as the order in which the values are passed to the Runtime.Notify() call. If we want our notification classes to have properties that are not being mapped we need to decorate them with the [NotMapped] attribute.

Section 8: Creating the SignalR Hub

To add SignalR to our ASP.NET Core application we add reference to the Microsoft.AspNetCore.SignalR package. Then we need to add the following lines in our Startup.cs file: In the ConfigureServices() method we add the following line:

And in the Configure() method we configure the routing for our hubs like so:

< to be continued >

Section 9: Creating the Angular client

<coming soon>

Disclaimer: This article was written as a submission to the CityOfZion dapps competition #3. It is still incomplete, but I am publishing it due to the deadline closing tonight. Expect improvements and additions in the coming days and weeks. If you find incorrectness, have suggestions or any questions, please do not hesitate to post a comment below or contact me on the Neo discord server directly. My alias there is @insomnia#5785 . I don’t expect too many people actually reaching out, so you can count on your questions being answered.

P.S. Special thanks go to @yasenm#7001 and Nik4321 for their participation in making of the sample code for this article.

--

--