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 threads is a discussion block any realm can embed, keyed on the page it appears on rather than on the realm t...

Readme View source

gno.land/r/moul/x/social/threads/v0

A discussion block any realm can embed, keyed on the page it appears on rather than on the realm that stores it. Two lines in the host realm, which stores nothing:

1import "gno.land/r/moul/x/social/threads/v0"
2
3func Render(path string) string {
4	return body + threads.RenderBlock()
5}

RenderBlock takes no argument because it does not need one. A plain read exported by a realm and called by another opens no realm frame, so unsafe.CurrentRealm() inside it reports the caller: the host realm's own path is the page key, and nothing has to be configured, registered or passed. RenderBlockFor(page) is the explicit form, for a realm that renders several pages or already holds its path as a constant. Adding a cur realm parameter to either would silently reverse that and key every embed to this realm.

It is the text half of r/moul/reactions: the same embed shape, the same page keys, and the same reason for existing.

The token, and the three things it has to answer

THREAD is a GRC20 issued through p/moul/x/social/coin, which refuses to create a token until all three of these are written down:

mint 1 THREAD to a thread's author, the first time each distinct address replies to it. People, not messages, and never yourself.
sink Boost burns 10 THREAD to pin a thread to the top of its page for 1,000 blocks. It is the only thing that removes supply, and the only thing the token does.
buyer anyone who wants placement and has not earned it. They have to acquire THREAD from somebody who did, with Transfer.

Placement is the one thing a discussion has that people want and cannot all have at once, which is what makes it a sink worth paying for rather than a leaderboard. A pin extends from whichever is later, now or the current pin, so buying one never shortens somebody else's, and Boost charges before it pins.

It is farmable, and saying so is cheaper than pretending otherwise: two addresses replying to each other mint one THREAD each per thread. The fix is a gate on who counts as a replier, which belongs in r/moul/x/social/vouch and not here. Until then the cost of farming is one transaction per point and the benefit is capped by what a pin is worth.

The token is registered with r/nt/grc20reg, so a realm that does not import this one can still find and move it by key: TokenKey() returns it.

Why this realm is not private

Every other realm in the x/social family is private = true and can be redeployed in place. This one cannot be, twice over: a private realm cannot be imported at all, which would delete the embed, and handing an object to another realm (registering the token) panics at runtime from a private realm. Every thread, balance and pin under this path belongs to the people who wrote them, so a fix ships as a v1 beside it rather than as a redeploy that wipes the lot.

Reading it

Render("") is the index: the counts, the recent threads across every page, the token's three declarations, and the embed snippet. Render("page/<page key>") is one page's threads, Render("thread/<id>") is one thread with its replies and the reply and boost buttons.


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/threads/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 threads is a discussion block any realm can embed, keyed on the page it appears on rather than on the realm that stores it.

It is the text half of gno.land/r/moul/reactions: the same embed shape, the same page keys, two lines in the host realm.

Example
1import "gno.land/r/moul/x/social/threads/v0"
2
3func Render(path string) string {
4	return body + threads.RenderBlock()
5}

RenderBlock takes no argument because it does not need one: a plain read exported by a realm and called by another opens no realm frame, so unsafe.CurrentRealm() inside it reports the CALLER. The host realm's own path is the page key, and nothing has to be configured or registered. RenderBlockFor is the explicit form, for a realm that renders more than one page or already holds its path as a constant.

The token, and the three things it has to answer

THREAD exists because placement is the one thing a discussion has that people want and cannot all have at once.

Example
1mint   1 THREAD to a thread's author, the first time each distinct
2       address replies to it. People, not messages, and never yourself.
3sink   [Boost] burns PinCost THREAD to pin a thread to the top of its
4       page for PinBlocks blocks. That is the only thing that removes
5       supply, and the only thing the token does.
6buyer  anyone who wants placement and has not earned it. They have to
7       acquire THREAD from someone who did, with [Transfer], which is
8       what makes attention a market rather than a scoreboard.

