ThyraFactory

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/ThyraFactory.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {Create2} from "openzeppelin-contracts/contracts/utils/Create2.sol";
import {SafeProxyFactory, SafeProxy} from "safe-smart-account/contracts/proxies/SafeProxyFactory.sol";
import {SafeHelpers} from "./Libraries/SafeHelpers.sol";
import {ThyraDiamond} from "./ThyraDiamond.sol";
import {IOwnershipFacet} from "./Interfaces/IOwnershipFacet.sol";

/// @title ThyraFactory
/// @author ThyraWallet Team
/// @notice Factory contract for deploying Thyra Accounts (Safe + ThyraDiamond module)
/// @dev All facet implementations should be pre-deployed and shared across all Diamond instances
///      This follows the standard Diamond proxy pattern where facets are reusable implementations
/// @custom:version 1.0.0
contract ThyraFactory is ReentrancyGuard {
    /// @notice Version of the factory
    string public constant VERSION = "1.0";

    /// @notice Safe Proxy Factory address
    address public immutable SAFE_PROXY_FACTORY;

    /// @notice Safe Singleton implementation address
    address public immutable SAFE_SINGLETON;

    /// @notice Safe MultiSend contract address
    address public immutable SAFE_MULTI_SEND;

    /// @notice Safe fallback handler address
    address public immutable SAFE_FALLBACK_HANDLER;

    /// @notice Pre-deployed facet addresses shared by all Diamond instances
    address public immutable DIAMOND_CUT_FACET;
    address public immutable DIAMOND_LOUPE_FACET;
    address public immutable EXECUTOR_FACET;
    address public immutable OWNERSHIP_FACET;

    /// @notice ThyraRegistry address for ExecutorFacet instances
    address public immutable THYRA_REGISTRY;

    /// @notice Track deployment nonces for deterministic addresses
    mapping(bytes32 ownersHash => uint256 count) public ownerSafeCount;

    /// @notice Events
    event ThyraAccountDeployed(
        address indexed safeAddress, address indexed diamondAddress, address[] owners, uint256 threshold
    );
    event ThyraSubAccountDeployed(
        address indexed subAccount,
        address indexed diamond,
        address indexed parentSafe,
        address[] owners,
        uint256 threshold
    );

    /// @notice Errors
    error SafeProxyCreationFailed();
    error DiamondDeploymentFailed();
    error ModuleEnableFailed();
    error InvalidParentSafe();

    /// @notice Constructor - all facet addresses should be pre-deployed implementations
    /// @param _safeProxyFactory Address of Safe Proxy Factory
    /// @param _safeSingleton Address of Safe Singleton
    /// @param _safeMultiSend Address of Safe MultiSend
    /// @param _safeFallbackHandler Address of Safe Fallback Handler
    /// @param _diamondCutFacet Address of pre-deployed DiamondCutFacet implementation
    /// @param _diamondLoupeFacet Address of pre-deployed DiamondLoupeFacet implementation
    /// @param _executorFacet Address of pre-deployed ExecutorFacet implementation
    /// @param _ownershipFacet Address of pre-deployed OwnershipFacet implementation
    /// @param _thyraRegistry Address of ThyraRegistry for ExecutorFacet
    constructor(
        address _safeProxyFactory,
        address _safeSingleton,
        address _safeMultiSend,
        address _safeFallbackHandler,
        address _diamondCutFacet,
        address _diamondLoupeFacet,
        address _executorFacet,
        address _ownershipFacet,
        address _thyraRegistry
    ) {
        SAFE_PROXY_FACTORY = _safeProxyFactory;
        SAFE_SINGLETON = _safeSingleton;
        SAFE_MULTI_SEND = _safeMultiSend;
        SAFE_FALLBACK_HANDLER = _safeFallbackHandler;
        DIAMOND_CUT_FACET = _diamondCutFacet;
        DIAMOND_LOUPE_FACET = _diamondLoupeFacet;
        EXECUTOR_FACET = _executorFacet;
        OWNERSHIP_FACET = _ownershipFacet;
        THYRA_REGISTRY = _thyraRegistry;
    }

    /// @notice Deploy a new Thyra Account (Safe + ThyraDiamond module)
    /// @param _owners List of Safe owners
    /// @param _threshold Number of required confirmations
    /// @param _salt Salt for deterministic deployment
    /// @return _safe Address of deployed Safe wallet
    function deployThyraAccount(address[] calldata _owners, uint256 _threshold, bytes32 _salt)
        external
        nonReentrant
        returns (address _safe)
    {
        bytes32 ownersHash = keccak256(abi.encode(_owners));

        // 1. Deploy ThyraDiamond first (will be enabled as module)
        address diamond = _deployThyraDiamond(_salt, ownersHash);

        // 2. Deploy Safe proxy with inline initialization data
        _safe = _createSafe(_setupSafeWithModule(_owners, _threshold, diamond, false, address(0)), _owners, _salt);

        // 3. Initialize Diamond with factory and Safe address in one call
        IOwnershipFacet(diamond).initialize(address(this), _safe);

        // 4. Emit event
        emit ThyraAccountDeployed(_safe, diamond, _owners, _threshold);
    }

    /// @notice Deploy a new Thyra Sub Account with parent Safe as module
    /// @param _owners List of Safe owners
    /// @param _threshold Number of required confirmations
    /// @param _parentSafe Address of parent Safe that will be enabled as module
    /// @param _salt Salt for deterministic deployment
    /// @return _subAccount Address of deployed Sub Account Safe
    function deploySubAccount(address[] calldata _owners, uint256 _threshold, address _parentSafe, bytes32 _salt)
        external
        nonReentrant
        returns (address _subAccount)
    {
        // Validate parent Safe address
        if (_parentSafe == address(0)) revert InvalidParentSafe();

        // Note: We don't check if _parentSafe is valid because:
        // 1. An invalid _parentSafe brings no benefit to the sub account owner
        // 2. It's the caller's responsibility to provide a valid parent Safe
        // 3. An invalid module simply won't function, causing no harm

        bytes32 ownersHash = keccak256(abi.encode(_owners));

        // 1. Deploy ThyraDiamond first (will be enabled as module)
        address diamond = _deployThyraDiamond(_salt, ownersHash);

        // 2. Deploy Safe proxy with both modules (diamond + parent safe)
        _subAccount = _createSafe(_setupSafeWithModule(_owners, _threshold, diamond, true, _parentSafe), _owners, _salt);

        // 3. Initialize Diamond with factory and Safe address in one call
        IOwnershipFacet(diamond).initialize(address(this), _subAccount);

        // 4. Emit event
        emit ThyraSubAccountDeployed(_subAccount, diamond, _parentSafe, _owners, _threshold);
    }

    /// @notice Deploy a new ThyraDiamond contract using pre-deployed facets with nonce retry mechanism
    /// @param _salt Salt for deterministic deployment
    /// @param _ownersHash Hash of owners array for nonce generation
    /// @return diamond Address of deployed ThyraDiamond
    function _deployThyraDiamond(bytes32 _salt, bytes32 _ownersHash) private returns (address diamond) {
        // Deploy ThyraDiamond with pre-deployed shared facet addresses
        bytes memory creationCode = abi.encodePacked(
            type(ThyraDiamond).creationCode,
            abi.encode(
                address(this), // _contractOwner (temporary, will be transferred to Safe)
                DIAMOND_CUT_FACET, // _diamondCutFacet (shared implementation)
                DIAMOND_LOUPE_FACET, // _diamondLoupeFacet (shared implementation)
                EXECUTOR_FACET, // _executorFacet (shared implementation)
                OWNERSHIP_FACET // _ownershipFacet (shared implementation)
            )
        );

        // Try deployment with incremental nonce until successful (similar to Safe deployment)
        do {
            // Generate nonce using the same counter as Safe deployment
            uint256 nonce = ownerSafeCount[_ownersHash];
            
            // Generate salt for Create2 deployment with nonce
            bytes32 salt = keccak256(abi.encodePacked("ThyraDiamond", _salt, nonce, VERSION));

            // Compute the address that would be deployed
            address predictedAddress = Create2.computeAddress(salt, keccak256(creationCode), address(this));
            
            // Check if contract already exists at this address
            if (predictedAddress.code.length > 0) {
                // Address collision, increment nonce and retry
                ownerSafeCount[_ownersHash]++;
                continue;
            }
            
            // Deploy using Create2
            diamond = Create2.deploy(0, salt, creationCode);
            
            // Deployment successful, exit loop
            break;
        } while (ownerSafeCount[_ownersHash] < type(uint256).max);

        // Check deployment was successful
        if (diamond == address(0)) revert DiamondDeploymentFailed();
    }

    /// @notice Setup Safe initialization data with modules
    /// @param _owners List of Safe owners
    /// @param _threshold Number of required confirmations
    /// @param _diamond Address of ThyraDiamond to enable as module
    /// @param _isSubAccount Whether this is a sub account (needs parent Safe as module)
    /// @param _parentSafe Address of parent Safe (only used if isSubAccount is true)
    /// @return Encoded setup data for Safe proxy
    function _setupSafeWithModule(
        address[] memory _owners,
        uint256 _threshold,
        address _diamond,
        bool _isSubAccount,
        address _parentSafe
    ) private view returns (bytes memory) {
        // Determine number of transactions needed
        uint256 txnCount = _isSubAccount ? 2 : 1;
        SafeHelpers.Executable[] memory txns = new SafeHelpers.Executable[](txnCount);

        // Always enable ThyraDiamond as module
        txns[0] = SafeHelpers.Executable({
            callType: SafeHelpers.CallType.CALL,
            target: address(0), // Will be set to Safe address during setup
            value: 0,
            data: abi.encodeWithSignature("enableModule(address)", _diamond)
        });

        // If this is a sub account, also enable parent Safe as module
        if (_isSubAccount) {
            if (_parentSafe == address(0)) revert InvalidParentSafe();

            txns[1] = SafeHelpers.Executable({
                callType: SafeHelpers.CallType.CALL,
                target: address(0), // Will be set to Safe address during setup
                value: 0,
                data: abi.encodeWithSignature("enableModule(address)", _parentSafe)
            });
        }

        // Pack transactions for MultiSend
        bytes memory packedTxns = SafeHelpers.packMultisendTxns(txns);

        // Return Safe setup call
        return abi.encodeWithSignature(
            "setup(address[],uint256,address,bytes,address,address,uint256,address)",
            _owners, // _owners
            _threshold, // _threshold
            SAFE_MULTI_SEND, // to (MultiSend for setup)
            abi.encodeWithSignature( // data (MultiSend call)
            "multiSend(bytes)", packedTxns),
            SAFE_FALLBACK_HANDLER, // fallbackHandler
            address(0), // paymentToken (ETH)
            0, // payment
            payable(address(0)) // paymentReceiver
        );
    }

    /// @notice Create Safe proxy with nonce retry mechanism
    /// @param _initializer Safe setup data
    /// @param _owners List of owners for nonce calculation
    /// @param _salt User provided salt
    /// @return _safe Address of created Safe
    function _createSafe(bytes memory _initializer, address[] calldata _owners, bytes32 _salt)
        private
        returns (address _safe)
    {
        bytes32 ownersHash = keccak256(abi.encode(_owners));

        // Try deployment with incremental nonce until successful
        do {
            uint256 nonce = _genNonce(ownersHash, _salt);

            try SafeProxyFactory(SAFE_PROXY_FACTORY).createProxyWithNonce(SAFE_SINGLETON, _initializer, nonce) returns (
                SafeProxy deployedProxy
            ) {
                _safe = address(deployedProxy);
            } catch {
                // Nonce collision, will retry with incremented nonce
                // ownerSafeCount was already incremented in _genNonce
            }
        } while (_safe == address(0));

        if (_safe == address(0)) revert SafeProxyCreationFailed();
    }

    /// @notice Generate deterministic nonce for Safe deployment
    /// @param _ownersHash Hash of owners array
    /// @param _salt User provided salt
    /// @return Generated nonce
    function _genNonce(bytes32 _ownersHash, bytes32 _salt) private returns (uint256) {
        return uint256(keccak256(abi.encodePacked(_ownersHash, ownerSafeCount[_ownersHash]++, _salt, VERSION)));
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Create2.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}
"
    },
    "lib/safe-smart-account/contracts/proxies/SafeProxyFactory.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

