Cliff-gated periodic release schedule, with optional admin revocation.
Source at contracts/core/vesting.sil,
design notes at
docs/patterns/core/vesting.md.
Same paste-ready tooling shape as the Ownable canonical walkthrough; this doc focuses on what's specific to vesting.
The two patterns are siblings — both run a periodic-release singleton
with termination = allowed. Choose based on:
- Vesting — has a cliff, after which periodic releases begin.
Designed for compensation/equity flows: employee earns
Xper period, but nothing until the cliff date. - Streaming Payment — no cliff. First release happens at
next_release_timeand continues from there. Designed for service-payment flows: pay $X/month from day one.
Both expose a single beneficiary, both allow a non-beneficiary actor (admin / sender) to terminate. Pick by cliff semantics, not by function-name preference.
npm install
npm run bootstrap:silvercConstructor:
Vesting(
pubkey init_beneficiary,
pubkey init_admin,
int init_total_allocation,
int init_claimed_amount, // = 0 at deploy
int init_cliff_time, // unix seconds
int init_period, // seconds between releases
int init_release_per_period,
bool init_revocable
)
BENEFICIARY=02$(openssl rand -hex 31)
ADMIN=02$(openssl rand -hex 31)
TOTAL=120000000 # full grant
RELEASE=10000000 # per period (so 12 periods total)
PERIOD=2592000 # 30 days
CLIFF=$(date -d '+90 days' +%s)
npx opensilver deploy-plan core.vesting \
--ctor "[\"$BENEFICIARY\", \"$ADMIN\", $TOTAL, 0, $CLIFF, $PERIOD, $RELEASE, true]" \
--network kaspa:testnet-12 \
> vesting-deploy-plan.jsondeployment.entrypoints lists ["claim", "revoke"].
When total_allocation - claimed_amount > release_per_period, the
beneficiary draws release_per_period, the schedule advances:
claimed_amount' = claimed_amount + release_per_period
cliff_time' = cliff_time + period
Note that the per-claim gate is tx.time >= prev_state.cliff_time,
which gets pushed forward each period. The cliff field doubles as the
"next release time" for every subsequent draw — the first claim opens
the cliff, every later claim opens the next period.
When the remaining grant is <= release_per_period, the beneficiary
draws the rest and the schedule ends. termination = allowed is what
makes this branch legal.
revoke requires prev_state.revocable == true (set at deploy; cannot
be changed) and an admin signature. The full unclaimed balance returns
to the admin in a single P2PK output minus the 1000-sompi miner fee.
If you want a non-revocable grant (e.g. a public-commitment retirement
fund) deploy with init_revocable = false. The revoke entrypoint is
still callable but the require(prev_state.revocable) gate rejects
every attempt.
- Compile-validated: ✓ (
tests/vesting-compile.test.ts) - Runtime-validated: ✓ (
runtime-tests/tests/core_runtime.rs, partial + terminal claim + pre-cliff rejection + revoke) - Audit-checked: ✓ (
tests/audit/audit-all-patterns.test.ts)