A fair-launch lockdrop (Astroport-style).
During the lock window, participants lock lockToken for a duration of their choosing. Their reward
allocation is weighted by amount × lock duration, so committing more, for longer, earns a larger slice of
the reward pool. The operator funds the rewardToken pool and finalizes it; after a participant's chosen
unlock time they reclaim their locked tokens, and once finalized they claim their pro-rata reward. It bootstraps
a new token's distribution by rewarding genuine, time-weighted commitment rather than mercenary capital.
lock(amount, unlockTime)— during the window;unlockTimemust belockWindowEnd + [minDuration, maxDuration]. Weight =amount × (unlockTime − lockWindowEnd). One lock per address.fundRewards(amount)— operator tops up the reward pool (only before finalization).finalize()— operator freezes the pool and opens claims (only after the lock window). Once.withdraw()— reclaim locked tokens once your unlock time passes.claim()— claimrewardPool × yourWeight / totalWeightonce finalized. Once.- Views:
pendingReward(user),isLockOpen(),locks(user).
ReentrancyGuard+SafeERC20; all pulls credit the actual amount received (fee-on-transfer safe).- Rewards are frozen at
finalize()before any claim, so allocations can't shift under claimants; pro-rata uses flooredmulDiv, so the pool is never over-distributed. - Locked principal is never at risk — it is always returned in full at unlock, independent of the reward math.
- Fully tested: unit tests for the amount×duration weighting, pro-rata rewards, unlock/withdraw, and every
phase/access guard, plus two stateful invariants (
fail_on_revert = true):- lock-token conservation — the lock-token balance always equals the un-withdrawn locked total;
- reward conservation — the reward-token balance always equals the reward pool minus rewards claimed.
forge test
FOUNDRY_INVARIANT_FAIL_ON_REVERT=true forge test --match-path "test/Lockdrop.invariant.t.sol"LOCK_TOKEN=0x... REWARD_TOKEN=0x... LOCK_WINDOW_END=1750000000 \
MIN_DURATION=2592000 MAX_DURATION=31536000 \
forge script script/Deploy.s.sol --rpc-url "$RPC_URL" --broadcastNot audited. Reference implementation — review before any real deployment.