Skip to content
0xSlots

Slot

One slot, one contract. Deployed as a BeaconProxy by the SlotFactory.

Occupant operations

function buy(
    address account,
    uint256 depositAmount,
    uint256 selfAssessedPrice
) external;

Takes the slot. Pays the current occupant their declared price, plus depositAmount into escrow. account is who becomes occupant — it need not be msg.sender, so you can buy on someone's behalf.

Vacant slots cost only the deposit; there is no incumbent to pay.

function selfAssess(uint256 newPrice) external;   // occupant or operator
function topUp(uint256 amount) external;          // anyone may fund a slot
function withdraw(uint256 amount) external;       // occupant only
function release() external;                      // occupant only, always allowed

topUp is deliberately open: anyone can keep a slot solvent, including the recipient who benefits from it staying occupied.

withdraw takes back surplus deposit, subject to minDepositSeconds. release returns the remaining deposit and vacates.

Permissionless operations

function collect() external;     // push accrued tax to the recipient
function liquidate() external;   // remove an insolvent occupant, earn the bounty
function claim(address account) external;

Anyone may call these. liquidate requires the deposit to be fully exhausted; the bounty is liquidationBountyBps of collected tax, not of anyone's deposit.

claim is the escape hatch for a refund that could not be pushed — a blocklisting token, a contract that reverts on receipt. Rather than let that brick the slot, the amount is credited to withdrawableOf and claimed later.

Delegation

function setOperator(address operator, bool approved) external;
mapping(address => mapping(address => bool)) public isOperator;

Lets another address manage your price without holding the slot. Keyed by occupant, so approvals survive leaving and re-entering.

Manager operations

Only available for whatever was declared mutable at creation.

function proposeTaxUpdate(uint256 newPct) external;
function proposeModuleUpdate(address newModule) external;
function proposePolicyUpdate(address newPolicy) external;
function cancelPendingUpdates() external;
function setLiquidationBounty(uint256 newBps) external;

Changes are proposed, not applied. They take effect at the next occupancy change, so the terms cannot shift under someone mid-tenancy.

Reading state

function getSlotInfo() external view returns (SlotInfo memory);

Everything in one call — identity, live financials, module metadata, pending updates, occupancy policy. Prefer it over assembling individual getters, which costs more round trips and can tear across a state change.

Individual getters exist too:

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);

Tax

Tax accrues per second at taxPercentage basis points per 30 days:

owed = price × taxPercentage × elapsed / (30 days × 10_000)

It is charged against the deposit at the start of every mutating call, so state is always settled before anything else happens. Nothing runs on a timer.

A charge is capped by the remaining deposit. An occupant who has run dry pays less than they owe and becomes liquidatable — which is why accounting must use amounts actually paid rather than amounts theoretically owed.

Events

EventFires on
Boughtoccupancy transfer
Releasedoccupant leaves
Liquidatedinsolvent occupant removed
PriceUpdatedself-assessment
Deposited / Withdrawndeposit changes
Settledtax charged — amounts only
TaxPaidtax charged — with the payer
TaxCollectedtax pushed to the recipient

TaxPaid is the one to reduce over for per-address accounting. Settled carries the same amounts but not who paid them, so it cannot be attributed. TaxPaid always fires, including when a module hook fails.