In gno.land, code is organized into packages that are stored on-chain. This guide explains the different types of packages, how they're organized, and how to work with them.
Gno has two fundamental package types:
Pure packages are stateless Gno libraries meant to be reused by other Gno code. Here are the defining features of pure packages:
- Don't maintain state between calls
- Can be imported by both realms and other pure packages
- Are stored under paths beginning with
/p/ - Can be written & deployed to the chain by anyone, permissionlessly
- Users cannot call functions in pure packages directly
- Documentation should be contained within package code as comments, following the Go doc standard
Example: gno.land/p/demo/avl (An AVL tree implementation)
Realms are stateful applications (smart contracts) that can:
- Maintain persistent state between transactions
- Expose functions for interaction
- Render web content
- Import pure packages and use their functionality
- Are stored under paths beginning with
/r/
Example: gno.land/r/demo/boards (A discussion forum application)
For more details on realms, see the dedicated Realms documentation.
A package path is a unique identifier for any package that lives on the gno.land
blockchain. It consists of multiple parts separated with / and follows this
structure:
gno.land/[r|p]/[namespace]/[package-name]
│ │ │
│ │ └── Name of the package
│ └── Namespace (often a username)
└── Type (realm or pure package)
For example:
gno.land/r/gnoland/homeis the gno.land home realmgno.land/r/leon/hofis the Hall of Fame realmgno.land/p/demo/avlis the AVL tree package
The components of these paths are:
gno.landis the chain domain. Currently, onlygno.landis supported, but the ecosystem may expand in the future.porrdeclare the type of package found at the path.pstands for pure package, whilerrepresents realm.demo,gnoland, etc., represent namespaces as described below.home,hof,avl, etc., represent the package name found at the path.
Two important facts about package paths:
- The maximum length of a package path is
256characters. - A realm's address is directly derived from its package path, by using
std.DerivePkgAddr()
Namespaces provide users with the exclusive ability to publish code under their designated identifiers, similar to GitHub's user and organization model. For detailed information on how to register and use namespaces, see Users and Teams.
Initially, all users are granted a default namespace with their address - a pseudo-anonymous (PA) namespace - to which the associated address can deploy. This namespace has the following format:
gno.land/{p,r}/{std.Address}/**
For example, for address g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5, all the
following paths are valid for deployments:
gno.land/p/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/mypackagegno.land/r/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/myrealmgno.land/p/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/mypackage/subpackage/packagegno.land/r/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/subpackage/realm
Apart from package names, developers can define subpackages to further organize
their code, as seen in the example above. Packages can have any varying level of
depth as long as the full package path doesn't exceed 256 characters.
To register a custom namespace:
- Register a username at
gno.land/r/gnoland/users - Once registered, you can deploy packages under that namespace
- Only you can deploy to your namespace
This prevents impersonation and name squatting, ensuring package path authenticity.
Gno packages can import other packages using standard Go import syntax:
import (
"gno.land/p/demo/avl" // Pure package import
"gno.land/r/demo/users" // Realm import (access exported functions)
)To better understand how packages work, let's look at a few commonly used ones
from the examples
folder, available under the gno.land/p/demo path.
Deployed under gno.land/p/demo/avl, the AVL package provides a tree structure
for storing data. It replaces the functionality of the native map in Gno, as
maps are not fully deterministic. Usage example:
package myrealm
import (
"gno.land/p/demo/avl"
)
// This AVL tree will be persisted after transaction calls
var tree *avl.Tree
func Set(key string, value int) {
// tree.Set takes in a string key, and a value that can be of any type
tree.Set(key, value)
}
func Get(key string) int {
// tree.Get returns the value at given key in its raw form,
// and a bool to signify the existence of the key-value pair
rawValue, exists := tree.Get(key)
if !exists {
panic("value at given key does not exist")
}
// rawValue needs to be converted into the proper type before returning it
return rawValue.(int)
}View the package on the Portal Loop network or on GitHub.
Deployed under gno.land/p/demo/ufmt, this package is a minimal version of the
fmt package:
// Package ufmt provides utility functions for formatting strings, similarly
// to the Go package "fmt", of which only a subset is currently supported
// (hence the name µfmt - micro fmt).
package ufmtView the package on the Portal Loop network or on GitHub.
Deployed under gno.land/p/demo/seqid, this package provides a simple way to
have sequential IDs in Gno:
// Package seqid provides a simple way to have sequential IDs which will be
// ordered correctly when inserted in an AVL tree.
//
// Sample usage:
//
// var id seqid.ID
// var users avl.Tree
//
// func NewUser() {
// users.Set(id.Next().String(), &User{ ... })
// }
package seqidView the package on the Portal Loop network or on GitHub.
You can explore all deployed packages using gnoweb.
This provides transparency and allows you to learn from existing code.
For detailed instructions on creating your own packages:
- For realms, see Example Minisocial dApp
- For deployment, see Deploying Gno Packages