Skip to Content
⚠️ Alert: Sylan is under active development—only trust contract/wallet addresses announced on our official channels; we will never DM you, ask for funds, or run surprise airdrops/presales.
ContractsSylan Vesting

Sylan Vesting

Owner‑managed, non‑upgradeable vesting contract for SYL. Supports cliff + linear vesting with slice periods, revocable schedules, and pull‑payment withdrawals. Uses Solady libraries (Ownable, ReentrancyGuard, SafeTransferLib) and a minimal ERC‑20 interface.

Timestamps in this contract are seconds (Solidity block.timestamp), not milliseconds.


What it does

  • Lock an amount of SYL for a beneficiary and release it linearly after an optional cliff.
  • Allow the owner to revoke revocable schedules (releasing any vested portion first).
  • Keep a running total of allocated tokens so the owner can only withdraw unallocated balances.

Data model

struct VestingSchedule { address beneficiary; // who receives tokens uint256 cliff; // timestamp = start + cliffSeconds uint256 start; // vesting start (unix seconds) uint256 duration; // total duration (seconds) uint256 slicePeriodSeconds; // vest accrues in these step sizes (>=1) bool revocable; // can owner revoke? uint256 amountTotal; // total allocated to this schedule uint256 released; // amount already claimed bool revoked; // if true, no more vesting accrues }
  • Schedule ID: keccak256(abi.encodePacked(holder, index))
    Helpers: computeNextVestingScheduleIdForHolder(holder), computeVestingScheduleIdForAddressAndIndex(holder, index).

Lifecycle

1) Create

createVestingSchedule(beneficiary, start, cliffSeconds, duration, slicePeriodSeconds, revocable, amount)onlyOwner

Checks:

  • amount > 0, duration > 0, slicePeriodSeconds ≥ 1, duration ≥ cliffSeconds
  • Contract getWithdrawableAmount() ≥ amount
  • Records schedule under a new scheduleId and increments the holder’s schedule count.
  • Emits VestingCreated(scheduleId, beneficiary, amount)

2) Release

release(scheduleId, amount) — callable by beneficiary or owner

  • Computes releasable via _computeReleasableAmount (see Math).
  • Transfers amount SYL to beneficiary and bumps released.
  • Emits TokensReleased(scheduleId, beneficiary, amount)

3) Revoke (optional)

revoke(scheduleId)onlyOwner

  • Requires revocable == true and not already revoked.
  • First releases any currently vested amount, then marks the schedule revoked.
  • Unreleased remainder is removed from vestingSchedulesTotalAmount.
  • Emits VestingRevoked(scheduleId, beneficiary, unreleasedAmount)

4) Withdraw unallocated

withdraw(amount)onlyOwner

  • Transfers out tokens that are not locked by active schedules.
  • Guarded by nonReentrant and checks amount ≤ getWithdrawableAmount().
  • Emits TokensWithdrawn(to, amount)

Math (releasable amount)

Let t = block.timestamp.

  • If t < cliff or revoked == true0.
  • If t ≥ start + durationamountTotal - released (everything left).
  • Else linear with slicing:
    • timeFromCliff = t - cliff
    • vestedSeconds = floor(timeFromCliff / slice) * slice
    • vestedAmount = amountTotal * vestedSeconds / duration
    • releasable = max(vestedAmount - released, 0)

getCurrentTime() is an internal virtual helper returning block.timestamp (useful for testing via inheritance/mocks).


External interface (selected)

Writes

  • createVestingSchedule(address,uint256,uint256,uint256,uint256,bool,uint256)
  • release(bytes32,uint256)
  • revoke(bytes32)
  • withdraw(uint256)

Reads

  • getVestingSchedule(bytes32) → VestingSchedule
  • computeReleasableAmount(bytes32) → uint256
  • getReleasableAmountForBeneficiary(address) → uint256 (sums across that address)
  • getVestingSchedulesCountByBeneficiary(address) → uint256
  • getVestingSchedulesCount() → uint256
  • getVestingSchedulesTotalAmount() → uint256
  • getVestingScheduleByAddressAndIndex(address,uint256) → VestingSchedule
  • getVestingIdAtIndex(uint256) → bytes32
  • getToken() → address
  • getWithdrawableAmount() → uint256
  • computeNextVestingScheduleIdForHolder(address) → bytes32
  • computeVestingScheduleIdForAddressAndIndex(address,uint256) → bytes32

Receive/Fallback

  • receive() and fallback() are present (no-op) to avoid accidental reverts on ETH transfers.

Events

  • VestingCreated(bytes32 scheduleId, address beneficiary, uint256 amount)
  • TokensReleased(bytes32 scheduleId, address beneficiary, uint256 amount)
  • VestingRevoked(bytes32 scheduleId, address beneficiary, uint256 unreleasedAmount)
  • TokensWithdrawn(address to, uint256 amount)

Minimal ABI (client claims)

For UIs that only let a beneficiary claim and inspect schedules:

[ {"type":"function","stateMutability":"view","name":"getVestingSchedulesCountByBeneficiary","inputs":[{"name":"beneficiary","type":"address"}],"outputs":[{"type":"uint256"}]}, {"type":"function","stateMutability":"view","name":"getVestingScheduleByAddressAndIndex","inputs":[{"name":"holder","type":"address"},{"name":"index","type":"uint256"}],"outputs":[{"type":"tuple","components":[ {"name":"beneficiary","type":"address"}, {"name":"cliff","type":"uint256"}, {"name":"start","type":"uint256"}, {"name":"duration","type":"uint256"}, {"name":"slicePeriodSeconds","type":"uint256"}, {"name":"revocable","type":"bool"}, {"name":"amountTotal","type":"uint256"}, {"name":"released","type":"uint256"}, {"name":"revoked","type":"bool"} ]}]}, {"type":"function","stateMutability":"view","name":"computeReleasableAmount","inputs":[{"name":"scheduleId","type":"bytes32"}],"outputs":[{"type":"uint256"}]}, {"type":"function","stateMutability":"nonpayable","name":"release","inputs":[{"name":"scheduleId","type":"bytes32"},{"name":"amount","type":"uint256"}],"outputs":[]} ]

Integration tips

  • Decimals: token uses 18 decimals; format UI amounts accordingly.
  • Claim UX: display computeReleasableAmount(id) and allow partial or full release.
  • Safety: wrap release in a try/catch or pre‑read the releasable amount to avoid spurious reverts.
  • Admin UI: hide revoke unless revocable == true; warn that revoke is final and only frees the unreleased portion.

Admin & security notes

  • Only the owner can create schedules, revoke, and withdraw unallocated tokens.
  • withdraw(amount) cannot reduce reserved funds below active allocations (vestingSchedulesTotalAmount).
  • Consider holding ownership in a Gnosis Safe; keep the vesting contract funded before creating schedules.

Verification checklist

  • Schedules created for correct beneficiaries/amounts (unit tests).
  • Cliff/duration/slice parameters reviewed; duration ≥ cliff.
  • getWithdrawableAmount() always ≥ 0; treasury top‑ups tested.
  • Revoke path tested releases vested portion first.
  • getReleasableAmountForBeneficiary matches sum of individual schedules.

Out of scope for this page

  • Token roles & upgrades → Sylan Token page.
  • Addresses/ABIs → Architecture → Addresses & ABIs.
  • Presale/vesting allocations → see Presale and project tokenomics docs.
Last updated on