SlotsClient
The main entry point for reading and writing slots. It talks to the contracts over RPC — it does not query the indexer. Source
Constructor
import { SlotsClient } from "@0xslots/sdk";
const client = new SlotsClient({
publicClient, // viem PublicClient — every read
walletClient, // viem WalletClient — every write; needs an account and a chain
factoryAddress, // SlotFactory — only createSlot and the collectAll family
offerBookAddress, // OfferBook — defaults to the one recorded for the wallet's chain
lensAddress, // SlotLens — slotState, slotStates, moduleUpdate; same default
});Every field is optional; a method that needs a missing one throws a
SlotsError naming it. createSlotsClient(config) is the same thing as a
function.
Reads
const state = await client.slotState(slot);
// occupant, price, deposit, taxOwed, isVacant, isInsolvent,
// secondsUntilLiquidation, currency, taxRateBps, minRunwaySeconds, recipient,
// manager, mutableTax, mutableRecipient, mutableModule, module, settings,
// fee, scopes, pending, occupiedSince, lastSettled, collectedTax, tenureIdslotState is one SlotLens.getSlotInfo call, and slotStates(slots) reads
many in one call. Individual reads exist for the common
fields (occupant, price, deposit, taxOwed, module, scopes,
fee, terms, tenureId, …), plus:
| Method | Returns |
|---|---|
quoteBuy(slot, account, deposit) | what buy will charge, including account's debt |
minDepositForBuy(slot, price) | the smallest deposit a buy accepts, under ripe queued terms |
minDepositToHold(slot, price) | the floor selfAssess and withdraw enforce |
pending(slot) | queued terms, with appliesAt and applies |
hasRipeTerms(slot) | whether the next buy lands them — the chain's clock, not yours |
fee(slot) / scopes(slot) | the module's fee and scopes, as the slot copied them |
moduleUpdate(slot) | { current, declared, feeDiffers, scopesDiffer }, from the lens |
debtOf(slot, account) / claimableOf(slot, account?) | debt carried / credits to claim |
isOperator(slot, operator) | live, per tenure |
Writes
All return Promise<Hash>. ERC-20 approval is handled automatically.
await client.createSlot(init); // see SlotInit below
await client.simulateCreateSlot(init); // the address it would get
await client.buy({ slot, account, selfAssessedPrice, depositAmount /*, maxPayment */ });
await client.simulateBuy(params); // throws the module's own error on refusal
await client.liquidateAndBuy(params); // ERC-20 slots, one multicall
await client.selfAssess(slot, newPrice);
await client.topUp(slot, amount);
await client.withdraw(slot, amount);
await client.manageTerms(slot, { newPrice, topUpAmount, withdrawAmount });
await client.setOperator(slot, operator, true);
await client.release(slot);
await client.collect(slot); // permissionless
await client.collectAll(slots); // through the factory
await client.simulateCollectAll(slots); // what each would pay out
await client.liquidate(slot); // permissionless — no bounty
await client.claim(slot, account); // pull a credited payoutbuy takes an account — who becomes occupant; it need not be the signer. It
sends the quote it just read as maxPayment unless you pass one, so the
transaction pays what it was quoted or reverts. maxPayment: 0n disables the
ceiling on purpose.
manageTerms reprices and moves the deposit as one submission, in the only
order that passes both funding checks: top up, reprice, withdraw. A native
top-up goes first as its own transaction, because multicall is not payable.
SlotInit
import { zeroAddress } from "viem";
await client.createSlot({
currency: zeroAddress, // native ETH
manager: "0x...", // required exactly when something is mutable
mutableTax: true,
mutableRecipient: false,
mutableModule: true,
taxTerms: { recipient: "0x...", rateBps: 100, minRunwaySeconds: 86_400 },
moduleTerms: { module, settings }, // omit for no module
});viem encodes a struct by component name, so the SDK takes a typed shape and
assertSlotInit refuses what the contract would refuse before any gas is spent.
Manager operations
await client.proposeTerms(slot, { taxRateBps: 200 });
await client.proposeTerms(slot, { recipient, minRunwaySeconds: 3_600 });
await client.proposeTerms(slot, { moduleTerms: { module, settings } });
await client.proposeTerms(slot, { moduleTerms: NO_MODULE }); // remove the module
await client.cancelTerms(slot, TERMS.MODULE); // one term
await client.cancelTerms(slot); // everything (ALL_TERMS)
const update = await client.moduleUpdate(slot);
if (update.feeDiffers) await client.acceptFee(slot, update.declared!.fee); // now
if (update.scopesDiffer) await client.acceptScopes(slot, update.declared!.scopes); // next buy
await client.setManager(slot, next);Presence, not truthiness, decides what proposeTerms queues. Terms land at the
next buy after the one-hour delay — see How a slot works.
Modules
import { minimumTenureModuleAddress } from "@0xslots/contracts";
import { toHex } from "viem";
const module = minimumTenureModuleAddress[chainId];
const settings = toHex(7n * 24n * 3600n, { size: 32 }); // abi.encode(uint256 window)
await client.validateSettings(module, settings); // { ok: true } | { ok: false, reason }
await client.readScopes(module, settings); // what the module asks, today
await client.readFee(module, settings);
await client.moduleMetadata(module); // parsed metadata() JSON, or nullvalidateSettings runs the same check the slot runs at proposal and resolves with
the module's own reason rather than throwing, so a form can show it.
Constants: TERMS, ALL_TERMS, SCOPE_BITS, unpackScopes, NO_MODULE,
NO_SETTINGS, MAX_PRICE, MAX_TAX_BPS, BASIS_POINTS, MONTH_SECONDS,
TERMS_DELAY_SECONDS.
Selling through the OfferBook
// as the bidder — posting moves nothing; approve the book first
await client.approveOfferBook(slot, await client.offerCost(slot, me, price, deposit));
await client.postOffer({ slot, price, deposit, expiry });
// as the occupant — grant once per tenure, then accept
await client.authorizeOfferBook(slot);
const { best } = await client.offerBoard(slot);
if (best) await client.acceptOffer(slot, best.id, best.price);offerBoard returns only live offers, highest first, as the book itself judges
them. See OfferBook.
Collections and collectives
Two more clients ship beside SlotsClient:
CollectionsClientcreates and mints slot-bound NFT collections (createCollection,quoteMint,approveMint,mint,setBaseURI).CollectivesClientcreates and governsSlotCollectives — a split recipient with role-gated management.COLLECTIVE_ROLESnames the roles:taxproposes tax rates,policyproposes modules and accepts their updates (proposeModule,acceptFee,acceptScopes,cancelModuleProposal),splitedits the payout. Holders are set at creation withtaxManagers,policyManagersandsplitManagers.