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 source realm

Realm crowdfund is permissionless all-or-nothing crowdfunding in GNOT: a creator opens a campaign with a funding goal...

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.

Constants 13

const MinDurationBlocks, MaxDurationBlocks

1const (
2	MinDurationBlocks = int64(1_200)
3	MaxDurationBlocks = int64(1_300_000)
4)
source

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 MaxTitleLen, MaxMemoLen

1const (
2	MaxTitleLen = 100
3	MaxMemoLen  = 256
4)
source

Input bounds. Both fields are free text and are sanitized before reaching markdown.

const ClaimWindowBlocks

1const ClaimWindowBlocks = int64(650_000)
source

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 Denom

1const Denom = "ugnot"
source

Denom is the only asset this realm holds.

const FeeCollector

1const FeeCollector = address("g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3")
source

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 MaxFeeBps

1const MaxFeeBps = int64(500)
source

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 MaxOpenCampaigns

1const MaxOpenCampaigns = 1024
source

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 MaxOpenPerCreator

1const MaxOpenPerCreator = int64(8)
source

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 MaxRenderRows

1const MaxRenderRows = 20
source

MaxRenderRows bounds the campaign list on the index page.

const MinGoal

1const MinGoal = int64(1_000_000)
source

MinGoal keeps dust campaigns out: 1 GNOT.

const MinPledge

1const MinPledge = int64(10_000)
source

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 RealmPath

1const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/crowdfund"
source

RealmPath is this realm's own path, used to build Render links.

const SuccessFeeBps

1const SuccessFeeBps = int64(100)
source

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%.

Functions 26

func CampaignMemo

Action
1func CampaignMemo(id int64) string
source

CampaignMemo returns a campaign's raw memo (same caveat as the title).

func CampaignTitle

Action
1func CampaignTitle(id int64) string
source

CampaignTitle returns a campaign's raw title (unsanitized: callers rendering it into markdown must sanitize, as this realm's Render does).

func CreatorCancel

crossing Action
1func CreatorCancel(cur realm, id int64)
source

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.

func CreatorClaim

crossing Action
1func CreatorClaim(cur realm, id int64)
source

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.

func Height

Action
1func Height() int64
source

Height returns the current chain height, the clock every deadline is measured against.

func Held

Action
1func Held() int64
source

Held returns H: the ugnot actually at this realm's address.

func IsRefundable

Action
1func IsRefundable(id int64) bool
source

IsRefundable reports whether Refund would currently accept this campaign (for any backer with a live pledge).

func Lapse

crossing Action
1func Lapse(cur realm, id int64)
source

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.

func Launch

crossing Action
1func Launch(cur realm, title, memo string, goal, durationBlocks int64) int64
source

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.

func NumCampaigns

Action
1func NumCampaigns() int64
source

NumCampaigns returns how many campaigns have ever been launched. Prune removes records but never reuses ids.

func OpenOf

Action
1func OpenOf(addr address) int64
source

OpenOf returns how many unsettled campaigns addr currently has.

func Pledge

crossing Action
1func Pledge(cur realm, id int64)
source

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.

func PledgeOf

Action
1func PledgeOf(id int64, addr address) int64
source

PledgeOf returns addr's live pledge in a campaign (0 if none, and 0 for settled campaigns whose pledge table has been dropped).

func Prune

crossing Action
1func Prune(cur realm, id int64)
source

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.

func Refund

crossing Action
1func Refund(cur realm, id int64)
source

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.

func Render

1func Render(path string) string
source

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.

func SettleFailed

crossing Action
1func SettleFailed(cur realm, id int64)
source

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.

func Status

Action
1func Status(id int64) string
source

Status returns the human verdict for a campaign right now: "funding", "succeeded (awaiting creator claim)", "failed (refunds open)", "paid", "cancelled", or "lapsed".

func Surplus

Action
1func Surplus() int64
source

Surplus returns H - (B + F) — out-of-band coins, sweepable.

func SweepSurplus

crossing Action
1func SweepSurplus(cur realm, denom string) int64
source

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.

func Unpledge

crossing Action
1func Unpledge(cur realm, id int64)
source

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.

func WithdrawFees

crossing Action
1func WithdrawFees(cur realm) int64
source

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.

Imports 9

Source Files 3