PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/moul/x/daily/vestoken/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / daily / vestoken
Files
3 (README)(gnomod.toml)
Exported functions
9
Module
gno.land/r/moul/x/daily/vestoken/v0
gno
0.9

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • vestoken.gnogno
vestoken.gnogno
1// Package vestoken is a fungible token where new supply is never handed out2// instantly — it ports the idea behind Solidity's VestingWallet / linear3// token-streaming contracts (e.g. Sablier) into a single gno.land realm.4//5// MintVested opens a linear vesting grant instead of crediting a balance6// directly. The beneficiary calls Claim over time to pull whatever has7// unlocked so far into their liquid, transferable balance. State is8// package-level and persistent; state-mutating functions are crossing9// functions (`cur realm`) per the gno 0.9 interrealm convention.10package vestoken1112import (13	"chain/runtime"14	"errors"15	"sort"16	"strconv"17	"strings"18)1920const (21	name   = "VestToken"22	symbol = "VEST"23)2425// grant is one linear vesting schedule: `total` unlocks evenly from `start`26// to `start+duration` (measured in block height), and `claimed` tracks how27// much of the unlocked amount the beneficiary has already pulled.28type grant struct {29	total    uint6430	claimed  uint6431	start    int6432	duration int6433}3435var (36	// balances holds liquid, already-claimed tokens — transferable like a37	// normal ERC-20 balance.38	balances = map[address]uint64{}39	// grants holds at most one active vesting schedule per beneficiary.40	grants = map[address]*grant{}41	// totalSupply is every token ever minted (vesting or liquid).42	totalSupply uint6443)4445var (46	errZeroAddress   = errors.New("vestoken: zero address")47	errZeroAmount    = errors.New("vestoken: amount must be positive")48	errZeroDuration  = errors.New("vestoken: duration must be positive")49	errGrantActive   = errors.New("vestoken: beneficiary already has an unclaimed grant")50	errNoGrant       = errors.New("vestoken: no active grant for this account")51	errInsufficient  = errors.New("vestoken: insufficient balance")52	errOverflow      = errors.New("vestoken: supply overflow")53	errSpoofedRealm  = errors.New("vestoken: spoofed realm")54	errNotUser       = errors.New("vestoken: caller is not a user account")55)5657// caller returns the immediate caller's address for a crossing function,58// enforcing the gno 0.9 authentication primitive: cur must be the live59// crossing frame before its Previous() is trusted.60func caller(cur realm) address {61	if !cur.IsCurrent() {62		panic(errSpoofedRealm)63	}64	prev := cur.Previous()65	if !prev.IsUser() {66		panic(errNotUser)67	}68	return prev.Address()69}7071// MintVested opens a new linear vesting grant for `to`: `amount` tokens72// unlock block-by-block over the next `durationBlocks` blocks, starting now.73// A beneficiary may only hold one active grant at a time. Crossing function.74func MintVested(cur realm, to address, amount uint64, durationBlocks int64) {75	_ = caller(cur) // authenticate the crossing frame; anyone may open a grant in this demo76	if !to.IsValid() {77		panic(errZeroAddress)78	}79	if amount == 0 {80		panic(errZeroAmount)81	}82	if durationBlocks <= 0 {83		panic(errZeroDuration)84	}85	if g, ok := grants[to]; ok && g.claimed < g.total {86		panic(errGrantActive)87	}88	if totalSupply+amount < totalSupply {89		panic(errOverflow)90	}91	totalSupply += amount92	grants[to] = &grant{93		total:    amount,94		start:    runtime.ChainHeight(),95		duration: durationBlocks,96	}97}9899// Claim pulls whatever has unlocked so far from the caller's grant into100// their liquid balance, and returns how much was claimed. Crossing function.101func Claim(cur realm) uint64 {102	who := caller(cur)103	g, ok := grants[who]104	if !ok {105		panic(errNoGrant)106	}107108	unlocked := vestedAmount(g, runtime.ChainHeight())109	claimable := unlocked - g.claimed110	if claimable == 0 {111		return 0112	}113114	g.claimed = unlocked115	balances[who] += claimable116	if g.claimed >= g.total {117		delete(grants, who)118	}119	return claimable120}121122// vestedAmount computes how much of g has unlocked by the given height.123func vestedAmount(g *grant, height int64) uint64 {124	elapsed := height - g.start125	if elapsed <= 0 {126		return 0127	}128	if elapsed >= g.duration {129		return g.total130	}131	return g.total * uint64(elapsed) / uint64(g.duration)132}133134// Transfer moves `amount` of the caller's liquid (already-claimed) balance135// to `to`. Crossing function.136func Transfer(cur realm, to address, amount uint64) {137	from := caller(cur)138	if !to.IsValid() {139		panic(errZeroAddress)140	}141	if amount == 0 {142		return143	}144	if balances[from] < amount {145		panic(errInsufficient)146	}147	balances[from] -= amount148	balances[to] += amount149}150151// BalanceOf returns the liquid (claimed, transferable) balance of holder.152func BalanceOf(holder address) uint64 {153	return balances[holder]154}155156// GrantOf returns the beneficiary's active grant, if any: total amount,157// amount claimed so far, and the amount currently claimable.158func GrantOf(beneficiary address) (total uint64, claimed uint64, claimable uint64) {159	g, ok := grants[beneficiary]160	if !ok {161		return 0, 0, 0162	}163	unlocked := vestedAmount(g, runtime.ChainHeight())164	return g.total, g.claimed, unlocked - g.claimed165}166167// TotalSupply returns every token ever minted, vesting or liquid.168func TotalSupply() uint64 { return totalSupply }169170// Name returns the token name.171func Name() string { return name }172173// Symbol returns the token symbol.174func Symbol() string { return symbol }175176// Render produces the gnoweb Markdown view: metadata, active vesting177// grants, and liquid balances — both tables sorted by address for178// deterministic output. Render is NOT a crossing function.179func Render(path string) string {180	var b strings.Builder181182	b.WriteString("# " + name + " (" + symbol + ")\n\n")183	b.WriteString("A linearly-vesting ERC-20-style token on gno.land — ")184	b.WriteString("new supply unlocks block-by-block instead of landing instantly.\n\n")185	b.WriteString("- **Name:** " + name + "\n")186	b.WriteString("- **Symbol:** " + symbol + "\n")187	b.WriteString("- **Total supply:** " + strconv.FormatUint(totalSupply, 10) + "\n")188	b.WriteString("- **Current height:** " + strconv.FormatInt(runtime.ChainHeight(), 10) + "\n\n")189190	b.WriteString("## Active grants\n\n")191	grantKeys := sortedAddrKeys(grantAddrs())192	if len(grantKeys) == 0 {193		b.WriteString("_No active grants — call `MintVested` to open one._\n\n")194	} else {195		b.WriteString("| Beneficiary | Total | Claimed | Claimable now |\n")196		b.WriteString("| --- | ---: | ---: | ---: |\n")197		for _, k := range grantKeys {198			g := grants[address(k)]199			unlocked := vestedAmount(g, runtime.ChainHeight())200			b.WriteString("| `" + k + "` | " +201				strconv.FormatUint(g.total, 10) + " | " +202				strconv.FormatUint(g.claimed, 10) + " | " +203				strconv.FormatUint(unlocked-g.claimed, 10) + " |\n")204		}205		b.WriteString("\n")206	}207208	b.WriteString("## Liquid balances\n\n")209	balKeys := sortedAddrKeys(balanceAddrs())210	if len(balKeys) == 0 {211		b.WriteString("_No claimed balances yet — call `Claim` once a grant has unlocked._\n")212		return b.String()213	}214	b.WriteString("| Address | Amount |\n")215	b.WriteString("| --- | ---: |\n")216	for _, k := range balKeys {217		b.WriteString("| `" + k + "` | " +218			strconv.FormatUint(balances[address(k)], 10) + " |\n")219	}220221	return b.String()222}223224func grantAddrs() []string {225	keys := make([]string, 0, len(grants))226	for addr := range grants {227		keys = append(keys, addr.String())228	}229	return keys230}231232func balanceAddrs() []string {233	keys := make([]string, 0, len(balances))234	for addr, bal := range balances {235		if bal == 0 {236			continue237		}238		keys = append(keys, addr.String())239	}240	return keys241}242243func sortedAddrKeys(keys []string) []string {244	sort.Strings(keys)245	return keys246}247

Functions

  • BalanceOf(holder string) uint64

  • Claim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}) uint64

  • GrantOf(beneficiary string) (total uint64, claimed uint64, claimable uint64)

  • MintVested(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, to string, amount uint64, durationBlocks int64)

  • Name() string

  • Render(path string) string

  • Symbol() string

  • TotalSupply() uint64

  • Transfer(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, to string, amount uint64)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.