/web3_programming_with_gno

This is a short tutorial on how to get started with Web3 programming using Gnolang and Gnoland. You only need some basic Go programming language knowledge. Gnolang is a version of Golang but it is built for Web3 programming. This means that you can directly transfer your Go skills to Web3 development.

Primary LanguageGoCreative Commons Zero v1.0 UniversalCC0-1.0

How to get started with Web3 Programming using Gno

This is a short tutorial on how to get started with Web3 programming using Gnolang and Gnoland.

You only need some basic Go programming language knowledge.

Gnolang an interpreted variant of the Golang language that is built for Web3 programming.

This means that you can directly transfer your Go skills to Web3 development.

Install the latest version of Go if you haven't already.

On Linux:

go version
>> go version go1.21.1 linux/amd64

And on macOS:

go version
>> go version go1.21.1 darwin/arm64

Source Code Editing Tools

For editing the code of this project I will be using Visual Studio Code (VSCode).

The following two VSCode extensions are highly recommended:

However, please feel free to use any other editing environment that you prefer.

Introductory Gno

Overview and Concepts

What is Gnoland?

Gnoland is a new smart contract platform based on Gnolang, a fork of Golang. Gnolang inherits the concurrency and developer-friendliness of Golang while fully leveraging the rich libraries and packages that are already available. This approach greatly accelerates application development by allowing dapp developers to reuse and re-assemble existing code, similar to how Cosmos simplified blockchain deployment.

Gnoland introduces multiple concepts that together form a scalable, transparent, and sustainable blockchain ecosystem that suggest a viable solution to challenges that existing blockchains are faced with.

Packages

Packages encompass functionalities that are more closely aligned with the characteristics and capabilities of realms, as opposed to standard libraries.

  • A unit that contains functionalities and utilities that can be used in realms.
  • Gnolang Packages are stateless.
  • The default import path is gno.land/p/~~~.
  • Can be imported to other realms or packages.

Realms

A Gnolang Realm is where the state of an application lives.

  • Realms are Smart Contracts in Gnolang.
  • Realms are stateful.
  • The default import path is gno.land/r/~~~.
  • Each realm has the capability to publicly export the function Render(path string) string, which performs rendering when passed a valid markdown as a parameter for the specified path.

The Example Go Project that we will port to Gnolang

The example project for this tutorial is a simple number guessing game.

In the Go programming language, the essential logic for a command line guessing game program looks as follows:

// Set up the module:
// go mod init github.com/pietergreyling/web3_programming_with_gno/m/v2

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

// Main holds the global state in this program - - - -
func main() {

	min, max := 1, 100
	tries := 0
	correct := false

	secret := rand.Intn(max-min) + min

	fmt.Println("-- Please guess a number between 1 and 100.")

	reader := bufio.NewReader(os.Stdin)

	for {
		tries++

		input, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("-- Error when reading input!", err)
			continue
		}

		input = strings.TrimSuffix(input, "\n")

		guess, err := strconv.Atoi(input)
		if err != nil {
			fmt.Println("-- Invalid input - that is not a number!")
			fmt.Println("-- Please enter a number.")
			continue
		}

		correct = TestGuess(guess, secret, min, max)
		if correct {
			break
		}
	}

	fmt.Println("-- You tried", tries, "times!")
}

// No global state is held here - - - -
func TestGuess(guess int, secret int, min int, max int) bool {

	correct := false

	fmt.Println("-- Your guess is: ", guess)

	if guess > secret {
		correct = false
		fmt.Println("-- That is higher than the secret number.")
		fmt.Println("-- Please try again...")
	} else if guess < secret {
		correct = false
		fmt.Println("-- That is lower than the secret number.")
		fmt.Println("-- Please try again...")
	} else {
		correct = true
		fmt.Println("-- That is the correct number, congratulations!")
	}

	return correct
}

Please notice that we have made sure to separate the stateful main function from the stateless number guessing code. This is in order to be in line with the Gnolang concepts of Realms and Packages:

  • The stateful main function will become a Realm
  • The stateless number guessing code will become a Package

As preparation for porting this to a Gnolang realm and package architecture we run a quick test by executing the following:

cd src_go
go run .
-- Please guess a number between 1 and 100.
50
-- Your guess is:  50
-- That is lower than the secret number.
-- Please try again...
75
-- Your guess is:  75
-- That is higher than the secret number.
-- Please try again...
62
-- Your guess is:  62
-- That is lower than the secret number.
-- Please try again...
68
-- Your guess is:  68
-- That is higher than the secret number.
-- Please try again...
65
-- Your guess is:  65
-- That is lower than the secret number.
-- Please try again...
67
-- Your guess is:  67
-- That is the correct number, congratulations!
-- You tried 6 times!

