Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
DR fee mutualizer · Boson Protocol
Skip to content

DR fee mutualizer

A dispute resolver (DR) charges a fee when an exchange is escalated. Two naïve options for who pays:

  • Seller pays. Then any buyer can grief the seller by escalating frivolously.
  • Buyer pays. Then buyers are discouraged from escalating legitimate complaints.

Neither is good. The DR fee mutualizer introduces a third option: an external mutualizer contract that pools DR fees. A seller pays a small premium per offer (or per-exchange-cap-per-period), and the mutualizer pays the DR fee from the pool if escalation happens. Buyers can escalate freely; sellers don't bear the unilateral risk; the mutualizer operator earns a margin via the premium spread.

The protocol speaks a minimal interface (IDRFeeMutualizer) — isSellerCovered, requestDRFee, returnDRFee. Anyone can deploy a mutualizer; the reference implementation IDRFeeMutualizerClient adds agreement management on top. A seller picks a mutualizer per offer at creation time (or self-mutualises by setting the address to zero).

Flow

requestDRFee is called by the protocol at commit time; if the mutualizer can't cover, the commit reverts (no buyer/seller funds get locked; no voucher issued; safe failure mode).

Self-mutualised offers

Setting DRParameters.mutualizerAddress to address(0) means the seller bears the DR fee themselves out of their treasury. This is the default and the right call for low-DR-fee offers (e.g. canonical free DR), high-trust contexts, or when no mutualizer is available.

Most offers in the protocol today are self-mutualised with feeAmount = 0, so the question is mainly academic until you use a paid DR.

When to use

  • High-value offers with a paid DR — capped per-exchange fee + amortised premium is cheaper than the worst-case escalation.
  • Marketplaces with broad seller bases — operators can run a mutualizer as a service.
  • Sellers with predictable dispute rates — the math works out if your actual dispute rate is below the mutualizer's pricing.

Building a mutualizer

Implement IDRFeeMutualizer (the minimal interface) at a contract you control. You can also implement the optional IDRFeeMutualizerClient to expose admin/seller-facing agreement management — strongly recommended for ecosystem interop:

interface IDRFeeMutualizer is IERC165 {
    function isSellerCovered(
        uint256 sellerId,
        uint256 feeAmount,
        address tokenAddress,
        uint256 disputeResolverId
    ) external view returns (bool);
 
    function requestDRFee(
        uint256 sellerId,
        uint256 feeAmount,
        address tokenAddress,
        uint256 exchangeId,
        uint256 disputeResolverId
    ) external returns (bool success);
 
    function returnDRFee(uint256 exchangeId, uint256 feeAmount) external payable;
}

The protocol calls requestDRFee from the address of ProtocolDiamond; verify the caller and revert on anyone else. returnDRFee is also Diamond-only.

For native-currency agreements, returnDRFee sends msg.value == feeAmount. For ERC-20s it pulls via transferFrom (so your mutualizer needs Diamond as an approved spender), or the Diamond pushes via transfer — implementations vary; check the reference.

IDRFeeMutualizerClient extends with:

  • deposit / withdraw — fund the pool.
  • newAgreement / voidAgreement / payPremium — agreement lifecycle.
  • getAgreement / getAgreementId — reads.

Each agreement specifies a seller, exchange-token, dispute-resolver triple (DR ID 0 = universal across all DRs), plus per-tx cap, total cap, time period, premium, and refundOnCancel. This shape covers the common business models without locking implementations into it.

Configure on an offer

await sdk.createOffer({
  // …offer fields
  // DRParameters used to be just `disputeResolverId`; mutualizerAddress was added later.
  drParameters: {
    disputeResolverId,
    mutualizerAddress: MY_MUTUALIZER_ADDRESS, // or 0x0 for self-mutualised
  },
})

Switching mutualizers later is allowed (mutualizer address is one of the few mutable offer fields). Existing exchanges keep their original mutualizer — the change only affects future commits.

Read coverage status before committing

A buyer or seller dApp should check whether the mutualizer will actually cover before letting the buyer commit, so the commit doesn't revert at the worst possible moment:

const mutualizer = new ethers.Contract(mutualizerAddress, MUTUALIZER_ABI, provider)
const covered = await mutualizer.isSellerCovered(
  sellerId,
  drFeeAmount,
  exchangeToken,
  disputeResolverId,
)
if (!covered) showWarning("Seller's mutualizer is out of funds; commits will revert")

Common gotchas

  • Pool drain risk. If a popular mutualizer runs dry, every commit on every offer pointing at it reverts. Subscribe to balance events or fall back to self-mutualisation.
  • Premium economics. A mutualizer pricing assumes a known dispute rate. Bad actors can stack escalations against a single covered seller. Limit per-seller exposure (maxAmountPerTx, maxAmountTotal).
  • Trust-bootstrapping. A new mutualizer with no track record is a hard sell. Operators typically front the float themselves until premiums catch up.
  • One mutualizer, many sellers. Pool the risk widely so the law of large numbers actually kicks in.
  • DR refusing arbitration returns the fee to the mutualizer (not the seller). The seller's premium is the cost of the option; if the DR refuses, the option's value was the unused coverage.

Related