From 3861476523d10479d99f1d9e624b1c98cab4683e Mon Sep 17 00:00:00 2001
From: Borja Aranda <borja@ephemerahq.com>
Date: Tue, 18 Mar 2025 22:48:34 +0100
Subject: [PATCH 1/7] PayerRegistry implementation

---
 .gitignore                             |    3 +
 lib/forge-std                          |    2 +-
 lib/oz                                 |    2 +-
 lib/oz-upgradeable                     |    2 +-
 src/PayerRegistry.sol                  | 1026 ++++++++++++++++++++++++
 src/interfaces/IFeeDistributor.sol     |  103 +++
 src/interfaces/IPayerRegistry.sol      |  547 +++++++++++++
 src/interfaces/IPayerReportManager.sol |  161 ++++
 8 files changed, 1843 insertions(+), 3 deletions(-)
 create mode 100644 src/PayerRegistry.sol
 create mode 100644 src/interfaces/IFeeDistributor.sol
 create mode 100644 src/interfaces/IPayerRegistry.sol
 create mode 100644 src/interfaces/IPayerReportManager.sol

diff --git a/.gitignore b/.gitignore
index 412b708..a209e7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,6 @@ node_modules
 gasreport.ansi
 
 .vscode/
+
+# Solidity wake extension
+.wake/
diff --git a/lib/forge-std b/lib/forge-std
index 3b20d60..8ba9031 160000
--- a/lib/forge-std
+++ b/lib/forge-std
@@ -1 +1 @@
-Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981
+Subproject commit 8ba9031ffcbe25aa0d1224d3ca263a995026e477
diff --git a/lib/oz b/lib/oz
index acd4ff7..fda6b85 160000
--- a/lib/oz
+++ b/lib/oz
@@ -1 +1 @@
-Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527
+Subproject commit fda6b85f2c65d146b86d513a604554d15abd6679
diff --git a/lib/oz-upgradeable b/lib/oz-upgradeable
index 3d5fa5c..36ec707 160000
--- a/lib/oz-upgradeable
+++ b/lib/oz-upgradeable
@@ -1 +1 @@
-Subproject commit 3d5fa5c24c411112bab47bec25cfa9ad0af0e6e8
+Subproject commit 36ec7079af1a68bd866f6b9f4cf2f4dddee1e7bc
diff --git a/src/PayerRegistry.sol b/src/PayerRegistry.sol
new file mode 100644
index 0000000..0b99375
--- /dev/null
+++ b/src/PayerRegistry.sol
@@ -0,0 +1,1026 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.28;
+
+import { AccessControlUpgradeable } from "../lib/oz-upgradeable/contracts/access/AccessControlUpgradeable.sol";
+import { EnumerableSet } from "../lib/oz/contracts/utils/structs/EnumerableSet.sol";
+import { IERC20 } from "../lib/oz/contracts/token/ERC20/IERC20.sol";
+import { IERC20Metadata } from "../lib/oz/contracts/token/ERC20/extensions/IERC20Metadata.sol";
+import { IERC165 } from "../lib/oz/contracts/utils/introspection/IERC165.sol";
+import { Initializable } from "../lib/oz-upgradeable/contracts/proxy/utils/Initializable.sol";
+import { PausableUpgradeable } from "../lib/oz-upgradeable/contracts/utils/PausableUpgradeable.sol";
+import { ReentrancyGuardUpgradeable } from "../lib/oz-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol";
+import { SafeERC20 } from "../lib/oz/contracts/token/ERC20/utils/SafeERC20.sol";
+import { UUPSUpgradeable } from "../lib/oz-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
+
+import { IFeeDistributor } from "./interfaces/IFeeDistributor.sol";
+import { INodes } from "./interfaces/INodes.sol";
+import { IPayerRegistry } from "./interfaces/IPayerRegistry.sol";
+import { IPayerReportManager } from "./interfaces/IPayerReportManager.sol";
+
+/**
+ * @title  PayerRegistry
+ * @notice Implementation for managing payer USDC deposits, usage settlements,
+ *         and a secure withdrawal process.
+ */
+contract PayerRegistry is
+    Initializable,
+    AccessControlUpgradeable,
+    UUPSUpgradeable,
+    PausableUpgradeable,
+    ReentrancyGuardUpgradeable,
+    IPayerRegistry
+{
+    using SafeERC20 for IERC20;
+    using EnumerableSet for EnumerableSet.AddressSet;
+
+    /* ============ Constants ============ */
+
+    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
+    string internal constant USDC_SYMBOL = "USDC";
+    uint8 private constant PAYER_OPERATOR_ID = 1;
+    uint64 private constant DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS = 10_000_000;
+    uint64 private constant DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS = 10_000_000;
+    uint64 private constant DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS = 50_000_000;
+    uint32 private constant DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD = 6 hours;
+    uint32 private constant ABSOLUTE_MINIMUM_TRANSFER_FEES_PERIOD = 1 hours;
+    uint32 private constant DEFAULT_WITHDRAWAL_LOCK_PERIOD = 3 days;
+    uint32 private constant ABSOLUTE_MINIMUM_WITHDRAWAL_LOCK_PERIOD = 1 days;
+
+    /* ============ UUPS Storage ============ */
+
+    /// @custom:storage-location erc7201:xmtp.storage.Payer
+    struct PayerStorage {
+        // Configuration and state parameters (fits in 2 slots)
+        uint64 minimumRegistrationAmountMicroDollars;
+        uint64 minimumDepositAmountMicroDollars;
+        uint64 maxTolerableDebtAmountMicroDollars;
+        uint64 lastFeeTransferTimestamp;
+        uint64 totalDeposited;
+        uint64 totalDebt;
+        uint64 pendingFees;
+        uint64 collectedFees;
+        uint32 withdrawalLockPeriod;
+        uint32 transferFeesPeriod;
+
+        // Contract addresses (fits in 3 slots)
+        address usdcToken;
+        address feeDistributor;
+        address nodeRegistry;
+        address payerReportManager;
+
+        // Mappings and dynamic sets (each starts at its own storage slot)
+        mapping(address => Payer) payers;
+        mapping(address => Withdrawal) withdrawals;
+        EnumerableSet.AddressSet totalPayers;
+        EnumerableSet.AddressSet activePayers;
+        EnumerableSet.AddressSet debtPayers;
+    }
+
+    // keccak256(abi.encode(uint256(keccak256("xmtp.storage.Payer")) - 1)) & ~bytes32(uint256(0xff))
+    bytes32 internal constant PAYER_STORAGE_LOCATION =
+        0xd0335f337c570f3417b0f0d20340c88da711d60e810b5e9b3ecabe9ccfcdce5a;
+
+    function _getPayerStorage() internal pure returns (PayerStorage storage $) {
+        // slither-disable-next-line assembly
+        assembly {
+            $.slot := PAYER_STORAGE_LOCATION
+        }
+    }
+
+    /* ============ Modifiers ============ */
+
+    /**
+     * @dev Modifier to check if caller is an active node operator.
+     */
+    modifier onlyNodeOperator(uint256 nodeId) {
+        require(_getIsActiveNodeOperator(nodeId), UnauthorizedNodeOperator());
+        _;
+    }
+
+    /**
+     * @dev Modifier to check if caller is the payer report contract.
+     */
+    modifier onlyPayerReport() {
+        require(msg.sender == _getPayerStorage().payerReportManager, Unauthorized());
+        _;
+    }
+
+    /**
+     * @dev Modifier to check if address is an active payer.
+     */
+    modifier onlyPayer(address payer) {
+        require(_payerExists(msg.sender), PayerDoesNotExist());
+        _;
+    }
+
+    /* ============ Initialization ============ */
+
+    /**
+     * @notice Initializes the contract with the deployer as admin.
+     * @param  initialAdmin The address of the admin.
+     * @dev    There's a chicken-egg problem here with PayerReport and Distribution contracts.
+     *         We need to deploy these contracts first, then set their addresses
+     *         in the Payer contract.
+     */
+    function initialize(address initialAdmin, address usdcToken, address nodesContract) public initializer {
+        if (initialAdmin == address(0) || usdcToken == address(0) || nodesContract == address(0)) {
+            revert InvalidAddress();
+        }
+
+        __UUPSUpgradeable_init();
+        __AccessControl_init();
+        __Pausable_init();
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        $.minimumRegistrationAmountMicroDollars = DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS;
+        $.minimumDepositAmountMicroDollars = DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS;
+        $.withdrawalLockPeriod = DEFAULT_WITHDRAWAL_LOCK_PERIOD;
+        $.maxTolerableDebtAmountMicroDollars = DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS;
+        $.transferFeesPeriod = DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD;
+
+        _setUsdcToken(usdcToken);
+        _setNodeRegistry(nodesContract);
+
+        _grantRole(DEFAULT_ADMIN_ROLE, initialAdmin);
+        _grantRole(ADMIN_ROLE, initialAdmin);
+    }
+
+    /* ============ Payers Management ============ */
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function register(uint64 amount) external whenNotPaused {
+        PayerStorage storage $ = _getPayerStorage();
+
+        require(amount >= $.minimumRegistrationAmountMicroDollars, InsufficientAmount());
+
+        if (_payerExists(msg.sender)) revert PayerAlreadyRegistered();
+
+        IERC20($.usdcToken).safeTransferFrom(msg.sender, address(this), amount);
+
+        // New payer registration
+        $.payers[msg.sender] = Payer({
+            balance: amount,
+            debtAmount: 0,
+            latestDepositTimestamp: uint64(block.timestamp)
+        });
+
+        // Add new payer to active and total payers sets
+        require($.activePayers.add(msg.sender), FailedToRegisterPayer());
+        require($.totalPayers.add(msg.sender), FailedToRegisterPayer());
+
+        _increaseTotalDeposited(amount);
+
+        emit PayerRegistered(msg.sender, amount);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function deposit(uint64 amount) external whenNotPaused nonReentrant onlyPayer(msg.sender) {
+        _validateAndProcessDeposit(msg.sender, msg.sender, amount);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function deposit(address payer, uint64 amount) external whenNotPaused {
+        _revertIfPayerDoesNotExist(payer);
+
+        _validateAndProcessDeposit(msg.sender, payer, amount);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function deactivatePayer(uint256 nodeId, address payer) external whenNotPaused onlyNodeOperator(nodeId) {
+        _revertIfPayerDoesNotExist(payer);
+
+        _deactivatePayer(nodeId, payer);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function deletePayer(address payer) external whenNotPaused onlyRole(ADMIN_ROLE) {
+        _revertIfPayerDoesNotExist(payer);
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        require($.withdrawals[payer].withdrawableTimestamp == 0, PayerInWithdrawal());
+
+        Payer memory _storedPayer = $.payers[payer];
+
+        if (_storedPayer.balance > 0 || _storedPayer.debtAmount > 0) {
+            revert PayerHasBalanceOrDebt();
+        }
+
+        // Delete all payer data
+        delete $.payers[payer];
+        require($.totalPayers.remove(payer), FailedToDeletePayer());
+        require($.activePayers.remove(payer), FailedToDeletePayer());
+
+        emit PayerDeleted(payer, uint64(block.timestamp));
+    }
+
+    /* ========== Payers Balance Management ========= */
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function requestWithdrawal(uint64 amount) external whenNotPaused onlyPayer(msg.sender) {
+        if (_withdrawalExists(msg.sender)) revert WithdrawalAlreadyRequested();
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        Payer memory _storedPayer = $.payers[msg.sender];
+
+        require(_storedPayer.debtAmount == 0, PayerHasDebt());
+        require(_storedPayer.balance >= amount, InsufficientBalance());
+
+        // Balance to be withdrawn is deducted from the payer's balance,
+        // it can't be used to settle payments.
+        $.payers[msg.sender].balance -= amount;
+        _decreaseTotalDeposited(amount);
+
+        uint64 withdrawableTimestamp = uint64(block.timestamp) + $.withdrawalLockPeriod;
+
+        $.withdrawals[msg.sender] = Withdrawal({
+            withdrawableTimestamp: withdrawableTimestamp,
+            amount: amount
+        });
+
+        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
+
+        emit WithdrawalRequested(msg.sender, withdrawableTimestamp, amount);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function cancelWithdrawal() external whenNotPaused onlyPayer(msg.sender) {
+        _revertIfWithdrawalNotExists(msg.sender);
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        Withdrawal memory _withdrawal = $.withdrawals[msg.sender];
+
+        delete $.withdrawals[msg.sender];
+
+        $.payers[msg.sender].balance += _withdrawal.amount;
+        _increaseTotalDeposited(_withdrawal.amount);
+
+        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
+
+        emit WithdrawalCancelled(msg.sender, _withdrawal.withdrawableTimestamp);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function finalizeWithdrawal() external whenNotPaused nonReentrant onlyPayer(msg.sender) {
+        _revertIfWithdrawalNotExists(msg.sender);
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        Withdrawal memory _withdrawal = $.withdrawals[msg.sender];
+
+        require(block.timestamp >= _withdrawal.withdrawableTimestamp, WithdrawalPeriodNotElapsed());
+
+        delete $.withdrawals[msg.sender];
+
+        uint64 _finalWithdrawalAmount = _withdrawal.amount;
+
+        if ($.payers[msg.sender].debtAmount > 0) {
+            _finalWithdrawalAmount = _settleDebts(msg.sender, _withdrawal.amount, true);
+        }
+
+        if (_finalWithdrawalAmount > 0) {
+            IERC20($.usdcToken).safeTransfer(msg.sender, _finalWithdrawalAmount);
+        }
+
+        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
+
+        emit WithdrawalFinalized(msg.sender, _withdrawal.withdrawableTimestamp, _finalWithdrawalAmount);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getWithdrawalStatus(address payer) external view returns (Withdrawal memory withdrawal) {
+        _revertIfPayerDoesNotExist(payer);
+
+        return _getPayerStorage().withdrawals[payer];
+    }
+
+    /* ============ Usage Settlement ============ */
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function settleUsage(uint256 originatorNode, address[] calldata payerList, uint64[] calldata usageAmountsList)
+        external
+        whenNotPaused
+        nonReentrant
+        onlyPayerReport
+    {
+        require(payerList.length == usageAmountsList.length, InvalidPayerListLength());
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        uint64 _settledFees = 0;
+        uint64 _pendingFees = $.pendingFees;
+
+        for (uint256 i = 0; i < payerList.length; i++) {
+            address payer = payerList[i];
+            uint64 usage = usageAmountsList[i];
+
+            // This should never happen, as PayerReport has already verified the payers and amounts.
+            // Payers in payerList should always exist and be active.
+            if (!_payerExists(payer) || !_payerIsActive(payer)) continue;
+
+            Payer memory _storedPayer = $.payers[payer];
+
+            if (_storedPayer.balance < usage) {
+                uint64 _debt = usage - _storedPayer.balance;
+
+                _settledFees += _storedPayer.balance;
+                _pendingFees += _storedPayer.balance;
+
+                _storedPayer.balance = 0;
+                _storedPayer.debtAmount = _debt;
+                $.payers[payer] = _storedPayer;
+
+                _addDebtor(payer);
+                _increaseTotalDebt(_debt);
+
+                if (_debt > $.maxTolerableDebtAmountMicroDollars) _deactivatePayer(PAYER_OPERATOR_ID, payer);
+
+                emit PayerBalanceUpdated(payer, _storedPayer.balance, _storedPayer.debtAmount);
+
+                continue;
+            }
+
+            _settledFees += usage;
+            _pendingFees += usage;
+
+            _storedPayer.balance -= usage;
+
+            $.payers[payer] = _storedPayer;
+
+            emit PayerBalanceUpdated(payer, _storedPayer.balance, _storedPayer.debtAmount);
+        }
+
+        $.pendingFees = _pendingFees;
+
+        emit UsageSettled(originatorNode, uint64(block.timestamp), _settledFees);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function transferFeesToDistribution() external whenNotPaused nonReentrant {
+        PayerStorage storage $ = _getPayerStorage();
+
+        /// @dev slither marks this as a security issue because validators can modify block.timestamp.
+        ///      However, in this scenario it's fine, as we'd just send fees a earlier than expected.
+        ///      It would be a bigger issue if we'd rely on timestamp for randomness or calculations.
+        // slither-disable-next-line timestamp
+        require(block.timestamp - $.lastFeeTransferTimestamp >= $.transferFeesPeriod, InsufficientTimePassed());
+
+        uint64 _pendingFeesAmount = $.pendingFees;
+
+        require(_pendingFeesAmount > 0, InsufficientAmount());
+
+        IERC20($.usdcToken).safeTransfer($.feeDistributor, _pendingFeesAmount);
+
+        $.lastFeeTransferTimestamp = uint64(block.timestamp);
+        $.collectedFees += _pendingFeesAmount;
+        $.pendingFees = 0;
+
+        emit FeesTransferred(uint64(block.timestamp), _pendingFeesAmount);
+    }
+
+    /* ========== Administrative Functions ========== */
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setFeeDistributor(address newFeeDistributor) external onlyRole(ADMIN_ROLE) {
+        _setFeeDistributor(newFeeDistributor);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setPayerReportManager(address newPayerReportManager) external onlyRole(ADMIN_ROLE) {
+        _setPayerReportManager(newPayerReportManager);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setNodeRegistry(address newNodeRegistry) external onlyRole(ADMIN_ROLE) {
+        _setNodeRegistry(newNodeRegistry);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setUsdcToken(address newUsdcToken) external onlyRole(ADMIN_ROLE) {
+        _setUsdcToken(newUsdcToken);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setMinimumDeposit(uint64 newMinimumDeposit) external onlyRole(ADMIN_ROLE) {
+        require(newMinimumDeposit > DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS, InvalidMinimumDeposit());
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        uint64 oldMinimumDeposit = $.minimumDepositAmountMicroDollars;
+        $.minimumDepositAmountMicroDollars = newMinimumDeposit;
+
+        emit MinimumDepositSet(oldMinimumDeposit, newMinimumDeposit);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setMinimumRegistrationAmount(uint64 newMinimumRegistrationAmount) external onlyRole(ADMIN_ROLE) {
+        require(
+            newMinimumRegistrationAmount > DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS,
+            InvalidMinimumRegistrationAmount()
+        );
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        uint64 _oldMinimumRegistrationAmount = $.minimumRegistrationAmountMicroDollars;
+        $.minimumRegistrationAmountMicroDollars = newMinimumRegistrationAmount;
+
+        emit MinimumRegistrationAmountSet(_oldMinimumRegistrationAmount, newMinimumRegistrationAmount);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setWithdrawalLockPeriod(uint32 newWithdrawalLockPeriod) external onlyRole(ADMIN_ROLE) {
+        require(newWithdrawalLockPeriod >= ABSOLUTE_MINIMUM_WITHDRAWAL_LOCK_PERIOD, InvalidWithdrawalLockPeriod());
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        uint32 _oldWithdrawalLockPeriod = $.withdrawalLockPeriod;
+        $.withdrawalLockPeriod = newWithdrawalLockPeriod;
+
+        emit WithdrawalLockPeriodSet(_oldWithdrawalLockPeriod, newWithdrawalLockPeriod);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setMaxTolerableDebtAmount(uint64 newMaxTolerableDebtAmountMicroDollars) external onlyRole(ADMIN_ROLE) {
+        require(newMaxTolerableDebtAmountMicroDollars > 0, InvalidMaxTolerableDebtAmount());
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        uint64 _oldMaxTolerableDebtAmount = $.maxTolerableDebtAmountMicroDollars;
+        $.maxTolerableDebtAmountMicroDollars = newMaxTolerableDebtAmountMicroDollars;
+
+        emit MaxTolerableDebtAmountSet(_oldMaxTolerableDebtAmount, newMaxTolerableDebtAmountMicroDollars);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function setTransferFeesPeriod(uint32 newTransferFeesPeriod) external onlyRole(ADMIN_ROLE) {
+        require(newTransferFeesPeriod >= ABSOLUTE_MINIMUM_TRANSFER_FEES_PERIOD, InvalidTransferFeesPeriod());
+
+        PayerStorage storage $ = _getPayerStorage();
+
+        uint32 _oldTransferFeesPeriod = $.transferFeesPeriod;
+        $.transferFeesPeriod = newTransferFeesPeriod;
+
+        emit TransferFeesPeriodSet(_oldTransferFeesPeriod, newTransferFeesPeriod);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function pause() external onlyRole(ADMIN_ROLE) {
+        _pause();
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function unpause() external onlyRole(ADMIN_ROLE) {
+        _unpause();
+    }
+
+    /* ============ Getters ============ */
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getPayer(address payer) external view returns (Payer memory payerInfo) {
+        _revertIfPayerDoesNotExist(payer);
+
+        return _getPayerStorage().payers[payer];
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getActivePayers(uint32 offset, uint32 limit)
+        external
+        view
+        returns (Payer[] memory payers, bool hasMore)
+    {
+        PayerStorage storage $ = _getPayerStorage();
+
+        (address[] memory _payerAddresses, bool _hasMore) = _getPaginatedAddresses($.activePayers, offset, limit);
+
+        payers = new Payer[](_payerAddresses.length);
+        for (uint256 i = 0; i < _payerAddresses.length; i++) {
+            payers[i] = $.payers[_payerAddresses[i]];
+        }
+
+        return (payers, _hasMore);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getIsActivePayer(address payer) public view returns (bool isActive) {
+        _revertIfPayerDoesNotExist(payer);
+
+        return _getPayerStorage().activePayers.contains(payer);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getPayerBalance(address payer) external view returns (uint64 balance) {
+        _revertIfPayerDoesNotExist(payer);
+
+        return _getPayerStorage().payers[payer].balance;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getPayersInDebt(uint32 offset, uint32 limit)
+        external
+        view
+        returns (Payer[] memory payers, bool hasMore)
+    {
+        PayerStorage storage $ = _getPayerStorage();
+
+        (address[] memory _payerAddresses, bool _hasMore) = _getPaginatedAddresses($.debtPayers, offset, limit);
+
+        payers = new Payer[](_payerAddresses.length);
+        for (uint256 i = 0; i < _payerAddresses.length; i++) {
+            payers[i] = $.payers[_payerAddresses[i]];
+        }
+
+        return (payers, _hasMore);
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getTotalPayerCount() external view returns (uint256 count) {
+        return _getPayerStorage().totalPayers.length();
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getActivePayerCount() external view returns (uint256 count) {
+        return _getPayerStorage().activePayers.length();
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getLastFeeTransferTimestamp() external view returns (uint64 timestamp) {
+        return _getPayerStorage().lastFeeTransferTimestamp;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getTotalValueLocked() external view returns (uint64 totalValueLocked) {
+        PayerStorage storage $ = _getPayerStorage();
+
+        if ($.totalDebt > $.totalDeposited) return 0;
+
+        return $.totalDeposited - $.totalDebt;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getTotalDebt() external view returns (uint64 totalDebt) {
+        return _getPayerStorage().totalDebt;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getContractBalance() external view returns (uint256 balance) {
+        return IERC20(_getPayerStorage().usdcToken).balanceOf(address(this));
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getFeeDistributor() external view returns (address feeDistributorAddress) {
+        return _getPayerStorage().feeDistributor;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getNodeRegistry() external view returns (address nodeRegistryAddress) {
+        return _getPayerStorage().nodeRegistry;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getPayerReportManager() external view returns (address payerReportManagerAddress) {
+        return _getPayerStorage().payerReportManager;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getMinimumDeposit() external view returns (uint64 minimumDeposit) {
+        return _getPayerStorage().minimumDepositAmountMicroDollars;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getMinimumRegistrationAmount() external view returns (uint64 minimumRegistrationAmount) {
+        return _getPayerStorage().minimumRegistrationAmountMicroDollars;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getWithdrawalLockPeriod() external view returns (uint32 lockPeriod) {
+        return _getPayerStorage().withdrawalLockPeriod;
+    }
+
+    /**
+     * @inheritdoc IPayerRegistry
+     */
+    function getPendingFees() external view returns (uint64 fees) {
+        return _getPayerStorage().pendingFees;
+    }
+
+    /* ============ Internal ============ */
+
+    /**
+    * @notice Validates and processes a deposit or donation
+    * @param from The address funds are coming from
+    * @param to The payer account receiving the deposit
+    * @param amount The amount to deposit
+    */
+    function _validateAndProcessDeposit(address from, address to, uint64 amount) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        require(amount >= $.minimumDepositAmountMicroDollars, InsufficientAmount());
+        require($.withdrawals[to].withdrawableTimestamp == 0, PayerInWithdrawal());
+
+        IERC20($.usdcToken).safeTransferFrom(from, address(this), amount);
+
+        _updatePayerBalance(to, amount);
+
+        emit PayerBalanceUpdated(to, $.payers[to].balance, $.payers[to].debtAmount);
+    }
+
+    /**
+     * @notice Updates a payer's balance, handling debt settlement if applicable.
+     * @param  payerAddress The address of the payer.
+     * @param  amount The amount to add to the payer's balance.
+     * @return leftoverAmount Amount remaining after debt settlement (if any).
+     */
+    function _updatePayerBalance(address payerAddress, uint64 amount) internal returns (uint64 leftoverAmount) {
+        Payer storage _payer = _getPayerStorage().payers[payerAddress];
+
+        if (_payer.debtAmount > 0) {
+            return _settleDebts(payerAddress, amount, false);
+        } else {
+            _payer.balance += amount;
+            _increaseTotalDeposited(amount);
+            return amount;
+        }
+    }
+
+    /**
+     * @notice Settles debts for a payer, updating their balance and total amounts.
+     * @param  payer The address of the payer.
+     * @param  amount The amount to settle debts for.
+     * @param  isWithdrawal Whether the debt settlement happens during a withdrawal.
+     * @return amountAfterSettlement The amount remaining after debt settlement.
+     */
+    function _settleDebts(address payer, uint64 amount, bool isWithdrawal) internal returns (uint64 amountAfterSettlement) {
+        PayerStorage storage $ = _getPayerStorage();
+
+        Payer memory _storedPayer = $.payers[payer];
+
+        if (_storedPayer.debtAmount < amount) {
+            uint64 _debtToRemove = _storedPayer.debtAmount;
+            amount -= _debtToRemove;
+
+            // For regular deposits, add remaining amount to balance.
+            // In withdrawals, that amount was moved to the withdrawal balance.
+            if (!isWithdrawal) {
+                _storedPayer.balance += amount;
+                _increaseTotalDeposited(amount);
+            }
+
+            _removeDebtor(payer);
+            _increaseTotalDeposited(amount);
+            _decreaseTotalDebt(_debtToRemove);
+
+            amountAfterSettlement = amount;
+        } else {
+            _storedPayer.debtAmount -= amount;
+
+            _decreaseTotalDebt(amount);
+
+            amountAfterSettlement = 0;
+        }
+
+        $.payers[payer] = _storedPayer;
+
+        return amountAfterSettlement;
+    }
+
+    /**
+     * @notice Checks if a payer exists.
+     * @param  payer The address of the payer to check.
+     * @return exists True if the payer exists, false otherwise.
+     */
+    function _payerExists(address payer) internal view returns (bool exists) {
+        return _getPayerStorage().payers[payer].latestDepositTimestamp != 0;
+    }
+
+    /**
+     * @notice Checks if a payer is active.
+     * @param  payer The address of the payer to check.
+     * @return isActive True if the payer is active, false otherwise.
+     */
+    function _payerIsActive(address payer) internal view returns (bool isActive) {
+        return _getPayerStorage().activePayers.contains(payer);
+    }
+
+    /**
+     * @notice Deactivates a payer.
+     * @param  payer The address of the payer to deactivate.
+     */
+    function _deactivatePayer(uint256 operatorId, address payer) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        require($.activePayers.remove(payer), FailedToDeactivatePayer());
+
+        emit PayerDeactivated(operatorId, payer);
+    }
+
+    /**
+     * @notice Reverts if a payer does not exist.
+     * @param  payer The address of the payer to check.
+     */
+    function _revertIfPayerDoesNotExist(address payer) internal view {
+        require(_payerExists(payer), PayerDoesNotExist());
+    }
+
+    /**
+     * @notice Checks if a withdrawal exists.
+     * @param  payer The address of the payer to check.
+     * @return exists True if the withdrawal exists, false otherwise.
+     */
+    function _withdrawalExists(address payer) internal view returns (bool exists) {
+        return _getPayerStorage().withdrawals[payer].withdrawableTimestamp != 0;
+    }
+
+    /**
+     * @notice Reverts if a withdrawal does not exist.
+     * @param  payer The address of the payer to check.
+     */
+    function _revertIfWithdrawalNotExists(address payer) internal view {
+        require(_withdrawalExists(payer), WithdrawalNotExists());
+    }
+
+    /**
+     * @notice Removes a payer from the debt payers set.
+     * @param  payer The address of the payer to remove.
+     */
+    function _removeDebtor(address payer) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        if ($.debtPayers.contains(payer)) {
+            require($.debtPayers.remove(payer), FailedToRemoveDebtor());
+        }
+    }
+
+    /**
+     * @notice Adds a payer to the debt payers set.
+     * @param  payer The address of the payer to add.
+     */
+    function _addDebtor(address payer) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        if (!$.debtPayers.contains(payer)) {
+            require($.debtPayers.add(payer), FailedToAddDebtor());
+        }
+    }
+
+    /**
+     * @notice Checks if a given address is an active node operator.
+     * @param  nodeId The nodeID of the operator to check.
+     * @return isActiveNodeOperator True if the address is an active node operator, false otherwise.
+     */
+    function _getIsActiveNodeOperator(uint256 nodeId) internal view returns (bool isActiveNodeOperator) {
+        INodes nodes = INodes(_getPayerStorage().nodeRegistry);
+
+        require(msg.sender == nodes.ownerOf(nodeId), Unauthorized());
+
+        // TODO: Change for a better filter.
+        return nodes.getReplicationNodeIsActive(nodeId);
+    }
+
+    /**
+     * @notice Sets the FeeDistributor contract.
+     * @param  newFeeDistributor The address of the new FeeDistributor contract.
+     */
+    function _setFeeDistributor(address newFeeDistributor) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        try IFeeDistributor(newFeeDistributor).supportsInterface(type(IFeeDistributor).interfaceId) returns (bool supported) {
+            require(supported, InvalidFeeDistributor());
+        } catch {
+            revert InvalidFeeDistributor();
+        }
+
+        $.feeDistributor = newFeeDistributor;
+
+        emit FeeDistributorSet(newFeeDistributor);
+    }
+
+    /**
+     * @notice Sets the PayerReportManager contract.
+     * @param  newPayerReportManager The address of the new PayerReportManager contract.
+     */
+    function _setPayerReportManager(address newPayerReportManager) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        try IPayerReportManager(newPayerReportManager).supportsInterface(type(IPayerReportManager).interfaceId) returns (bool supported) {
+            require(supported, InvalidPayerReportManager());
+        } catch {
+            revert InvalidPayerReportManager();
+        }
+
+        $.payerReportManager = newPayerReportManager;
+
+        emit PayerReportManagerSet(newPayerReportManager);
+    }
+
+    /**
+     * @notice Sets the NodeRegistry contract.
+     * @param  newNodeRegistry The address of the new NodeRegistry contract.
+     */
+    function _setNodeRegistry(address newNodeRegistry) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        try INodes(newNodeRegistry).supportsInterface(type(INodes).interfaceId) returns (bool supported) {
+            require(supported, InvalidNodeRegistry());
+        } catch {
+            revert InvalidNodeRegistry();
+        }
+
+        $.nodeRegistry = newNodeRegistry;
+
+        emit NodeRegistrySet(newNodeRegistry);
+    }
+
+    /**
+     * @notice Sets the USDC token contract.
+     * @param  newUsdcToken The address of the new USDC token contract.
+     */
+    function _setUsdcToken(address newUsdcToken) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        try IERC20Metadata(newUsdcToken).symbol() returns (string memory symbol) {
+            require(keccak256(bytes(symbol)) == keccak256(bytes(USDC_SYMBOL)), InvalidUsdcTokenContract());
+        } catch {
+            revert InvalidUsdcTokenContract();
+        }
+
+        $.usdcToken = newUsdcToken;
+
+        emit UsdcTokenSet(newUsdcToken);
+    }
+
+    /**
+     * @notice Increases the total amount deposited by a given amount.
+     * @param  amount The amount to increase the total amount deposited by.
+     */
+    function _increaseTotalDeposited(uint64 amount) internal {
+        if (amount > 0) _getPayerStorage().totalDeposited += amount;
+    }
+
+    /**
+     * @notice Decreases the total amount deposited by a given amount.
+     * @param  amount The amount to decrease the total amount deposited by.
+     */
+    function _decreaseTotalDeposited(uint64 amount) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        $.totalDeposited = amount > $.totalDeposited ? 0 : $.totalDeposited - amount;
+    }
+
+    /**
+     * @notice Increases the total debt amount by a given amount.
+     * @param  amount The amount to increase the total debt amount by.
+     */
+    function _increaseTotalDebt(uint64 amount) internal {
+        _getPayerStorage().totalDebt += amount;
+    }
+
+    /**
+     * @notice Decreases the total debt amount by a given amount.
+     * @param  amount The amount to decrease the total debt amount by.
+     */
+    function _decreaseTotalDebt(uint64 amount) internal {
+        PayerStorage storage $ = _getPayerStorage();
+
+        $.totalDebt = amount > $.totalDebt ? 0 : $.totalDebt - amount;
+    }
+
+    /**
+     * @notice Internal helper for paginated access to EnumerableSet.AddressSet.
+     * @param  addressSet The EnumerableSet to paginate.
+     * @param  offset The starting index.
+     * @param  limit Maximum number of items to return.
+     * @return addresses Array of addresses from the set.
+     * @return hasMore Whether there are more items after this page.
+     */
+    function _getPaginatedAddresses(EnumerableSet.AddressSet storage addressSet, uint256 offset, uint256 limit)
+        internal
+        view
+        returns (address[] memory addresses, bool hasMore)
+    {
+        uint256 _totalCount = addressSet.length();
+
+        if (offset >= _totalCount) revert OutOfBounds();
+
+        uint256 _count = _totalCount - offset;
+        if (_count > limit) {
+            _count = limit;
+            hasMore = true;
+        } else {
+            hasMore = false;
+        }
+
+        addresses = new address[](_count);
+
+        for (uint256 i = 0; i < _count; i++) {
+            addresses[i] = addressSet.at(offset + i);
+        }
+
+        return (addresses, hasMore);
+    }
+
+    /* ============ Upgradeability ============ */
+
+    /**
+     * @dev   Authorizes the upgrade of the contract.
+     * @param newImplementation The address of the new implementation.
+     */
+    function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {
+        require(newImplementation != address(0), InvalidAddress());
+        emit UpgradeAuthorized(msg.sender, newImplementation);
+    }
+
+    /* ============ ERC165 ============ */
+
+    /**
+     * @dev Override to support IPayerRegistry, IERC165 and AccessControlUpgradeable.
+     */
+    function supportsInterface(bytes4 interfaceId)
+        public
+        view
+        override(IERC165, AccessControlUpgradeable)
+        returns (bool supported)
+    {
+        return interfaceId == type(IPayerRegistry).interfaceId || super.supportsInterface(interfaceId);
+    }
+}
diff --git a/src/interfaces/IFeeDistributor.sol b/src/interfaces/IFeeDistributor.sol
new file mode 100644
index 0000000..8bee78c
--- /dev/null
+++ b/src/interfaces/IFeeDistributor.sol
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.28;
+
+import { IERC165 } from "../../lib/oz/contracts/utils/introspection/IERC165.sol";
+
+/**
+ * @title  IFeeDistributor
+ * @notice Interface for distributing rewards.
+ */
+interface IFeeDistributor is IERC165 {
+    /* ============ Distribution ============ */
+
+    /**
+     * @notice Distributes the rewards.
+     * @dev    Only callable by the Payer contract. When called:
+     *         - The protocol fee is deducted and kept in the treasury.
+     *         - The Node Operators are paid for their operational costs.
+     *         - Any excess is split between:
+     *           - The possibility of minting XMTP rebates to incentivize Payers.
+     *           - Used to buy back and burn XMTP tokens.
+     */
+    function distributeRewards() external; /* onlyPayerContract */
+
+    /* ============ Administrative Functions ============ */
+
+    /**
+     * @notice Refreshes the node operator list.
+     * @dev    Only callable by the admin or active nodes.
+     *         This function is used to refresh the node operator list.
+     *         It is called when a new node is added, disabled or removed.
+     */
+    function refreshNodeOperatorList() external;
+
+    /**
+     * @notice Sets the address of the nodes contract for operator verification.
+     * @param  nodesContract The address of the new nodes contract.
+     *
+     * Emits `NodesContractUpdated`.
+     */
+    function setNodesContract(address nodesContract) external;
+
+    /**
+     * @notice Sets the address of the payer contract.
+     * @param  payerContract The address of the new payer contract.
+     *
+     * Emits `PayerContractUpdated`.
+     */
+    function setPayerContract(address payerContract) external;
+
+    /**
+     * @notice Sets the protocol fee.
+     * @param  newProtocolFee New protocol fee (in basis points, e.g., 100 = 1%).
+     */
+    function setProtocolFee(uint256 newProtocolFee) external;
+
+    /**
+     * @notice Sets the rebates percentage which will be minted as XMTP rebates.
+     * @param  newRebatesPercentage New rebates percentage.
+     *
+     * Emits `RebatesPercentageUpdated`.
+     */
+    function setRebatesPercentage(uint256 newRebatesPercentage) external;
+
+    /**
+     * @notice Sets the address of the USDC token contract.
+     * @param  usdcToken The address of the new USDC token contract.
+     *
+     * Emits `UsdcTokenUpdated`.
+     */
+    function setUsdcToken(address usdcToken) external;
+
+    /* ============ Getters ============ */
+
+    /**
+     * @notice Returns the current address of the nodes contract.
+     * @return nodesContract The current address of the nodes contract.
+     */
+    function getNodesContract() external view returns (address nodesContract);
+
+    /**
+     * @notice Returns the current address of the payer contract.
+     * @return payerContract The current address of the payer contract.
+     */
+    function getPayerContract() external view returns (address payerContract);
+
+    /**
+     * @notice Returns the current protocol fee (in basis points).
+     * @return protocolFee The current protocol fee.
+     */
+    function getProtocolFee() external view returns (uint256 protocolFee);
+
+    /**
+     * @notice Returns the current rebates percentage (in basis points).
+     * @return rebatesPercentage The current rebates percentage.
+     */
+    function getRebatesPercentage() external view returns (uint256 rebatesPercentage);
+
+    /**
+     * @notice Returns the current address of the USDC token contract.
+     * @return usdcToken The current address of the USDC token contract.
+     */
+    function getUsdcToken() external view returns (address usdcToken);
+}
diff --git a/src/interfaces/IPayerRegistry.sol b/src/interfaces/IPayerRegistry.sol
new file mode 100644
index 0000000..ef530b8
--- /dev/null
+++ b/src/interfaces/IPayerRegistry.sol
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.28;
+
+import {IERC165} from "../../lib/oz/contracts/utils/introspection/IERC165.sol";
+
+/**
+ * @title  IPayerRegistryEvents
+ * @notice Interface for events emitted by the PayerRegistry contract.
+ */
+interface IPayerRegistryEvents {
+    /// @dev Emitted when the fee distributor address is updated.
+    event FeeDistributorSet(address indexed newFeeDistributor);
+
+    /// @dev Emitted when fees are transferred to the distribution contract.
+    event FeesTransferred(uint64 indexed timestamp, uint64 amount);
+
+    /// @dev Emitted when the maximum tolerable debt amount is updated.
+    event MaxTolerableDebtAmountSet(uint64 oldMaxTolerableDebtAmount, uint64 newMaxTolerableDebtAmount);
+
+    /// @dev Emitted when the minimum deposit amount is updated.
+    event MinimumDepositSet(uint64 oldMinimumDeposit, uint64 newMinimumDeposit);
+
+    /// @dev Emitted when the minimum registration amount is updated.
+    event MinimumRegistrationAmountSet(uint64 oldMinimumRegistrationAmount, uint64 newMinimumRegistrationAmount);
+
+    /// @dev Emitted when the node registry address is updated.
+    event NodeRegistrySet(address indexed newNodeRegistry);
+
+    /// @dev Emitted when a payer balance is updated.
+    event PayerBalanceUpdated(address indexed payer, uint64 newBalance, uint64 newDebtAmount);
+
+    /// @dev Emitted when a payer is deactivated by an owner.
+    event PayerDeactivated(uint256 indexed operatorId, address indexed payer);
+
+    /// @dev Emitted when a payer is permanently deleted from the system.
+    event PayerDeleted(address indexed payer, uint64 timestamp);
+
+    /// @dev Emitted when a new payer is registered.
+    event PayerRegistered(address indexed payer, uint64 amount);
+
+    /// @dev Emitted when the payer report manager address is updated.
+    event PayerReportManagerSet(address indexed newPayerReportManager);
+
+    /// @dev Emitted when the transfer fees period is updated.
+    event TransferFeesPeriodSet(uint32 oldTransferFeesPeriod, uint32 newTransferFeesPeriod);
+
+    /// @dev Emitted when the upgrade is authorized.
+    event UpgradeAuthorized(address indexed upgrader, address indexed newImplementation);
+
+    /// @dev Emitted when usage is settled and fees are calculated.
+    event UsageSettled(uint256 indexed originatorNode, uint64 timestamp, uint64 collectedFees);
+
+    /// @dev Emitted when the USDC token address is updated.
+    event UsdcTokenSet(address indexed newUsdcToken);
+
+    /// @dev Emitted when a payer cancels a withdrawal request.
+    event WithdrawalCancelled(address indexed payer, uint64 indexed withdrawableTimestamp);
+
+    /// @dev Emitted when a payer's withdrawal is finalized.
+    event WithdrawalFinalized(address indexed payer, uint64 indexed withdrawableTimestamp, uint64 amount);
+
+    /// @dev Emitted when the withdrawal lock period is updated.
+    event WithdrawalLockPeriodSet(uint32 oldWithdrawalLockPeriod, uint32 newWithdrawalLockPeriod);
+
+    /// @dev Emitted when a payer initiates a withdrawal request.
+    event WithdrawalRequested(
+        address indexed payer, uint64 indexed withdrawableTimestamp, uint64 amount
+    );
+}
+
+/**
+ * @title  IPayerRegistryErrors
+ * @notice Interface for errors emitted by the Payer contract.
+ */
+interface IPayerRegistryErrors {
+    /// @dev Error thrown when arrays have mismatched lengths.
+    error ArrayLengthMismatch();
+
+    /// @notice Error thrown when adding a debtor has failed.
+    error FailedToAddDebtor();
+
+    /// @notice Error thrown when deactivating a payer has failed.
+    error FailedToDeactivatePayer();
+
+    /// @notice Error thrown when deleting a payer has failed.
+    error FailedToDeletePayer();
+
+    /// @notice Error thrown when granting a role has failed.
+    error FailedToGrantRole(bytes32 role, address account);
+
+    /// @notice Error thrown when registering a payer has failed.
+    error FailedToRegisterPayer();
+
+    /// @notice Error thrown when removing a debtor has failed.
+    error FailedToRemoveDebtor();
+
+    /// @dev Error thrown when an address is invalid (usually zero address).
+    error InvalidAddress();
+
+    /// @dev Error thrown when the amount is insufficient.
+    error InsufficientAmount();
+
+    /// @dev Error thrown when balance is insufficient.
+    error InsufficientBalance();
+
+    /// @dev Error thrown when insufficient time has passed since the last fee transfer.
+    error InsufficientTimePassed();
+
+    /// @dev Error thrown when contract is not the fee distributor.
+    error InvalidFeeDistributor();
+
+    /// @dev Error thrown when the maximum tolerable debt amount is invalid.
+    error InvalidMaxTolerableDebtAmount();
+
+    /// @dev Error thrown when the minimum deposit is invalid.
+    error InvalidMinimumDeposit();
+
+    /// @dev Error thrown when the minimum registration amount is invalid.
+    error InvalidMinimumRegistrationAmount();
+
+    /// @dev Error thrown when contract is not the node registry.
+    error InvalidNodeRegistry();
+
+    /// @notice Error thrown when the payer list length is invalid.
+    error InvalidPayerListLength();
+
+    /// @dev Error thrown when contract is not the payer report manager.
+    error InvalidPayerReportManager();
+
+    /// @dev Error thrown when trying to backdate settlement too far.
+    error InvalidSettlementTime();
+
+    /// @dev Error thrown when the transfer fees period is invalid.
+    error InvalidTransferFeesPeriod();
+
+    /// @dev Error thrown when contract is not the USDC token contract.
+    error InvalidUsdcTokenContract();
+
+    /// @dev Error thrown when the withdrawal lock period is invalid.
+    error InvalidWithdrawalLockPeriod();
+
+    /// @dev Error thrown when a lock period has not yet elapsed.
+    error LockPeriodNotElapsed();
+
+    /// @notice Error thrown when the offset is out of bounds.
+    error OutOfBounds();
+
+    /// @dev Error thrown when payer already exists.
+    error PayerAlreadyRegistered();
+
+    /// @dev Error thrown when payer does not exist.
+    error PayerDoesNotExist();
+
+    /// @dev Error thrown when trying to delete a payer with balance or debt.
+    error PayerHasBalanceOrDebt();
+
+    /// @dev Error thrown when payer has debt.
+    error PayerHasDebt();
+
+    /// @dev Error thrown when trying to delete a payer in withdrawal state.
+    error PayerInWithdrawal();
+
+    /// @dev Error thrown when a payer is not active.
+    error PayerIsNotActive();
+
+    /// @dev Error thrown when a call is unauthorized.
+    error Unauthorized();
+
+    /// @dev Error thrown when caller is not an authorized node operator.
+    error UnauthorizedNodeOperator();
+
+    /// @dev Error thrown when a withdrawal is already in progress.
+    error WithdrawalAlreadyRequested();
+
+    /// @dev Error thrown when a withdrawal is not in the requested state.
+    error WithdrawalNotExists();
+
+    /// @dev Error thrown when a withdrawal is not in the requested state.
+    error WithdrawalNotRequested();
+
+    /// @dev Error thrown when a withdrawal lock period has not yet elapsed.
+    error WithdrawalPeriodNotElapsed();
+}
+
+/**
+ * @title  IPayerRegistry
+ * @notice Interface for managing payer USDC deposits, usage settlements,
+ *         and a secure withdrawal process.
+ */
+interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors {
+    /* ============ Structs ============ */
+
+    /**
+     * @dev   Struct to store payer information.
+     * @param balance                The current USDC balance of the payer.
+     * @param debtAmount             The amount of fees owed but not yet settled.
+     * @param latestDepositTimestamp The timestamp of the most recent deposit.
+     */
+    struct Payer {
+        uint64 balance;
+        uint64 debtAmount;
+        uint64 latestDepositTimestamp;
+    }
+
+    /**
+     * @dev   Struct to store withdrawal request information.
+     * @param withdrawableTimestamp The timestamp when the withdrawal can be finalized.
+     * @param amount                The amount requested for withdrawal.
+     */
+    struct Withdrawal {
+        uint64 withdrawableTimestamp;
+        uint64 amount;
+    }
+
+    /* ============ Payer Management ============ */
+
+    /**
+     * @notice Registers the caller as a new payer upon depositing the minimum required USDC.
+     *         The caller must approve this contract to spend USDC beforehand.
+     * @param  amount The amount of USDC to deposit (must be at least the minimum required).
+     *
+     * Emits `PayerRegistered`.
+     */
+    function register(uint64 amount) external;
+
+    /**
+     * @notice Allows the caller to deposit USDC into their own payer account.
+     *         The caller must approve this contract to spend USDC beforehand.
+     * @param  amount The amount of USDC to deposit.
+     *
+     * Emits `PayerBalanceUpdated`.
+     */
+    function deposit(uint64 amount) external;
+
+    /**
+     * @notice Allows anyone to donate USDC to an existing payer's account.
+     *         The sender must approve this contract to spend USDC beforehand.
+     * @param  payer  The address of the payer receiving the donation.
+     * @param  amount The amount of USDC to donate.
+     *
+     * Emits `PayerBalanceUpdated`.
+     */
+    function deposit(address payer, uint64 amount) external;
+
+    /**
+     * @notice Deactivates a payer, signaling XMTP nodes they should not accept messages from them.
+     *         Only callable by authorized node operators.
+     * @param  operatorId The ID of the operator calling the function.
+     * @param  payer      The address of the payer to deactivate.
+     *
+     * Emits `PayerDeactivated`.
+     */
+    function deactivatePayer(uint256 operatorId, address payer) external;
+
+    /**
+     * @notice Permanently deletes a payer from the system.
+     * @dev    Can only delete payers with zero balance and zero debt who are not in withdrawal.
+     *         Only callable by authorized node operators.
+     * @param  payer The address of the payer to delete.
+     *
+     * Emits `PayerDeleted`.
+     */
+    function deletePayer(address payer) external;
+
+    /* ============ Payer Balance Management ============ */
+
+    /**
+     * @notice Initiates a withdrawal request for the caller.
+     *         - Sets the payer into withdrawal mode (no further usage allowed).
+     *         - Records a timestamp for the withdrawal lock period.
+     * @param  amount The amount to withdraw (can be less than or equal to current balance).
+     *
+     * Emits `WithdrawalRequest`.
+     * Emits `PayerBalanceUpdated`.
+     */
+    function requestWithdrawal(uint64 amount) external;
+
+    /**
+     * @notice Cancels a previously requested withdrawal, removing withdrawal mode.
+     * @dev    Only callable by the payer who initiated the withdrawal.
+     *
+     * Emits `WithdrawalCancelled`.
+     * Emits `PayerBalanceUpdated`.
+     */
+    function cancelWithdrawal() external;
+
+    /**
+     * @notice Finalizes a payer's withdrawal after the lock period has elapsed.
+     *         - Accounts for any pending usage during the lock.
+     *         - Returns the unspent balance to the payer.
+     *
+     * Emits `WithdrawalFinalized`.
+     * Emits `PayerBalanceUpdated`.
+     */
+    function finalizeWithdrawal() external;
+
+    /**
+     * @notice Checks if a payer is currently in withdrawal mode and the timestamp
+     *         when they initiated the withdrawal.
+     * @param  payer                 The address to check.
+     * @return withdrawal            The withdrawal status.
+     */
+    function getWithdrawalStatus(address payer) external view returns (Withdrawal memory withdrawal);
+
+    /* ============ Usage Settlement ============ */
+
+    /**
+     * @notice Settles usage for a contiguous batch of (payer, amount) entries.
+     * Assumes that the PayerReport contract has already verified the validity of the payers and amounts.
+     *
+     * @param  originatorNode The originator node of the usage.
+     * @param  payers         A contiguous array of payer addresses.
+     * @param  amounts        A contiguous array of usage amounts corresponding to each payer.
+     *
+     * Emits `UsageSettled`.
+     * Emits `PayerBalanceUpdated` for each payer.
+     */
+    function settleUsage(
+        uint256 originatorNode,
+        address[] calldata payers,
+        uint64[] calldata amounts
+    ) external; /* onlyPayerReport */
+
+    /**
+     * @notice Transfers all pending fees to the designated distribution contract.
+     * @dev    Uses a single storage write for updating accumulated fees.
+     *
+     * Emits `FeesTransferred`.
+     */
+    function transferFeesToDistribution() external;
+
+    /* ============ Administrative Functions ============ */
+
+    /**
+     * @notice Sets the address of the fee distributor.
+     * @param  feeDistributor The address of the new fee distributor.
+     *
+     * Emits `FeeDistributorUpdated`.
+     */
+    function setFeeDistributor(address feeDistributor) external;
+
+    /**
+     * @notice Sets the address of the payer report manager.
+     * @param  payerReportManager The address of the new payer report manager.
+     *
+     * Emits `PayerReportManagerUpdated`.
+     */
+    function setPayerReportManager(address payerReportManager) external;
+
+    /**
+     * @notice Sets the address of the node registry for operator verification.
+     * @param  nodeRegistry The address of the new node registry.
+     *
+     * Emits `NodeRegistryUpdated`.
+     */
+    function setNodeRegistry(address nodeRegistry) external;
+
+    /**
+     * @notice Sets the address of the USDC token contract.
+     * @param  usdcToken The address of the new USDC token contract.
+     *
+     * Emits `UsdcTokenUpdated`.
+     */
+    function setUsdcToken(address usdcToken) external;
+
+    /**
+     * @notice Sets the minimum deposit amount required for registration.
+     * @param  newMinimumDeposit The new minimum deposit amount.
+     *
+     * Emits `MinimumDepositUpdated`.
+     */
+    function setMinimumDeposit(uint64 newMinimumDeposit) external;
+
+    /**
+     * @notice Sets the minimum deposit amount required for registration.
+     * @param  newMinimumRegistrationAmount The new minimum deposit amount.
+     *
+     * Emits `MinimumRegistrationAmountUpdated`.
+     */
+    function setMinimumRegistrationAmount(uint64 newMinimumRegistrationAmount) external;
+
+    /**
+     * @notice Sets the withdrawal lock period.
+     * @param  newWithdrawalLockPeriod The new withdrawal lock period.
+     *
+     * Emits `WithdrawalLockPeriodUpdated`.
+     */
+    function setWithdrawalLockPeriod(uint32 newWithdrawalLockPeriod) external;
+
+    /**
+     * @notice Sets the maximum tolerable debt amount.
+     * @param  newMaxTolerableDebtAmount The new maximum tolerable debt amount.
+     *
+     * Emits `MaxTolerableDebtAmountUpdated`.
+     */
+    function setMaxTolerableDebtAmount(uint64 newMaxTolerableDebtAmount) external;
+
+    /**
+     * @notice Sets the transfer fees period.
+     * @param  newTransferFeesPeriod The new transfer fees period.
+     *
+     * Emits `TransferFeesPeriodUpdated`.
+     */
+    function setTransferFeesPeriod(uint32 newTransferFeesPeriod) external;
+
+    /**
+     * @notice Pauses the contract functions in case of emergency.
+     *
+     * Emits `Paused()`.
+     */
+    function pause() external;
+
+    /**
+     * @notice Unpauses the contract.
+     *
+     * Emits `Unpaused()`.
+     */
+    function unpause() external;
+
+    /* ============ Getters ============ */
+
+    /**
+     * @notice Returns the payer information.
+     * @param  payer The address of the payer.
+     * @return payerInfo The payer information.
+     */
+    function getPayer(address payer) external view returns (Payer memory payerInfo);
+
+    /**
+     * @notice Returns all active payers in a paginated response.
+     * @param  offset Number of payers to skip before starting to return results.
+     * @param  limit  Maximum number of payers to return.
+     * @return payers The payer information.
+     * @return hasMore True if there are more payers to retrieve.
+     */
+    function getActivePayers(uint32 offset, uint32 limit)
+        external
+        view
+        returns (Payer[] memory payers, bool hasMore);
+
+    /**
+     * @notice Checks if a given address is an active payer.
+     * @param  payer    The address to check.
+     * @return isActive True if the address is an active payer, false otherwise.
+     */
+    function getIsActivePayer(address payer) external view returns (bool isActive);
+
+    /**
+     * @notice Returns a paginated list of payers with outstanding debt.
+     * @param  offset      Number of payers to skip before starting to return results.
+     * @param  limit       Maximum number of payers to return.
+     * @return payers      Array of payer addresses with debt.
+     * @return hasMore     True if there are more payers to retrieve.
+     */
+    function getPayersInDebt(uint32 offset, uint32 limit)
+        external
+        view
+        returns (Payer[] memory payers, bool hasMore);
+
+    /**
+     * @notice Returns the total number of registered payers.
+     * @return count The total number of registered payers.
+     */
+    function getTotalPayerCount() external view returns (uint256 count);
+
+    /**
+     * @notice Returns the number of active payers.
+     * @return count The number of active payers.
+     */
+    function getActivePayerCount() external view returns (uint256 count);
+
+    /**
+     * @notice Returns the timestamp of the last fee transfer to the rewards contract.
+     * @return timestamp The last fee transfer timestamp.
+     */
+    function getLastFeeTransferTimestamp() external view returns (uint64 timestamp);
+
+    /**
+     * @notice Returns the total value locked in the contract (all payer balances).
+     * @return tvl The total value locked in USDC.
+     */
+    function getTotalValueLocked() external view returns (uint64 tvl);
+
+    /**
+     * @notice Returns the total outstanding debt amount across all payers.
+     * @return totalDebt The total debt amount in USDC.
+     */
+    function getTotalDebt() external view returns (uint64 totalDebt);
+
+    /**
+     * @notice Returns the actual USDC balance held by the contract.
+     * @dev    This can be used to verify the contract's accounting is accurate.
+     * @return balance The USDC token balance of the contract.
+     */
+    function getContractBalance() external view returns (uint256 balance);
+
+    /**
+     * @notice Retrieves the address of the current distribution contract.
+     * @return feeDistributor The address of the fee distributor.
+     */
+    function getFeeDistributor() external view returns (address feeDistributor);
+
+    /**
+     * @notice Retrieves the address of the current nodes contract.
+     * @return nodeRegistry The address of the node registry.
+     */
+    function getNodeRegistry() external view returns (address nodeRegistry);
+
+    /**
+     * @notice Retrieves the address of the current payer report manager.
+     * @return payerReportManager The address of the payer report manager.
+     */
+    function getPayerReportManager() external view returns (address payerReportManager);
+
+    /**
+     * @notice Retrieves the minimum deposit amount required to register as a payer.
+     * @return minimumDeposit The minimum deposit amount in USDC.
+     */
+    function getMinimumDeposit() external view returns (uint64 minimumDeposit);
+
+    /**
+     * @notice Retrieves the minimum deposit amount required to register as a payer.
+     * @return minimumRegistrationAmount The minimum deposit amount in USDC.
+     */
+    function getMinimumRegistrationAmount() external view returns (uint64 minimumRegistrationAmount);
+
+    /**
+     * @notice Retrieves the current total balance of a given payer.
+     * @param  payer   The address of the payer.
+     * @return balance The current balance of the payer.
+     */
+    function getPayerBalance(address payer) external view returns (uint64 balance);
+
+    /**
+     * @notice Returns the duration of the lock period required before a withdrawal
+     *         can be finalized.
+     * @return lockPeriod The lock period in seconds.
+     */
+    function getWithdrawalLockPeriod() external view returns (uint32 lockPeriod);
+
+    /**
+     * @notice Retrieves the total pending fees that have not yet been transferred
+     *         to the distribution contract.
+     * @return fees The total pending fees in USDC.
+     */
+    function getPendingFees() external view returns (uint64 fees);
+}
diff --git a/src/interfaces/IPayerReportManager.sol b/src/interfaces/IPayerReportManager.sol
new file mode 100644
index 0000000..9dd0c75
--- /dev/null
+++ b/src/interfaces/IPayerReportManager.sol
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.28;
+
+import {IERC165} from "../../lib/oz/contracts/utils/introspection/IERC165.sol";
+
+/**
+ * @title  IPayerReportManager
+ * @notice Interface for the PayerReportManager contract handling usage reports and batch settlements.
+ */
+interface IPayerReportManager is IERC165 {
+
+    /* ============ Structs ============ */
+
+    /**
+     * @notice A struct containing the usage report details.
+     * @param  originatorNode       The address of the originator node.
+     * @param  startingSequenceID   The starting sequence ID of the report.
+     * @param  endingSequenceID     The ending sequence ID of the report.
+     * @param  lastMessageTimestamp The timestamp of the last message in the report.
+     * @param  reportTimestamp      The timestamp of the report.
+     * @param  reportMerkleRoot     The Merkle root of the report.
+     * @param  leafCount            The number of leaves in the report. A leaf is a single (payer, amount) pair.
+     */
+    struct PayerReport {
+        address originatorNode;
+        uint256 startingSequenceID;
+        uint256 endingSequenceID;
+        uint256 lastMessageTimestamp;
+        uint256 reportTimestamp;
+        bytes32 reportMerkleRoot;
+        uint16 leafCount;
+    }
+
+    /* ============ Events ============ */
+
+    /**
+     * @dev Emitted when an originator node submits a usage report.
+     * The report includes the Merkle root of the detailed (payer, amount) data.
+     * Note: Payers and amounts are not stored on-chain, only emitted in the event.
+     * Nodes listen to this event to get full details of the report.
+     */
+    event PayerReportSubmitted(
+        address indexed originatorNode,
+        uint256 indexed reportIndex,
+        bytes32 indexed reportMerkleRoot,
+        uint256 startingSequenceID,
+        uint256 endingSequenceID,
+        uint256 lastMessageTimestamp,
+        uint256 reportTimestamp,
+        uint16 leafCount
+    );
+
+    /**
+     * @dev Emitted when a node attests to the correctness of a report.
+     */
+    event PayerReportAttested(
+        address indexed originatorNode, uint256 indexed reportIndex, bytes32 indexed reportMerkleRoot
+    );
+
+    /**
+     * @dev Emitted when a usage report is confirmed.
+     */
+    event PayerReportConfirmed(
+        address indexed originatorNode, uint256 indexed reportIndex, bytes32 indexed reportMerkleRoot
+    );
+
+    /**
+     * @dev Emitted when a batch of usage is settled.
+     */
+    event PayerReportPartiallySettled(
+        address indexed originatorNode,
+        uint256 indexed reportIndex,
+        bytes32 indexed reportMerkleRoot,
+        address[] payers,
+        uint256[] amounts,
+        uint16 offset
+    );
+
+    /**
+     * @dev Emitted when a usage report is fully settled.
+     */
+    event PayerReportFullySettled(
+        address indexed originatorNode, uint256 indexed reportIndex, bytes32 indexed reportMerkleRoot
+    );
+
+    /* ============ Usage Report Logic ============ */
+
+    /**
+     * @notice Submits a usage report for a node covering messages from startingSequenceID to endingSequenceID.
+     * @param  payerReport A struct containing the usage report details.
+     *
+     * Emits a PayerReportSubmitted event.
+     */
+    function submitPayerReport(PayerReport calldata payerReport) external;
+
+    /**
+     * @notice Allows nodes to attest to the correctness of a submitted usage report.
+     * @param  originatorNode The node that submitted the report.
+     * @param  reportIndex    The index of the report.
+     *
+     * Emits a PayerReportAttested event.
+     */
+    function attestPayerReport(address originatorNode, uint256 reportIndex) external;
+
+    /**
+     * @notice Returns a list of all payer reports for a given originator node.
+     * @param  originatorNode      The address of the originator node.
+     * @return startingSequenceIDs The array of starting sequence IDs for each report.
+     * @return reportsMerkleRoots  The array of Merkle roots for each report.
+     */
+    function listPayerReports(address originatorNode)
+        external
+        view
+        returns (uint256[] memory startingSequenceIDs, bytes32[] memory reportsMerkleRoots);
+
+    /**
+     * @notice Returns summary info about a specific usage report.
+     * @param  originatorNode The node that submitted the report.
+     * @param  reportIndex    The index of the report.
+     * @return payerReport    A PayerReport struct with the report details.
+     */
+    function getPayerReport(address originatorNode, uint256 reportIndex)
+        external
+        view
+        returns (PayerReport memory payerReport);
+
+    /**
+     * @notice Settles a contiguous batch of usage data from a confirmed report.
+     * Verifies an aggregated Merkle proof that the provided (payer, amount) batch is included in the report's committed
+     * Merkle root, then calls the settleUsage function in the Payer contract.
+     *
+     * @param originatorNode The node that submitted the report.
+     * @param reportIndex    The index of the report.
+     * @param offset         The index of the batch in the report's data (managed off-chain).
+     * @param payers         A contiguous array of payer addresses.
+     * @param amounts        A contiguous array of usage amounts corresponding to each payer.
+     * @param proof          An aggregated Merkle proof containing branch hashes.
+     *
+     * Emits a UsageSettled event.
+     */
+    function settleUsageBatch(
+        address originatorNode,
+        uint256 reportIndex,
+        uint16 offset,
+        address[] calldata payers,
+        uint256[] calldata amounts,
+        bytes32[] calldata proof
+    ) external;
+
+    /**
+     * @notice Sets the maximum batch size for usage settlements.
+     * @param  maxBatchSize The new maximum batch size.
+     */
+    function setMaxBatchSize(uint256 maxBatchSize) external;
+
+    /**
+     * @notice Returns the current maximum batch size.
+     * @return batchSize The current maximum batch size.
+     */
+    function getMaxBatchSize() external view returns (uint256 batchSize);
+}

From 1b0746af1218e06b14d6fac9d349375c70591d6e Mon Sep 17 00:00:00 2001
From: Borja Aranda <borja@ephemerahq.com>
Date: Wed, 19 Mar 2025 11:59:25 +0100
Subject: [PATCH 2/7] apply reviewed changes

---
 .solhint.json                          |    1 +
 src/PayerRegistry.sol                  |  303 ++++---
 src/interfaces/IPayerRegistry.sol      |   16 +-
 src/interfaces/IPayerReportManager.sol |   30 +-
 yarn.lock                              | 1050 ++++++++++++++++++++++++
 5 files changed, 1211 insertions(+), 189 deletions(-)
 create mode 100644 yarn.lock

diff --git a/.solhint.json b/.solhint.json
index aefec5f..a5397f1 100644
--- a/.solhint.json
+++ b/.solhint.json
@@ -17,6 +17,7 @@
         ],
         "comprehensive-interface": "off",
         "const-name-snakecase": "off",
+        "custom-errors": "off",
         "func-name-mixedcase": "off",
         "func-visibility": [
             "warn",
diff --git a/src/PayerRegistry.sol b/src/PayerRegistry.sol
index 0b99375..e3cc539 100644
--- a/src/PayerRegistry.sol
+++ b/src/PayerRegistry.sol
@@ -36,15 +36,15 @@ contract PayerRegistry is
     /* ============ Constants ============ */
 
     bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
-    string internal constant USDC_SYMBOL = "USDC";
-    uint8 private constant PAYER_OPERATOR_ID = 1;
-    uint64 private constant DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS = 10_000_000;
-    uint64 private constant DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS = 10_000_000;
-    uint64 private constant DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS = 50_000_000;
-    uint32 private constant DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD = 6 hours;
-    uint32 private constant ABSOLUTE_MINIMUM_TRANSFER_FEES_PERIOD = 1 hours;
-    uint32 private constant DEFAULT_WITHDRAWAL_LOCK_PERIOD = 3 days;
-    uint32 private constant ABSOLUTE_MINIMUM_WITHDRAWAL_LOCK_PERIOD = 1 days;
+    string internal constant _USDC_SYMBOL = "USDC";
+    uint8 private constant _PAYER_OPERATOR_ID = 1;
+    uint64 private constant _DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS = 10_000_000;
+    uint64 private constant _DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS = 10_000_000;
+    uint64 private constant _DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS = 50_000_000;
+    uint32 private constant _DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD = 6 hours;
+    uint32 private constant _ABSOLUTE_MINIMUM_TRANSFER_FEES_PERIOD = 1 hours;
+    uint32 private constant _DEFAULT_WITHDRAWAL_LOCK_PERIOD = 3 days;
+    uint32 private constant _ABSOLUTE_MINIMUM_WITHDRAWAL_LOCK_PERIOD = 1 days;
 
     /* ============ UUPS Storage ============ */
 
@@ -61,13 +61,11 @@ contract PayerRegistry is
         uint64 collectedFees;
         uint32 withdrawalLockPeriod;
         uint32 transferFeesPeriod;
-
         // Contract addresses (fits in 3 slots)
         address usdcToken;
         address feeDistributor;
         address nodeRegistry;
         address payerReportManager;
-
         // Mappings and dynamic sets (each starts at its own storage slot)
         mapping(address => Payer) payers;
         mapping(address => Withdrawal) withdrawals;
@@ -77,26 +75,18 @@ contract PayerRegistry is
     }
 
     // keccak256(abi.encode(uint256(keccak256("xmtp.storage.Payer")) - 1)) & ~bytes32(uint256(0xff))
-    bytes32 internal constant PAYER_STORAGE_LOCATION =
+    bytes32 internal constant _PAYER_STORAGE_LOCATION =
         0xd0335f337c570f3417b0f0d20340c88da711d60e810b5e9b3ecabe9ccfcdce5a;
 
     function _getPayerStorage() internal pure returns (PayerStorage storage $) {
         // slither-disable-next-line assembly
         assembly {
-            $.slot := PAYER_STORAGE_LOCATION
+            $.slot := _PAYER_STORAGE_LOCATION
         }
     }
 
     /* ============ Modifiers ============ */
 
-    /**
-     * @dev Modifier to check if caller is an active node operator.
-     */
-    modifier onlyNodeOperator(uint256 nodeId) {
-        require(_getIsActiveNodeOperator(nodeId), UnauthorizedNodeOperator());
-        _;
-    }
-
     /**
      * @dev Modifier to check if caller is the payer report contract.
      */
@@ -133,11 +123,11 @@ contract PayerRegistry is
 
         PayerStorage storage $ = _getPayerStorage();
 
-        $.minimumRegistrationAmountMicroDollars = DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS;
-        $.minimumDepositAmountMicroDollars = DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS;
-        $.withdrawalLockPeriod = DEFAULT_WITHDRAWAL_LOCK_PERIOD;
-        $.maxTolerableDebtAmountMicroDollars = DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS;
-        $.transferFeesPeriod = DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD;
+        $.minimumRegistrationAmountMicroDollars = _DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS;
+        $.minimumDepositAmountMicroDollars = _DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS;
+        $.withdrawalLockPeriod = _DEFAULT_WITHDRAWAL_LOCK_PERIOD;
+        $.maxTolerableDebtAmountMicroDollars = _DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS;
+        $.transferFeesPeriod = _DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD;
 
         _setUsdcToken(usdcToken);
         _setNodeRegistry(nodesContract);
@@ -195,7 +185,7 @@ contract PayerRegistry is
     /**
      * @inheritdoc IPayerRegistry
      */
-    function deactivatePayer(uint256 nodeId, address payer) external whenNotPaused onlyNodeOperator(nodeId) {
+    function deactivatePayer(uint256 nodeId, address payer) external whenNotPaused {
         _revertIfPayerDoesNotExist(payer);
 
         _deactivatePayer(nodeId, payer);
@@ -211,9 +201,7 @@ contract PayerRegistry is
 
         require($.withdrawals[payer].withdrawableTimestamp == 0, PayerInWithdrawal());
 
-        Payer memory _storedPayer = $.payers[payer];
-
-        if (_storedPayer.balance > 0 || _storedPayer.debtAmount > 0) {
+        if ($.payers[payer].balance > 0 || $.payers[payer].debtAmount > 0) {
             revert PayerHasBalanceOrDebt();
         }
 
@@ -235,10 +223,8 @@ contract PayerRegistry is
 
         PayerStorage storage $ = _getPayerStorage();
 
-        Payer memory _storedPayer = $.payers[msg.sender];
-
-        require(_storedPayer.debtAmount == 0, PayerHasDebt());
-        require(_storedPayer.balance >= amount, InsufficientBalance());
+        require($.payers[msg.sender].debtAmount == 0, PayerHasDebt());
+        require($.payers[msg.sender].balance >= amount, InsufficientBalance());
 
         // Balance to be withdrawn is deducted from the payer's balance,
         // it can't be used to settle payments.
@@ -247,10 +233,7 @@ contract PayerRegistry is
 
         uint64 withdrawableTimestamp = uint64(block.timestamp) + $.withdrawalLockPeriod;
 
-        $.withdrawals[msg.sender] = Withdrawal({
-            withdrawableTimestamp: withdrawableTimestamp,
-            amount: amount
-        });
+        $.withdrawals[msg.sender] = Withdrawal({ withdrawableTimestamp: withdrawableTimestamp, amount: amount });
 
         emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
 
@@ -265,16 +248,16 @@ contract PayerRegistry is
 
         PayerStorage storage $ = _getPayerStorage();
 
-        Withdrawal memory _withdrawal = $.withdrawals[msg.sender];
+        Withdrawal memory withdrawal = $.withdrawals[msg.sender];
 
         delete $.withdrawals[msg.sender];
 
-        $.payers[msg.sender].balance += _withdrawal.amount;
-        _increaseTotalDeposited(_withdrawal.amount);
+        $.payers[msg.sender].balance += withdrawal.amount;
+        _increaseTotalDeposited(withdrawal.amount);
 
         emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
 
-        emit WithdrawalCancelled(msg.sender, _withdrawal.withdrawableTimestamp);
+        emit WithdrawalCancelled(msg.sender, withdrawal.withdrawableTimestamp);
     }
 
     /**
@@ -285,25 +268,26 @@ contract PayerRegistry is
 
         PayerStorage storage $ = _getPayerStorage();
 
-        Withdrawal memory _withdrawal = $.withdrawals[msg.sender];
+        Withdrawal memory withdrawal = $.withdrawals[msg.sender];
 
-        require(block.timestamp >= _withdrawal.withdrawableTimestamp, WithdrawalPeriodNotElapsed());
+        // slither-disable-next-line timestamp
+        require(block.timestamp >= withdrawal.withdrawableTimestamp, WithdrawalPeriodNotElapsed());
 
         delete $.withdrawals[msg.sender];
 
-        uint64 _finalWithdrawalAmount = _withdrawal.amount;
+        uint64 finalWithdrawalAmount = withdrawal.amount;
 
         if ($.payers[msg.sender].debtAmount > 0) {
-            _finalWithdrawalAmount = _settleDebts(msg.sender, _withdrawal.amount, true);
+            finalWithdrawalAmount = _settleDebts(msg.sender, withdrawal.amount, true);
         }
 
-        if (_finalWithdrawalAmount > 0) {
-            IERC20($.usdcToken).safeTransfer(msg.sender, _finalWithdrawalAmount);
+        if (finalWithdrawalAmount > 0) {
+            IERC20($.usdcToken).safeTransfer(msg.sender, finalWithdrawalAmount);
         }
 
         emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
 
-        emit WithdrawalFinalized(msg.sender, _withdrawal.withdrawableTimestamp, _finalWithdrawalAmount);
+        emit WithdrawalFinalized(msg.sender, withdrawal.withdrawableTimestamp, finalWithdrawalAmount);
     }
 
     /**
@@ -320,18 +304,17 @@ contract PayerRegistry is
     /**
      * @inheritdoc IPayerRegistry
      */
-    function settleUsage(uint256 originatorNode, address[] calldata payerList, uint64[] calldata usageAmountsList)
-        external
-        whenNotPaused
-        nonReentrant
-        onlyPayerReport
-    {
+    function settleUsage(
+        uint256 originatorNode,
+        address[] calldata payerList,
+        uint64[] calldata usageAmountsList
+    ) external whenNotPaused nonReentrant onlyPayerReport {
         require(payerList.length == usageAmountsList.length, InvalidPayerListLength());
 
         PayerStorage storage $ = _getPayerStorage();
 
-        uint64 _settledFees = 0;
-        uint64 _pendingFees = $.pendingFees;
+        uint64 settledFees = 0;
+        uint64 pendingFees = $.pendingFees;
 
         for (uint256 i = 0; i < payerList.length; i++) {
             address payer = payerList[i];
@@ -341,41 +324,41 @@ contract PayerRegistry is
             // Payers in payerList should always exist and be active.
             if (!_payerExists(payer) || !_payerIsActive(payer)) continue;
 
-            Payer memory _storedPayer = $.payers[payer];
+            Payer memory storedPayer = $.payers[payer];
 
-            if (_storedPayer.balance < usage) {
-                uint64 _debt = usage - _storedPayer.balance;
+            if (storedPayer.balance < usage) {
+                uint64 debt = usage - storedPayer.balance;
 
-                _settledFees += _storedPayer.balance;
-                _pendingFees += _storedPayer.balance;
+                settledFees += storedPayer.balance;
+                pendingFees += storedPayer.balance;
 
-                _storedPayer.balance = 0;
-                _storedPayer.debtAmount = _debt;
-                $.payers[payer] = _storedPayer;
+                storedPayer.balance = 0;
+                storedPayer.debtAmount = debt;
+                $.payers[payer] = storedPayer;
 
                 _addDebtor(payer);
-                _increaseTotalDebt(_debt);
+                _increaseTotalDebt(debt);
 
-                if (_debt > $.maxTolerableDebtAmountMicroDollars) _deactivatePayer(PAYER_OPERATOR_ID, payer);
+                if (debt > $.maxTolerableDebtAmountMicroDollars) _deactivatePayer(_PAYER_OPERATOR_ID, payer);
 
-                emit PayerBalanceUpdated(payer, _storedPayer.balance, _storedPayer.debtAmount);
+                emit PayerBalanceUpdated(payer, storedPayer.balance, storedPayer.debtAmount);
 
                 continue;
             }
 
-            _settledFees += usage;
-            _pendingFees += usage;
+            settledFees += usage;
+            pendingFees += usage;
 
-            _storedPayer.balance -= usage;
+            storedPayer.balance -= usage;
 
-            $.payers[payer] = _storedPayer;
+            $.payers[payer] = storedPayer;
 
-            emit PayerBalanceUpdated(payer, _storedPayer.balance, _storedPayer.debtAmount);
+            emit PayerBalanceUpdated(payer, storedPayer.balance, storedPayer.debtAmount);
         }
 
-        $.pendingFees = _pendingFees;
+        $.pendingFees = pendingFees;
 
-        emit UsageSettled(originatorNode, uint64(block.timestamp), _settledFees);
+        emit UsageSettled(originatorNode, uint64(block.timestamp), settledFees);
     }
 
     /**
@@ -390,17 +373,17 @@ contract PayerRegistry is
         // slither-disable-next-line timestamp
         require(block.timestamp - $.lastFeeTransferTimestamp >= $.transferFeesPeriod, InsufficientTimePassed());
 
-        uint64 _pendingFeesAmount = $.pendingFees;
+        uint64 pendingFeesAmount = $.pendingFees;
 
-        require(_pendingFeesAmount > 0, InsufficientAmount());
+        require(pendingFeesAmount > 0, InsufficientAmount());
 
-        IERC20($.usdcToken).safeTransfer($.feeDistributor, _pendingFeesAmount);
+        IERC20($.usdcToken).safeTransfer($.feeDistributor, pendingFeesAmount);
 
         $.lastFeeTransferTimestamp = uint64(block.timestamp);
-        $.collectedFees += _pendingFeesAmount;
+        $.collectedFees += pendingFeesAmount;
         $.pendingFees = 0;
 
-        emit FeesTransferred(uint64(block.timestamp), _pendingFeesAmount);
+        emit FeesTransferred(uint64(block.timestamp), pendingFeesAmount);
     }
 
     /* ========== Administrative Functions ========== */
@@ -437,7 +420,7 @@ contract PayerRegistry is
      * @inheritdoc IPayerRegistry
      */
     function setMinimumDeposit(uint64 newMinimumDeposit) external onlyRole(ADMIN_ROLE) {
-        require(newMinimumDeposit > DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS, InvalidMinimumDeposit());
+        require(newMinimumDeposit > _DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS, InvalidMinimumDeposit());
 
         PayerStorage storage $ = _getPayerStorage();
 
@@ -452,30 +435,30 @@ contract PayerRegistry is
      */
     function setMinimumRegistrationAmount(uint64 newMinimumRegistrationAmount) external onlyRole(ADMIN_ROLE) {
         require(
-            newMinimumRegistrationAmount > DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS,
+            newMinimumRegistrationAmount > _DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS,
             InvalidMinimumRegistrationAmount()
         );
 
         PayerStorage storage $ = _getPayerStorage();
 
-        uint64 _oldMinimumRegistrationAmount = $.minimumRegistrationAmountMicroDollars;
+        uint64 oldMinimumRegistrationAmount = $.minimumRegistrationAmountMicroDollars;
         $.minimumRegistrationAmountMicroDollars = newMinimumRegistrationAmount;
 
-        emit MinimumRegistrationAmountSet(_oldMinimumRegistrationAmount, newMinimumRegistrationAmount);
+        emit MinimumRegistrationAmountSet(oldMinimumRegistrationAmount, newMinimumRegistrationAmount);
     }
 
     /**
      * @inheritdoc IPayerRegistry
      */
     function setWithdrawalLockPeriod(uint32 newWithdrawalLockPeriod) external onlyRole(ADMIN_ROLE) {
-        require(newWithdrawalLockPeriod >= ABSOLUTE_MINIMUM_WITHDRAWAL_LOCK_PERIOD, InvalidWithdrawalLockPeriod());
+        require(newWithdrawalLockPeriod >= _ABSOLUTE_MINIMUM_WITHDRAWAL_LOCK_PERIOD, InvalidWithdrawalLockPeriod());
 
         PayerStorage storage $ = _getPayerStorage();
 
-        uint32 _oldWithdrawalLockPeriod = $.withdrawalLockPeriod;
+        uint32 oldWithdrawalLockPeriod = $.withdrawalLockPeriod;
         $.withdrawalLockPeriod = newWithdrawalLockPeriod;
 
-        emit WithdrawalLockPeriodSet(_oldWithdrawalLockPeriod, newWithdrawalLockPeriod);
+        emit WithdrawalLockPeriodSet(oldWithdrawalLockPeriod, newWithdrawalLockPeriod);
     }
 
     /**
@@ -486,24 +469,24 @@ contract PayerRegistry is
 
         PayerStorage storage $ = _getPayerStorage();
 
-        uint64 _oldMaxTolerableDebtAmount = $.maxTolerableDebtAmountMicroDollars;
+        uint64 oldMaxTolerableDebtAmount = $.maxTolerableDebtAmountMicroDollars;
         $.maxTolerableDebtAmountMicroDollars = newMaxTolerableDebtAmountMicroDollars;
 
-        emit MaxTolerableDebtAmountSet(_oldMaxTolerableDebtAmount, newMaxTolerableDebtAmountMicroDollars);
+        emit MaxTolerableDebtAmountSet(oldMaxTolerableDebtAmount, newMaxTolerableDebtAmountMicroDollars);
     }
 
     /**
      * @inheritdoc IPayerRegistry
      */
     function setTransferFeesPeriod(uint32 newTransferFeesPeriod) external onlyRole(ADMIN_ROLE) {
-        require(newTransferFeesPeriod >= ABSOLUTE_MINIMUM_TRANSFER_FEES_PERIOD, InvalidTransferFeesPeriod());
+        require(newTransferFeesPeriod >= _ABSOLUTE_MINIMUM_TRANSFER_FEES_PERIOD, InvalidTransferFeesPeriod());
 
         PayerStorage storage $ = _getPayerStorage();
 
-        uint32 _oldTransferFeesPeriod = $.transferFeesPeriod;
+        uint32 oldTransferFeesPeriod = $.transferFeesPeriod;
         $.transferFeesPeriod = newTransferFeesPeriod;
 
-        emit TransferFeesPeriodSet(_oldTransferFeesPeriod, newTransferFeesPeriod);
+        emit TransferFeesPeriodSet(oldTransferFeesPeriod, newTransferFeesPeriod);
     }
 
     /**
@@ -534,21 +517,17 @@ contract PayerRegistry is
     /**
      * @inheritdoc IPayerRegistry
      */
-    function getActivePayers(uint32 offset, uint32 limit)
-        external
-        view
-        returns (Payer[] memory payers, bool hasMore)
-    {
+    function getActivePayers(uint32 offset, uint32 limit) external view returns (Payer[] memory payers, bool hasMore) {
         PayerStorage storage $ = _getPayerStorage();
 
-        (address[] memory _payerAddresses, bool _hasMore) = _getPaginatedAddresses($.activePayers, offset, limit);
+        (address[] memory payerAddresses, bool hasMore_) = _getPaginatedAddresses($.activePayers, offset, limit);
 
-        payers = new Payer[](_payerAddresses.length);
-        for (uint256 i = 0; i < _payerAddresses.length; i++) {
-            payers[i] = $.payers[_payerAddresses[i]];
+        payers = new Payer[](payerAddresses.length);
+        for (uint256 i = 0; i < payerAddresses.length; i++) {
+            payers[i] = $.payers[payerAddresses[i]];
         }
 
-        return (payers, _hasMore);
+        return (payers, hasMore_);
     }
 
     /**
@@ -572,21 +551,17 @@ contract PayerRegistry is
     /**
      * @inheritdoc IPayerRegistry
      */
-    function getPayersInDebt(uint32 offset, uint32 limit)
-        external
-        view
-        returns (Payer[] memory payers, bool hasMore)
-    {
+    function getPayersInDebt(uint32 offset, uint32 limit) external view returns (Payer[] memory payers, bool hasMore) {
         PayerStorage storage $ = _getPayerStorage();
 
-        (address[] memory _payerAddresses, bool _hasMore) = _getPaginatedAddresses($.debtPayers, offset, limit);
+        (address[] memory payerAddresses, bool hasMore_) = _getPaginatedAddresses($.debtPayers, offset, limit);
 
-        payers = new Payer[](_payerAddresses.length);
-        for (uint256 i = 0; i < _payerAddresses.length; i++) {
-            payers[i] = $.payers[_payerAddresses[i]];
+        payers = new Payer[](payerAddresses.length);
+        for (uint256 i = 0; i < payerAddresses.length; i++) {
+            payers[i] = $.payers[payerAddresses[i]];
         }
 
-        return (payers, _hasMore);
+        return (payers, hasMore_);
     }
 
     /**
@@ -687,11 +662,11 @@ contract PayerRegistry is
     /* ============ Internal ============ */
 
     /**
-    * @notice Validates and processes a deposit or donation
-    * @param from The address funds are coming from
-    * @param to The payer account receiving the deposit
-    * @param amount The amount to deposit
-    */
+     * @notice Validates and processes a deposit or donation
+     * @param from The address funds are coming from
+     * @param to The payer account receiving the deposit
+     * @param amount The amount to deposit
+     */
     function _validateAndProcessDeposit(address from, address to, uint64 amount) internal {
         PayerStorage storage $ = _getPayerStorage();
 
@@ -712,12 +687,12 @@ contract PayerRegistry is
      * @return leftoverAmount Amount remaining after debt settlement (if any).
      */
     function _updatePayerBalance(address payerAddress, uint64 amount) internal returns (uint64 leftoverAmount) {
-        Payer storage _payer = _getPayerStorage().payers[payerAddress];
+        Payer storage payer = _getPayerStorage().payers[payerAddress];
 
-        if (_payer.debtAmount > 0) {
+        if (payer.debtAmount > 0) {
             return _settleDebts(payerAddress, amount, false);
         } else {
-            _payer.balance += amount;
+            payer.balance += amount;
             _increaseTotalDeposited(amount);
             return amount;
         }
@@ -730,36 +705,40 @@ contract PayerRegistry is
      * @param  isWithdrawal Whether the debt settlement happens during a withdrawal.
      * @return amountAfterSettlement The amount remaining after debt settlement.
      */
-    function _settleDebts(address payer, uint64 amount, bool isWithdrawal) internal returns (uint64 amountAfterSettlement) {
+    function _settleDebts(
+        address payer,
+        uint64 amount,
+        bool isWithdrawal
+    ) internal returns (uint64 amountAfterSettlement) {
         PayerStorage storage $ = _getPayerStorage();
 
-        Payer memory _storedPayer = $.payers[payer];
+        Payer memory storedPayer = $.payers[payer];
 
-        if (_storedPayer.debtAmount < amount) {
-            uint64 _debtToRemove = _storedPayer.debtAmount;
-            amount -= _debtToRemove;
+        if (storedPayer.debtAmount < amount) {
+            uint64 debtToRemove = storedPayer.debtAmount;
+            amount -= debtToRemove;
 
             // For regular deposits, add remaining amount to balance.
             // In withdrawals, that amount was moved to the withdrawal balance.
             if (!isWithdrawal) {
-                _storedPayer.balance += amount;
+                storedPayer.balance += amount;
                 _increaseTotalDeposited(amount);
             }
 
             _removeDebtor(payer);
             _increaseTotalDeposited(amount);
-            _decreaseTotalDebt(_debtToRemove);
+            _decreaseTotalDebt(debtToRemove);
 
             amountAfterSettlement = amount;
         } else {
-            _storedPayer.debtAmount -= amount;
+            storedPayer.debtAmount -= amount;
 
             _decreaseTotalDebt(amount);
 
             amountAfterSettlement = 0;
         }
 
-        $.payers[payer] = _storedPayer;
+        $.payers[payer] = storedPayer;
 
         return amountAfterSettlement;
     }
@@ -802,6 +781,15 @@ contract PayerRegistry is
         require(_payerExists(payer), PayerDoesNotExist());
     }
 
+    function _revertIfNotNodeOperator(uint256 nodeId) internal view {
+        INodes nodes = INodes(_getPayerStorage().nodeRegistry);
+
+        require(msg.sender == nodes.ownerOf(nodeId), Unauthorized());
+
+        // TODO: Change for a better filter.
+        return nodes.getReplicationNodeIsActive(nodeId);
+    }
+
     /**
      * @notice Checks if a withdrawal exists.
      * @param  payer The address of the payer to check.
@@ -843,20 +831,6 @@ contract PayerRegistry is
         }
     }
 
-    /**
-     * @notice Checks if a given address is an active node operator.
-     * @param  nodeId The nodeID of the operator to check.
-     * @return isActiveNodeOperator True if the address is an active node operator, false otherwise.
-     */
-    function _getIsActiveNodeOperator(uint256 nodeId) internal view returns (bool isActiveNodeOperator) {
-        INodes nodes = INodes(_getPayerStorage().nodeRegistry);
-
-        require(msg.sender == nodes.ownerOf(nodeId), Unauthorized());
-
-        // TODO: Change for a better filter.
-        return nodes.getReplicationNodeIsActive(nodeId);
-    }
-
     /**
      * @notice Sets the FeeDistributor contract.
      * @param  newFeeDistributor The address of the new FeeDistributor contract.
@@ -864,7 +838,9 @@ contract PayerRegistry is
     function _setFeeDistributor(address newFeeDistributor) internal {
         PayerStorage storage $ = _getPayerStorage();
 
-        try IFeeDistributor(newFeeDistributor).supportsInterface(type(IFeeDistributor).interfaceId) returns (bool supported) {
+        try IFeeDistributor(newFeeDistributor).supportsInterface(type(IFeeDistributor).interfaceId) returns (
+            bool supported
+        ) {
             require(supported, InvalidFeeDistributor());
         } catch {
             revert InvalidFeeDistributor();
@@ -882,7 +858,9 @@ contract PayerRegistry is
     function _setPayerReportManager(address newPayerReportManager) internal {
         PayerStorage storage $ = _getPayerStorage();
 
-        try IPayerReportManager(newPayerReportManager).supportsInterface(type(IPayerReportManager).interfaceId) returns (bool supported) {
+        try
+            IPayerReportManager(newPayerReportManager).supportsInterface(type(IPayerReportManager).interfaceId)
+        returns (bool supported) {
             require(supported, InvalidPayerReportManager());
         } catch {
             revert InvalidPayerReportManager();
@@ -919,7 +897,7 @@ contract PayerRegistry is
         PayerStorage storage $ = _getPayerStorage();
 
         try IERC20Metadata(newUsdcToken).symbol() returns (string memory symbol) {
-            require(keccak256(bytes(symbol)) == keccak256(bytes(USDC_SYMBOL)), InvalidUsdcTokenContract());
+            require(keccak256(bytes(symbol)) == keccak256(bytes(_USDC_SYMBOL)), InvalidUsdcTokenContract());
         } catch {
             revert InvalidUsdcTokenContract();
         }
@@ -973,26 +951,26 @@ contract PayerRegistry is
      * @return addresses Array of addresses from the set.
      * @return hasMore Whether there are more items after this page.
      */
-    function _getPaginatedAddresses(EnumerableSet.AddressSet storage addressSet, uint256 offset, uint256 limit)
-        internal
-        view
-        returns (address[] memory addresses, bool hasMore)
-    {
-        uint256 _totalCount = addressSet.length();
+    function _getPaginatedAddresses(
+        EnumerableSet.AddressSet storage addressSet,
+        uint256 offset,
+        uint256 limit
+    ) internal view returns (address[] memory addresses, bool hasMore) {
+        uint256 totalCount = addressSet.length();
 
-        if (offset >= _totalCount) revert OutOfBounds();
+        if (offset >= totalCount) revert OutOfBounds();
 
-        uint256 _count = _totalCount - offset;
-        if (_count > limit) {
-            _count = limit;
+        uint256 count = totalCount - offset;
+        if (count > limit) {
+            count = limit;
             hasMore = true;
         } else {
             hasMore = false;
         }
 
-        addresses = new address[](_count);
+        addresses = new address[](count);
 
-        for (uint256 i = 0; i < _count; i++) {
+        for (uint256 i = 0; i < count; i++) {
             addresses[i] = addressSet.at(offset + i);
         }
 
@@ -1015,12 +993,9 @@ contract PayerRegistry is
     /**
      * @dev Override to support IPayerRegistry, IERC165 and AccessControlUpgradeable.
      */
-    function supportsInterface(bytes4 interfaceId)
-        public
-        view
-        override(IERC165, AccessControlUpgradeable)
-        returns (bool supported)
-    {
+    function supportsInterface(
+        bytes4 interfaceId
+    ) public view override(IERC165, AccessControlUpgradeable) returns (bool supported) {
         return interfaceId == type(IPayerRegistry).interfaceId || super.supportsInterface(interfaceId);
     }
 }
diff --git a/src/interfaces/IPayerRegistry.sol b/src/interfaces/IPayerRegistry.sol
index ef530b8..d08553d 100644
--- a/src/interfaces/IPayerRegistry.sol
+++ b/src/interfaces/IPayerRegistry.sol
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.28;
 
-import {IERC165} from "../../lib/oz/contracts/utils/introspection/IERC165.sol";
+import { IERC165 } from "../../lib/oz/contracts/utils/introspection/IERC165.sol";
 
 /**
  * @title  IPayerRegistryEvents
@@ -63,9 +63,7 @@ interface IPayerRegistryEvents {
     event WithdrawalLockPeriodSet(uint32 oldWithdrawalLockPeriod, uint32 newWithdrawalLockPeriod);
 
     /// @dev Emitted when a payer initiates a withdrawal request.
-    event WithdrawalRequested(
-        address indexed payer, uint64 indexed withdrawableTimestamp, uint64 amount
-    );
+    event WithdrawalRequested(address indexed payer, uint64 indexed withdrawableTimestamp, uint64 amount);
 }
 
 /**
@@ -433,10 +431,7 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      * @return payers The payer information.
      * @return hasMore True if there are more payers to retrieve.
      */
-    function getActivePayers(uint32 offset, uint32 limit)
-        external
-        view
-        returns (Payer[] memory payers, bool hasMore);
+    function getActivePayers(uint32 offset, uint32 limit) external view returns (Payer[] memory payers, bool hasMore);
 
     /**
      * @notice Checks if a given address is an active payer.
@@ -452,10 +447,7 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      * @return payers      Array of payer addresses with debt.
      * @return hasMore     True if there are more payers to retrieve.
      */
-    function getPayersInDebt(uint32 offset, uint32 limit)
-        external
-        view
-        returns (Payer[] memory payers, bool hasMore);
+    function getPayersInDebt(uint32 offset, uint32 limit) external view returns (Payer[] memory payers, bool hasMore);
 
     /**
      * @notice Returns the total number of registered payers.
diff --git a/src/interfaces/IPayerReportManager.sol b/src/interfaces/IPayerReportManager.sol
index 9dd0c75..9857dc4 100644
--- a/src/interfaces/IPayerReportManager.sol
+++ b/src/interfaces/IPayerReportManager.sol
@@ -1,14 +1,13 @@
 // SPDX-License-Identifier: MIT
 pragma solidity 0.8.28;
 
-import {IERC165} from "../../lib/oz/contracts/utils/introspection/IERC165.sol";
+import { IERC165 } from "../../lib/oz/contracts/utils/introspection/IERC165.sol";
 
 /**
  * @title  IPayerReportManager
  * @notice Interface for the PayerReportManager contract handling usage reports and batch settlements.
  */
 interface IPayerReportManager is IERC165 {
-
     /* ============ Structs ============ */
 
     /**
@@ -54,14 +53,18 @@ interface IPayerReportManager is IERC165 {
      * @dev Emitted when a node attests to the correctness of a report.
      */
     event PayerReportAttested(
-        address indexed originatorNode, uint256 indexed reportIndex, bytes32 indexed reportMerkleRoot
+        address indexed originatorNode,
+        uint256 indexed reportIndex,
+        bytes32 indexed reportMerkleRoot
     );
 
     /**
      * @dev Emitted when a usage report is confirmed.
      */
     event PayerReportConfirmed(
-        address indexed originatorNode, uint256 indexed reportIndex, bytes32 indexed reportMerkleRoot
+        address indexed originatorNode,
+        uint256 indexed reportIndex,
+        bytes32 indexed reportMerkleRoot
     );
 
     /**
@@ -80,7 +83,9 @@ interface IPayerReportManager is IERC165 {
      * @dev Emitted when a usage report is fully settled.
      */
     event PayerReportFullySettled(
-        address indexed originatorNode, uint256 indexed reportIndex, bytes32 indexed reportMerkleRoot
+        address indexed originatorNode,
+        uint256 indexed reportIndex,
+        bytes32 indexed reportMerkleRoot
     );
 
     /* ============ Usage Report Logic ============ */
@@ -108,10 +113,9 @@ interface IPayerReportManager is IERC165 {
      * @return startingSequenceIDs The array of starting sequence IDs for each report.
      * @return reportsMerkleRoots  The array of Merkle roots for each report.
      */
-    function listPayerReports(address originatorNode)
-        external
-        view
-        returns (uint256[] memory startingSequenceIDs, bytes32[] memory reportsMerkleRoots);
+    function listPayerReports(
+        address originatorNode
+    ) external view returns (uint256[] memory startingSequenceIDs, bytes32[] memory reportsMerkleRoots);
 
     /**
      * @notice Returns summary info about a specific usage report.
@@ -119,10 +123,10 @@ interface IPayerReportManager is IERC165 {
      * @param  reportIndex    The index of the report.
      * @return payerReport    A PayerReport struct with the report details.
      */
-    function getPayerReport(address originatorNode, uint256 reportIndex)
-        external
-        view
-        returns (PayerReport memory payerReport);
+    function getPayerReport(
+        address originatorNode,
+        uint256 reportIndex
+    ) external view returns (PayerReport memory payerReport);
 
     /**
      * @notice Settles a contiguous batch of usage data from a confirmed report.
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..ce11db0
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,1050 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/code-frame@^7.0.0":
+  version "7.26.2"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
+  integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.25.9"
+    js-tokens "^4.0.0"
+    picocolors "^1.0.0"
+
+"@babel/helper-validator-identifier@^7.25.9":
+  version "7.25.9"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
+  integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+
+"@pnpm/config.env-replace@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c"
+  integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==
+
+"@pnpm/network.ca-file@^1.0.1":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983"
+  integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==
+  dependencies:
+    graceful-fs "4.2.10"
+
+"@pnpm/npm-conf@^2.1.0":
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz#bb375a571a0bd63ab0a23bece33033c683e9b6b0"
+  integrity sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==
+  dependencies:
+    "@pnpm/config.env-replace" "^1.1.0"
+    "@pnpm/network.ca-file" "^1.0.1"
+    config-chain "^1.1.11"
+
+"@prettier/sync@^0.3.0":
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/@prettier/sync/-/sync-0.3.0.tgz#91f2cfc23490a21586d1cf89c6f72157c000ca1e"
+  integrity sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw==
+
+"@sindresorhus/is@^5.2.0":
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668"
+  integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==
+
+"@solidity-parser/parser@^0.19.0":
+  version "0.19.0"
+  resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.19.0.tgz#37a8983b2725af9b14ff8c4a475fa0e98d773c3f"
+  integrity sha512-RV16k/qIxW/wWc+mLzV3ARyKUaMUTBy9tOLMzFhtNSKYeTAanQ3a5MudJKf/8arIFnA2L27SNjarQKmFg0w/jA==
+
+"@szmarczak/http-timer@^5.0.1":
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a"
+  integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==
+  dependencies:
+    defer-to-connect "^2.0.1"
+
+"@types/http-cache-semantics@^4.0.2":
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
+  integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==
+
+ajv@^6.12.6:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
+ajv@^8.0.1:
+  version "8.17.1"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
+  integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
+  dependencies:
+    fast-deep-equal "^3.1.3"
+    fast-uri "^3.0.1"
+    json-schema-traverse "^1.0.0"
+    require-from-string "^2.0.2"
+
+ansi-escapes@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7"
+  integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==
+  dependencies:
+    environment "^1.0.0"
+
+ansi-regex@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+  integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-regex@^6.0.1:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654"
+  integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+  dependencies:
+    color-convert "^2.0.1"
+
+ansi-styles@^6.0.0, ansi-styles@^6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
+  integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
+antlr4@^4.13.1-patch-1:
+  version "4.13.2"
+  resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.13.2.tgz#0d084ad0e32620482a9c3a0e2470c02e72e4006d"
+  integrity sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==
+
+argparse@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+ast-parents@^0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3"
+  integrity sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==
+
+astral-regex@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
+  integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+brace-expansion@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+  integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+  dependencies:
+    balanced-match "^1.0.0"
+
+braces@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+  integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+  dependencies:
+    fill-range "^7.1.1"
+
+cacheable-lookup@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27"
+  integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==
+
+cacheable-request@^10.2.8:
+  version "10.2.14"
+  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-10.2.14.tgz#eb915b665fda41b79652782df3f553449c406b9d"
+  integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==
+  dependencies:
+    "@types/http-cache-semantics" "^4.0.2"
+    get-stream "^6.0.1"
+    http-cache-semantics "^4.1.1"
+    keyv "^4.5.3"
+    mimic-response "^4.0.0"
+    normalize-url "^8.0.0"
+    responselike "^3.0.0"
+
+callsites@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+  integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+chalk@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+  dependencies:
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
+
+chalk@^5.4.1:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
+  integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
+
+cli-cursor@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38"
+  integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==
+  dependencies:
+    restore-cursor "^5.0.0"
+
+cli-truncate@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a"
+  integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==
+  dependencies:
+    slice-ansi "^5.0.0"
+    string-width "^7.0.0"
+
+color-convert@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+  integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+  dependencies:
+    color-name "~1.1.4"
+
+color-name@~1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colorette@^2.0.20:
+  version "2.0.20"
+  resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
+  integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
+
+commander@^10.0.0:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
+  integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
+
+commander@^13.1.0:
+  version "13.1.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46"
+  integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==
+
+config-chain@^1.1.11:
+  version "1.1.13"
+  resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
+  integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
+  dependencies:
+    ini "^1.3.4"
+    proto-list "~1.2.1"
+
+cosmiconfig@^8.0.0:
+  version "8.3.6"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
+  integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==
+  dependencies:
+    import-fresh "^3.3.0"
+    js-yaml "^4.1.0"
+    parse-json "^5.2.0"
+    path-type "^4.0.0"
+
+cross-spawn@^7.0.3:
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
+  integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+  dependencies:
+    path-key "^3.1.0"
+    shebang-command "^2.0.0"
+    which "^2.0.1"
+
+debug@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
+  integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+  dependencies:
+    ms "^2.1.3"
+
+decompress-response@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+  integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+  dependencies:
+    mimic-response "^3.1.0"
+
+deep-extend@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+  integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+defer-to-connect@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
+  integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
+
+emoji-regex@^10.3.0:
+  version "10.4.0"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4"
+  integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==
+
+emoji-regex@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+environment@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1"
+  integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==
+
+error-ex@^1.3.1:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
+  integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+  dependencies:
+    is-arrayish "^0.2.1"
+
+eventemitter3@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
+  integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
+
+execa@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
+  integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==
+  dependencies:
+    cross-spawn "^7.0.3"
+    get-stream "^8.0.1"
+    human-signals "^5.0.0"
+    is-stream "^3.0.0"
+    merge-stream "^2.0.0"
+    npm-run-path "^5.1.0"
+    onetime "^6.0.0"
+    signal-exit "^4.1.0"
+    strip-final-newline "^3.0.0"
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+  integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-diff@^1.1.2, fast-diff@^1.2.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
+  integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
+
+fast-json-stable-stringify@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+  integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-uri@^3.0.1:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748"
+  integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==
+
+fill-range@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+  integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+form-data-encoder@^2.1.2:
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5"
+  integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+  integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+get-east-asian-width@^1.0.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389"
+  integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==
+
+get-stream@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+  integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
+get-stream@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
+  integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==
+
+glob@^8.0.3:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
+  integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^5.0.1"
+    once "^1.3.0"
+
+got@^12.1.0:
+  version "12.6.1"
+  resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549"
+  integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==
+  dependencies:
+    "@sindresorhus/is" "^5.2.0"
+    "@szmarczak/http-timer" "^5.0.1"
+    cacheable-lookup "^7.0.0"
+    cacheable-request "^10.2.8"
+    decompress-response "^6.0.0"
+    form-data-encoder "^2.1.2"
+    get-stream "^6.0.1"
+    http2-wrapper "^2.1.10"
+    lowercase-keys "^3.0.0"
+    p-cancelable "^3.0.0"
+    responselike "^3.0.0"
+
+graceful-fs@4.2.10:
+  version "4.2.10"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
+  integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
+
+has-flag@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+  integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+http-cache-semantics@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
+  integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
+
+http2-wrapper@^2.1.10:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a"
+  integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==
+  dependencies:
+    quick-lru "^5.1.1"
+    resolve-alpn "^1.2.0"
+
+human-signals@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
+  integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
+
+ignore@^5.2.4:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
+  integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
+
+import-fresh@^3.3.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
+  integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ini@^1.3.4, ini@~1.3.0:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+  integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+is-arrayish@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+  integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+
+is-fullwidth-code-point@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-fullwidth-code-point@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
+  integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
+
+is-fullwidth-code-point@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704"
+  integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==
+  dependencies:
+    get-east-asian-width "^1.0.0"
+
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-stream@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac"
+  integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==
+
+isexe@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+  integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+js-tokens@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-yaml@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+  integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+  dependencies:
+    argparse "^2.0.1"
+
+json-buffer@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+  integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-parse-even-better-errors@^2.3.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+  integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json-schema-traverse@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+  integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-schema-traverse@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+  integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+keyv@^4.5.3:
+  version "4.5.4"
+  resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+  integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+  dependencies:
+    json-buffer "3.0.1"
+
+latest-version@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-7.0.0.tgz#843201591ea81a4d404932eeb61240fe04e9e5da"
+  integrity sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==
+  dependencies:
+    package-json "^8.1.0"
+
+lilconfig@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
+  integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
+
+lines-and-columns@^1.1.6:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
+  integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+
+lint-staged@^15.5.0:
+  version "15.5.0"
+  resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.5.0.tgz#fa6464cfb06e0faf5bb167f83186e952ff6e569e"
+  integrity sha512-WyCzSbfYGhK7cU+UuDDkzUiytbfbi0ZdPy2orwtM75P3WTtQBzmG40cCxIa8Ii2+XjfxzLH6Be46tUfWS85Xfg==
+  dependencies:
+    chalk "^5.4.1"
+    commander "^13.1.0"
+    debug "^4.4.0"
+    execa "^8.0.1"
+    lilconfig "^3.1.3"
+    listr2 "^8.2.5"
+    micromatch "^4.0.8"
+    pidtree "^0.6.0"
+    string-argv "^0.3.2"
+    yaml "^2.7.0"
+
+listr2@^8.2.5:
+  version "8.2.5"
+  resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.5.tgz#5c9db996e1afeb05db0448196d3d5f64fec2593d"
+  integrity sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==
+  dependencies:
+    cli-truncate "^4.0.0"
+    colorette "^2.0.20"
+    eventemitter3 "^5.0.1"
+    log-update "^6.1.0"
+    rfdc "^1.4.1"
+    wrap-ansi "^9.0.0"
+
+lodash.truncate@^4.4.2:
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
+  integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
+
+lodash@^4.17.21:
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+log-update@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4"
+  integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==
+  dependencies:
+    ansi-escapes "^7.0.0"
+    cli-cursor "^5.0.0"
+    slice-ansi "^7.1.0"
+    strip-ansi "^7.1.0"
+    wrap-ansi "^9.0.0"
+
+lowercase-keys@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2"
+  integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==
+
+merge-stream@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+  integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+micromatch@^4.0.8:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
+  integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
+  dependencies:
+    braces "^3.0.3"
+    picomatch "^2.3.1"
+
+mimic-fn@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
+  integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
+
+mimic-function@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076"
+  integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==
+
+mimic-response@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+  integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
+mimic-response@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f"
+  integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==
+
+minimatch@^5.0.1:
+  version "5.1.6"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
+  integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
+  dependencies:
+    brace-expansion "^2.0.1"
+
+minimist@^1.2.0:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+  integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+ms@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+normalize-url@^8.0.0:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.1.tgz#9b7d96af9836577c58f5883e939365fa15623a4a"
+  integrity sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==
+
+npm-run-path@^5.1.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f"
+  integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==
+  dependencies:
+    path-key "^4.0.0"
+
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+  dependencies:
+    wrappy "1"
+
+onetime@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4"
+  integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==
+  dependencies:
+    mimic-fn "^4.0.0"
+
+onetime@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60"
+  integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==
+  dependencies:
+    mimic-function "^5.0.0"
+
+p-cancelable@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050"
+  integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==
+
+package-json@^8.1.0:
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8"
+  integrity sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==
+  dependencies:
+    got "^12.1.0"
+    registry-auth-token "^5.0.1"
+    registry-url "^6.0.0"
+    semver "^7.3.7"
+
+parent-module@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+  integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+  dependencies:
+    callsites "^3.0.0"
+
+parse-json@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
+  integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    error-ex "^1.3.1"
+    json-parse-even-better-errors "^2.3.0"
+    lines-and-columns "^1.1.6"
+
+path-key@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-key@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
+  integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
+
+path-type@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+  integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+picocolors@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
+  integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+picomatch@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pidtree@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c"
+  integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==
+
+pluralize@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
+  integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
+
+prettier-linter-helpers@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
+  integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
+  dependencies:
+    fast-diff "^1.1.2"
+
+prettier-plugin-solidity@^1.4.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.2.tgz#d4f6173674e73a29731a8c79c45ab6f5246310df"
+  integrity sha512-VVD/4XlDjSzyPWWCPW8JEleFa8JNKFYac5kNlMjVXemQyQZKfpekPMhFZSePuXB6L+RixlFvWe20iacGjFYrLw==
+  dependencies:
+    "@solidity-parser/parser" "^0.19.0"
+    semver "^7.6.3"
+
+prettier@^2.8.3:
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
+  integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
+
+prettier@^3.5.3:
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5"
+  integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
+
+proto-list@~1.2.1:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+  integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
+
+punycode@^2.1.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
+  integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+quick-lru@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+  integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
+rc@1.2.8:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+  integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+  dependencies:
+    deep-extend "^0.6.0"
+    ini "~1.3.0"
+    minimist "^1.2.0"
+    strip-json-comments "~2.0.1"
+
+registry-auth-token@^5.0.1:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.1.0.tgz#3c659047ecd4caebd25bc1570a3aa979ae490eca"
+  integrity sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==
+  dependencies:
+    "@pnpm/npm-conf" "^2.1.0"
+
+registry-url@^6.0.0:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-6.0.1.tgz#056d9343680f2f64400032b1e199faa692286c58"
+  integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==
+  dependencies:
+    rc "1.2.8"
+
+require-from-string@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+resolve-alpn@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
+  integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
+
+resolve-from@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+  integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+responselike@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626"
+  integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==
+  dependencies:
+    lowercase-keys "^3.0.0"
+
+restore-cursor@^5.0.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7"
+  integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==
+  dependencies:
+    onetime "^7.0.0"
+    signal-exit "^4.1.0"
+
+rfdc@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca"
+  integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
+
+semver@^7.3.7, semver@^7.5.2, semver@^7.6.3:
+  version "7.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
+  integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
+
+shebang-command@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+  integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+  dependencies:
+    shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+  integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+signal-exit@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
+  integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
+slice-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
+  integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
+  dependencies:
+    ansi-styles "^4.0.0"
+    astral-regex "^2.0.0"
+    is-fullwidth-code-point "^3.0.0"
+
+slice-ansi@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
+  integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==
+  dependencies:
+    ansi-styles "^6.0.0"
+    is-fullwidth-code-point "^4.0.0"
+
+slice-ansi@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9"
+  integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==
+  dependencies:
+    ansi-styles "^6.2.1"
+    is-fullwidth-code-point "^5.0.0"
+
+solhint-plugin-prettier@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.1.0.tgz#2f46999e26d6c6bc80281c22a7a21e381175bef7"
+  integrity sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw==
+  dependencies:
+    "@prettier/sync" "^0.3.0"
+    prettier-linter-helpers "^1.0.0"
+
+solhint@^5.0.5:
+  version "5.0.5"
+  resolved "https://registry.yarnpkg.com/solhint/-/solhint-5.0.5.tgz#43d9c730c2b22e11aa45582580749e0801a049d4"
+  integrity sha512-WrnG6T+/UduuzSWsSOAbfq1ywLUDwNea3Gd5hg6PS+pLUm8lz2ECNr0beX609clBxmDeZ3676AiA9nPDljmbJQ==
+  dependencies:
+    "@solidity-parser/parser" "^0.19.0"
+    ajv "^6.12.6"
+    antlr4 "^4.13.1-patch-1"
+    ast-parents "^0.0.1"
+    chalk "^4.1.2"
+    commander "^10.0.0"
+    cosmiconfig "^8.0.0"
+    fast-diff "^1.2.0"
+    glob "^8.0.3"
+    ignore "^5.2.4"
+    js-yaml "^4.1.0"
+    latest-version "^7.0.0"
+    lodash "^4.17.21"
+    pluralize "^8.0.0"
+    semver "^7.5.2"
+    strip-ansi "^6.0.1"
+    table "^6.8.1"
+    text-table "^0.2.0"
+  optionalDependencies:
+    prettier "^2.8.3"
+
+string-argv@^0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
+  integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
+
+string-width@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
+string-width@^7.0.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc"
+  integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==
+  dependencies:
+    emoji-regex "^10.3.0"
+    get-east-asian-width "^1.0.0"
+    strip-ansi "^7.1.0"
+
+strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
+strip-ansi@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+  integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+  dependencies:
+    ansi-regex "^6.0.1"
+
+strip-final-newline@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd"
+  integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
+
+strip-json-comments@~2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+  integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
+
+supports-color@^7.1.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+  dependencies:
+    has-flag "^4.0.0"
+
+table@^6.8.1:
+  version "6.9.0"
+  resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5"
+  integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==
+  dependencies:
+    ajv "^8.0.1"
+    lodash.truncate "^4.4.2"
+    slice-ansi "^4.0.0"
+    string-width "^4.2.3"
+    strip-ansi "^6.0.1"
+
+text-table@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+  integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
+uri-js@^4.2.2:
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+  integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+  dependencies:
+    punycode "^2.1.0"
+
+which@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+  dependencies:
+    isexe "^2.0.0"
+
+wrap-ansi@^9.0.0:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e"
+  integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==
+  dependencies:
+    ansi-styles "^6.2.1"
+    string-width "^7.0.0"
+    strip-ansi "^7.1.0"
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+yaml@^2.7.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"
+  integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==

From d94183820dc9fc8a2f9175aa7dc58cbec8844ce5 Mon Sep 17 00:00:00 2001
From: Borja Aranda <borja@ephemerahq.com>
Date: Wed, 19 Mar 2025 12:20:37 +0100
Subject: [PATCH 3/7] remove multiple functions

---
 src/PayerRegistry.sol             | 49 ++++---------------------------
 src/interfaces/IPayerRegistry.sol | 25 ----------------
 2 files changed, 6 insertions(+), 68 deletions(-)

diff --git a/src/PayerRegistry.sol b/src/PayerRegistry.sol
index e3cc539..ff9a0bb 100644
--- a/src/PayerRegistry.sol
+++ b/src/PayerRegistry.sol
@@ -13,7 +13,7 @@ import { SafeERC20 } from "../lib/oz/contracts/token/ERC20/utils/SafeERC20.sol";
 import { UUPSUpgradeable } from "../lib/oz-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
 
 import { IFeeDistributor } from "./interfaces/IFeeDistributor.sol";
-import { INodes } from "./interfaces/INodes.sol";
+import { INodeRegistry } from "./interfaces/INodeRegistry.sol";
 import { IPayerRegistry } from "./interfaces/IPayerRegistry.sol";
 import { IPayerReportManager } from "./interfaces/IPayerReportManager.sol";
 
@@ -69,7 +69,6 @@ contract PayerRegistry is
         // Mappings and dynamic sets (each starts at its own storage slot)
         mapping(address => Payer) payers;
         mapping(address => Withdrawal) withdrawals;
-        EnumerableSet.AddressSet totalPayers;
         EnumerableSet.AddressSet activePayers;
         EnumerableSet.AddressSet debtPayers;
     }
@@ -159,7 +158,6 @@ contract PayerRegistry is
 
         // Add new payer to active and total payers sets
         require($.activePayers.add(msg.sender), FailedToRegisterPayer());
-        require($.totalPayers.add(msg.sender), FailedToRegisterPayer());
 
         _increaseTotalDeposited(amount);
 
@@ -186,33 +184,12 @@ contract PayerRegistry is
      * @inheritdoc IPayerRegistry
      */
     function deactivatePayer(uint256 nodeId, address payer) external whenNotPaused {
+        _revertIfNotNodeOperator(nodeId);
         _revertIfPayerDoesNotExist(payer);
 
         _deactivatePayer(nodeId, payer);
     }
 
-    /**
-     * @inheritdoc IPayerRegistry
-     */
-    function deletePayer(address payer) external whenNotPaused onlyRole(ADMIN_ROLE) {
-        _revertIfPayerDoesNotExist(payer);
-
-        PayerStorage storage $ = _getPayerStorage();
-
-        require($.withdrawals[payer].withdrawableTimestamp == 0, PayerInWithdrawal());
-
-        if ($.payers[payer].balance > 0 || $.payers[payer].debtAmount > 0) {
-            revert PayerHasBalanceOrDebt();
-        }
-
-        // Delete all payer data
-        delete $.payers[payer];
-        require($.totalPayers.remove(payer), FailedToDeletePayer());
-        require($.activePayers.remove(payer), FailedToDeletePayer());
-
-        emit PayerDeleted(payer, uint64(block.timestamp));
-    }
-
     /* ========== Payers Balance Management ========= */
 
     /**
@@ -564,20 +541,6 @@ contract PayerRegistry is
         return (payers, hasMore_);
     }
 
-    /**
-     * @inheritdoc IPayerRegistry
-     */
-    function getTotalPayerCount() external view returns (uint256 count) {
-        return _getPayerStorage().totalPayers.length();
-    }
-
-    /**
-     * @inheritdoc IPayerRegistry
-     */
-    function getActivePayerCount() external view returns (uint256 count) {
-        return _getPayerStorage().activePayers.length();
-    }
-
     /**
      * @inheritdoc IPayerRegistry
      */
@@ -782,12 +745,12 @@ contract PayerRegistry is
     }
 
     function _revertIfNotNodeOperator(uint256 nodeId) internal view {
-        INodes nodes = INodes(_getPayerStorage().nodeRegistry);
+        INodeRegistry nodes = INodeRegistry(_getPayerStorage().nodeRegistry);
 
         require(msg.sender == nodes.ownerOf(nodeId), Unauthorized());
 
-        // TODO: Change for a better filter.
-        return nodes.getReplicationNodeIsActive(nodeId);
+        // TODO(borja): Change for a better filter.
+        require(nodes.getReplicationNodeIsActive(nodeId), Unauthorized());
     }
 
     /**
@@ -878,7 +841,7 @@ contract PayerRegistry is
     function _setNodeRegistry(address newNodeRegistry) internal {
         PayerStorage storage $ = _getPayerStorage();
 
-        try INodes(newNodeRegistry).supportsInterface(type(INodes).interfaceId) returns (bool supported) {
+        try INodeRegistry(newNodeRegistry).supportsInterface(type(INodeRegistry).interfaceId) returns (bool supported) {
             require(supported, InvalidNodeRegistry());
         } catch {
             revert InvalidNodeRegistry();
diff --git a/src/interfaces/IPayerRegistry.sol b/src/interfaces/IPayerRegistry.sol
index d08553d..7715a9a 100644
--- a/src/interfaces/IPayerRegistry.sol
+++ b/src/interfaces/IPayerRegistry.sol
@@ -32,9 +32,6 @@ interface IPayerRegistryEvents {
     /// @dev Emitted when a payer is deactivated by an owner.
     event PayerDeactivated(uint256 indexed operatorId, address indexed payer);
 
-    /// @dev Emitted when a payer is permanently deleted from the system.
-    event PayerDeleted(address indexed payer, uint64 timestamp);
-
     /// @dev Emitted when a new payer is registered.
     event PayerRegistered(address indexed payer, uint64 amount);
 
@@ -250,16 +247,6 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      */
     function deactivatePayer(uint256 operatorId, address payer) external;
 
-    /**
-     * @notice Permanently deletes a payer from the system.
-     * @dev    Can only delete payers with zero balance and zero debt who are not in withdrawal.
-     *         Only callable by authorized node operators.
-     * @param  payer The address of the payer to delete.
-     *
-     * Emits `PayerDeleted`.
-     */
-    function deletePayer(address payer) external;
-
     /* ============ Payer Balance Management ============ */
 
     /**
@@ -449,18 +436,6 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      */
     function getPayersInDebt(uint32 offset, uint32 limit) external view returns (Payer[] memory payers, bool hasMore);
 
-    /**
-     * @notice Returns the total number of registered payers.
-     * @return count The total number of registered payers.
-     */
-    function getTotalPayerCount() external view returns (uint256 count);
-
-    /**
-     * @notice Returns the number of active payers.
-     * @return count The number of active payers.
-     */
-    function getActivePayerCount() external view returns (uint256 count);
-
     /**
      * @notice Returns the timestamp of the last fee transfer to the rewards contract.
      * @return timestamp The last fee transfer timestamp.

From 7143dc507f4d3683c926b2ca3866673ecd5e1086 Mon Sep 17 00:00:00 2001
From: Borja Aranda <borja@ephemerahq.com>
Date: Wed, 19 Mar 2025 12:25:08 +0100
Subject: [PATCH 4/7] remove functions

---
 src/PayerRegistry.sol | 68 +++++++++++++++++--------------------------
 1 file changed, 26 insertions(+), 42 deletions(-)

diff --git a/src/PayerRegistry.sol b/src/PayerRegistry.sol
index ff9a0bb..889eba4 100644
--- a/src/PayerRegistry.sol
+++ b/src/PayerRegistry.sol
@@ -369,14 +369,38 @@ contract PayerRegistry is
      * @inheritdoc IPayerRegistry
      */
     function setFeeDistributor(address newFeeDistributor) external onlyRole(ADMIN_ROLE) {
-        _setFeeDistributor(newFeeDistributor);
+        PayerStorage storage $ = _getPayerStorage();
+
+        try IFeeDistributor(newFeeDistributor).supportsInterface(type(IFeeDistributor).interfaceId) returns (
+            bool supported
+        ) {
+            require(supported, InvalidFeeDistributor());
+        } catch {
+            revert InvalidFeeDistributor();
+        }
+
+        $.feeDistributor = newFeeDistributor;
+
+        emit FeeDistributorSet(newFeeDistributor);
     }
 
     /**
      * @inheritdoc IPayerRegistry
      */
     function setPayerReportManager(address newPayerReportManager) external onlyRole(ADMIN_ROLE) {
-        _setPayerReportManager(newPayerReportManager);
+        PayerStorage storage $ = _getPayerStorage();
+
+        try
+            IPayerReportManager(newPayerReportManager).supportsInterface(type(IPayerReportManager).interfaceId)
+        returns (bool supported) {
+            require(supported, InvalidPayerReportManager());
+        } catch {
+            revert InvalidPayerReportManager();
+        }
+
+        $.payerReportManager = newPayerReportManager;
+
+        emit PayerReportManagerSet(newPayerReportManager);
     }
 
     /**
@@ -794,46 +818,6 @@ contract PayerRegistry is
         }
     }
 
-    /**
-     * @notice Sets the FeeDistributor contract.
-     * @param  newFeeDistributor The address of the new FeeDistributor contract.
-     */
-    function _setFeeDistributor(address newFeeDistributor) internal {
-        PayerStorage storage $ = _getPayerStorage();
-
-        try IFeeDistributor(newFeeDistributor).supportsInterface(type(IFeeDistributor).interfaceId) returns (
-            bool supported
-        ) {
-            require(supported, InvalidFeeDistributor());
-        } catch {
-            revert InvalidFeeDistributor();
-        }
-
-        $.feeDistributor = newFeeDistributor;
-
-        emit FeeDistributorSet(newFeeDistributor);
-    }
-
-    /**
-     * @notice Sets the PayerReportManager contract.
-     * @param  newPayerReportManager The address of the new PayerReportManager contract.
-     */
-    function _setPayerReportManager(address newPayerReportManager) internal {
-        PayerStorage storage $ = _getPayerStorage();
-
-        try
-            IPayerReportManager(newPayerReportManager).supportsInterface(type(IPayerReportManager).interfaceId)
-        returns (bool supported) {
-            require(supported, InvalidPayerReportManager());
-        } catch {
-            revert InvalidPayerReportManager();
-        }
-
-        $.payerReportManager = newPayerReportManager;
-
-        emit PayerReportManagerSet(newPayerReportManager);
-    }
-
     /**
      * @notice Sets the NodeRegistry contract.
      * @param  newNodeRegistry The address of the new NodeRegistry contract.

From 38c102125e2fde57c29f9d2941a36c728d5c71ee Mon Sep 17 00:00:00 2001
From: Borja Aranda <borja@ephemerahq.com>
Date: Wed, 19 Mar 2025 14:17:52 +0100
Subject: [PATCH 5/7] make balance int64

---
 src/PayerRegistry.sol             | 260 +++++++-----------------------
 src/interfaces/IPayerRegistry.sol |  44 +----
 2 files changed, 60 insertions(+), 244 deletions(-)

diff --git a/src/PayerRegistry.sol b/src/PayerRegistry.sol
index 889eba4..06f3850 100644
--- a/src/PayerRegistry.sol
+++ b/src/PayerRegistry.sol
@@ -37,10 +37,8 @@ contract PayerRegistry is
 
     bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
     string internal constant _USDC_SYMBOL = "USDC";
-    uint8 private constant _PAYER_OPERATOR_ID = 1;
     uint64 private constant _DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS = 10_000_000;
     uint64 private constant _DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS = 10_000_000;
-    uint64 private constant _DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS = 50_000_000;
     uint32 private constant _DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD = 6 hours;
     uint32 private constant _ABSOLUTE_MINIMUM_TRANSFER_FEES_PERIOD = 1 hours;
     uint32 private constant _DEFAULT_WITHDRAWAL_LOCK_PERIOD = 3 days;
@@ -53,12 +51,9 @@ contract PayerRegistry is
         // Configuration and state parameters (fits in 2 slots)
         uint64 minimumRegistrationAmountMicroDollars;
         uint64 minimumDepositAmountMicroDollars;
-        uint64 maxTolerableDebtAmountMicroDollars;
-        uint64 lastFeeTransferTimestamp;
-        uint64 totalDeposited;
-        uint64 totalDebt;
         uint64 pendingFees;
         uint64 collectedFees;
+        uint64 lastFeeTransferTimestamp;
         uint32 withdrawalLockPeriod;
         uint32 transferFeesPeriod;
         // Contract addresses (fits in 3 slots)
@@ -125,7 +120,6 @@ contract PayerRegistry is
         $.minimumRegistrationAmountMicroDollars = _DEFAULT_MINIMUM_REGISTRATION_AMOUNT_MICRO_DOLLARS;
         $.minimumDepositAmountMicroDollars = _DEFAULT_MINIMUM_DEPOSIT_AMOUNT_MICRO_DOLLARS;
         $.withdrawalLockPeriod = _DEFAULT_WITHDRAWAL_LOCK_PERIOD;
-        $.maxTolerableDebtAmountMicroDollars = _DEFAULT_MAX_TOLERABLE_DEBT_AMOUNT_MICRO_DOLLARS;
         $.transferFeesPeriod = _DEFAULT_MINIMUM_TRANSFER_FEES_PERIOD;
 
         _setUsdcToken(usdcToken);
@@ -150,17 +144,11 @@ contract PayerRegistry is
         IERC20($.usdcToken).safeTransferFrom(msg.sender, address(this), amount);
 
         // New payer registration
-        $.payers[msg.sender] = Payer({
-            balance: amount,
-            debtAmount: 0,
-            latestDepositTimestamp: uint64(block.timestamp)
-        });
+        $.payers[msg.sender] = Payer({ balance: int64(amount), latestDepositTimestamp: uint64(block.timestamp) });
 
         // Add new payer to active and total payers sets
         require($.activePayers.add(msg.sender), FailedToRegisterPayer());
 
-        _increaseTotalDeposited(amount);
-
         emit PayerRegistered(msg.sender, amount);
     }
 
@@ -180,16 +168,6 @@ contract PayerRegistry is
         _validateAndProcessDeposit(msg.sender, payer, amount);
     }
 
-    /**
-     * @inheritdoc IPayerRegistry
-     */
-    function deactivatePayer(uint256 nodeId, address payer) external whenNotPaused {
-        _revertIfNotNodeOperator(nodeId);
-        _revertIfPayerDoesNotExist(payer);
-
-        _deactivatePayer(nodeId, payer);
-    }
-
     /* ========== Payers Balance Management ========= */
 
     /**
@@ -200,19 +178,17 @@ contract PayerRegistry is
 
         PayerStorage storage $ = _getPayerStorage();
 
-        require($.payers[msg.sender].debtAmount == 0, PayerHasDebt());
-        require($.payers[msg.sender].balance >= amount, InsufficientBalance());
+        require($.payers[msg.sender].balance >= int64(amount), InsufficientBalance());
 
         // Balance to be withdrawn is deducted from the payer's balance,
         // it can't be used to settle payments.
-        $.payers[msg.sender].balance -= amount;
-        _decreaseTotalDeposited(amount);
+        $.payers[msg.sender].balance -= int64(amount);
 
         uint64 withdrawableTimestamp = uint64(block.timestamp) + $.withdrawalLockPeriod;
 
         $.withdrawals[msg.sender] = Withdrawal({ withdrawableTimestamp: withdrawableTimestamp, amount: amount });
 
-        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
+        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance);
 
         emit WithdrawalRequested(msg.sender, withdrawableTimestamp, amount);
     }
@@ -229,10 +205,9 @@ contract PayerRegistry is
 
         delete $.withdrawals[msg.sender];
 
-        $.payers[msg.sender].balance += withdrawal.amount;
-        _increaseTotalDeposited(withdrawal.amount);
+        $.payers[msg.sender].balance += int64(withdrawal.amount);
 
-        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
+        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance);
 
         emit WithdrawalCancelled(msg.sender, withdrawal.withdrawableTimestamp);
     }
@@ -254,15 +229,16 @@ contract PayerRegistry is
 
         uint64 finalWithdrawalAmount = withdrawal.amount;
 
-        if ($.payers[msg.sender].debtAmount > 0) {
-            finalWithdrawalAmount = _settleDebts(msg.sender, withdrawal.amount, true);
+        if ($.payers[msg.sender].balance < 0) {
+            finalWithdrawalAmount = _settleDebtsBeforeWithdrawal(msg.sender, withdrawal.amount);
         }
 
         if (finalWithdrawalAmount > 0) {
             IERC20($.usdcToken).safeTransfer(msg.sender, finalWithdrawalAmount);
         }
 
-        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance, $.payers[msg.sender].debtAmount);
+        /// @dev re-emitting the balance update, as it can change due to debt settlement.
+        emit PayerBalanceUpdated(msg.sender, $.payers[msg.sender].balance);
 
         emit WithdrawalFinalized(msg.sender, withdrawal.withdrawableTimestamp, finalWithdrawalAmount);
     }
@@ -298,39 +274,45 @@ contract PayerRegistry is
             uint64 usage = usageAmountsList[i];
 
             // This should never happen, as PayerReport has already verified the payers and amounts.
-            // Payers in payerList should always exist and be active.
-            if (!_payerExists(payer) || !_payerIsActive(payer)) continue;
+            // In case it does, skip the payer and process the rest.
+            if (!_payerExists(payer)) continue;
 
             Payer memory storedPayer = $.payers[payer];
 
-            if (storedPayer.balance < usage) {
-                uint64 debt = usage - storedPayer.balance;
+            if (storedPayer.balance >= int64(usage)) {
+                settledFees += usage;
+                pendingFees += usage;
 
-                settledFees += storedPayer.balance;
-                pendingFees += storedPayer.balance;
+                storedPayer.balance -= int64(usage);
 
-                storedPayer.balance = 0;
-                storedPayer.debtAmount = debt;
                 $.payers[payer] = storedPayer;
 
-                _addDebtor(payer);
-                _increaseTotalDebt(debt);
+                emit PayerBalanceUpdated(payer, storedPayer.balance);
 
-                if (debt > $.maxTolerableDebtAmountMicroDollars) _deactivatePayer(_PAYER_OPERATOR_ID, payer);
+                continue;
+            }
 
-                emit PayerBalanceUpdated(payer, storedPayer.balance, storedPayer.debtAmount);
+            if (storedPayer.balance < 0) {
+                storedPayer.balance -= int64(usage);
+
+                $.payers[payer] = storedPayer;
+
+                emit PayerBalanceUpdated(payer, storedPayer.balance);
 
                 continue;
             }
 
-            settledFees += usage;
-            pendingFees += usage;
+            // Payer has balance, but not enough to settle the usage.
+            _addDebtor(payer);
 
-            storedPayer.balance -= usage;
+            settledFees += uint64(storedPayer.balance);
+            pendingFees += uint64(storedPayer.balance);
+
+            storedPayer.balance -= int64(usage);
 
             $.payers[payer] = storedPayer;
 
-            emit PayerBalanceUpdated(payer, storedPayer.balance, storedPayer.debtAmount);
+            emit PayerBalanceUpdated(payer, storedPayer.balance);
         }
 
         $.pendingFees = pendingFees;
@@ -462,20 +444,6 @@ contract PayerRegistry is
         emit WithdrawalLockPeriodSet(oldWithdrawalLockPeriod, newWithdrawalLockPeriod);
     }
 
-    /**
-     * @inheritdoc IPayerRegistry
-     */
-    function setMaxTolerableDebtAmount(uint64 newMaxTolerableDebtAmountMicroDollars) external onlyRole(ADMIN_ROLE) {
-        require(newMaxTolerableDebtAmountMicroDollars > 0, InvalidMaxTolerableDebtAmount());
-
-        PayerStorage storage $ = _getPayerStorage();
-
-        uint64 oldMaxTolerableDebtAmount = $.maxTolerableDebtAmountMicroDollars;
-        $.maxTolerableDebtAmountMicroDollars = newMaxTolerableDebtAmountMicroDollars;
-
-        emit MaxTolerableDebtAmountSet(oldMaxTolerableDebtAmount, newMaxTolerableDebtAmountMicroDollars);
-    }
-
     /**
      * @inheritdoc IPayerRegistry
      */
@@ -543,7 +511,7 @@ contract PayerRegistry is
     /**
      * @inheritdoc IPayerRegistry
      */
-    function getPayerBalance(address payer) external view returns (uint64 balance) {
+    function getPayerBalance(address payer) external view returns (int64 balance) {
         _revertIfPayerDoesNotExist(payer);
 
         return _getPayerStorage().payers[payer].balance;
@@ -572,24 +540,6 @@ contract PayerRegistry is
         return _getPayerStorage().lastFeeTransferTimestamp;
     }
 
-    /**
-     * @inheritdoc IPayerRegistry
-     */
-    function getTotalValueLocked() external view returns (uint64 totalValueLocked) {
-        PayerStorage storage $ = _getPayerStorage();
-
-        if ($.totalDebt > $.totalDeposited) return 0;
-
-        return $.totalDeposited - $.totalDebt;
-    }
-
-    /**
-     * @inheritdoc IPayerRegistry
-     */
-    function getTotalDebt() external view returns (uint64 totalDebt) {
-        return _getPayerStorage().totalDebt;
-    }
-
     /**
      * @inheritdoc IPayerRegistry
      */
@@ -662,72 +612,42 @@ contract PayerRegistry is
 
         IERC20($.usdcToken).safeTransferFrom(from, address(this), amount);
 
-        _updatePayerBalance(to, amount);
-
-        emit PayerBalanceUpdated(to, $.payers[to].balance, $.payers[to].debtAmount);
-    }
+        $.payers[to].balance += int64(amount);
 
-    /**
-     * @notice Updates a payer's balance, handling debt settlement if applicable.
-     * @param  payerAddress The address of the payer.
-     * @param  amount The amount to add to the payer's balance.
-     * @return leftoverAmount Amount remaining after debt settlement (if any).
-     */
-    function _updatePayerBalance(address payerAddress, uint64 amount) internal returns (uint64 leftoverAmount) {
-        Payer storage payer = _getPayerStorage().payers[payerAddress];
-
-        if (payer.debtAmount > 0) {
-            return _settleDebts(payerAddress, amount, false);
-        } else {
-            payer.balance += amount;
-            _increaseTotalDeposited(amount);
-            return amount;
-        }
+        emit PayerBalanceUpdated(to, $.payers[to].balance);
     }
 
     /**
-     * @notice Settles debts for a payer, updating their balance and total amounts.
+     * @notice Settles debts for a payer before withdrawal.
      * @param  payer The address of the payer.
-     * @param  amount The amount to settle debts for.
-     * @param  isWithdrawal Whether the debt settlement happens during a withdrawal.
-     * @return amountAfterSettlement The amount remaining after debt settlement.
-     */
-    function _settleDebts(
-        address payer,
-        uint64 amount,
-        bool isWithdrawal
-    ) internal returns (uint64 amountAfterSettlement) {
-        PayerStorage storage $ = _getPayerStorage();
-
-        Payer memory storedPayer = $.payers[payer];
-
-        if (storedPayer.debtAmount < amount) {
-            uint64 debtToRemove = storedPayer.debtAmount;
-            amount -= debtToRemove;
+     * @param  amount The withdrawal amount that can be used to settle debt.
+     * @return remainingAmount The amount left for withdrawal after settling debt.
+     */
+    function _settleDebtsBeforeWithdrawal(address payer, uint64 amount) internal returns (uint64 remainingAmount) {
+        Payer storage payerData = _getPayerStorage().payers[payer];
 
-            // For regular deposits, add remaining amount to balance.
-            // In withdrawals, that amount was moved to the withdrawal balance.
-            if (!isWithdrawal) {
-                storedPayer.balance += amount;
-                _increaseTotalDeposited(amount);
-            }
+        if (payerData.balance >= 0) return amount;
 
-            _removeDebtor(payer);
-            _increaseTotalDeposited(amount);
-            _decreaseTotalDebt(debtToRemove);
+        // Balance is always negative, we can safely negate it to get the debt.
+        uint64 debt = uint64(-payerData.balance);
 
-            amountAfterSettlement = amount;
-        } else {
-            storedPayer.debtAmount -= amount;
+        // If debt is greater than or equal to the withdrawal amount,
+        // use the entire amount to reduce debt.
+        if (debt >= amount) {
+            payerData.balance += int64(amount);
 
-            _decreaseTotalDebt(amount);
+            // If balance is now 0, remove from debtors
+            if (payerData.balance == 0) _removeDebtor(payer);
 
-            amountAfterSettlement = 0;
+            return 0;
         }
 
-        $.payers[payer] = storedPayer;
+        payerData.balance = 0;
 
-        return amountAfterSettlement;
+        _removeDebtor(payer);
+
+        // Return remaining amount after settling debt
+        return amount - debt;
     }
 
     /**
@@ -739,27 +659,6 @@ contract PayerRegistry is
         return _getPayerStorage().payers[payer].latestDepositTimestamp != 0;
     }
 
-    /**
-     * @notice Checks if a payer is active.
-     * @param  payer The address of the payer to check.
-     * @return isActive True if the payer is active, false otherwise.
-     */
-    function _payerIsActive(address payer) internal view returns (bool isActive) {
-        return _getPayerStorage().activePayers.contains(payer);
-    }
-
-    /**
-     * @notice Deactivates a payer.
-     * @param  payer The address of the payer to deactivate.
-     */
-    function _deactivatePayer(uint256 operatorId, address payer) internal {
-        PayerStorage storage $ = _getPayerStorage();
-
-        require($.activePayers.remove(payer), FailedToDeactivatePayer());
-
-        emit PayerDeactivated(operatorId, payer);
-    }
-
     /**
      * @notice Reverts if a payer does not exist.
      * @param  payer The address of the payer to check.
@@ -768,15 +667,6 @@ contract PayerRegistry is
         require(_payerExists(payer), PayerDoesNotExist());
     }
 
-    function _revertIfNotNodeOperator(uint256 nodeId) internal view {
-        INodeRegistry nodes = INodeRegistry(_getPayerStorage().nodeRegistry);
-
-        require(msg.sender == nodes.ownerOf(nodeId), Unauthorized());
-
-        // TODO(borja): Change for a better filter.
-        require(nodes.getReplicationNodeIsActive(nodeId), Unauthorized());
-    }
-
     /**
      * @notice Checks if a withdrawal exists.
      * @param  payer The address of the payer to check.
@@ -854,42 +744,6 @@ contract PayerRegistry is
         emit UsdcTokenSet(newUsdcToken);
     }
 
-    /**
-     * @notice Increases the total amount deposited by a given amount.
-     * @param  amount The amount to increase the total amount deposited by.
-     */
-    function _increaseTotalDeposited(uint64 amount) internal {
-        if (amount > 0) _getPayerStorage().totalDeposited += amount;
-    }
-
-    /**
-     * @notice Decreases the total amount deposited by a given amount.
-     * @param  amount The amount to decrease the total amount deposited by.
-     */
-    function _decreaseTotalDeposited(uint64 amount) internal {
-        PayerStorage storage $ = _getPayerStorage();
-
-        $.totalDeposited = amount > $.totalDeposited ? 0 : $.totalDeposited - amount;
-    }
-
-    /**
-     * @notice Increases the total debt amount by a given amount.
-     * @param  amount The amount to increase the total debt amount by.
-     */
-    function _increaseTotalDebt(uint64 amount) internal {
-        _getPayerStorage().totalDebt += amount;
-    }
-
-    /**
-     * @notice Decreases the total debt amount by a given amount.
-     * @param  amount The amount to decrease the total debt amount by.
-     */
-    function _decreaseTotalDebt(uint64 amount) internal {
-        PayerStorage storage $ = _getPayerStorage();
-
-        $.totalDebt = amount > $.totalDebt ? 0 : $.totalDebt - amount;
-    }
-
     /**
      * @notice Internal helper for paginated access to EnumerableSet.AddressSet.
      * @param  addressSet The EnumerableSet to paginate.
diff --git a/src/interfaces/IPayerRegistry.sol b/src/interfaces/IPayerRegistry.sol
index 7715a9a..c855604 100644
--- a/src/interfaces/IPayerRegistry.sol
+++ b/src/interfaces/IPayerRegistry.sol
@@ -14,9 +14,6 @@ interface IPayerRegistryEvents {
     /// @dev Emitted when fees are transferred to the distribution contract.
     event FeesTransferred(uint64 indexed timestamp, uint64 amount);
 
-    /// @dev Emitted when the maximum tolerable debt amount is updated.
-    event MaxTolerableDebtAmountSet(uint64 oldMaxTolerableDebtAmount, uint64 newMaxTolerableDebtAmount);
-
     /// @dev Emitted when the minimum deposit amount is updated.
     event MinimumDepositSet(uint64 oldMinimumDeposit, uint64 newMinimumDeposit);
 
@@ -27,7 +24,7 @@ interface IPayerRegistryEvents {
     event NodeRegistrySet(address indexed newNodeRegistry);
 
     /// @dev Emitted when a payer balance is updated.
-    event PayerBalanceUpdated(address indexed payer, uint64 newBalance, uint64 newDebtAmount);
+    event PayerBalanceUpdated(address indexed payer, int64 newBalance);
 
     /// @dev Emitted when a payer is deactivated by an owner.
     event PayerDeactivated(uint256 indexed operatorId, address indexed payer);
@@ -104,9 +101,6 @@ interface IPayerRegistryErrors {
     /// @dev Error thrown when contract is not the fee distributor.
     error InvalidFeeDistributor();
 
-    /// @dev Error thrown when the maximum tolerable debt amount is invalid.
-    error InvalidMaxTolerableDebtAmount();
-
     /// @dev Error thrown when the minimum deposit is invalid.
     error InvalidMinimumDeposit();
 
@@ -188,12 +182,10 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
     /**
      * @dev   Struct to store payer information.
      * @param balance                The current USDC balance of the payer.
-     * @param debtAmount             The amount of fees owed but not yet settled.
      * @param latestDepositTimestamp The timestamp of the most recent deposit.
      */
     struct Payer {
-        uint64 balance;
-        uint64 debtAmount;
+        int64 balance;
         uint64 latestDepositTimestamp;
     }
 
@@ -237,16 +229,6 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      */
     function deposit(address payer, uint64 amount) external;
 
-    /**
-     * @notice Deactivates a payer, signaling XMTP nodes they should not accept messages from them.
-     *         Only callable by authorized node operators.
-     * @param  operatorId The ID of the operator calling the function.
-     * @param  payer      The address of the payer to deactivate.
-     *
-     * Emits `PayerDeactivated`.
-     */
-    function deactivatePayer(uint256 operatorId, address payer) external;
-
     /* ============ Payer Balance Management ============ */
 
     /**
@@ -372,14 +354,6 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      */
     function setWithdrawalLockPeriod(uint32 newWithdrawalLockPeriod) external;
 
-    /**
-     * @notice Sets the maximum tolerable debt amount.
-     * @param  newMaxTolerableDebtAmount The new maximum tolerable debt amount.
-     *
-     * Emits `MaxTolerableDebtAmountUpdated`.
-     */
-    function setMaxTolerableDebtAmount(uint64 newMaxTolerableDebtAmount) external;
-
     /**
      * @notice Sets the transfer fees period.
      * @param  newTransferFeesPeriod The new transfer fees period.
@@ -442,18 +416,6 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      */
     function getLastFeeTransferTimestamp() external view returns (uint64 timestamp);
 
-    /**
-     * @notice Returns the total value locked in the contract (all payer balances).
-     * @return tvl The total value locked in USDC.
-     */
-    function getTotalValueLocked() external view returns (uint64 tvl);
-
-    /**
-     * @notice Returns the total outstanding debt amount across all payers.
-     * @return totalDebt The total debt amount in USDC.
-     */
-    function getTotalDebt() external view returns (uint64 totalDebt);
-
     /**
      * @notice Returns the actual USDC balance held by the contract.
      * @dev    This can be used to verify the contract's accounting is accurate.
@@ -496,7 +458,7 @@ interface IPayerRegistry is IERC165, IPayerRegistryEvents, IPayerRegistryErrors
      * @param  payer   The address of the payer.
      * @return balance The current balance of the payer.
      */
-    function getPayerBalance(address payer) external view returns (uint64 balance);
+    function getPayerBalance(address payer) external view returns (int64 balance);
 
     /**
      * @notice Returns the duration of the lock period required before a withdrawal

From e841d528dacc88156e7097652bf8ee6e5f0dba3e Mon Sep 17 00:00:00 2001
From: Borja Aranda <borja@ephemerahq.com>
Date: Wed, 19 Mar 2025 14:31:06 +0100
Subject: [PATCH 6/7] interface changes

---
 src/PayerRegistry.sol             | 3 ---
 src/interfaces/IPayerRegistry.sol | 6 +++---
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/src/PayerRegistry.sol b/src/PayerRegistry.sol
index 06f3850..7fd66cc 100644
--- a/src/PayerRegistry.sol
+++ b/src/PayerRegistry.sol
@@ -48,7 +48,6 @@ contract PayerRegistry is
 
     /// @custom:storage-location erc7201:xmtp.storage.Payer
     struct PayerStorage {
-        // Configuration and state parameters (fits in 2 slots)
         uint64 minimumRegistrationAmountMicroDollars;
         uint64 minimumDepositAmountMicroDollars;
         uint64 pendingFees;
@@ -56,12 +55,10 @@ contract PayerRegistry is
         uint64 lastFeeTransferTimestamp;
         uint32 withdrawalLockPeriod;
         uint32 transferFeesPeriod;
-        // Contract addresses (fits in 3 slots)
         address usdcToken;
         address feeDistributor;
         address nodeRegistry;
         address payerReportManager;
-        // Mappings and dynamic sets (each starts at its own storage slot)
         mapping(address => Payer) payers;
         mapping(address => Withdrawal) withdrawals;
         EnumerableSet.AddressSet activePayers;
diff --git a/src/interfaces/IPayerRegistry.sol b/src/interfaces/IPayerRegistry.sol
index c855604..12296ac 100644
--- a/src/interfaces/IPayerRegistry.sol
+++ b/src/interfaces/IPayerRegistry.sol
@@ -86,9 +86,6 @@ interface IPayerRegistryErrors {
     /// @notice Error thrown when removing a debtor has failed.
     error FailedToRemoveDebtor();
 
-    /// @dev Error thrown when an address is invalid (usually zero address).
-    error InvalidAddress();
-
     /// @dev Error thrown when the amount is insufficient.
     error InsufficientAmount();
 
@@ -98,6 +95,9 @@ interface IPayerRegistryErrors {
     /// @dev Error thrown when insufficient time has passed since the last fee transfer.
     error InsufficientTimePassed();
 
+    /// @dev Error thrown when an address is invalid (usually zero address).
+    error InvalidAddress();
+
     /// @dev Error thrown when contract is not the fee distributor.
     error InvalidFeeDistributor();
 

From d9eb59a624ea546d0ec2220b5ab7a23871ba49bd Mon Sep 17 00:00:00 2001
From: Borja Aranda <borja@ephemerahq.com>
Date: Thu, 20 Mar 2025 11:05:17 +0100
Subject: [PATCH 7/7] missing nonreentrants

---
 src/PayerRegistry.sol | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/PayerRegistry.sol b/src/PayerRegistry.sol
index 7fd66cc..e3e705e 100644
--- a/src/PayerRegistry.sol
+++ b/src/PayerRegistry.sol
@@ -159,9 +159,8 @@ contract PayerRegistry is
     /**
      * @inheritdoc IPayerRegistry
      */
-    function deposit(address payer, uint64 amount) external whenNotPaused {
+    function deposit(address payer, uint64 amount) external whenNotPaused nonReentrant {
         _revertIfPayerDoesNotExist(payer);
-
         _validateAndProcessDeposit(msg.sender, payer, amount);
     }