import {SafeProxy} from "./SafeProxy.sol";

/**
 * @title Proxy Factory - Allows to create a new proxy contract and execute a message call to the new proxy within one transaction.
 * @author Stefan George - @Georgi87
 */
contract SafeProxyFactory {
    event ProxyCreation(SafeProxy indexed proxy, address singleton);
    event ProxyCreationL2(SafeProxy indexed proxy, address singleton, bytes initializer, uint256 saltNonce);
    event ChainSpecificProxyCreationL2(SafeProxy indexed proxy, address singleton, bytes initializer, uint256 saltNonce, uint256 chainId);

    /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.
    function proxyCreationCode() public pure returns (bytes memory) {
        return type(SafeProxy).creationCode;
    }

    /**
     * @notice Internal method to create a new proxy contract using CREATE2. Optionally executes an initializer call to a new proxy.
     * @param _singleton Address of singleton contract. Must be deployed at the time of execution.
     * @param initializer (Optional) Payload for a message call to be sent to a new proxy contract.
     * @param salt Create2 salt to use for calculating the address of the new proxy contract.
     * @return proxy Address of the new proxy contract.
     */
    function deployProxy(address _singleton, bytes memory initializer, bytes32 salt) internal returns (SafeProxy proxy) {
        require(isContract(_singleton), "Singleton contract not deployed");

        bytes memory deploymentData = abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
        /* solhint-disable no-inline-assembly */
        /// @solidity memory-safe-assembly
        assembly {
            proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
        }
        /* solhint-enable no-inline-assembly */
        require(address(proxy) != address(0), "Create2 call failed");

        if (initializer.length > 0) {
            /* solhint-disable no-inline-assembly */
            /// @solidity memory-safe-assembly
            assembly {
                if iszero(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0)) {
                    let ptr := mload(0x40)
                    returndatacopy(ptr, 0x00, returndatasize())
                    revert(ptr, returndatasize())
                }
            }
            /* solhint-enable no-inline-assembly */
        }
    }

    /**
     * @notice Deploys a new proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
     * @param _singleton Address of singleton contract. Must be deployed at the time of execution.
     * @param initializer Payload for a message call to be sent to a new proxy contract.
     * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
     */
    function createProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce) public returns (SafeProxy proxy) {
        // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatenating it
        bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
        proxy = deployProxy(_singleton, initializer, salt);
        emit ProxyCreation(proxy, _singleton);
    }

    /**
     * @notice Deploys a new proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
     * @dev Emits an extra event to allow tracking of `initializer` and `saltNonce`.
     * @param _singleton Address of singleton contract. Must be deployed at the time of execution.
     * @param initializer Payload for a message call to be sent to a new proxy contract.
     * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
     */
    function createProxyWithNonceL2(address _singleton, bytes memory initializer, uint256 saltNonce) public returns (SafeProxy proxy) {
        proxy = createProxyWithNonce(_singleton, initializer, saltNonce);
        emit ProxyCreationL2(proxy, _singleton, initializer, saltNonce);
    }

    /**
     * @notice Deploys a new chain-specific proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
     * @dev Allows to create a new proxy contract that should exist only on 1 network (e.g. specific governance or admin accounts)
     *      by including the chain id in the create2 salt. Such proxies cannot be created on other networks by replaying the transaction.
     * @param _singleton Address of singleton contract. Must be deployed at the time of execution.
     * @param initializer Payload for a message call to be sent to a new proxy contract.
     * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
     */
    function createChainSpecificProxyWithNonce(
        address _singleton,
        bytes memory initializer,
        uint256 saltNonce
    ) public returns (SafeProxy proxy) {
        // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatenating it
        bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce, getChainId()));
        proxy = deployProxy(_singleton, initializer, salt);
        emit ProxyCreation(proxy, _singleton);
    }

    /**
     * @notice Deploys a new chain-specific proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
     * @dev Allows to create a new proxy contract that should exist only on 1 network (e.g. specific governance or admin accounts)
     *      by including the chain id in the create2 salt. Such proxies cannot be created on other networks by replaying the transaction.
     *      Emits an extra event to allow tracking of `initializer` and `saltNonce`.
     * @param _singleton Address of singleton contract. Must be deployed at the time of execution.
     * @param initializer Payload for a message call to be sent to a new proxy contract.
     * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
     */
    function createChainSpecificProxyWithNonceL2(
        address _singleton,
        bytes memory initializer,
        uint256 saltNonce
    ) public returns (SafeProxy proxy) {
        proxy = createChainSpecificProxyWithNonce(_singleton, initializer, saltNonce);
        emit ChainSpecificProxyCreationL2(proxy, _singleton, initializer, saltNonce, getChainId());
    }

    /**
     * @notice Returns true if `account` is a contract.
     * @dev This function will return false if invoked during the constructor of a contract,
     *      as the code is not created until after the constructor finishes.
     * @param account The address being queried
     * @return True if `account` is a contract
     */
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        /* solhint-disable no-inline-assembly */
        /// @solidity memory-safe-assembly
        assembly {
            size := extcodesize(account)
        }
        /* solhint-enable no-inline-assembly */
        return size > 0;
    }

    /**
     * @notice Returns the ID of the chain the contract is currently deployed on.
     * @return The ID of the current chain as a uint256.
     */
    function getChainId() public view returns (uint256) {
        uint256 id;
        /* solhint-disable no-inline-assembly */
        /// @solidity memory-safe-assembly
        assembly {
            id := chainid()
        }
        /* solhint-enable no-inline-assembly */
        return id;
    }
}
"
    },
    "src/Libraries/SafeHelpers.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

