Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

crowdfund package

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:

Example
1CreatorClaim  -> book.Claim   (due, goal met, creator collects)
2SettleFailed  -> book.Claim   (due, goal UNMET, anyone; duebook
3                               leaves claim policy to its consumer)
4CreatorCancel -> book.Cancel  (owner renounces, any time while open)
5Lapse         -> 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):

Example
 1Launch (anyone)          : opens [funding]; schedules the deferral.
 2Pledge (backer, +send)   : while height < deadline. Real coins.
 3Unpledge (backer)        : full own pledge back, while height <
 4                           deadline. All-or-nothing holds: nothing
 5                           is locked until the deadline passes.
 6CreatorClaim (creator)   : height in [deadline, claimDeadline),
 7                           raised >= goal -> [paid]. Pays
 8                           raised - fee to the creator, accrues the
 9                           fee. The ONLY transition that charges
10                           anything.
11SettleFailed (ANYONE)    : height >= deadline, raised < goal ->
12                           [failed]. Makes the failure terminal and
13                           frees the campaign's slots immediately;
14                           refunds were already open on this path.
15CreatorCancel (creator)  : while the deferral is open -> [cancelled].
16                           Refunds open. A creator who cancels after
17                           succeeding is renouncing the pot.
18Lapse (ANYONE)           : height >= claimDeadline -> [lapsed].
19                           Refunds open. The permissionless valve: a
20                           creator who never collects cannot strand
21                           backer money, and a dead campaign cannot
22                           hold its duebook slot forever.
23Refund (backer)          : own pledge back, fee-free, whenever the
24                           campaign is [cancelled], [lapsed], or open
25                           past its deadline with raised < goal.
26Prune (ANYONE)           : removes a settled, fully-drained campaign
27                           record. The freed storage deposit is
28                           refunded by the chain to the CALLER, which
29                           is the incentive to call it.
30WithdrawFees (ANYONE)    : pays the accrued fee pot to the
31                           compile-time FeeCollector. Permissionless
32                           because the destination is fixed.
33SweepSurplus (ANYONE)    : out-of-band coins to the FeeCollector,
34                           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:

Example
1H == 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.

Function

Prune

func Prune(cur realm, id int64)

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.

Param

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund" -func "Prune" -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "onyx-1" -remote "https://rpc.onyx.testnets.gno.land" ADDRESSgnokey query -remote "https://rpc.onyx.testnets.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund" -func "Prune" -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "onyx-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.onyx.testnets.gno.land" call.tx