It is farmable, and saying so is cheaper than pretending otherwise: two addresses replying to each other mint one THREAD each per thread. The fix is a gate on who counts as a replier, which belongs in a vouching realm and not here; until then the cost of farming is one transaction per point and the benefit is capped by what a pin is worth.

Constants 1

const EarnPerReplier, PinCost, PinBlocks, BlockThreads

 1const (
 2	// EarnPerReplier is minted to a thread's author the first time each
 3	// distinct address replies to it.
 4	EarnPerReplier = int64(1)
 5
 6	// PinCost is burned by Boost, and PinBlocks is how long the pin lasts.
 7	// A pin extends from whichever is later, now or the current pin, so
 8	// buying one never shortens somebody else's.
 9	PinCost   = int64(10)
10	PinBlocks = int64(1000)
11
12	// BlockThreads is how many threads an embedded block shows before it
13	// turns into a link to the full page.
14	BlockThreads = 5
15)
source

Functions 18

func Allowance

Action
1func Allowance(owner, spender address) int64
source

Allowance is what owner let spender draw.

func Approve

crossing Action
1func Approve(cur realm, spender address, amount int64)
source

Approve lets spender draw amount of the caller's THREAD.

func Boost

crossing Action
1func Boost(cur realm, threadID int64)
source

Boost burns PinCost THREAD from the caller and pins the thread to the top of its page for PinBlocks blocks.

Anyone may boost anyone's thread. The pin is on the thread and not on the buyer, so the thing being sold is placement and not authorship.

func Count

Action
1func Count(page string) int
source

Count is how many threads a page holds.

func PinnedUntil

Action
1func PinnedUntil(threadID int64) int64
source

PinnedUntil is the height a thread stops being pinned at, or 0.

func Post

crossing Action
1func Post(cur realm, page, body string) int64
source

Post opens a thread under page.

page is a package path, the full one: "gno.land/r/moul/home". It does not have to be a realm that embeds the block, and nothing checks that it exists. A page is just a name, which is what lets a realm key discussion on something finer than itself.

func Render

1func Render(path string) string
source

Render routes three views: the realm's own index, one page's threads, and one thread.

A page key contains slashes, so "page/<key>" is split on the prefix rather than by path element: realmpath gives the parts, and rejoining them would only be a way to get it wrong.

func RenderBlock

Action
1func RenderBlock() string
source

RenderBlock returns the block for the realm CALLING it, which is the form a host realm embeds.

It has no cur realm parameter on purpose. A plain read is borrowed: gno opens no realm frame for it, so unsafe.CurrentRealm() reports the caller's path and the block keys itself with no argument. Adding a cur realm here would silently reverse that and key every embed to this realm instead.

func RenderBlockFor

Action
1func RenderBlockFor(page string) string
source

RenderBlockFor is RenderBlock for a named page. Use it from a crossing function, where there is no caller to read off the stack, or when one realm renders several pages.

func Repliers

Action
1func Repliers(threadID int64) int
source

Repliers is how many distinct addresses have replied to a thread, which is also how much its author has earned from it.

func Reply

crossing Action
1func Reply(cur realm, threadID int64, body string)
source

Reply appends to a thread.

The first time each distinct address replies to a given thread, its author earns EarnPerReplier. Replying again earns nothing, and replying to your own thread never earns anything.

func Total

Action
1func Total() int
source

Total is how many threads exist, across every page.

func TotalSupply

Action
1func TotalSupply() int64
source

TotalSupply is the THREAD in existence: everything earned, less everything burned by Boost.

func Transfer

crossing Action
1func Transfer(cur realm, to address, amount int64)
source

Transfer moves the caller's own THREAD. It is what makes the sink a market: somebody who wants a pin and has written nothing has to buy from an author.

func TransferFrom

crossing Action
1func TransferFrom(cur realm, from, to address, amount int64)
source

TransferFrom spends an allowance the caller was granted.

Imports 12

Source Files 5