/// @title SafeHelpers - Library for Safe transaction encoding
/// @notice Based on Brahma.fi implementation for enhanced security
library SafeHelpers {
    enum CallType {
        CALL, // 0 - Regular call
        DELEGATECALL // 1 - Delegate call

    }

    struct Executable {
        CallType callType;
        address target;
        uint256 value;
        bytes data;
    }

    /// @notice Errors
    error InvalidMultiSendInput();
    error UnableToParseOperation();

    /// @notice Pack multiple transactions for MultiSend
    /// @param _txns Array of executable transactions
    /// @return packedTxns Packed transaction data
    /// @dev Enhanced version based on Brahma implementation with strict validation
    function packMultisendTxns(Executable[] memory _txns) internal pure returns (bytes memory packedTxns) {
        uint256 len = _txns.length;
        if (len == 0) revert InvalidMultiSendInput();

        uint256 i = 0;
        do {
            uint8 call = uint8(_parseOperationEnum(_txns[i].callType));
            uint256 calldataLength = _txns[i].data.length;

            bytes memory encodedTxn = abi.encodePacked(
                bytes1(call), bytes20(_txns[i].target), bytes32(_txns[i].value), bytes32(calldataLength), _txns[i].data
            );

            if (i != 0) {
                // If not first transaction, append to packedTxns
                packedTxns = abi.encodePacked(packedTxns, encodedTxn);
            } else {
                // If first transaction, set packedTxns to encodedTxn
                packedTxns = encodedTxn;
            }

            unchecked {
                ++i;
            }
        } while (i < len);
    }

    /// @notice Converts a CallType enum to operation code with validation
    /// @dev Reverts with UnableToParseOperation error if the CallType is not supported
    /// @param callType The CallType enum to be converted
    /// @return operation The converted operation code (0 for CALL, 1 for DELEGATECALL)
    function _parseOperationEnum(CallType callType) internal pure returns (CallType operation) {
        if (callType == CallType.DELEGATECALL) {
            operation = CallType.DELEGATECALL; // = 1
        } else if (callType == CallType.CALL) {
            operation = CallType.CALL; // = 0
        } else {
            revert UnableToParseOperation();
        }
    }
}
"
    },
    "src/ThyraDiamond.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

