Skip to main content

What Are Conditions?

Conditions are pluggable contracts that control who can perform actions on a PaymentOperator. Each operator has 5 condition slots — one per action:
SlotControls
AUTHORIZE_CONDITIONWho can authorize payments
CHARGE_CONDITIONWho can charge partial amounts
RELEASE_CONDITIONWho can release from escrow
REFUND_IN_ESCROW_CONDITIONWho can refund during escrow
REFUND_POST_ESCROW_CONDITIONWho can refund after release

ICondition Interface

interface ICondition {
    function check(
        AuthCaptureEscrow.PaymentInfo calldata paymentInfo,
        uint256 amount,
        address caller
    ) external view returns (bool allowed);
}
Parameters:
  • paymentInfo — The payment information struct
  • amount — The amount involved in the action (0 for authorization-only checks like refund request status updates)
  • caller — The address attempting the action
Return: true if the caller is authorized, false otherwise.

Default Behavior

Condition slot = address(0) — always returns true (allow). The action is unrestricted. This means you only need to set conditions for slots you want to restrict. Leave the rest as address(0).

Singleton vs Per-Deployment

TypeExamplesDeploy Strategy
SingletonPayerCondition, ReceiverCondition, AlwaysTrueConditionDeployed once, reuse everywhere
Per-deploymentStaticAddressCondition, EscrowPeriod, FreezeDeploy per use case via factories
ComposableAnd/Or/NotCombine existing conditions via factories

Security Rules

Conditions MUST NOT revert. Return false to deny — never revert. The operator converts false into a ConditionNotMet error.
  • Conditions should be view or pure to prevent reentrancy attacks
  • Never make external state-changing calls inside a condition
  • Test thoroughly — edge cases in authorization logic can lead to locked funds

Configuration Patterns

Conditions compose to create flexible authorization policies. Here are common patterns:

Open Authorization, Restricted Release

config = {
    authorizeCondition: ALWAYS_TRUE_CONDITION,  // Anyone can authorize
    authorizeRecorder: escrowRecorder,          // Record time
    releaseCondition: releaseCondition,         // Restricted
    // ...
};

Payer-Only Actions

config = {
    authorizeCondition: PAYER_CONDITION,        // Only payer
    releaseCondition: PAYER_CONDITION,          // Only payer
    // ...
};

Arbiter-Controlled

config = {
    authorizeCondition: ARBITER_CONDITION,      // Only arbiter
    chargeCondition: ARBITER_CONDITION,         // Only arbiter
    releaseCondition: ARBITER_CONDITION,        // Only arbiter
    refundInEscrowCondition: ARBITER_CONDITION, // Only arbiter
    refundPostEscrowCondition: ARBITER_CONDITION,
    // ...
};
For complete configuration examples, see the Examples page.

Gas Optimization

Singleton Reuse

Singleton conditions are deployed once and reused by all operators. Reference the existing addresses — don’t deploy new instances:
// Good: Reference the singleton address
const config1 = { authorizeCondition: PAYER_CONDITION };
const config2 = { authorizeCondition: PAYER_CONDITION }; // Same address

Stateless Conditions

Prefer stateless conditions when possible:
// Stateless: No storage reads (pure)
function check(PaymentInfo calldata payment, uint256, address caller)
    external pure returns (bool)
{
    return caller == payment.receiver;  // Pure computation
}

// Stateful: Storage reads cost gas (view)
function check(PaymentInfo calldata payment, uint256, address caller)
    external view returns (bool)
{
    return allowList[caller];  // SLOAD costs gas
}

Next Steps