> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fortytwo.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Model

> x402Escrow is designed for adversarial environments. This section describes the threat model, protection mechanisms, and trust assumptions.

<Badge color="gray">x402Escrow</Badge>

## Trust Assumptions

| Party           | Trusted to                                                                 | Not trusted to                                                            |
| --------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **Client**      | Sign valid EIP-3009 authorizations.                                        | Act honestly after signing (escrow protects against overpayment).         |
| **Facilitator** | Call `settle()` and `release()` correctly.                                 | Be available 24/7 (timeout refund protects against offline facilitators). |
| **Admin**       | Configure reasonable timeouts.                                             | Upgrade the contract (only Owner can).                                    |
| **Owner**       | Authorize safe upgrades.                                                   | Access escrowed funds directly.                                           |
| **USDC token**  | Implement EIP-3009 correctly. Transfer exact amounts (no fee-on-transfer). | —                                                                         |

## Protection Mechanisms

### Reentrancy Protection

All state-changing functions that interact with external contracts (`settle`, `release`, `refundAfterTimeout`) are protected by:

1. **ReentrancyGuard** — OpenZeppelin's `nonReentrant` modifier prevents recursive calls.
2. **Checks-Effects-Interactions pattern** — escrow storage is cleared (effects) before any external token transfers (interactions).

### MEV Protection

EIP-3009 `ReceiveWithAuthorization` requires `msg.sender == to`. Since `to` is set to the escrow contract address, a mempool observer who extracts the signed authorization from a pending transaction cannot use it — only the escrow contract can execute the transfer.

### Fee-on-transfer Detection

After calling `receiveWithAuthorization()`, `settle()` verifies that the contract USDC balance increased by exactly `maxAmount`. If a token charges transfer fees (resulting in a smaller balance increase), the transaction reverts with `TransferMismatch()`.

This prevents a class of attacks where fee-on-transfer tokens would create escrows with less USDC than recorded, leading to insolvency on release.

### Access Control Boundaries

```
Owner       --> _authorizeUpgrade()       (UUPS upgrades only)
Admin       --> setTimeout()              (configuration only)
                grantRole() / revokeRole()
Facilitator --> settle()                  (fund operations only)
                release()
Anyone      --> refundAfterTimeout()      (permissionless safety net)
                getEscrow()
```

No single role can both configure the contract and access funds. The Owner cannot call `release()`. The Facilitator cannot call `setTimeout()`. This limits the blast radius of any key compromise.

### Two-step Ownership Transfer

Ownership uses OpenZeppelin's `Ownable2Step`:

1. Current owner calls `transferOwnership(newOwner)`.
2. New owner calls `acceptOwnership()`.

This prevents accidental or irreversible ownership transfer to the wrong address.

### Per-escrow Deadlines

Each escrow stores its own `refundAt` timestamp at creation. Subsequent calls to `setTimeout()` do not modify existing escrows. This prevents an attack where an admin could extend timeouts to delay client refunds on active escrows.

### Input Validation

| Input                  | Validation                          | Error                                |
| ---------------------- | ----------------------------------- | ------------------------------------ |
| Addresses (initialize) | Must be non-zero                    | `ZeroAddress()`                      |
| USDC address           | Must be a contract                  | `NotContract()`                      |
| Amount                 | Non-zero, fits `uint56`             | `InvalidAmount()`                    |
| Timeout                | 300 ≤ value ≤ 86400                 | `InvalidTimeout()`                   |
| EIP-3009 window        | `validAfter < now < validBefore`    | `NotYetValid()` / `TimeoutExpired()` |
| Escrow existence       | `client != address(0)`              | `EscrowNotFound()`                   |
| Escrow uniqueness      | No active escrow with the same ID   | `EscrowAlreadyExists()`              |
| Release amount         | `facilitatorAmount ≤ escrow.amount` | `InvalidAmount()`                    |

## Attack Scenarios

### Facilitator Goes Offline

**Impact**: client funds are locked until timeout.<br />
**Mitigation**: `refundAfterTimeout()` is permissionless. After `refundAt`, anyone (including a relayer or the client themselves) can trigger the refund. Default timeout is 90 minutes.

### Facilitator Overcharges

**Impact**: facilitator receives more than the fair cost.<br />
**Mitigation**: the facilitator cannot withdraw more than `escrow.amount` (the client's signed maximum). The client controls their maximum exposure by choosing the `maxAmount` in the EIP-3009 signature.

### Facilitator Double-Releases

**Impact**: none.<br />
**Mitigation**: `release()` clears the escrow before transferring. A second call reverts with `EscrowNotFound()`.

### Admin Sets Extreme Timeout

**Impact**: new escrows have very short or very long refund windows.<br />
**Mitigation**: the timeout is bounded to \[5 minutes, 24 hours]. Existing escrows are unaffected.

### Signature Replay

**Impact**: funds locked twice from the same authorization.<br />
**Mitigation**: EIP-3009 nonces are one-time use at the USDC token level. The escrow also checks `EscrowAlreadyExists` for the derived escrow ID.

### Malicious Upgrade

**Impact**: contract logic replaced with malicious code.<br />
**Mitigation**: only the Owner (typically a multisig) can authorize upgrades via `_authorizeUpgrade()`. Two-step ownership transfer prevents accidental owner change.

## Upgradeability Considerations

x402Escrow uses the UUPS proxy pattern (ERC-1967). Key properties:

* **State persists across upgrades**: proxy storage is preserved when the implementation changes.
* **Storage layout must be compatible**: future versions must not reorder or remove existing storage variables.
* **Escrow struct is stable**: the packed `Escrow` struct occupies one slot. If the struct changes in a future version, a storage migration would be required.
* **Upgrade is atomic**: `upgradeToAndCall()` switches the implementation and optionally calls an initializer in one transaction.

## Recommendations for Production

1. **Use a multisig for Owner and Admin roles.** A single EOA controlling upgrades or role assignment is a single point of failure.
2. **Monitor `Deposited` and `Released` events.** Off-chain monitoring can detect anomalous patterns (e.g., releases without corresponding service activity).
3. **Set appropriate timeouts.** Shorter timeouts reduce client risk but give facilitators less time to settle. The default 90 minutes suits most API workloads.
4. **Rotate facilitator keys.** Since `release()` pays `msg.sender`, you can add new facilitator addresses and revoke old ones without affecting active escrows.
