Skip to content

feat: add VestingWallet contract with linear/cliff vesting and revocation#52

Open
Bosun-Josh121 wants to merge 2 commits into
SmartDropLabs:mainfrom
Bosun-Josh121:feat/vesting-wallet
Open

feat: add VestingWallet contract with linear/cliff vesting and revocation#52
Bosun-Josh121 wants to merge 2 commits into
SmartDropLabs:mainfrom
Bosun-Josh121:feat/vesting-wallet

Conversation

@Bosun-Josh121

Copy link
Copy Markdown

Summary

Implements the VestingWallet Soroban contract for time-locked token distributions, closing #33.

Tokens are deposited at initialisation and unlock linearly over a configurable ledger range, with an optional cliff period before which nothing is releasable. An optional revocation flag lets an admin cancel the unvested portion at any time while preserving the beneficiary's already-vested balance.

What was implemented

Contract: soroban/contracts/vesting-wallet/

Function Description
initialize(beneficiary, token, total_amount, start_ledger, cliff_ledger, end_ledger, revocable, admin) One-time setup; pulls total_amount from admin into the contract
release() Permissionless; transfers all vested-but-unclaimed tokens to the stored beneficiary
revoke() Admin-only, requires revocable = true; returns unvested tokens to admin and freezes the vested cap
vested_amount() Returns 0 before cliff, linear proportion during schedule, full total after end
released_amount() Cumulative tokens already transferred to beneficiary
releasable() vested_amount() - released_amount()

Vesting formula (linear with cliff)

if current < cliff  { return 0; }
if current >= end   { return total; }
total * (current - start) / (end - start)

The cliff acts as a lock: nothing is releasable before cliff_ledger, but the linear calculation still uses start_ledger as its origin — so the beneficiary earns the "cliff period" proportionally once the cliff is reached.

Revocation behaviour

  • Admin calls revoke() → unvested tokens (total - vested_at_revoke) are returned immediately
  • vested_amount() is frozen at the revocation snapshot; beneficiary can still release() whatever was vested
  • A second revoke() call returns AlreadyRevoked

Tests (20 passing)

All acceptance criteria are covered:

  • test_cliff_not_reached_releasable_is_zero — nothing releasable before cliff
  • test_past_end_full_amount_releasable — full amount available after schedule ends
  • test_revoke_midway_beneficiary_keeps_vested_portion — vested portion stays claimable after revocation
  • test_vested_amount_with_cliff — 0 before cliff, correct proportion at cliff and midpoint
  • test_release_transfers_correct_amount / test_release_twice_respects_already_released — release accounting
  • test_revoke_sends_unvested_to_admin / test_revoke_after_partial_release_only_returns_unvested
  • test_vested_amount_frozen_after_revoke — no further vesting after revocation
  • test_double_initialize_returns_error / test_revoke_when_not_revocable_returns_error / test_revoke_twice_returns_already_revoked — error guards

Checklist

  • contracts/vesting-wallet/ contract created
  • Linear and cliff vesting implemented
  • release transfers correct amount and updates released_amount
  • revoke only callable by admin and only when revocable = true
  • vested_amount returns 0 before cliff, proportional during vesting, total after end
  • Test: cliff not reached → 0 releasable
  • Test: past end → full amount releasable
  • Test: revoke midway → beneficiary keeps vested portion

Closes #33

…tion

Implements a token vesting wallet for SmartDrop that supports cliff + linear
unlocking schedules and optional admin revocation. Beneficiaries claim via
release(); admins can revoke unvested tokens while preserving already-vested
amounts. Ships with 20 tests covering all acceptance criteria.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add vesting contract for time-locked token releases

1 participant