Skip to Content
Market (MarketRouter)

Market (MarketRouter)

MarketRouter is the canonical veCLAIM lock management router and only supported transfer gateway.

Strict mode invariant: The Furnace is the only counterparty for listing settlement. No user-to-user lock transfers. Market is for listing/delisting/managing locks as limit sells to Furnace and for bonus target escrow entry orders.

Transfer restriction: VeClaimNFT only allows transfers via mineMarket (MarketRouter), except Furnace custody transfers for sellback.

Design: 0% fee | Duration-based settlement penalty (25% at 365d, ~0.5% at 7d → Furnace reserve) | CLAIM-denominated payouts and budgets

UI mapping

Everything under Furnace: /furnace/market — manage your listings, manage bonus target escrow orders, and run Sell now (no global listings table).

Trading pause

tradingPaused (guardian-controlled):

  • When true: listing + settlement + escrow execution revert
  • Exits still work: delistLock, cancelExpiredListing, cancelBonusTargetEscrow, emergencyDelist (after 7d)

Limit sell (Market listing)

A Market listing is a limit sell intent to the Furnace.

User posts:

  • minClaimOut = Minimum payout (net CLAIM the seller is willing to receive)
  • expiresAtTime = Expiry for the listing (unix seconds; after expiry, it must not be fillable). UI default: 30d.

State: listings[tokenId] = {seller, minClaimOut, expiresAtTime, listedAtTime, active}

ActionMethodNotes
Create listing (limit sell)listLock(tokenId, minClaimOut, expiresAtTime)Must own, approve to MarketRouter, not expired, not listed
DelistdelistLock(tokenId)Always callable (even paused)
Cancel expired (permissionless)cancelExpiredListing(tokenId)Callable by anyone after expiry
Emergency delistemergencyDelist(tokenId)After EMERGENCY_DELIST_MIN_AGE = 7 days

Settlement flow (net payout guard):

  1. Clear listing state (CEI)
  2. Read the live Furnace sell quote (gross): claimOutGross
  3. Calculate penalty:
    • penaltyBps = SELL_ROUND_TRIP_LOSS_MAX_BPS × remainingSec / MAX_LOCK_DURATION
  4. Compute net payout:
    • claimOutNet = claimOutGross - floor(claimOutGross * penaltyBps / 10_000)
  5. Require:
    • block.timestamp <= expiresAtTime
    • claimOutNet >= minClaimOut
  6. Transfer penalty to Furnace reserve
  7. Transfer claimOutNet to seller
  8. ShareholderRoyalties.checkpointTransfer(seller, furnace)
  9. Transfer lock to Furnace and burn

Limit buy (Buy intent) via bonus target escrow

A bonus target escrow is the protocol’s limit buy (Buy intent) mechanism.

It is a CLAIM budget escrowed in MarketRouter that executes into the Furnace when a target quote condition is met.

Not buying from users — this queues an entry into the Furnace at a desired bonus level and lock configuration.

User posts (canonical intent fields):

  • budgetClaim (UI label: Input amount; aka amountIn) = CLAIM budget escrowed in MarketRouter
  • expiresAt (UI label: Expiry) = the latest time this intent may execute
    • implemented via escrowTtlSeconds at creation; extendable up to 90d
  • execution trigger (choose one):
    • minVeOut (UI label: Minimum output) = intent executes only if the live quote yields veOut >= minVeOut
    • targetBonusBps (UI helper) + slippageBps (derives minVeOut at execution from the live quote)
  • lock config: durationSeconds, destinationLockId, createAutoMax

Creation:

  • createBonusTargetEscrowWithTarget(...)
    • stores a target bonus and output slippage tolerance used by executeAutoFurnace
ActionMethod
Create (recommended)createBonusTargetEscrowWithTarget(targetBonusBps, budgetClaim, durationSeconds, createAutoMax, escrowTtlSeconds, destinationLockId, slippageBps)
ExecuteexecuteAutoFurnace(offerId) — reverts if expired, checks target bonus, calls Furnace.enterWithClaimFor
CancelcancelBonusTargetEscrow(offerId) — creator-only, refunds
Cancel expiredcancelExpiredBonusTargetEscrow(offerId) — permissionless after expiry
ExtendextendBonusTargetEscrowExpiry(offerId, newExpiresAt) — creator-only, max 90d

Notes:

  • targetBonusBps (if used) is compared to the live Furnace quote (effective bonus bps).
  • slippageBps is applied as a down-only guard on the quoted veOut to derive minVeOut:
    • minVeOut = floor(veOut * (10_000 - slippageBps) / 10_000)
  • Execution must satisfy (before expiry):
    • block.timestamp <= expiresAt
    • veOut >= minVeOut (explicitly, or implicitly via target bonus + slippage)

Related:

  • Market buy (Buy now / Enter now) is a direct Furnace entry (see Furnace docs). It is immediate and uses slippage-derived output guards (and a short deadline for swap paths).

Sellback paths

All lock exits route to the Furnace:

  1. Limit sell (Market listing): settlement of an active listing when claimOutNet >= minClaimOut and before expiresAtTime.
  2. Market sell (Sell now): immediate sellback
    • sellLockToFurnace(tokenId, minClaimOut, deadline)
    • Frontend derives minClaimOut from slippage and uses a short deadline (TTL) to prevent stale execution.
    • Auto-delists if the lock is listed, then burns the lock.

Events

Defined in src/lib/Events.sol:

EventMeaning
LockListedListing created
LockDelistedListing removed (reason: NORMAL, EMERGENCY, SETTLED_BY_FURNACE, SOLD_TO_FURNACE, EXPIRED)
ListingSettledFurnace settled listing
MarketSellToFurnaceSell now executed via MarketRouter
BonusTargetEscrowCreatedEntry order created
BonusTargetEscrowConfiguredTarget bonus + slippage configured
BonusTargetEscrowAutoFurnaceExecutedEntry order executed into Furnace
BonusTargetEscrowExpiryExtendedEntry order expiry extended
BonusTargetEscrowCancelledEntry order cancelled
BonusTargetEscrowExpiredEntry order expired
TradingPausedChangedPause state changed