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

v0 source realm

Package crews is small-group coordination you can create in one transaction: three to fifteen people with a shared po...

Readme View source

crews

A crew is three to fifteen people with a shared pot, a way to decide, and a way to leave with their share. One transaction to start one.

The chain wiring for p/moul/x/social/crew, which holds the share math and the proposal state. This realm reads the caller, reads the coins attached to the call, asks the engine, and renders the result.

The API

call
Create(name) payable. You become the crew's first member, your shares bought out of what you sent at InitialPricePerShare.
Join(crewID) payable. Mints shares at what a share is worth now: amount * totalShares / treasury, rounded down.
Fund(crewID) payable. Adds to the treasury and mints nothing, so every existing share is worth more.
Propose(crewID, text) any member. Open for VoteBlocks blocks.
Vote(proposalID, yes) weighted by the shares you hold right now, changeable while it is open.
Close(proposalID) anyone, after the deadline. Records passed or failed by share weight.
Ragequit(crewID) burn every share you hold, be credited your pro-rata of the treasury, leave.
Withdraw() collect what you were credited.

Reads: Get, Count, SharesOf, MemberCount, Treasury, ValuePerShare, ProposalOf, Tally, CreditOf, TotalOwed.

Render serves three views: the index, :crew/<id>, and :proposal/<id>.

Shares are the token, and that is the answer

The shares are the token. They are the vote weight and the claim on the treasury at once, minted by joining and burned by leaving, so nothing about them is decorative and nobody has to be persuaded they are worth something.

v0 keeps them as an internal ledger rather than one GRC20 per crew, because a GRC20 per crew means a realm per crew. A transferable share belongs in p/moul/x/social/coin, the sibling that refuses to exist until its mint rule, sink and buyer are declared. This realm does not import it.

The custody caveat, which is real

One realm address holds every crew's treasury, with per-crew accounting inside it. The chain sees one balance; which crew owns which part of it is a number in this realm's state.

So a bug in that accounting is a bug across crews, not inside one. An arithmetic error that over-credits one crew's ragequit pays it out of another crew's money, and nothing at the bank layer would refuse that transfer: as far as the chain is concerned it is all the same address paying itself out.

The fix is the instance-per-realm pattern (EFFECTIVE_GNO.md section 1.4, in this repository): one realm per crew, each holding its own coins at its own address, with p/moul/x/social/crew as the shared engine. That is v1. It is not v0 because deploying a realm per crew is a publish per crew, which is the opposite of "create it in one transaction", and the one transaction is the whole product claim.

Payouts are pulled, never pushed

A ragequit credits an internal ledger and the leaver calls Withdraw themselves. One address that cannot be paid cannot wedge anybody else, and the credit is zeroed before the coins move, so a reentrant call finds nothing left to take.

What a proposal does not do

Nothing. A v0 proposal is advisory text: passing one records that the crew agreed by share weight at a point in time, and moves no coins, changes no membership and binds no code. Executing a payout is the next step, and it is what would turn this into a treasury contract rather than a notice board.

Why this realm is private

private = true, so it can be redeployed at this path by its creator, and no other realm may import it. The trade is the usual one: a redeploy wipes every package-level variable, so every crew, every share and every credit would go with it while the coins stayed at the address. Nothing imports this realm and nothing is meant to, so the redeploy door is worth more than the import one while the design is still moving. The v1 above is the move that closes it.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

On mainnet: deployment status transactions unique callers deployed revision

Dependency graph:

gno.land/r/moul/x/social/crews/v0 dependency graph

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.

Overview

Package crews is small-group coordination you can create in one transaction: three to fifteen people with a shared pot, a way to decide, and a way to leave with their share.

It is the chain wiring for gno.land/p/moul/x/social/crew, which holds the share math and the proposal state. This realm reads the caller, reads the coins attached to the call, asks the engine, and renders the result.

Example
1Create    pay in, and you are the crew's first member
2Join      pay in at what a share is worth NOW, not at what it cost them
3Fund      pay in and mint nothing, so every existing share is worth more
4Propose   any member opens an advisory question
5Vote      weighted by the shares you hold, changeable while it is open
6Close     anyone, once the deadline has gone by
7Ragequit  burn your shares, be credited your slice, leave
8Withdraw  collect what you were credited

The shares are the token

There is no second asset to issue. A share is the vote weight and the claim on the treasury at the same moment, minted by paying in and burned by leaving, so nothing about it is decorative and nobody has to be persuaded it is worth something. v0 keeps the shares as an internal ledger rather than one GRC20 per crew, because a GRC20 per crew means a realm per crew. A transferable share belongs in gno.land/p/moul/x/social/coin, the sibling that refuses to exist until its mint rule, sink and buyer are declared; this realm does not import it.

The custody caveat, which is real

