Cosmic Bull

timelock_guardian

gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian on gno.land mainnet

realmpipeline-applicationsecurity-delayed-executionlive on mainnet

Delay a sensitive action, with time to cancel it.

An action waits out a minimum delay in the open before it can run, and a named guardian can veto it in the meantime.

In detail curated

Delayed-execution guardian: queue, delay window, execute or cancel. GitHub-pipeline application #2.

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/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian.GetPending()' \
  --remote https://rpc.gno.land:443

GetPending 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 9 crossing functions, with the argument names filled in.

The gno.land mainnet builder spends real GNOT. Start with the read above, and size -gas-fee from a simulation rather than from any example.

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/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian"

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.

Called from real code

Not an example written for this page. These lines are quoted from upkeep, a deployed package in this portfolio that imports this one as guardian, out of the committed file whose sha256 gno.land mainnet attested. They compile because they are compiled.

guardian.Execute(cross(cur), actionID)
mainnet/r/upkeep/upkeep.gno line 195, gno.land mainnet

Deployments

gno.land mainnet gnoland-1

RenderedSourceCall builderState

chain-attested
Import pathgno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian
Namespaceg1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Deployed at height336,668
Deploy transaction7c64883ecaed7a2ce6240f836b36381b4d4d84adec51c38ba171eae3b33e2d8d look it up on the RPC
Deployerg1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Gas used75,859,960
Storagenot attested per package on this chain — see the limitations below
Files on chaingnomod.toml timelock_guardian.gno timelock_guardian_test.gno
Deployed bytestimelock_guardian.gno — 24,983 bytes
sha25672ca7049496257e6fb00216aa027736fa90d41c1dbc91cf8059fc8fd29080f07
Realm addressg1nlrwhuatzwqggak4wld5jfn6keztwt3atfnnq9 derived, never confirmed against the realm

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

curl -sS 'https://gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian$download&file=timelock_guardian.gno' | shasum -a 256

Expected: 72ca7049496257e6fb00216aa027736fa90d41c1dbc91cf8059fc8fd29080f07 — 24,983 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.

API

chain-derived 15 exported functions, 2 types.

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

AcceptTargetOwnershipCancelExecuteExpireGetActionGetPendingGetTargetIsExecutedIsReadyRegisterTargetRenderScheduleSetGuardianTransferTargetOwnershipVeto

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 (
	// MinDelayFloor is the smallest minimum delay a target may register.
	// A timelock with a 1-second delay protects nothing.
	MinDelayFloor = int64(60)

	// MaxDelay bounds both registered minimum delays and per-action
	// delays. Also the overflow guard: MaxDelay seconds in nanoseconds
	// is far below int64 range, so ExecuteAfter arithmetic cannot wrap
	// into the past.
	MaxDelay = int64(10 * 365 * 24 * 3600) // 10 years

	// GracePeriod is how long an action stays executable once ready.
	// After it, the action expires: something scheduled and forgotten
	// cannot be sprung on a target years later.
	GracePeriod = int64(30 * 24 * 3600) // 30 days

	// Quotas are PER-OWNER only (re-audit round 3): any global cap is a
	// shared resource a few sybil accounts can exhaust forever (10y
	// delays defeat expiry sweeping), bricking every other tenant. With
	// per-owner quotas an attacker only ever consumes their own budget;
	// state growth is priced in gas and funded accounts.
	MaxTargetsPerOwner  = 10
	MaxPendingPerTarget = 20
	MaxTargetNameLen    = 64
	MaxDataLen          = 2000
	MaxRenderActions    = 100
	// RenderIndexCap bounds the render-ordering index (fix Y4): the
	// index self-trims to this size, so reaping an entry from it is a
	// bounded scan no matter how many actions the realm has ever seen.
	// Records older than the window stay in state (GetAction/IsExecuted
	// are map reads and permanent); they only leave the front page.
	RenderIndexCap = 2 * MaxRenderActions
)

Types

type Action

type Action struct {
	ID           string
	Target       string
	Creator      address
	Data         string // encoded call data or description of the action
	ScheduledAt  time.Time
	ExecuteAfter time.Time
	ExpiresAt    time.Time // ExecuteAfter + GracePeriod; not executable after
	Executed     bool
}

Action represents a scheduled operation that can only execute after its delay has elapsed, and only within its grace window.

State model (re-audit 2026-09-02): only PENDING and EXECUTED actions are stored. Executed records are permanent attestations consumers check via IsExecuted. Cancelled, vetoed, and expired actions are REAPED from state — their history lives in emitted events — so the live-action cap bounds live exposure and can never be consumed permanently by schedule/cancel cycling.

Exported fieldTypeDoc
IDstring
Targetstring
Creatoraddress
Datastringencoded call data or description of the action
ScheduledAttime.Time
ExecuteAftertime.Time
ExpiresAttime.TimeExecuteAfter + GracePeriod; not executable after
Executedbool

type TargetConfig

type TargetConfig struct {
	Name     string
	Owner    address
	Guardian address // empty = no guardian
	MinDelay int64   // seconds; every action for this target waits at least this
	// PendingOwner is the offered-but-not-accepted new owner (fix Y5:
	// ownership moves in two steps, so a stranger can never have a
	// target — and its quota slot and pending obligations — dumped on
	// them without consenting).
	PendingOwner address
}

