const BoardSize
BoardSize is how many addresses the index page ranks.
Package vouch is a web of trust other realms can gate on: one address vouches for another, in writing, optionally wit...
gno.land/r/moul/x/social/vouch/v0A 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.
| 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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
BondFrom is what from locked on target, or zero.
BondedFor is the total ugnot locked on addr by everyone vouching for them.
Count is how many vouches exist.
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.
Mutual reports whether a and b vouch for each other, which is the cheapest sybil shape and therefore worth discounting.
Owed is what addr can collect with Withdraw.
People is how many addresses have at least one vouch for them.
ReasonFrom is what from wrote about target, or the empty string. It is raw caller text: escape it before rendering it anywhere.
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.
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.
ScoreOf is how many distinct addresses vouch for addr.
TotalBonded is everything locked on vouches that still stand.
TotalOwed is every revoked bond nobody has collected yet. This realm holds TotalBonded plus TotalOwed on behalf of other people.
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.
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.