Cosmic Bull

permission_registry

gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/permission_registry on onyx-1 testnet

realmapplicationaccess-controllive on onyx-1

Register a resource and control who may do what with it.

Access rules live in one shared place with a clear owner, so a realm can check a caller's permission instead of hard-coding addresses.

In detail curated

MULTI-TENANT NAMED-PERMISSION REGISTRY -- resource -> permission -> holder, with an admin per resource, two-step admin transfer under nominee consent, pruning of emptied permissions, and a 90-day name reservation that preserves the original creator's reclaim right. Has(resource, permission, holder) is the read consuming realms call. NO CUSTODY BY CONSTRUCTION: no banker, no payable path, and rejectStraySend(cur) on all 8 entrypoints, so the realm address reads exists:false / coins "0". THIS IS NOT A NEW APPLICATION AND THE DEPLOY'S OBJECTIVE IS NARROW. The same bytes were audited (1 RED / 6 YELLOW, all resolved before broadcast), remediated and deployed to pearl-1 on 2026-09-21 at height 604615, tx a30c0942...; pearl-1 then retired on 2026-09-30 with all four hostnames NXDOMAIN, so that record is historical -- frozen, unverifiable and not re-derivable (section 7c). This broadcast re-establishes QUERYABLE live behavioural evidence for exactly these bytes on a chain that answers. The port is zero-substitution: the source has no first-party imports and no address literal, so only gnomod.toml's module line differs between the chain copies, and permission_registry.gno is BYTE-IDENTICAL to the pearl-1 and mainnet trees at sha256 603fec57... Byte-match PASSED on onyx-1 (raw-RPC vm/qfile, 6 of 6 checks). Selected under section 8a, which delegates the TESTNET selection and nothing downstream; the gnoland-1 selection remains the operator's.

Use it

A realm holds state. Reading it is a free query; changing it is a transaction you sign yourself.

Read it — no key, no account, no GNOT

gnokey query vm/qeval \
  --data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/permission_registry.ListResources()' \
  --remote https://rpc.onyx.testnets.gno.land:443

ListResources is read-only, so this is a query rather than a transaction: it costs nothing, signs nothing and needs no funded account. Every exported function is listed under the API below; swap the name and the arguments to read any of the others.

Change its state

Every state change is a transaction you sign with your own key — this site never asks for one and holds none. You do not have to hand-write the transaction: gnoweb generates the exact gnokey maketx call command for any of this realm's 7 crossing functions, with the argument names filled in.

Read the known limitations further down this page first. They record what was not verified on chain, which is usually the part that matters.

Import it from another realm

import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/permission_registry"

The path is the address: there is no package manager, nothing to add to a manifest and no version to pin, so the package you compile against is the one the chain runs. It differs per chain because the namespace in it is the deploying account's.

Deployments

onyx-1 testnet onyx-1

RenderedSourceCall builderState

chain-attested
Import pathgno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/permission_registry
Namespaceg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Deployed at height686,107
Deploy transaction915e3fb96a3145b4831e2d3eaa80baa489da0c8b78ca210fa3141a4964da52fc look it up on the RPC
Deployerg1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3
Gas used25,626,543
Storagenot attested per package on this chain — see the limitations below
Files on chaingnomod.toml permission_registry.gno
Deployed bytespermission_registry.gno — 18,070 bytes
sha256603fec571a55d051353e101c64c0490a5268124abda8d88ac703b9bbf26fb763
Realm addressg1x9tatmqcdlas2w6k724mrs2zcgh3awf3s5wkhk

Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:

curl -sS 'https://onyx.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/permission_registry$download&file=permission_registry.gno' | shasum -a 256

Expected: 603fec571a55d051353e101c64c0490a5268124abda8d88ac703b9bbf26fb763 — 18,070 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

API

chain-derived 13 exported functions, 1 type.

Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:

AcceptAdminCancelAdminTransferCreateResourceDeleteResourceGetAdminGetPendingAdminGetPermissionsGrantHasListResourcesRenderRevokeTransferAdmin

Overview

This package carries no package doc comment on chain, so there is nothing for vm/qdoc to return and gnoweb's $help Overview is empty. Deployed bytes are immutable, so this cannot be repaired in place — see [an unpublished record] §2.4.

Imports

Constants and variables

const (
	// MaxResources bounds total registry state. Raised from the upstream
	// 200 as part of the R1 remediation: with a per-admin quota now
	// carrying the anti-monopoly duty, the global cap is a pure state
	// bound rather than the sole defense against namespace exhaustion.
	MaxResources = 1000

	// MaxResourcesPerAdmin bounds how many resources one address may hold
	// at once. R1 (audit 2026-09-21): the upstream design had only a
	// global cap on a permissionless shared registry, so one unprivileged
	// key could occupy every slot for ~200 cheap transactions and
	// permanently deny the registry to every other tenant. DeleteResource
	// is admin-only, so the squat was irreversible.
	MaxResourcesPerAdmin = 20

	MaxPermissionsPerResource = 50
	MaxHoldersPerPermission   = 200
	MaxNameLen                = 64

	// ReservationPeriod is how long a deleted resource name stays
	// reserved. Finite (re-audit 2026-09-02): eternal tombstones let an
	// attacker cycle create/delete to lock the namespace forever.
	ReservationPeriod = int64(90 * 24 * 3600) // 90 days

	// Render bounds (Y3, audit 2026-09-21). Render walks the whole
	// registry and is reachable by any viewer through gnoweb and
	// vm/qrender, so its cost is borne by third parties rather than by
	// whoever grew the state. Uncapped, the declared limits allowed
	// 1000*50*200 rendered holder entries. The full data stays available
	// through ListResources / GetPermissions / Has, which are bounded per
	// call by construction.
	MaxRenderResources   = 20
	MaxRenderPermissions = 8
	MaxRenderHolders     = 10
)