import {LibDiamond} from "./Libraries/LibDiamond.sol";
import {LibDefaultFacets} from "./Libraries/LibDefaultFacets.sol";
// solhint-disable-next-line no-unused-import
import {LibUtil} from "./Libraries/LibUtil.sol";

/// @title Thyra Diamond
/// @author ThyraWallet Team
/// @notice Base EIP-2535 Diamond Proxy Contract for Thyra Account.
/// @custom:version 1.0.0
contract ThyraDiamond {
    /// @notice Default facet addresses using immutable for gas-optimized fast path
    address private immutable I_DIAMOND_CUT_FACET;
    address private immutable I_DIAMOND_LOUPE_FACET;
    address private immutable I_EXECUTOR_FACET;
    address private immutable I_OWNERSHIP_FACET;

    constructor(
        address _contractOwner,
        address _diamondCutFacet,
        address _diamondLoupeFacet,
        address _executorFacet,
        address _ownershipFacet
    ) payable {
        LibDiamond.setContractOwner(_contractOwner);

        // Initialize immutable default facet addresses for fast path optimization
        I_DIAMOND_CUT_FACET = _diamondCutFacet;
        I_DIAMOND_LOUPE_FACET = _diamondLoupeFacet;
        I_EXECUTOR_FACET = _executorFacet;
        I_OWNERSHIP_FACET = _ownershipFacet;
        
        // Note: Factory and Safe wallet initialization moved to OwnershipFacet.initialize()
        // called by Factory after deployment to avoid constructor call issues
    }

    // Two-tiered lookup system: fast path + slow path fallback
    // Provides gas-optimized fast path lookup for commonly used default facets
    // solhint-disable-next-line no-complex-fallback
    fallback() external payable {
        address facet;

        // Phase 1: Fast path - check if default facet (no storage access, extremely low gas cost)
        LibDefaultFacets.DefaultFacetType facetType = LibDefaultFacets.getDefaultFacetType(msg.sig);

        if (facetType == LibDefaultFacets.DefaultFacetType.DiamondCut) {
            facet = I_DIAMOND_CUT_FACET;
        } else if (facetType == LibDefaultFacets.DefaultFacetType.DiamondLoupe) {
            facet = I_DIAMOND_LOUPE_FACET;
        } else if (facetType == LibDefaultFacets.DefaultFacetType.Executor) {
            facet = I_EXECUTOR_FACET;
        } else if (facetType == LibDefaultFacets.DefaultFacetType.Ownership) {
            facet = I_OWNERSHIP_FACET;
        } else {
            // Phase 2: Slow path - fallback to Diamond storage lookup (compatibility guarantee)
            LibDiamond.DiamondStorage storage ds;
            bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION;

            // get diamond storage
            // solhint-disable-next-line no-inline-assembly
            assembly {
                ds.slot := position
            }

            // get facet from function selector
            facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress;
        }

        // Validate facet address validity
        if (facet == address(0)) {
            revert LibDiamond.FunctionDoesNotExist();
        }

        // Execute delegatecall to target facet (final execution logic same for both paths)
        // solhint-disable-next-line no-inline-assembly
        assembly {
            // copy function selector and any arguments
            calldatacopy(0, 0, calldatasize())
            // execute function call using the facet
            let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
            // get any return value
            returndatacopy(0, 0, returndatasize())
            // return any return value or error back to the caller
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    // Able to receive ether
    // solhint-disable-next-line no-empty-blocks
    receive() external payable {}
}
"
    },
    "src/Interfaces/IOwnershipFacet.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

import {IERC173} from "./IERC173.sol";

/// @title Interface for OwnershipFacet (Extended)
/// @author ThyraWallet Team
/// @notice Extended ownership interface including Safe wallet initialization
/// @custom:version 1.0.0
interface IOwnershipFacet is IERC173 {
    /// @notice Initialize Diamond with factory and Safe wallet
    /// @param _factory Address of the Factory that deployed this Diamond
    /// @param _safeWallet Address of the Safe wallet
    function initialize(address _factory, address _safeWallet) external;

    /// @notice Get the Safe wallet address
    /// @return Safe wallet address (zero address if not initialized)
    function safeWallet() external view returns (address);

    /// @notice Get the factory address
    /// @return Factory address that deployed this Diamond
    function factory() external view returns (address);
}

"
    },
    "lib/openzeppelin-contracts/contracts/utils/Errors.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}