We have now verified that the program is working as expected.

Setting up the Gno Development Environment

Prerequisites

The dependencies below are required to develop applications for the Gno platform.

  • Install gofumpt.

    go install mvdan.cc/gofumpt@latest
  • Install Gno.

    I will use this method:

    cd [YOUR_PROJECT_ROOT_DIRECTORY]
    git clone https://github.com/gnolang/gno.git
    cd ./gno
    make install_gno

    But one can also install gno like this:

    go install github.com/gnolang/gno/gnovm/cmd/gno
  • Install Gnokey.

    cd ./gno
    make install_gnokey
  • Install Gnoland.

    cd ./gno/gno.land
    make install.gnoland
  • Install Gnoweb (install and run a local gnoland instance first).

    cd ./gno/gno.land
    make install.gnoweb

For verification, our installation output should look similar to the following:

go install mvdan.cc/gofumpt@latest
>> go: downloading mvdan.cc/gofumpt v0.5.0
>> go: downloading golang.org/x/sync v0.1.0
>> go: downloading golang.org/x/mod v0.10.0
>> go: downloading golang.org/x/tools v0.8.0

make install_gno
>> make --no-print-directory -C ./gnovm    install
>> go install ./cmd/gno
>> [+] 'gno' is installed. more info in ./gnovm/.

make install_gnokey
>> make --no-print-directory -C ./gno.land install.gnokey
>> go install ./cmd/gnokey
>> [+] 'gnokey' is installed. more info in ./gno.land/.

make install.gnoland
>> go install ./cmd/gnoland

make install.gnoweb
>> go install ./cmd/gnoweb
>> go: downloading github.com/gorilla/mux v1.8.0
>> go: downloading github.com/gotuna/gotuna v0.6.0
>> go: downloading github.com/gorilla/sessions v1.2.1
>> go: downloading github.com/gorilla/securecookie v1.1.1

We can now ask for help from the Gno tools:

gno help 

gnokey help

gnoland help

Testing the Local Development Platform Setup

Starting Gnoland:

cd ./gno/gno.land
gnoland start
>> .level 1 .msg Starting multiAppConn [impl multiAppConn [module proxy]]
>> .level 1 .msg Starting localClient [[impl localClient [module abci-client connection query]] [module proxy]]
.....

Starting Gnoweb:

gnoweb
>> Running on http://127.0.0.1:8888

Navigating to Gnoweb at http://127.0.0.1:8888 with a browser should present the following:


Building the GnoGuess Application

Where to place our Code

Currently, in order to create our Gnolang package and realm .gno code files, we have to be aware of the following folders:

# the root
gno/examples/gno.land

# the folder for packages
gno/examples/gno.land/p/demo
 
# the folder for realms
gno/examples/gno.land/r/demo

# the folder tree
├── gno
│   ├── examples
│   │   └── gno.land
│   │       ├── p
│   │       │   └── demo
│   │       │       ├── ...
│   │       │    
│   │       └── r
│   │           ├── demo
│   │           │   ├── ...

This is where our gnolang source code will be:

# the folder for our packages
gno/examples/gno.land/p/demo/guess
 
# the folder for our realm and tests
gno/examples/gno.land/r/demo/guess

# the folder tree
├── gno
│   ├── examples
│   │   └── gno.land
│   │       ├── p
│   │       │   └── demo
│   │       │       └── guess
│   │       └── r
│   │           └── demo
│   │               └── guess

I have duplicated the directory tree and code files as follows in this repository. This is for convenience and in order to not push any changes to the gno repository since it is a sub-repository/submodule of the tutorial repository. However, our /demo/guess code still needs to be run from within the cloned /gno repository.

# the root
src_gno/gno/examples/gno.land

# the folder for our packages
src_gno/gno/examples/gno.land/p/demo/guess
 
# the folder for our realm and tests
src_gno/gno/examples/gno.land/r/demo/guess

# the folder tree
├── src_gno
│   ├── gno
│   │   ├── examples
│   │   │   └── gno.land
│   │   │       ├── p
│   │   │       │   └── demo
│   │   │       │       └── guess
│   │   │       └── r
│   │   │           └── demo
│   │   │               └── guess

References