1// Package vault demonstrates a minimal time-locked token vault built on top of2// the grc20 token package. Deposited tokens are held at the realm's own3// address; withdrawing them is a two-step process: Unvault marks an amount for4// withdrawal and starts a lock timer, then (once the lock elapses) Redeem moves5// the tokens back to the depositor.6package vault78import (9 "chain/runtime"10 "chain/runtime/unsafe"1112 tokens "gno.land/p/nt/grc20/v0"13)1415// test1 receives the initial mint at construction.16var test1 = address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")1718// lockDuration is the number of blocks that must pass between Unvault and19// Redeem.20const lockDuration = int64(100)2122// Token and FooVault-equivalent state are exported for reading; privateLedger23// stays unexported (unrestricted mint/burn/transfer).24var (25 Token *tokens.Token26 privateLedger *tokens.PrivateLedger27 vaultAddr address // address holding all deposited tokens2829 vaulted = map[address]uint{} // still-locked deposits per owner30 pending = map[address]uint{} // amount marked for withdrawal31 unlockAt = map[address]int64{} // block height at which pending unlocks32 recoverOf = map[address]address{} // recovery address per depositor33)3435func init(cur realm) {36 // generate the token and mint some tokens to test1.37 Token, privateLedger = tokens.NewToken("Foo Token", "FOO", 4, 0, cur)38 privateLedger.Mint(test1, 100000000)3940 // the vault holds deposited tokens at the realm's own address.41 vaultAddr = cur.Address()42}4344func MyBalance() int64 {45 return Token.BalanceOf(unsafe.OriginCaller())46}4748// Deposit moves amount tokens from the caller into the vault, recording a49// recovery address for later use.50func Deposit(cur realm, amount uint, recoverAddress address) {51 caller := unsafe.OriginCaller()52 if err := privateLedger.Transfer(caller, vaultAddr, int64(amount)); err != nil {53 panic(err)54 }55 vaulted[caller] += amount56 recoverOf[caller] = recoverAddress57}5859// Recover sends target's still-locked deposit to its recorded recovery address.60func Recover(cur realm, target address) {61 amount := vaulted[target]62 if amount == 0 {63 return64 }65 dest := recoverOf[target]66 if err := privateLedger.Transfer(vaultAddr, dest, int64(amount)); err != nil {67 panic(err)68 }69 vaulted[target] = 070}7172// Unvault marks amount of the caller's deposit for withdrawal and starts the73// lock timer.74func Unvault(cur realm, amount uint) {75 caller := unsafe.OriginCaller()76 if vaulted[caller] < amount {77 panic("insufficient vaulted balance")78 }79 vaulted[caller] -= amount80 pending[caller] += amount81 unlockAt[caller] = runtime.ChainHeight() + lockDuration82}8384// Redeem transfers amount of the caller's matured pending withdrawal back to85// the caller.86func Redeem(cur realm, amount uint) {87 caller := unsafe.OriginCaller()88 if pending[caller] < amount {89 panic("insufficient pending balance")90 }91 if runtime.ChainHeight() < unlockAt[caller] {92 panic("still locked")93 }94 if err := privateLedger.Transfer(vaultAddr, caller, int64(amount)); err != nil {95 panic(err)96 }97 pending[caller] -= amount98}99Deposit(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}, amount uint, recoverAddress string)
MyBalance() int64
Recover(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}, target string)
Redeem(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}, amount uint)
Unvault(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}, amount uint)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.