"
    },
    "lib/safe-smart-account/contracts/proxies/SafeProxy.sol": {
      "content": "// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable one-contract-per-file */
pragma solidity >=0.7.0 <0.9.0;

/**
 * @title IProxy - Helper interface to access the singleton address of the Proxy on-chain.
 * @author Richard Meissner - @rmeissner
 */
interface IProxy {
    function masterCopy() external view returns (address);
}

/**
 * @title SafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract.
 * @author Stefan George - <stefan@gnosis.io>
 * @author Richard Meissner - <richard@gnosis.io>
 */
contract SafeProxy {
    // Singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.
    // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`
    address internal singleton;

    /**
     * @notice Constructor function sets address of singleton contract.
     * @param _singleton Singleton address.
     */
    constructor(address _singleton) {
        require(_singleton != address(0), "Invalid singleton address provided");
        singleton = _singleton;
    }

    /// @dev Fallback function forwards all transactions and returns all received return data.
    fallback() external payable {
        // Note that this assembly block is **intentionally** not marked as memory-safe. First of all, it isn't memory
        // safe to begin with, and turning this into memory-safe assembly would just make it less gas efficient.
        // Additionally, we noticed that converting this to memory-safe assembly had no affect on optimizations of other
        // contracts (as it always gets compiled alone in its own compilation unit anyway). Because the assembly block
        // always halts and never returns control back to Solidity, disrespecting Solidity's memory safety invariants
        // is not an issue.
        /* solhint-disable no-inline-assembly */
        assembly {
            let _singleton := sload(0)
            // 0xa619486e == uint32(bytes4(keccak256("masterCopy()"))). Only the 4 first bytes of calldata are
            // considered to make it 100% Solidity ABI conformant.
            if eq(shr(224, calldataload(0)), 0xa619486e) {
                // We mask the singleton address when handling the `masterCopy()` call to ensure that it is correctly
                // ABI-encoded. We do this by shifting the address left by 96 bits (or 12 bytes) and then storing it in
                // memory with a 12 byte offset from where the return data starts. Note that we **intentionally** only
                // do this for the `masterCopy()` call, since the EVM `DELEGATECALL` opcode ignores the most-significant
                // 12 bytes from the address, so we do not need to make sure the top bytes are cleared when proxying
                // calls to the `singleton`. This saves us a tiny amount of gas per proxied call. Additionally, we write
                // to the "zero-memory" slot instead of the scratch space, which guarantees that 12 bytes of memory
                // preceding the singleton address are zero (which would not be guaranteed for the scratch space) [1].
                // This ensures that the data we return has the leading 12 bytes set to zero and conforms to the
                // Solidity ABI [2].
                //
                // [1]: https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html
                // [2]: https://docs.soliditylang.org/en/v0.7.6/abi-spec.html#formal-specification-of-the-encoding
                mstore(0x6c, shl(96, _singleton))
                return(0x60, 0x20)
            }
            calldatacopy(0, 0, calldatasize())
            let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            if iszero(success) {
                revert(0, returndatasize())
            }
            return(0, returndatasize())
        }
        /* solhint-enable no-inline-assembly */
    }
}
"
    },
    "src/Libraries/LibDiamond.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

