Skip to content
0xSlots

SlotFactory

Deploys slots as beacon proxies sharing one implementation. The factory itself is UUPS-upgradeable. A slot's address comes from CREATE2 over the chain id and the factory's slot count — not from its terms — so a slot is identified by its address.

function createSlot(SlotInit calldata init) external returns (address slot);
 
event SlotCreated(address indexed slot, address indexed recipient, address indexed creator,
                  address currency, address module);

One call, one struct. A new slot parameter goes into SlotInit, never into a second creation function.

SlotInit

struct SlotInit {
    IERC20  currency;          // address(0) = native ETH
    address manager;           // required exactly when something is mutable; zero otherwise
    bool    mutableTax;        // rate and minimum runway
    bool    mutableRecipient;
    bool    mutableModule;
    TaxTerms taxTerms;
    ModuleTerms moduleTerms;
}
 
struct TaxTerms {
    address recipient;         // receives the tax, less any module fee; never zero
    uint16  rateBps;           // basis points of the price per 30 days, 1..10_000
    uint32  minRunwaySeconds;  // tax a buy must fund, in seconds; zero = no minimum
}
 
struct ModuleTerms {
    address module;            // zero for none
    bytes   settings;          // its configuration; must be empty when module is
}

initialize refuses, before anything is stored:

  • a currency that is neither zero nor a contract (InvalidCurrency);
  • a manager on a fully immutable slot, or none on a mutable one (InvalidManager);
  • a rateBps outside 1..10,000 (InvalidTax) or a zero recipient (InvalidRecipient);
  • settings without a module, or a module that rejects its settings, declares invalid scopes or fee, or cannot answer within its gas allowance (InvalidModule, InvalidModuleFee, ModuleTooExpensive, or the module's own error from validateSettings).

If the module declares onInstall, it is called at creation.

minRunwaySeconds guards against a buyer declaring a huge price with no means to pay: the deposit must cover that many seconds of tax at the declared price. It rounds up — a truncating client-side copy lands one unit short and reverts. It is capped at one year (MAX_MIN_RUNWAY; InvalidRunway above it), here and on every later proposal.

Collecting in bulk

function collectAll(address[] calldata slots) external returns (uint256[] memory collected);
function collectFrom(address slot) external returns (uint256 amount);
function isSlot(address) external view returns (bool);

collectAll calls collect() on each slot this factory created, isolating failures: a slot with nothing to collect, a reverting afterCallbacksMustSucceed module or an address the factory did not create leaves a zero rather than failing the batch. Nothing about it is privileged — collect() is permissionless and pays each slot's own recipient. Simulate it to learn the amounts.

Admin

function transferAdmin(address next) external;              // hand over the role
function upgradeBeacon(address implementation) external;    // move every slot at once
function implementation() external view returns (address);
 
event AdminTransferred(address indexed from, address indexed to);
event BeaconUpgraded(address indexed implementation);

upgradeBeacon moves every slot to a new implementation in one call — they all share one beacon. Slot storage is ERC-7201-namespaced and append-only for that reason.