Locks (veCLAIM)
This section documents VeClaimNFT (veCLAIM).
What a lock is
- veCLAIM is an ERC721 NFT.
- Each tokenId represents a lock:
- amount (CLAIM principal + any locked bonus)
- lockEnd (unlock timestamp when autoMax is OFF; when autoMax is ON, reads use an effective end = block.timestamp + MAX_LOCK_DURATION, rolling)
- autoMax (bool)
- listed (bool, used by MarketRouter)
Constraints (v1.0.0 constants):
- MIN_LOCK_AMOUNT = 1,000 CLAIM
- MIN_LOCK_DURATION = 7 days
- MAX_LOCK_DURATION = 365 days
- MAX_VE_NFTS_PER_USER = 32
ve math (linear decay)
For a lock with:
- amount = A
- lockEnd = E
- now = N
ve at time N is:
if N >= E: ve = 0
else: ve = floor( A * (E - N) / MAX_LOCK_DURATION )Implications:
- more CLAIM => more ve
- more remaining time => more ve (for non-AutoMax locks)
- For non-AutoMax locks, ve decays linearly to 0 at lockEnd
- For AutoMax locks, remaining time is always MAX_LOCK_DURATION, so ve stays equal to amount (no decay) while AutoMax is ON
[//]: # (SOLID salute to Andre Cronje + Solidly for the ve-lock (vote-escrow) mechanics lineage.)
AutoMax
AutoMax is an automatic “keep me at max ve forever” switch.
Rules:
- AutoMax is opt-in per lock. It can be set at creation or toggled later via
setAutoMax(tokenId, enabled). - While autoMax is true:
- the effective lockEnd is always
block.timestamp + MAX_LOCK_DURATION(rolling) - remaining time is always MAX_LOCK_DURATION, so
ve == amount(no decay) - the lock MUST NOT be treated as expired while autoMax is true
- the lock never becomes unlockable;
unlock(tokenId)MUST revert
- the effective lockEnd is always
- Turning AutoMax off sets
lockEnd = block.timestamp + MAX_LOCK_DURATION(a fresh max-duration decaying lock).- The user can unlock after 1 year.
Integrator notes:
- Treat
getLockInfo(tokenId).lockEndas an effective lockEnd. For AutoMax it is time-dependent. - For offchain ve math: if
autoMax == true, useve = amount.
Transfer restrictions (Furnace-only)
Direct transfers are restricted.
Strict mode invariant:
- The Furnace is the only counterparty for listing settlement.
- There are no user-to-user lock transfers or sales.
Expected behavior:
- MarketRouter (
mineMarket) is the only supported transfer gateway for lock management. - All lock exits route to the Furnace (listing settlement or instant sellback).
- Strict mode:
mineMarketmay only transfer a lock into the Furnace (to == furnace) for settlement/sellback.- MarketRouter may transfer a lock into the Furnace during settlement/sellback.
- The Furnace never transfers veCLAIM NFTs; it burns locks via
furnaceBurnAndWithdrawafter taking custody.
- Locks marked listed = true are considered frozen for mutation until delisted or settled.
Checkpointing and cached totals
VeClaimNFT exposes global checkpointing to keep aggregates consistent and gas-bounded.
Key views:
- totalLockedClaim()
- totalVeCached()
- globalLastTs()
Key calls:
- checkpointGlobalState()
- checkpointTotalVe()
Where they are used:
- MineCore runs a gas-guarded checkpoint loop before takeover finalization.
- MaintenanceHub.poke() runs best-effort checkpointing as general upkeep.
UI: show Total ve and royalty share (%)
The official UI uses ve data to show players their current power and weight in royalties.
Recommended reads (ordered):
userVe = VeClaimNFT.veBalanceOf(user)(accurate per-user aggregate)totalVe = VeClaimNFT.totalVeCurrent()(view-only; denom for UI share display)
UI share calculation:
- If
totalVe == 0: share = 0 - Else:
shareBps = floor(userVe * 10_000 / totalVe) - Display
sharePct = shareBps / 100with 2 decimals (use<0.01%for tiny values).
Notes:
totalVeCurrentis view-only (no checkpoint in this call); it is only as fresh as the last global checkpoint.- If checkpoints lag, share can be stale. Best-effort display.
Integrator notes
- For on-chain rewards accounting (e.g. ShareholderRoyalties flush), use totalVeCached() only after ve.checkpointTotalVe().
- For user balances, veBalanceOf(user) is definitive but can be more expensive than cached reads.
- Never assume tokenIds are sequential per user.