README.md
gno.land/r/moul/demo/vault
Step 7 and last of the r/moul/demo walkthrough: a time-locked vault over the
GRC20 token from grc20.
Withdrawing is deliberately two steps, so that a stolen key does not drain the vault in one transaction:
1func Deposit(cur realm, amount uint, recoverAddress address) // caller -> vault
2func Unvault(cur realm, amount uint) // arm the timer
3func Redeem(cur realm, amount uint) // after the lock
4func Recover(cur realm, target address) // escape hatch
Unvault moves an amount from vaulted to pending and stamps
unlockAt = ChainHeight() + 100. Redeem refuses until the chain passes that
height. In between, the depositor (or whoever is watching) can call Recover
to push the still-locked balance to the recovery address recorded at deposit
time. That is the pattern: a withdrawal announces itself, and the announcement
window is when a theft can be reversed.
Deposited tokens live at the realm's own address (cur.Address()), which is
what makes the vault custodial rather than an accounting fiction.
What is wrong with it on purpose
This is a demo, and two things would sink it in production:
Recoverhas no caller check. Anyone can callRecover(target)for any target. It only ever pays out to the address the target itself recorded, so it cannot steal, but it is a free denial of service: a bystander can rewind anyone's deposit at will.- Only
Unvault/Redeemare covered by tests.Recoverhas none, flagged as aTODOin the test file.
Read it for the two-step withdrawal shape, not as a vault to deploy. (Note that
r/moul/x/daily/vault is a different thing entirely: a key-value store, not a
token vault.)
Previous: grc20.
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:

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.