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 moultest issues \`moultest\`, a NATIVE coin, and gives it away.

Readme View source

gno.land/r/moul/x/moultest/v0

A native coin called moultest, and a faucet with four ceilings on it. Not a GRC20: the chain's own bank holds it, exactly as it holds GNOT.

/gno.land/r/moul/x/moultest/v0:moultest

That string is the whole coin. There is no name, no symbol, no decimals field, no ledger in this realm and no entry in any registry, because a native denom has none of those things. 1000 moultest is a thousand moultest.

Help yourself

gnokey maketx call -pkgpath gno.land/r/moul/x/moultest/v0 -func Claim ...

10,000 per claim, to the caller and never to an address the caller names, once every 100 blocks, 100,000 per address for life, 1,000,000 ever. Burning gives none of it back: issuance is counted monotonically, or claim-burn-claim would be an unlimited faucet. The supply ceiling is there so the experiment ends, not so the faucet scales.

What the coin can do that a GRC20 cannot

Moving it needs nothing from this realm. It is a bank message, so any wallet sends it with no realm call, no Approve, no allowance outliving the transfer:

gnokey maketx send -to <address> -send 2500/gno.land/r/moul/x/moultest/v0:moultest ...

It can also ride a call. The -send envelope of a transaction carries coins into the realm being called, before a line of its code runs, which is what Tip uses:

gnokey maketx call -pkgpath gno.land/r/moul/x/moultest/v0 -func Tip \
  -args <address> -args "nice work" \
  -send 1000/gno.land/r/moul/x/moultest/v0:moultest ...

Tip forwards that envelope to the recipient and writes the note on a public board. It spends through a BankerTypeOriginSend banker, whose entire authority is the envelope: it cannot touch the realm's own balance, and a test pins that by leaving 5,000 moultest at the realm address and checking that exactly the 250 that arrived leaves again.

That banker type is only available to a call whose entry frame is the realm named by the message, so Tip needs maketx call and does not work under maketx run.

What it costs, which is the more interesting half

A GRC20's mint and burn sit behind a ledger this realm chooses to guard. A native coin's sit behind a banker, and banker.RemoveCoin takes an arbitrary address. Nothing on the chain stops an issuing realm from deleting anybody's balance at any time.

This realm does not expose that. Burn reads the caller from the call frame and takes no address parameter, so it can only destroy the caller's own coins. But "does not expose it" is the entire protection, and it lasts exactly as long as the code deployed at this path. Worth knowing before treating any realm-issued native coin as something you own.

Redeploys

private = true, so this path can be redeployed with corrected code instead of being abandoned for a /v1. The coins survive that (they are bank state, held at their owners' addresses, and the denom embeds the package path, which does not move) and everything on the render page does not: claim history, caps consumed and the tip board all return to their init values, handing every account a fresh lifetime allowance. Acceptable for a coin that is worthless by construction, and not acceptable for one that is not.


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

Dependency graph:

gno.land/r/moul/x/moultest/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 moultest issues `moultest`, a NATIVE coin, and gives it away.

Native here means the chain's own bank holds it, exactly as it holds GNOT. It is not a GRC20: there is no ledger in this realm's storage, no balance map, no Transfer function, no allowance. A realm with the right banker calls IssueCoin once and from that moment the coin is a first-class chain object, and this realm has no further say in who holds it.

What that buys, and what it costs

The interesting half is what disappears. Moving moultest needs no code here at all: a plain bank send does it, from any wallet, with no realm call and no approval dance, because the transfer is a tm2 bank message rather than a function call. It can also RIDE a transaction: the `-send` envelope of a call carries it into the realm being called, which is the one thing no GRC20 can do, and Tip exists to show it. The account page of any explorer lists it beside GNOT with nothing registered anywhere.

The half that is worse is the authority. A GRC20's mint and burn live behind a PrivateLedger this realm chooses to guard; a native coin's live in a banker minted from a realm handle, and banker.RemoveCoin takes an ARBITRARY address. Nothing in the chain stops the issuing realm from deleting anybody's balance at any time. This realm does not expose that (see Burn, which is scoped to the caller and to nobody else), but "does not expose it" is the only protection there is, and it lasts exactly as long as the deployed code. That is the real asymmetry against a GRC20, not the ergonomics.

There is no decimals field either, nor a name, nor a symbol: a native denom is a string and nothing else. 1000 moultest is a thousand moultest.

Why it is capped

Free money with no ceiling is a spam vector rather than a faucet, so Claim is bounded four ways, and the caps are the point of the experiment as much as the coin is:

  • ClaimAmount per call, to the CALLER only, never to an address the caller names, so nobody can dust a stranger with it.
  • ClaimEvery blocks of cooldown between two claims by one account.
  • MaxPerAccount over that account's lifetime.
  • MaxSupply over the realm's lifetime.

Burn does not give any of it back. Issuance is counted monotonically, so claim-burn-claim cannot walk around the per-account cap; what burning moves is the circulating supply, which is why the two numbers are reported separately.

It is a private realm

gnomod.toml declares private = true, so this path can be redeployed with corrected code instead of being abandoned for a v1. The price is measured elsewhere in this repo and applies here too: the coins survive a redeploy (they are bank state, held at their owners' addresses, and the denom embeds the package path, which does not move), and everything on this page does not. Claim history, caps consumed and the tip board all return to their init values, which would hand every account a fresh MaxPerAccount. For a coin that is worthless by construction that is an acceptable trade; it would not be for one that is not.

Constants 2

const Name, Path, Link

 1const (
 2	// Name is the coin's base denom: the part after the colon. The chain caps
 3	// it at 16 lowercase characters.
 4	Name = "moultest"
 5	// Path is this realm's package path. The denom embeds it verbatim, so the
 6	// coin and the code are permanently the same name.
 7	Path = "gno.land/r/moul/x/moultest/v0"
 8	// Link is Path as a gnoweb route.
 9	Link = "/r/moul/x/moultest/v0"
10)
source

const ClaimAmount, ClaimEvery, MaxPerAccount, MaxSupply, MaxNote, MaxTips

 1const (
 2	// ClaimAmount is issued per successful [Claim].
 3	ClaimAmount = int64(10_000)
 4	// ClaimEvery is the cooldown, in blocks, between two claims by one account.
 5	ClaimEvery = int64(100)
 6	// MaxPerAccount is the lifetime ceiling on what one address can claim here,
 7	// ten claims' worth. Burning does not raise it.
 8	MaxPerAccount = int64(100_000)
 9	// MaxSupply is the lifetime ceiling on what this realm can ever issue: a
10	// hundred claims' worth, all told. Nothing lowers it, so the coin cannot be
11	// inflated after the fact, and the experiment has an end rather than a
12	// budget. A faucet meant to serve a crowd would put the ceiling somewhere
13	// else; this one is meant to run out.
14	MaxSupply = int64(1_000_000)
15
16	// MaxNote is the longest tip note kept, in bytes.
17	MaxNote = 120
18	// MaxTips is how many tips the board shows. Older ones fall off.
19	MaxTips = 20
20)
source

Variables 1

var Denom

1var Denom = chain.CoinDenom(Path, Name)
source

Denom is the full chain denom, "/" + Path + ":" + Name. This is the string a wallet, an explorer or a `-send` flag needs; the bank knows nothing about the realm behind it.

Functions 11

func BalanceOf

Action
1func BalanceOf(who address) int64
source

BalanceOf returns what the bank holds of moultest for `who`. It reads the chain, not this realm: a balance this realm never saw counts the same.

func Burn

crossing Action
1func Burn(cur realm, amount int64)
source

Burn destroys `amount` of the CALLER's moultest, and only the caller's.

The banker this realm holds could remove coins from any address on the chain without asking, so the `who` here is read from the call frame and is never a parameter. A clawback is what an issuing realm is always able to write; the choice not to is made here, once, and cannot be revisited without a redeploy.

Burning lowers what circulates and does not return any cap headroom: see MaxPerAccount.

func Circulating

Action
1func Circulating() int64
source

Circulating returns how much moultest exists right now, from the bank.

func Claim

crossing Action
1func Claim(cur realm) int64
source

Claim issues ClaimAmount of moultest to the caller and returns it.

It never issues to an address the caller names. That is deliberate: a faucet that mints to a third party is a way to spray a denom nobody asked for into strangers' wallets, and their account page then carries it forever.

func Issued

Action
1func Issued() int64
source

Issued returns how much this realm has ever issued. It only grows, and it is what MaxSupply caps.

func NextClaim

Action
1func NextClaim(who address) (int64, bool)
source

NextClaim returns the block height at which `who` may claim again, and whether they have ever claimed at all.

func Render

1func Render(path string) string
source

Render shows the coin, what is left under each cap, and the tip board. `?<address>` adds that address's balance and claim state.

func Tip

crossing Action
1func Tip(cur realm, to address, note string) int64
source

Tip forwards the moultest attached to THIS transaction to `to`, and writes the note on a public board.

The coin arrives in the `-send` envelope of the call, credited to this realm's address before a line of this function runs, and leaves through a BankerTypeOriginSend banker, whose whole authority is that envelope: it can spend what this message paid in and not one coin more, not even out of the realm's own balance. So the realm can route a payment it was handed while being structurally unable to touch anything else.

A GRC20 has no equivalent. Its tokens cannot ride a message, so the same flow costs an Approve, then a call, then an allowance that outlives both.

Anything else in the envelope (the GNOT covering a storage deposit, say) is left alone and stays with the realm.

Imports 9

Source Files 5