Slot
The contract behind one position. Every mutating call settles accrued tax before anything else happens, so the numbers are correct whenever anyone looks — nothing runs on a timer.
Occupant operations
function buy(
address account, // who gets SEATED — not necessarily msg.sender
uint256 selfAssessedPrice,
uint256 depositAmount,
uint256 maxPayment // ceiling on the total charged; 0 disables it
) external payable;
function selfAssess(uint256 newPrice) external; // occupant or operator
function topUp(uint256 amount) external payable; // anyone may fund a slot
function withdraw(uint256 amount) external; // occupant only
function release() external; // occupant only, always allowedbuy separates who pays (msg.sender) from who holds (account), so a
contract can acquire a slot on someone's behalf.
Selling into an offer
Slot.sell no longer exists. It took a buyer's signed order and seated them —
a SECOND seating path, resetting the tenure like buy but running beforeSell
instead of beforeBuy, so every hook author had two doors to police.
A consensual sale is now two calls the core already had, performed by the OfferBook inside the occupant's own transaction:
slot.selfAssess(price) // the occupant's price, restated
slot.buy(bidder, price, deposit, max) // the ordinary market pathAtomic, so nothing can take the slot between them. The economics are unchanged —
buy already refunds the outgoing occupant their deposit plus the price.
For the book to make the first call the occupant must make it their operator:
slot.setOperator(book, true); // lapses with the tenure
book.acceptOffer(slot, id); // occupant onlyPermissionless operations
function collect() external; // push accrued tax to the recipient
function liquidate() external; // evict an insolvent occupant
function claim(address account) external;claim withdraws funds credited to an account when a direct payout failed (a
refund to a contract that rejected it, say) — pull, not push.
Delegation
function setOperator(address operator, bool allowed) external; // occupant onlyAn operator may selfAssess on the occupant's behalf — delegate price changes
without handing over the slot.
Manager operations
Only if the slot was created with mutableTax or mutableHook, and only by the
manager. Proposed terms apply after a delay (TERMS_DELAY, 1 day).
function proposeTerms(
uint256 newTax,
address newHook, // address(0) detaches the hook
bytes32 newHookData, // that hook's configuration; must be 0 when it is
bool changeTax,
bool changeHook
) external;
function cancelTerms(bool cancelTax, bool cancelHook) external;Tax and hook are proposed and cancelled independently, with a flag each. They may be governed by different parties behind one manager address; an all-or-nothing cancel would let whoever moves the hook wipe a queued tax change as a side effect. A change touching a mutable dimension you did not open reverts.
newHookData is not a third dimension — it rides under changeHook, because a
hook and the configuration meant for it are one decision. The hook is asked to
validate it now: one that rejects it is refused here rather than attached and
vetoing every callback afterwards.
event TermsProposed(
uint256 taxBps, address hook, bytes32 hookData, bool tax, bool hook_
);
event TermsApplied(uint256 taxBps, address hook, bytes32 hookData);
event TermsCancelled(bool tax, bool hook);Reading state
function getSlotInfo() external view returns (SlotInfo memory);Everything in one call — terms, the attached hook and its snapshotted
HookFlags, live occupancy and financials, and any queued terms. Prefer it over
assembling individual getters, which each re-read and re-settle.
struct SlotInfo {
// terms
address recipient; IERC20 currency; address manager;
uint256 taxBps; uint256 minDepositSeconds;
bool mutableTax; bool mutableHook;
// extension
address hook; bytes32 hookData; HookFlags hookFlags;
// occupancy
address occupant; uint256 price; uint256 deposit;
uint64 occupiedSince; uint64 tenureId; uint64 lastSettled;
// money, as of this block
uint256 taxOwed; uint256 collectedTax;
bool isVacant; bool isInsolvent; uint256 secondsUntilLiquidation;
// queued terms
uint256 pendingTaxBps; address pendingHook; bytes32 pendingHookData;
bool pendingHasTax; bool pendingHasHook;
uint64 pendingProposedAt; bool hasRipeTerms;
}Individual getters exist for the common fields:
function occupant() external view returns (address);
function price() external view returns (uint256);
function deposit() external view returns (uint256);
function taxOwed() external view returns (uint256);
function isInsolvent() external view returns (bool);
function isVacant() external view returns (bool);Events
The occupancy and money log. Sold and OrderCancelled went with sell — a
sale now emits Bought on the slot and Filled on the book.
event Bought(/* buyer, account, price, deposit, … */);
event Released(address indexed occupant, uint256 refund);
event Liquidated(address indexed by, address indexed occupant);
event PriceSet(address indexed by, uint256 oldPrice, uint256 newPrice);
event Deposited(address indexed by, uint256 amount, uint256 total);
event Withdrawn(address indexed occupant, uint256 amount, uint256 left);
event Settled(uint256 owed, uint256 paid, uint256 depositLeft);
event TaxPaid(address indexed payer, uint256 owed, uint256 paid);
event TaxCollected(address indexed recipient, uint256 amount);
event OperatorSet(address indexed operator, bool allowed, uint64 indexed tenureId);
event Credited(address indexed account, uint256 amount); // a push payment failed
event Claimed(address indexed account, uint256 amount); // and was later pulledOperatorSet carries the tenureId it is scoped to. An approval dies with the
tenure and there is no revocation event, so a log without it cannot be replayed
into isOperator — an indexer would show a previous occupant's bot as a
co-signer on somebody else's asking price.
TaxPaid is the one to reduce over for per-address accounting, and it always
fires — including when an after hook call fails. A failed hook emits
HookCallFailed(hook, selector) and is otherwise swallowed; a hook that reverts
its own attachment check is dropped with HookDetached(hook). Neither can block
the operation, which is the whole point of the after side.