The configuration examples below use simplified pseudo-code (e.g., new StaticAddressCondition(...), new AndCondition(...)) to illustrate the logical composition of conditions. In practice, deploy conditions via their respective factory contracts using viem. See the SDK quickstart for executable code.
Example 1: Standard E-Commerce with 7-Day Escrow
Use Case: Online marketplace with buyer protection. 7-day escrow period, payer can freeze for 3 days, receiver or arbiter can release after escrow.
Complete Configuration
Deploy Arbiter Condition
// Deploy designated address condition for arbiter
const arbiterCondition = await new StaticAddressCondition ( arbiterAddress );
Deploy Freeze Policy
// Payer can freeze, arbiter can unfreeze (or wait for expiry)
const freezePolicy = await freezePolicyFactory . deploy (
PAYER_CONDITION , // Only payer can freeze
arbiterCondition . address , // Only arbiter can unfreeze
3 * 24 * 60 * 60 // 3 days (auto-expires)
);
Deploy EscrowPeriod and Freeze
// 7-day escrow period (combined recorder + condition)
const escrowPeriod = await escrowPeriodFactory . deploy (
7 * 24 * 60 * 60 , // 7 days
zeroHash // bytes32(0) = operator-only
);
// Freeze condition linked to escrow period
const freeze = await freezeFactory . deploy (
freezePolicy ,
escrowPeriod // Link to escrow period for time constraint
);
Create Release Condition
// (Receiver OR Arbiter) AND (EscrowPassed AND NotFrozen)
const receiverOrArbiter = await new OrCondition ([
RECEIVER_CONDITION ,
arbiterCondition . address
]);
// Compose escrow period and freeze checks
const escrowAndFreeze = await new AndCondition ([
escrowPeriod , // Escrow period passed
freeze // Not frozen
]);
const releaseCondition = await new AndCondition ([
receiverOrArbiter ,
escrowAndFreeze
]);
Deploy Operator
const config = {
feeRecipient: arbiterAddress , // Arbiter earns fees for dispute resolution
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: ALWAYS_TRUE_CONDITION ,
authorizeRecorder: escrowPeriod , // Same address for recording auth time
chargeCondition: RECEIVER_CONDITION ,
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: releaseCondition ,
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: arbiterCondition . address ,
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: arbiterCondition . address ,
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Example 2: Instant Payment (Using Charge)
Use Case: Digital goods or services where immediate payment to seller is expected.
Configuration
// Deploy arbiter condition for disputes
const arbiterCondition = await new StaticAddressCondition ( arbiterAddress );
const config = {
feeRecipient: arbiterAddress , // Arbiter earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: '0x0000000000000000000000000000000000000000' , // Not used (charge handles auth)
authorizeRecorder: '0x0000000000000000000000000000000000000000' ,
chargeCondition: RECEIVER_CONDITION , // Only receiver can charge
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: RECEIVER_CONDITION , // Fallback to release remaining
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: arbiterCondition . address ,
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: arbiterCondition . address ,
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Buyer approves tokens → Seller calls charge() → Funds transferred in one tx
(authorizes + charges atomically)
Trade-offs:
✅ Single transaction - no separate authorize step
✅ Instant delivery for digital goods
✅ Better UX - seller gets paid immediately
❌ No buyer protection escrow period
❌ Payment immediately moves to post-escrow state
Example 3: Physical Goods with Extended Escrow
Use Case: International shipping with 14-day escrow and 5-day receiver freeze period.
Configuration
// Deploy arbiter condition
const arbiterCondition = await new StaticAddressCondition ( arbiterAddress );
// Receiver can freeze for 5 days, arbiter can unfreeze
const freezePolicy = await freezePolicyFactory . deploy (
RECEIVER_CONDITION , // Receiver can freeze (product defect)
arbiterCondition . address , // Arbiter can unfreeze (dispute resolution)
5 * 24 * 60 * 60 // 5 days (auto-expires)
);
// 14-day escrow (shipping + inspection)
const escrowPeriod = await escrowPeriodFactory . deploy (
14 * 24 * 60 * 60 , // 14 days
zeroHash // bytes32(0) = operator-only
);
// Deploy Freeze linked to escrow period
const freeze = await freezeFactory . deploy ( freezePolicy , escrowPeriod );
// Receiver OR Arbiter can release (after escrow + not frozen)
const releaseCondition = await new AndCondition ([
await new OrCondition ([ RECEIVER_CONDITION , arbiterCondition . address ]),
await new AndCondition ([ escrowPeriod , freeze ])
]);
const config = {
feeRecipient: arbiterAddress , // Arbiter earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: ALWAYS_TRUE_CONDITION ,
authorizeRecorder: escrowPeriod , // Same address for recording auth time
chargeCondition: '0x0000000000000000000000000000000000000000' ,
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: releaseCondition ,
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: arbiterCondition . address ,
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: arbiterCondition . address ,
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Example 4: Service-Based Payments (Milestone Release)
Use Case: Freelance work with milestone-based releases. Receiver can trigger partial releases.
Configuration
// Deploy arbiter condition for disputes
const arbiterCondition = await new StaticAddressCondition ( arbiterAddress );
// 3-day escrow per milestone (no freeze)
const escrowPeriod = await escrowPeriodFactory . deploy (
3 * 24 * 60 * 60 , // 3 days per milestone
zeroHash // bytes32(0) = operator-only
);
// Receiver can release after short escrow
const releaseCondition = await new AndCondition ([
RECEIVER_CONDITION , // Only receiver
escrowPeriod // Escrow period passed
]);
const config = {
feeRecipient: arbiterAddress , // Arbiter earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: PAYER_CONDITION , // Only payer authorizes
authorizeRecorder: escrowPeriod , // Record auth time
chargeCondition: RECEIVER_CONDITION , // Receiver can charge partials
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: releaseCondition ,
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: arbiterCondition . address ,
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: arbiterCondition . address ,
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Example 5: Arbiter-Controlled Escrow
Use Case: Fully managed escrow service where arbiter controls all actions.
Configuration
// Deploy arbiter condition
const arbiterCondition = await new StaticAddressCondition ( arbiterAddress );
const config = {
feeRecipient: arbiterAddress , // Arbiter earns all fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: arbiterCondition . address , // Arbiter creates payments
authorizeRecorder: '0x0000000000000000000000000000000000000000' ,
chargeCondition: arbiterCondition . address , // Arbiter charges
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: arbiterCondition . address , // Arbiter releases
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: arbiterCondition . address , // Arbiter refunds
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: arbiterCondition . address ,
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Factory-level fees still apply (MAX_TOTAL_FEE_RATE and PROTOCOL_FEE_PERCENTAGE). This example assumes protocol takes no cut at factory level.
Use cases:
Professional escrow services
Legal settlements
High-value transactions requiring oversight
Example 6: Receiver-Initiated Refunds
Use Case: Receiver can voluntarily offer refunds (e.g., return policy).
Configuration
// Deploy arbiter condition
const arbiterCondition = await new StaticAddressCondition ( arbiterAddress );
// 7-day escrow (no freeze)
const escrowPeriod = await escrowPeriodFactory . deploy (
7 * 24 * 60 * 60 ,
zeroHash // bytes32(0) = operator-only
);
// Receiver OR Arbiter can release
const releaseCondition = await new AndCondition ([
await new OrCondition ([ RECEIVER_CONDITION , arbiterCondition . address ]),
escrowPeriod // Escrow period passed
]);
// Receiver OR Arbiter can refund
const refundCondition = await new OrCondition ([
RECEIVER_CONDITION ,
arbiterCondition . address
]);
const config = {
feeRecipient: arbiterAddress , // Arbiter earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: ALWAYS_TRUE_CONDITION ,
authorizeRecorder: escrowPeriod , // Record auth time
chargeCondition: '0x0000000000000000000000000000000000000000' ,
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: releaseCondition ,
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: refundCondition , // Receiver OR Arbiter
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: RECEIVER_CONDITION , // Only receiver post-escrow
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Example 7: Subscription Payments
Use Case: Recurring payments with automatic charge capability.
Configuration
// Deploy condition for service provider (no arbiter needed for subscriptions)
const providerCondition = await new StaticAddressCondition ( serviceProviderAddress );
// Receiver can charge immediately (no escrow)
const config = {
feeRecipient: serviceProviderAddress , // Service provider earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: PAYER_CONDITION , // Payer sets up subscription
authorizeRecorder: '0x0000000000000000000000000000000000000000' ,
chargeCondition: providerCondition . address , // Provider charges monthly
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: providerCondition . address , // Provider releases
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: '0x0000000000000000000000000000000000000000' , // No refunds (no arbiter)
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
For subscriptions with dispute resolution, deploy an arbiter condition and use it for refund conditions. This example shows a simple subscription without arbiter.
Authorization Expiry for Subscriptions: When authorizing, set authorizationExpiry to limit how long the service provider can charge. For example, a 12-month subscription would set expiry to block.timestamp + 365 days. After expiry, the payer can reclaim any unused funds by calling void().
Payment Flow
Example 8: DAO Treasury Controlled
Use Case: DAO manages grant releases via multisig governance. No arbiter needed - DAO is the authority.
Configuration
// Deploy condition for DAO multisig
const daoCondition = await new StaticAddressCondition ( DAO_MULTISIG_ADDRESS );
const config = {
feeRecipient: DAO_MULTISIG_ADDRESS , // DAO treasury earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: daoCondition . address , // DAO authorizes grants
authorizeRecorder: '0x0000000000000000000000000000000000000000' ,
chargeCondition: '0x0000000000000000000000000000000000000000' ,
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: daoCondition . address , // DAO must approve releases
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: daoCondition . address , // DAO can refund if needed
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: '0x0000000000000000000000000000000000000000' , // No post-escrow refunds
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Benefits:
Governance-controlled releases
No third-party arbiter needed
DAO earns fees back to treasury
On-chain transparent decision making
Use Case: Platform manages time-proportional streaming payments. Users can cancel anytime.
Configuration
// Deploy condition for platform address
const platformCondition = await new StaticAddressCondition ( PLATFORM_ADDRESS );
// Time-proportional charge condition (custom — not provided out of the box,
// this is a hypothetical custom condition you would implement yourself)
const timeProportionalCondition = await new TimeProportionalCondition ();
const config = {
feeRecipient: PLATFORM_ADDRESS , // Platform earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: PAYER_CONDITION , // Payer authorizes stream
authorizeRecorder: '0x0000000000000000000000000000000000000000' ,
chargeCondition: new AndCondition ([
RECEIVER_CONDITION ,
timeProportionalCondition // Can only charge proportional to time
]),
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: RECEIVER_CONDITION , // Receiver releases remaining
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: PAYER_CONDITION , // Payer can cancel stream
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: '0x0000000000000000000000000000000000000000' , // No refunds after charged
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Benefits:
Time-based fairness (can’t charge ahead of time)
User can cancel anytime
No arbiter needed
Platform controls no disputes
Example 10: Self-Service Invoice Payment (No Arbiter)
Use Case: B2B invoice payments with no disputes. Companies trust each other directly.
Configuration
// No arbiter - receiver controls release, platform earns fees
const platformCondition = await new StaticAddressCondition ( PLATFORM_ADDRESS );
const config = {
feeRecipient: PLATFORM_ADDRESS , // Platform earns fees
feeCalculator: feeCalculatorAddress , // Operator fee calculator
authorizeCondition: PAYER_CONDITION , // Payer creates invoice payment
authorizeRecorder: '0x0000000000000000000000000000000000000000' ,
chargeCondition: RECEIVER_CONDITION , // Receiver charges on delivery
chargeRecorder: '0x0000000000000000000000000000000000000000' ,
releaseCondition: RECEIVER_CONDITION , // Receiver releases on payment terms
releaseRecorder: '0x0000000000000000000000000000000000000000' ,
refundInEscrowCondition: RECEIVER_CONDITION , // Receiver can refund (invoice error)
refundInEscrowRecorder: '0x0000000000000000000000000000000000000000' ,
refundPostEscrowCondition: '0x0000000000000000000000000000000000000000' , // No post-delivery refunds
refundPostEscrowRecorder: '0x0000000000000000000000000000000000000000'
};
const operator = await operatorFactory . deployOperator ( config );
Payment Flow
Benefits:
Trusted B2B relationship (no arbiter overhead)
Standard payment terms enforced on-chain
Receiver can self-correct invoice errors
Platform monetizes via fees only
Fee Configuration Comparison
Use Case Max Fee (bps) Protocol % Operator % Fee Recipient Total on 1000 USDC E-Commerce (Arbiter) 5 (0.05%) 25% 75% Arbiter 0.50 USDC Instant Payment (Arbiter) 10 (0.1%) 50% 50% Arbiter 1.00 USDC Physical Goods (Arbiter) 3 (0.03%) 20% 80% Arbiter 0.30 USDC Service/Milestone (Arbiter) 8 (0.08%) 30% 70% Arbiter 0.80 USDC Managed Escrow (Arbiter) 20 (0.2%) 0% 100% Arbiter 2.00 USDC Receiver Refunds (Arbiter) 5 (0.05%) 25% 75% Arbiter 0.50 USDC Subscription (Provider) 15 (0.15%) 40% 60% Service Provider 1.50 USDC DAO Grants (DAO) 5 (0.05%) 20% 80% DAO Treasury 0.50 USDC Streaming (Platform) 10 (0.1%) 30% 70% Platform 1.00 USDC B2B Invoice (Platform) 10 (0.1%) 50% 50% Platform 1.00 USDC
Configuration Checklist
Before deploying, verify:
Testing Your Configuration
import { createTestClient , http , parseUnits , keccak256 , toHex } from 'viem' ;
import { baseSepolia } from 'viem/chains' ;
import { PaymentOperatorABI } from '@x402r/core/abis' ;
// Deploy on Base Sepolia first
const operatorAddress = await factory . write . deployOperator ([ config ]);
const testClient = createTestClient ({
chain: baseSepolia ,
transport: http (),
mode: 'anvil' ,
});
// 1. Authorize
await walletClient . writeContract ({
address: operatorAddress ,
abi: PaymentOperatorABI ,
functionName: 'authorize' ,
args: [ paymentInfo , parseUnits ( '100' , 6 ), tokenCollectorAddress , collectorData ],
});
// 2. Try to release immediately (should fail if escrow configured)
// Expect revert with ConditionNotMet
try {
await walletClient . writeContract ({
address: operatorAddress ,
abi: PaymentOperatorABI ,
functionName: 'release' ,
args: [ paymentInfo , parseUnits ( '100' , 6 )],
});
} catch ( e ) {
console . log ( 'Expected revert: escrow period not passed' );
}
// 3. Fast forward time (requires Anvil/Hardhat test node)
await testClient . increaseTime ({ seconds: 7 * 24 * 60 * 60 });
await testClient . mine ({ blocks: 1 });
// 4. Release after escrow
await walletClient . writeContract ({
address: operatorAddress ,
abi: PaymentOperatorABI ,
functionName: 'release' ,
args: [ paymentInfo , parseUnits ( '100' , 6 )],
});
// Verify funds transferred correctly
Best Practices
Use well-tested configurations for your first deployments:
Standard 7-day escrow
3-day payer freeze
Arbiter-only refunds
Low fee rates (3-5 bps)
Research competitors’ escrow periods and fees:
E-commerce: 3-7 days typical
Freelance: 3-14 days per milestone
High-value: 14-30 days common
Test your configuration handles:
Immediate release attempts
Freeze during escrow
Freeze expiry
Refunds in both states
Fee distribution
Multiple partial charges
Keep records of deployed configurations: {
"operator" : "0x..." ,
"arbiter" : "0x..." ,
"escrowPeriod" : "7 days" ,
"freezeDuration" : "3 days" ,
"freezeBy" : "payer" ,
"releaseBy" : "receiver OR arbiter" ,
"maxFeeBps" : 5 ,
"protocolFeePct" : 25 ,
"network" : "base-mainnet" ,
"deployedAt" : "2025-01-25"
}
Next Steps