SwitchboxFactory

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/SwitchboxFactory.sol": {
      "content": "// SWITCHBOX® Proprietary Licence
// Copyright © 2025. All rights reserved.
//
// This software is proprietary and confidential.
// No use, reproduction, distribution, or modification is permitted
// without prior written permission from the owner.
//
// Contact: contact@theswitch.box
// Jurisdiction: Singapore
//
// Full licence terms: See LICENSE.txt in the root of this repository.
// SPDX-License-Identifier: LicenseRef-SWITCHBOX
pragma solidity ^0.8.20;

import "./Switchbox.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title SwitchboxFactory
 * @author Anthony Oparaocha
 * @notice A factory contract for deploying and tracking instances of the Switchbox contract.
 * @dev This factory allows users to easily deploy their own personal Switchbox contract
 *      with a one-time fee payment in USDC tokens. The factory maintains a registry
 *      of all deployed contracts and provides querying capabilities for inactive switches.
 *
 * Key Features:
 * - Deploy new Switchbox contracts with configurable inactivity thresholds
 * - Automatic fee distribution to platform addresses
 * - Comprehensive tracking of deployed contracts
 * - Query functions for inactive and claimable switches
 * - Role-based access control for future extensibility
 *
 * Fee Structure:
 * - Flat fee: 50 USDC per Switchbox deployment
 * - Distribution: 50% to platform1, 30% to platform2, 20% to platform3
 */