ONE realm address holds EVERY crew's treasury, with per-crew accounting inside it. The chain sees one balance; which crew owns which part of it is a number in this realm's state. So a bug in the accounting is a bug across crews, not inside one: an arithmetic error that over-credits one crew's ragequit pays it out of another crew's money, and nothing at the bank layer would refuse that transfer.

The fix is the instance-per-realm pattern (EFFECTIVE_GNO.md section 1.4): one realm per crew, each holding its own coins at its own address, with this package as the shared engine. That is v1. It is not v0 because deploying a realm per crew is a publish per crew, which is the opposite of "create it in one transaction", and the whole product claim here is the one transaction.

Payouts are pulled

Nothing is ever pushed. A ragequit credits an internal ledger and the leaver calls Withdraw themselves, so one address that cannot be paid cannot wedge anybody else, and the credit is zeroed before the coins move.

Constants 1

const IndexCrews

1const IndexCrews = 20
source

IndexCrews is how many crews the index page lists.

Functions 18

func Close

crossing Action
1func Close(cur realm, proposalID int64) bool
source

Close records a proposal as passed or failed once its deadline has gone by, and returns which. Anyone may call it: closing is bookkeeping, not authority.

func Create

crossing Action
1func Create(cur realm, name string) int64
source

Create opens a crew and makes the caller its first member.

Send ugnot with the call: the founder's shares are bought out of it at crew.InitialPricePerShare, and the whole amount, remainder included, becomes the treasury. Returns the new crew's id.

func CreditOf

Action
1func CreditOf(who address) int64
source

CreditOf is what an address can withdraw right now, in ugnot.

func Fund

crossing Action
1func Fund(cur realm, crewID int64) int64
source

Fund adds what the caller sent to a crew's treasury and mints nothing, so every existing share is worth more afterwards.

It is how revenue, a grant or a member topping the pot up arrives. Anyone may fund any crew, member or not: there is no way to refuse a transfer on this chain anyway, and pretending otherwise would only move the donation to a bank send the realm cannot account for.

func Get

Action
1func Get(crewID int64) (string, int64, int, int64, int64)
source

Get returns a crew's name, the height it was created at, how many members it has, its total shares and its treasury in ugnot.

Flat values rather than the crew itself: handing out a pointer to live state is a mutation handle nothing checks, and a struct is not something a wallet can decode anyway.

func Join

crossing Action
1func Join(cur realm, crewID int64) int64
source

Join mints the caller shares at what a share is worth right now and adds what they sent to the treasury. Returns the shares minted.

The price is amount * totalShares / treasury, rounded down, so somebody joining a crew that has tripled its money pays three times what the founders did. Send at least ValuePerShare ugnot, or there is no whole share to mint.

func Propose

crossing Action
1func Propose(cur realm, crewID int64, text string) int64
source

Propose opens an advisory question on a crew. Any member may propose, and it stays open for crew.VoteBlocks blocks. Returns the proposal's id.

A proposal EXECUTES NOTHING. Passing one records that the crew agreed by share weight, and moves no coins, changes no membership and binds no code. Executing a payout is the next step and it is not in v0.

func Ragequit

crossing Action
1func Ragequit(cur realm, crewID int64) int64
source

Ragequit burns every share the caller holds, credits them their pro-rata slice of the treasury and removes them from the crew. Returns the amount credited, which Withdraw collects.

Nobody has to agree. That is what makes a share mean something: the exit is priced by the same number that votes, and a member outvoted on everything can still leave with what they put in plus their part of what the crew built.

func Render

1func Render(path string) string
source

Render routes three views: the index, one crew, and one proposal.

func SharesOf

Action
1func SharesOf(crewID int64, who address) int64
source

SharesOf is how many shares who holds in a crew.

func Tally

Action
1func Tally(proposalID int64) (int64, int64)
source

Tally is a proposal's share-weighted yes and no.

func TotalOwed

Action
1func TotalOwed() int64
source

TotalOwed is every outstanding credit, which is the part of this realm's balance that belongs to people who have already left.

func Treasury

Action
1func Treasury(crewID int64) int64
source

Treasury is what a crew holds, in ugnot. The coins themselves sit at this realm's single address: see the package doc on custody.

func ValuePerShare

Action
1func ValuePerShare(crewID int64) int64
source

ValuePerShare is what one of a crew's shares is worth in ugnot, rounded down.

func Vote

crossing Action
1func Vote(cur realm, proposalID int64, yes bool)
source

Vote records the caller's ballot, weighted by the shares they hold at this moment. One ballot per member, changeable while the proposal is open.

func Withdraw

crossing Action
1func Withdraw(cur realm) int64
source

Withdraw sends the caller everything they have been credited, and returns it.

The credit is zeroed before the coins move, so a reentrant call finds nothing left to take. Nothing in this realm ever pushes value: one address that cannot be paid must not be able to wedge everybody else.

Imports 12

Source Files 5