/p/nt/groups/v0
groups
A Group is a set of addresses (the base set) plus any number of named
Roles, each with its own member set and optional metadata. One Group
per DAO, per board, per permissions instance — whatever your realm manages.
Group
├── base set: the plain members (guests, users, council — you decide)
└── roles
├── "admin": member set + meta
└── "moderator": member set + meta
Quick start
1import "gno.land/p/nt/groups/v0"
2
3var group = groups.NewGroup()
4
5func init() {
6 // Base members.
7 group.Add(address("g1alice..."))
8 group.Add(address("g1bob..."))
9
10 // A role with its own members.
11 admins, _ := group.AddRole("admin")
12 admins.Members().Add(address("g1carol..."))
13}
Three kinds of operations
Every membership operation belongs to exactly one family, so a call site always says which semantic it means — checking the base set and checking "anywhere in the group" are different questions with different methods.
| Family | Methods | Looks at |
|---|---|---|
| Base set | Add, Remove, Has, Size, Iterate |
base set only |
| Role registry | AddRole, GetRole, HasRole, RemoveRole, RoleCount, IterateRoles |
the named roles |
| Aggregated | HasAny, TotalSize, IterateAll, RemoveFromAll |
base + every role, deduplicated |
| Aggregated | RolesContaining |
every role — base membership is not a role |
(NewGroup and the Readonly() views sit outside the families; views are
covered below.)
So with alice in the base set only and dave in the "council" role only:
1group.Has(alice) // true — alice is a base member
2group.Has(dave) // false — Has never consults roles
3group.HasAny(dave) // true — dave is somewhere in the group
4group.RolesContaining(dave) // ["council"]
An address may appear in the base set and several roles at once;
TotalSize and IterateAll count and yield it once. All iterators take
offset, count for pagination, and the callback returns true to stop.
RemoveRole discards only the role itself — its members stay wherever
else they appear. RemoveFromAll is the opposite: it purges one address
from the base set and every role.
Sharing across realms: readonly views
A *Group or *Role is a mutable handle: anyone holding it can change
your data (method calls run with the allocating realm's storage authority).
Readonly() returns a view that structurally cannot mutate — no mutator
methods exist on it at all.
Three rules at realm boundaries:
- Never accept a
*Group/*Rolefrom an untrusted caller. - Never return a
*Group/*Roleto one — returngroup.Readonly()(a*ReadonlyGroup) orrole.Readonly()instead. - Never trust a readonly view someone else hands you: it is a live window onto their data, which they can change between your reads.
The meta slot
Role.SetMeta(meta any) stores arbitrary per-role data — permission bits,
a description, a quorum. Store value types only (strings, ints, value
structs/slices). Do not store pointers to types with mutator methods (such
as *avl.Tree or *addrset.Set): Meta() returns the value as-is, so a
reader holding a readonly view could call those mutators on it.
See doc.gno for the precise security model, and
filetests/z_readme_filetest.gno for this README as a running example.