crowdfund
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund on gno.land mainnetgno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund on onyx-1 testnet
realmfactory-applicationvalue-distributionlive on mainnetlive on onyx-1
Raise funds all-or-nothing against a goal and a deadline.
If the goal is missed every backer takes back exactly what they pledged, fee-free, because the realm held the coins all along.
In detail curated
gno.land mainnet
Permissionless all-or-nothing crowdfunding in GNOT, the first mainnet realm in this portfolio to invite third-party funds. Campaigns with a goal and a block deadline; backers pledge real coins the realm holds; hit the goal and the creator collects minus a 1% success fee (hard cap 5% by construction), miss it and every backer reclaims exactly their pledge, fee-free. Settlement is one duebook deferral per campaign; conservation H == B + F + S tracked O(1); no admin, every term a compile-time constant. FAITHFUL re-namespacing of the onyx-1 original (factory build #10, Group 6); deployed at genesis with NO state-changing call yet made against the live realm.
onyx-1 testnet
Permissionless all-or-nothing crowdfunding in GNOT: campaigns with a goal and a block deadline; backers pledge real coins the realm holds; hit the goal and the creator collects minus a 1% success fee (hard-capped at 5% by the feeledger the realm is constructed with), miss it and every backer reclaims exactly their pledge, fee-free. Settlement is one duebook deferral per campaign (CreatorClaim/SettleFailed via Claim, CreatorCancel via Cancel, Lapse via Expire), so a campaign settles at most once structurally. Conservation H == B + F + S tracked O(1). No admin: every term is a compile-time constant. Application-factory build #10, Group 6 -- the first application on onyx-1 and the first in this portfolio deployed to a chain where nothing of ours previously existed.
One description per chain, because this application is deployed on more than one chain and each account was written for its own deployment. The programs differ only where the chain's namespace appears in the source — proved, not asserted, under Deployments below — so where the accounts differ they differ in emphasis and in which chain's history they describe, not in what the code does.
campaign state read live in your browser, on mainnet and the free trial chain
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
on gno.land mainnet
gnokey query vm/qeval \
--data 'gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund.FeesAccrued()' \
--remote https://rpc.gno.land:443on onyx-1 testnet
gnokey query vm/qeval \
--data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund.FeesAccrued()' \
--remote https://rpc.onyx.testnets.gno.land:443FeesAccrued 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 11 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
on gno.land mainnet
import "gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund"on onyx-1 testnet
import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund"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
Deployed on 2 chains, and the bytes are NOT identical — each block below carries its own digest. The difference is the namespace: an import path contains the address that deployed the package it names, so the same application built for two chains differs on the 5 lines where that address appears, and nowhere else. The build proves that rather than asserting it — it substitutes each chain's own namespace out of its committed source and requires what remains to be byte-equal, and refuses to publish a shared page if it is not. Differing lines: 141, 142, 143, 149, 169.
gno.land mainnet gnoland-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund |
|---|---|
| Namespace | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Deployed at height | 490,126 |
| Deploy transaction | 5f5c7efcd265d417098f8a713edef794ccebc06d9ed53fb0c2acbf8f7e14fee3 look it up on the RPC |
| Deployer | g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39 |
| Gas used | 85,219,496 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | crowdfund.gno crowdfund_test.gno gnomod.toml |
| Deployed bytes | crowdfund.gno — 36,926 bytes |
| sha256 | 596bb9f578cd87b26bfab94689a3412b800e3e68af056c01e7b19f6712c2be87 |
| Realm address | g1zkh7qlde5xqn72jt3jxu7xv4jgu06uf5v6vagx confirmed against the realm itself |
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/crowdfund$download&file=crowdfund.gno' | shasum -a 256Expected: 596bb9f578cd87b26bfab94689a3412b800e3e68af056c01e7b19f6712c2be87 — 36,926 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
onyx-1 testnet onyx-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 96,831 |
| Deploy transaction | badbfb277af37ec0fdfc4024b3d996329e7aaa8b48972f79f245f5622d8b2b61 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 85,218,902 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | crowdfund.gno crowdfund_test.gno gnomod.toml |
| Deployed bytes | crowdfund.gno — 36,926 bytes |
| sha256 | bcd6bd363e26b613bf9d98a239d8c3a06e9bae288495957426707d91b64dacd6 |
| Realm address | g143g78xtvkm0cp433x5e88rxm863u7r0yg4nxq4 confirmed against the realm itself |
Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://onyx.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund$download&file=crowdfund.gno' | shasum -a 256Expected: bcd6bd363e26b613bf9d98a239d8c3a06e9bae288495957426707d91b64dacd6 — 36,926 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 26 exported functions.
Read from vm/qdoc on gnoland-1. It describes the onyx-1 testnet deployment as well: the bytes are NOT identical, and the reason they still answer for each other is narrower and worth stating. The 2 deployments differ only where the chain's own namespace appears, which this build proves by substitution — mostly in import paths, which are not part of a package's public surface. But 2 of those differences are an exported constant, so read the value below as gnoland-1's value and not as the package's. The names and types are identical on every chain, so the signatures and doc comments do carry across; these values do not. The API is the only thing that carries across; every provenance fact above is per-chain.
Per-chain constants, from each chain's own committed source:
RealmPath(const, line 149)- gno.land mainnet —
const RealmPath = "/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund" - onyx-1 testnet —
const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund"
- gno.land mainnet —
FeeCollector(const, line 169)- gno.land mainnet —
const FeeCollector = address("g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39") - onyx-1 testnet —
const FeeCollector = address("g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3")
- gno.land mainnet —
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it — against the chain you pick. The two are separate deployments with separate state, and a call to one does nothing to the other:
gno.land mainnet gnoland-1
CampaignMemoCampaignTitleCreatorCancelCreatorClaimFeesAccruedHeightHeldIsRefundableLapseLaunchLiabilitiesNumCampaignsOpenCampaignsOpenOfPledgePledgeOfPruneRefundRenderSettleFailedStatusSurplusSweepSurplusTotalBackedUnpledgeWithdrawFees
onyx-1 testnet onyx-1
CampaignMemoCampaignTitleCreatorCancelCreatorClaimFeesAccruedHeightHeldIsRefundableLapseLaunchLiabilitiesNumCampaignsOpenCampaignsOpenOfPledgePledgeOfPruneRefundRenderSettleFailedStatusSurplusSweepSurplusTotalBackedUnpledgeWithdrawFees
Overview
Realm crowdfund is permissionless all-or-nothing crowdfunding in GNOT: a creator opens a campaign with a funding goal and a block deadline, backers pledge real coins that the realm holds, and settlement is conditional — if the goal is reached by the deadline the creator may collect the pot (minus a bounded success fee), and if it is not, every backer reclaims exactly what they pledged, fee-free.
WHY THIS EXISTS (see DISCOVERY.md): at the heights recorded there, nothing on onyx-1 holds backer coins against a goal-and-deadline condition at all, and the one live crowdfunding realm in the wider ecosystem (mainnet r/moul/x/daily/crowdfund, read in full) is explicitly accounting-only — its own doc says "No real coin moves". The one real-coin implementation found anywhere ran on retired pearl-1 under a third-party namespace and is unreachable. This realm's delta is stated at that size and no larger: it is the assurance-contract mechanic — conditional custody with a fee-free refund path — done with this portfolio's live-validated custody discipline, not a new idea.
COMPOSITION: coin movement is delegated to coinio, fee arithmetic and the fee pot to feeledger, settlement timing and exactly-once authorization to duebook, ordered storage to p/nt/avl/v0, and free-text render output to the ecosystem sanitizer p/nt/markdown/sanitize/v0. This realm owns only the campaign state machine and the conservation bookkeeping.
THE SETTLEMENT IS ONE DEFERRAL. Launch schedules exactly one duebook deferral per campaign — owner: the creator, due: the deadline, expiry: the end of the claim window — and every terminal transition is one of the ways that deferral can be consumed:
CreatorClaim -> book.Claim (due, goal met, creator collects) SettleFailed -> book.Claim (due, goal UNMET, anyone; duebook leaves claim policy to its consumer) CreatorCancel -> book.Cancel (owner renounces, any time while open) Lapse -> book.Expire (anyone, once the claim window is over)
Because duebook consumes a deferral before returning and never reuses an ID, a campaign can settle AT MOST ONCE, structurally — double-claim is not guarded against, it is unrepresentable. The book's open- deferral cap is likewise this realm's cap on open campaigns.
LIFECYCLE (stored state in brackets; "succeeded"/"failed" are derived views of an open campaign after its deadline, not stored states):
Launch (anyone) : opens [funding]; schedules the deferral. Pledge (backer, +send) : while height < deadline. Real coins. Unpledge (backer) : full own pledge back, while height < deadline. All-or-nothing holds: nothing is locked until the deadline passes. CreatorClaim (creator) : height in [deadline, claimDeadline), raised >= goal -> [paid]. Pays raised - fee to the creator, accrues the fee. The ONLY transition that charges anything. SettleFailed (ANYONE) : height >= deadline, raised < goal -> [failed]. Makes the failure terminal and frees the campaign's slots immediately; refunds were already open on this path. CreatorCancel (creator) : while the deferral is open -> [cancelled]. Refunds open. A creator who cancels after succeeding is renouncing the pot. Lapse (ANYONE) : height >= claimDeadline -> [lapsed]. Refunds open. The permissionless valve: a creator who never collects cannot strand backer money, and a dead campaign cannot hold its duebook slot forever. Refund (backer) : own pledge back, fee-free, whenever the campaign is [cancelled], [lapsed], or open past its deadline with raised < goal. Prune (ANYONE) : removes a settled, fully-drained campaign record. The freed storage deposit is refunded by the chain to the CALLER, which is the incentive to call it. WithdrawFees (ANYONE) : pays the accrued fee pot to the compile-time FeeCollector. Permissionless because the destination is fixed. SweepSurplus (ANYONE) : out-of-band coins to the FeeCollector, never touching tracked liabilities.
REFUNDS NEVER RACE THE CLAIM: while a succeeded campaign is inside its claim window, Refund refuses — the pot is the creator's to collect. The moment the window ends (Lapse) or the creator renounces (Cancel), and at any time after a failed deadline, Refund pays each backer exactly their pledge. There is no partial outcome and no fee on any refund path, so a backer's worst case is their money back.
THE FEE, precisely: feeBps is snapshotted at Launch from the compile-time SuccessFeeBps, so a campaign's terms are fixed when it is created and visible in CampaignInfo from that moment. The fee is charged ONCE, at CreatorClaim, on the raised amount, with feeledger's floor rounding (rounding favors the creator). The ledger is constructed with the compile-time MaxFeeBps cap, so a fee above the cap is refused by the primitive, not by a check in this file. There is no fee on pledges, no fee on refunds, and no fee on failure.
THERE IS NO ADMIN. The fee rate, the fee collector, every bound and every window are compile-time constants; no address can change terms, pause the realm, or touch a campaign it does not own. The deployer has no privilege of any kind after deployment.
MONETARY INVARIANT (conservation): let H be the ugnot held at this realm's address, B the sum of raised amounts across all stored campaigns (tracked O(1) as totalBacked), F the accrued fee pot, and S >= 0 the out-of-band surplus:
H == B + F + S
Every transition moves value between exactly two terms inside one transaction: Pledge raises H and B together (coinio.Receive is the receipt-guaranteed shape); Unpledge and Refund debit B before the identical amount leaves H (checks-effects-interactions); CreatorClaim moves one campaign's raised out of B, splits it into a creator payout (leaves H) and a fee (enters F); WithdrawFees debits F before the payout; any panic aborts the whole transaction; this realm never issues or removes coins.
ONLY GNOT IS ACCEPTED: Pledge rejects any envelope that is not exactly one positive ugnot coin (coinio.Receive). Every other entrypoint rejects attached coins outright rather than converting them into sweepable surplus.
AUTHORIZATION: every identity is derived from the crossing entrypoint's cur.Previous().Address(). No function takes a caller identity as a parameter, so pledging as another backer, claiming another creator's campaign, or refunding another backer's pledge is impossible by construction.
STORAGE: a backer's pledge entry is paid for by the backer's own transaction deposit; a campaign record by the creator's. CreatorClaim drops the whole pledge table in one assignment, and Prune removes the settled record — both free storage, and the chain refunds freed deposits to the transaction that frees them.
Imports
chainchain/runtimechain/runtime/unsafegno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coiniogno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/duebookgno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledgergno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0strconv
Constants and variables
MinDurationBlocks and MaxDurationBlocks bound a campaign's funding period, enforced structurally: they are the duebook's minDelay and maxDelay, so an out-of-range duration is refused by the primitive. At onyx-1's roughly 3-5s blocks, ~1,200 blocks is on the order of an hour or two, and ~1,300,000 blocks is on the order of 45-75 days.
const (
MinDurationBlocks = int64(1_200)
MaxDurationBlocks = int64(1_300_000)
)
Input bounds. Both fields are free text and are sanitized before reaching markdown.
const (
MaxTitleLen = 100
MaxMemoLen = 256
)
ClaimWindowBlocks is how long after the deadline a successful creator may collect before the campaign becomes permissionlessly lapsable and the pot refundable. It is every campaign's deferral ttl.
const ClaimWindowBlocks = int64(650_000)
Denom is the only asset this realm holds.
const Denom = "ugnot"
FeeCollector receives withdrawn fees and swept surplus. Compile-time constant: there is no setter and no transfer path. It is the deploying namespace's address — stated plainly: the operator of this realm.
const FeeCollector = address("g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39")
MaxFeeBps is the hard ceiling on any success fee this realm could ever charge, enforced by the feeledger the realm is constructed with — a feeBps above it is refused by the primitive's own validation, not by a check in this file. 500 bps = 5%.
const MaxFeeBps = int64(500)
MaxOpenCampaigns caps simultaneously unsettled campaigns, via the duebook's own open-deferral cap. The AVAILABILITY BOUND this implies is documented rather than hidden: Launch is permissionless, so the cap is exhaustible in principle. What each unsettled slot costs an attacker is the mitigation — a failed campaign is permissionlessly settleable (SettleFailed) the moment its deadline passes, so holding a slot past its deadline requires MEETING THE GOAL, i.e. locking at least MinGoal of real capital per slot for the claim window. Filling the whole table therefore costs >= 1,024 GNOT locked for ~a month per cycle, against a realm whose existing campaigns keep working regardless. Accepted for a testnet deployment and recorded in the deployment record (audit finding Y1).
const MaxOpenCampaigns = 1024
MaxOpenPerCreator bounds how much of the global cap one creator can occupy (permission_registry R1 precedent: an uncapped per-principal count lets one key monopolize a shared bound).
const MaxOpenPerCreator = int64(8)
MaxRenderRows bounds the campaign list on the index page.
const MaxRenderRows = 20
MinGoal keeps dust campaigns out: 1 GNOT.
const MinGoal = int64(1_000_000)
MinPledge bounds pledge-table growth per GNOT of hostile capital (audit open question 1): at 0.01 GNOT per entry, bloating one campaign's table to even 10,000 entries costs 100 refundable-but- locked GNOT plus gas. Honest micro-backing survives: 0.01 GNOT is two orders of magnitude below MinGoal.
const MinPledge = int64(10_000)
RealmPath is this realm's own path, used to build Render links.
const RealmPath = "/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund"
SuccessFeeBps is the fee actually snapshotted into every campaign at Launch: 100 bps = 1%, charged once, on CreatorClaim, on the raised amount, floor-rounded in the creator's favor. fee_split precedent: the most conservative live fee in this portfolio is also 1%.
const SuccessFeeBps = int64(100)
Functions
CampaignMemo
func CampaignMemo(id int64) string
CampaignMemo returns a campaign's raw memo (same caveat as the title).
CampaignTitle
func CampaignTitle(id int64) string
CampaignTitle returns a campaign's raw title (unsanitized: callers rendering it into markdown must sanitize, as this realm's Render does).
CreatorCancel
func CreatorCancel(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCreatorCancel(cross(cur), ...).
CreatorCancel settles the caller's own campaign as cancelled and opens refunds. Permitted at any time while the settlement deferral is open — during funding, and equally after a successful deadline (renouncing the pot). The duebook Cancel is owner-gated and consumes the deferral.
CreatorClaim
func CreatorClaim(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCreatorClaim(cross(cur), ...).
CreatorClaim collects a successful campaign: creator only, from the deadline until the claim window closes, and only with the goal reached. Pays raised minus the snapshotted fee to the creator and accrues the fee. The duebook Claim consumes the campaign's deferral, so this can succeed at most once per campaign, ever.
FeesAccrued
func FeesAccrued() int64
FeesAccrued returns the F term: charged, not yet withdrawn.
Height
func Height() int64
Height returns the current chain height, the clock every deadline is measured against.
Held
func Held() int64
Held returns H: the ugnot actually at this realm's address.
IsRefundable
func IsRefundable(id int64) bool
IsRefundable reports whether Refund would currently accept this campaign (for any backer with a live pledge).
Lapse
func Lapse(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asLapse(cross(cur), ...).
Lapse settles a campaign whose claim window has closed. Anyone may call it — a lapsed campaign's pot belongs to its backers, and the caller is doing them (and the duebook's open-slot budget) a service. The duebook Expire refuses while the window is still open.
Launch
func Launch(cur realm, title, memo string, goal, durationBlocks int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asLaunch(cross(cur), ...).
Launch opens a campaign and returns its id. Anyone may launch; the caller becomes the creator. The fee terms (SuccessFeeBps at this moment — a compile-time constant, so always SuccessFeeBps) are snapshotted into the campaign and are fixed from here on. The funding duration is given in blocks and must be within [MinDurationBlocks, MaxDurationBlocks] — enforced by the duebook.
Liabilities
func Liabilities() int64
Liabilities returns B + F — everything this realm owes.
NumCampaigns
func NumCampaigns() int64
NumCampaigns returns how many campaigns have ever been launched. Prune removes records but never reuses ids.
OpenCampaigns
func OpenCampaigns() int64
OpenCampaigns returns how many campaigns are currently unsettled.
OpenOf
func OpenOf(addr address) int64
OpenOf returns how many unsettled campaigns addr currently has.
Pledge
func Pledge(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPledge(cross(cur), ...).
Pledge backs a campaign with the attached coins. Direct EOA calls with -send only (the receipt-guaranteed shape); exactly one positive ugnot coin. Open while height < deadline.
PledgeOf
func PledgeOf(id int64, addr address) int64
PledgeOf returns addr's live pledge in a campaign (0 if none, and 0 for settled campaigns whose pledge table has been dropped).
Prune
func Prune(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPrune(cross(cur), ...).
Prune removes a settled campaign record that holds no coins, reclaiming its storage. Anyone may call it: the chain refunds the freed storage deposit to the pruning transaction, which is the incentive. A record with raised > 0 still carries backer entitlements and is not prunable.
Refund
func Refund(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asRefund(cross(cur), ...).
Refund returns the caller's entire pledge from a campaign that will never pay its creator: cancelled, lapsed, or past its deadline with the goal unmet. Always whole, always fee-free. While a succeeded campaign is inside its claim window the pot is the creator's to collect, so Refund refuses — if the creator never collects, Lapse reopens this path.
Render
func Render(path string) string
Render shows the realm at "" and a campaign page at "<id>". Titles and memos are free text and pass through the ecosystem sanitizer before hitting markdown.
SettleFailed
func SettleFailed(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSettleFailed(cross(cur), ...).
SettleFailed settles a campaign that reached its deadline with the goal unmet. Anyone may call it — there is nothing to steer: refunds were already open on this path, so the only effects are making the failure terminal and freeing the campaign's duebook slot (and its creator's cap slot) immediately instead of at the end of the claim window. This is what makes slot-squatting with unfunded campaigns pointless: holding a slot past the deadline requires meeting the goal, i.e. real locked capital.
The deferral is consumed with the duebook's Claim under this realm's policy (goal unmet), which the primitive explicitly leaves to its consumer; the timing verdict (not due before the deadline) is the primitive's own.
Status
func Status(id int64) string
Status returns the human verdict for a campaign right now: "funding", "succeeded (awaiting creator claim)", "failed (refunds open)", "paid", "cancelled", or "lapsed".
Surplus
func Surplus() int64
Surplus returns H - (B + F) — out-of-band coins, sweepable.
SweepSurplus
func SweepSurplus(cur realm, denom string) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSweepSurplus(cross(cur), ...).
SweepSurplus sends out-of-band coins to the FeeCollector: for the pot denom, everything above tracked liabilities; for any foreign denom, the full balance. Tracked backer money and the fee pot are untouchable by construction (coinio.Sweep refuses to dip below the reserve). Anyone may call it.
TotalBacked
func TotalBacked() int64
TotalBacked returns the B term: coins held for campaigns.
Unpledge
func Unpledge(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asUnpledge(cross(cur), ...).
Unpledge returns the caller's entire pledge while funding is still open. All-or-nothing means nothing is committed before the deadline, so backing out is always whole and always free.
WithdrawFees
func WithdrawFees(cur realm) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdrawFees(cross(cur), ...).
WithdrawFees pays the entire accrued fee pot to the compile-time FeeCollector. Anyone may call it: the destination is fixed, so there is nothing for a caller to steer.
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
A first-party dependency is imported from the SAME chain's namespace, so each deployment above depends on that chain's copy — the paths differ even where the code does not.
chain-attested| Imports | gnoland-1: chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/duebook, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconvonyx-1: chain, chain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebook, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv |
|---|---|
| First-party dependencies | gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/duebook, gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/feeledger, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebook, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curatedgno.land mainnet
- The deadline-crossing settlement paths WERE exercised live on 2026-10-01 (phase 2, [an unpublished record]): CreatorClaim with the first real 1% fee (10,000 ugnot on 1,000,000, tx ee51f24a...83ba5e), WithdrawFees to the fixed collector triggered by a non-collector key, SettleFailed by a non-creator, and the fee-free post-failure refund. What remains tests-only on this chain is the Lapse SUCCESS path, which needs a 650,000-block claim window to elapse (~several weeks); its refusal while the window is open was asserted live in phase 1, and the success path is covered by TestClaimTimingIsTheDuebooksVerdict and TestLapseRefusesWhileClaimWindowIsOpen.
- Audit Y1 residual, documented in-source at MaxOpenCampaigns: the 1,024-slot open-campaign table is exhaustible by locking >= 1,024 GNOT for a ~claim-window cycle, and the SettleFailed mitigation assumes a keeper actually calls it. Existing campaigns keep working regardless.
- Launch-slot exhaustion re-scoped at mainnet strength: the per-creator cap is Sybil-bypassable and a zero-pledge campaign holds a duebook slot for its whole duration at gas + refundable-deposit cost only -- the in-source mitigation claim prices only the post-deadline half, and the prior acceptance was testnet-scoped while this realm is live here. Availability-only; no funds at risk. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].
onyx-1 testnet
- The deadline-crossing settlement paths WERE exercised live on 2026-10-01 (phase 2, [an unpublished record]): CreatorClaim with the first real 1% fee (10,000 ugnot on 1,000,000, tx ee51f24a...83ba5e), WithdrawFees to the fixed collector triggered by a non-collector key, SettleFailed by a non-creator, and the fee-free post-failure refund. What remains tests-only on this chain is the Lapse SUCCESS path, which needs a 650,000-block claim window to elapse (~several weeks); its refusal while the window is open was asserted live in phase 1, and the success path is covered by TestClaimTimingIsTheDuebooksVerdict and TestLapseRefusesWhileClaimWindowIsOpen.
- Audit Y1 residual, documented in-source at MaxOpenCampaigns: the 1,024-slot open-campaign table is exhaustible by locking >= 1,024 GNOT for a ~claim-window cycle, and the SettleFailed mitigation assumes a keeper actually calls it. Existing campaigns keep working regardless.
- Launch-slot exhaustion re-scoped at mainnet strength: the per-creator cap is Sybil-bypassable and a zero-pledge campaign holds a duebook slot for its whole duration at gas + refundable-deposit cost only -- the in-source mitigation claim prices only the post-deadline half, and the prior acceptance was testnet-scoped while the realm is live on gnoland-1. Availability-only; no funds at risk. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].
Source
| Source file (gnoland-1) | mainnet/r/crowdfund/crowdfund.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 (gnoland-1) | yes — byte-identical |
| Source file (onyx-1) | onyx/r/crowdfund/crowdfund.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 (onyx-1) | yes — byte-identical |