Skip to main content

Overview

x402r uses an additive modular fee system: totalFee = protocolFee + operatorFee. Each layer is independently configurable, and fees are split between a shared protocol recipient and a per-operator fee recipient.

Fee Architecture

Two Fee Layers

LayerConfigured ByMutabilityRecipient
Protocol FeeProtocolFeeConfig (shared)Swappable calculator with 7-day timelockprotocolFeeRecipient on ProtocolFeeConfig
Operator FeeFEE_CALCULATOR (per-operator)Immutable — set at deploy timeFEE_RECIPIENT on operator

Example Calculation

For a 1000 USDC payment with 50 bps protocol fee + 250 bps operator fee:
ComponentRateAmountGoes To
Protocol Fee50 bps (0.5%)5.00 USDCprotocolFeeRecipient
Operator Fee250 bps (2.5%)25.00 USDCFEE_RECIPIENT
Total Fee300 bps (3%)30.00 USDC
Receiver Gets970.00 USDCPayment receiver

IFeeCalculator Interface

Both protocol and operator fees use the same interface:
interface IFeeCalculator {
    /// @notice Calculate fee in basis points for a payment action
    /// @param paymentInfo The payment info struct
    /// @param amount The payment amount
    /// @param caller The address initiating the action
    /// @return feeBps The fee in basis points (e.g., 50 = 0.5%)
    function calculateFee(
        AuthCaptureEscrow.PaymentInfo calldata paymentInfo,
        uint256 amount,
        address caller
    ) external view returns (uint256 feeBps);
}
This enables flexible fee models — static rates, volume-based tiers, per-token pricing, or any custom logic.

StaticFeeCalculator

The simplest implementation — returns a fixed basis points value for every payment:
contract StaticFeeCalculator is IFeeCalculator {
    uint256 public immutable FEE_BPS;

    constructor(uint256 _feeBps) {
        if (_feeBps > 10000) revert FeeTooHigh();
        FEE_BPS = _feeBps;
    }

    function calculateFee(
        AuthCaptureEscrow.PaymentInfo calldata,
        uint256,
        address
    ) external view override returns (uint256 feeBps) {
        return FEE_BPS;
    }
}
Deploy via StaticFeeCalculatorFactory for deterministic CREATE2 addresses:
// Deploy a 250 bps (2.5%) fee calculator
const calculatorAddress = await staticFeeCalculatorFactory.write.deploy([250]);

// Same fee rate = same address (idempotent)
const sameAddress = await staticFeeCalculatorFactory.write.deploy([250]);

Fee Locking

Fees are locked at authorization time to prevent protocol fee changes from breaking already-authorized payments.
struct AuthorizedFees {
    uint16 totalFeeBps;     // Combined protocol + operator
    uint16 protocolFeeBps;  // Stored separately for accurate distribution
}

mapping(bytes32 paymentInfoHash => AuthorizedFees) public authorizedFees;
Flow:
  1. authorize() calculates fees and stores them in authorizedFees[hash]
  2. release() uses the stored fees — not the current calculator rates
  3. Protocol fee timelocks can’t break already-authorized payments
charge() calculates fees inline since it authorizes and captures atomically — there’s no gap where fees could change.

Fee Bounds Validation

Payers commit to an acceptable fee range via minFeeBps and maxFeeBps in PaymentInfo. The operator validates at authorize() and charge() time:
(uint16 totalFeeBps, uint16 protocolFeeBps) = _calculateFees(paymentInfo, amount);
if (totalFeeBps < paymentInfo.minFeeBps || totalFeeBps > paymentInfo.maxFeeBps) {
    revert FeeBoundsIncompatible(totalFeeBps, paymentInfo.minFeeBps, paymentInfo.maxFeeBps);
}
This ensures payers always know the fee range they’re agreeing to.

Fee Distribution

Fees accumulate in the operator contract and are distributed via distributeFees():
// Anyone can call to distribute fees for a token
operator.distributeFees(usdcAddress);
// Protocol share → protocolFeeRecipient
// Operator share → FEE_RECIPIENT
How it works:
  1. Check operator’s token balance
  2. Protocol share = accumulatedProtocolFees[token] (tracked per-token)
  3. Operator share = remaining balance
  4. Transfer protocol share to protocolFeeRecipient
  5. Transfer operator share to FEE_RECIPIENT
  6. Reset accumulated tracking to 0
distributeFees() is permissionless — anyone can trigger distribution. This prevents fees from being stuck in the operator.

ProtocolFeeConfig

Shared protocol-level fee governance with built-in safety:

Constants

ParameterValue
MAX_PROTOCOL_FEE_BPS500 (5%)
TIMELOCK_DELAY7 days

Calculator Changes (7-Day Timelock)

// Step 1: Queue new calculator
await protocolFeeConfig.queueCalculator(newCalculatorAddress);
// Emits CalculatorChangeQueued(newCalculator, executeAfter)

// Step 2: Wait 7 days

// Step 3: Execute
await protocolFeeConfig.executeCalculator();
// Emits CalculatorChangeExecuted(newCalculator)

// Or cancel:
await protocolFeeConfig.cancelCalculator();

Recipient Changes (7-Day Timelock)

// Step 1: Queue new recipient
await protocolFeeConfig.queueRecipient(newRecipientAddress);

// Step 2: Wait 7 days

// Step 3: Execute
await protocolFeeConfig.executeRecipient();
Operator fees are immutable — set at deploy time via IFeeCalculator and FEE_RECIPIENT. Only protocol fees can be changed (with 7-day timelock). Already-authorized payments use locked fee rates regardless.

Disabling Protocol Fees

Set the protocol fee calculator to address(0) to disable protocol fees entirely. The operator will calculate 0 bps for the protocol layer.

FEE_RECIPIENT Roles

The operator’s FEE_RECIPIENT varies by use case:
Use CaseFEE_RECIPIENTDescription
MarketplaceArbiter addressArbiter earns fees for dispute resolution
SubscriptionService providerProvider earns fees for service delivery
DAO GrantsDAO multisigFees return to treasury
PlatformPlatform treasuryPlatform monetizes via fees
B2B InvoicePlatform addressPlatform earns for facilitation

Fee Configuration Comparison

Use CaseTotal FeeProtocolOperatorReceiver on 1000 USDC
E-Commerce Marketplace300 bps (3%)50 bps250 bps970.00 USDC
Task Execution Platform1300 bps (13%)100 bps1200 bps870.00 USDC
Subscription / SaaS500 bps (5%)50 bps450 bps950.00 USDC
Managed Escrow800 bps (8%)0 bps800 bps920.00 USDC
B2B Invoice100 bps (1%)25 bps75 bps990.00 USDC
Freelance / Gig1000 bps (10%)100 bps900 bps900.00 USDC

Next Steps