PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
timelock_guardian
Namespace
g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Files
3 (gnomod.toml)
Exported functions
15
Module
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian
gno
0.9

Files (3)

  • gnomod.tomltoml
  • timelock_guardian.gnogno
  • timelock_guardian_test.gnogno
timelock_guardian.gnogno
1package timelock_guardian23import (4	"chain"5	"chain/runtime/unsafe"6	"strconv"7	"strings"8	"time"9)1011// TargetConfig binds a named target to the only address allowed to12// schedule actions against it, an enforced minimum delay, and an13// optional guardian who can veto pending actions. Without this binding14// a timelock attests nothing: anyone could schedule their own15// short-delay action against any name and "execute" it.16type TargetConfig struct {17	Name     string18	Owner    address19	Guardian address // empty = no guardian20	MinDelay int64   // seconds; every action for this target waits at least this21	// PendingOwner is the offered-but-not-accepted new owner (fix Y5:22	// ownership moves in two steps, so a stranger can never have a23	// target — and its quota slot and pending obligations — dumped on24	// them without consenting).25	PendingOwner address26}2728// Action represents a scheduled operation that can only execute after29// its delay has elapsed, and only within its grace window.30//31// State model (re-audit 2026-09-02): only PENDING and EXECUTED actions32// are stored. Executed records are permanent attestations consumers33// check via IsExecuted. Cancelled, vetoed, and expired actions are34// REAPED from state — their history lives in emitted events — so the35// live-action cap bounds live exposure and can never be consumed36// permanently by schedule/cancel cycling.37type Action struct {38	ID           string39	Target       string40	Creator      address41	Data         string // encoded call data or description of the action42	ScheduledAt  time.Time43	ExecuteAfter time.Time44	ExpiresAt    time.Time // ExecuteAfter + GracePeriod; not executable after45	Executed     bool46}4748const (49	// MinDelayFloor is the smallest minimum delay a target may register.50	// A timelock with a 1-second delay protects nothing.51	MinDelayFloor = int64(60)5253	// MaxDelay bounds both registered minimum delays and per-action54	// delays. Also the overflow guard: MaxDelay seconds in nanoseconds55	// is far below int64 range, so ExecuteAfter arithmetic cannot wrap56	// into the past.57	MaxDelay = int64(10 * 365 * 24 * 3600) // 10 years5859	// GracePeriod is how long an action stays executable once ready.60	// After it, the action expires: something scheduled and forgotten61	// cannot be sprung on a target years later.62	GracePeriod = int64(30 * 24 * 3600) // 30 days6364	// Quotas are PER-OWNER only (re-audit round 3): any global cap is a65	// shared resource a few sybil accounts can exhaust forever (10y66	// delays defeat expiry sweeping), bricking every other tenant. With67	// per-owner quotas an attacker only ever consumes their own budget;68	// state growth is priced in gas and funded accounts.69	MaxTargetsPerOwner  = 1070	MaxPendingPerTarget = 2071	MaxTargetNameLen    = 6472	MaxDataLen          = 200073	MaxRenderActions    = 10074	// RenderIndexCap bounds the render-ordering index (fix Y4): the75	// index self-trims to this size, so reaping an entry from it is a76	// bounded scan no matter how many actions the realm has ever seen.77	// Records older than the window stay in state (GetAction/IsExecuted78	// are map reads and permanent); they only leave the front page.79	RenderIndexCap = 2 * MaxRenderActions80)8182var (83	targets      map[string]*TargetConfig84	targetNames  []string // insertion-ordered for deterministic iteration85	ownerTargets map[address]int86	actions      map[string]*Action87	actionIDs    []string // insertion-ordered render window, self-trimmed to RenderIndexCap88	// pending holds each target's LIVE pending action IDs in insertion89	// order (fix Y4): every list is bounded by MaxPendingPerTarget, so90	// scans, sweeps, and removals are bounded per call regardless of how91	// much state OTHER tenants have grown — queue position in a global92	// list is no longer a shared resource.93	pending map[string][]string94	// pendingTargets lists the targets holding >= 1 live pending action,95	// maintained with O(1) swap-remove via ptIndex (round-2 fix Y-1): no96	// write path ever scans a list another tenant can grow, and97	// GetPending/Render iterate only targets that actually have work.98	pendingTargets []string99	ptIndex        map[string]int100	nextID         int101)102103func init() {104	targets = make(map[string]*TargetConfig)105	targetNames = []string{}106	ownerTargets = make(map[address]int)107	actions = make(map[string]*Action)108	actionIDs = []string{}109	pending = make(map[string][]string)110	pendingTargets = []string{}111	ptIndex = make(map[string]int)112	nextID = 1113}114115func now() time.Time {116	return time.Now()117}118119// ---------- helpers ----------120121// rejectStraySend aborts when coins are attached to a call (fix Y6):122// this realm handles no funds and holds no banker, so an attached send123// would strand on the realm address forever. Aborting reverts the124// transfer back to the sender. Fails open for realm-routed calls, whose125// attached send lands on the intermediary realm, never here.126func rejectStraySend(cur realm) {127	if cur.Previous().IsUserCall() && len(unsafe.OriginSend()) > 0 {128		panic("this realm does not accept coins")129	}130}131132func mustGetTarget(name string) *TargetConfig {133	t, ok := targets[name]134	if !ok {135		panic("target not found: " + name)136	}137	return t138}139140func mustGet(id string) *Action {141	a, ok := actions[id]142	if !ok {143		panic("action not found: " + id)144	}145	return a146}147148func isValidName(name string) bool {149	if name == "" || len(name) > MaxTargetNameLen {150		return false151	}152	for _, c := range name {153		if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {154			return false155		}156	}157	return true158}159160func expired(a *Action) bool {161	return !a.Executed && now().After(a.ExpiresAt)162}163164func removeID(list []string, id string) []string {165	for i, x := range list {166		if x == id {167			return append(list[:i], list[i+1:]...)168		}169	}170	return list171}172173func addPendingTarget(target string) {174	if _, ok := ptIndex[target]; ok {175		return176	}177	ptIndex[target] = len(pendingTargets)178	pendingTargets = append(pendingTargets, target)179}180181// removePendingTarget drops a target from the with-pendings list in182// O(1) by swapping the last entry into its slot. Order afterwards is183// deterministic (a pure function of the operation history), which is184// all iteration needs.185func removePendingTarget(target string) {186	i, ok := ptIndex[target]187	if !ok {188		return189	}190	last := len(pendingTargets) - 1191	moved := pendingTargets[last]192	pendingTargets[i] = moved193	ptIndex[moved] = i194	pendingTargets = pendingTargets[:last]195	delete(ptIndex, target)196}197198// dropPending removes id from its target's pending list. Bounded: the199// list never exceeds MaxPendingPerTarget.200func dropPending(target, id string) {201	l := removeID(pending[target], id)202	if len(l) == 0 {203		delete(pending, target)204		removePendingTarget(target)205	} else {206		pending[target] = l207	}208}209210// reap removes a non-executed action from state entirely; its history211// is the emitted event. Executed records are permanent attestations:212// reap refuses them outright (round-2 hardening), so no future caller213// can erase one by mistake.214func reap(id string) {215	a := actions[id]216	if a != nil && a.Executed {217		return218	}219	if a != nil {220		dropPending(a.Target, id)221	}222	delete(actions, id)223	actionIDs = removeID(actionIDs, id)224}225226// sweepExpired reaps the expired pending actions of ONE target (fix227// Y4/Y2: the old global budget-windowed sweep let long-delay entries at228// the front of a shared queue starve everything behind them). A229// target's list is bounded by MaxPendingPerTarget, so the sweep is a230// bounded scan. Expiry history is the emitted event.231func sweepExpired(target string) {232	var live []string233	for _, id := range pending[target] {234		a := actions[id]235		if a == nil {236			continue237		}238		if expired(a) {239			chain.Emit("timelock_expired", "id", id, "target", a.Target)240			delete(actions, id)241			actionIDs = removeID(actionIDs, id)242			continue243		}244		live = append(live, id)245	}246	if len(live) == 0 {247		delete(pending, target)248		removePendingTarget(target)249	} else {250		pending[target] = live251	}252}253254// pendingCount is the length of the target's live list. The counter it255// replaces (fix Y4) could desync from the lists it mirrored; a length256// cannot. It may briefly include not-yet-swept expired actions; those257// are reaped by the next Schedule's sweep or by anyone's Expire, and in258// the worst case an owner briefly under-uses their own quota — never259// another tenant's.260func pendingCount(target string) int {261	return len(pending[target])262}263264// sanitize makes attacker-controlled text safe to embed in markdown and265// single-line summaries: backticks, pipes, newlines, link syntax, and266// raw HTML brackets (fix Y3: gnoweb has no HTML sanitization layer, so267// a literal <script> tag must never reach the page) are replaced so a268// Data string cannot break out of its cell, inject rows or markup, or269// render a live link.270func sanitize(s string) string {271	r := strings.NewReplacer(272		"`", "'", "|", "/", "\n", " ", "\r", " ",273		"[", "(", "]", ")", "*", "·", "_", "-",274		"<", "(", ">", ")",275	)276	return r.Replace(s)277}278279// truncate shortens s to at most n runes without splitting a multibyte280// character. n is clamped to a minimum of 4 (re-audit: n<=2 sliced out281// of bounds).282func truncate(s string, n int) string {283	if n < 4 {284		n = 4285	}286	runes := []rune(s)287	if len(runes) <= n {288		return s289	}290	return string(runes[:n-3]) + "..."291}292293// ---------- target registration ----------294295// RegisterTarget creates a named target. The caller becomes its owner —296// the only address that may schedule actions against it. minDelay is297// the enforced floor for every action's delay. guardian may be empty298// (no guardian) or an address empowered to veto pending actions.299func RegisterTarget(cur realm, name string, minDelay int64, guardian address) {300	rejectStraySend(cur)301	owner := cur.Previous().Address()302	if !isValidName(name) {303		panic("target name must be 1-" + strconv.Itoa(MaxTargetNameLen) +304			" chars, lowercase alphanumeric with underscores only")305	}306	if _, exists := targets[name]; exists {307		panic("target already registered: " + name)308	}309	if ownerTargets[owner] >= MaxTargetsPerOwner {310		panic("per-owner target limit reached")311	}312	if minDelay < MinDelayFloor {313		panic("minimum delay must be at least " + strconv.FormatInt(MinDelayFloor, 10) + " seconds")314	}315	if minDelay > MaxDelay {316		panic("minimum delay exceeds the maximum of " + strconv.FormatInt(MaxDelay, 10) + " seconds")317	}318	if guardian != "" && !guardian.IsValid() {319		panic("invalid guardian address: " + string(guardian))320	}321322	targets[name] = &TargetConfig{323		Name:     name,324		Owner:    owner,325		Guardian: guardian,326		MinDelay: minDelay,327	}328	targetNames = append(targetNames, name)329	ownerTargets[owner]++330	chain.Emit("timelock_target_registered", "target", name,331		"owner", string(owner), "guardian", string(guardian),332		"min_delay", strconv.FormatInt(minDelay, 10))333}334335// SetGuardian changes (or clears, with "") the target's guardian.336// Owner only, and REFUSED while the target has pending actions: the337// guardian's veto power exists precisely to check the owner during a338// delay window, so the owner must not be able to strip it mid-window.339//340// KNOWN LIMIT (documented, round-3 audit): the owner can cancel all341// pending actions, change the guardian, and reschedule — the price is342// a full fresh MinDelay on every rescheduled action, and every step343// emits an event (cancellations + the guardian change below), so344// observers always get MinDelay of warning under the new guardian345// regime. Guardians protect open windows, not the owner's future.346func SetGuardian(cur realm, targetName string, guardian address) {347	rejectStraySend(cur)348	t := mustGetTarget(targetName)349	if cur.Previous().Address() != t.Owner {350		panic("only the target owner can set the guardian")351	}352	// expired-but-unswept actions protect nothing; sweep them so they353	// cannot block a guardian rotation (round-2 hardening)354	sweepExpired(targetName)355	if pendingCount(targetName) > 0 {356		panic("cannot change the guardian while actions are pending")357	}358	if guardian != "" && !guardian.IsValid() {359		panic("invalid guardian address: " + string(guardian))360	}361	t.Guardian = guardian362	chain.Emit("timelock_guardian_changed", "target", targetName,363		"guardian", string(guardian))364}365366// TransferTargetOwnership OFFERS a target to a new owner; the nominee367// must AcceptTargetOwnership to complete it (fix Y5: a one-step368// transfer let anyone fill a stranger's per-owner quota and dump369// pending obligations — with an attacker-chosen guardian — on an370// address that never asked). Owner only. Pass "" to clear a pending371// offer. Nothing changes hands until the nominee accepts.372func TransferTargetOwnership(cur realm, targetName string, newOwner address) {373	rejectStraySend(cur)374	t := mustGetTarget(targetName)375	if cur.Previous().Address() != t.Owner {376		panic("only the target owner can transfer ownership")377	}378	if newOwner == "" {379		t.PendingOwner = ""380		chain.Emit("timelock_ownership_offer_cleared", "target", targetName)381		return382	}383	if !newOwner.IsValid() {384		panic("invalid new owner address: " + string(newOwner))385	}386	if newOwner == t.Owner {387		panic("new owner is already the owner")388	}389	t.PendingOwner = newOwner390	chain.Emit("timelock_ownership_offered", "target", targetName,391		"pending_owner", string(newOwner))392}393394// AcceptTargetOwnership completes a pending ownership offer; only the395// nominee can accept. The nominee's quota is checked HERE — consent396// time — so an offer can never overfill an account that did not agree397// to carry it.398func AcceptTargetOwnership(cur realm, targetName string) {399	rejectStraySend(cur)400	t := mustGetTarget(targetName)401	a := cur.Previous().Address()402	if t.PendingOwner == "" || a != t.PendingOwner {403		panic("caller is not the pending owner")404	}405	if ownerTargets[a] >= MaxTargetsPerOwner {406		panic("accepting would exceed the per-owner target limit")407	}408	ownerTargets[t.Owner]--409	if ownerTargets[t.Owner] <= 0 {410		delete(ownerTargets, t.Owner)411	}412	ownerTargets[a]++413	t.Owner = a414	t.PendingOwner = ""415	chain.Emit("timelock_ownership_transferred", "target", targetName,416		"new_owner", string(a))417}418419// ---------- write operations ----------420421// Schedule creates a new timelocked action against a registered target.422// Only the target's owner may schedule. The delay must be at least the423// target's registered minimum and at most MaxDelay. Returns the action ID.424func Schedule(cur realm, targetName, data string, delay int64) string {425	rejectStraySend(cur)426	t := mustGetTarget(targetName)427	caller := cur.Previous().Address()428	if caller != t.Owner {429		panic("only the target owner can schedule actions for " + targetName)430	}431	if data == "" {432		panic("data must not be empty")433	}434	if len(data) > MaxDataLen {435		panic("data exceeds " + strconv.Itoa(MaxDataLen) + " bytes")436	}437	if delay < t.MinDelay {438		panic("delay below the target's minimum of " + strconv.FormatInt(t.MinDelay, 10) + " seconds")439	}440	if delay > MaxDelay {441		panic("delay exceeds the maximum of " + strconv.FormatInt(MaxDelay, 10) + " seconds")442	}443444	sweepExpired(targetName)445	if pendingCount(targetName) >= MaxPendingPerTarget {446		panic("too many pending actions for target " + targetName)447	}448449	id := "action_" + strconv.Itoa(nextID)450	nextID++451452	ts := now()453	execAfter := ts.Add(time.Duration(delay) * time.Second)454	actions[id] = &Action{455		ID:           id,456		Target:       targetName,457		Creator:      caller,458		Data:         data,459		ScheduledAt:  ts,460		ExecuteAfter: execAfter,461		ExpiresAt:    execAfter.Add(time.Duration(GracePeriod) * time.Second),462	}463	actionIDs = append(actionIDs, id)464	// self-trim (fix Y4): the render index stays bounded, so reaping an465	// entry from it can never become an unbounded scan. Trimmed entries466	// remain in the actions map; only front-page ordering forgets them.467	if len(actionIDs) > RenderIndexCap {468		actionIDs = append([]string{}, actionIDs[len(actionIDs)-MaxRenderActions:]...)469	}470	pending[targetName] = append(pending[targetName], id)471	addPendingTarget(targetName)472	// full-attribute event (round-3 audit): reaped records must be473	// reconstructible from events alone474	chain.Emit("timelock_scheduled", "id", id, "target", targetName,475		"creator", string(caller), "data", data,476		"delay", strconv.FormatInt(delay, 10),477		"execute_after", execAfter.Format(time.RFC3339),478		"expires_at", execAfter.Add(time.Duration(GracePeriod)*time.Second).Format(time.RFC3339))479	return id480}481482// Execute marks an action as executed. Anyone can call this — the483// timelock is the protection, not the executor's identity. The action484// must exist, be pending, its delay elapsed, and its grace window not485// yet expired.486func Execute(cur realm, actionID string) string {487	rejectStraySend(cur)488	a := mustGet(actionID)489	if a.Executed {490		panic("action already executed")491	}492	if now().Before(a.ExecuteAfter) {493		remaining := a.ExecuteAfter.Sub(now())494		panic("too early: " + strconv.FormatInt(int64(remaining/time.Second), 10) + "s remaining")495	}496	if now().After(a.ExpiresAt) {497		panic("action expired: the grace window of " +498			strconv.FormatInt(GracePeriod, 10) + "s after readiness has passed")499	}500501	a.Executed = true502	dropPending(a.Target, actionID)503	chain.Emit("timelock_executed", "id", actionID, "target", a.Target)504	return "executed: " + actionID505}506507// Cancel removes a pending action. Only the target's CURRENT owner can508// cancel (re-audit: the scheduling creator's rights must not survive an509// ownership transfer). The record is reaped; history is the event.510func Cancel(cur realm, actionID string) string {511	rejectStraySend(cur)512	a := mustGet(actionID)513	t := mustGetTarget(a.Target)514	if cur.Previous().Address() != t.Owner {515		panic("only the target owner can cancel")516	}517	if a.Executed {518		panic("cannot cancel an already-executed action")519	}520	if expired(a) {521		panic("action expired: nothing to cancel (use Expire)")522	}523524	chain.Emit("timelock_cancelled", "id", actionID, "target", a.Target)525	reap(actionID)526	return "cancelled: " + actionID527}528529// Veto cancels a pending action as the target's guardian. This is the530// guardian's whole power: it can stop a scheduled action during the531// delay window, never create or execute one. The record is reaped;532// history is the event.533func Veto(cur realm, actionID string) string {534	rejectStraySend(cur)535	a := mustGet(actionID)536	t := mustGetTarget(a.Target)537	if t.Guardian == "" {538		panic("target has no guardian: " + a.Target)539	}540	if cur.Previous().Address() != t.Guardian {541		panic("only the target guardian can veto")542	}543	if a.Executed {544		panic("cannot veto an already-executed action")545	}546	if expired(a) {547		panic("action expired: nothing to veto (use Expire)")548	}549550	chain.Emit("timelock_vetoed", "id", actionID, "target", a.Target)551	reap(actionID)552	return "vetoed: " + actionID553}554555// Expire reaps a provably expired action. Permissionless (fix Y2): an556// expired action decides nothing — reaping it only writes down what the557// clock already decided — so anyone may free the quota slot it holds.558// This is the recovery valve that makes a wedged target impossible:559// before it, a phantom expired entry consumed quota and blocked560// SetGuardian until a global sweep happened to reach it; now its own561// target's owner — or anyone else — reaps it directly.562func Expire(cur realm, actionID string) string {563	rejectStraySend(cur)564	a := mustGet(actionID)565	if a.Executed {566		panic("action already executed")567	}568	if !expired(a) {569		panic("action is not expired")570	}571	chain.Emit("timelock_expired", "id", actionID, "target", a.Target)572	reap(actionID)573	return "expired: " + actionID574}575576// ---------- read-only queries ----------577578// GetTarget returns a formatted summary of a registered target.579func GetTarget(targetName string) string {580	t := mustGetTarget(targetName)581	var b strings.Builder582	b.WriteString("Target: " + t.Name + "\n")583	b.WriteString("Owner: " + string(t.Owner) + "\n")584	guardian := "none"585	if t.Guardian != "" {586		guardian = string(t.Guardian)587	}588	b.WriteString("Guardian: " + guardian + "\n")589	b.WriteString("Min delay: " + strconv.FormatInt(t.MinDelay, 10) + "s\n")590	if t.PendingOwner != "" {591		b.WriteString("Pending owner: " + string(t.PendingOwner) + "\n")592	}593	return b.String()594}595596// GetAction returns a formatted summary of a single pending or executed597// action. Cancelled/vetoed/expired actions are reaped — their history598// is in emitted events.599func GetAction(actionID string) string {600	a := mustGet(actionID)601	return formatAction(a)602}603604// GetPending returns the IDs of all pending, non-expired actions,605// grouped by target, insertion-ordered within a target. Only targets606// that actually hold live pendings are visited (round-2 fix Y-1), so607// the scan cannot be inflated by registrations alone.608func GetPending() string {609	var results []string610	for _, name := range pendingTargets {611		for _, id := range pending[name] {612			a := actions[id]613			if a == nil || a.Executed || expired(a) {614				continue615			}616			results = append(results, a.ID)617		}618	}619	if len(results) == 0 {620		return "none"621	}622	return strings.Join(results, ", ")623}624625// IsReady returns true if the action exists, is pending, its delay has626// elapsed, and it has not expired.627func IsReady(actionID string) bool {628	a, ok := actions[actionID]629	if !ok || a.Executed || expired(a) {630		return false631	}632	return !now().Before(a.ExecuteAfter)633}634635// IsExecuted returns true if the action exists and was executed. This is636// the consumer-side check: combined with target registration it attests637// that the target's registered owner scheduled the action, it waited at638// least the registered minimum delay, no guardian vetoed it, and it was639// executed within its grace window. Executed records are permanent.640func IsExecuted(actionID string) bool {641	a, ok := actions[actionID]642	return ok && a.Executed643}644645// ---------- render ----------646647// Render returns a markdown overview. Never panics. Cancelled, vetoed,648// and expired actions are reaped from state; their history is in649// events. The page shows the most recent actions only (the ordering650// index is bounded — fix Y4); older executed records stay queryable651// via GetAction/IsExecuted forever.652func Render(path string) string {653	if actions == nil || len(actions) == 0 {654		return "# Timelock Guardian\n\nNo actions scheduled.\n"655	}656657	// The Pending section reads the live per-target lists, so flooding658	// the realm with executed churn can never push a still-live pending659	// action off the page during its delay window (round-2 fix Y-2).660	var pendingList []*Action661	truncated := false662	for _, name := range pendingTargets {663		for _, id := range pending[name] {664			a := actions[id]665			if a == nil || a.Executed || expired(a) {666				continue667			}668			if len(pendingList) >= MaxRenderActions {669				truncated = true670				break671			}672			pendingList = append(pendingList, a)673		}674		if truncated {675			break676		}677	}678679	// The Executed section reads the bounded render index: recent680	// attestations only; older ones stay queryable via GetAction.681	var executedList []*Action682	show := actionIDs683	if len(show) > MaxRenderActions {684		show = show[len(show)-MaxRenderActions:]685		truncated = true686	}687	for _, id := range show {688		a := actions[id]689		if a == nil || !a.Executed {690			continue691		}692		executedList = append(executedList, a)693	}694695	var b strings.Builder696	b.WriteString("# Timelock Guardian\n\n")697	if truncated {698		b.WriteString("_Showing at most " + strconv.Itoa(MaxRenderActions) + " actions per section._\n\n")699	}700701	section := func(title string, list []*Action) {702		if len(list) == 0 {703			return704		}705		b.WriteString("## " + title + " (" + strconv.Itoa(len(list)) + ")\n\n")706		for _, a := range list {707			b.WriteString(renderActionRow(a))708		}709		b.WriteString("\n")710	}711	section("Pending", pendingList)712	section("Executed", executedList)713714	return b.String()715}716717// ---------- formatting helpers ----------718719func formatAction(a *Action) string {720	var b strings.Builder721	b.WriteString("ID: " + a.ID + "\n")722	b.WriteString("Target: " + sanitize(a.Target) + "\n")723	b.WriteString("Creator: " + string(a.Creator) + "\n")724	b.WriteString("Data: " + sanitize(a.Data) + "\n")725	b.WriteString("Scheduled: " + a.ScheduledAt.Format(time.RFC3339) + "\n")726	b.WriteString("Execute after: " + a.ExecuteAfter.Format(time.RFC3339) + "\n")727	b.WriteString("Expires: " + a.ExpiresAt.Format(time.RFC3339) + "\n")728729	switch {730	case a.Executed:731		b.WriteString("Status: executed\n")732	case expired(a):733		b.WriteString("Status: expired\n")734	case now().Before(a.ExecuteAfter):735		remaining := a.ExecuteAfter.Sub(now())736		b.WriteString("Status: locked (" + strconv.FormatInt(int64(remaining/time.Second), 10) + "s remaining)\n")737	default:738		b.WriteString("Status: ready\n")739	}740	return b.String()741}742743func renderActionRow(a *Action) string {744	data := truncate(sanitize(a.Data), 40)745746	status := "pending"747	switch {748	case a.Executed:749		status = "executed"750	case expired(a):751		status = "expired"752	case !now().Before(a.ExecuteAfter):753		status = "**ready**"754	}755756	return "- **" + a.ID + "** | target: `" + sanitize(a.Target) + "` | " + data + " | " + status + "\n"757}758

Functions

  • AcceptTargetOwnership(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string)

  • Cancel(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string

  • Execute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string

  • Expire(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string

  • GetAction(actionID string) string

  • GetPending() string

  • GetTarget(targetName string) string

  • IsExecuted(actionID string) bool

  • IsReady(actionID string) bool

  • RegisterTarget(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string, minDelay int64, guardian string)

  • Render(path string) string

  • Schedule(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string, data string, delay int64) string

  • SetGuardian(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string, guardian string)

  • TransferTargetOwnership(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, targetName string, newOwner string)

  • Veto(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, actionID string) string

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.