Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Protocol

SlotFactory

The factory deploys slots via BeaconProxy (UUPS-upgradeable). Each slot gets a deterministic address based on keccak256(recipient, currency, config).

function createSlot(
    address recipient,
    IERC20 currency,
    SlotConfig memory config,
    SlotInitParams memory initParams
) external returns (address slot);
 
function createSlots(
    address recipient,
    IERC20 currency,
    SlotConfig memory config,
    SlotInitParams memory initParams,
    uint256 count
) external returns (address[] memory slots)

SlotConfig (immutable)

struct SlotConfig {
    bool mutableTax;      // Can the tax rate be changed?
    bool mutableModule;   // Can the module be changed?
    address manager;      // Who can propose config changes (address(0) = no one)
}

SlotInitParams

struct SlotInitParams {
    uint256 taxPercentage;         // Tax rate in bps per month (100 = 1%)
    address module;                // Hook contract (address(0) = none)
    uint256 liquidationBountyBps;  // Bounty for liquidators in bps
    uint256 minDepositSeconds;     // Minimum deposit to cover (protocol min: 1 day)
}

The factory also maintains a module registry — modules can be verified so users know they're safe to use.


Slot

Each slot is a standalone smart contract. No shared state between slots.

Core operations

/// Buy a slot (or take it from current occupant)
function buy(address account, uint256 depositAmount, uint256 selfAssessedPrice) external;
 
/// Leave the slot, get remaining deposit back
function release() external;
 
/// Change your self-assessed price
function selfAssess(uint256 newPrice) external;
 
/// Add to your deposit
function topUp(uint256 amount) external;
 
/// Withdraw excess deposit
function withdraw(uint256 amount) external;
 
/// Liquidate an insolvent slot (anyone can call, earns bounty)
function liquidate() external;
 
/// Send accumulated tax to the recipient (anyone can call)
function collect() external;

Manager operations

If mutableTax or mutableModule is true, the manager can propose changes. Updates only apply on the next ownership transition — the current occupant's terms never change under them.

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

Tax

Tax accrues linearly: taxOwed = price * taxPercentage * elapsed / (30 days * 10000)

The occupant maintains a deposit that covers future tax. When depleted, anyone can liquidate() and earn a bounty.

Roles

RoleRevenueConfigSlot state
RecipientReceives tax + sale proceedsNo controlNo control
ManagerNo revenueProposes tax/module changesNo control
OccupantPays taxNo controlSets price, manages deposit

Modules

Modules are optional hook contracts attached to a slot. They implement ISlotsModule (which extends IERC165):

interface ISlotsModule is IERC165 {
    // Identity
    function name() external view returns (string memory);
    function version() external view returns (string memory);
 
    // Lifecycle hooks (called by the Slot contract)
    function onTransfer(uint256 slotId, address from, address to) external;
    function onPriceUpdate(uint256 slotId, uint256 oldPrice, uint256 newPrice) external;
    function onRelease(uint256 slotId, address from) external;
 
    // Fee configuration
    function feeBps() external view returns (uint256);
    function feeRecipient() external view returns (address);
 
    // Metadata
    function moduleURI() external view returns (string memory);
}

Lifecycle Hooks

Called by the Slot contract during state transitions. msg.sender is always the slot contract.

HookTriggered byTypical use
onTransfer(slotId, from, to)buy()Clear metadata, update access control
onPriceUpdate(slotId, oldPrice, newPrice)selfAssess()React to price changes
onRelease(slotId, from)release(), liquidate()Clean up slot state

Fee Configuration

Modules can take a cut from collected tax. The fee is deducted when collect() is called on the slot.

FunctionDescription
feeBps()Fee in basis points (e.g. 500 = 5%). Taken from collected tax before it reaches the recipient. Return 0 for no fee.
feeRecipient()Address that receives module fees — can be an EOA, multisig, Splits contract, etc.

Metadata

FunctionDescription
moduleURI()URI pointing to module metadata (e.g. ipfs://Qm... with JSON containing image, description). Can be empty.

ERC-165

Modules must return true for both ISlotsModule.interfaceId and IERC165.interfaceId in supportsInterface(). The factory uses this to verify a contract is a valid module.

MetadataModule

The primary module. Lets the occupant attach a URI (e.g. IPFS) to the slot. Clears metadata on transfer and release.

// Set metadata for a slot (occupant only)
function updateMetadata(address slot, string calldata uri) external;
 
// Read metadata
function tokenURI(address slot) external view returns (string memory);

FeedPostModule

Like MetadataModule but supports trusted routers for atomic buy+post flows. Used by The Feed.

// Direct post (occupant only)
function updateMetadata(address slot, string calldata uri) external;
 
// Post via trusted router (atomic buy+post)
function postFor(address account, address slot, string calldata uri) external;