import {LibDiamond} from "../Libraries/LibDiamond.sol";
import {LibUtil} from "../Libraries/LibUtil.sol";
import {OnlyContractOwner} from "../Errors/GenericErrors.sol";

/// @title LibDiamond
/// @custom:version 1.0.0
/// @notice This library implements the EIP-2535 Diamond Standard
library LibDiamond {
    bytes32 internal constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

    // Diamond specific errors
    error IncorrectFacetCutAction();
    error NoSelectorsInFace();
    error FunctionAlreadyExists();
    error FacetAddressIsZero();
    error FacetAddressIsNotZero();
    error FacetContainsNoCode();
    error FunctionDoesNotExist();
    error FunctionIsImmutable();
    error InitZeroButCalldataNotEmpty();
    error CalldataEmptyButInitNotZero();
    error InitReverted();
    // ----------------

    struct FacetAddressAndPosition {
        address facetAddress;
        uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
    }

    struct FacetFunctionSelectors {
        bytes4[] functionSelectors;
        uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
    }

    struct DiamondStorage {
        // maps function selector to the facet address and
        // the position of the selector in the facetFunctionSelectors.selectors array
        mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
        // maps facet addresses to function selectors
        mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
        // facet addresses
        address[] facetAddresses;
        // Used to query if a contract implements an interface.
        // Used to implement ERC-165.
        mapping(bytes4 => bool) supportedInterfaces;
        // owner of the contract
        address contractOwner;
    }

    enum FacetCutAction {
        Add,
        Replace,
        Remove
    }
    // Add=0, Replace=1, Remove=2

    struct FacetCut {
        address facetAddress;
        FacetCutAction action;
        bytes4[] functionSelectors;
    }

    function diamondStorage() internal pure returns (DiamondStorage storage ds) {
        bytes32 position = DIAMOND_STORAGE_POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            ds.slot := position
        }
    }

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);

    function setContractOwner(address _newOwner) internal {
        DiamondStorage storage ds = diamondStorage();
        address previousOwner = ds.contractOwner;
        ds.contractOwner = _newOwner;
        emit OwnershipTransferred(previousOwner, _newOwner);
    }

    function contractOwner() internal view returns (address contractOwner_) {
        contractOwner_ = diamondStorage().contractOwner;
    }

    function enforceIsContractOwner() internal view {
        if (msg.sender != diamondStorage().contractOwner) {
            revert OnlyContractOwner();
        }
    }

    // Internal function version of diamondCut
    function diamondCut(FacetCut[] memory _diamondCut, address _init, bytes memory _calldata) internal {
        for (uint256 facetIndex; facetIndex < _diamondCut.length;) {
            LibDiamond.FacetCutAction action = _diamondCut[facetIndex].action;
            if (action == LibDiamond.FacetCutAction.Add) {
                addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
            } else if (action == LibDiamond.FacetCutAction.Replace) {
                replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
            } else if (action == LibDiamond.FacetCutAction.Remove) {
                removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
            } else {
                revert IncorrectFacetCutAction();
            }
            unchecked {
                ++facetIndex;
            }
        }
        emit DiamondCut(_diamondCut, _init, _calldata);
        initializeDiamondCut(_init, _calldata);
    }

    function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
        if (_functionSelectors.length == 0) {
            revert NoSelectorsInFace();
        }
        DiamondStorage storage ds = diamondStorage();
        if (LibUtil.isZeroAddress(_facetAddress)) {
            revert FacetAddressIsZero();
        }
        uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
        // add new facet address if it does not exist
        if (selectorPosition == 0) {
            addFacet(ds, _facetAddress);
        }
        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
            if (!LibUtil.isZeroAddress(oldFacetAddress)) {
                revert FunctionAlreadyExists();
            }
            addFunction(ds, selector, selectorPosition, _facetAddress);
            unchecked {
                ++selectorPosition;
                ++selectorIndex;
            }
        }
    }

    function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
        if (_functionSelectors.length == 0) {
            revert NoSelectorsInFace();
        }
        DiamondStorage storage ds = diamondStorage();
        if (LibUtil.isZeroAddress(_facetAddress)) {
            revert FacetAddressIsZero();
        }
        uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
        // add new facet address if it does not exist
        if (selectorPosition == 0) {
            addFacet(ds, _facetAddress);
        }
        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
            if (oldFacetAddress == _facetAddress) {
                revert FunctionAlreadyExists();
            }
            removeFunction(ds, oldFacetAddress, selector);
            addFunction(ds, selector, selectorPosition, _facetAddress);
            unchecked {
                ++selectorPosition;
                ++selectorIndex;
            }
        }
    }

    function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
        if (_functionSelectors.length == 0) {
            revert NoSelectorsInFace();
        }
        DiamondStorage storage ds = diamondStorage();
        // if function does not exist then do nothing and return
        if (!LibUtil.isZeroAddress(_facetAddress)) {
            revert FacetAddressIsNotZero();
        }
        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
            removeFunction(ds, oldFacetAddress, selector);
            unchecked {
                ++selectorIndex;
            }
        }
    }

    function addFacet(DiamondStorage storage ds, address _facetAddress) internal {
        enforceHasContractCode(_facetAddress);
        ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length;
        ds.facetAddresses.push(_facetAddress);
    }

    function addFunction(DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress)
        internal
    {
        ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition;
        ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector);
        ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;
    }

    function removeFunction(DiamondStorage storage ds, address _facetAddress, bytes4 _selector) internal {
        if (LibUtil.isZeroAddress(_facetAddress)) {
            revert FunctionDoesNotExist();
        }
        // an immutable function is a function defined directly in a diamond
        if (_facetAddress == address(this)) {
            revert FunctionIsImmutable();
        }
        // replace selector with last selector, then delete last selector
        uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition;
        uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1;
        // if not the same then replace _selector with lastSelector
        if (selectorPosition != lastSelectorPosition) {
            bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition];
            ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector;
            ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition);
        }
        // delete the last selector
        ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();
        delete ds.selectorToFacetAndPosition[_selector];

        // if no more selectors for facet address then delete the facet address
        if (lastSelectorPosition == 0) {
            // replace facet address with last facet address and delete last facet address
            uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;
            uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition;
            if (facetAddressPosition != lastFacetAddressPosition) {
                address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition];
                ds.facetAddresses[facetAddressPosition] = lastFacetAddress;
                ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition;
            }
            ds.facetAddresses.pop();
            delete ds
                .facetFunctionSelectors[_facetAddress]
                .facetAddressPosition;
        }
    }

    function initializeDiamondCut(address _init, bytes memory _calldata) internal {
        if (LibUtil.isZeroAddress(_init)) {
            if (_calldata.length != 0) {
                revert InitZeroButCalldataNotEmpty();
            }
        } else {
            if (_calldata.length == 0) {
                revert CalldataEmptyButInitNotZero();
            }
            if (_init != address(this)) {
                enforceHasContractCode(_init);
            }
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory error) = _init.delegatecall(_calldata);
            if (!success) {
                if (error.length > 0) {
                    // bubble up the error
                    revert(string(error));
                } else {
                    revert InitReverted();
                }
            }
        }
    }

    function enforceHasContractCode(address _contract) internal view {
        uint256 contractSize;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            contractSize := extcodesize(_contract)
        }
        if (contractSize == 0) {
            revert FacetContainsNoCode();
        }
    }
}
"
    },
    "src/Libraries/LibDefaultFacets.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

