ETH Price: $2,905.59 (-1.11%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Advanced mode:
Parent Transaction Hash Method Block
From
To
View All Internal Transactions
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
BuyerHandlerFacet

Compiler Version
v0.8.22+commit.4fc1097e

Optimization Enabled:
Yes with 100 runs

Other Settings:
shanghai EvmVersion
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

import "../../domain/BosonConstants.sol";
import { BuyerBase } from "../bases/BuyerBase.sol";
import { ProtocolLib } from "../libs/ProtocolLib.sol";
import { IBosonBuyerHandler } from "../../interfaces/handlers/IBosonBuyerHandler.sol";

/**
 * @title BuyerHandlerFacet
 *
 * @notice Handles buyer account management requests and queries.
 */
contract BuyerHandlerFacet is BuyerBase, IBosonBuyerHandler {
    /**
     * @notice Initializes facet.
     */
    function initialize() public {
        // No-op initializer.
        // - needed by the deployment script, which expects a no-args initializer on facets other than the config handler
        // - exception here because IBosonAccountHandler is contributed to by multiple facets which do not have their own individual interfaces
    }

    /**
     * @notice Creates a buyer.
     *
     * Emits a BuyerCreated event if successful.
     *
     * Reverts if:
     * - The buyers region of protocol is paused
     * - Wallet address is zero address
     * - Active is not true
     * - Wallet address is not unique to this buyer
     *
     * @param _buyer - the fully populated struct with buyer id set to 0x0
     */
    function createBuyer(Buyer memory _buyer) external buyersNotPaused nonReentrant {
        //Check active is not set to false
        if (!_buyer.active) revert MustBeActive();

        //check that the wallet address is unique to one buyer id
        if (protocolLookups().buyerIdByWallet[_buyer.wallet] != 0) revert BuyerAddressMustBeUnique();

        createBuyerInternal(_buyer);
    }

    /**
     * @notice Updates a buyer, with the exception of the active flag.
     *         All other fields should be filled, even those staying the same.
     * @dev    Active flag passed in by caller will be ignored. The value from storage will be used.
     *
     * Emits a BuyerUpdated event if successful.
     *
     * Reverts if:
     * - The buyers region of protocol is paused
     * - Caller is not the wallet address of the stored buyer
     * - Wallet address is zero address
     * - Address is not unique to this buyer
     * - Buyer does not exist
     * - Current wallet address has outstanding vouchers
     *
     * @param _buyer - the fully populated buyer struct
     */
    function updateBuyer(Buyer memory _buyer) external buyersNotPaused nonReentrant {
        // Cache protocol lookups for reference
        ProtocolLib.ProtocolLookups storage lookups = protocolLookups();

        // Check for zero address
        if (_buyer.wallet == address(0)) revert InvalidAddress();

        bool exists;
        Buyer storage buyer;

        // Check Buyer exists in buyers mapping
        (exists, buyer) = fetchBuyer(_buyer.id);

        // Buyer must already exist
        if (!exists) revert NoSuchBuyer();

        // Get message sender
        address sender = _msgSender();

        // Check that msg.sender is the wallet address for this buyer
        if (buyer.wallet != sender) revert NotBuyerWallet();

        // Check that current wallet address does not own any vouchers, if changing wallet address
        if (buyer.wallet != _buyer.wallet) {
            if (lookups.voucherCount[_buyer.id] != 0) revert WalletOwnsVouchers();
        }

        // Check that the wallet address is unique to one buyer id if new
        mapping(address => uint256) storage buyerIds = lookups.buyerIdByWallet;
        uint256 buyerId = buyerIds[_buyer.wallet];
        if (buyerId != 0 && buyerId != _buyer.id) revert BuyerAddressMustBeUnique();

        // Delete current mappings
        delete buyerIds[sender];

        // Ignore active flag passed in by caller and set to value in storage.
        _buyer.active = buyer.active;
        storeBuyer(_buyer);

        // Notify watchers of state change
        emit BuyerUpdated(_buyer.id, _buyer, sender);
    }

    /**
     * @notice Gets the details about a buyer.
     *
     * @param _buyerId - the id of the buyer to check
     * @return exists - whether the buyer was found
     * @return buyer - the buyer details. See {BosonTypes.Buyer}
     */
    function getBuyer(uint256 _buyerId) external view returns (bool exists, Buyer memory buyer) {
        return fetchBuyer(_buyerId);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` 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 amount) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 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 {
    using Address for address;

    /**
     * @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.encodeWithSelector(token.transfer.selector, 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.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 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.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @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.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

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

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @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 silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import { IAccessControl } from "../interfaces/IAccessControl.sol";
import { IDiamondCut } from "../interfaces/diamond/IDiamondCut.sol";

/**
 * @title DiamondLib
 *
 * @notice Provides Diamond storage slot and supported interface checks.
 *
 * @notice Based on Nick Mudge's gas-optimized diamond-2 reference,
 * with modifications to support role-based access and management of
 * supported interfaces. Also added copious code comments throughout.
 *
 * Reference Implementation  : https://github.com/mudgen/diamond-2-hardhat
 * EIP-2535 Diamond Standard : https://eips.ethereum.org/EIPS/eip-2535
 *
 * N.B. Facet management functions from original `DiamondLib` were refactored/extracted
 * to JewelerLib, since business facets also use this library for access control and
 * managing supported interfaces.
 *
 * @author Nick Mudge <[email protected]> (https://twitter.com/mudgen)
 * @author Cliff Hall <[email protected]> (https://twitter.com/seaofarrows)
 */
library DiamondLib {
    bytes32 internal constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

    struct DiamondStorage {
        // Maps function selectors to the facets that execute the functions
        // and maps the selectors to their position in the selectorSlots array.
        // func selector => address facet, selector position
        mapping(bytes4 => bytes32) facets;
        // Array of slots of function selectors.
        // Each slot holds 8 function selectors.
        mapping(uint256 => bytes32) selectorSlots;
        // The number of function selectors in selectorSlots
        uint16 selectorCount;
        // Used to query if a contract implement is an interface.
        // Used to implement ERC-165.
        mapping(bytes4 => bool) supportedInterfaces;
        // The Boson Protocol AccessController
        IAccessControl accessController;
    }

    /**
     * @notice Gets the Diamond storage slot.
     *
     * @return ds - Diamond storage slot cast to DiamondStorage
     */
    function diamondStorage() internal pure returns (DiamondStorage storage ds) {
        bytes32 position = DIAMOND_STORAGE_POSITION;
        assembly {
            ds.slot := position
        }
    }

    /**
     * @notice Adds a supported interface to the Diamond.
     *
     * @param _interfaceId - the interface to add
     */
    function addSupportedInterface(bytes4 _interfaceId) internal {
        // Get the DiamondStorage struct
        DiamondStorage storage ds = diamondStorage();

        // Flag the interfaces as supported
        ds.supportedInterfaces[_interfaceId] = true;
    }

    /**
     * @notice Removes a supported interface from the Diamond.
     *
     * @param _interfaceId - the interface to remove
     */
    function removeSupportedInterface(bytes4 _interfaceId) internal {
        // Get the DiamondStorage struct
        DiamondStorage storage ds = diamondStorage();

        // Flag the interfaces as unsupported
        ds.supportedInterfaces[_interfaceId] = false;
    }

    /**
     * @notice Checks if a specific interface is supported.
     * Implementation of ERC-165 interface detection standard.
     *
     * @param _interfaceId - the sighash of the given interface
     * @return - whether or not the interface is supported
     */
    function supportsInterface(bytes4 _interfaceId) internal view returns (bool) {
        // Get the DiamondStorage struct
        DiamondStorage storage ds = diamondStorage();

        // Return the value
        return ds.supportedInterfaces[_interfaceId];
    }
}

File 9 of 25 : BosonConstants.sol
import "./BosonTypes.sol";

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

// Access Control Roles
bytes32 constant ADMIN = keccak256("ADMIN"); // Role Admin
bytes32 constant PAUSER = keccak256("PAUSER"); // Role for pausing the protocol
bytes32 constant PROTOCOL = keccak256("PROTOCOL"); // Role for facets of the ProtocolDiamond
bytes32 constant CLIENT = keccak256("CLIENT"); // Role for clients of the ProtocolDiamond
bytes32 constant UPGRADER = keccak256("UPGRADER"); // Role for performing contract and config upgrades
bytes32 constant FEE_COLLECTOR = keccak256("FEE_COLLECTOR"); // Role for collecting fees from the protocol

// Generic
uint256 constant HUNDRED_PERCENT = 10000; // 100% in basis points
uint256 constant PROTOCOL_ENTITY_ID = 0; // Entity ID for the protocol itself
uint256 constant VOIDED_OFFER_ID = type(uint256).max; // Offer ID for voided non-listed offers

// Pause Handler
uint256 constant ALL_REGIONS_MASK = (1 << (uint256(type(BosonTypes.PausableRegion).max) + 1)) - 1;

// Reentrancy guard
uint256 constant NOT_ENTERED = 1;
uint256 constant ENTERED = 2;

// Twin handler
uint256 constant SINGLE_TWIN_RESERVED_GAS = 160000;
uint256 constant MINIMAL_RESIDUAL_GAS = 230000;

// Config related
bytes32 constant VOUCHER_PROXY_SALT = keccak256(abi.encodePacked("BosonVoucherProxy"));

// Funds related
string constant NATIVE_CURRENCY = "Native currency";
string constant TOKEN_NAME_UNSPECIFIED = "Token name unavailable";

// EIP712Lib
string constant PROTOCOL_NAME = "Boson Protocol";
string constant PROTOCOL_VERSION = "V2";
bytes32 constant EIP712_DOMAIN_TYPEHASH = keccak256(
    bytes("EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)")
);
uint256 constant SLOT_SIZE = 32; // Size of a slot in bytes, used for encoding and decoding

// BosonVoucher
string constant VOUCHER_NAME = "Boson Voucher (rNFT)";
string constant VOUCHER_SYMBOL = "BOSON_VOUCHER_RNFT";

// Meta Transactions - Error
string constant FUNCTION_CALL_NOT_SUCCESSFUL = "Function call not successful";

// External contracts errors
string constant OWNABLE_ZERO_ADDRESS = "Ownable: new owner is the zero address"; // exception message from OpenZeppelin Ownable
string constant ERC721_INVALID_TOKEN_ID = "ERC721: invalid token ID"; // exception message from OpenZeppelin ERC721

// Meta Transactions - Structs
bytes32 constant META_TRANSACTION_TYPEHASH = keccak256(
    bytes(
        "MetaTransaction(uint256 nonce,address from,address contractAddress,string functionName,bytes functionSignature)"
    )
);
bytes32 constant OFFER_DETAILS_TYPEHASH = keccak256("MetaTxOfferDetails(address buyer,uint256 offerId)");
bytes32 constant META_TX_COMMIT_TO_OFFER_TYPEHASH = keccak256(
    "MetaTxCommitToOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxOfferDetails offerDetails)MetaTxOfferDetails(address buyer,uint256 offerId)"
);
bytes32 constant CONDITIONAL_OFFER_DETAILS_TYPEHASH = keccak256(
    "MetaTxConditionalOfferDetails(address buyer,uint256 offerId,uint256 tokenId)"
);
bytes32 constant META_TX_COMMIT_TO_CONDITIONAL_OFFER_TYPEHASH = keccak256(
    "MetaTxCommitToConditionalOffer(uint256 nonce,address from,address contractAddress,string functionName,MetaTxConditionalOfferDetails offerDetails)MetaTxConditionalOfferDetails(address buyer,uint256 offerId,uint256 tokenId)"
);
bytes32 constant EXCHANGE_DETAILS_TYPEHASH = keccak256("MetaTxExchangeDetails(uint256 exchangeId)");
bytes32 constant META_TX_EXCHANGE_TYPEHASH = keccak256(
    "MetaTxExchange(uint256 nonce,address from,address contractAddress,string functionName,MetaTxExchangeDetails exchangeDetails)MetaTxExchangeDetails(uint256 exchangeId)"
);
bytes32 constant FUND_DETAILS_TYPEHASH = keccak256(
    "MetaTxFundDetails(uint256 entityId,address[] tokenList,uint256[] tokenAmounts)"
);
bytes32 constant META_TX_FUNDS_TYPEHASH = keccak256(
    "MetaTxFund(uint256 nonce,address from,address contractAddress,string functionName,MetaTxFundDetails fundDetails)MetaTxFundDetails(uint256 entityId,address[] tokenList,uint256[] tokenAmounts)"
);
bytes32 constant DISPUTE_RESOLUTION_DETAILS_TYPEHASH = keccak256(
    "MetaTxDisputeResolutionDetails(uint256 exchangeId,uint256 buyerPercentBasisPoints,bytes signature)"
);
bytes32 constant META_TX_DISPUTE_RESOLUTIONS_TYPEHASH = keccak256(
    "MetaTxDisputeResolution(uint256 nonce,address from,address contractAddress,string functionName,MetaTxDisputeResolutionDetails disputeResolutionDetails)MetaTxDisputeResolutionDetails(uint256 exchangeId,uint256 buyerPercentBasisPoints,bytes signature)"
);

// Function names
string constant COMMIT_TO_OFFER = "commitToOffer(address,uint256)";
string constant COMMIT_TO_CONDITIONAL_OFFER = "commitToConditionalOffer(address,uint256,uint256)";
string constant CANCEL_VOUCHER = "cancelVoucher(uint256)";
string constant REDEEM_VOUCHER = "redeemVoucher(uint256)";
string constant COMPLETE_EXCHANGE = "completeExchange(uint256)";
string constant WITHDRAW_FUNDS = "withdrawFunds(uint256,address[],uint256[])";
string constant RETRACT_DISPUTE = "retractDispute(uint256)";
string constant RAISE_DISPUTE = "raiseDispute(uint256)";
string constant ESCALATE_DISPUTE = "escalateDispute(uint256)";
string constant RESOLVE_DISPUTE = "resolveDispute(uint256,uint256,bytes)";

File 10 of 25 : BosonErrors.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

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

interface BosonErrors {
    // Pause related
    // Trying to unpause a protocol when it's not paused
    error NotPaused();
    // Whenever a region is paused, and a method from that region is called
    error RegionPaused(BosonTypes.PausableRegion region);

    // General
    // Input parameter of type address is zero address
    error InvalidAddress();
    // Exchange or dispute is in different state than expected when certain action is called
    error InvalidState();
    // Two or more array parameters with different lengths
    error ArrayLengthMismatch();
    // Array elements that are not in ascending order (i.e arr[i-1] > arr[i])
    error NonAscendingOrder();
    // Called contract returned an unexpected value
    error UnexpectedDataReturned(bytes data);

    // Reentrancy guard
    // Reentrancy guard is active and second call to protocol is made
    error ReentrancyGuard();

    // Protocol initialization related
    // Trying to initialize the facet when it's already initialized
    error AlreadyInitialized(); // ToDo consider adding the facet to the error message
    // Initialization of some facet failed
    error ProtocolInitializationFailed(); // ToDo consider adding the facet to the error message
    // Trying to initialize the protocol with empty version
    error VersionMustBeSet();
    // Length of _addresses and _calldata arrays do not match
    error AddressesAndCalldataLengthMismatch(); // ToDo consider reusing ArrayLengthMismatch
    // The new protocol version is not subsequent to the current one
    error WrongCurrentVersion();
    // Initialization can be done only through proxy
    error DirectInitializationNotAllowed();
    // Initialization of v2.3.0 can be done only if not twin exists
    error TwinsAlreadyExist();

    // Access related
    // ToDo consider having a single error, with a parameter for the role
    // Caller is not authorized to call the method
    error AccessDenied();
    // Caller is not entitiy's assistant
    error NotAssistant();
    // Caller is not entitiy's admin
    error NotAdmin();
    // Caller is not entitiy's admin and assistant
    error NotAdminAndAssistant();
    // Caller is neither the buyer or the seller involved in the exchange
    error NotBuyerOrSeller();
    // Caller is not the owner of the voucher
    error NotVoucherHolder();
    // Caller is not the buyer
    error NotBuyerWallet();
    // Caller is not the agent
    error NotAgentWallet();
    // Caller is not dispute resolver assistant
    error NotDisputeResolverAssistant();
    // Caller is not the creator of the offer
    error NotOfferCreator();
    // Supplied clerk is not zero address
    error ClerkDeprecated();

    // Account-related
    // Entity must be active
    error MustBeActive();
    // Seller's address cannot be already used in another seller
    error SellerAddressMustBeUnique();
    // Buyer's address cannot be already used in another buyer
    error BuyerAddressMustBeUnique();
    // DR's address cannot be already used in another DR
    error DisputeResolverAddressMustBeUnique();
    // Agent's address cannot be already used in another agent
    error AgentAddressMustBeUnique();
    // Seller does not exist
    error NoSuchSeller();
    // Buyer does not exist
    error NoSuchBuyer();
    // Dispute resolver does not exist
    error NoSuchDisputeResolver();
    // Agent does not exist
    error NoSuchAgent();
    // Entity does not exist
    error NoSuchEntity();
    // Buyer is involved in an non-finalized exchange
    error WalletOwnsVouchers();
    // Escalation period is not greater than zero or is more than the max allowed
    error InvalidEscalationPeriod();
    // Action would remove the last supported fee from the DR (must always have at least one)
    error InexistentDisputeResolverFees();
    // Trying to add a fee that already exists
    error DuplicateDisputeResolverFees();
    // Trying to remove a fee that does not exist
    error DisputeResolverFeeNotFound();
    // Trying to approve a seller that is already approved (list of sellers that DR will handle disputes for)
    error SellerAlreadyApproved();
    // Trying to assing a DR that had not approved the seller
    error SellerNotApproved();
    // Trying to add or removed 0 sellers
    error InexistentAllowedSellersList();
    // Custom auth token is not yet supported
    error InvalidAuthTokenType();
    // Seller must use either and address or auth token for authentication, but not both
    error AdminOrAuthToken();
    // A single auth token can only be used by one seller
    error AuthTokenMustBeUnique();
    // Sum of protocol and agent fee exceed the max allowed fee
    error InvalidAgentFeePercentage();
    // Trying to finalize the update, while it's not even started
    error NoPendingUpdateForAccount();
    // Only the account itself can finalize the update
    error UnauthorizedCallerUpdate();
    // Trying to update the account with the same values
    error NoUpdateApplied();
    // Creating a seller's collection failed
    error CloneCreationFailed();
    // Seller's salt is already used by another seller
    error SellerSaltNotUnique();

    // Offer related
    // Offer does not exist
    error NoSuchOffer();
    // Offer parameters are invalid
    error InvalidOffer();
    // Collection index is invalid for the context
    error InvalidCollectionIndex();
    // Offer finishes in the past or it starts after it finishes
    error InvalidOfferPeriod();
    // Buyer cancellation penalty is higher than the item price
    error InvalidOfferPenalty();
    // New offer must be actiove
    error OfferMustBeActive();
    // Offer can be added to same group only once
    error OfferMustBeUnique();
    // Offer has been voided
    error OfferHasBeenVoided();
    // Current timestamp is higher than offer's expiry timestamp
    error OfferHasExpired();
    // Current timestamp is lower than offer's start timestamp
    error OfferNotAvailable();
    // Offer's quantity available is zero
    error OfferSoldOut();
    // Buyer is not allowed to commit to the offer (does not meet the token gating requirements)
    error CannotCommit();
    // Bundle cannot be created since exchganes for offer exist already
    error ExchangeForOfferExists();
    // Buyer-initiated offer cannot have seller-specific fields (sellerId, collectionIndex, royaltyInfo)
    error InvalidBuyerOfferFields();
    // Seller-initiated offer cannot have buyer-specific fields (buyerId, quantityAvailable)
    error InvalidSellerOfferFields();
    // Buyer cannot provide seller parameters when committing to an offer
    error SellerParametersNotAllowed();
    // Invalid offer creator value specified
    error InvalidOfferCreator();
    // Voucher must have either a fixed expiry or a fixed redeemable period, not both
    error AmbiguousVoucherExpiry();
    // Redemption period starts after it ends or it ends before offer itself expires
    error InvalidRedemptionPeriod();
    // Dispute period is less than minimal dispute period allowed
    error InvalidDisputePeriod();
    // Resolution period is not within the allowed range or it's being misconfigured (minimal > maximal)
    error InvalidResolutionPeriod();
    // Dispute resolver does not exist or is not active
    error InvalidDisputeResolver();
    // Quantity available is zero
    error InvalidQuantityAvailable();
    // Chose DR does not support the fees in the chosen exchange token
    error DRUnsupportedFee();
    // Sum of protocol and agent fee exceeds the max allowed fee
    error AgentFeeAmountTooHigh();
    // Sum of protocol and agent fee exceeds the seller defined max fee
    error TotalFeeExceedsLimit();
    // Collection does not exist
    error NoSuchCollection();
    // Royalty recipient is not allow listed for the seller
    error InvalidRoyaltyRecipient();
    // Total royality fee exceeds the max allowed
    error InvalidRoyaltyPercentage();
    // Specified royalty recipient already added
    error RecipientNotUnique();
    // Trying to access an out of bounds royalty recipient
    error InvalidRoyaltyRecipientId();
    // Array of royalty recipients is not sorted by id
    error RoyaltyRecipientIdsNotSorted();
    // Trying to remove the default recipient (treasury)
    error CannotRemoveDefaultRecipient();
    // Supplying too many Royalty info structs
    error InvalidRoyaltyInfo();
    // Trying to change the default recipient address (treasury)
    error WrongDefaultRecipient();
    // Price discovery offer has non zero price
    error InvalidPriceDiscoveryPrice();
    // Trying to set the same mutualizer as the existing one
    error SameMutualizerAddress();

    // Group related
    // Group does not exist
    error NoSuchGroup();
    // Offer is not in a group
    error OfferNotInGroup();
    // Group remains the same
    error NothingUpdated();
    // There is a logical error in the group's condition parameters or it's not supported yet
    error InvalidConditionParameters();
    // Group does not have a condition
    error GroupHasNoCondition();
    // Group has a condition
    error GroupHasCondition();
    // User exhaused the number of commits allowed for the group
    error MaxCommitsReached();
    // The supplied token id is outside the condition's range
    error TokenIdNotInConditionRange();
    // ERC20 and ERC721 require zero token id
    error InvalidTokenId();

    // Exchange related
    // Exchange does not exist
    error NoSuchExchange();
    // Exchange cannot be completed yet
    error DisputePeriodNotElapsed();
    // Current timestamp is outside the voucher's redeemable period
    error VoucherNotRedeemable();
    // New expiration date is earlier than existing expiration date
    error VoucherExtensionNotValid();
    // Voucher cannot be expired yet
    error VoucherStillValid();
    // Voucher has expired and cannot be transferred anymore
    error VoucherHasExpired();
    // Exchange has not been finalized yet
    error ExchangeIsNotInAFinalState();
    // Exchange with the same id already exists
    error ExchangeAlreadyExists();
    // Range length is 0, is more than quantity available or it would cause an overflow
    error InvalidRangeLength();
    // Exchange is being finalized into an invalid state
    error InvalidTargeExchangeState();

    // Twin related
    // Twin does not exist
    error NoSuchTwin();
    // Seller did not approve the twin transfer
    error NoTransferApproved();
    // Twin transfer failed
    error TwinTransferUnsuccessful();
    // Token address is 0 or it does not implement the required interface
    error UnsupportedToken();
    // Twin cannot be removed if it's in a bundle
    error BundleForTwinExists();
    // Supply available is zero
    error InvalidSupplyAvailable();
    // Twin is Fungible or Multitoken and amount was set
    error InvalidAmount();
    // Twin is NonFungible and amount was not set
    error InvalidTwinProperty(); // ToDo consider replacing with InvalidAmount
    // Token range overlap with another, starting token id is too high or end of range would overflow
    error InvalidTwinTokenRange();
    // Token does not support IERC721 interface
    error InvalidTokenAddress();

    // Bundle related
    // Bundle does not exist
    error NoSuchBundle();
    // Twin is not in a bundle
    error TwinNotInBundle();
    // Offer is not in a bundle
    error OfferNotInBundle();
    // Offer can appear in a bundle only once
    error BundleOfferMustBeUnique();
    // Twin can appear in a bundle only once
    error BundleTwinMustBeUnique();
    // Twin supply does not covver all offers in the bundle
    error InsufficientTwinSupplyToCoverBundleOffers();
    // Bundle cannot be created without an offer or a twin
    error BundleRequiresAtLeastOneTwinAndOneOffer();

    // Funds related
    // Native token must be represented with zero address
    error NativeWrongAddress();
    // Amount sent along (msg.value) does not match the expected amount
    error NativeWrongAmount();
    // Token list lenght does not match the amount list length
    error TokenAmountMismatch(); // ToDo consider replacing with ArrayLengthMismatch
    // Token list is empty
    error NothingToWithdraw();
    // Call is not allowed to transfer the funds
    error NotAuthorized();
    // Token transfer failed
    error TokenTransferFailed();
    // Received amount does not match the expected amount
    error InsufficientValueReceived();
    // Seller's pool does not have enough funds to encumber
    error InsufficientAvailableFunds();
    // Native token was sent when ERC20 was expected
    error NativeNotAllowed();
    // Trying to deposit zero amount
    error ZeroDepositNotAllowed();

    // DR Fee related
    // DR fee mutualizer cannot provide coverage for the fee
    error DRFeeMutualizerCannotProvideCoverage();

    // Meta-Transactions related
    // Meta-transaction nonce is invalid
    error NonceUsedAlready();
    // Function signature does not match it's name
    error InvalidFunctionName();
    // Signature has invalid parameters
    error InvalidSignature();
    // Function is not allowed to be executed as a meta-transaction
    error FunctionNotAllowlisted();
    // Signer does not match the expected one or ERC1271 signature is not valid
    error SignatureValidationFailed();

    // Dispute related
    // Dispute cannot be raised since the period to do it has elapsed
    error DisputePeriodHasElapsed();
    // Mutualizer address does not implement the required interface
    error UnsupportedMutualizer();
    // Dispute cannot be resolved anymore and must be finalized with expireDispute
    error DisputeHasExpired();
    // Buyer gets more than 100% of the total pot
    error InvalidBuyerPercent();
    // Dispute is still valid and cannot be expired yet
    error DisputeStillValid();
    // New dispute timeout is earlier than existing dispute timeout
    error InvalidDisputeTimeout();
    // Absolute zero offers cannot be escalated
    error EscalationNotAllowed();
    // Dispute is being finalized into an invalid state
    error InvalidTargeDisputeState();

    // Config related
    // Percentage exceeds 100%
    error InvalidFeePercentage();
    // Zero config value is not allowed
    error ValueZeroNotAllowed();

    // BosonVoucher
    // Trying to issue an voucher that is in a reseverd range
    error ExchangeIdInReservedRange();
    // Trying to premint vouchers for an offer that does not have a reserved range
    error NoReservedRangeForOffer();
    // Trying to reserve a range that is already reserved
    error OfferRangeAlreadyReserved();
    // Range start at 0 is not allowed
    error InvalidRangeStart();
    // Amount to premint exceeds the range length
    error InvalidAmountToMint();
    // Trying to silent mint vouchers not belonging to the range owner
    error NoSilentMintAllowed();
    // Trying to premint the voucher of already expired offer
    error OfferExpiredOrVoided();
    // Trying to burn preminted vouchers of still valid offer
    error OfferStillValid();
    // Trying to burn more vouchers than available
    error AmountExceedsRangeOrNothingToBurn();
    // Royalty fee exceeds the max allowed
    error InvalidRoyaltyFee();
    // Trying to assign the premined vouchers to the address that is neither the contract owner nor the contract itself
    error InvalidToAddress();
    // Call to an external contract was not successful
    error ExternalCallFailed();
    // Trying to interact with external contract in a way that could result in transferring assets from the contract
    error InteractionNotAllowed();

    // Price discovery related
    // Price discovery returned a price that does not match the expected one
    error PriceMismatch();
    // Token id is mandatory for bid orders and wrappers
    error TokenIdMandatory();
    // Incoming token id does not match the expected one
    error TokenIdMismatch();
    // Using price discovery for non-price discovery offer or using ordinary commit for price discovery offer
    error InvalidPriceType();
    // Missing price discovery contract address or data
    error InvalidPriceDiscovery();
    // Trying to set incoming voucher when it's already set, indicating reentrancy
    error IncomingVoucherAlreadySet();
    // Conduit address must be zero ()
    error InvalidConduitAddress();
    // Protocol does not know what token id to use
    error TokenIdNotSet();
    // Transferring a preminted voucher to wrong recipient
    error VoucherTransferNotAllowed();
    // Price discovery contract returned a negative price
    error NegativePriceNotAllowed();
    // Price discovery did not send the voucher to the protocol
    error VoucherNotReceived();
    // Price discovery did not send the voucher from the protocol
    error VoucherNotTransferred();
    // Either token with wrong id received or wrong voucher contract made the transfer
    error UnexpectedERC721Received();
    // Royalty fee exceeds the price
    error FeeAmountTooHigh();
    // Price does not cover the cancellation penalty
    error PriceDoesNotCoverPenalty();

    // Fee Table related
    // Thrown if asset is not supported in feeTable feature.
    error FeeTableAssetNotSupported();
}

File 11 of 25 : BosonTypes.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

/**
 * @title BosonTypes
 *
 * @notice Enums and structs used by the Boson Protocol contract ecosystem.
 */

contract BosonTypes {
    enum PausableRegion {
        Offers,
        Twins,
        Bundles,
        Groups,
        Sellers,
        Buyers,
        DisputeResolvers,
        Agents,
        Exchanges,
        Disputes,
        Funds,
        Orchestration,
        MetaTransaction,
        PriceDiscovery,
        SequentialCommit
    }

    enum EvaluationMethod {
        None, // None should always be at index 0. Never change this value.
        Threshold,
        SpecificToken
    }

    enum GatingType {
        PerAddress,
        PerTokenId
    }

    enum ExchangeState {
        Committed,
        Revoked,
        Canceled,
        Redeemed,
        Completed,
        Disputed
    }

    enum DisputeState {
        Resolving,
        Retracted,
        Resolved,
        Escalated,
        Decided,
        Refused
    }

    enum TokenType {
        FungibleToken,
        NonFungibleToken,
        MultiToken
    } // ERC20, ERC721, ERC1155

    enum MetaTxInputType {
        Generic,
        CommitToOffer,
        Exchange,
        Funds,
        CommitToConditionalOffer,
        ResolveDispute
    }

    enum AuthTokenType {
        None,
        Custom, // For future use
        Lens,
        ENS
    }

    enum SellerUpdateFields {
        Admin,
        Assistant,
        Clerk, // Deprecated.
        AuthToken
    }

    enum DisputeResolverUpdateFields {
        Admin,
        Assistant,
        Clerk // Deprecated.
    }

    enum PriceType {
        Static, // Default should always be at index 0. Never change this value.
        Discovery
    }

    enum OfferCreator {
        Seller, // Default should always be at index 0. Never change this value.
        Buyer
    }

    struct AuthToken {
        uint256 tokenId;
        AuthTokenType tokenType;
    }

    struct Seller {
        uint256 id;
        address assistant;
        address admin;
        address clerk; // Deprecated. Kept for backwards compatibility.
        address payable treasury;
        bool active;
        string metadataUri;
    }

    struct Buyer {
        uint256 id;
        address payable wallet;
        bool active;
    }

    struct RoyaltyRecipient {
        uint256 id;
        address payable wallet;
    }

    struct DisputeResolver {
        uint256 id;
        uint256 escalationResponsePeriod;
        address assistant;
        address admin;
        address clerk; // Deprecated. Kept for backwards compatibility.
        address payable treasury;
        string metadataUri;
        bool active;
    }

    struct DisputeResolverFee {
        address tokenAddress;
        string tokenName;
        uint256 feeAmount;
    }

    struct Agent {
        uint256 id;
        uint256 feePercentage;
        address payable wallet;
        bool active;
    }

    struct DisputeResolutionTerms {
        uint256 disputeResolverId;
        uint256 escalationResponsePeriod;
        uint256 feeAmount;
        uint256 buyerEscalationDeposit;
        address payable mutualizerAddress; // Address of the DR fee mutualizer
    }

    struct Offer {
        uint256 id;
        uint256 sellerId;
        uint256 price;
        uint256 sellerDeposit;
        uint256 buyerCancelPenalty;
        uint256 quantityAvailable;
        address exchangeToken;
        PriceType priceType;
        OfferCreator creator;
        string metadataUri;
        string metadataHash;
        bool voided;
        uint256 collectionIndex;
        RoyaltyInfo[] royaltyInfo;
        uint256 buyerId; // For buyer-created offers, stores the buyer who created the offer
    }

    struct DRParameters {
        uint256 disputeResolverId;
        address payable mutualizerAddress;
    }

    struct OfferDates {
        uint256 validFrom;
        uint256 validUntil;
        uint256 voucherRedeemableFrom;
        uint256 voucherRedeemableUntil;
    }

    struct OfferDurations {
        uint256 disputePeriod;
        uint256 voucherValid;
        uint256 resolutionPeriod;
    }

    struct FullOffer {
        Offer offer;
        OfferDates offerDates;
        OfferDurations offerDurations;
        DRParameters drParameters;
        Condition condition;
        uint256 agentId;
        uint256 feeLimit;
        bool useDepositedFunds;
    }

    struct Group {
        uint256 id;
        uint256 sellerId;
        uint256[] offerIds;
    }

    struct Condition {
        EvaluationMethod method;
        TokenType tokenType;
        address tokenAddress;
        GatingType gating; // added in v2.3.0. All conditions created before that have a default value of "PerAddress"
        uint256 minTokenId;
        uint256 threshold;
        uint256 maxCommits;
        uint256 maxTokenId;
    }

    struct Exchange {
        uint256 id;
        uint256 offerId;
        uint256 buyerId;
        uint256 finalizedDate;
        ExchangeState state;
        address payable mutualizerAddress;
    }

    struct ExchangeCosts {
        uint256 resellerId;
        uint256 price;
        uint256 protocolFeeAmount;
        uint256 royaltyAmount;
        uint256 royaltyInfoIndex;
    }

    struct Voucher {
        uint256 committedDate;
        uint256 validUntilDate;
        uint256 redeemedDate;
        bool expired;
    }

    struct Dispute {
        uint256 exchangeId;
        uint256 buyerPercent;
        DisputeState state;
    }

    struct DisputeDates {
        uint256 disputed;
        uint256 escalated;
        uint256 finalized;
        uint256 timeout;
    }

    struct Receipt {
        uint256 exchangeId;
        uint256 offerId;
        uint256 buyerId;
        uint256 sellerId;
        uint256 price;
        uint256 sellerDeposit;
        uint256 buyerCancelPenalty;
        OfferFees offerFees;
        uint256 agentId;
        address exchangeToken;
        uint256 finalizedDate;
        Condition condition;
        uint256 committedDate;
        uint256 redeemedDate;
        bool voucherExpired;
        uint256 disputeResolverId;
        uint256 disputedDate;
        uint256 escalatedDate;
        DisputeState disputeState;
        TwinReceipt[] twinReceipts;
    }

    struct TokenRange {
        uint256 start;
        uint256 end;
        uint256 twinId;
    }

    struct Twin {
        uint256 id;
        uint256 sellerId;
        uint256 amount; // ERC1155 / ERC20 (amount to be transferred to each buyer on redemption)
        uint256 supplyAvailable; // all
        uint256 tokenId; // ERC1155 / ERC721 (must be initialized with the initial pointer position of the ERC721 ids available range)
        address tokenAddress; // all
        TokenType tokenType;
    }

    struct TwinReceipt {
        uint256 twinId;
        uint256 tokenId; // only for ERC721 and ERC1155
        uint256 amount; // only for ERC1155 and ERC20
        address tokenAddress;
        TokenType tokenType;
    }

    struct Bundle {
        uint256 id;
        uint256 sellerId;
        uint256[] offerIds;
        uint256[] twinIds;
    }

    struct Funds {
        address tokenAddress;
        string tokenName;
        uint256 availableAmount;
    }

    struct MetaTransaction {
        uint256 nonce;
        address from;
        address contractAddress;
        string functionName;
        bytes functionSignature;
    }

    struct HashInfo {
        bytes32 typeHash;
        function(bytes memory) internal pure returns (bytes32) hashFunction;
    }

    struct OfferFees {
        uint256 protocolFee;
        uint256 agentFee;
    }

    struct VoucherInitValues {
        string contractURI;
        uint256 royaltyPercentage;
        bytes32 collectionSalt;
    }

    struct Collection {
        address collectionAddress;
        string externalId;
    }

    struct PriceDiscovery {
        uint256 price;
        Side side;
        address priceDiscoveryContract;
        address conduit;
        bytes priceDiscoveryData;
    }

    enum Side {
        Ask,
        Bid,
        Wrapper // Side is not relevant from the protocol perspective
    }

    struct RoyaltyInfo {
        address payable[] recipients;
        uint256[] bps;
    }

    struct RoyaltyRecipientInfo {
        address payable wallet;
        uint256 minRoyaltyPercentage;
    }

    struct PremintParameters {
        uint256 reservedRangeLength;
        address to;
    }

    struct Payoff {
        uint256 seller;
        uint256 buyer;
        uint256 protocol;
        uint256 agent;
        uint256 disputeResolver;
        uint256 mutualizer;
    }

    struct SellerOfferParams {
        uint256 collectionIndex;
        RoyaltyInfo royaltyInfo;
        address payable mutualizerAddress;
    }
}

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

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

/**
 * @title IDRFeeMutualizer
 * @notice Interface for dispute resolver fee mutualization
 *
 * The ERC-165 identifier for this interface is: 0xe627eff4
 */
interface IDRFeeMutualizer is IERC165 {
    /**
     * @notice Checks if a seller is covered for a specific DR fee
     * @param _sellerId The seller ID
     * @param _feeAmount The fee amount to cover
     * @param _tokenAddress The token address (address(0) for native currency)
     * @param _disputeResolverId The dispute resolver ID (0 for universal agreement covering all dispute resolvers)
     * @return bool True if the seller is covered, false otherwise
     * @dev Checks for both specific dispute resolver agreements and universal agreements (disputeResolverId = 0).
     */
    function isSellerCovered(
        uint256 _sellerId,
        uint256 _feeAmount,
        address _tokenAddress,
        uint256 _disputeResolverId
    ) external view returns (bool);

    /**
     * @notice Requests a DR fee for a seller
     * @param _sellerId The seller ID
     * @param _feeAmount The fee amount to cover
     * @param _tokenAddress The token address (address(0) for native currency)
     * @param _exchangeId The exchange ID
     * @param _disputeResolverId The dispute resolver ID (0 for universal agreement)
     * @return success True if the request was successful, false otherwise
     * @dev Only callable by the Boson protocol. Returns false if seller is not covered.
     *
     * Emits a {DRFeeProvided} event if successful.
     *
     * Reverts if:
     * - Caller is not the Boson protocol
     * - feeAmount is 0
     * - Pool balance is insufficient
     * - ERC20 or native currency transfer fails
     */
    function requestDRFee(
        uint256 _sellerId,
        uint256 _feeAmount,
        address _tokenAddress,
        uint256 _exchangeId,
        uint256 _disputeResolverId
    ) external returns (bool success);

    /**
     * @notice Notifies the mutualizer that the exchange has been finalized and any unused fee can be returned
     * @param _exchangeId The exchange ID
     * @param _feeAmount The amount being returned (0 means protocol kept all fees)
     * @dev Only callable by the Boson protocol. For native currency, token is wrapped and must be transferred as ERC20.
     *
     * Emits a {DRFeeReturned} event.
     *
     * Reverts if:
     * - Caller is not the Boson protocol
     * - exchangeId is not found
     * - token transfer fails
     */
    function finalizeExchange(uint256 _exchangeId, uint256 _feeAmount) external;
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

/**
 * @title IDiamondCut
 *
 * @notice Manages Diamond Facets.
 *
 * Reference Implementation  : https://github.com/mudgen/diamond-2-hardhat
 * EIP-2535 Diamond Standard : https://eips.ethereum.org/EIPS/eip-2535
 *
 * The ERC-165 identifier for this interface is: 0x1f931c1c
 *
 * @author Nick Mudge <[email protected]> (https://twitter.com/mudgen)
 */
interface IDiamondCut {
    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);

    enum FacetCutAction {
        Add,
        Replace,
        Remove
    }

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

    /**
     * @notice Cuts facets of the Diamond.
     *
     * Adds/replaces/removes any number of function selectors.
     *
     * If populated, _calldata is executed with delegatecall on _init
     *
     * Reverts if caller does not have UPGRADER role
     *
     * @param _facetCuts - contains the facet addresses and function selectors
     * @param _init - the address of the contract or facet to execute _calldata
     * @param _calldata - a function call, including function selector and arguments
     */
    function diamondCut(FacetCut[] calldata _facetCuts, address _init, bytes calldata _calldata) external;
}

File 14 of 25 : IBosonAccountEvents.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

import { BosonTypes } from "../../domain/BosonTypes.sol";

/**
 * @title IBosonAccountEvents
 *
 * @notice Defines events related to management of accounts within the protocol.
 */
interface IBosonAccountEvents {
    event SellerCreated(
        uint256 indexed sellerId,
        BosonTypes.Seller seller,
        address voucherCloneAddress,
        BosonTypes.AuthToken authToken,
        address indexed executedBy
    );
    event SellerUpdatePending(
        uint256 indexed sellerId,
        BosonTypes.Seller pendingSeller,
        BosonTypes.AuthToken pendingAuthToken,
        address indexed executedBy
    );
    event SellerUpdateApplied(
        uint256 indexed sellerId,
        BosonTypes.Seller seller,
        BosonTypes.Seller pendingSeller,
        BosonTypes.AuthToken authToken,
        BosonTypes.AuthToken pendingAuthToken,
        address indexed executedBy
    );
    event RoyaltyRecipientsChanged(
        uint256 indexed sellerId,
        BosonTypes.RoyaltyRecipientInfo[] royaltyRecipients,
        address indexed executedBy
    );
    event BuyerCreated(uint256 indexed buyerId, BosonTypes.Buyer buyer, address indexed executedBy);
    event BuyerUpdated(uint256 indexed buyerId, BosonTypes.Buyer buyer, address indexed executedBy);
    event AgentUpdated(uint256 indexed agentId, BosonTypes.Agent agent, address indexed executedBy);
    event DisputeResolverCreated(
        uint256 indexed disputeResolverId,
        BosonTypes.DisputeResolver disputeResolver,
        BosonTypes.DisputeResolverFee[] disputeResolverFees,
        uint256[] sellerAllowList,
        address indexed executedBy
    );
    event DisputeResolverUpdatePending(
        uint256 indexed disputeResolverId,
        BosonTypes.DisputeResolver pendingDisputeResolver,
        address indexed executedBy
    );
    event DisputeResolverUpdateApplied(
        uint256 indexed disputeResolverId,
        BosonTypes.DisputeResolver disputeResolver,
        BosonTypes.DisputeResolver pendingDisputeResolver,
        address indexed executedBy
    );
    event DisputeResolverFeesAdded(
        uint256 indexed disputeResolverId,
        BosonTypes.DisputeResolverFee[] disputeResolverFees,
        address indexed executedBy
    );
    event DisputeResolverFeesRemoved(
        uint256 indexed disputeResolverId,
        address[] feeTokensRemoved,
        address indexed executedBy
    );
    event AllowedSellersAdded(uint256 indexed disputeResolverId, uint256[] addedSellers, address indexed executedBy);
    event AllowedSellersRemoved(
        uint256 indexed disputeResolverId,
        uint256[] removedSellers,
        address indexed executedBy
    );
    event AgentCreated(uint256 indexed agentId, BosonTypes.Agent agent, address indexed executedBy);
    event CollectionCreated(
        uint256 indexed sellerId,
        uint256 collectionIndex,
        address collectionAddress,
        string indexed externalId,
        address indexed executedBy
    );
}

File 15 of 25 : IBosonFundsEvents.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

/**
 * @title IBosonFundsBaseEvents
 *
 * @notice Defines events related to management of funds within the protocol.
 */
interface IBosonFundsBaseEvents {
    event FundsDeposited(
        uint256 indexed entityId,
        address indexed executedBy,
        address indexed tokenAddress,
        uint256 amount
    );

    event FundsEncumbered(
        uint256 indexed entityId,
        address indexed exchangeToken,
        uint256 amount,
        address indexed executedBy
    );
    event FundsReleased(
        uint256 indexed exchangeId,
        uint256 indexed entityId,
        address indexed exchangeToken,
        uint256 amount,
        address executedBy
    );
    event ProtocolFeeCollected(
        uint256 indexed exchangeId,
        address indexed exchangeToken,
        uint256 amount,
        address indexed executedBy
    );
    event FundsWithdrawn(
        uint256 indexed sellerId,
        address indexed withdrawnTo,
        address indexed tokenAddress,
        uint256 amount,
        address executedBy
    );
    event DRFeeRequested(
        uint256 indexed exchangeId,
        address indexed tokenAddress,
        uint256 feeAmount,
        address indexed mutualizerAddress,
        address executedBy
    );
    event DRFeeReturned(
        uint256 indexed exchangeId,
        address indexed tokenAddress,
        uint256 returnAmount,
        address indexed mutualizerAddress,
        address executedBy
    );
    event DRFeeReturnFailed(
        uint256 indexed exchangeId,
        address indexed tokenAddress,
        uint256 returnAmount,
        address indexed mutualizerAddress,
        address executedBy
    );
}

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

import { BosonTypes } from "../../domain/BosonTypes.sol";

/**
 * @title IBosonBuyerHandler
 *
 * @notice Handles creation, update, retrieval of buyers within the protocol.
 *
 * The ERC-165 identifier for this interface is: 0x3bcf1749
 */
interface IBosonBuyerHandler {
    /**
     * @notice Creates a buyer.
     *
     * Emits an BuyerCreated event if successful.
     *
     * Reverts if:
     * - The buyers region of protocol is paused
     * - Wallet address is zero address
     * - Active is not true
     * - Wallet address is not unique to this buyer
     *
     * @param _buyer - the fully populated struct with buyer id set to 0x0
     */
    function createBuyer(BosonTypes.Buyer memory _buyer) external;

    /**
     * @notice Updates a buyer, with the exception of the active flag.
     *         All other fields should be filled, even those staying the same.
     * @dev    Active flag passed in by caller will be ignored. The value from storage will be used.
     *
     * Emits a BuyerUpdated event if successful.
     *
     * Reverts if:
     * - The buyers region of protocol is paused
     * - Caller is not the wallet address of the stored buyer
     * - Wallet address is zero address
     * - Address is not unique to this buyer
     * - Buyer does not exist
     * - Current wallet address has outstanding vouchers
     *
     * @param _buyer - the fully populated buyer struct
     */
    function updateBuyer(BosonTypes.Buyer memory _buyer) external;

    /**
     * @notice Gets the details about a buyer.
     *
     * @param _buyerId - the id of the buyer to check
     * @return exists - whether the buyer was found
     * @return buyer - the buyer details. See {BosonTypes.Buyer}
     */
    function getBuyer(uint256 _buyerId) external view returns (bool exists, BosonTypes.Buyer memory buyer);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity 0.8.22;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity 0.8.22;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

/**
 * @title IWrappedNative
 *
 * @notice Provides the minimum interface for native token wrapper
 */
interface IWrappedNative {
    function withdraw(uint256) external;

    function deposit() external payable;

    function transfer(address, uint256) external returns (bool);

    function transferFrom(address, address, uint256) external returns (bool);

    function approve(address, uint256) external returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "./../../domain/BosonConstants.sol";
import { IBosonAccountEvents } from "../../interfaces/events/IBosonAccountEvents.sol";
import { ProtocolBase } from "./ProtocolBase.sol";

/**
 * @title BuyerBase
 *
 * @notice Provides methods for buyer creation that can be shared across facets.
 */
contract BuyerBase is ProtocolBase, IBosonAccountEvents {
    /**
     * @notice Creates a Buyer.
     *
     * Emits a BuyerCreated event if successful.
     *
     * Reverts if:
     * - Wallet address is zero address
     * - Active is not true
     * - Wallet address is not unique to this buyer
     *
     * @param _buyer - the fully populated struct with buyer id set to 0x0
     */
    function createBuyerInternal(Buyer memory _buyer) internal {
        //Check for zero address
        if (_buyer.wallet == address(0)) revert InvalidAddress();

        // Get the next account id and increment the counter
        uint256 buyerId = protocolCounters().nextAccountId++;

        _buyer.id = buyerId;
        storeBuyer(_buyer);

        //Notify watchers of state change
        emit BuyerCreated(_buyer.id, _buyer, _msgSender());
    }

    /**
     * @notice Stores buyer struct in storage.
     *
     * @param _buyer - the fully populated struct with buyer id set
     */
    function storeBuyer(Buyer memory _buyer) internal {
        // Get storage location for buyer
        (, Buyer storage buyer) = fetchBuyer(_buyer.id);

        // Set buyer props individually since memory structs can't be copied to storage
        buyer.id = _buyer.id;
        buyer.wallet = _buyer.wallet;
        buyer.active = _buyer.active;

        //Map the buyer's wallet address to the buyerId.
        protocolLookups().buyerIdByWallet[_buyer.wallet] = _buyer.id;
    }

    /**
     * @notice Checks if buyer exists for buyer address. If not, account is created for buyer address.
     *
     * Reverts if buyer exists but is inactive.
     *
     * @param _buyer - the buyer address to check
     * @return buyerId - the buyer id
     */
    function getValidBuyer(address payable _buyer) internal returns (uint256 buyerId) {
        // Find or create the account associated with the specified buyer address
        bool exists;
        (exists, buyerId) = getBuyerIdByWallet(_buyer);

        if (exists) {
            // Fetch the existing buyer account
            (, Buyer storage buyer) = fetchBuyer(buyerId);

            // Make sure buyer account is active
            if (!buyer.active) revert MustBeActive();
        } else {
            // Create the buyer account
            Buyer memory newBuyer;
            newBuyer.wallet = _buyer;
            newBuyer.active = true;

            createBuyerInternal(newBuyer);
            buyerId = newBuyer.id;
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "../../domain/BosonConstants.sol";
import { BosonErrors } from "../../domain/BosonErrors.sol";
import { BosonTypes } from "../../domain/BosonTypes.sol";
import { ProtocolLib } from "../libs/ProtocolLib.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IBosonFundsBaseEvents } from "../../interfaces/events/IBosonFundsEvents.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { IDRFeeMutualizer } from "../../interfaces/clients/IDRFeeMutualizer.sol";
import { Context } from "@openzeppelin/contracts/utils/Context.sol";
import { IWrappedNative } from "../../interfaces/IWrappedNative.sol";

/**
 * @title FundsBase
 *
 * @dev
 */
abstract contract FundsBase is Context {
    using SafeERC20 for IERC20;
    IWrappedNative internal immutable wNative;

    /**
     * @notice Takes in the offer id and entity id and encumbers the appropriate funds during commitToOffer.
     * For seller-created offers: encumbers seller's pre-deposited deposit and validates buyer's incoming payment.
     * For buyer-created offers: encumbers buyer's pre-deposited payment and validates seller's incoming deposit.
     * If offer is preminted, caller's funds are not encumbered, but the funds are covered from pre-deposited amounts.
     *
     * Emits FundsEncumbered event if successful.
     *
     * Reverts if:
     * - Incoming payment is in native token and caller does not send enough
     * - Incoming payment is in some ERC20 token and caller also sends native currency
     * - Contract at token address does not support ERC20 function transferFrom
     * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer)
     * - Entity has less pre-deposited funds available than required amount
     * - Received ERC20 token amount differs from the expected value
     *
     * @param _offerId - id of the offer with the details
     * @param _entityId - id of the committing entity (buyer for seller-created offers, seller for buyer-created offers)
     * @param _incomingAmount - the amount being paid by the committing entity
     * @param _isPreminted - flag indicating if the offer is preminted
     * @param _priceType - price type, either static or discovery
     */
    function encumberFunds(
        uint256 _offerId,
        uint256 _entityId,
        uint256 _incomingAmount,
        bool _isPreminted,
        BosonTypes.PriceType _priceType
    ) internal {
        // Load protocol entities storage
        ProtocolLib.ProtocolEntities storage pe = ProtocolLib.protocolEntities();

        // get message sender
        address sender = _msgSender();

        // fetch offer to get the exchange token, price and seller
        // this will be called only from commitToOffer so we expect that exchange actually exist
        BosonTypes.Offer storage offer = pe.offers[_offerId];
        address exchangeToken = offer.exchangeToken;

        if (!_isPreminted) {
            validateIncomingPayment(exchangeToken, _incomingAmount);
            emit IBosonFundsBaseEvents.FundsDeposited(_entityId, sender, exchangeToken, _incomingAmount);
            emit IBosonFundsBaseEvents.FundsEncumbered(_entityId, exchangeToken, _incomingAmount, sender);
        }

        if (offer.creator == BosonTypes.OfferCreator.Buyer) {
            decreaseAvailableFunds(offer.buyerId, exchangeToken, offer.price);
            emit IBosonFundsBaseEvents.FundsEncumbered(offer.buyerId, exchangeToken, offer.price, sender);
        } else {
            uint256 sellerId = offer.sellerId;
            bool isPriceDiscovery = _priceType == BosonTypes.PriceType.Discovery;
            uint256 sellerFundsEncumbered = offer.sellerDeposit +
                (_isPreminted && !isPriceDiscovery ? _incomingAmount : 0);
            decreaseAvailableFunds(sellerId, exchangeToken, sellerFundsEncumbered);
            emit IBosonFundsBaseEvents.FundsEncumbered(sellerId, exchangeToken, sellerFundsEncumbered, sender);
        }
    }

    /**
     * @notice Validates that incoming payments matches expectation. If token is a native currency, it makes sure
     * msg.value is correct. If token is ERC20, it transfers the value from the sender to the protocol.
     *
     * Emits ERC20 Transfer event in call stack if successful.
     *
     * Reverts if:
     * - Offer price is in native token and caller does not send enough
     * - Offer price is in some ERC20 token and caller also sends native currency
     * - Contract at token address does not support ERC20 function transferFrom
     * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer)
     * - Received ERC20 token amount differs from the expected value
     *
     * @param _exchangeToken - address of the token (0x for native currency)
     * @param _value - value expected to receive
     */
    function validateIncomingPayment(address _exchangeToken, uint256 _value) internal {
        if (_exchangeToken == address(0)) {
            // if transfer is in the native currency, msg.value must match offer price
            if (msg.value != _value) revert BosonErrors.InsufficientValueReceived();
        } else {
            // when price is in an erc20 token, transferring the native currency is not allowed
            if (msg.value != 0) revert BosonErrors.NativeNotAllowed();

            // if transfer is in ERC20 token, try to transfer the amount from buyer to the protocol
            transferFundsIn(_exchangeToken, _value);
        }
    }

    /**
     * @notice Takes in the exchange id and releases the funds to buyer, seller and dispute resolver depending on the state of the exchange.
     * It is called only from finalizeExchange and finalizeDispute.
     *
     * Emits FundsReleased and/or ProtocolFeeCollected event if payoffs are warranted and transaction is successful.
     *
     * @param _exchangeId - exchange id
     */
    function releaseFunds(uint256 _exchangeId) internal {
        // Load protocol entities storage
        ProtocolLib.ProtocolEntities storage pe = ProtocolLib.protocolEntities();

        // Get the exchange and its state
        // Since this should be called only from certain functions from exchangeHandler and disputeHandler
        // exchange must exist and be in a completed state, so that's not checked explicitly
        BosonTypes.Exchange storage exchange = pe.exchanges[_exchangeId];

        // Get offer from storage to get the details about sellerDeposit, price, sellerId, exchangeToken and buyerCancelPenalty
        BosonTypes.Offer storage offer = pe.offers[exchange.offerId];

        // calculate the payoffs depending on state exchange is in
        BosonTypes.Payoff memory payoff;

        BosonTypes.OfferFees storage offerFee = pe.offerFees[exchange.offerId];
        uint256 offerPrice = offer.priceType == BosonTypes.PriceType.Discovery ? 0 : offer.price;
        BosonTypes.ExchangeCosts[] storage exchangeCosts = pe.exchangeCosts[_exchangeId];
        uint256 lastPrice = exchangeCosts.length == 0 ? offerPrice : exchangeCosts[exchangeCosts.length - 1].price;
        {
            // scope to avoid stack too deep errors
            BosonTypes.ExchangeState exchangeState = exchange.state;
            uint256 sellerDeposit = offer.sellerDeposit;
            bool isEscalated = pe.disputeDates[_exchangeId].escalated != 0;

            if (exchangeState == BosonTypes.ExchangeState.Completed) {
                // COMPLETED
                payoff.protocol = offerFee.protocolFee;
                // buyerPayoff is 0
                payoff.agent = offerFee.agentFee;
                payoff.seller = offerPrice + sellerDeposit - payoff.protocol - payoff.agent;
            } else if (exchangeState == BosonTypes.ExchangeState.Revoked) {
                // REVOKED
                // sellerPayoff is 0
                payoff.buyer = lastPrice + sellerDeposit;
            } else if (exchangeState == BosonTypes.ExchangeState.Canceled) {
                // CANCELED
                uint256 buyerCancelPenalty = offer.buyerCancelPenalty;
                payoff.seller = sellerDeposit + buyerCancelPenalty;
                payoff.buyer = lastPrice - buyerCancelPenalty;
            } else if (exchangeState == BosonTypes.ExchangeState.Disputed) {
                // DISPUTED
                // determine if buyerEscalationDeposit was encumbered or not
                // if dispute was escalated, disputeDates.escalated is populated
                uint256 buyerEscalationDeposit = isEscalated
                    ? pe.disputeResolutionTerms[exchange.offerId].buyerEscalationDeposit
                    : 0;

                // get the information about the dispute, which must exist
                BosonTypes.Dispute storage dispute = pe.disputes[_exchangeId];
                BosonTypes.DisputeState disputeState = dispute.state;

                if (disputeState == BosonTypes.DisputeState.Retracted) {
                    // RETRACTED - same as "COMPLETED"
                    payoff.protocol = offerFee.protocolFee;
                    payoff.agent = offerFee.agentFee;
                    // buyerPayoff is 0
                    payoff.seller =
                        offerPrice +
                        sellerDeposit -
                        payoff.protocol -
                        payoff.agent +
                        buyerEscalationDeposit;

                    // DR is paid if dispute was escalated
                    payoff.disputeResolver = isEscalated ? pe.disputeResolutionTerms[exchange.offerId].feeAmount : 0;
                } else if (disputeState == BosonTypes.DisputeState.Refused) {
                    // REFUSED
                    payoff.seller = sellerDeposit;
                    payoff.buyer = lastPrice + buyerEscalationDeposit;
                    // DR is not paid when dispute is refused
                } else {
                    // RESOLVED or DECIDED
                    uint256 commonPot = sellerDeposit + buyerEscalationDeposit;
                    payoff.buyer = applyPercent(commonPot, dispute.buyerPercent);
                    payoff.seller = commonPot - payoff.buyer;

                    payoff.buyer = payoff.buyer + applyPercent(lastPrice, dispute.buyerPercent);
                    payoff.seller = payoff.seller + offerPrice - applyPercent(offerPrice, dispute.buyerPercent);

                    // DR is always paid for escalated disputes (Decided or Resolved with escalation)
                    if (isEscalated) {
                        payoff.disputeResolver = pe.disputeResolutionTerms[exchange.offerId].feeAmount;
                    }
                }
            }
        }

        address exchangeToken = offer.exchangeToken;

        // Original seller and last buyer are done
        // Release funds to intermediate sellers (if they exist)
        // and add the protocol fee to the total
        {
            (uint256 sequentialProtocolFee, uint256 sequentialRoyalties) = releaseFundsToIntermediateSellers(
                _exchangeId,
                exchange.state,
                offerPrice,
                exchangeToken,
                offer
            );
            payoff.seller += sequentialRoyalties;
            payoff.protocol += sequentialProtocolFee;
        }

        // Store payoffs to availablefunds and notify the external observers
        address sender = _msgSender();
        if (payoff.seller > 0) {
            increaseAvailableFundsAndEmitEvent(_exchangeId, offer.sellerId, exchangeToken, payoff.seller, sender);
        }
        if (payoff.buyer > 0) {
            increaseAvailableFundsAndEmitEvent(_exchangeId, exchange.buyerId, exchangeToken, payoff.buyer, sender);
        }

        if (payoff.protocol > 0) {
            increaseAvailableFunds(PROTOCOL_ENTITY_ID, exchangeToken, payoff.protocol);
            emit IBosonFundsBaseEvents.ProtocolFeeCollected(_exchangeId, exchangeToken, payoff.protocol, sender);
        }
        if (payoff.agent > 0) {
            // Get the agent for offer
            uint256 agentId = ProtocolLib.protocolLookups().agentIdByOffer[exchange.offerId];
            increaseAvailableFundsAndEmitEvent(_exchangeId, agentId, exchangeToken, payoff.agent, sender);
        }
        BosonTypes.DisputeResolutionTerms memory drTerms = pe.disputeResolutionTerms[offer.id];
        if (payoff.disputeResolver > 0) {
            increaseAvailableFundsAndEmitEvent(
                _exchangeId,
                drTerms.disputeResolverId,
                exchangeToken,
                payoff.disputeResolver,
                sender
            );
        }

        // Return unused DR fee to mutualizer or seller's pool
        if (drTerms.feeAmount != 0) {
            payoff.mutualizer = drTerms.feeAmount - payoff.disputeResolver;

            // Use exchange-level mutualizer address (locked at commitment time)
            address mutualizerAddress = exchange.mutualizerAddress;
            if (mutualizerAddress == address(0)) {
                if (payoff.mutualizer > 0) {
                    increaseAvailableFundsAndEmitEvent(
                        _exchangeId,
                        offer.sellerId,
                        exchangeToken,
                        payoff.mutualizer,
                        sender
                    );
                }
            } else {
                if (payoff.mutualizer > 0) {
                    if (exchangeToken == address(0)) {
                        exchangeToken = address(wNative);
                        wNative.deposit{ value: payoff.mutualizer }();
                    }
                    uint256 oldAllowance = IERC20(exchangeToken).allowance(address(this), mutualizerAddress);
                    IERC20(exchangeToken).forceApprove(mutualizerAddress, payoff.mutualizer + oldAllowance);
                }

                try
                    IDRFeeMutualizer(mutualizerAddress).finalizeExchange{
                        gas: ProtocolLib.protocolLimits().mutualizerGasStipend
                    }(_exchangeId, payoff.mutualizer)
                {
                    emit IBosonFundsBaseEvents.DRFeeReturned(
                        _exchangeId,
                        exchangeToken,
                        payoff.mutualizer,
                        mutualizerAddress,
                        sender
                    );
                } catch {
                    // Ignore failure to not block the main flow
                    emit IBosonFundsBaseEvents.DRFeeReturnFailed(
                        _exchangeId,
                        exchangeToken,
                        payoff.mutualizer,
                        mutualizerAddress,
                        sender
                    );
                }
            }
        }
    }

    /**
     * @notice Takes the exchange id and releases the funds to original seller if offer.priceType is Discovery
     * and to all intermediate resellers in case of sequential commit, depending on the state of the exchange.
     * It is called only from releaseFunds. Protocol fee and royalties are calculated and returned to releaseFunds, where they are added to the total.
     *
     * Emits FundsReleased events for non zero payoffs.
     *
     * @param _exchangeId - exchange id
     * @param _exchangeState - state of the exchange
     * @param _initialPrice - initial price of the offer
     * @param _exchangeToken - address of the token used for the exchange
     * @param _offer - offer struct
     * @return protocolFee - protocol fee from secondary sales
     * @return sellerRoyalties - royalties from secondary sales collected for the seller
     */
    function releaseFundsToIntermediateSellers(
        uint256 _exchangeId,
        BosonTypes.ExchangeState _exchangeState,
        uint256 _initialPrice,
        address _exchangeToken,
        BosonTypes.Offer storage _offer
    ) internal returns (uint256 protocolFee, uint256 sellerRoyalties) {
        BosonTypes.ExchangeCosts[] storage exchangeCosts;

        // calculate effective price multiplier
        uint256 effectivePriceMultiplier;
        {
            ProtocolLib.ProtocolEntities storage pe = ProtocolLib.protocolEntities();

            exchangeCosts = pe.exchangeCosts[_exchangeId];

            // if price type was static and no sequential commit happened, just return
            if (exchangeCosts.length == 0) {
                return (0, 0);
            }

            {
                if (_exchangeState == BosonTypes.ExchangeState.Completed) {
                    // COMPLETED, buyer pays full price
                    effectivePriceMultiplier = HUNDRED_PERCENT;
                } else if (
                    _exchangeState == BosonTypes.ExchangeState.Revoked ||
                    _exchangeState == BosonTypes.ExchangeState.Canceled
                ) {
                    // REVOKED or CANCELED, buyer pays nothing (buyerCancelPenalty is not considered payment)
                    effectivePriceMultiplier = 0;
                } else if (_exchangeState == BosonTypes.ExchangeState.Disputed) {
                    // DISPUTED
                    // get the information about the dispute, which must exist
                    BosonTypes.Dispute storage dispute = pe.disputes[_exchangeId];
                    BosonTypes.DisputeState disputeState = dispute.state;

                    if (disputeState == BosonTypes.DisputeState.Retracted) {
                        // RETRACTED - same as "COMPLETED"
                        effectivePriceMultiplier = HUNDRED_PERCENT;
                    } else if (disputeState == BosonTypes.DisputeState.Refused) {
                        // REFUSED, buyer pays nothing
                        effectivePriceMultiplier = 0;
                    } else {
                        // RESOLVED or DECIDED
                        effectivePriceMultiplier = HUNDRED_PERCENT - dispute.buyerPercent;
                    }
                }
            }
        }

        uint256 resellerBuyPrice = _initialPrice; // the price that reseller paid for the voucher
        address msgSender = _msgSender();
        uint256 len = exchangeCosts.length;
        for (uint256 i = 0; i < len; ) {
            // Since all elements of exchangeCosts[i] are used, it makes sense to copy them to memory
            BosonTypes.ExchangeCosts memory secondaryCommit = exchangeCosts[i];

            // amount to be released
            uint256 currentResellerAmount;

            // inside the scope to avoid stack too deep error
            {
                if (effectivePriceMultiplier > 0) {
                    protocolFee =
                        protocolFee +
                        applyPercent(secondaryCommit.protocolFeeAmount, effectivePriceMultiplier);
                    sellerRoyalties += distributeRoyalties(
                        _exchangeId,
                        _offer,
                        secondaryCommit,
                        effectivePriceMultiplier
                    );
                }

                // secondary price without protocol fee and royalties
                uint256 reducedSecondaryPrice = secondaryCommit.price -
                    secondaryCommit.protocolFeeAmount -
                    secondaryCommit.royaltyAmount;

                // Calculate amount to be released to the reseller:
                // + part of the price that they paid (relevant for unhappy paths)
                // + price of the voucher that they sold reduced for part that goes to next reseller, royalties and protocol fee
                // - immediate payout that was released already during the sequential commit
                currentResellerAmount =
                    applyPercent(resellerBuyPrice, (HUNDRED_PERCENT - effectivePriceMultiplier)) +
                    secondaryCommit.price -
                    applyPercent(secondaryCommit.price, (HUNDRED_PERCENT - effectivePriceMultiplier)) -
                    applyPercent(secondaryCommit.protocolFeeAmount, effectivePriceMultiplier) -
                    applyPercent(secondaryCommit.royaltyAmount, effectivePriceMultiplier) -
                    Math.min(resellerBuyPrice, reducedSecondaryPrice);

                resellerBuyPrice = secondaryCommit.price;
            }

            if (currentResellerAmount > 0) {
                increaseAvailableFundsAndEmitEvent(
                    _exchangeId,
                    secondaryCommit.resellerId,
                    _exchangeToken,
                    currentResellerAmount,
                    msgSender
                );
            }

            unchecked {
                i++;
            }
        }
    }

    /**
     * @notice Forwards values to increaseAvailableFunds and emits notifies external listeners.
     *
     * Emits FundsReleased events
     *
     * @param _exchangeId - exchange id
     * @param _entityId - id of the entity to which the funds are released
     * @param _tokenAddress - address of the token used for the exchange
     * @param _amount - amount of tokens to be released
     * @param _sender - address of the sender that executed the transaction
     */
    function increaseAvailableFundsAndEmitEvent(
        uint256 _exchangeId,
        uint256 _entityId,
        address _tokenAddress,
        uint256 _amount,
        address _sender
    ) internal {
        increaseAvailableFunds(_entityId, _tokenAddress, _amount);
        emit IBosonFundsBaseEvents.FundsReleased(_exchangeId, _entityId, _tokenAddress, _amount, _sender);
    }

    /**
     * @notice Tries to transfer tokens from the caller to the protocol.
     *
     * Emits ERC20 Transfer event in call stack if successful.
     *
     * Reverts if:
     * - Contract at token address does not support ERC20 function transferFrom
     * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer)
     * - Received ERC20 token amount differs from the expected value
     *
     * @param _tokenAddress - address of the token to be transferred
     * @param _from - address to transfer funds from
     * @param _amount - amount to be transferred
     */
    function transferFundsIn(address _tokenAddress, address _from, uint256 _amount) internal {
        if (_amount > 0) {
            // protocol balance before the transfer
            uint256 protocolTokenBalanceBefore = IERC20(_tokenAddress).balanceOf(address(this));
            // transfer ERC20 tokens from the caller
            IERC20(_tokenAddress).safeTransferFrom(_from, address(this), _amount);
            // protocol balance after the transfer
            uint256 protocolTokenBalanceAfter = IERC20(_tokenAddress).balanceOf(address(this));
            // make sure that expected amount of tokens was transferred
            if (protocolTokenBalanceAfter - protocolTokenBalanceBefore != _amount)
                revert BosonErrors.InsufficientValueReceived();
        }
    }

    /**
     * @notice Same as transferFundsIn(address _tokenAddress, address _from, uint256 _amount),
     * but _from is message sender
     *
     * @param _tokenAddress - address of the token to be transferred
     * @param _amount - amount to be transferred
     */
    function transferFundsIn(address _tokenAddress, uint256 _amount) internal {
        transferFundsIn(_tokenAddress, _msgSender(), _amount);
    }

    /**
     * @notice Tries to transfer native currency or tokens from the protocol to the recipient.
     *
     * Emits FundsWithdrawn event if successful.
     * Emits ERC20 Transfer event in call stack if ERC20 token is withdrawn and transfer is successful.
     *
     * Reverts if:
     * - Transfer of native currency is not successful (i.e. recipient is a contract which reverted)
     * - Contract at token address does not support ERC20 function transfer
     * - Available funds is less than amount to be decreased
     *
     * @param _entityId - id of entity for which funds should be decreased, or 0 for protocol
     * @param _tokenAddress - address of the token to be transferred
     * @param _to - address of the recipient
     * @param _amount - amount to be transferred
     */
    function transferFundsOut(uint256 _entityId, address _tokenAddress, address payable _to, uint256 _amount) internal {
        // first decrease the amount to prevent the reentrancy attack
        decreaseAvailableFunds(_entityId, _tokenAddress, _amount);

        // try to transfer the funds
        transferFundsOut(_tokenAddress, _to, _amount);

        // notify the external observers
        emit IBosonFundsBaseEvents.FundsWithdrawn(_entityId, _to, _tokenAddress, _amount, _msgSender());
    }

    /**
     * @notice Tries to transfer native currency or tokens from the protocol to the recipient.
     *
     * Emits ERC20 Transfer event in call stack if ERC20 token is withdrawn and transfer is successful.
     *
     * Reverts if:
     * - Transfer of native currency is not successful (i.e. recipient is a contract which reverted)
     * - Contract at token address does not support ERC20 function transfer
     * - Available funds is less than amount to be decreased
     *
     * @param _tokenAddress - address of the token to be transferred
     * @param _to - address of the recipient
     * @param _amount - amount to be transferred
     */
    function transferFundsOut(address _tokenAddress, address payable _to, uint256 _amount) internal {
        // try to transfer the funds
        if (_tokenAddress == address(0)) {
            // transfer native currency
            (bool success, ) = _to.call{ value: _amount }("");
            if (!success) revert BosonErrors.TokenTransferFailed();
        } else {
            // transfer ERC20 tokens
            IERC20(_tokenAddress).safeTransfer(_to, _amount);
        }
    }

    /**
     * @notice Increases the amount, available to withdraw or use as a seller deposit.
     *
     * @param _entityId - id of entity for which funds should be increased, or 0 for protocol
     * @param _tokenAddress - funds contract address or zero address for native currency
     * @param _amount - amount to be credited
     */
    function increaseAvailableFunds(uint256 _entityId, address _tokenAddress, uint256 _amount) internal {
        ProtocolLib.ProtocolLookups storage pl = ProtocolLib.protocolLookups();

        // if the current amount of token is 0, the token address must be added to the token list
        mapping(address => uint256) storage availableFunds = pl.availableFunds[_entityId];
        if (availableFunds[_tokenAddress] == 0) {
            address[] storage tokenList = pl.tokenList[_entityId];
            tokenList.push(_tokenAddress);
            //Set index mapping. Should be index in tokenList array + 1
            pl.tokenIndexByAccount[_entityId][_tokenAddress] = tokenList.length;
        }

        // update the available funds
        availableFunds[_tokenAddress] += _amount;
    }

    /**
     * @notice Decreases the amount available to withdraw or use as a seller deposit.
     *
     * Reverts if:
     * - Available funds is less than amount to be decreased
     *
     * @param _entityId - id of entity for which funds should be decreased, or 0 for protocol
     * @param _tokenAddress - funds contract address or zero address for native currency
     * @param _amount - amount to be taken away
     */
    function decreaseAvailableFunds(uint256 _entityId, address _tokenAddress, uint256 _amount) internal {
        if (_amount > 0) {
            ProtocolLib.ProtocolLookups storage pl = ProtocolLib.protocolLookups();

            // get available funds from storage
            mapping(address => uint256) storage availableFunds = pl.availableFunds[_entityId];
            uint256 entityFunds = availableFunds[_tokenAddress];

            // make sure that seller has enough funds in the pool and reduce the available funds
            if (entityFunds < _amount) revert BosonErrors.InsufficientAvailableFunds();

            // Use unchecked to optimize execution cost. The math is safe because of the require above.
            unchecked {
                availableFunds[_tokenAddress] = entityFunds - _amount;
            }

            // if available funds are totally emptied, the token address is removed from the seller's tokenList
            if (entityFunds == _amount) {
                // Get the index in the tokenList array, which is 1 less than the tokenIndexByAccount index
                address[] storage tokenList = pl.tokenList[_entityId];
                uint256 lastTokenIndex = tokenList.length - 1;
                mapping(address => uint256) storage entityTokens = pl.tokenIndexByAccount[_entityId];
                uint256 index = entityTokens[_tokenAddress] - 1;

                // if target is last index then only pop and delete are needed
                // otherwise, we overwrite the target with the last token first
                if (index != lastTokenIndex) {
                    // Need to fill gap caused by delete if more than one element in storage array
                    address tokenToMove = tokenList[lastTokenIndex];
                    // Copy the last token in the array to this index to fill the gap
                    tokenList[index] = tokenToMove;
                    // Reset index mapping. Should be index in tokenList array + 1
                    entityTokens[tokenToMove] = index + 1;
                }
                // Delete last token address in the array, which was just moved to fill the gap
                tokenList.pop();
                // Delete from index mapping
                delete entityTokens[_tokenAddress];
            }
        }
    }

    /**
     * @notice Distributes the royalties to external recipients and seller's treasury.
     *
     * @param _offer - storage pointer to the offer
     * @param _secondaryCommit - information about the secondary commit (royaltyInfoIndex, price, escrowedRoyaltyAmount)
     * @param _effectivePriceMultiplier - multiplier for the price, depending on the state of the exchange
     */
    function distributeRoyalties(
        uint256 _exchangeId,
        BosonTypes.Offer storage _offer,
        BosonTypes.ExchangeCosts memory _secondaryCommit,
        uint256 _effectivePriceMultiplier
    ) internal returns (uint256 sellerRoyalties) {
        address sender = _msgSender();
        address exchangeToken = _offer.exchangeToken;
        BosonTypes.RoyaltyInfo storage _royaltyInfo = _offer.royaltyInfo[_secondaryCommit.royaltyInfoIndex];
        uint256 len = _royaltyInfo.recipients.length;
        uint256 totalAmount;
        uint256 effectivePrice = applyPercent(_secondaryCommit.price, _effectivePriceMultiplier);
        ProtocolLib.ProtocolLookups storage pl = ProtocolLib.protocolLookups();
        for (uint256 i = 0; i < len; ) {
            address payable recipient = _royaltyInfo.recipients[i];
            uint256 amount = applyPercent(_royaltyInfo.bps[i], effectivePrice);
            totalAmount += amount;
            if (recipient == address(0)) {
                // goes to seller's treasury
                sellerRoyalties += amount;
            } else {
                // Make funds available to withdraw
                if (amount > 0) {
                    increaseAvailableFundsAndEmitEvent(
                        _exchangeId,
                        pl.royaltyRecipientIdByWallet[recipient],
                        exchangeToken,
                        amount,
                        sender
                    );
                }
            }

            unchecked {
                i++;
            }
        }

        // if there is a remainder due to rounding, it goes to the seller's treasury
        sellerRoyalties =
            sellerRoyalties +
            applyPercent(_secondaryCommit.royaltyAmount, _effectivePriceMultiplier) -
            totalAmount;
    }

    /**
     * @notice Returns the balance of the protocol for the given token address
     *
     * @param _tokenAddress - the address of the token to check the balance for
     * @return balance - the balance of the protocol for the given token address
     */
    function getBalance(address _tokenAddress) internal view returns (uint256) {
        return _tokenAddress == address(0) ? address(this).balance : IERC20(_tokenAddress).balanceOf(address(this));
    }

    /**
     * @notice Calulates the percentage of the amount.
     *
     * @param _amount - amount to be used for the calculation
     * @param _percent - percentage to be calculated, in basis points (1% = 100, 100% = 10000)
     */
    function applyPercent(uint256 _amount, uint256 _percent) internal pure returns (uint256) {
        if (_percent == HUNDRED_PERCENT) return _amount;
        if (_percent == 0) return 0;

        return (_amount * _percent) / HUNDRED_PERCENT;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "./../../domain/BosonConstants.sol";
import { BosonErrors } from "../../domain/BosonErrors.sol";
import { ProtocolLib } from "../libs/ProtocolLib.sol";
import { BosonTypes } from "../../domain/BosonTypes.sol";

/**
 * @title PausableBase
 *
 * @notice Provides modifiers for regional pausing
 */
contract PausableBase is BosonTypes {
    /**
     * @notice Modifier that checks the Offers region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier offersNotPaused() {
        revertIfPaused(PausableRegion.Offers);
        _;
    }

    /**
     * @notice Modifier that checks the Twins region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier twinsNotPaused() {
        revertIfPaused(PausableRegion.Twins);
        _;
    }

    /**
     * @notice Modifier that checks the Bundles region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier bundlesNotPaused() {
        revertIfPaused(PausableRegion.Bundles);
        _;
    }

    /**
     * @notice Modifier that checks the Groups region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier groupsNotPaused() {
        revertIfPaused(PausableRegion.Groups);
        _;
    }

    /**
     * @notice Modifier that checks the Sellers region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier sellersNotPaused() {
        revertIfPaused(PausableRegion.Sellers);
        _;
    }

    /**
     * @notice Modifier that checks the Buyers region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier buyersNotPaused() {
        revertIfPaused(PausableRegion.Buyers);
        _;
    }

    /**
     * @notice Modifier that checks the Agents region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier agentsNotPaused() {
        revertIfPaused(PausableRegion.Agents);
        _;
    }

    /**
     * @notice Modifier that checks the DisputeResolvers region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier disputeResolversNotPaused() {
        revertIfPaused(PausableRegion.DisputeResolvers);
        _;
    }

    /**
     * @notice Modifier that checks the Exchanges region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier exchangesNotPaused() {
        revertIfPaused(PausableRegion.Exchanges);
        _;
    }

    /**
     * @notice Modifier that checks the Disputes region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier disputesNotPaused() {
        revertIfPaused(PausableRegion.Disputes);
        _;
    }

    /**
     * @notice Modifier that checks the Funds region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier fundsNotPaused() {
        revertIfPaused(PausableRegion.Funds);
        _;
    }

    /**
     * @notice Modifier that checks the Orchestration region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier orchestrationNotPaused() {
        revertIfPaused(PausableRegion.Orchestration);
        _;
    }

    /**
     * @notice Modifier that checks the MetaTransaction region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier metaTransactionsNotPaused() {
        revertIfPaused(PausableRegion.MetaTransaction);
        _;
    }

    /**
     * @notice Modifier that checks the PriceDiscovery region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier priceDiscoveryNotPaused() {
        revertIfPaused(PausableRegion.PriceDiscovery);
        _;
    }

    /**
     * @notice Modifier that checks the SequentialCommit region is not paused
     *
     * Reverts if region is paused
     *
     * See: {BosonTypes.PausableRegion}
     */
    modifier sequentialCommitNotPaused() {
        revertIfPaused(PausableRegion.SequentialCommit);
        _;
    }

    /**
     * @notice Checks if a region of the protocol is paused.
     *
     * Reverts if region is paused
     *
     * @param _region the region to check pause status for
     */
    function revertIfPaused(PausableRegion _region) internal view {
        // Region enum value must be used as the exponent in a power of 2
        uint256 powerOfTwo = 1 << uint256(_region);
        if ((ProtocolLib.protocolStatus().pauseScenario & powerOfTwo) == powerOfTwo)
            revert BosonErrors.RegionPaused(_region);
    }
}

File 23 of 25 : ProtocolBase.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.22;

import "../../domain/BosonConstants.sol";
import { BosonErrors } from "../../domain/BosonErrors.sol";
import { ProtocolLib } from "../libs/ProtocolLib.sol";
import { DiamondLib } from "../../diamond/DiamondLib.sol";
import { BosonTypes } from "../../domain/BosonTypes.sol";
import { PausableBase } from "./PausableBase.sol";
import { FundsBase } from "./FundsBase.sol";
import { ReentrancyGuardBase } from "./ReentrancyGuardBase.sol";

/**
 * @title ProtocolBase
 *
 * @notice Provides domain and common modifiers to Protocol facets
 */
abstract contract ProtocolBase is PausableBase, FundsBase, ReentrancyGuardBase, BosonErrors {
    /**
     * @notice Modifier to protect initializer function from being invoked twice.
     */
    modifier onlyUninitialized(bytes4 interfaceId) {
        ProtocolLib.ProtocolStatus storage ps = protocolStatus();
        if (ps.initializedInterfaces[interfaceId]) revert AlreadyInitialized();
        ps.initializedInterfaces[interfaceId] = true;
        _;
    }

    /**
     * @notice Modifier that checks that the caller has a specific role.
     *
     * Reverts if caller doesn't have role.
     *
     * See: {AccessController.hasRole}
     *
     * @param _role - the role to check
     */
    modifier onlyRole(bytes32 _role) {
        DiamondLib.DiamondStorage storage ds = DiamondLib.diamondStorage();
        if (!ds.accessController.hasRole(_role, _msgSender())) revert AccessDenied();
        _;
    }

    /**
     * @notice Get the Protocol Addresses slot
     *
     * @return pa - the Protocol Addresses slot
     */
    function protocolAddresses() internal pure returns (ProtocolLib.ProtocolAddresses storage pa) {
        pa = ProtocolLib.protocolAddresses();
    }

    /**
     * @notice Get the Protocol Limits slot
     *
     * @return pl - the Protocol Limits slot
     */
    function protocolLimits() internal pure returns (ProtocolLib.ProtocolLimits storage pl) {
        pl = ProtocolLib.protocolLimits();
    }

    /**
     * @notice Get the Protocol Entities slot
     *
     * @return pe - the Protocol Entities slot
     */
    function protocolEntities() internal pure returns (ProtocolLib.ProtocolEntities storage pe) {
        pe = ProtocolLib.protocolEntities();
    }

    /**
     * @notice Get the Protocol Lookups slot
     *
     * @return pl - the Protocol Lookups slot
     */
    function protocolLookups() internal pure returns (ProtocolLib.ProtocolLookups storage pl) {
        pl = ProtocolLib.protocolLookups();
    }

    /**
     * @notice Get the Protocol Fees slot
     *
     * @return pf - the Protocol Fees slot
     */
    function protocolFees() internal pure returns (ProtocolLib.ProtocolFees storage pf) {
        pf = ProtocolLib.protocolFees();
    }

    /**
     * @notice Get the Protocol Counters slot
     *
     * @return pc the Protocol Counters slot
     */
    function protocolCounters() internal pure returns (ProtocolLib.ProtocolCounters storage pc) {
        pc = ProtocolLib.protocolCounters();
    }

    /**
     * @notice Get the Protocol meta-transactions storage slot
     *
     * @return pmti the Protocol meta-transactions storage slot
     */
    function protocolMetaTxInfo() internal pure returns (ProtocolLib.ProtocolMetaTxInfo storage pmti) {
        pmti = ProtocolLib.protocolMetaTxInfo();
    }

    /**
     * @notice Get the Protocol Status slot
     *
     * @return ps the Protocol Status slot
     */
    function protocolStatus() internal pure returns (ProtocolLib.ProtocolStatus storage ps) {
        ps = ProtocolLib.protocolStatus();
    }

    /**
     * @notice Gets a seller id from storage by assistant address
     *
     * @param _assistant - the assistant address of the seller
     * @return exists - whether the seller id exists
     * @return sellerId  - the seller id
     */
    function getSellerIdByAssistant(address _assistant) internal view returns (bool exists, uint256 sellerId) {
        // Get the seller id
        sellerId = protocolLookups().sellerIdByAssistant[_assistant];

        // Determine existence
        exists = (sellerId > 0);
    }

    /**
     * @notice Gets a seller id from storage by admin address
     *
     * @param _admin - the admin address of the seller
     * @return exists - whether the seller id exists
     * @return sellerId  - the seller id
     */
    function getSellerIdByAdmin(address _admin) internal view returns (bool exists, uint256 sellerId) {
        // Get the seller id
        sellerId = protocolLookups().sellerIdByAdmin[_admin];

        // Determine existence
        exists = (sellerId > 0);
    }

    /**
     * @notice Gets a seller id from storage by auth token.  A seller will have either an admin address or an auth token
     *
     * @param _authToken - the potential _authToken of the seller.
     * @return exists - whether the seller id exists
     * @return sellerId  - the seller id
     */
    function getSellerIdByAuthToken(
        AuthToken calldata _authToken
    ) internal view returns (bool exists, uint256 sellerId) {
        // Get the seller id
        sellerId = protocolLookups().sellerIdByAuthToken[_authToken.tokenType][_authToken.tokenId];

        // Determine existence
        exists = (sellerId > 0);
    }

    /**
     * @notice Gets a buyer id from storage by wallet address
     *
     * @param _wallet - the wallet address of the buyer
     * @return exists - whether the buyer id exists
     * @return buyerId  - the buyer id
     */
    function getBuyerIdByWallet(address _wallet) internal view returns (bool exists, uint256 buyerId) {
        // Get the buyer id
        buyerId = protocolLookups().buyerIdByWallet[_wallet];

        // Determine existence
        exists = (buyerId > 0);
    }

    /**
     * @notice Gets a agent id from storage by wallet address
     *
     * @param _wallet - the wallet address of the buyer
     * @return exists - whether the buyer id exists
     * @return agentId  - the buyer id
     */
    function getAgentIdByWallet(address _wallet) internal view returns (bool exists, uint256 agentId) {
        // Get the buyer id
        agentId = protocolLookups().agentIdByWallet[_wallet];

        // Determine existence
        exists = (agentId > 0);
    }

    /**
     * @notice Gets a dispute resolver id from storage by assistant address
     *
     * @param _assistant - the assistant address of the dispute resolver
     * @return exists - whether the dispute resolver id exists
     * @return disputeResolverId  - the dispute resolver  id
     */
    function getDisputeResolverIdByAssistant(
        address _assistant
    ) internal view returns (bool exists, uint256 disputeResolverId) {
        // Get the dispute resolver id
        disputeResolverId = protocolLookups().disputeResolverIdByAssistant[_assistant];

        // Determine existence
        exists = (disputeResolverId > 0);
    }

    /**
     * @notice Gets a dispute resolver id from storage by admin address
     *
     * @param _admin - the admin address of the dispute resolver
     * @return exists - whether the dispute resolver id exists
     * @return disputeResolverId  - the dispute resolver id
     */
    function getDisputeResolverIdByAdmin(
        address _admin
    ) internal view returns (bool exists, uint256 disputeResolverId) {
        // Get the dispute resolver id
        disputeResolverId = protocolLookups().disputeResolverIdByAdmin[_admin];

        // Determine existence
        exists = (disputeResolverId > 0);
    }

    /**
     * @notice Gets a group id from storage by offer id
     *
     * @param _offerId - the offer id
     * @return exists - whether the group id exists
     * @return groupId  - the group id.
     */
    function getGroupIdByOffer(uint256 _offerId) internal view returns (bool exists, uint256 groupId) {
        // Get the group id
        groupId = protocolLookups().groupIdByOffer[_offerId];

        // Determine existence
        exists = (groupId > 0);
    }

    /**
     * @notice Fetches a given seller from storage by id
     *
     * @param _sellerId - the id of the seller
     * @return exists - whether the seller exists
     * @return seller - the seller details. See {BosonTypes.Seller}
     * @return authToken - optional AuthToken struct that specifies an AuthToken type and tokenId that the user can use to do admin functions
     */
    function fetchSeller(
        uint256 _sellerId
    ) internal view returns (bool exists, Seller storage seller, AuthToken storage authToken) {
        // Cache protocol entities for reference
        ProtocolLib.ProtocolEntities storage entities = protocolEntities();

        // Get the seller's slot
        seller = entities.sellers[_sellerId];

        //Get the seller's auth token's slot
        authToken = entities.authTokens[_sellerId];

        // Determine existence
        exists = (_sellerId > 0 && seller.id == _sellerId);
    }

    /**
     * @notice Fetches a given buyer from storage by id
     *
     * @param _buyerId - the id of the buyer
     * @return exists - whether the buyer exists
     * @return buyer - the buyer details. See {BosonTypes.Buyer}
     */
    function fetchBuyer(uint256 _buyerId) internal view returns (bool exists, BosonTypes.Buyer storage buyer) {
        // Get the buyer's slot
        buyer = protocolEntities().buyers[_buyerId];

        // Determine existence
        exists = (_buyerId > 0 && buyer.id == _buyerId);
    }

    /**
     * @notice Fetches a given dispute resolver from storage by id
     *
     * @param _disputeResolverId - the id of the dispute resolver
     * @return exists - whether the dispute resolver exists
     * @return disputeResolver - the dispute resolver details. See {BosonTypes.DisputeResolver}
     * @return disputeResolverFees - list of fees dispute resolver charges per token type. Zero address is native currency. See {BosonTypes.DisputeResolverFee}
     */
    function fetchDisputeResolver(
        uint256 _disputeResolverId
    )
        internal
        view
        returns (
            bool exists,
            BosonTypes.DisputeResolver storage disputeResolver,
            BosonTypes.DisputeResolverFee[] storage disputeResolverFees
        )
    {
        // Cache protocol entities for reference
        ProtocolLib.ProtocolEntities storage entities = protocolEntities();

        // Get the dispute resolver's slot
        disputeResolver = entities.disputeResolvers[_disputeResolverId];

        //Get dispute resolver's fee list slot
        disputeResolverFees = entities.disputeResolverFees[_disputeResolverId];

        // Determine existence
        exists = (_disputeResolverId > 0 && disputeResolver.id == _disputeResolverId);
    }

    /**
     * @notice Fetches a given agent from storage by id
     *
     * @param _agentId - the id of the agent
     * @return exists - whether the agent exists
     * @return agent - the agent details. See {BosonTypes.Agent}
     */
    function fetchAgent(uint256 _agentId) internal view returns (bool exists, BosonTypes.Agent storage agent) {
        // Get the agent's slot
        agent = protocolEntities().agents[_agentId];

        // Determine existence
        exists = (_agentId > 0 && agent.id == _agentId);
    }

    /**
     * @notice Fetches a given offer from storage by id
     *
     * @param _offerId - the id of the offer
     * @return exists - whether the offer exists
     * @return offer - the offer details. See {BosonTypes.Offer}
     */
    function fetchOffer(uint256 _offerId) internal view returns (bool exists, Offer storage offer) {
        // Get the offer's slot
        offer = protocolEntities().offers[_offerId];

        // Determine existence
        exists = (_offerId > 0 && offer.id == _offerId);
    }

    /**
     * @notice Fetches the offer dates from storage by offer id
     *
     * @param _offerId - the id of the offer
     * @return offerDates - the offer dates details. See {BosonTypes.OfferDates}
     */
    function fetchOfferDates(uint256 _offerId) internal view returns (BosonTypes.OfferDates storage offerDates) {
        // Get the offerDates slot
        offerDates = protocolEntities().offerDates[_offerId];
    }

    /**
     * @notice Fetches the offer durations from storage by offer id
     *
     * @param _offerId - the id of the offer
     * @return offerDurations - the offer durations details. See {BosonTypes.OfferDurations}
     */
    function fetchOfferDurations(
        uint256 _offerId
    ) internal view returns (BosonTypes.OfferDurations storage offerDurations) {
        // Get the offer's slot
        offerDurations = protocolEntities().offerDurations[_offerId];
    }

    /**
     * @notice Fetches the dispute resolution terms from storage by offer id
     *
     * @param _offerId - the id of the offer
     * @return disputeResolutionTerms - the details about the dispute resolution terms. See {BosonTypes.DisputeResolutionTerms}
     */
    function fetchDisputeResolutionTerms(
        uint256 _offerId
    ) internal view returns (BosonTypes.DisputeResolutionTerms storage disputeResolutionTerms) {
        // Get the disputeResolutionTerms slot
        disputeResolutionTerms = protocolEntities().disputeResolutionTerms[_offerId];
    }

    /**
     * @notice Fetches a given group from storage by id
     *
     * @param _groupId - the id of the group
     * @return exists - whether the group exists
     * @return group - the group details. See {BosonTypes.Group}
     */
    function fetchGroup(uint256 _groupId) internal view returns (bool exists, Group storage group) {
        // Get the group's slot
        group = protocolEntities().groups[_groupId];

        // Determine existence
        exists = (_groupId > 0 && group.id == _groupId);
    }

    /**
     * @notice Fetches the Condition from storage by group id
     *
     * @param _groupId - the id of the group
     * @return condition - the condition details. See {BosonTypes.Condition}
     */
    function fetchCondition(uint256 _groupId) internal view returns (BosonTypes.Condition storage condition) {
        // Get the offerDates slot
        condition = protocolEntities().conditions[_groupId];
    }

    /**
     * @notice Fetches a given exchange from storage by id
     *
     * @param _exchangeId - the id of the exchange
     * @return exists - whether the exchange exists
     * @return exchange - the exchange details. See {BosonTypes.Exchange}
     */
    function fetchExchange(uint256 _exchangeId) internal view returns (bool exists, Exchange storage exchange) {
        // Get the exchange's slot
        exchange = protocolEntities().exchanges[_exchangeId];

        // Determine existence
        exists = (_exchangeId > 0 && exchange.id == _exchangeId);
    }

    /**
     * @notice Fetches a given voucher from storage by exchange id
     *
     * @param _exchangeId - the id of the exchange associated with the voucher
     * @return voucher - the voucher details. See {BosonTypes.Voucher}
     */
    function fetchVoucher(uint256 _exchangeId) internal view returns (Voucher storage voucher) {
        // Get the voucher
        voucher = protocolEntities().vouchers[_exchangeId];
    }

    /**
     * @notice Fetches a given dispute from storage by exchange id
     *
     * @param _exchangeId - the id of the exchange associated with the dispute
     * @return exists - whether the dispute exists
     * @return dispute - the dispute details. See {BosonTypes.Dispute}
     */
    function fetchDispute(
        uint256 _exchangeId
    ) internal view returns (bool exists, Dispute storage dispute, DisputeDates storage disputeDates) {
        // Cache protocol entities for reference
        ProtocolLib.ProtocolEntities storage entities = protocolEntities();

        // Get the dispute's slot
        dispute = entities.disputes[_exchangeId];

        // Get the disputeDates slot
        disputeDates = entities.disputeDates[_exchangeId];

        // Determine existence
        exists = (_exchangeId > 0 && dispute.exchangeId == _exchangeId);
    }

    /**
     * @notice Fetches a given twin from storage by id
     *
     * @param _twinId - the id of the twin
     * @return exists - whether the twin exists
     * @return twin - the twin details. See {BosonTypes.Twin}
     */
    function fetchTwin(uint256 _twinId) internal view returns (bool exists, Twin storage twin) {
        // Get the twin's slot
        twin = protocolEntities().twins[_twinId];

        // Determine existence
        exists = (_twinId > 0 && twin.id == _twinId);
    }

    /**
     * @notice Fetches a given bundle from storage by id
     *
     * @param _bundleId - the id of the bundle
     * @return exists - whether the bundle exists
     * @return bundle - the bundle details. See {BosonTypes.Bundle}
     */
    function fetchBundle(uint256 _bundleId) internal view returns (bool exists, Bundle storage bundle) {
        // Get the bundle's slot
        bundle = protocolEntities().bundles[_bundleId];

        // Determine existence
        exists = (_bundleId > 0 && bundle.id == _bundleId);
    }

    /**
     * @notice Gets offer from protocol storage, makes sure it exist and not voided
     *
     * Reverts if:
     * - Offer does not exist
     * - Offer already voided
     *
     *  @param _offerId - the id of the offer to check
     */
    function getValidOffer(uint256 _offerId) internal view returns (Offer storage offer) {
        bool exists;

        // Get offer
        (exists, offer) = fetchOffer(_offerId);

        // Offer must already exist
        if (!exists) revert NoSuchOffer();

        // Offer must not already be voided
        if (offer.voided) revert OfferHasBeenVoided();
    }

    /**
     * @notice Gets offer and seller from protocol storage
     *
     * Reverts if:
     * - Offer does not exist
     * - Offer already voided
     * - Seller assistant is not the caller
     *
     *  @param _offerId - the id of the offer to check
     *  @return offer - the offer details. See {BosonTypes.Offer}
     */
    function getValidOfferWithSellerCheck(uint256 _offerId) internal view returns (Offer storage offer) {
        // Get offer
        offer = getValidOffer(_offerId);

        // Get seller, we assume seller exists if offer exists
        (, Seller storage seller, ) = fetchSeller(offer.sellerId);

        // Caller must be seller's assistant address
        if (seller.assistant != _msgSender()) revert NotAssistant();
    }

    /**
     * @notice Gets a valid offer from storage and checks that the caller is authorized to modify it
     *
     * Reverts if:
     * - Offer id is invalid
     * - Offer has already been voided
     * - Caller is not authorized (for seller-created offers: not the seller assistant; for buyer-created offers: not the buyer who created it)
     *
     *  @param _offerId - the id of the offer to check
     *  @return offer - the offer details. See {BosonTypes.Offer}
     *  @return creatorId - the id of the creator (sellerId for seller-created offers, buyerId for buyer-created offers)
     */
    function getValidOfferWithCreatorCheck(
        uint256 _offerId
    ) internal view returns (Offer storage offer, uint256 creatorId) {
        // Get offer
        offer = getValidOffer(_offerId);

        if (offer.creator == OfferCreator.Seller) {
            // For seller-created offers, check that caller is the seller's assistant
            (, Seller storage seller, ) = fetchSeller(offer.sellerId);
            if (seller.assistant != _msgSender()) revert NotAssistant();
            creatorId = offer.sellerId;
        } else if (offer.creator == OfferCreator.Buyer) {
            // For buyer-created offers, check that caller is the buyer who created the offer
            (, Buyer storage buyer) = fetchBuyer(offer.buyerId);
            if (buyer.wallet != _msgSender()) revert NotOfferCreator();
            creatorId = offer.buyerId;
        }
    }

    /**
     * @notice Gets the bundle id for a given offer id.
     *
     * @param _offerId - the offer id.
     * @return exists - whether the bundle id exists
     * @return bundleId  - the bundle id.
     */
    function fetchBundleIdByOffer(uint256 _offerId) internal view returns (bool exists, uint256 bundleId) {
        // Get the bundle id
        bundleId = protocolLookups().bundleIdByOffer[_offerId];

        // Determine existence
        exists = (bundleId > 0);
    }

    /**
     * @notice Gets the bundle id for a given twin id.
     *
     * @param _twinId - the twin id.
     * @return exists - whether the bundle id exist
     * @return bundleId  - the bundle id.
     */
    function fetchBundleIdByTwin(uint256 _twinId) internal view returns (bool exists, uint256 bundleId) {
        // Get the bundle id
        bundleId = protocolLookups().bundleIdByTwin[_twinId];

        // Determine existence
        exists = (bundleId > 0);
    }

    /**
     * @notice Gets the exchange ids for a given offer id.
     *
     * @param _offerId - the offer id.
     * @return exists - whether the exchange Ids exist
     * @return exchangeIds  - the exchange Ids.
     */
    function getExchangeIdsByOffer(
        uint256 _offerId
    ) internal view returns (bool exists, uint256[] storage exchangeIds) {
        // Get the exchange Ids
        exchangeIds = protocolLookups().exchangeIdsByOffer[_offerId];

        // Determine existence
        exists = (exchangeIds.length > 0);
    }

    /**
     * @notice Make sure the caller is buyer associated with the exchange
     *
     * Reverts if
     * - caller is not the buyer associated with exchange
     *
     * @param _currentBuyer - id of current buyer associated with the exchange
     */
    function checkBuyer(uint256 _currentBuyer) internal view {
        // Get the caller's buyer account id
        (, uint256 buyerId) = getBuyerIdByWallet(_msgSender());

        // Must be the buyer associated with the exchange (which is always voucher holder)
        if (buyerId != _currentBuyer) revert NotVoucherHolder();
    }

    /**
     * @notice Get a valid exchange and its associated voucher
     *
     * Reverts if
     * - Exchange does not exist
     * - Exchange is not in the expected state
     *
     * @param _exchangeId - the id of the exchange to complete
     * @param _expectedState - the state the exchange should be in
     * @return exchange - the exchange
     * @return voucher - the voucher
     */
    function getValidExchange(
        uint256 _exchangeId,
        ExchangeState _expectedState
    ) internal view returns (Exchange storage exchange, Voucher storage voucher) {
        // Get the exchange
        bool exchangeExists;
        (exchangeExists, exchange) = fetchExchange(_exchangeId);

        // Make sure the exchange exists
        if (!exchangeExists) revert NoSuchExchange();

        // Make sure the exchange is in expected state
        if (exchange.state != _expectedState) revert InvalidState();

        // Get the voucher
        voucher = fetchVoucher(_exchangeId);
    }

    uint256 private constant ADDRESS_LENGTH = 20;

    /**
     * @notice Returns the current sender address.
     */
    function _msgSender() internal view override returns (address) {
        uint256 msgDataLength = msg.data.length;

        if (msg.sender == address(this) && msgDataLength >= ADDRESS_LENGTH) {
            unchecked {
                return address(bytes20(msg.data[msgDataLength - ADDRESS_LENGTH:]));
            }
        } else {
            return msg.sender;
        }
    }

    /**
     * @notice Gets the agent id for a given offer id.
     *
     * @param _offerId - the offer id.
     * @return exists - whether the exchange id exist
     * @return agentId - the agent id.
     */
    function fetchAgentIdByOffer(uint256 _offerId) internal view returns (bool exists, uint256 agentId) {
        // Get the agent id
        agentId = protocolLookups().agentIdByOffer[_offerId];

        // Determine existence
        exists = (agentId > 0);
    }

    /**
     * @notice Fetches the offer fees from storage by offer id
     *
     * @param _offerId - the id of the offer
     * @return offerFees - the offer fees details. See {BosonTypes.OfferFees}
     */
    function fetchOfferFees(uint256 _offerId) internal view returns (BosonTypes.OfferFees storage offerFees) {
        // Get the offerFees slot
        offerFees = protocolEntities().offerFees[_offerId];
    }

    /**
     * @notice Fetches a list of twin receipts from storage by exchange id
     *
     * @param _exchangeId - the id of the exchange
     * @return exists - whether one or more twin receipt exists
     * @return twinReceipts - the list of twin receipts. See {BosonTypes.TwinReceipt}
     */
    function fetchTwinReceipts(
        uint256 _exchangeId
    ) internal view returns (bool exists, TwinReceipt[] storage twinReceipts) {
        // Get the twin receipts slot
        twinReceipts = protocolLookups().twinReceiptsByExchange[_exchangeId];

        // Determine existence
        exists = (_exchangeId > 0 && twinReceipts.length > 0);
    }

    /**
     * @notice Fetches a condition from storage by exchange id
     *
     * @param _exchangeId - the id of the exchange
     * @return exists - whether one condition exists for the exchange
     * @return condition - the condition. See {BosonTypes.Condition}
     */
    function fetchConditionByExchange(
        uint256 _exchangeId
    ) internal view returns (bool exists, Condition storage condition) {
        // Get the condition slot
        condition = protocolLookups().exchangeCondition[_exchangeId];

        // Determine existence
        exists = (_exchangeId > 0 && condition.method != EvaluationMethod.None);
    }

    /**
     * @notice calculate the protocol fee amount for a given exchange
     *
     * @param _exchangeToken - the token used for the exchange
     * @param _price - the price of the exchange
     * @return protocolFee - the protocol fee
     */
    function _getProtocolFee(address _exchangeToken, uint256 _price) internal view returns (uint256 protocolFee) {
        // Check if the exchange token is the Boson token
        if (_exchangeToken == protocolAddresses().token) {
            // Return the flatBoson fee percentage if the exchange token is the Boson token
            return protocolFees().flatBoson;
        }
        uint256 feePercentage = _getFeePercentage(_exchangeToken, _price);
        return applyPercent(_price, feePercentage);
    }

    /**
     * @notice calculate the protocol fee percentage for a given exchange
     *
     * @param _exchangeToken - the token used for the exchange
     * @param _price - the price of the exchange
     * @return feePercentage - the protocol fee percentage based on token price (using protocol fee table)
     */
    function _getFeePercentage(address _exchangeToken, uint256 _price) internal view returns (uint256 feePercentage) {
        if (_exchangeToken == protocolAddresses().token) revert FeeTableAssetNotSupported();

        ProtocolLib.ProtocolFees storage fees = protocolFees();
        uint256[] storage priceRanges = fees.tokenPriceRanges[_exchangeToken];
        uint256[] storage feePercentages = fees.tokenFeePercentages[_exchangeToken];

        // If the token has a custom fee table, find the appropriate percentage
        uint256 priceRangesLength = priceRanges.length;
        if (priceRangesLength > 0) {
            unchecked {
                uint256 i;
                for (; i < priceRangesLength - 1; ++i) {
                    if (_price <= priceRanges[i]) {
                        // Return the fee percentage for the matching price range
                        return feePercentages[i];
                    }
                }

                // If price exceeds all ranges, use the highest fee percentage
                return feePercentages[i];
            }
        }

        // If no custom fee table exists, fallback to using the default protocol percentage
        return fees.percentage;
    }

    /**
     * @notice Fetches a clone address from storage by seller id and collection index
     * If the collection index is 0, the clone address is the seller's main collection,
     * otherwise it is the clone address of the additional collection at the given index.
     *
     * @param _lookups - storage slot for protocol lookups
     * @param _sellerId - the id of the seller
     * @param _collectionIndex - the index of the collection
     * @return cloneAddress - the clone address
     */
    function getCloneAddress(
        ProtocolLib.ProtocolLookups storage _lookups,
        uint256 _sellerId,
        uint256 _collectionIndex
    ) internal view returns (address cloneAddress) {
        return
            _collectionIndex == 0
                ? _lookups.cloneAddress[_sellerId]
                : _lookups.additionalCollections[_sellerId][_collectionIndex - 1].collectionAddress;
    }

    /**
     * @notice Internal helper to get royalty information and seller for a chosen exchange.
     *
     * Reverts if exchange does not exist.
     *
     * @param _queryId - offer id or exchange id
     * @param _isExchangeId - indicates if the query represents the exchange id
     * @return royaltyInfo - list of royalty recipients and corresponding bps
     * @return royaltyInfoIndex - index of the royalty info
     * @return treasury - the seller's treasury address
     */
    function fetchRoyalties(
        uint256 _queryId,
        bool _isExchangeId
    ) internal view returns (RoyaltyInfo storage royaltyInfo, uint256 royaltyInfoIndex, address treasury) {
        RoyaltyInfo[] storage royaltyInfoAll;
        if (_isExchangeId) {
            (bool exists, Exchange storage exchange) = fetchExchange(_queryId);
            if (!exists) revert NoSuchExchange();
            _queryId = exchange.offerId;
        }

        // not using fetchOffer to reduce gas costs (limitation of royalty registry)
        ProtocolLib.ProtocolEntities storage pe = protocolEntities();
        Offer storage offer = pe.offers[_queryId];
        treasury = pe.sellers[offer.sellerId].treasury;
        royaltyInfoAll = pe.offers[_queryId].royaltyInfo;

        uint256 royaltyInfoLength = royaltyInfoAll.length;
        if (royaltyInfoLength == 0) revert NoSuchOffer();
        royaltyInfoIndex = royaltyInfoLength - 1;
        // get the last royalty info
        return (royaltyInfoAll[royaltyInfoIndex], royaltyInfoIndex, treasury);
    }

    /**
     * @notice Helper function that calculates the total royalty percentage for a given exchange
     *
     * @param _bps - storage slot for array of royalty percentages
     * @return totalBps - the total royalty percentage
     */
    function getTotalRoyaltyPercentage(uint256[] storage _bps) internal view returns (uint256 totalBps) {
        uint256 bpsLength = _bps.length;
        for (uint256 i = 0; i < bpsLength; ) {
            totalBps += _bps[i];

            unchecked {
                i++;
            }
        }
    }
}

File 24 of 25 : ReentrancyGuardBase.sol
// SPDX-License-Identifier: MIT
import "../../domain/BosonConstants.sol";
import { BosonErrors } from "../../domain/BosonErrors.sol";
import { ProtocolLib } from "../libs/ProtocolLib.sol";

pragma solidity 0.8.22;

/**
 * @notice Contract module that helps prevent reentrant calls to a function.
 *
 * The majority of code, comments and general idea is taken from OpenZeppelin implementation.
 * Code was adjusted to work with the storage layout used in the protocol.
 * Reference implementation: OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
 *
 * 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.
 *
 * @dev 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 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 ReentrancyGuardBase {
    /**
     * @notice 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() {
        ProtocolLib.ProtocolStatus storage ps = ProtocolLib.protocolStatus();
        // On the first call to nonReentrant, ps.reentrancyStatus will be NOT_ENTERED
        if (ps.reentrancyStatus == ENTERED) revert BosonErrors.ReentrancyGuard();

        // Any calls to nonReentrant after this point will fail
        ps.reentrancyStatus = ENTERED;

        _;

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

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import { BosonTypes } from "../../domain/BosonTypes.sol";

/**
 * @title ProtocolLib
 *
 * @notice Provides access to the protocol addresses, limits, entities, fees, counters, initializers and  metaTransactions slots for Facets.
 */
library ProtocolLib {
    bytes32 internal constant PROTOCOL_ADDRESSES_POSITION = keccak256("boson.protocol.addresses");
    bytes32 internal constant PROTOCOL_LIMITS_POSITION = keccak256("boson.protocol.limits");
    bytes32 internal constant PROTOCOL_ENTITIES_POSITION = keccak256("boson.protocol.entities");
    bytes32 internal constant PROTOCOL_LOOKUPS_POSITION = keccak256("boson.protocol.lookups");
    bytes32 internal constant PROTOCOL_FEES_POSITION = keccak256("boson.protocol.fees");
    bytes32 internal constant PROTOCOL_COUNTERS_POSITION = keccak256("boson.protocol.counters");
    bytes32 internal constant PROTOCOL_STATUS_POSITION = keccak256("boson.protocol.initializers");
    bytes32 internal constant PROTOCOL_META_TX_POSITION = keccak256("boson.protocol.metaTransactions");

    // Protocol addresses storage
    struct ProtocolAddresses {
        // Address of the Boson Protocol treasury
        address payable treasury;
        // Address of the Boson Token (ERC-20 contract)
        address payable token;
        // Address of the Boson Protocol Voucher beacon
        address voucherBeacon;
        // Address of the Boson Beacon proxy implementation
        address beaconProxy;
        // Address of the Boson Price Discovery
        address priceDiscovery;
    }

    // Protocol limits storage
    struct ProtocolLimits {
        // limit on the resolution period that a seller can specify
        uint256 maxResolutionPeriod;
        // limit on the escalation response period that a dispute resolver can specify
        uint256 maxEscalationResponsePeriod;
        // lower limit for dispute period
        uint256 minDisputePeriod;
        // limit how many exchanges can be processed in single batch transaction
        uint16 maxExchangesPerBatch;
        // limit how many offers can be added to the group
        uint16 maxOffersPerGroup;
        // limit how many offers can be added to the bundle
        uint16 maxOffersPerBundle;
        // limit how many twins can be added to the bundle
        uint16 maxTwinsPerBundle;
        // limit how many offers can be processed in single batch transaction
        uint16 maxOffersPerBatch;
        // limit how many different tokens can be withdrawn in a single transaction
        uint16 maxTokensPerWithdrawal;
        // limit how many dispute resolver fee structs can be processed in a single transaction
        uint16 maxFeesPerDisputeResolver;
        // limit how many disputes can be processed in single batch transaction
        uint16 maxDisputesPerBatch;
        // limit how many sellers can be added to or removed from an allow list in a single transaction
        uint16 maxAllowedSellers;
        // limit the sum of (protocol fee percentage + agent fee percentage) of an offer fee
        uint16 maxTotalOfferFeePercentage;
        // limit the max royalty percentage that can be set by the seller
        uint16 maxRoyaltyPercentage;
        // limit the max number of vouchers that can be preminted in a single transaction
        uint256 maxPremintedVouchers;
        // lower limit for resolution period
        uint256 minResolutionPeriod;
        // Gas to forward when returning DR fee to mutualizer
        uint256 mutualizerGasStipend;
    }

    // Protocol fees storage
    struct ProtocolFees {
        // Default percentage that will be taken as a fee from the net of a Boson Protocol exchange.
        // This fee is returned if no fee ranges are configured in the fee table for the given asset.
        uint256 percentage; // 1.75% = 175, 100% = 10000
        // Flat fee taken for exchanges in $BOSON
        uint256 flatBoson;
        // buyer escalation deposit percentage
        uint256 buyerEscalationDepositPercentage;
        // Token-specific fee tables
        mapping(address => uint256[]) tokenPriceRanges; // Price ranges for each token
        mapping(address => uint256[]) tokenFeePercentages; // Fee percentages for each price range
    }

    // Protocol entities storage
    struct ProtocolEntities {
        // offer id => offer
        mapping(uint256 => BosonTypes.Offer) offers;
        // offer id => offer dates
        mapping(uint256 => BosonTypes.OfferDates) offerDates;
        // offer id => offer fees
        mapping(uint256 => BosonTypes.OfferFees) offerFees;
        // offer id => offer durations
        mapping(uint256 => BosonTypes.OfferDurations) offerDurations;
        // offer id => dispute resolution terms
        mapping(uint256 => BosonTypes.DisputeResolutionTerms) disputeResolutionTerms;
        // exchange id => exchange
        mapping(uint256 => BosonTypes.Exchange) exchanges;
        // exchange id => voucher
        mapping(uint256 => BosonTypes.Voucher) vouchers;
        // exchange id => dispute
        mapping(uint256 => BosonTypes.Dispute) disputes;
        // exchange id => dispute dates
        mapping(uint256 => BosonTypes.DisputeDates) disputeDates;
        // seller id => seller
        mapping(uint256 => BosonTypes.Seller) sellers;
        // buyer id => buyer
        mapping(uint256 => BosonTypes.Buyer) buyers;
        // dispute resolver id => dispute resolver
        mapping(uint256 => BosonTypes.DisputeResolver) disputeResolvers;
        // dispute resolver id => dispute resolver fee array
        mapping(uint256 => BosonTypes.DisputeResolverFee[]) disputeResolverFees;
        // agent id => agent
        mapping(uint256 => BosonTypes.Agent) agents;
        // group id => group
        mapping(uint256 => BosonTypes.Group) groups;
        // group id => condition
        mapping(uint256 => BosonTypes.Condition) conditions;
        // bundle id => bundle
        mapping(uint256 => BosonTypes.Bundle) bundles;
        // twin id => twin
        mapping(uint256 => BosonTypes.Twin) twins;
        // entity id => auth token
        mapping(uint256 => BosonTypes.AuthToken) authTokens;
        // exchange id => sequential commit info
        mapping(uint256 => BosonTypes.ExchangeCosts[]) exchangeCosts;
        // entity id => royalty recipient account
        mapping(uint256 => BosonTypes.RoyaltyRecipient) royaltyRecipients;
    }

    // Protocol lookups storage
    struct ProtocolLookups {
        // offer id => exchange ids
        mapping(uint256 => uint256[]) exchangeIdsByOffer;
        // offer id => bundle id
        mapping(uint256 => uint256) bundleIdByOffer;
        // twin id => bundle id
        mapping(uint256 => uint256) bundleIdByTwin;
        // offer id => group id
        mapping(uint256 => uint256) groupIdByOffer;
        // offer id => agent id
        mapping(uint256 => uint256) agentIdByOffer;
        // seller assistant address => sellerId
        mapping(address => uint256) sellerIdByAssistant;
        // seller admin address => sellerId
        mapping(address => uint256) sellerIdByAdmin;
        // seller clerk address => sellerId
        // @deprecated sellerIdByClerk is no longer used. Keeping it for backwards compatibility.
        mapping(address => uint256) sellerIdByClerk;
        // buyer wallet address => buyerId
        mapping(address => uint256) buyerIdByWallet;
        // dispute resolver assistant address => disputeResolverId
        mapping(address => uint256) disputeResolverIdByAssistant;
        // dispute resolver admin address => disputeResolverId
        mapping(address => uint256) disputeResolverIdByAdmin;
        // dispute resolver clerk address => disputeResolverId
        // @deprecated disputeResolverIdByClerk is no longer used. Keeping it for backwards compatibility.
        mapping(address => uint256) disputeResolverIdByClerk;
        // dispute resolver id to fee token address => index of the token address
        mapping(uint256 => mapping(address => uint256)) disputeResolverFeeTokenIndex;
        // agent wallet address => agentId
        mapping(address => uint256) agentIdByWallet;
        // account id => token address => amount
        mapping(uint256 => mapping(address => uint256)) availableFunds;
        // account id => all tokens with balance > 0
        mapping(uint256 => address[]) tokenList;
        // account id => token address => index on token addresses list
        mapping(uint256 => mapping(address => uint256)) tokenIndexByAccount;
        // seller id => cloneAddress
        mapping(uint256 => address) cloneAddress;
        // buyer id => number of active vouchers
        mapping(uint256 => uint256) voucherCount;
        // buyer address => groupId => commit count (addresses that have committed to conditional offers)
        mapping(address => mapping(uint256 => uint256)) conditionalCommitsByAddress;
        // AuthTokenType => Auth NFT contract address.
        mapping(BosonTypes.AuthTokenType => address) authTokenContracts;
        // AuthTokenType => tokenId => sellerId
        mapping(BosonTypes.AuthTokenType => mapping(uint256 => uint256)) sellerIdByAuthToken;
        // seller id => token address (only ERC721) => start and end of token ids range
        mapping(uint256 => mapping(address => BosonTypes.TokenRange[])) twinRangesBySeller;
        // seller id => token address (only ERC721) => twin ids
        // @deprecated twinIdsByTokenAddressAndBySeller is no longer used. Keeping it for backwards compatibility.
        mapping(uint256 => mapping(address => uint256[])) twinIdsByTokenAddressAndBySeller;
        // exchange id => BosonTypes.TwinReceipt
        mapping(uint256 => BosonTypes.TwinReceipt[]) twinReceiptsByExchange;
        // dispute resolver id => list of allowed sellers
        mapping(uint256 => uint256[]) allowedSellers;
        // dispute resolver id => seller id => index of allowed seller in allowedSellers
        mapping(uint256 => mapping(uint256 => uint256)) allowedSellerIndex;
        // exchange id => condition
        mapping(uint256 => BosonTypes.Condition) exchangeCondition;
        // groupId => offerId => index on Group.offerIds array
        mapping(uint256 => mapping(uint256 => uint256)) offerIdIndexByGroup;
        // seller id => Seller
        mapping(uint256 => BosonTypes.Seller) pendingAddressUpdatesBySeller;
        // seller id => AuthToken
        mapping(uint256 => BosonTypes.AuthToken) pendingAuthTokenUpdatesBySeller;
        // dispute resolver id => DisputeResolver
        mapping(uint256 => BosonTypes.DisputeResolver) pendingAddressUpdatesByDisputeResolver;
        // twin id => range id
        mapping(uint256 => uint256) rangeIdByTwin;
        // tokenId => groupId =>  commit count (count how many times a token has been used as gate for this group)
        mapping(uint256 => mapping(uint256 => uint256)) conditionalCommitsByTokenId;
        // seller id => collections
        mapping(uint256 => BosonTypes.Collection[]) additionalCollections;
        // seller id => seller salt used to create collections
        mapping(uint256 => bytes32) sellerSalt;
        // seller salt => is used
        mapping(bytes32 => bool) isUsedSellerSalt;
        // seller id => royalty recipients info
        mapping(uint256 => BosonTypes.RoyaltyRecipientInfo[]) royaltyRecipientsBySeller;
        // seller id => royalty recipient => index of royalty recipient in royaltyRecipientsBySeller
        mapping(uint256 => mapping(address => uint256)) royaltyRecipientIndexBySellerAndRecipient;
        // royalty recipient wallet address => agentId
        mapping(address => uint256) royaltyRecipientIdByWallet;
        // offer hash -> offer id
        mapping(bytes32 => uint256) offerIdByHash;
    }

    // Incrementing id counters
    struct ProtocolCounters {
        // Next account id
        uint256 nextAccountId;
        // Next offer id
        uint256 nextOfferId;
        // Next exchange id
        uint256 nextExchangeId;
        // Next twin id
        uint256 nextTwinId;
        // Next group id
        uint256 nextGroupId;
        // Next twin id
        uint256 nextBundleId;
    }

    // Storage related to Meta Transactions
    struct ProtocolMetaTxInfo {
        // [deprecated]
        bytes32 deprecatedSlot;
        // The domain Separator of the protocol
        bytes32 domainSeparator;
        // address => nonce => nonce used indicator
        mapping(address => mapping(uint256 => bool)) usedNonce;
        // The cached chain id
        uint256 cachedChainId;
        // map function name to input type
        mapping(string => BosonTypes.MetaTxInputType) inputType;
        // map input type => hash info
        mapping(BosonTypes.MetaTxInputType => BosonTypes.HashInfo) hashInfo;
        // Can function be executed using meta transactions
        mapping(bytes32 => bool) isAllowlisted;
    }

    // Individual facet initialization states
    struct ProtocolStatus {
        // the current pause scenario, a sum of PausableRegions as powers of two
        uint256 pauseScenario;
        // reentrancy status
        uint256 reentrancyStatus;
        // interface id => initialized?
        mapping(bytes4 => bool) initializedInterfaces;
        // version => initialized?
        mapping(bytes32 => bool) initializedVersions;
        // Current protocol version
        bytes32 version;
        // Incoming voucher id
        uint256 incomingVoucherId;
        // Incoming voucher clone address
        address incomingVoucherCloneAddress;
    }

    /**
     * @dev Gets the protocol addresses slot
     *
     * @return pa - the protocol addresses slot
     */
    function protocolAddresses() internal pure returns (ProtocolAddresses storage pa) {
        bytes32 position = PROTOCOL_ADDRESSES_POSITION;
        assembly {
            pa.slot := position
        }
    }

    /**
     * @notice Gets the protocol limits slot
     *
     * @return pl - the protocol limits slot
     */
    function protocolLimits() internal pure returns (ProtocolLimits storage pl) {
        bytes32 position = PROTOCOL_LIMITS_POSITION;
        assembly {
            pl.slot := position
        }
    }

    /**
     * @notice Gets the protocol entities slot
     *
     * @return pe - the protocol entities slot
     */
    function protocolEntities() internal pure returns (ProtocolEntities storage pe) {
        bytes32 position = PROTOCOL_ENTITIES_POSITION;
        assembly {
            pe.slot := position
        }
    }

    /**
     * @notice Gets the protocol lookups slot
     *
     * @return pl - the protocol lookups slot
     */
    function protocolLookups() internal pure returns (ProtocolLookups storage pl) {
        bytes32 position = PROTOCOL_LOOKUPS_POSITION;
        assembly {
            pl.slot := position
        }
    }

    /**
     * @notice Gets the protocol fees slot
     *
     * @return pf - the protocol fees slot
     */
    function protocolFees() internal pure returns (ProtocolFees storage pf) {
        bytes32 position = PROTOCOL_FEES_POSITION;
        assembly {
            pf.slot := position
        }
    }

    /**
     * @notice Gets the protocol counters slot
     *
     * @return pc - the protocol counters slot
     */
    function protocolCounters() internal pure returns (ProtocolCounters storage pc) {
        bytes32 position = PROTOCOL_COUNTERS_POSITION;
        assembly {
            pc.slot := position
        }
    }

    /**
     * @notice Gets the protocol meta-transactions storage slot
     *
     * @return pmti - the protocol meta-transactions storage slot
     */
    function protocolMetaTxInfo() internal pure returns (ProtocolMetaTxInfo storage pmti) {
        bytes32 position = PROTOCOL_META_TX_POSITION;
        assembly {
            pmti.slot := position
        }
    }

    /**
     * @notice Gets the protocol status slot
     *
     * @return ps - the the protocol status slot
     */
    function protocolStatus() internal pure returns (ProtocolStatus storage ps) {
        bytes32 position = PROTOCOL_STATUS_POSITION;
        assembly {
            ps.slot := position
        }
    }
}

Settings
{
  "viaIR": false,
  "optimizer": {
    "enabled": true,
    "runs": 100,
    "details": {
      "yul": true
    }
  },
  "evmVersion": "shanghai",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[],"name":"AccessDenied","type":"error"},{"inputs":[],"name":"AddressesAndCalldataLengthMismatch","type":"error"},{"inputs":[],"name":"AdminOrAuthToken","type":"error"},{"inputs":[],"name":"AgentAddressMustBeUnique","type":"error"},{"inputs":[],"name":"AgentFeeAmountTooHigh","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"AmbiguousVoucherExpiry","type":"error"},{"inputs":[],"name":"AmountExceedsRangeOrNothingToBurn","type":"error"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"AuthTokenMustBeUnique","type":"error"},{"inputs":[],"name":"BundleForTwinExists","type":"error"},{"inputs":[],"name":"BundleOfferMustBeUnique","type":"error"},{"inputs":[],"name":"BundleRequiresAtLeastOneTwinAndOneOffer","type":"error"},{"inputs":[],"name":"BundleTwinMustBeUnique","type":"error"},{"inputs":[],"name":"BuyerAddressMustBeUnique","type":"error"},{"inputs":[],"name":"CannotCommit","type":"error"},{"inputs":[],"name":"CannotRemoveDefaultRecipient","type":"error"},{"inputs":[],"name":"ClerkDeprecated","type":"error"},{"inputs":[],"name":"CloneCreationFailed","type":"error"},{"inputs":[],"name":"DRFeeMutualizerCannotProvideCoverage","type":"error"},{"inputs":[],"name":"DRUnsupportedFee","type":"error"},{"inputs":[],"name":"DirectInitializationNotAllowed","type":"error"},{"inputs":[],"name":"DisputeHasExpired","type":"error"},{"inputs":[],"name":"DisputePeriodHasElapsed","type":"error"},{"inputs":[],"name":"DisputePeriodNotElapsed","type":"error"},{"inputs":[],"name":"DisputeResolverAddressMustBeUnique","type":"error"},{"inputs":[],"name":"DisputeResolverFeeNotFound","type":"error"},{"inputs":[],"name":"DisputeStillValid","type":"error"},{"inputs":[],"name":"DuplicateDisputeResolverFees","type":"error"},{"inputs":[],"name":"EscalationNotAllowed","type":"error"},{"inputs":[],"name":"ExchangeAlreadyExists","type":"error"},{"inputs":[],"name":"ExchangeForOfferExists","type":"error"},{"inputs":[],"name":"ExchangeIdInReservedRange","type":"error"},{"inputs":[],"name":"ExchangeIsNotInAFinalState","type":"error"},{"inputs":[],"name":"ExternalCallFailed","type":"error"},{"inputs":[],"name":"FeeAmountTooHigh","type":"error"},{"inputs":[],"name":"FeeTableAssetNotSupported","type":"error"},{"inputs":[],"name":"FunctionNotAllowlisted","type":"error"},{"inputs":[],"name":"GroupHasCondition","type":"error"},{"inputs":[],"name":"GroupHasNoCondition","type":"error"},{"inputs":[],"name":"IncomingVoucherAlreadySet","type":"error"},{"inputs":[],"name":"InexistentAllowedSellersList","type":"error"},{"inputs":[],"name":"InexistentDisputeResolverFees","type":"error"},{"inputs":[],"name":"InsufficientAvailableFunds","type":"error"},{"inputs":[],"name":"InsufficientTwinSupplyToCoverBundleOffers","type":"error"},{"inputs":[],"name":"InsufficientValueReceived","type":"error"},{"inputs":[],"name":"InteractionNotAllowed","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidAgentFeePercentage","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidAmountToMint","type":"error"},{"inputs":[],"name":"InvalidAuthTokenType","type":"error"},{"inputs":[],"name":"InvalidBuyerOfferFields","type":"error"},{"inputs":[],"name":"InvalidBuyerPercent","type":"error"},{"inputs":[],"name":"InvalidCollectionIndex","type":"error"},{"inputs":[],"name":"InvalidConditionParameters","type":"error"},{"inputs":[],"name":"InvalidConduitAddress","type":"error"},{"inputs":[],"name":"InvalidDisputePeriod","type":"error"},{"inputs":[],"name":"InvalidDisputeResolver","type":"error"},{"inputs":[],"name":"InvalidDisputeTimeout","type":"error"},{"inputs":[],"name":"InvalidEscalationPeriod","type":"error"},{"inputs":[],"name":"InvalidFeePercentage","type":"error"},{"inputs":[],"name":"InvalidFunctionName","type":"error"},{"inputs":[],"name":"InvalidOffer","type":"error"},{"inputs":[],"name":"InvalidOfferCreator","type":"error"},{"inputs":[],"name":"InvalidOfferPenalty","type":"error"},{"inputs":[],"name":"InvalidOfferPeriod","type":"error"},{"inputs":[],"name":"InvalidPriceDiscovery","type":"error"},{"inputs":[],"name":"InvalidPriceDiscoveryPrice","type":"error"},{"inputs":[],"name":"InvalidPriceType","type":"error"},{"inputs":[],"name":"InvalidQuantityAvailable","type":"error"},{"inputs":[],"name":"InvalidRangeLength","type":"error"},{"inputs":[],"name":"InvalidRangeStart","type":"error"},{"inputs":[],"name":"InvalidRedemptionPeriod","type":"error"},{"inputs":[],"name":"InvalidResolutionPeriod","type":"error"},{"inputs":[],"name":"InvalidRoyaltyFee","type":"error"},{"inputs":[],"name":"InvalidRoyaltyInfo","type":"error"},{"inputs":[],"name":"InvalidRoyaltyPercentage","type":"error"},{"inputs":[],"name":"InvalidRoyaltyRecipient","type":"error"},{"inputs":[],"name":"InvalidRoyaltyRecipientId","type":"error"},{"inputs":[],"name":"InvalidSellerOfferFields","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidState","type":"error"},{"inputs":[],"name":"InvalidSupplyAvailable","type":"error"},{"inputs":[],"name":"InvalidTargeDisputeState","type":"error"},{"inputs":[],"name":"InvalidTargeExchangeState","type":"error"},{"inputs":[],"name":"InvalidToAddress","type":"error"},{"inputs":[],"name":"InvalidTokenAddress","type":"error"},{"inputs":[],"name":"InvalidTokenId","type":"error"},{"inputs":[],"name":"InvalidTwinProperty","type":"error"},{"inputs":[],"name":"InvalidTwinTokenRange","type":"error"},{"inputs":[],"name":"MaxCommitsReached","type":"error"},{"inputs":[],"name":"MustBeActive","type":"error"},{"inputs":[],"name":"NativeNotAllowed","type":"error"},{"inputs":[],"name":"NativeWrongAddress","type":"error"},{"inputs":[],"name":"NativeWrongAmount","type":"error"},{"inputs":[],"name":"NegativePriceNotAllowed","type":"error"},{"inputs":[],"name":"NoPendingUpdateForAccount","type":"error"},{"inputs":[],"name":"NoReservedRangeForOffer","type":"error"},{"inputs":[],"name":"NoSilentMintAllowed","type":"error"},{"inputs":[],"name":"NoSuchAgent","type":"error"},{"inputs":[],"name":"NoSuchBundle","type":"error"},{"inputs":[],"name":"NoSuchBuyer","type":"error"},{"inputs":[],"name":"NoSuchCollection","type":"error"},{"inputs":[],"name":"NoSuchDisputeResolver","type":"error"},{"inputs":[],"name":"NoSuchEntity","type":"error"},{"inputs":[],"name":"NoSuchExchange","type":"error"},{"inputs":[],"name":"NoSuchGroup","type":"error"},{"inputs":[],"name":"NoSuchOffer","type":"error"},{"inputs":[],"name":"NoSuchSeller","type":"error"},{"inputs":[],"name":"NoSuchTwin","type":"error"},{"inputs":[],"name":"NoTransferApproved","type":"error"},{"inputs":[],"name":"NoUpdateApplied","type":"error"},{"inputs":[],"name":"NonAscendingOrder","type":"error"},{"inputs":[],"name":"NonceUsedAlready","type":"error"},{"inputs":[],"name":"NotAdmin","type":"error"},{"inputs":[],"name":"NotAdminAndAssistant","type":"error"},{"inputs":[],"name":"NotAgentWallet","type":"error"},{"inputs":[],"name":"NotAssistant","type":"error"},{"inputs":[],"name":"NotAuthorized","type":"error"},{"inputs":[],"name":"NotBuyerOrSeller","type":"error"},{"inputs":[],"name":"NotBuyerWallet","type":"error"},{"inputs":[],"name":"NotDisputeResolverAssistant","type":"error"},{"inputs":[],"name":"NotOfferCreator","type":"error"},{"inputs":[],"name":"NotPaused","type":"error"},{"inputs":[],"name":"NotVoucherHolder","type":"error"},{"inputs":[],"name":"NothingToWithdraw","type":"error"},{"inputs":[],"name":"NothingUpdated","type":"error"},{"inputs":[],"name":"OfferExpiredOrVoided","type":"error"},{"inputs":[],"name":"OfferHasBeenVoided","type":"error"},{"inputs":[],"name":"OfferHasExpired","type":"error"},{"inputs":[],"name":"OfferMustBeActive","type":"error"},{"inputs":[],"name":"OfferMustBeUnique","type":"error"},{"inputs":[],"name":"OfferNotAvailable","type":"error"},{"inputs":[],"name":"OfferNotInBundle","type":"error"},{"inputs":[],"name":"OfferNotInGroup","type":"error"},{"inputs":[],"name":"OfferRangeAlreadyReserved","type":"error"},{"inputs":[],"name":"OfferSoldOut","type":"error"},{"inputs":[],"name":"OfferStillValid","type":"error"},{"inputs":[],"name":"PriceDoesNotCoverPenalty","type":"error"},{"inputs":[],"name":"PriceMismatch","type":"error"},{"inputs":[],"name":"ProtocolInitializationFailed","type":"error"},{"inputs":[],"name":"RecipientNotUnique","type":"error"},{"inputs":[],"name":"ReentrancyGuard","type":"error"},{"inputs":[{"internalType":"enum BosonTypes.PausableRegion","name":"region","type":"uint8"}],"name":"RegionPaused","type":"error"},{"inputs":[],"name":"RoyaltyRecipientIdsNotSorted","type":"error"},{"inputs":[],"name":"SameMutualizerAddress","type":"error"},{"inputs":[],"name":"SellerAddressMustBeUnique","type":"error"},{"inputs":[],"name":"SellerAlreadyApproved","type":"error"},{"inputs":[],"name":"SellerNotApproved","type":"error"},{"inputs":[],"name":"SellerParametersNotAllowed","type":"error"},{"inputs":[],"name":"SellerSaltNotUnique","type":"error"},{"inputs":[],"name":"SignatureValidationFailed","type":"error"},{"inputs":[],"name":"TokenAmountMismatch","type":"error"},{"inputs":[],"name":"TokenIdMandatory","type":"error"},{"inputs":[],"name":"TokenIdMismatch","type":"error"},{"inputs":[],"name":"TokenIdNotInConditionRange","type":"error"},{"inputs":[],"name":"TokenIdNotSet","type":"error"},{"inputs":[],"name":"TokenTransferFailed","type":"error"},{"inputs":[],"name":"TotalFeeExceedsLimit","type":"error"},{"inputs":[],"name":"TwinNotInBundle","type":"error"},{"inputs":[],"name":"TwinTransferUnsuccessful","type":"error"},{"inputs":[],"name":"TwinsAlreadyExist","type":"error"},{"inputs":[],"name":"UnauthorizedCallerUpdate","type":"error"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"UnexpectedDataReturned","type":"error"},{"inputs":[],"name":"UnexpectedERC721Received","type":"error"},{"inputs":[],"name":"UnsupportedMutualizer","type":"error"},{"inputs":[],"name":"UnsupportedToken","type":"error"},{"inputs":[],"name":"ValueZeroNotAllowed","type":"error"},{"inputs":[],"name":"VersionMustBeSet","type":"error"},{"inputs":[],"name":"VoucherExtensionNotValid","type":"error"},{"inputs":[],"name":"VoucherHasExpired","type":"error"},{"inputs":[],"name":"VoucherNotReceived","type":"error"},{"inputs":[],"name":"VoucherNotRedeemable","type":"error"},{"inputs":[],"name":"VoucherNotTransferred","type":"error"},{"inputs":[],"name":"VoucherStillValid","type":"error"},{"inputs":[],"name":"VoucherTransferNotAllowed","type":"error"},{"inputs":[],"name":"WalletOwnsVouchers","type":"error"},{"inputs":[],"name":"WrongCurrentVersion","type":"error"},{"inputs":[],"name":"WrongDefaultRecipient","type":"error"},{"inputs":[],"name":"ZeroDepositNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"feePercentage","type":"uint256"},{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.Agent","name":"agent","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"AgentCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"agentId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"feePercentage","type":"uint256"},{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.Agent","name":"agent","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"AgentUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeResolverId","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"addedSellers","type":"uint256[]"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"AllowedSellersAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeResolverId","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"removedSellers","type":"uint256[]"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"AllowedSellersRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"buyerId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.Buyer","name":"buyer","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"BuyerCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"buyerId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.Buyer","name":"buyer","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"BuyerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"sellerId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collectionIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"collectionAddress","type":"address"},{"indexed":true,"internalType":"string","name":"externalId","type":"string"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"CollectionCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeResolverId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"escalationResponsePeriod","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"string","name":"metadataUri","type":"string"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.DisputeResolver","name":"disputeResolver","type":"tuple"},{"components":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"string","name":"tokenName","type":"string"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"indexed":false,"internalType":"struct BosonTypes.DisputeResolverFee[]","name":"disputeResolverFees","type":"tuple[]"},{"indexed":false,"internalType":"uint256[]","name":"sellerAllowList","type":"uint256[]"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"DisputeResolverCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeResolverId","type":"uint256"},{"components":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"string","name":"tokenName","type":"string"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"indexed":false,"internalType":"struct BosonTypes.DisputeResolverFee[]","name":"disputeResolverFees","type":"tuple[]"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"DisputeResolverFeesAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeResolverId","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"feeTokensRemoved","type":"address[]"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"DisputeResolverFeesRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeResolverId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"escalationResponsePeriod","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"string","name":"metadataUri","type":"string"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.DisputeResolver","name":"disputeResolver","type":"tuple"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"escalationResponsePeriod","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"string","name":"metadataUri","type":"string"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.DisputeResolver","name":"pendingDisputeResolver","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"DisputeResolverUpdateApplied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeResolverId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"escalationResponsePeriod","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"string","name":"metadataUri","type":"string"},{"internalType":"bool","name":"active","type":"bool"}],"indexed":false,"internalType":"struct BosonTypes.DisputeResolver","name":"pendingDisputeResolver","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"DisputeResolverUpdatePending","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"sellerId","type":"uint256"},{"components":[{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"uint256","name":"minRoyaltyPercentage","type":"uint256"}],"indexed":false,"internalType":"struct BosonTypes.RoyaltyRecipientInfo[]","name":"royaltyRecipients","type":"tuple[]"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"RoyaltyRecipientsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"sellerId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"string","name":"metadataUri","type":"string"}],"indexed":false,"internalType":"struct BosonTypes.Seller","name":"seller","type":"tuple"},{"indexed":false,"internalType":"address","name":"voucherCloneAddress","type":"address"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"enum BosonTypes.AuthTokenType","name":"tokenType","type":"uint8"}],"indexed":false,"internalType":"struct BosonTypes.AuthToken","name":"authToken","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"SellerCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"sellerId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"string","name":"metadataUri","type":"string"}],"indexed":false,"internalType":"struct BosonTypes.Seller","name":"seller","type":"tuple"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"string","name":"metadataUri","type":"string"}],"indexed":false,"internalType":"struct BosonTypes.Seller","name":"pendingSeller","type":"tuple"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"enum BosonTypes.AuthTokenType","name":"tokenType","type":"uint8"}],"indexed":false,"internalType":"struct BosonTypes.AuthToken","name":"authToken","type":"tuple"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"enum BosonTypes.AuthTokenType","name":"tokenType","type":"uint8"}],"indexed":false,"internalType":"struct BosonTypes.AuthToken","name":"pendingAuthToken","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"SellerUpdateApplied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"sellerId","type":"uint256"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"assistant","type":"address"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"clerk","type":"address"},{"internalType":"address payable","name":"treasury","type":"address"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"string","name":"metadataUri","type":"string"}],"indexed":false,"internalType":"struct BosonTypes.Seller","name":"pendingSeller","type":"tuple"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"enum BosonTypes.AuthTokenType","name":"tokenType","type":"uint8"}],"indexed":false,"internalType":"struct BosonTypes.AuthToken","name":"pendingAuthToken","type":"tuple"},{"indexed":true,"internalType":"address","name":"executedBy","type":"address"}],"name":"SellerUpdatePending","type":"event"},{"inputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct BosonTypes.Buyer","name":"_buyer","type":"tuple"}],"name":"createBuyer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_buyerId","type":"uint256"}],"name":"getBuyer","outputs":[{"internalType":"bool","name":"exists","type":"bool"},{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct BosonTypes.Buyer","name":"buyer","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address payable","name":"wallet","type":"address"},{"internalType":"bool","name":"active","type":"bool"}],"internalType":"struct BosonTypes.Buyer","name":"_buyer","type":"tuple"}],"name":"updateBuyer","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60a060405234801561000f575f80fd5b5060805161080a6100255f395f505061080a5ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c80630ae2126d1461004e5780635bf608b8146100635780636adb0d9c1461008d5780638129fc1c14610061575b5f80fd5b61006161005c36600461062c565b6100a0565b005b6100766100713660046106ae565b6102b0565b6040516100849291906106e9565b60405180910390f35b61006161009b36600461062c565b610311565b6100aa60056103cc565b5f6100b361041e565b905060028160010154036100da576040516345f5ce8b60e11b815260040160405180910390fd5b600260018201555f6100ea610442565b60208401519091506001600160a01b03166101185760405163e6c4247b60e01b815260040160405180910390fd5b5f80610126855f0151610466565b90925090508161014957604051630d8be60360e01b815260040160405180910390fd5b5f6101526104a8565b60018301549091506001600160a01b038083169116146101855760405163ca80ad0960e01b815260040160405180910390fd5b602086015160018301546001600160a01b039081169116146101d15785515f908152601285016020526040902054156101d1576040516394d9168760e01b815260040160405180910390fd5b6020808701516001600160a01b03165f908152600886019182905260409020548015801590610201575087518114155b1561021f5760405163db0a421960e01b815260040160405180910390fd5b6001600160a01b0383165f90815260208390526040808220919091556001850154600160a01b900460ff16151590890152610259886104e8565b826001600160a01b0316885f01517f8c142a19e4bb1ffe2ec690c8fcba815c4da8ebc5ff10d7dce8757dc36b0958fb8a6040516102969190610706565b60405180910390a350506001948501949094555050505050565b604080516060810182525f808252602082018190529181018290526102d483610466565b60408051606081018252825481526001909201546001600160a01b0381166020840152600160a01b900460ff161515908201529094909350915050565b61031b60056103cc565b5f61032461041e565b9050600281600101540361034b576040516345f5ce8b60e11b815260040160405180910390fd5b60026001820155604082015161037457604051630fd491cd60e21b815260040160405180910390fd5b61037c610442565b6020808401516001600160a01b03165f90815260089290920190526040902054156103ba5760405163db0a421960e01b815260040160405180910390fd5b6103c382610570565b60019081015550565b5f81600e8111156103df576103df61071a565b6001901b905080816103ef61041e565b54160361041a5781604051633ff588ab60e01b8152600401610411919061072e565b60405180910390fd5b5050565b7fe7ad30265b8084c731e610579d091ebe2886115e8a5cacb6b704e4392f48b2c190565b7f612ef4010290807451b703920dc633bec38eb4e2d0b1fda71d2a122f1b61c7ca90565b5f8181527fc973828674331e26e5b526db986992b6e682e76403c37e06ee7953caf0e663d16020526040812082158015906104a15750805483145b9150915091565b5f3633301480156104ba575060148110155b156104e1576104cf366013198301815f610754565b6104d89161077b565b60601c91505090565b3391505090565b5f6104f5825f0151610466565b8351808255602085015160018301805460408801511515600160a01b026001600160a81b03199091166001600160a01b03909316929092179190911790559092509050610540610442565b6008015f84602001516001600160a01b03166001600160a01b031681526020019081526020015f20819055505050565b60208101516001600160a01b031661059b5760405163e6c4247b60e01b815260040160405180910390fd5b5f7f236215f8ddaf4ac1232d1f2516766788cddc14962c93aafa73278d9e2e4497dd8054905f6105ca836107b0565b9091555080835290506105dc826104e8565b6105e46104a8565b6001600160a01b0316825f01517fffca0ebd6b049280991820182bf71bdf86f0f1099e7865f4672bcbe7c3bfac64846040516106209190610706565b60405180910390a35050565b5f6060828403121561063c575f80fd5b6040516060810181811067ffffffffffffffff8211171561066b57634e487b7160e01b5f52604160045260245ffd5b6040528235815260208301356001600160a01b038116811461068b575f80fd5b6020820152604083013580151581146106a2575f80fd5b60408201529392505050565b5f602082840312156106be575f80fd5b5035919050565b805182526020808201516001600160a01b0316908301526040908101511515910152565b8215158152608081016106ff60208301846106c5565b9392505050565b6060810161071482846106c5565b92915050565b634e487b7160e01b5f52602160045260245ffd5b60208101600f831061074e57634e487b7160e01b5f52602160045260245ffd5b91905290565b5f8085851115610762575f80fd5b8386111561076e575f80fd5b5050820193919092039150565b6bffffffffffffffffffffffff1981358181169160148510156107a85780818660140360031b1b83161692505b505092915050565b5f600182016107cd57634e487b7160e01b5f52601160045260245ffd5b506001019056fea26469706673582212203addf23cf8b990febb2a682b4e472e3c1287208cddda973b79a437c20b194d3e64736f6c63430008160033

Deployed Bytecode

0x608060405234801561000f575f80fd5b506004361061004a575f3560e01c80630ae2126d1461004e5780635bf608b8146100635780636adb0d9c1461008d5780638129fc1c14610061575b5f80fd5b61006161005c36600461062c565b6100a0565b005b6100766100713660046106ae565b6102b0565b6040516100849291906106e9565b60405180910390f35b61006161009b36600461062c565b610311565b6100aa60056103cc565b5f6100b361041e565b905060028160010154036100da576040516345f5ce8b60e11b815260040160405180910390fd5b600260018201555f6100ea610442565b60208401519091506001600160a01b03166101185760405163e6c4247b60e01b815260040160405180910390fd5b5f80610126855f0151610466565b90925090508161014957604051630d8be60360e01b815260040160405180910390fd5b5f6101526104a8565b60018301549091506001600160a01b038083169116146101855760405163ca80ad0960e01b815260040160405180910390fd5b602086015160018301546001600160a01b039081169116146101d15785515f908152601285016020526040902054156101d1576040516394d9168760e01b815260040160405180910390fd5b6020808701516001600160a01b03165f908152600886019182905260409020548015801590610201575087518114155b1561021f5760405163db0a421960e01b815260040160405180910390fd5b6001600160a01b0383165f90815260208390526040808220919091556001850154600160a01b900460ff16151590890152610259886104e8565b826001600160a01b0316885f01517f8c142a19e4bb1ffe2ec690c8fcba815c4da8ebc5ff10d7dce8757dc36b0958fb8a6040516102969190610706565b60405180910390a350506001948501949094555050505050565b604080516060810182525f808252602082018190529181018290526102d483610466565b60408051606081018252825481526001909201546001600160a01b0381166020840152600160a01b900460ff161515908201529094909350915050565b61031b60056103cc565b5f61032461041e565b9050600281600101540361034b576040516345f5ce8b60e11b815260040160405180910390fd5b60026001820155604082015161037457604051630fd491cd60e21b815260040160405180910390fd5b61037c610442565b6020808401516001600160a01b03165f90815260089290920190526040902054156103ba5760405163db0a421960e01b815260040160405180910390fd5b6103c382610570565b60019081015550565b5f81600e8111156103df576103df61071a565b6001901b905080816103ef61041e565b54160361041a5781604051633ff588ab60e01b8152600401610411919061072e565b60405180910390fd5b5050565b7fe7ad30265b8084c731e610579d091ebe2886115e8a5cacb6b704e4392f48b2c190565b7f612ef4010290807451b703920dc633bec38eb4e2d0b1fda71d2a122f1b61c7ca90565b5f8181527fc973828674331e26e5b526db986992b6e682e76403c37e06ee7953caf0e663d16020526040812082158015906104a15750805483145b9150915091565b5f3633301480156104ba575060148110155b156104e1576104cf366013198301815f610754565b6104d89161077b565b60601c91505090565b3391505090565b5f6104f5825f0151610466565b8351808255602085015160018301805460408801511515600160a01b026001600160a81b03199091166001600160a01b03909316929092179190911790559092509050610540610442565b6008015f84602001516001600160a01b03166001600160a01b031681526020019081526020015f20819055505050565b60208101516001600160a01b031661059b5760405163e6c4247b60e01b815260040160405180910390fd5b5f7f236215f8ddaf4ac1232d1f2516766788cddc14962c93aafa73278d9e2e4497dd8054905f6105ca836107b0565b9091555080835290506105dc826104e8565b6105e46104a8565b6001600160a01b0316825f01517fffca0ebd6b049280991820182bf71bdf86f0f1099e7865f4672bcbe7c3bfac64846040516106209190610706565b60405180910390a35050565b5f6060828403121561063c575f80fd5b6040516060810181811067ffffffffffffffff8211171561066b57634e487b7160e01b5f52604160045260245ffd5b6040528235815260208301356001600160a01b038116811461068b575f80fd5b6020820152604083013580151581146106a2575f80fd5b60408201529392505050565b5f602082840312156106be575f80fd5b5035919050565b805182526020808201516001600160a01b0316908301526040908101511515910152565b8215158152608081016106ff60208301846106c5565b9392505050565b6060810161071482846106c5565b92915050565b634e487b7160e01b5f52602160045260245ffd5b60208101600f831061074e57634e487b7160e01b5f52602160045260245ffd5b91905290565b5f8085851115610762575f80fd5b8386111561076e575f80fd5b5050820193919092039150565b6bffffffffffffffffffffffff1981358181169160148510156107a85780818660140360031b1b83161692505b505092915050565b5f600182016107cd57634e487b7160e01b5f52601160045260245ffd5b506001019056fea26469706673582212203addf23cf8b990febb2a682b4e472e3c1287208cddda973b79a437c20b194d3e64736f6c63430008160033

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.