Deployments
Where addresses come from
DeployProtocol writes one record per contract to
apps/contracts/deployments/<chainId>/<Contract>.json, with an address, the
startBlock the indexer begins from, and a version. Only records carrying a
version belong to this protocol: the retired protocol's scripts wrote the
same filenames, so @0xslots/contracts, the indexer and the deploy script all
ignore a record without one.
@0xslots/contracts generates its address maps from those records, so a
contract appears on exactly the chains it has been deployed to. CHAINS,
DEFAULT_CHAIN and isDeployedOn(chainId) are derived from
slotFactoryAddress, so the set of usable chains is exactly the set that has a
SlotFactory recorded. Chains sort local, then testnets, then mainnets, so
DEFAULT_CHAIN is never a mainnet by accident.
Addresses are CREATE2 through the canonical deployer, salted by contract name and version, so the same code with the same admin and constructor arguments lands at the same address on every chain.
Deploying to a chain
Each chain the deploy tooling knows has a config file in
apps/contracts/deployments/config/ — today anvil, Base, Base Sepolia and
Ethereum Sepolia. From the repo root:
pnpm protocol deploy --dry --chain base-sepolia # simulate, send nothing
pnpm protocol deploy --chain base-sepoliaLocal anvil — 31337
pnpm dev:local at the repo root starts a chain, deploys the protocol with the
same DeployProtocol script and runs an indexer against it. Local addresses move
whenever a contract's code changes, so read them back rather than copying them:
cat apps/contracts/deployments/31337/SlotFactory.jsonCHAINS includes anvil only when NODE_ENV === "development". Bundlers inline
NODE_ENV, so a production build drops it at compile time. The address maps
themselves are unconditional.
Naming a module
A slot stores its module as a bare address. @0xslots/contracts ships a small
catalogue of the modules a client can name, per chain:
import { findKnownModule, knownModules } from "@0xslots/contracts";
const known = findKnownModule(chainId, slot.module);
// known?.name — "AdLand", "Minimum tenure", or undefined for a module this
// client does not recogniseA module appears in knownModules on exactly the chains it is deployed to,
because the entries are derived from the deployment records. An unknown address
reads back as undefined rather than a guess — the honest answer for a module
anyone can deploy.
There is one MinimumTenureModule per chain. The window a slot enforces is its
own settings, so a single deployment serves every duration.
ABIs and addresses
import {
slotAbi,
slotFactoryAbi,
offerBookAbi,
minimumTenureModuleAbi,
adLandAbi,
slotCollectiveAbi,
slotCollectiveFactoryAbi,
slotBoundNftAbi,
slotBoundNftFactoryAbi,
slotBoundNftWrapperAbi,
} from "@0xslots/contracts";
import {
slotFactoryAddress,
offerBookAddress,
minimumTenureModuleAddress,
adLandAddress,
slotCollectiveFactoryAddress,
slotImplementationAddress, // the beacon implementation, not a slot
deployBlockOf, // the lower bound for historical log queries
} from "@0xslots/contracts";
const factory = slotFactoryAddress[chainId]; // Address | undefined