Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 70 additions & 24 deletions contracts/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,63 @@ description: "System architecture, payment flows, and contract relationships"
icon: "diagram-project"
---

## System Diagrams

For detailed visual architecture diagrams, see the [x402r-contracts repository](https://github.com/BackTrackCo/x402r-contracts#architecture):
## System Overview

```mermaid
flowchart TB
subgraph Users
Payer
Receiver
DesAddr["Designated Address<br/>(Arbiter / Provider / DAO)"]
end

subgraph Factories
POF[PaymentOperatorFactory]
EPF[EscrowPeriodFactory]
FF[FreezeFactory]
end

subgraph Operator["PaymentOperator (per config)"]
Auth[authorize]
Charge[charge]
Release[release]
RefundIE[refundInEscrow]
RefundPE[refundPostEscrow]
end

subgraph Plugins["Conditions & Recorders"]
Cond["ICondition<br/>(check before action)"]
Rec["IRecorder<br/>(record after action)"]
EP[EscrowPeriod]
Freeze[Freeze]
And[AndCondition]
Or[OrCondition]
end

Escrow[AuthCaptureEscrow]
RR[RefundRequest]

Payer -->|authorize / freeze| Operator
Receiver -->|release / charge| Operator
DesAddr -->|refund / release| Operator
Payer -->|requestRefund| RR

POF -->|deploys| Operator
EPF -->|deploys| EP
FF -->|deploys| Freeze

Operator -->|checks| Cond
Operator -->|calls| Rec
Operator -->|locks/releases funds| Escrow

EP -.->|implements| Cond
EP -.->|implements| Rec
Freeze -.->|implements| Cond
And -.->|composes| Cond
Or -.->|composes| Cond
```

- [System Overview](https://github.com/BackTrackCo/x402r-contracts#system-overview) - User interactions, operator slots, and escrow state machine
- [Contract Relationships](https://github.com/BackTrackCo/x402r-contracts#contract-relationships) - How factories, operators, and conditions interact
- [Condition Combinator Pattern](https://github.com/BackTrackCo/x402r-contracts#condition-combinator-pattern) - Composable authorization logic
- [Escrow Period & Freeze Flow](https://github.com/BackTrackCo/x402r-contracts#escrow-period--freeze-flow) - Timeline of payment states
For additional visual diagrams, see the [x402r-contracts repository](https://github.com/BackTrackCo/x402r-contracts#architecture).

## Payment Flow Sequence

Expand Down Expand Up @@ -236,23 +285,20 @@ Ownership transfers use Solady's Ownable pattern:
### Core Events

```solidity
// Payment lifecycle
event PaymentAuthorized(bytes32 indexed paymentId, address indexed payer, uint256 amount);
event PaymentReleased(bytes32 indexed paymentId, uint256 amount);
event PaymentRefunded(bytes32 indexed paymentId, uint256 amount, bool inEscrow);

// Refund requests
event RefundRequested(bytes32 indexed paymentId, address indexed requester);
event RefundApproved(bytes32 indexed paymentId, address indexed approver);
event RefundDenied(bytes32 indexed paymentId, address indexed denier);

// Freeze state
event PaymentFrozen(bytes32 indexed paymentId, uint40 frozenAt);
event PaymentUnfrozen(bytes32 indexed paymentId);

// Fee changes
event FeeUpdateQueued(uint256 newMaxFee, uint256 newProtocolPct, uint256 effectiveTime);
event FeeUpdateExecuted(uint256 maxFee, uint256 protocolPct);
// Payment lifecycle (PaymentOperator events)
event AuthorizationCreated(bytes32 indexed paymentInfoHash, address indexed payer, address indexed receiver, uint256 amount, uint256 timestamp);
event ChargeExecuted(bytes32 indexed paymentInfoHash, address indexed payer, address indexed receiver, uint256 amount, uint256 timestamp);
event ReleaseExecuted(AuthCaptureEscrow.PaymentInfo paymentInfo, uint256 amount, uint256 timestamp);
event RefundInEscrowExecuted(AuthCaptureEscrow.PaymentInfo paymentInfo, address indexed payer, uint256 amount);
event RefundPostEscrowExecuted(AuthCaptureEscrow.PaymentInfo paymentInfo, address indexed payer, uint256 amount);

// Fee distribution
event FeesDistributed(address indexed token, uint256 protocolAmount, uint256 arbiterAmount);
event OperatorDeployed(address indexed operator, address indexed feeRecipient, address indexed releaseCondition);

// Freeze state (Freeze contract events)
event PaymentFrozen(bytes32 indexed paymentInfoHash, uint40 frozenAt);
event PaymentUnfrozen(bytes32 indexed paymentInfoHash);
```

These events enable off-chain monitoring and indexing.
Expand Down
88 changes: 46 additions & 42 deletions contracts/conditions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ Pre-deployed singletons for role-based access control.

Only the payment payer can call the action.

**Address (Base Sepolia):** `0xDc0D800007ceACFf1299b926Ce22B4d4edCE6Ce7`
**Address (Base Sepolia):** `0xBAF68176FF94CAdD403EF7FbB776bbca548AC09D`

**Logic:**
```solidity
function check(PaymentInfo calldata payment, address caller)
function check(PaymentInfo calldata payment, uint256, address caller)
external pure returns (bool)
{
return caller == payment.payer;
Expand All @@ -117,11 +117,11 @@ function check(PaymentInfo calldata payment, address caller)

Only the payment receiver can call the action.

**Address (Base Sepolia):** `0x138Bf828643350AA3692aedDE8b2254eDF4D07EF`
**Address (Base Sepolia):** `0x12EDefd4549c53497689067f165c0f101796Eb6D`

**Logic:**
```solidity
function check(PaymentInfo calldata payment, address caller)
function check(PaymentInfo calldata payment, uint256, address caller)
external pure returns (bool)
{
return caller == payment.receiver;
Expand All @@ -148,7 +148,7 @@ contract StaticAddressCondition is ICondition {
DESIGNATED_ADDRESS = _designatedAddress;
}

function check(PaymentInfo calldata payment, address caller)
function check(PaymentInfo calldata payment, uint256, address caller)
external view returns (bool)
{
return caller == DESIGNATED_ADDRESS;
Expand Down Expand Up @@ -179,11 +179,11 @@ const daoCondition = new StaticAddressCondition(daoMultisigAddress);

Anyone can call the action (no restrictions).

**Address (Base Sepolia):** `0xe2659dc0d716B1226DF6a09A5f47862cd1ff6733`
**Address (Base Sepolia):** `0x785cC83DEa3d46D5509f3bf7496EAb26D42EE610`

**Logic:**
```solidity
function check(PaymentInfo calldata payment, address caller)
function check(PaymentInfo calldata payment, uint256, address caller)
external pure returns (bool)
{
return true;
Expand All @@ -205,15 +205,14 @@ Compose multiple conditions with logical operators.
All conditions must pass (`A && B && C`).

**Usage:**
```solidity
// Deploy combinator
AndCondition combo = new AndCondition([
RECEIVER_CONDITION, // Must be receiver
ESCROW_PERIOD_CONDITION // Must be after escrow
```typescript
// Deploy via factory
const comboAddress = await andConditionFactory.write.deploy([
[RECEIVER_CONDITION, ESCROW_PERIOD_ADDRESS] // Must be receiver AND after escrow
]);

// Use in operator config
config.releaseCondition = address(combo);
config.releaseCondition = comboAddress;
```

**Example:** Release requires receiver AND escrow passed
Expand All @@ -223,14 +222,13 @@ config.releaseCondition = address(combo);
At least one condition must pass (`A || B`).

**Usage:**
```solidity
```typescript
// Receiver OR Arbiter can release
OrCondition combo = new OrCondition([
RECEIVER_CONDITION,
ARBITER_CONDITION
const comboAddress = await orConditionFactory.write.deploy([
[RECEIVER_CONDITION, ARBITER_CONDITION]
]);

config.releaseCondition = address(combo);
config.releaseCondition = comboAddress;
```

**Example:** Either receiver or arbiter can release
Expand All @@ -240,11 +238,11 @@ config.releaseCondition = address(combo);
Inverts a condition (`!A`).

**Usage:**
```solidity
```typescript
// Anyone EXCEPT payer can call
NotCondition combo = new NotCondition(PAYER_CONDITION);
const comboAddress = await notConditionFactory.write.deploy([PAYER_CONDITION]);

config.releaseCondition = address(combo);
config.releaseCondition = comboAddress;
```

**Example:** Prevent payer from releasing their own payment
Expand All @@ -253,19 +251,17 @@ config.releaseCondition = address(combo);

Combine combinators for complex logic:

```solidity
```typescript
// (Receiver OR Arbiter) AND EscrowPassed
OrCondition receiverOrArbiter = new OrCondition([
RECEIVER_CONDITION,
ARBITER_CONDITION
const receiverOrArbiter = await orConditionFactory.write.deploy([
[RECEIVER_CONDITION, ARBITER_CONDITION]
]);

AndCondition releaseCondition = new AndCondition([
address(receiverOrArbiter),
ESCROW_PERIOD_CONDITION
const releaseCondition = await andConditionFactory.write.deploy([
[receiverOrArbiter, ESCROW_PERIOD_ADDRESS]
]);

config.releaseCondition = address(releaseCondition);
config.releaseCondition = releaseCondition;
```

**Logic Tree:**
Expand Down Expand Up @@ -448,13 +444,12 @@ function getPaymentIndex(
Combines multiple recorders into one, calling each in sequence.

**Usage:**
```solidity
RecorderCombinator combo = new RecorderCombinator([
escrowPeriod, // Records authorization time
paymentIndexRecorder // Records payment index
```typescript
const comboAddress = await recorderCombinatorFactory.write.deploy([
[escrowPeriodAddress, paymentIndexRecorderAddress] // Records auth time + payment index
]);

config.authorizeRecorder = address(combo);
config.authorizeRecorder = comboAddress;
```

### Custom Recorders
Expand Down Expand Up @@ -540,16 +535,24 @@ config = {

### Pattern 4: Multi-Party Release (2-of-3)

```solidity
```typescript
// Requires any 2 of 3 parties to agree: Payer, Receiver, or Arbiter
// This creates three OR branches, each requiring a different pair to call simultaneously
const twoOfThree = new OrCondition([
new AndCondition([PAYER_CONDITION, RECEIVER_CONDITION]), // Payer AND Receiver
new AndCondition([PAYER_CONDITION, ARBITER_CONDITION]), // Payer AND Arbiter
new AndCondition([RECEIVER_CONDITION, ARBITER_CONDITION]) // Receiver AND Arbiter
const payerAndReceiver = await andConditionFactory.write.deploy([
[PAYER_CONDITION, RECEIVER_CONDITION]
]);
const payerAndArbiter = await andConditionFactory.write.deploy([
[PAYER_CONDITION, ARBITER_CONDITION]
]);
const receiverAndArbiter = await andConditionFactory.write.deploy([
[RECEIVER_CONDITION, ARBITER_CONDITION]
]);

const twoOfThree = await orConditionFactory.write.deploy([
[payerAndReceiver, payerAndArbiter, receiverAndArbiter]
]);

config.releaseCondition = address(twoOfThree);
config.releaseCondition = twoOfThree;
```

**Use case:** Multi-sig style releases requiring coordination
Expand Down Expand Up @@ -594,14 +597,14 @@ Prefer stateless conditions when possible:

```solidity
// ✅ Stateless: No storage reads
function check(PaymentInfo calldata payment, address caller)
function check(PaymentInfo calldata payment, uint256, address caller)
external pure returns (bool)
{
return caller == payment.receiver; // Pure computation
}

// ❌ Stateful: Storage reads
function check(PaymentInfo calldata payment, address caller)
function check(PaymentInfo calldata payment, uint256, address caller)
external view returns (bool)
{
return allowList[caller]; // SLOAD costs gas
Expand Down Expand Up @@ -642,6 +645,7 @@ contract TimeOfDayCondition is ICondition {

function check(
PaymentInfo calldata payment,
uint256,
address caller
) external view returns (bool) {
uint256 hour = (block.timestamp / 3600) % 24;
Expand Down
9 changes: 6 additions & 3 deletions contracts/core-contracts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ Protocol fee changes require 7-day timelock. Operator fees are immutable (set at
- **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 and arbiter cannot be changed
- **Immutable Core** - Escrow, conditions, and fee configuration cannot be changed

---

Expand Down Expand Up @@ -481,8 +481,7 @@ stateDiagram-v2
NonExistent --> InEscrow: authorize()
InEscrow --> Released: release()
InEscrow --> Settled: void() / refundInEscrow()
Released --> Settled: (settled)
InEscrow --> Settled: reclaim() / refundPostEscrow()
Released --> Settled: reclaim() / refundPostEscrow()
Settled --> [*]

note right of InEscrow
Expand Down Expand Up @@ -520,6 +519,10 @@ function authorize(

**Requires:** Payer has approved escrow contract for `amount` of `token`

<Note>
The base escrow contract uses individual parameters (paymentId, payer, receiver, etc.) while the PaymentOperator wraps them in a `PaymentInfo` struct. The operator translates between the two formats internally.
</Note>

#### release()

Releases tokens to receiver. Called by operator.
Expand Down
Loading