Gno.land · Smart Contracts · Infrastructure
On-chain timelock for critical contract operations on Gno.land.
When a contract admin executes a critical action — changing parameters, upgrading logic, moving treasury funds — it takes effect immediately. There is no window for anyone to review the change, raise objections, or exit the system before the damage is done.
This is how rug pulls work. This is how governance bypasses happen. This is how honest mistakes become irreversible.
The core issue: immediate execution of sensitive operations in systems that require trust.
timelock_guardian introduces a mandatory delay between scheduling an action and executing it. Every critical operation goes through three steps:
- Register — bind a target name to an owner, an enforced minimum delay, and an optional guardian
- Schedule — the target's owner declares what they intend to do, with at least the registered delay
- Wait — the action is publicly visible but locked for the delay period
- Execute — after the delay (and within a 30-day grace window), anyone can trigger the action
During the waiting period, the community can review the scheduled action. If it's malicious or wrong, the creator or target owner can cancel it — or the guardian can veto it.
RegisterTarget(cross, "treasury", 86400, guardianAddr)
Schedule(cross, "treasury", "transfer 1000000ugnot to g1abc...", 86400)
// locked for 24 hours, publicly visible; guardian may veto during the wait
// after 24h (and within 30 days): Execute(cross, "action_1")
- DAOs can enforce cooling periods on treasury operations
- Protocol teams can prove they won't rug — scheduled upgrades are visible before they happen
- Users get exit time when they see a pending action they disagree with
- Multisigs can add timelocks on top of approval workflows
- Auditors can verify that all sensitive operations go through a delay
Timelocks are the simplest trust-minimization primitive. They don't prevent bad actions — they prevent surprise bad actions.
Targets are registered once, binding the name to the only address allowed to schedule against it and to an enforced minimum delay (floor: 60 seconds, cap: 10 years). Actions are stored with ScheduledAt, ExecuteAfter, and ExpiresAt timestamps; the realm uses time.Now() to enforce the delay and the grace window at execution time.
Anyone can execute a ready action. The timelock is the protection mechanism, not the executor's identity. This prevents a single point of failure where only the creator can trigger execution. A ready action that nobody executes within the 30-day grace window expires and can never execute.
Consumers integrating with the timelock check IsExecuted(actionID): a true result attests the target's registered owner scheduled it, it waited at least the registered minimum delay, no guardian vetoed it, and it executed within its grace window.
Only the creator can cancel. This prevents griefing where someone cancels another user's scheduled action.
Execution is one-shot. Once executed, an action is permanently marked and cannot be replayed.
Schedule(cross, "treasury", "transfer 500000ugnot to g1grant_recipient...", 86400)
// returns: "action_1"
GetPending()
// returns: "action_1, action_3"
IsReady("action_1")
// returns: true (after delay has passed)
Execute(cross, "action_1")
// returns: "executed: action_1"
// panics if delay hasn't elapsed
Cancel(cross, "action_1")
// returns: "cancelled: action_1"
GetAction("action_1")
// ID: action_1
// Creator: g1admin...
// Target: treasury
// Data: transfer 500000ugnot to g1grant_recipient...
// Scheduled: 2025-01-15T10:00:00Z
// Execute after: 2025-01-16T10:00:00Z
// Status: ready
import "gno.land/r/timelock_guardian"
func ProposeUpgrade(cross realm, newCodeHash string) string {
// Schedule the upgrade with a 48-hour delay
return timelock_guardian.Schedule(
cross,
"gno.land/r/my_protocol",
"upgrade to " + newCodeHash,
172800, // 48 hours
)
}
func ExecuteUpgrade(cross realm, actionID string) {
timelock_guardian.Execute(cross, actionID)
// proceed with actual upgrade logic
}| Function | Access | Description |
|---|---|---|
Schedule(cross, target, data, delay) |
Anyone | Create a timelocked action. Caller is creator. |
Execute(cross, actionID) |
Anyone | Execute after delay. Panics if too early. |
Cancel(cross, actionID) |
Creator | Cancel a pending action. |
GetAction(actionID) |
Anyone | Full details of an action. |
GetPending() |
Anyone | List all pending action IDs. |
IsReady(actionID) |
Anyone | Check if delay has elapsed. |
Render(path) |
Anyone | Markdown overview of all actions by status. |
gnokey query vm/qeval --data 'gno.land/r/timelock_guardian.GetPending()' --remote <rpc>
gnokey query vm/qeval --data 'gno.land/r/timelock_guardian.IsReady("action_1")' --remote <rpc>
gnokey query vm/qeval --data 'gno.land/r/timelock_guardian.GetAction("action_1")' --remote <rpc>
Visit /r/timelock_guardian on any Gno.land node to see all pending, executed, and cancelled actions.
| Realm | Layer |
|---|---|
| fee_split | Revenue & value flow |
| permission_registry | Access control |
| service_registry | Discovery |
| upgrade_registry | Upgrade tracking |
| timelock_guardian | Security |