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}
| Action | Method | Notes |
|---|---|---|
| Create listing (limit sell) | listLock(tokenId, minClaimOut, expiresAtTime) | Must own, approve to MarketRouter, not expired, not listed |
| Delist | delistLock(tokenId) | Always callable (even paused) |
| Cancel expired (permissionless) | cancelExpiredListing(tokenId) | Callable by anyone after expiry |
| Emergency delist | emergencyDelist(tokenId) | After EMERGENCY_DELIST_MIN_AGE = 7 days |
Settlement flow (net payout guard):
- Clear listing state (CEI)
- Read the live Furnace sell quote (gross):
claimOutGross - Calculate penalty:
penaltyBps = SELL_ROUND_TRIP_LOSS_MAX_BPS × remainingSec / MAX_LOCK_DURATION
- Compute net payout:
claimOutNet = claimOutGross - floor(claimOutGross * penaltyBps / 10_000)
- Require:
block.timestamp <= expiresAtTimeclaimOutNet >= minClaimOut
- Transfer penalty to Furnace reserve
- Transfer
claimOutNetto seller ShareholderRoyalties.checkpointTransfer(seller, furnace)- 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; akaamountIn) = CLAIM budget escrowed in MarketRouterexpiresAt(UI label: Expiry) = the latest time this intent may execute- implemented via
escrowTtlSecondsat creation; extendable up to 90d
- implemented via
- execution trigger (choose one):
minVeOut(UI label: Minimum output) = intent executes only if the live quote yieldsveOut >= minVeOuttargetBonusBps(UI helper) +slippageBps(derivesminVeOutat execution from the live quote)
- lock config:
durationSeconds,destinationLockId,createAutoMax
Creation:
createBonusTargetEscrowWithTarget(...)- stores a target bonus and output slippage tolerance used by
executeAutoFurnace
- stores a target bonus and output slippage tolerance used by
| Action | Method |
|---|---|
| Create (recommended) | createBonusTargetEscrowWithTarget(targetBonusBps, budgetClaim, durationSeconds, createAutoMax, escrowTtlSeconds, destinationLockId, slippageBps) |
| Execute | executeAutoFurnace(offerId) — reverts if expired, checks target bonus, calls Furnace.enterWithClaimFor |
| Cancel | cancelBonusTargetEscrow(offerId) — creator-only, refunds |
| Cancel expired | cancelExpiredBonusTargetEscrow(offerId) — permissionless after expiry |
| Extend | extendBonusTargetEscrowExpiry(offerId, newExpiresAt) — creator-only, max 90d |
Notes:
targetBonusBps(if used) is compared to the live Furnace quote (effective bonus bps).slippageBpsis applied as a down-only guard on the quotedveOutto deriveminVeOut:minVeOut = floor(veOut * (10_000 - slippageBps) / 10_000)
- Execution must satisfy (before expiry):
block.timestamp <= expiresAtveOut >= 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:
- Limit sell (Market listing): settlement of an active listing when
claimOutNet >= minClaimOutand beforeexpiresAtTime. - Market sell (Sell now): immediate sellback
sellLockToFurnace(tokenId, minClaimOut, deadline)- Frontend derives
minClaimOutfrom slippage and uses a shortdeadline(TTL) to prevent stale execution. - Auto-delists if the lock is listed, then burns the lock.
Events
Defined in src/lib/Events.sol:
| Event | Meaning |
|---|---|
LockListed | Listing created |
LockDelisted | Listing removed (reason: NORMAL, EMERGENCY, SETTLED_BY_FURNACE, SOLD_TO_FURNACE, EXPIRED) |
ListingSettled | Furnace settled listing |
MarketSellToFurnace | Sell now executed via MarketRouter |
BonusTargetEscrowCreated | Entry order created |
BonusTargetEscrowConfigured | Target bonus + slippage configured |
BonusTargetEscrowAutoFurnaceExecuted | Entry order executed into Furnace |
BonusTargetEscrowExpiryExtended | Entry order expiry extended |
BonusTargetEscrowCancelled | Entry order cancelled |
BonusTargetEscrowExpired | Entry order expired |
TradingPausedChanged | Pause state changed |