Overview
The main payment operator contract with pluggable conditions for flexible authorization logic.- Type: Operator instance (one per fee recipient + configuration)
- Deployment: Via PaymentOperatorFactory
- Immutability: No pause switch, no upgrade path
- Configuration: 10 slots for conditions and hooks
- Use Cases: Marketplace, subscriptions, streaming, grants, custom flows
Immutable Fields
State
10-Slot Configuration
Condition Slots (Before Actions)
Condition Slots (Before Actions)
- AUTHORIZE_PRE_ACTION_CONDITION - Who can authorize payments
- CHARGE_PRE_ACTION_CONDITION - Who can charge partial amounts
- CAPTURE_PRE_ACTION_CONDITION - Who can capture funds from escrow
- VOID_PRE_ACTION_CONDITION - Who can refund during escrow
- REFUND_PRE_ACTION_CONDITION - Who can refund after capture
address(0) = always allowHook Slots (After Actions)
Hook Slots (After Actions)
- AUTHORIZE_POST_ACTION_HOOK - Record authorization (for example, timestamp)
- CHARGE_POST_ACTION_HOOK - Record charge event
- CAPTURE_POST_ACTION_HOOK - Record capture
- VOID_POST_ACTION_HOOK - Record void
- REFUND_POST_ACTION_HOOK - Record refund
address(0) = no recording (no-op)Key Methods
authorize()
Authorizes a payment and locks funds in escrow.paymentInfo- Payment info struct (must haveoperator == address(this),feeReceiver == address(this))amount- Amount to authorizetokenCollector- Address of the token collector contractcollectorData- Data to pass to the token collector
- Check
AUTHORIZE_PRE_ACTION_CONDITION(if set) - Check fee bounds compatibility
- Store fees at authorization time (prevents protocol fee changes from breaking capture)
- Call
escrow.authorize() - Call
AUTHORIZE_POST_ACTION_HOOK(if set) - Emit
AuthorizeExecuted
AUTHORIZE_PRE_ACTION_CONDITION (default: anyone)
charge()
Direct charge - collects payment and immediately transfers to receiver (no escrow hold).paymentInfo- Payment info struct (must haveoperator == address(this),feeReceiver == address(this))amount- Amount to chargetokenCollector- Address of the token collector contractcollectorData- Data to pass to the token collector
- Check
CHARGE_PRE_ACTION_CONDITION(if set) - Check fee bounds compatibility
- Call
escrow.charge()- funds go directly to receiver - Accumulate protocol fees for later distribution
- Call
CHARGE_POST_ACTION_HOOK(if set) - Emit
ChargeExecuted
CHARGE_PRE_ACTION_CONDITION (default: anyone)
Unlike
authorize(), funds go directly to receiver without escrow hold. Refunds are only possible via refund().capture()
Releases funds from escrow to receiver (capture).paymentInfo- Payment info structamount- Amount to capturedata- Optional pass-through data for the pre/post action plugins
- Check
CAPTURE_PRE_ACTION_CONDITION(if set) - Use fees stored at authorization time
- Call
escrow.capture() - Accumulate protocol fees for later distribution
- Call
CAPTURE_POST_ACTION_HOOK(if set) - Emit
CaptureExecuted
CAPTURE_PRE_ACTION_CONDITION
Marketplace example: Receiver OR StaticAddressCondition(arbiter) + escrow passed
Subscription example: StaticAddressCondition(serviceProvider)
DAO example: StaticAddressCondition(daoMultisig)
void()
Returns all escrowed funds to the payer before capture. Full-only:escrow.void() empties the authorization in one transaction.
paymentInfo- Payment info structdata- Optional pass-through data for the pre/post action plugins
- Check
VOID_PRE_ACTION_CONDITION(if set) - Call
escrow.void()to return escrowed funds to payer - Call
VOID_POST_ACTION_HOOK(if set) - Emit
VoidExecuted
VOID_PRE_ACTION_CONDITION
Marketplace example: StaticAddressCondition(arbiter) for disputes
Return policy example: Receiver OR StaticAddressCondition(arbiter)
DAO example: StaticAddressCondition(daoMultisig)
Subscription example: address(0), no voids allowed
refund()
Refunds a payment after capture (after the receiver has the funds).paymentInfo- Payment info structamount- Amount to refund to payertokenCollector- Address of the token collector that will source the refundcollectorData- Data to pass to the token collector (for example, signatures)
- Check
REFUND_PRE_ACTION_CONDITION(if set) - Call
escrow.refund()- token collector enforces permission - Call
REFUND_POST_ACTION_HOOK(if set) - Emit
RefundExecuted
REFUND_PRE_ACTION_CONDITION. The token collector also enforces permission (for example, the receiver must have approved it, or collectorData contains the receiver’s signature).
Marketplace example: StaticAddressCondition(arbiter) - post-delivery disputes
Return policy example: Receiver - voluntary returns
Most configurations: address(0) - no refunds
Fee System (Modular, Additive)
Fees are additive and modular:totalFee = protocolFee + operatorFee
Fee Architecture
totalFee = protocolFee + operatorFee, split between protocolFeeRecipient and the operator’s FEE_RECEIVER. For a worked example with concrete amounts, see the Fee System.
Fee Locking:
The operator calculates fees at authorize() time and stores them in authorizedFees[hash]. This stops later protocol fee changes from breaking capture of already-authorized payments.
- Arbiter (marketplace with disputes)
- Service Provider (subscriptions)
- Platform Treasury (platform-controlled)
- DAO Multisig (governance-controlled)
Fee Distribution
Fees accumulate in the operator contract. CalldistributeFees(token) to disburse them:
Protocol Fee Changes (7-day Timelock)
Protocol fee calculator and recipient changes require a 7-day timelock onProtocolFeeConfig. See Fee System: 7-day timelock for the full queue, wait, execute workflow.
Security Features
- ReentrancyGuardTransient - EIP-1153 transient storage for gas-efficient reentrancy protection
- Ownership - Solady’s Ownable with 2-step transfer
- Timelock - 7-day delay on protocol fee changes (operator fees are immutable)
- Immutable Core - Escrow, conditions, and fee configuration stay fixed after deployment
Next Steps
Periphery Contracts
AuthCaptureEscrow, RefundRequest, and other supporting contracts.
Conditions
Explore the pluggable condition system.
Factories
Deploy operators with factory patterns.
Examples
See real-world configuration examples.
