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
currencythat is neither zero nor a contract (InvalidCurrency); - a manager on a fully immutable slot, or none on a mutable one (
InvalidManager); - a
rateBpsoutside 1..10,000 (InvalidTax) or a zero recipient (InvalidRecipient); settingswithout 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 fromvalidateSettings).
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.