contract SwitchboxFactory {
    using SafeERC20 for IERC20;

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Constants                              ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Flat fee in USDC (with 6 decimals) required to create a Switchbox
    /// @dev Fee is collected in USDC tokens and distributed to platform addresses
    uint256 public constant flatFee = 50 * 10 ** 6; // 50 USDC with 6 decimals

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                              Events                                ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Emitted when a new Switchbox contract is created
    /// @param owner The address that will own the new Switchbox
    /// @param contractAddress The address of the newly deployed Switchbox contract
    /// @param inactivityThreshold The inactivity period in seconds for the new contract
    /// @dev This event is emitted after successful deployment and fee distribution
    event SwitchBoxCreated(
        address indexed owner,
        address indexed contractAddress,
        uint256 inactivityThreshold
    );

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                           State Variables                          ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Array containing addresses of all deployed Switchbox contracts
    /// @dev This array maintains the complete registry of all deployed contracts
    /// @dev Used for iteration and querying purposes
    address[] public allPermitSwitches;

    /// @notice Mapping from user address to array of their deployed Switchbox contracts
    /// @dev Provides efficient lookup of contracts by owner
    mapping(address => address[]) public userPermitSwitches;

    /// @notice Mapping from user address to array of their deployed Switchbox contracts
    /// @dev Provides the latest switchbox by any user
    mapping(address => address) public userLastSwitchBox;

    /// @notice The oracle address used for Switchbox contracts
    /// @dev This oracle can ping Switchbox contracts on behalf of owners
    address public immutable oracle;

    /// @notice The USDC token contract address used for fee payments
    /// @dev Immutable address that cannot be changed after deployment
    /// @dev All deployment fees are paid in this token
    address public immutable usdcToken;

    /// @notice First platform fee recipient address (receives 50% of fees)
    /// @dev Immutable address that receives the largest portion of deployment fees
    /// @dev Used for platform maintenance and development costs
    address public immutable platformFeeAddress1 =
        0x6F8b4F05434DF0F1aF51f3e27A34b36dC8FbBAc3;

    /// @notice Second platform fee recipient address (receives 30% of fees)
    /// @dev Immutable address that receives the second portion of deployment fees
    /// @dev Used for platform operations and support costs
    address public immutable platformFeeAddress2 =
        0x26f7956Aef04a93e36baf1aD296A103229E227A0;

    /// @notice Third platform fee recipient address (receives remaining 20% of fees)
    /// @dev Immutable address that receives the final portion of deployment fees
    /// @dev Used for platform growth and expansion costs
    address public immutable platformFeeAddress3 =
        0x2112c5647EA653B224F1d174B39da3eB0D7005aA;

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Constructor                            ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Constructs the SwitchboxFactory contract
    /// @param _usdcToken The address of the USDC token contract for fee collection
    /// @param _oracleAddress The address of the oracle for auto-ping
    /// @dev Sets up the factory with the USDC token address and initializes state
    constructor(address _usdcToken, address _oracleAddress) {
        require(
            _usdcToken != address(0),
            "SwitchboxFactory: Invalid USDC token address"
        );
        require(
            _oracleAddress != address(0),
            "SwitchboxFactory: Invalid Oracle address"
        );
        usdcToken = _usdcToken;
        oracle = _oracleAddress;
    }

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                           External Functions                       ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Deploys a new Switchbox contract for the specified owner
    /// @param _inactivityThreshold The inactivity period in seconds for the new contract
    /// @return The address of the newly created Switchbox contract
    /// @dev Requires payment of flatFee in USDC tokens from the caller
    /// @dev Each user can only create one Switchbox contract (enforced by factory)
    /// @dev Automatically distributes fees to three platform addresses: 50%, 30%, and 20%
    /// @dev Emits SwitchBoxCreated event upon successful deployment
    /// @dev Adds the new contract to tracking arrays for future queries
    function createSwitchBox(
        uint256 _inactivityThreshold
    ) external returns (address) {
        address _owner = msg.sender;
        if (userPermitSwitches[_owner].length > 0) {
            address lastSwitchBox = userLastSwitchBox[_owner];
            require(
                Switchbox(lastSwitchBox).isDeleted(),
                "Last One Still Active"
            );
        }

        // Collect the deployment fee
        IERC20(usdcToken).safeTransferFrom(msg.sender, address(this), flatFee);

        // Deploy the new Switchbox contract
        Switchbox newSwitch = new Switchbox(
            _owner,
            _inactivityThreshold,
            oracle
        );
        address newContractAddress = address(newSwitch);

        // Distribute fees to platform addresses
        uint256 fee1 = (flatFee * 50) / 100;
        uint256 fee2 = (flatFee * 30) / 100;
        uint256 fee3 = flatFee - fee1 - fee2;

        IERC20(usdcToken).safeTransfer(platformFeeAddress1, fee1);
        IERC20(usdcToken).safeTransfer(platformFeeAddress2, fee2);
        IERC20(usdcToken).safeTransfer(platformFeeAddress3, fee3);

        // Track the new contract
        userLastSwitchBox[_owner] = newContractAddress;
        allPermitSwitches.push(newContractAddress);
        userPermitSwitches[_owner].push(newContractAddress);

        emit SwitchBoxCreated(_owner, newContractAddress, _inactivityThreshold);
        return newContractAddress;
    }

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Getter Functions                       ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Retrieves all Switchbox contracts deployed by a specific user
    /// @param _user The address of the user to query
    /// @return An array of addresses of Switchbox contracts owned by the user
    /// @dev Returns an empty array if the user has no deployed contracts
    /// @dev Useful for frontend applications to display user's contracts
    function getSwitchesByUser(
        address _user
    ) external view returns (address[] memory) {
        return userPermitSwitches[_user];
    }

    /// @notice Retrieves all inactive Switchbox contracts (both claimed and unclaimed)
    /// @return An array of addresses of inactive Switchbox contracts
    /// @dev An inactive contract is one where the inactivity threshold has been exceeded
    /// @dev This includes contracts that have already been claimed and those that are still claimable
    /// @dev Uses a two-pass approach for gas efficiency: first count, then populate
    function getInactiveSwitches() external view returns (address[] memory) {
        uint256 inactiveCount = 0;

        // First pass: count inactive switches
        for (uint256 i = 0; i < allPermitSwitches.length; i++) {
            if (
                Switchbox(allPermitSwitches[i]).isInactive() &&
                !Switchbox(allPermitSwitches[i]).isDeleted()
            ) {
                inactiveCount++;
            }
        }

        // Create array with exact size needed
        address[] memory inactiveSwitches = new address[](inactiveCount);
        uint256 currentIndex = 0;

        // Second pass: populate array with inactive switches
        for (uint256 i = 0; i < allPermitSwitches.length; i++) {
            if (
                Switchbox(allPermitSwitches[i]).isInactive() &&
                !Switchbox(allPermitSwitches[i]).isDeleted()
            ) {
                inactiveSwitches[currentIndex] = allPermitSwitches[i];
                currentIndex++;
            }
        }

        return inactiveSwitches;
    }

    /// @notice Retrieves all inactive Switchbox contracts that are still claimable
    /// @return An array of addresses of inactive and unclaimed Switchbox contracts
    /// @dev This function returns only contracts that are inactive but haven't been claimed yet
    /// @dev Useful for watchdogs to identify contracts that need attention
    /// @dev Uses a two-pass approach for gas efficiency: first count, then populate
    function getInactiveClaimableSwitches()
        external
        view
        returns (address[] memory)
    {
        uint256 inactiveCount = 0;

        // First pass: count inactive and unclaimed switches
        for (uint256 i = 0; i < allPermitSwitches.length; i++) {
            if (
                Switchbox(allPermitSwitches[i]).isInactive() &&
                !Switchbox(allPermitSwitches[i]).isClaimed() &&
                !Switchbox(allPermitSwitches[i]).isDeleted()
            ) {
                inactiveCount++;
            }
        }

        // Create array with exact size needed
        address[] memory inactiveClaimableSwitches = new address[](
            inactiveCount
        );
        uint256 currentIndex = 0;

        // Second pass: populate array with inactive and unclaimed switches
        for (uint256 i = 0; i < allPermitSwitches.length; i++) {
            if (
                Switchbox(allPermitSwitches[i]).isInactive() &&
                !Switchbox(allPermitSwitches[i]).isClaimed() &&
                !Switchbox(allPermitSwitches[i]).isDeleted()
            ) {
                inactiveClaimableSwitches[currentIndex] = allPermitSwitches[i];
                currentIndex++;
            }
        }

        return inactiveClaimableSwitches;
    }

    /// @notice Retrieves the total number of deployed Switchbox contracts
    /// @return The count of all deployed Switchbox contracts
    /// @dev This is a simple getter that returns the length of the allPermitSwitches array
    /// @dev Useful for analytics and monitoring purposes
    function totalSwitches() external view returns (uint256) {
        return allPermitSwitches.length;
    }
}
"
    },
    "src/Switchbox.sol": {
      "content": "// SWITCHBOX® Proprietary Licence
// Copyright © 2025. All rights reserved.
//
// This software is proprietary and confidential.
// No use, reproduction, distribution, or modification is permitted
// without prior written permission from the owner.
//
// Contact: contact@theswitch.box
// Jurisdiction: Singapore
//
// Full licence terms: See LICENSE.txt in the root of this repository.
// SPDX-License-Identifier: LicenseRef-SWITCHBOX
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title Switchbox Asset Inheritance
 * @author Anthony Oparaocha
 * @dev This contract allows an owner to designate beneficiaries who can claim specified
 * ERC20 tokens if the owner fails to check in (ping) within a set inactivity period.
 * Assets can be allocated as percentages of the owner's balance at claim time.
 *
 * Key Features:
 * - Global beneficiary management with percentage-based distribution
 * - Automatic fee distribution to platform addresses
 * - Watchdog reward system for claim initiators
 * - Oracle-based ping functionality for automated activity detection
 * - Snapshot-based balance distribution for fair allocation
 */
contract Switchbox is ReentrancyGuard {
    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Structs                                ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /**
     * @notice Represents a beneficiary who can claim assets after the owner becomes inactive
     * @param beneficiaryAddress The address that will receive the allocated tokens
     * @param percentage The percentage of the owner's balance to allocate (in basis points, where 10000 = 100%)
     */
    struct Beneficiary {
        address beneficiaryAddress;
        uint256 percentage;
    }

    /**
     * @notice Represents an asset that can be claimed by beneficiaries.
     * @param tokenAddress The address of the ERC20 token contract.
     * @param snapshotBalance The balance snapshot taken at the time of first claim attempt.
     */
    struct Asset {
        address tokenAddress;
        uint256 snapshotBalance;
    }

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                            State Variables                         ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //
    using SafeERC20 for IERC20;

    /// @notice Array of global beneficiaries who can claim assets when the owner becomes inactive
    Beneficiary[] public globalBeneficiaries;

    /// @notice The owner of this Switchbox contract who can add assets and ping to stay active
    address public immutable owner;

    /// @notice The oracle address that can ping on behalf of the owner when detecting wallet activity
    address public oracle;

    /// @notice First platform fee recipient address (receives 50% of platform fees)
    address public immutable platformFeeAddress1 =
        0x6F8b4F05434DF0F1aF51f3e27A34b36dC8FbBAc3;

    /// @notice Second platform fee recipient address (receives 30% of platform fees)
    address public immutable platformFeeAddress2 =
        0x26f7956Aef04a93e36baf1aD296A103229E227A0;

    /// @notice Third platform fee recipient address (receives remaining 20% of platform fees)
    address public immutable platformFeeAddress3 =
        0x2112c5647EA653B224F1d174B39da3eB0D7005aA;

    /// @notice Platform fee in basis points (1.47% of claimed amounts)
    uint256 public constant PLATFORM_FEE_BPS = 147; // 1.47%

    /// @notice Watchdog reward in basis points (0.03% of claimed amounts for the claimer)
    uint256 public constant WATCHDOG_REWARD_BPS = 3; // 0.03%

    /// @notice Denominator for fee calculations (10000 = 100%)
    uint256 public constant FEE_DENOMINATOR = 10000;

    /// @notice Denominator for percentage calculations (10000 = 100%)
    uint256 public constant PERCENTAGE_DENOMINATOR = 10000; // 100% = 10000 basis points

    /// @notice The inactivity threshold in seconds after which assets can be claimed
    uint256 public immutable inactivityThreshold;

    /// @notice Timestamp of the last ping (owner activity)
    uint256 public lastPingTimestamp;

    /// @notice Flag indicating whether assets have been claimed from this contract
    bool public isClaimed;

    /// @notice Flag indicating whether switchBox is deleted or not
    bool public isDeleted;

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Mappings                               ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Mapping from token address to Asset struct containing asset configuration
    /// @dev Maps ERC20 token addresses to their corresponding Asset data
    mapping(address => Asset) public assets;

    /// @notice Array of all token addresses that have been added as assets
    /// @dev Used for iteration during batch claiming operations
    address[] public trackedAssets;

    /// @notice Mapping to track which tokens are enabled for use in the contract
    /// @dev Only enabled tokens can be added as assets or claimed by beneficiaries
    mapping(address => bool) public enabledTokens;

    /// @notice Array of all enabled token addresses for enumeration
    /// @dev Private array used internally to track enabled tokens for getter functions
    address[] private enabledTokenList;

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                            Events                                  ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Emitted when the owner or oracle pings the contract to reset the inactivity timer
    /// @param timestamp The block timestamp when the ping occurred
    /// @param _inactivityThreshold The inactivityThreshold of the Current SwitchBox
    event Pinged(uint256 timestamp, uint256 _inactivityThreshold);

    /// @notice Emitted when the oracle address is updated
    /// @param oracle The new oracle address
    event OracleUpdated(address oracle);

    /// @notice Emitted when the switchbox is deleted
    /// @param owner The owner address
    /// @param time The deletion timestamp
    event SwitchDeleted(address owner, uint256 time);

    /// @notice Emitted when a beneficiary successfully claims their share of an asset
    /// @param beneficiary The address of the beneficiary who received the tokens
    /// @param token The address of the ERC20 token that was claimed
    /// @param amount The total amount of tokens that were processed (before fees)
    /// @param percentage The beneficiary's percentage share in basis points
    /// @param watchdog The address of the account that initiated the claim (receives watchdog reward)
    event AssetClaimed(
        address indexed beneficiary,
        address indexed token,
        uint256 amount,
        uint256 percentage,
        address indexed watchdog
    );

    /// @notice Emitted when a token's enabled status is changed by the owner
    /// @param token The address of the ERC20 token whose status was changed
    /// @param enabled True if the token was enabled, false if disabled
    event TokenEnabled(address indexed token, bool enabled);

    /// @notice Emitted when the global beneficiaries list is updated
    /// @param beneficiaries Array of beneficiary addresses that were set
    /// @param percentages Array of percentage allocations in basis points corresponding to each beneficiary
    event GlobalBeneficiariesSet(address beneficiaries, uint256 percentages);

    /// @notice Emitted when a new asset is added to the contract for inheritance
    /// @param token The address of the ERC20 token that was added as an asset
    event AssetAdded(address indexed token);

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Modifiers                              ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Restricts function access to only the contract owner
    /// @dev Reverts with error if the caller is not the owner
    modifier onlyOwner() {
        require(msg.sender == owner, "Switchbox: Caller is not the owner");
        _;
    }

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Constructor                            ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Initializes the Switchbox contract with the specified parameters
    /// @dev Sets up the initial state including owner, inactivity threshold, oracle, and emits the first ping
    /// @param _inactivityThreshold The time in seconds after which the contract is considered inactive (must be > 0)
    /// @param _oracle The address of the oracle that can ping the contract (can be zero address initially)
    constructor(address _owner, uint256 _inactivityThreshold, address _oracle) {
        require(
            _inactivityThreshold > 0,
            "Switchbox: Inactivity threshold must be greater than 0"
        );
        require(
            _oracle != address(0),
            "Switchbox: Oracle Address Can Not Be Zero"
        );
        require(
            _owner != address(0),
            "Switchbox: Owner Address Can Not Be Zero"
        );
        inactivityThreshold = _inactivityThreshold;
        lastPingTimestamp = block.timestamp;
        owner = _owner;
        oracle = _oracle;
        emit Pinged(block.timestamp, inactivityThreshold);
    }

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Main Functions                         ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Updates the last ping timestamp to reset the inactivity timer
    /// @dev Can only be called by the contract owner or the designated oracle
    /// @dev Emits a Pinged event with the current timestamp
    function ping() public {
        require(
            msg.sender == owner || msg.sender == oracle,
            "Switchbox: Caller is not owner or oracle"
        );
        require(!isDeleted, "Switchbox: No Longer Exist");
        require(!isClaimed, "Switchbox: Assets have already been claimed");

        lastPingTimestamp = block.timestamp;
        emit Pinged(lastPingTimestamp, inactivityThreshold);
    }

    /// @notice Delete the complete switchbox, this will disable all functions
    /// @dev Can only be called by the contract owner
    /// @dev Emits a Delete event with the current timestamp and the owner
    function deleteSwitch() public onlyOwner {
        require(!isDeleted, "Switchbox: No Longer Exist");

        isDeleted = true;
        emit SwitchDeleted(owner, block.timestamp);
    }

    /// @notice Sets the oracle address that can ping on behalf of the owner
    /// @dev Can only be called by the owner or current oracle
    /// @param _oracle The new oracle address
    function setOracleAddress(address _oracle) public {
        require(
            msg.sender == owner || msg.sender == oracle,
            "Switchbox: Caller is not owner or oracle"
        );
        require(
            _oracle != address(0),
            "Switchbox: Oracle Address Can Not Be Zero"
        );
        require(!isClaimed, "Switchbox: Assets have already been claimed");
        require(!isDeleted, "Switchbox: No Longer Exist");
        ping();
        oracle = _oracle;
        emit OracleUpdated(oracle);
    }

    /// @notice Enables or disables a token for use in the contract
    /// @dev When disabling a token, also removes any associated assets and cleans up tracking arrays
    /// @dev Automatically calls ping() to reset the inactivity timer
    /// @param _tokenAddress The address of the ERC20 token to enable/disable (cannot be zero address)
    /// @param _enabled True to enable the token, false to disable it
    function enableToken(
        address _tokenAddress,
        bool _enabled
    ) public onlyOwner {
        require(
            _tokenAddress != address(0),
            "Switchbox: Token address cannot be zero"
        );
        require(!isClaimed, "Switchbox: Assets have already been claimed");
        require(!isDeleted, "Switchbox: No Longer Exist");

        bool wasEnabled = enabledTokens[_tokenAddress];
        if (_enabled == wasEnabled) return; // No change needed

        enabledTokens[_tokenAddress] = _enabled;

        if (_enabled) {
            // Add to list if not present
            bool exists = false;
            for (uint i = 0; i < enabledTokenList.length; i++) {
                if (enabledTokenList[i] == _tokenAddress) {
                    exists = true;
                    break;
                }
            }
            if (!exists) {
                enabledTokenList.push(_tokenAddress);
            }
        } else {
            for (uint i = 0; i < enabledTokenList.length; i++) {
                if (enabledTokenList[i] == _tokenAddress) {
                    enabledTokenList[i] = enabledTokenList[
                        enabledTokenList.length - 1
                    ];
                    enabledTokenList.pop();
                    break;
                }
            }

            if (assets[_tokenAddress].tokenAddress != address(0)) {
                // Check if asset exists
                delete assets[_tokenAddress];

                // Remove from trackedAssets if present
                for (uint i = 0; i < trackedAssets.length; i++) {
                    if (trackedAssets[i] == _tokenAddress) {
                        trackedAssets[i] = trackedAssets[
                            trackedAssets.length - 1
                        ];
                        trackedAssets.pop();
                        break;
                    }
                }
            }
        }
        ping();
        emit TokenEnabled(_tokenAddress, _enabled);
    }

    /// @notice Sets the global beneficiaries who will inherit assets when the contract becomes inactive
    /// @dev Replaces all existing global beneficiaries with the new set
    /// @dev Percentages must sum to exactly 100% (10000 basis points)
    /// @dev Automatically calls ping() to reset the inactivity timer
    /// @param _beneficiaries Array of beneficiary addresses (must not contain zero addresses)
    /// @param _percentages Array of percentages in basis points (must be > 0 and sum to 10000)
    function setGlobalBeneficiaries(
        address[] calldata _beneficiaries,
        uint256[] calldata _percentages
    ) external onlyOwner {
        require(
            _beneficiaries.length > 0,
            "Switchbox: Must have at least one beneficiary"
        );
        require(
            _beneficiaries.length == _percentages.length,
            "Switchbox: Beneficiaries and percentages arrays must have same length"
        );
        require(!isClaimed, "Switchbox: Assets have already been claimed");
        require(!isDeleted, "Switchbox: No Longer Exist");

        uint256 totalPercentage = 0;
        for (uint i = 0; i < _beneficiaries.length; i++) {
            require(
                _beneficiaries[i] != address(0),
                "Switchbox: Beneficiary address cannot be zero"
            );
            require(
                _percentages[i] > 0,
                "Switchbox: Percentage must be greater than 0"
            );
            totalPercentage += _percentages[i];
        }
        require(
            totalPercentage == PERCENTAGE_DENOMINATOR,
            "Switchbox: Percentages must sum to 100% (10000 basis points)"
        ); // Must equal 100%

        // Clear existing global beneficiaries and add new ones
        delete globalBeneficiaries;
        for (uint i = 0; i < _beneficiaries.length; i++) {
            globalBeneficiaries.push(
                Beneficiary({
                    beneficiaryAddress: _beneficiaries[i],
                    percentage: _percentages[i]
                })
            );
            emit GlobalBeneficiariesSet(_beneficiaries[i], _percentages[i]);
        }
        ping();
    }

    /// @notice Adds multiple assets (tokens) to the contract for inheritance
    /// @dev Global beneficiaries must be set before adding assets
    /// @dev Automatically calls ping() to reset the inactivity timer
    /// @param _tokenAddresses Array of ERC20 token addresses (must be enabled and non-zero)
    function addAssets(address[] calldata _tokenAddresses) external onlyOwner {
        require(
            _tokenAddresses.length > 0,
            "Switchbox: Must provide at least one token address"
        );
        require(!isClaimed, "Switchbox: Assets have already been claimed");
        require(!isDeleted, "Switchbox: No Longer Exist");

        for (uint256 i = 0; i < _tokenAddresses.length; i++) {
            address _tokenAddress = _tokenAddresses[i];
            require(
                _tokenAddress != address(0),
                "Switchbox: Token address cannot be zero"
            );
            require(
                assets[_tokenAddress].tokenAddress == address(0),
                "Switchbox: Asset already exists for this token"
            ); // Asset must not exist

            if (!enabledTokens[_tokenAddress]) {
                enableToken(_tokenAddress, true); //enable token if not already
            }

            // Create new Asset struct
            Asset storage asset = assets[_tokenAddress];
            asset.tokenAddress = _tokenAddress;

            // Add to tracked assets
            trackedAssets.push(_tokenAddress);

            emit AssetAdded(_tokenAddress);
        }

        ping();
    }

    /// @notice Claims all tracked assets and distributes them to global beneficiaries
    /// @dev Can only be called when the contract is inactive (past inactivity threshold)
    /// @dev Can only be called once per contract (prevents double-claiming)
    /// @dev Processes all tracked assets in a single transaction for gas efficiency
    /// @dev Takes snapshot of balances at claim time for fair distribution
    /// @dev Removes all processed assets from tracking after successful claims
    /// @dev Uses nonReentrant modifier to prevent reentrancy attacks
    function claimBatch() external nonReentrant {
        require(isInactive(), "Switchbox: Contract is not inactive yet");
        require(!isClaimed, "Switchbox: Assets have already been claimed");
        require(!isDeleted, "Switchbox: No Longer Exist");
        require(
            globalBeneficiaries.length > 0,
            "Switchbox: No global beneficiaries set"
        );
        require(
            trackedAssets.length > 0,
            "Switchbox: No assets available to claim"
        );

        uint256 i = 0;
        isClaimed = true;
        isDeleted = true;

        while (i < trackedAssets.length) {
            address tokenAddress = trackedAssets[i];
            Asset storage asset = assets[tokenAddress];
            if (
                asset.tokenAddress != address(0) && // Check if asset exists
                globalBeneficiaries.length > 0 && // Must have global beneficiaries
                enabledTokens[tokenAddress] // Only allow claims from enabled tokens
            ) {
                asset.snapshotBalance = IERC20(asset.tokenAddress).balanceOf(
                    self()
                );

                uint256 allowance = IERC20(asset.tokenAddress).allowance(
                    self(),
                    address(this)
                );

                // Process each global beneficiary using snapshot balance
                for (uint j = 0; j < globalBeneficiaries.length; j++) {
                    Beneficiary memory beneficiary = globalBeneficiaries[j];
                    uint256 amountClaimed = _safeTransferToBeneficiary(
                        asset,
                        beneficiary,
                        msg.sender,
                        allowance
                    );
                    emit AssetClaimed(
                        beneficiary.beneficiaryAddress,
                        asset.tokenAddress,
                        amountClaimed,
                        beneficiary.percentage,
                        msg.sender
                    );
                }
            }

            // Remove this token from trackedAssets
            trackedAssets[i] = trackedAssets[trackedAssets.length - 1];
            trackedAssets.pop();
        }
    }

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                             Getter Functions                       ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Get all enabled token addresses that can be claimed from this contract.
    /// @return Array of enabled token addresses.
    function getEnabledTokens() external view returns (address[] memory) {
        return enabledTokenList;
    }

    /// @dev Returns the owner's address.
    function self() private view returns (address) {
        return owner;
    }

    /// @notice Get the global beneficiaries for the contract.
    /// @return beneficiaryAddresses Array of beneficiary addresses.
    /// @return percentages Array of percentages in basis points.
    function getBeneficiaries()
        external
        view
        returns (
            address[] memory beneficiaryAddresses,
            uint256[] memory percentages
        )
    {
        uint256 beneficiaryCount = globalBeneficiaries.length;

        beneficiaryAddresses = new address[](beneficiaryCount);
        percentages = new uint256[](beneficiaryCount);

        for (uint i = 0; i < beneficiaryCount; i++) {
            beneficiaryAddresses[i] = globalBeneficiaries[i].beneficiaryAddress;
            percentages[i] = globalBeneficiaries[i].percentage;
        }
    }

    /// @notice Check if the contract is inactive based on the last ping timestamp.
    /// @dev A contract is considered inactive if the current timestamp exceeds
    ///      the last ping timestamp plus the inactivity threshold.
    /// @return True if the contract is inactive, false otherwise.
    function isInactive() public view returns (bool) {
        return block.timestamp >= lastPingTimestamp + inactivityThreshold;
    }

    // ╔════════════════════════════════════════════════════════════════════╗ //
    // ║                           Internal Functions                       ║ //
    // ╚════════════════════════════════════════════════════════════════════╝ //

    /// @notice Safely transfers tokens to a beneficiary with fee deductions.
    /// @dev Calculates the transfer amount based on snapshot balance or allowance,
    ///      deducts platform fees and watchdog rewards, then transfers the remaining
    ///      amount to the beneficiary. Returns 0 if the calculated amount is zero.
    /// @param _asset The asset storage reference containing token and snapshot data
    /// @param _beneficiary The beneficiary information including address and percentage
    /// @param _watchdog The address to receive the watchdog reward
    /// @param allowance The maximum amount allowed to be transferred
    /// @return The total amount transferred (including fees)
    function _safeTransferToBeneficiary(
        Asset storage _asset,
        Beneficiary memory _beneficiary,
        address _watchdog,
        uint256 allowance
    ) private returns (uint256) {
        address ownerAddress = self();
        uint256 currentBalance = IERC20(_asset.tokenAddress).balanceOf(
            ownerAddress
        );
        uint256 amountToTransfer;

        if (_asset.snapshotBalance > allowance) {
            amountToTransfer =
                (allowance * _beneficiary.percentage) /
                PERCENTAGE_DENOMINATOR;
        } else {
            amountToTransfer =
                (_asset.snapshotBalance * _beneficiary.percentage) /
                PERCENTAGE_DENOMINATOR;
        }

        if (amountToTransfer > currentBalance) {
            amountToTransfer = currentBalance;
        }

        if (
            amountToTransfer >
            IERC20(_asset.tokenAddress).allowance(self(), address(this))
        ) {
            amountToTransfer = IERC20(_asset.tokenAddress).allowance(
                self(),
                address(this)
            );
        }

        if (amountToTransfer == 0) {
            return 0;
        }

        uint256 platformFee = (amountToTransfer * PLATFORM_FEE_BPS) /
            FEE_DENOMINATOR;
        uint256 watchdogReward = (amountToTransfer * WATCHDOG_REWARD_BPS) /
            FEE_DENOMINATOR;
        uint256 totalFees = platformFee + watchdogReward;
        uint256 toBeneficiary = amountToTransfer - totalFees;

        if (watchdogReward > 0) {
            try
                IERC20(_asset.tokenAddress).transferFrom(
                    ownerAddress,
                    _watchdog,
                    watchdogReward
                )
            returns (bool ok) {
                if (!ok) {
                    watchdogReward = 0;
                }
            } catch {
                watchdogReward = 0;
            }
        }

        if (platformFee > 0) {
            try
                IERC20(_asset.tokenAddress).transferFrom(
                    ownerAddress,
                    address(this), // collect platform fee here
                    platformFee
                )
            returns (bool ok) {
                if (!ok) {
                    platformFee = 0;
                }
                _distributeFees(_asset.tokenAddress, platformFee);
            } catch {
                platformFee = 0;
            }
        }

        if (toBeneficiary > 0) {
            try
                IERC20(_asset.tokenAddress).transferFrom(
                    ownerAddress,
                    _beneficiary.beneficiaryAddress,
                    toBeneficiary
                )
            returns (bool ok) {
                if (!ok) {
                    toBeneficiary = 0;
                }
            } catch {
                toBeneficiary = 0;
            }
        }
        amountToTransfer = toBeneficiary + platformFee + watchdogReward;
        return amountToTransfer;
    }

    /// @notice Distributes platform fees among three fee addresses.
    /// @dev Splits the platform fee into three parts: 50%, 30%, and the remainder.
    ///      Transfers each portion to the respective platform fee addresses.
    /// @param tokenAddress The token contract address for the fee transfers
    /// @param platformFee The total platform fee amount to distribute
    function _distributeFees(
        address tokenAddress,
        uint256 platformFee
    ) private {
        uint256 fee1 = (platformFee * 50) / 100;
        uint256 fee2 = (platformFee * 30) / 100;
        uint256 fee3 = platformFee - fee1 - fee2;
        IERC20(tokenAddress).safeTransfer(platformFeeAddress1, fee1);
        IERC20(tokenAddress).safeTransfer(platformFeeAddress2, fee2);
        IERC20(tokenAddress).safeTransfer(platformFeeAddress3, fee3);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.20;

import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
 * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
 * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
 * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * @custom:oz-upgrades-unsafe-allow state-variable-immutable
 */
abstract contract EIP712 is IERC5267 {
    using ShortStrings for *;

    bytes32 private constant TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyin

Tags:
ERC20, ERC165, Multisig, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x10cd4140179617a0ad210e09c88f9a04af21a5da|verified:true|block:23531353|tx:0xb6b07245f1a29fdafce9e5ecb6d66863c7a94bd0b5d742620c31532aa0bb2714|first_check:1759913523

Submitted on: 2025-10-08 10:52:03

Comments

Log in to comment.

No comments yet.