Architecture — the Diamond
The Boson on-chain protocol is a single contract: a Diamond (EIP-2535) proxy that delegates to 21 handler facets, each implementing one domain. All write paths go through this single address.
Why a Diamond
- One address per chain — easier to integrate, easier to authorize, easier to audit.
- Upgradeable per-facet — protocol can ship a fix to dispute logic without touching offer logic.
- No 24 KB contract-size limit — facets are independent contracts; the Diamond is a router.
- Single storage namespace — all facets share the protocol storage, so cross-domain reads are cheap.
The 21 facets
| Facet | Domain |
|---|---|
OfferHandlerFacet | Create / void offers; offer queries |
ExchangeCommitFacet | commitToOffer, including buyer-initiated |
ExchangeHandlerFacet | Redeem, complete, cancel, revoke vouchers |
DisputeHandlerFacet | Raise / resolve / escalate / decide disputes |
FundsHandlerFacet | Deposit / withdraw treasuries |
AccountHandlerFacet | High-level entity creation |
SellerHandlerFacet | Seller-specific config, royalties, allowlists |
BuyerHandlerFacet | Buyer queries |
DisputeResolverHandlerFacet | DR registration, fees, allowlists |
AgentHandlerFacet | Protocol agents |
GroupHandlerFacet | Token-gating conditions |
BundleHandlerFacet | Multi-item bundles |
TwinHandlerFacet | Conditional twin items (rare) |
MetaTransactionsHandlerFacet | EIP-712 envelopes, token-auth |
OrchestrationHandlerFacet1 / 2 | Atomic combos (createOfferAndCommit, createOfferCommitAndRedeem) |
PriceDiscoveryHandlerFacet | Auction / discovery pricing |
SequentialCommitHandlerFacet | Batch / expiration semantics |
ConfigHandlerFacet | Protocol params, fees, pause regions |
PauseHandlerFacet | Region-based pause control |
ProtocolInitializationHandlerFacet | One-time setup |
Full reference: Reference → Contracts → Diamond facets.
Read paths
For reads, prefer the subgraph (see Eventing & indexing). The Diamond exposes getters too, but reading dozens of fields is expensive without batching.
Surrounding contracts
The Diamond does most of the work, but a few helpers ship alongside:
- Voucher (rNFT) ERC-721 — an individual collection contract used by sellers; minted on commit, burned on redemption. A seller can have multiple voucher contracts.
- Permit2 — the Uniswap permit-as-a-service contract, used by token-auth meta-tx.
- Forwarder — meta-tx forwarder for the Biconomy relay path.
- PriceDiscoveryClient — adapter contract for discovery-based price flows.
Addresses for all of these are in the per-chain JSON files; see Networks → Contract addresses.
Storage
All facets share a single ERC-7201-style storage namespace, with a per-domain struct: protocolEntities, protocolLookups, protocolCounters, protocolStatus, etc. Defined in BosonTypes.sol + ProtocolLib.sol.
You don't read storage directly — use the appropriate getter facet — but knowing the layout helps you decode events.
Upgrades
Per-facet upgrades are governed by a multisig. Upgrade scripts live in boson-protocol-contracts/scripts/upgrade-facets.js. Each upgrade gets a new configId only on breaking changes; routine logic changes preserve the existing configId.
Common footguns
- Don't hard-code a facet's contract address. All facets are reachable via the Diamond; the facet addresses change on upgrade.
- Don't decode events from a single-facet ABI. Use the merged ABI in
@bosonprotocol/common. - Pause regions matter. During an incident, individual regions (offers, exchanges, disputes) can be paused independently. Read
protocol.pausedRegions()before assuming a call will succeed.