/// @title LibDefaultFacets
/// @author ThyraWallet Team
/// @notice Pure logic library for classifying default facet function selectors for fast-path lookup optimization
/// @dev This library contains no state, only provides function selector to facet type mapping logic
/// @custom:version 1.0.0
library LibDefaultFacets {
    /// @notice Default Facet type enumeration
    /// @dev None indicates the selector doesn't belong to any default facet, needs storage lookup
    enum DefaultFacetType {
        None, // Not a default facet, requires storage lookup
        DiamondCut, // DiamondCutFacet - Diamond upgrade functionality
        DiamondLoupe, // DiamondLoupeFacet - Diamond information query
        Executor, // ExecutorFacet - Task execution functionality
        Ownership // OwnershipFacet - Ownership management

    }

    /// @notice Get the corresponding default facet type based on function selector
    /// @dev Uses if/else if chain for efficient selector matching, avoiding loops and complex logic
    /// @param _selector Function selector to classify
    /// @return Corresponding DefaultFacetType, returns None if no match
    function getDefaultFacetType(bytes4 _selector) internal pure returns (DefaultFacetType) {
        // ExecutorFacet function selectors (HIGHEST FREQUENCY - user transactions)
        if (
            _selector == 0x0a95afa8 // executeTransaction((address,uint256,bytes,uint8,uint32,bool,uint32,uint32,uint256,uint256,address))
                || _selector == 0xfd1c0a60 // getThyraRegistry()
        ) {
            return DefaultFacetType.Executor;
        }
        // DiamondLoupeFacet function selectors (MEDIUM FREQUENCY - tooling queries)
        else if (
            _selector == 0x7a0ed627 // facets()
                || _selector == 0xcdffacc6 // facetAddress(bytes4)
                || _selector == 0x01ffc9a7 // supportsInterface(bytes4)
                || _selector == 0x52ef6b2c // facetAddresses()
                || _selector == 0xadfca15e // facetFunctionSelectors(address)
        ) {
            return DefaultFacetType.DiamondLoupe;
        }
        // OwnershipFacet function selectors (LOW FREQUENCY - admin operations)
        else if (
            _selector == 0x8da5cb5b // owner() - Most common ownership query
                || _selector == 0xf2fde38b // transferOwnership(address)
                || _selector == 0x7200b829 // confirmOwnershipTransfer()
                || _selector == 0x23452b9c // cancelOwnershipTransfer()
                || _selector == 0x485cc955 // initialize(address,address)
                || _selector == 0x88cfce56 // safeWallet()
                || _selector == 0xc45a0155 // factory()
        ) {
            return DefaultFacetType.Ownership;
        }
        // DiamondCutFacet function selectors (LOWEST FREQUENCY - rare upgrades)
        else if (_selector == 0x1f931c1c) {
            // diamondCut((address,uint8,bytes4[])[],address,bytes)
            return DefaultFacetType.DiamondCut;
        }
        // If no default facet matches, return None
        else {
            return DefaultFacetType.None;
        }
    }
}
"
    },
    "src/Libraries/LibUtil.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
/// @custom:version 1.0.0
pragma solidity ^0.8.17;

// solhint-disable-next-line no-global-import
import "./LibBytes.sol";

library LibUtil {
    using LibBytes for bytes;

    function getRevertMsg(bytes memory _res) internal pure returns (string memory) {
        // If the _res length is less than 68, then the transaction failed silently (without a revert message)
        if (_res.length < 68) return "Transaction reverted silently";
        bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes
        return abi.decode(revertData, (string)); // All that remains is the revert string
    }

    /// @notice Determines whether the given address is the zero address
    /// @param addr The address to verify
    /// @return Boolean indicating if the address is the zero address
    function isZeroAddress(address addr) internal pure returns (bool) {
        return addr == address(0);
    }

    function revertWith(bytes memory data) internal pure {
        assembly {
            let dataSize := mload(data) // Load the size of the data
            let dataPtr := add(data, 0x20) // Advance data pointer to the next word
            revert(dataPtr, dataSize) // Revert with the given data
        }
    }
}
"
    },
    "src/Interfaces/IERC173.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