Types

type Reservation

type Reservation struct {
	Admin   address
	Creator address
	Expires time.Time
}

Reservation holds a deleted resource name for its former admin AND its original creator, and expires.

Functions

AcceptAdmin

func AcceptAdmin(cur realm, resourceName string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as AcceptAdmin(cross(cur), ...).

AcceptAdmin completes a pending handoff; only the nominee may call it. The nominee's quota is checked HERE — at consent time — so a nomination can never push an account past MaxResourcesPerAdmin without that account agreeing to carry the resource.

CancelAdminTransfer

func CancelAdminTransfer(cur realm, resourceName string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as CancelAdminTransfer(cross(cur), ...).

CancelAdminTransfer withdraws a pending nomination. Only the current admin can call this.

CreateResource

func CreateResource(cur realm, resourceName string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as CreateResource(cross(cur), ...).

CreateResource registers a new named resource. The caller becomes its admin and is the only address that can grant or revoke permissions on it. A deleted resource name stays reserved for its former admin and its original creator until the reservation expires.

Each address may administer at most MaxResourcesPerAdmin resources at once, and the registry holds at most MaxResources in total.

DeleteResource

func DeleteResource(cur realm, resourceName string)

Crossing function. Callable from a transaction via MsgCall, and from another realm as DeleteResource(cross(cur), ...).

DeleteResource removes a resource and every permission under it. Only the resource admin can call this. The name stays reserved for the caller and for the original creator: nobody else can re-create it and inherit its consumers until the reservation expires.

GetAdmin

func GetAdmin(resourceName string) string

GetAdmin returns the admin address of a resource.

GetPendingAdmin

func GetPendingAdmin(resourceName string) string

GetPendingAdmin returns the nominated admin awaiting acceptance for a resource, or "none".

GetPermissions

func GetPermissions(resourceName string, addr address) string

GetPermissions returns all permission names granted to addr on a resource, as a comma-separated string. Returns "none" if the address has no permissions.

Grant

func Grant(cur realm, resourceName, permission string, addr address)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Grant(cross(cur), ...).

Grant gives an address a named permission on a resource. Only the resource admin can call this. Panics if the permission is already granted to avoid silent no-ops.

Has

func Has(resourceName, permission string, addr address) bool

Has returns true if addr holds the named permission on the resource. Returns false (never panics) for unknown resources or permissions.

INTEGRATOR CONTRACT (Y7): Has takes the subject address explicitly and performs NO caller authentication — it answers "does this address hold this permission", not "may my caller do this". A consuming realm must derive addr from its own crossing entrypoint's cur.Previous().Address() and pass it in. Deriving it inside a non-crossing helper via unsafe.PreviousRealm() resolves the consumer's own caller's caller and is a Class-2 designation-forgery bug in the consumer.

ListResources

func ListResources() string

ListResources returns all registered resource names as a comma-separated string in registration order. Returns "none" if no resources exist.

Render

func Render(path string) string

Render returns a markdown overview. Never panics. Output is bounded by MaxRenderResources / MaxRenderPermissions / MaxRenderHolders (Y3); truncated sections name the query to use for complete data.

Revoke

func Revoke(cur realm, resourceName, permission string, addr address)

Crossing function. Callable from a transaction via MsgCall, and from another realm as Revoke(cross(cur), ...).

Revoke removes a permission from an address. Only the resource admin can call this. Panics if the permission was not granted. A permission left with no holders is pruned from the resource's permission list.

TransferAdmin

func TransferAdmin(cur realm, resourceName string, newAdmin address)

Crossing function. Callable from a transaction via MsgCall, and from another realm as TransferAdmin(cross(cur), ...).

TransferAdmin nominates a new admin for a resource. Only the current admin can call this, and the handoff does NOT take effect until the nominee calls AcceptAdmin.

Y4 (audit 2026-09-21): the upstream one-step transfer made a well-formed-but-unowned destination permanently fatal. address.IsValid only checks bech32 form, so a mistyped address passed the check and left the resource with an admin nobody controls — it could never again be granted on, revoked from, transferred or deleted, and its slot was lost from both the global cap and the former admin's quota forever. Nomination is reversible; only the nominee's consent is final.


Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.

Dependencies

chain-attested
Importschain/runtime/unsafe, sort, strconv, strings, time
First-party dependenciesnone

Known limitations

Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.

curated

Source

Source fileonyx/r/permission_registry/permission_registry.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository)
Matches the deployed bytesyes — byte-identical
Upstream repositoryhttps://github.com/SillyZir/permission_registry
Deployed from commite3d6aeaf3571ec35c2af68c5fbb756480b76ed03