upkeep
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/upkeep on gno.land mainnet
realmfactory-applicationautomation-incentiveslive on mainnet
Pay whoever triggers the maintenance calls nobody owns.
Permissionless upkeep only happens if someone shows up, so this funds a bounded reward and the work gets done on time.
In detail curated
Keeper-incentive realm: a funder-financed reward pot pays whoever triggers a permissionless maintenance valve through it. PokeExpire cross-calls the live subscriptions.Expire and PokeExecute the live timelock_guardian.Execute -- genuine realm-to-realm crossing calls; the reward is credited only when the downstream realm accepts the valve call in the same transaction. Rewards are admin-set under a compile-time 20000ugnot cap, pull-claimed via feeledger. Factory benchmark #6 (realm-to-realm composition).
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/upkeep.Funded()' \
--remote https://rpc.gno.land:443Funded 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/upkeep"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
gno.land mainnet gnoland-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/upkeep |
|---|---|
| Namespace | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Deployed at height | 351,458 |
| Deploy transaction | 106b6898bcb6b3ace45af04f9b61063e8b066736272272961b9f2282903a8165 look it up on the RPC |
| Deployer | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Gas used | 40,329,970 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | gnomod.toml upkeep.gno upkeep_test.gno |
| Deployed bytes | upkeep.gno — 14,955 bytes |
| sha256 | 11e667df98cf514fa38d4382638b04cd4d5deffb9e9b36c8f188e16b83cc4377 |
| Realm address | g1hqqxt0nr43udjc7lx8hxz96zj2htf93mykw4n9 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/upkeep$download&file=upkeep.gno' | shasum -a 256Expected: 11e667df98cf514fa38d4382638b04cd4d5deffb9e9b36c8f188e16b83cc4377 — 14,955 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 17 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
AcceptAdminBalanceOfClaimClaimAllFundFundedHeldPokeExecutePokeExpirePokesPotRenderRewardForSetRewardSweepDenomTransferAdminUsersTotal
Overview
Package upkeep pays people to run the ecosystem's permissionless valves. The portfolio's realms deliberately expose maintenance entrypoints that anyone may call — subscriptions.Expire frees a lapsed subscription slot, timelock_guardian.Execute fires a matured timelocked action — because no slot's liveness may depend on an interested party showing up. This realm adds the missing economics: funders finance a reward pot, and whoever triggers a valve THROUGH this realm is credited a bounded reward, claimable by pull.
THE REALM-TO-REALM BOUNDARY, PRECISELY:
which calls which : upkeep -> subscriptions.Expire(cross, id) upkeep -> timelock_guardian.Execute(cross, id) why it is necessary : the reward must be conditioned on the valve actually firing. Only making the call from inside this realm ties "the valve fired" and "the reward is credited" into one atomic transaction; observing from outside cannot. caller identity : downstream sees cur.Previous() = THIS realm, not the poking EOA. Both valves are permissionless BY DESIGN and use the caller identity for nothing, so the intermediary changes no authorization outcome. This realm is not a deputy for any downstream authority — it holds none to confuse. authorization boundary: upstream, anyone may poke (the reward is the only thing at stake and the pot is the only source). Downstream, each valve enforces its own STATE conditions (grace elapsed, delay matured) exactly as it would for a direct caller. ordering : downstream call FIRST, reward accounting AFTER. A downstream abort therefore reverts the whole transaction before any pot or ledger mutation exists. failure behavior : any downstream panic (not expirable, too early, already executed, unknown id...) aborts this transaction. No partial state, no reward, by VM atomicity — not by cleanup code. atomicity assumptions : a Gno transaction is all-or-nothing across realm boundaries; there is no catch/recover anywhere on this path (and none may be added — recovery would break exactly this guarantee). value movement : none crosses the boundary. Both valves move no coins; the poke transaction must carry no coins (subscriptions' assertNoSend reads the ORIGIN envelope unconditionally — measured, not assumed). Rewards move only inside this realm's ledger, funded by explicit Fund transactions. downstream rejection : reward denied automatically — the abort is the denial. downstream trust : neither valve trusts nor validates the caller; both validate state. This realm symmetrically does not trust the downstream REPLY beyond "it did not abort". replay : enforced downstream. A second Expire on the same subscription aborts ("subscription is expired"); a second Execute aborts ("action already executed"). A poker cannot be paid twice for one valve event. adversarial callers : an EOA or realm poking with bogus ids, premature targets, or replays hits a downstream abort and pays its own gas. The remaining economic edge — manufacturing expirable state to farm rewards — differs per task. sub_expire: every farmed subscription permanently locks a sub-record storage deposit of roughly 30,000ugnot at the observed 100ugnot/byte rate (Expire flips status; it does not free the record), which exceeds MaxReward before price and gas — loss-making at any legal setting. timelock_execute: a farmed Action record is small and its deposit can sit below the cap, so farming resistance there rests on the admin keeping the setting below the measured cycle cost. Funders trust the cap AND the admin's reward policy, not the cap alone. The pot is a donation either way — farming can drain it, never third-party balances.
Rewards default to 0 per task; the admin sets them within the compile-time MaxReward. The pot only moves down via successful pokes and only up via Fund. Conservation: Held == pot + UsersTotal (+ out-of-band surplus, recoverable above that reserve by SweepDenom). The ledger's fee cap is 0 — no fee exists anywhere in this realm.
Imports
chainchain/runtime/unsafegno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coiniogno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledgergno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptionsgno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardianstrconv
Constants and variables
Task identifiers — the two supported valves.
const (
TaskSubExpire = "sub_expire"
TaskTimelockExecute = "timelock_execute"
)
Denom is the only asset this realm accepts.
const Denom = "ugnot"
MaxReward is the compile-time ceiling on the per-poke reward: 20,000ugnot (0.02 GNOT). It bounds what any single poke can extract from the pot. For sub_expire it also defeats farming outright: one manufactured expirable subscription permanently locks a sub-record deposit of ~30,000ugnot at the observed 100ugnot/byte rate — already above the cap before price and gas. For timelock_execute the farmed Action record is smaller and its deposit can sit below the cap, so there the admin's setting, kept below the measured farm-cycle cost, is what deters farming. Funders trust the cap and the admin's policy together; the pot they fund is an explicit donation either way.
const MaxReward = int64(20000)
Functions
AcceptAdmin
func AcceptAdmin(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asAcceptAdmin(cross(cur), ...).
AcceptAdmin completes the handover; only the staged successor may. The sweeper role moves with the admin.
BalanceOf
func BalanceOf(a address) int64
Claim
func Claim(cur realm, amount int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaim(cross(cur), ...).
Claim sends amount ugnot of the caller's earned balance back to the caller.
ClaimAll
func ClaimAll(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaimAll(cross(cur), ...).
ClaimAll sends the caller's entire earned balance back to the caller.
Fund
func Fund(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asFund(cross(cur), ...).
Fund adds the attached coins to the reward pot. Anyone may fund; funding is a donation to ecosystem maintenance and is not refundable.
Funded
func Funded() int64
Held
func Held() int64
PokeExecute
func PokeExecute(cur realm, actionID string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPokeExecute(cross(cur), ...).
PokeExecute triggers timelock_guardian.Execute(actionID) through this realm and credits the caller the timelock_execute reward. The guardian decides whether the action is executable; its abort is the authorization.
PokeExpire
func PokeExpire(cur realm, subID int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPokeExpire(cross(cur), ...).
PokeExpire triggers subscriptions.Expire(subID) through this realm and credits the caller the sub_expire reward. The downstream realm decides whether the subscription is expirable; its abort is the authorization. The transaction must attach no coins (the downstream realm checks the origin envelope).
Pokes
func Pokes() int64
Pot
func Pot() int64
Render
func Render(path string) string
RewardFor
func RewardFor(task string) int64
SetReward
func SetReward(cur realm, task string, amount int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetReward(cross(cur), ...).
SetReward configures the per-poke reward for a task, bounded by MaxReward. Admin only. Zero disables the task.
SweepDenom
func SweepDenom(cur realm, denom string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSweepDenom(cross(cur), ...).
SweepDenom recovers out-of-band coins to the sweeper. For the pot denom the reserve is pot + earned balances — both structurally unreachable. Sweeper only.
TransferAdmin
func TransferAdmin(cur realm, successor address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asTransferAdmin(cross(cur), ...).
TransferAdmin stages a two-step admin handover.
UsersTotal
func UsersTotal() int64
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, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions, gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian, strconv |
|---|---|
| First-party dependencies | gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/subscriptions, gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/timelock_guardian |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- No state-changing call was made against this realm on gnoland-1. The live evidence is byte custody plus a successful vm/qrender; every state transition rests on the committed test suite (re-run under the mainnet pin on deploy day) and the fresh mainnet-candidate audit plus its independent adversarial challenge pass, not on mainnet behaviour ([an unpublished record], Live verification).
- storage_bytes and storage_deposit are null because gnoland-1 emits no /tm.StorageDepositEvent ON ADDPKG -- a scope that is load-bearing, because it DOES emit one on MsgCall (measured 2026-10-17, AL62); see the chain's storage_deposit_events field. The deposit actually charged was 2,862,900 ugnot, measured by differencing auth/accounts across the deploy heights and recorded in [an unpublished record], Cost; it is not chain-attested per package by this manifest.
- The deployed file set includes the _test.gno file. The pearl-1 deployment of the same realm carried only the source and gnomod.toml, so the mainnet package is three files where pearl's is two -- the test file is stored on chain and paid for in the deposit. It declares no crossing function and so adds no callable surface, but it is part of the immutable package.
- Deployed inert by design: pot 0, both rewards 0. Arming it (Fund / SetReward) is a separate operator decision not authorized by the deployment record. Fund on an unrewarded pot is a valid, non-refundable donation -- render the realm before funding.
- The deployed header's farming figures are superseded-conservative: it claims a ~30,000ugnot sub-record deposit and that a farmed Action deposit 'can sit below the cap'. Measured from pearl-1 storage events (.evidence/upkeep_storage_measurement_pearl.json): permanent residuals are ~655,800ugnot (sub_expire) and ~308,500ugnot (timelock_execute) -- 32.8x and 15.4x the 20,000 cap, so the cap alone defeats farming for BOTH task types. Every header error over-warns; no decision made on the wrong text is less safe than on the true one. The figures transfer to gnoland-1 only as bytes x price (both chains 100ugnot/B on deploy day, GovDAO-mutable). A clause stood here saying 'gnoland-1 emits no storage events so they cannot be re-measured here' and it is MEASURED FALSE in the one direction that matters: these residuals are CALL residuals (sub_expire, timelock_execute), and gnoland-1 DOES emit /tm.StorageDepositEvent on MsgCall -- measured 2026-10-17, 45 call txs probed, six emitting. What gnoland-1 emits nothing on is ADDPKG. So should upkeep ever be deployed and exercised there, these figures are re-measurable from the calls' own events and must be measured that way rather than balance-derived; the un-measurability was a FALSE CAUSE, not a chain limit. See docs/MAINNET_READINESS.md Amendments AL35 and AL62.
- Honest-keeper economics at the corrected figures: rewards in (11,000, 20,000] pay tightly-fee-sized keepers; the storage system additionally pays the poke's tx signer the downstream unlock refund (99,800 / 62,100ugnot measured on pearl), and a poker fronts a ~105,800-203,400ugnot storage float on upkeep's own growth until ClaimAll returns it (tx needs --max-deposit headroom).
- A realm-typed poker earns to its own address and needs its own crossing path to Claim/ClaimAll, or the earnings strand -- same caveat as the pearl twin and the subscriptions provider path.
Source
| Source file | mainnet/r/upkeep/upkeep.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes | yes — byte-identical |