TargetConfig binds a named target to the only address allowed to schedule actions against it, an enforced minimum delay, and an optional guardian who can veto pending actions. Without this binding a timelock attests nothing: anyone could schedule their own short-delay action against any name and "execute" it.

Exported fieldTypeDoc
Namestring
Owneraddress
Guardianaddressempty = no guardian
MinDelayint64seconds; every action for this target waits at least this
PendingOwneraddressPendingOwner is the offered-but-not-accepted new owner (fix Y5: ownership moves in two steps, so a stranger can never have a target — and its quota slot and pending obligations — dumped on them without consenting).

Functions

AcceptTargetOwnership

func AcceptTargetOwnership(cur realm, targetName string)

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

AcceptTargetOwnership completes a pending ownership offer; only the nominee can accept. The nominee's quota is checked HERE — consent time — so an offer can never overfill an account that did not agree to carry it.

Cancel

func Cancel(cur realm, actionID string) string

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

Cancel removes a pending action. Only the target's CURRENT owner can cancel (re-audit: the scheduling creator's rights must not survive an ownership transfer). The record is reaped; history is the event.

Execute

func Execute(cur realm, actionID string) string

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

Execute marks an action as executed. Anyone can call this — the timelock is the protection, not the executor's identity. The action must exist, be pending, its delay elapsed, and its grace window not yet expired.

Expire

func Expire(cur realm, actionID string) string

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

Expire reaps a provably expired action. Permissionless (fix Y2): an expired action decides nothing — reaping it only writes down what the clock already decided — so anyone may free the quota slot it holds. This is the recovery valve that makes a wedged target impossible: before it, a phantom expired entry consumed quota and blocked SetGuardian until a global sweep happened to reach it; now its own target's owner — or anyone else — reaps it directly.

GetAction

func GetAction(actionID string) string

GetAction returns a formatted summary of a single pending or executed action. Cancelled/vetoed/expired actions are reaped — their history is in emitted events.

GetPending

func GetPending() string

GetPending returns the IDs of all pending, non-expired actions, grouped by target, insertion-ordered within a target. Only targets that actually hold live pendings are visited (round-2 fix Y-1), so the scan cannot be inflated by registrations alone.

GetTarget

func GetTarget(targetName string) string

GetTarget returns a formatted summary of a registered target.

IsExecuted

func IsExecuted(actionID string) bool

IsExecuted returns true if the action exists and was executed. This is the consumer-side check: combined with target registration it attests that the target's registered owner scheduled the action, it waited at least the registered minimum delay, no guardian vetoed it, and it was executed within its grace window. Executed records are permanent.

IsReady

func IsReady(actionID string) bool

IsReady returns true if the action exists, is pending, its delay has elapsed, and it has not expired.

RegisterTarget

func RegisterTarget(cur realm, name string, minDelay int64, guardian address)

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

RegisterTarget creates a named target. The caller becomes its owner — the only address that may schedule actions against it. minDelay is the enforced floor for every action's delay. guardian may be empty (no guardian) or an address empowered to veto pending actions.

Render

func Render(path string) string

Render returns a markdown overview. Never panics. Cancelled, vetoed, and expired actions are reaped from state; their history is in events. The page shows the most recent actions only (the ordering index is bounded — fix Y4); older executed records stay queryable via GetAction/IsExecuted forever.

Schedule

func Schedule(cur realm, targetName, data string, delay int64) string

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

Schedule creates a new timelocked action against a registered target. Only the target's owner may schedule. The delay must be at least the target's registered minimum and at most MaxDelay. Returns the action ID.

SetGuardian

func SetGuardian(cur realm, targetName string, guardian address)

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

SetGuardian changes (or clears, with "") the target's guardian. Owner only, and REFUSED while the target has pending actions: the guardian's veto power exists precisely to check the owner during a delay window, so the owner must not be able to strip it mid-window.

KNOWN LIMIT (documented, round-3 audit): the owner can cancel all pending actions, change the guardian, and reschedule — the price is a full fresh MinDelay on every rescheduled action, and every step emits an event (cancellations + the guardian change below), so observers always get MinDelay of warning under the new guardian regime. Guardians protect open windows, not the owner's future.

TransferTargetOwnership

func TransferTargetOwnership(cur realm, targetName string, newOwner address)

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

TransferTargetOwnership OFFERS a target to a new owner; the nominee must AcceptTargetOwnership to complete it (fix Y5: a one-step transfer let anyone fill a stranger's per-owner quota and dump pending obligations — with an attacker-chosen guardian — on an address that never asked). Owner only. Pass "" to clear a pending offer. Nothing changes hands until the nominee accepts.

Veto

func Veto(cur realm, actionID string) string

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

Veto cancels a pending action as the target's guardian. This is the guardian's whole power: it can stop a scheduled action during the delay window, never create or execute one. The record is reaped; history is the event.


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, chain/runtime/unsafe, 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 filemainnet/r/timelock_guardian/timelock_guardian.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