Skip to Content
Locks (veCLAIM)

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
  • 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).lockEnd as an effective lockEnd. For AutoMax it is time-dependent.
  • For offchain ve math: if autoMax == true, use ve = 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: mineMarket may 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 furnaceBurnAndWithdraw after 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 / 100 with 2 decimals (use <0.01% for tiny values).

Notes:

  • totalVeCurrent is 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.