Realm on pearl-1
timelock_guardian
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/timelock_guardian
realmpipeline-applicationsecurity-delayed-execution
Delayed-execution guardian: queue, delay window, execute or cancel. GitHub-pipeline application #2.
Identity
| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/timelock_guardian |
|---|---|
| Kind | realm (/r/) |
| Chain | pearl-1 |
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Realm address | g1w06mqv4h53vtfgfeyz2eglplpwfpytcqqqjwkh derived, never confirmed against the realm |
Provenance
chain-attested| Deployed at height | 602,213 |
|---|---|
| Deploy transaction | 6db3f74c764523f8ea1d82f7cd419639a8bb597494b170a8d2ea56bc8779efbb look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 38,920,796 |
| Storage | 37,342 bytes, deposit 3734200ugnot |
| Files on chain | gnomod.toml timelock_guardian.gno |
| Deployed bytes | timelock_guardian.gno — 24,983 bytes |
| sha256 | 72ca7049496257e6fb00216aa027736fa90d41c1dbc91cf8059fc8fd29080f07 |
Do not take the hash above on trust. $download returns the bytes pearl-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://pearl.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/timelock_guardian$download&file=timelock_guardian.gno' | shasum -a 256Expected: 72ca7049496257e6fb00216aa027736fa90d41c1dbc91cf8059fc8fd29080f07 — 24,983 bytes. This was checked for all 21 packages while building this site's architecture record; every one matched. Use curl: pearl's edge answers 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 catalog/DISCOVERY_APIDOCS.md §2.4.
Imports
chainchain/runtime/unsafestrconvstringstime
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 field | Type | Doc |
|---|---|---|
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 |
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 field | Type | Doc |
|---|---|---|
Name | string | |
Owner | address | |
Guardian | address | empty = no guardian |
MinDelay | int64 | seconds; every action for this target waits at least this |
PendingOwner | address | 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). |
Functions
AcceptTargetOwnership
func AcceptTargetOwnership(cur realm, targetName string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asAcceptTargetOwnership(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 asCancel(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 asExecute(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 asExpire(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 asRegisterTarget(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 asSchedule(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 asSetGuardian(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 asTransferTargetOwnership(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 asVeto(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| Imports | chain, chain/runtime/unsafe, strconv, strings, time |
|---|---|
| First-party dependencies | none |
| Used by | none |
Known limitations
curatedThe manifest records no limitation for this package. That is an absence of a recorded caveat, not a proof that none exists — the deployment record below is the fuller account, and it always carries its own "what was NOT verified live" section.
Source and records
| Source file | pearl/r/timelock_guardian/timelock_guardian.gno at commit 6a510c665a53 in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes | yes — byte-identical |
| Upstream repository | https://github.com/SillyZir/timelock_guardian |
| Deployed from commit | bb05e64898967125b900a9275ca84229bff5e007 |
| Records | catalog/applications.md#timelock_guardianpearl/DEPLOYMENT.md |