# 0xSlots > Collectively Owned Slots Protocol ## Contracts ### Addresses #### Base Mainnet (8453) | Contract | Address | | ----------------- | -------------------------------------------- | | SlotFactory (Hub) | `0xbf2F890E8F5CCCB3A1D7c5030dBC1843B9E36B0e` | | MetadataModule | `0x0896A90c75d2cF195427fb4128f3f4b28f5b2Ef7` | #### Base Sepolia (84532) | Contract | Address | | ----------------- | -------------------------------------------- | | SlotFactory (Hub) | `0xc44De86e2A5f0C47f1Ba87C36DaBf54275814DEb` | | MetadataModule | `0x65392ac6fd773a9bd36c200abf848c8fa3c9f7f8` | | BatchCollector | `0xd3c7090C2F89c5132C3f91DD1da4bCffEAe10e13` | | ERC721Slots | `0x65e88189ac09527c5F7da0296ef33C77E5a6BE27` | #### Programmatic Access ```ts import { slotFactoryAddress, getSlotsHubAddress, getSupportedChainIds } from "@0xslots/contracts"; slotFactoryAddress[8453]; // "0xbf2F890E..." getSlotsHubAddress(8453); // Address | undefined getSupportedChainIds(); // [8453, 84532] ``` *** ### ABIs ```ts import { slotAbi, // Individual slot contract slotFactoryAbi, // Factory / Hub contract batchCollectorAbi, // BatchCollector utility erc721SlotsAbi, // ERC721Slots (Harberger NFT collections) metadataModuleAbi, // MetadataModule contract } from "@0xslots/contracts"; ``` #### Key Functions | ABI | Function | Type | Description | | ------------------- | ----------------------------- | ------------ | ------------------------ | | `slotAbi` | `getSlotInfo` | Read | Full slot state | | `slotAbi` | `buy` | Write | Buy or force-buy | | `slotAbi` | `selfAssess` | Write | Update price | | `slotAbi` | `topUp` / `withdraw` | Write | Manage deposit | | `slotAbi` | `release` | Write | Release slot | | `slotAbi` | `collect` | Write | Collect tax | | `slotAbi` | `liquidate` | Write | Liquidate insolvent slot | | `slotAbi` | `multicall` | Write | Batch calls | | `slotFactoryAbi` | `createSlot` / `createSlots` | Write | Deploy slots | | `metadataModuleAbi` | `updateMetadata` / `tokenURI` | Write / Read | Manage metadata | ## Getting Started ### Installation :::code-group ```bash [pnpm] pnpm add @0xslots/sdk @0xslots/contracts ``` ```bash [npm] npm install @0xslots/sdk @0xslots/contracts ``` ::: ### Create a Client ```ts import { SlotsClient, SlotsChain } from "@0xslots/sdk"; import { createPublicClient, createWalletClient, http } from "viem"; import { base } from "viem/chains"; const publicClient = createPublicClient({ chain: base, transport: http() }); // Read-only const client = new SlotsClient({ chainId: SlotsChain.BASE, publicClient }); // Read + write const walletClient = createWalletClient({ account: privateKeyToAccount("0x..."), chain: base, transport: http(), }); const client = new SlotsClient({ chainId: SlotsChain.BASE, publicClient, walletClient }); ``` ### Read Slots ```ts const { slots } = await client.getSlots({ first: 10 }); const { slot } = await client.getSlot({ id: "0x..." }); const info = await client.getSlotInfo("0x..."); // direct RPC read ``` ### Write Actions ```ts import { parseEther } from "viem"; // Buy a slot (ERC-20 approval handled automatically) await client.buy({ slot: "0x...", depositAmount: parseEther("1"), selfAssessedPrice: parseEther("10"), }); await client.selfAssess("0xSlot", parseEther("20")); await client.topUp("0xSlot", parseEther("0.5")); await client.withdraw("0xSlot", parseEther("0.1")); await client.release("0xSlot"); await client.collect("0xSlot"); // permissionless await client.liquidate("0xSlot"); // permissionless, earns bounty ``` ### React Integration ```tsx import { useSlotsClient, useSlotAction, useSlotOnChain } from "@0xslots/sdk/react"; function SlotView({ address }: { address: string }) { const { data: slot, isLoading } = useSlotOnChain(address, 8453); const { buy, busy } = useSlotAction({ onSuccess: (label, hash) => console.log(`${label}: ${hash}`), }); if (isLoading) return
Loading...
; if (!slot) return
Not found
; return (

Price: {slot.price.toString()}

Occupant: {slot.occupant ?? "Vacant"}

); } ``` ## Protocol ### Architecture Every slot is a **standalone smart contract** — no shared state. Slots are deployed through a Factory using CREATE2 (ERC-1167 minimal proxy clones), giving deterministic addresses based on `keccak256(recipient, currency, config)`. ``` Factory └─ createSlot(recipient, currency, config, initParams) └─ CREATE2 → Slot Proxy (clone) ├─ immutable: recipient, currency, mutableTax, mutableModule, manager └─ mutable: occupant, price, deposit, taxPercentage, module ``` #### Immutable Parameters (set at deployment) | Parameter | Description | | --------------- | ----------------------------------------------------------------- | | `recipient` | Receives tax revenue. Can be EOA, multisig, Splits, DAO. | | `currency` | ERC-20 token for pricing, deposits, and tax. | | `mutableTax` | Whether tax rate can be changed by the manager. | | `mutableModule` | Whether module can be changed by the manager. | | `manager` | Can propose config updates. `address(0)` if both flags are false. | #### Mutable State | Field | Description | | --------------- | ---------------------------------------------- | | `occupant` | Current holder (zero address = vacant). | | `price` | Self-assessed price (0 when vacant). | | `deposit` | Escrowed ERC-20 for tax payments. | | `taxPercentage` | Tax rate in basis points per month (100 = 1%). | | `module` | Hook contract address. | | `collectedTax` | Accumulated uncollected tax. | *** ### Slot Lifecycle #### `buy(depositAmount, selfAssessedPrice)` Settles tax on current occupant → refunds them → transfers price from buyer → stores deposit → sets new price. If vacant: buyer just deposits and self-assesses. #### `release()` Occupant exits. Settles tax → refunds deposit → slot becomes vacant. #### `selfAssess(newPrice)` / `topUp(amount)` / `withdraw(amount)` Occupant manages price and deposit. Cannot withdraw below `minDepositSeconds`. #### `liquidate()` Anyone can call when deposit is depleted. Slot returns to vacant, liquidator earns a bounty. #### `collect()` Permissionless. Sends accumulated tax to the `recipient`. *** ### Tax System Tax accrues linearly: `taxOwed = price × taxPercentage × elapsed / (30 days × 10000)` Tax is settled on every state change (`buy`, `release`, `selfAssess`, `topUp`, `withdraw`, `liquidate`). The occupant maintains a deposit that covers future tax. When it's depleted, anyone can `liquidate()` and earn a bounty (`liquidationBountyBps`). If `mutableTax` is true, the manager can propose rate changes via `proposeTaxUpdate()`. Changes only apply on the next ownership transition — the current occupant's terms are never changed under them. *** ### Roles | Role | Revenue | Config | Slot State | | ------------- | ---------------------------- | --------------------------- | --------------------------- | | **Recipient** | Receives tax + sale proceeds | No control | No control | | **Manager** | No revenue | Proposes tax/module changes | No control | | **Occupant** | Pays tax | No control | Sets price, manages deposit | No single address has "god-mode" admin powers. Recipient and manager can be the same address but don't have to be. *** ### Modules Modules are optional hook contracts called on ownership transitions (`buy`, `release`, `liquidate`). #### MetadataModule The primary module. Lets the occupant attach a URI (metadata) to the slot. ```ts await client.modules.metadata.updateMetadata(moduleAddress, slotAddress, "ipfs://..."); const uri = await client.modules.metadata.getURI(moduleAddress, slotAddress); ``` If `mutableModule` is true, the manager can propose module changes via `proposeModuleUpdate()`. Same pending-update model as tax changes. :::warning Modules execute arbitrary code during slot transitions. Only use verified modules or modules you've audited. ::: ## Subgraph The subgraph indexes all slot deployments, ownership transitions, tax events, and metadata updates. ### Endpoints | Network | Endpoint | | ------------ | -------------------------------------------------------------------------------------------- | | Base Mainnet | `https://gateway.thegraph.com/api/subgraphs/id/4sZrdv1SFzN4KzE9jiWDRuUyM4CnCrmvQ54Rv1s65qUq` | | Base Sepolia | `https://gateway.thegraph.com/api/subgraphs/id/Z361DLoMdPh9WAopH7shJP8WoXYAB9XeKrLUCTYjdZR` | ### Key Entities | Entity | Key Fields | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `Slot` | `id`, `recipient`, `currency`, `occupant`, `price`, `deposit`, `taxPercentage`, `collectedTax`, `totalCollected`, `createdAt`, `metadata` | | `Account` | `id`, `type` (EOA/CONTRACT/DELEGATED/SPLIT), `slotCount`, `occupiedCount`, `slotsAsRecipient`, `slotsAsOccupant` | | `Currency` | `id`, `name`, `symbol`, `decimals` | | `Module` | `id`, `name`, `version`, `verified` | | `MetadataSlot` | `id`, `uri`, `rawJson`, `adType`, `updatedBy`, `updateCount` | ### Event Entities All events include `slot`, `timestamp`, `blockNumber`, and `tx`. | Entity | Key Fields | | ---------------------- | --------------------------------------------------------- | | `BoughtEvent` | `buyer`, `previousOccupant`, `price`, `selfAssessedPrice` | | `ReleasedEvent` | `occupant`, `refund` | | `LiquidatedEvent` | `liquidator`, `occupant`, `bounty` | | `SettledEvent` | `taxOwed`, `taxPaid`, `depositRemaining` | | `TaxCollectedEvent` | `recipient`, `amount` | | `PriceUpdatedEvent` | `oldPrice`, `newPrice` | | `MetadataUpdatedEvent` | `author`, `uri`, `rawJson`, `adType` | ### Example Queries #### List Slots ```graphql { slots(first: 10, orderBy: createdAt, orderDirection: desc) { id recipient occupant price deposit taxPercentage currency { symbol decimals } module { name verified } createdAt } } ``` #### Slot Activity ```graphql query GetSlotActivity($slot: String!) { boughtEvents(where: { slot: $slot }, orderBy: timestamp, orderDirection: desc) { buyer price selfAssessedPrice timestamp } releasedEvents(where: { slot: $slot }, orderBy: timestamp, orderDirection: desc) { occupant refund timestamp } liquidatedEvents(where: { slot: $slot }, orderBy: timestamp, orderDirection: desc) { liquidator bounty timestamp } } ``` **SDK equivalent:** `await client.getSlotActivity({ slot: "0x..." })` #### Account Lookup ```graphql query GetAccount($id: ID!) { account(id: $id) { id type slotCount occupiedCount slotsAsRecipient { id price totalCollected } slotsAsOccupant { id price deposit } } } ``` ## SlotsClient The main entry point for interacting with 0xSlots. [Source](https://github.com/nezz0746/0xSlots/blob/main/packages/sdk/src/client.ts) ### Constructor ```ts import { SlotsClient, SlotsChain } from "@0xslots/sdk"; const client = new SlotsClient({ chainId: SlotsChain.BASE, // BASE (8453) or BASE_SEPOLIA (84532) publicClient, // viem PublicClient walletClient, // viem WalletClient (for writes) subgraphApiKey, // optional — Bearer token }); ``` ### Subgraph Queries ```ts const { slots } = await client.getSlots({ first: 10, skip: 0 }); const { slot } = await client.getSlot({ id: "0x..." }); const { slots } = await client.getSlotsByRecipient({ recipient: "0x..." }); const { slots } = await client.getSlotsByOccupant({ occupant: "0x..." }); const { factory } = await client.getFactory(); const { modules } = await client.getModules({ first: 10 }); const activity = await client.getSlotActivity({ slot: "0x..." }); const events = await client.getRecentEvents({ first: 20 }); ``` ### RPC Reads ```ts const info = await client.getSlotInfo("0x..."); // Returns: recipient, currency, occupant, price, deposit, taxPercentage, // taxOwed, secondsUntilLiquidation, insolvent, pendingTax, pendingModule, ... ``` ### Write Methods All return `Promise`. ERC-20 approval is handled automatically (atomic via EIP-5792 when supported). ```ts // Factory await client.createSlot({ recipient, currency, config, initParams }); await client.createSlots({ ...params, count: 5n }); // Slot interactions await client.buy({ slot, depositAmount, selfAssessedPrice }); await client.selfAssess(slot, newPrice); await client.topUp(slot, amount); await client.withdraw(slot, amount); await client.release(slot); await client.collect(slot); await client.liquidate(slot); // Manager await client.proposeTaxUpdate(slot, newPct); await client.proposeModuleUpdate(slot, newModule); await client.cancelPendingUpdates(slot); await client.setLiquidationBounty(slot, newBps); // Multicall await client.multicall(slot, [ { functionName: "selfAssess", args: [newPrice] }, { functionName: "topUp", args: [amount] }, ]); ``` ### Modules ```ts await client.modules.metadata.getSlots({ first: 10 }); await client.modules.metadata.getURI(moduleAddress, slotAddress); await client.modules.metadata.updateMetadata(moduleAddress, slotAddress, "ipfs://..."); ``` ## React Hooks Wagmi-wired hooks from `@0xslots/sdk/react`. Requires `wagmi`, `viem`, and `@tanstack/react-query` as peer dependencies. ### `useSlotsClient` Creates a memoized `SlotsClient` from wagmi's public and wallet clients. ```tsx import { useSlotsClient } from "@0xslots/sdk/react"; const client = useSlotsClient(); // connected chain const client = useSlotsClient(SlotsChain.BASE); // override chain const client = useSlotsClient(SlotsChain.BASE, "api-key"); // with subgraph key ``` *** ### `useSlotAction` Unified write executor with transaction lifecycle tracking. ```tsx import { useSlotAction } from "@0xslots/sdk/react"; const { buy, selfAssess, topUp, withdraw, release, collect, liquidate, createSlot, proposeTaxUpdate, proposeModuleUpdate, updateMetadata, exec, // generic executor busy, // isPending || isConfirming isPending, // wallet interaction in progress isConfirming, // waiting for on-chain confirmation isSuccess, activeAction, // label of current action } = useSlotAction({ onSuccess: (label, hash) => console.log(`${label}: ${hash}`), onError: (label, error) => console.error(`${label}: ${error}`), }); ``` *** ### `useSlotOnChain` Fetches a single slot's state from on-chain via RPC. Auto-invalidates on new blocks. ```tsx import { useSlotOnChain } from "@0xslots/sdk/react"; const { data: slot, isLoading, refetch } = useSlotOnChain(address, 8453); // slot: { occupant, price, deposit, taxPercentage, insolvent, currencySymbol, ... } ``` *** ### `useSlotsOnChain` Fetches multiple slots via multicall. Deduplicates currency metadata requests. ```tsx import { useSlotsOnChain } from "@0xslots/sdk/react"; const { data: slots, isLoading } = useSlotsOnChain(addresses, 8453); ```