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 vouch is a web of trust other realms can gate on: one address vouches for another, in writing, optionally wit...

Readme View source

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

A web of trust other realms gate on. One address vouches for another, in writing, optionally with GNOT locked behind it. Any realm can then ask whether an address is vouched for by at least N distinct people and refuse to serve it if it is not.

1import "gno.land/r/moul/x/social/vouch/v0"
2
3func Claim(cur realm) {
4	if !vouch.IsTrusted(cur.Previous().Address(), 2) {
5		panic("get two people to vouch for you first")
6	}
7	...
8}

That is the whole product. It exists because the apps beside it do not have a sybil gate: a realm that mints a point per distinct replier is farmed by two addresses replying to each other, and a realm that counts one vote per address is farmed by holding a hundred addresses. Neither can fix it alone, because neither knows anything about the people behind the addresses.

The API

write
Vouch(target, reason) payable. Records the caller's vouch. Coins sent are locked as a bond on it. Vouching again updates the reason and adds to the bond
Revoke(target) removes the caller's vouch and credits the bond back to them
Withdraw() pays the caller every bond their revocations freed
read
IsTrusted(addr, min) the gate. At least min distinct vouchers, min below one read as one
ScoreOf(addr) distinct inbound vouchers
BondedFor(addr) total ugnot bonded on them
VouchedBy(addr) / VouchesOf(addr) the two directions, sorted
Mutual(a, b) whether they vouch for each other
ReasonFrom(from, to) / BondFrom(from, to) one edge
Count, People, Owed, TotalBonded, TotalOwed the totals
Badge(addr) the one-line trust mark, for a host realm's own page

The engine is p/moul/x/social/vouch/v0, which holds the graph, the validation and the refund ledger and takes the height, the caller and the bond as arguments. This realm is the chain wiring and the Render.

Three traps it avoids

A score counts people, not vouches. A second vouch from the same address to the same target is an edit: the reason is replaced, the bond is added to, and the score does not move. Without that, the gate measures transactions, and transactions are for sale.

It never loops and sends. Revoke credits a ledger and Withdraw pays one payee, zeroing the credit before the coins leave. A realm that sent on revoke would hand control to the recipient mid-transition, and a recipient that refuses coins could make revoking impossible.

A realm cannot forward a bond. The payment envelope belongs to the transaction, not to the frame, so a realm that was itself paid and then calls Vouch would report coins sitting at its own address. This realm refuses that call outright rather than quietly reading the bond as zero. A realm may vouch; it may not vouch while holding somebody else's payment.

No slashing in v0, and that is the hard part

A bond is value at risk only in the sense that it is illiquid: it can be recovered by revoking, and nothing can take it away. That is not an oversight, and the missing half is not the accounting.

Slashing needs an arbiter, somebody who decides that a vouch was a lie. A DAO vote is a popularity contest against whoever is unpopular this month. A challenge market pays whoever is loudest and turns the graph into a griefing surface. An oracle is one key that can confiscate anybody's money. Shipping any of them by default would be shipping the wrong one, so v0 ships the part that is uncontroversial: who said what, who put money behind it, and the gate that reads it.

The bond still earns its place without slashing. An illiquid deposit is a cost a hundred throwaway addresses cannot all pay at once, which is exactly the shape the gate is defending against.

And can it have a token?

No, and the reason is the point. Every app in this family is asked the same question, and here the honest answer is no. A transferable vouch is a bought reputation: the moment a vouch can be sold, the score stops measuring what it claims to measure, and what it measures instead is who had the most money this week. That is the one failure mode a sybil gate cannot survive, because it is not a degradation, it is the attack.

The bond is GNOT. It is value at risk without being a market in trust itself: you can lock your own money behind a claim, and you cannot sell the claim.

What would change the answer is a token whose only use is being locked and slashed, with no transfer path that buys standing, and that needs an arbiter first, because without something that can slash it the token is a scoreboard with a price. If that day comes, the shape to issue it under is p/moul/x/social/coin/v0, a sibling package: a GRC20 that refuses to exist until its mint rule, its sink and its buyer are written down. It is not imported here, deliberately. Saying no with a reason is the deliverable.

Public, not private

Realms in this family default to private = true, which buys redeploy in place. This one is public and its gnomod.toml argues why: a private realm cannot be imported, and a gate that can only be read over the chain is not a gate, since a realm cannot pause mid-transaction to query itself. The second reason settles it: the graph and the refund ledger are package-level variables while the bonded ugnot sits at the realm's address, so a redeploy would wipe the record of whose money that is and keep the money. A realm holding somebody else's coins should not be able to forget who they belong to.


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/vouch/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 vouch is a web of trust other realms can gate on: one address vouches for another, in writing, optionally with GNOT locked behind it.

