Transaction
CB0439F28867D1…923EF1FFCF44
Block 26,131 · index 0 · indexed
Summary
- Hash
- CB0439F28867D1083D1ADE3FDF2CF0DBBE6568FE86774C2EE159923EF1FFCF44
- Block
- 26,131
- Size
- 8852 bytes
- Gas used
- 15,252,234 / 18,302,740
- Fee
- 1000000 ugnot
- Memo
- gnopublish
- Status
- success
Messages
Arguments · 9
- #1vestoken
- #2README.md
- #3# vestoken > ⚠️ **Experimental — generated with no human supervision.** This realm was > produced automatically by an MCP-driven agent to exercise the gno MCP server > and tooling, and to generate test content for gno compilers, linters and > formatters. **Not audited. Not for production.** Full context & folder README: > [r/moul/x/daily](https://github.com/moul/gno-contracts/blob/main/r/moul/x/daily/README.md) --- <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. > 🧪 **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](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
- #4gnomod.toml
- #5module = "gno.land/r/moul/x/daily/vestoken/v0" gno = "0.9"
- #6vestoken.gno
- #7// Package vestoken is a fungible token where new supply is never handed out // instantly — it ports the idea behind Solidity's VestingWallet / linear // token-streaming contracts (e.g. Sablier) into a single gno.land realm. // // MintVested opens a linear vesting grant instead of crediting a balance // directly. The beneficiary calls Claim over time to pull whatever has // unlocked so far into their liquid, transferable balance. State is // package-level and persistent; state-mutating functions are crossing // functions (`cur realm`) per the gno 0.9 interrealm convention. package vestoken import ( "chain/runtime" "errors" "sort" "strconv" "strings" ) const ( name = "VestToken" symbol = "VEST" ) // grant is one linear vesting schedule: `total` unlocks evenly from `start` // to `start+duration` (measured in block height), and `claimed` tracks how // much of the unlocked amount the beneficiary has already pulled. type grant struct { total uint64 claimed uint64 start int64 duration int64 } var ( // balances holds liquid, already-claimed tokens — transferable like a // normal ERC-20 balance. balances = map[address]uint64{} // grants holds at most one active vesting schedule per beneficiary. grants = map[address]*grant{} // totalSupply is every token ever minted (vesting or liquid). totalSupply uint64 ) var ( errZeroAddress = errors.New("vestoken: zero address") errZeroAmount = errors.New("vestoken: amount must be positive") errZeroDuration = errors.New("vestoken: duration must be positive") errGrantActive = errors.New("vestoken: beneficiary already has an unclaimed grant") errNoGrant = errors.New("vestoken: no active grant for this account") errInsufficient = errors.New("vestoken: insufficient balance") errOverflow = errors.New("vestoken: supply overflow") errSpoofedRealm = errors.New("vestoken: spoofed realm") errNotUser = errors.New("vestoken: caller is not a user account") ) // caller returns the immediate caller's address for a crossing function, // enforcing the gno 0.9 authentication primitive: cur must be the live // crossing frame before its Previous() is trusted. func caller(cur realm) address { if !cur.IsCurrent() { panic(errSpoofedRealm) } prev := cur.Previous() if !prev.IsUser() { panic(errNotUser) } return prev.Address() } // MintVested opens a new linear vesting grant for `to`: `amount` tokens // unlock block-by-block over the next `durationBlocks` blocks, starting now. // A beneficiary may only hold one active grant at a time. Crossing function. func MintVested(cur realm, to address, amount uint64, durationBlocks int64) { _ = caller(cur) // authenticate the crossing frame; anyone may open a grant in this demo if !to.IsValid() { panic(errZeroAddress) } if amount == 0 { panic(errZeroAmount) } if durationBlocks <= 0 { panic(errZeroDuration) } if g, ok := grants[to]; ok && g.claimed < g.total { panic(errGrantActive) } if totalSupply+amount < totalSupply { panic(errOverflow) } totalSupply += amount grants[to] = &grant{ total: amount, start: runtime.ChainHeight(), duration: durationBlocks, } } // Claim pulls whatever has unlocked so far from the caller's grant into // their liquid balance, and returns how much was claimed. Crossing function. func Claim(cur realm) uint64 { who := caller(cur) g, ok := grants[who] if !ok { panic(errNoGrant) } unlocked := vestedAmount(g, runtime.ChainHeight()) claimable := unlocked - g.claimed if claimable == 0 { return 0 } g.claimed = unlocked balances[who] += claimable if g.claimed >= g.total { delete(grants, who) } return claimable } // vestedAmount computes how much of g has unlocked by the given height. func vestedAmount(g *grant, height int64) uint64 { elapsed := height - g.start if elapsed <= 0 { return 0 } if elapsed >= g.duration { return g.total } return g.total * uint64(elapsed) / uint64(g.duration) } // Transfer moves `amount` of the caller's liquid (already-claimed) balance // to `to`. Crossing function. func Transfer(cur realm, to address, amount uint64) { from := caller(cur) if !to.IsValid() { panic(errZeroAddress) } if amount == 0 { return } if balances[from] < amount { panic(errInsufficient) } balances[from] -= amount balances[to] += amount } // BalanceOf returns the liquid (claimed, transferable) balance of holder. func BalanceOf(holder address) uint64 { return balances[holder] } // GrantOf returns the beneficiary's active grant, if any: total amount, // amount claimed so far, and the amount currently claimable. func GrantOf(beneficiary address) (total uint64, claimed uint64, claimable uint64) { g, ok := grants[beneficiary] if !ok { return 0, 0, 0 } unlocked := vestedAmount(g, runtime.ChainHeight()) return g.total, g.claimed, unlocked - g.claimed } // TotalSupply returns every token ever minted, vesting or liquid. func TotalSupply() uint64 { return totalSupply } // Name returns the token name. func Name() string { return name } // Symbol returns the token symbol. func Symbol() string { return symbol } // Render produces the gnoweb Markdown view: metadata, active vesting // grants, and liquid balances — both tables sorted by address for // deterministic output. Render is NOT a crossing function. func Render(path string) string { var b strings.Builder b.WriteString("# " + name + " (" + symbol + ")\n\n") b.WriteString("A linearly-vesting ERC-20-style token on gno.land — ") b.WriteString("new supply unlocks block-by-block instead of landing instantly.\n\n") b.WriteString("- **Name:** " + name + "\n") b.WriteString("- **Symbol:** " + symbol + "\n") b.WriteString("- **Total supply:** " + strconv.FormatUint(totalSupply, 10) + "\n") b.WriteString("- **Current height:** " + strconv.FormatInt(runtime.ChainHeight(), 10) + "\n\n") b.WriteString("## Active grants\n\n") grantKeys := sortedAddrKeys(grantAddrs()) if len(grantKeys) == 0 { b.WriteString("_No active grants — call `MintVested` to open one._\n\n") } else { b.WriteString("| Beneficiary | Total | Claimed | Claimable now |\n") b.WriteString("| --- | ---: | ---: | ---: |\n") for _, k := range grantKeys { g := grants[address(k)] unlocked := vestedAmount(g, runtime.ChainHeight()) b.WriteString("| `" + k + "` | " + strconv.FormatUint(g.total, 10) + " | " + strconv.FormatUint(g.claimed, 10) + " | " + strconv.FormatUint(unlocked-g.claimed, 10) + " |\n") } b.WriteString("\n") } b.WriteString("## Liquid balances\n\n") balKeys := sortedAddrKeys(balanceAddrs()) if len(balKeys) == 0 { b.WriteString("_No claimed balances yet — call `Claim` once a grant has unlocked._\n") return b.String() } b.WriteString("| Address | Amount |\n") b.WriteString("| --- | ---: |\n") for _, k := range balKeys { b.WriteString("| `" + k + "` | " + strconv.FormatUint(balances[address(k)], 10) + " |\n") } return b.String() } func grantAddrs() []string { keys := make([]string, 0, len(grants)) for addr := range grants { keys = append(keys, addr.String()) } return keys } func balanceAddrs() []string { keys := make([]string, 0, len(balances)) for addr, bal := range balances { if bal == 0 { continue } keys = append(keys, addr.String()) } return keys } func sortedAddrKeys(keys []string) []string { sort.Strings(keys) return keys }
- #8/gno.MemPackageType
- #9 MPUserProd
Result log
msg:0,success:true,log:,events:[]