# 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
```go
import "gno.land/p/nt/groups/v0"
var group = groups.NewGroup()
func init() {
// Base members.
group.Add(address("g1alice..."))
group.Add(address("g1bob..."))
// A role with its own members.
admins, _ := group.AddRole("admin")
admins.Members().Add(address("g1carol..."))
}
```
## 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:
```go
group.Has(alice) // true — alice is a base member
group.Has(dave) // false — Has never consults roles
group.HasAny(dave) // true — dave is somewhere in the group
group.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:
1. **Never accept** a `*Group`/`*Role` from an untrusted caller.
2. **Never return** a `*Group`/`*Role` to one — return
`group.Readonly()` (a `*ReadonlyGroup`) or `role.Readonly()` instead.
3. **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.
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.