/// @title Interface for ERC-173 (Contract Ownership Standard)
/// @author LI.FI (https://li.fi)
/// Note: the ERC-165 identifier for this interface is 0x7f5828d0
/// @custom:version 1.0.0
interface IERC173 {
    /// @dev This emits when ownership of a contract changes.
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /// @notice Get the address of the owner
    /// @return owner_ The address of the owner.
    function owner() external view returns (address owner_);

    /// @notice Set the address of the new owner of the contract
    /// @dev Set _newOwner to address(0) to renounce any ownership.
    /// @param _newOwner The address of the new owner of the contract
    function transferOwnership(address _newOwner) external;
}
"
    },
    "src/Errors/GenericErrors.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
/// @custom:version 1.0.2
pragma solidity ^0.8.17;

error AlreadyInitialized();
error CannotAuthoriseSelf();
error CannotBridgeToSameNetwork();
error ContractCallNotAllowed();
error CumulativeSlippageTooHigh(uint256 minAmount, uint256 receivedAmount);
error DiamondIsPaused();
error ETHTransferFailed();
error ExternalCallFailed();
error FunctionDoesNotExist();
error InformationMismatch();
error InsufficientBalance(uint256 required, uint256 balance);
error InvalidAmount();
error InvalidCallData();
error InvalidConfig();
error InvalidContract();
error InvalidDestinationChain();
error InvalidFallbackAddress();
error InvalidNonEVMReceiver();
error InvalidReceiver();
error InvalidSendingToken();
error NativeAssetNotSupported();
error NativeAssetTransferFailed();
error NoSwapDataProvided();
error NoSwapFromZeroBalance();
error NotAContract();
error NotInitialized();
error NoTransferToNullAddress();
error NullAddrIsNotAnERC20Token();
error NullAddrIsNotAValidSpender();
error OnlyContractOwner();
error RecoveryAddressCannotBeZero();
error ReentrancyError();
error TokenNotSupported();
error TransferFromFailed();
error UnAuthorized();
error UnsupportedChainId(uint256 chainId);
error WithdrawFailed();
error ZeroAmount();
"
    },
    "src/Libraries/LibBytes.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
/// @custom:version 1.0.0
pragma solidity ^0.8.17;

library LibBytes {
    // solhint-disable no-inline-assembly

    // LibBytes specific errors
    error SliceOverflow();
    error SliceOutOfBounds();
    error AddressOutOfBounds();

    bytes16 private constant _SYMBOLS = "0123456789abcdef";

    // -------------------------

    function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
        if (_length + 31 < _length) revert SliceOverflow();
        if (_bytes.length < _start + _length) revert SliceOutOfBounds();

        bytes memory tempBytes;

        assembly {
            switch iszero(_length)
            case 0 {
                // Get a location of some free memory and store it in tempBytes as
                // Solidity does for memory variables.
                tempBytes := mload(0x40)

                // The first word of the slice result is potentially a partial
                // word read from the original array. To read it, we calculate
                // the length of that partial word and start copying that many
                // bytes into the array. The first word we copy will start with
                // data we don't care about, but the last `lengthmod` bytes will
                // land at the beginning of the contents of the new array. When
                // we're done copying, we overwrite the full first word with
                // the actual length of the slice.
                let lengthmod := and(_length, 31)

                // The multiplication in the next line is necessary
                // because when slicing multiples of 32 bytes (lengthmod == 0)
                // the following copy loop was copying the origin's length
                // and then ending prematurely not copying everything it should.
                let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
                let end := add(mc, _length)

                for {
                    // The multiplication in the next line has the same exact purpose
                    // as the one above.
                    let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
                } lt(mc, end) {
                    mc := add(mc, 0x20)
                    cc := add(cc, 0x20)
                } { mstore(mc, mload(cc)) }

                mstore(tempBytes, _length)

                //update free-memory pointer
                //allocating the array padded to 32 bytes like the compiler does now
                mstore(0x40, and(add(mc, 31), not(31)))
            }
            //if we want a zero-length slice let's just return a zero-length array
            default {
                tempBytes := mload(0x40)
                //zero out the 32 bytes slice we are about to return
                //we need to do it because Solidity does not garbage collect
                mstore(tempBytes, 0)

                mstore(0x40, add(tempBytes, 0x20))
            }
        }

        return tempBytes;
    }

    function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
        if (_bytes.length < _start + 20) {
            revert AddressOutOfBounds();
        }
        address tempAddress;

        assembly {
            tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
        }

        return tempAddress;
    }

    /// Copied from OpenZeppelin's `Strings.sol` utility library.
    /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8335676b0e99944eef6a742e16dcd9ff6e68e609
    /// /contracts/utils/Strings.sol
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
    

Tags:
ERC165, Multisig, Voting, Upgradeable, Multi-Signature, Factory|addr:0x62280b9f854c806c216e42b5b6ccc77d091432e7|verified:true|block:23731841|tx:0x882fbc0f40e3a2d60c4e586a3de51181fd59ea308c01bcf0d6589441654b905b|first_check:1762345913

Submitted on: 2025-11-05 13:31:55

Comments

Log in to comment.

No comments yet.