The one call that matters is a plain read:

Example
1import "gno.land/r/moul/x/social/vouch/v0"
2
3func Claim(cur realm) {
4	if !vouch.IsTrusted(cur.Previous().Address(), 2) {
5		panic("get two people to vouch for you first")
6	}
7	…
8}

IsTrusted takes no cur realm on purpose. A read with no realm token is borrowed: gno opens no realm frame for it, so it costs the caller nothing beyond the read and cannot be tricked into acting as anybody. Nothing here branches on who is asking, and no read in this realm ever will: the answer to "is this address trusted" must not depend on who wants to know.

What it does, and what it refuses to do

Vouch is payable. Any coins sent with it are locked as a bond on that vouch, which is the voucher putting their own money where their claim is. A bond is optional and zero is the ordinary case. Revoke takes the vouch back and credits the bond to the voucher, who collects it with Withdraw.

There is no slashing in v0 and that is the interesting half. Slashing needs an arbiter, somebody who decides a vouch was a lie, and every candidate is a design question rather than a feature: a DAO vote is a popularity contest, a challenge market pays whoever is loudest, an oracle is one key that can confiscate anybody's money. The bond is still worth having without it, because an illiquid deposit is a cost a hundred throwaway addresses cannot all pay at once.

No token

This realm issues none, deliberately. A transferable vouch is a bought reputation, and the moment a vouch can be sold the score stops measuring what it says it measures. The bond is GNOT: value at risk, without being a market in trust itself. The README says what would change the answer.

Constants 1

const BoardSize

1const BoardSize = 10
source

BoardSize is how many addresses the index page ranks.

Functions 16

func Badge

Action
1func Badge(addr address) string
source

Badge is the one-line trust mark for addr, for a host realm that wants to show what its gate just read. Like IsTrusted it is a borrowed read and takes no realm token.

func BondFrom

Action
1func BondFrom(from, target address) int64
source

BondFrom is what from locked on target, or zero.

func BondedFor

Action
1func BondedFor(addr address) int64
source

BondedFor is the total ugnot locked on addr by everyone vouching for them.

func IsTrusted

Action
1func IsTrusted(addr address, min int) bool
source

IsTrusted reports whether addr is vouched for by at least min distinct addresses. This is the gate, and it is one line at the call site.

A min below one is read as one: a gate that lets everybody through is a bug in the caller rather than an answer this realm will agree to.

It cannot tell you that the vouchers are distinct people. Two addresses vouching for each other both reach a score of one, which Mutual exposes and which is the reason to ask for more than one.

func Mutual

Action
1func Mutual(a, b address) bool
source

Mutual reports whether a and b vouch for each other, which is the cheapest sybil shape and therefore worth discounting.

func People

Action
1func People() int
source

People is how many addresses have at least one vouch for them.

func ReasonFrom

Action
1func ReasonFrom(from, target address) string
source

ReasonFrom is what from wrote about target, or the empty string. It is raw caller text: escape it before rendering it anywhere.

func Render

1func Render(path string) string
source

Render routes two views: the realm's own index, and one address's page.

An address carries no slash, so the per-address route is a path element and not a prefix strip.

func Revoke

crossing Action
1func Revoke(cur realm, target address) int64
source

Revoke withdraws the caller's vouch for target and credits any bond back to them. The coins do not move here: call Withdraw to collect them.

Splitting it in two is the pull-payment pattern. A realm that sent on revoke would be handing control to the recipient in the middle of its own state change, and a recipient that refuses the coins could make revoking impossible.

func ScoreOf

Action
1func ScoreOf(addr address) int
source

ScoreOf is how many distinct addresses vouch for addr.

func TotalOwed

Action
1func TotalOwed() int64
source

TotalOwed is every revoked bond nobody has collected yet. This realm holds TotalBonded plus TotalOwed on behalf of other people.

func Vouch

crossing Action
1func Vouch(cur realm, target address, reason string)
source

Vouch records that the caller stands behind target, for the stated reason.

It is payable: coins sent with the call are locked as a bond on this vouch and are returned by Revoke, never by anything else. Sending nothing is fine and is the ordinary case.

Vouching again for the same address UPDATES the reason and ADDS to the bond. It does not count twice: the score this realm exists to publish is a count of people, so a second transaction from the same address buys precisely nothing. Vouching for yourself is refused.

func Withdraw

crossing Action
1func Withdraw(cur realm) int64
source

Withdraw pays the caller every bond their revoked vouches freed, and returns the amount sent.

The credit is zeroed before the coins leave, so a recipient that calls straight back in finds nothing to take.

Imports 10

Source Files 5