Forkchoice Ethereum Mainnet

Address Contract Verified

Address 0x962EaeDCe9487C63de122d0d776DB71a3D47f7e5
Balance 0 ETH
Nonce 1
Code Size 2297 bytes
Indexed Transactions 0 (1 on-chain, 0.6% indexed)
External Etherscan · Sourcify

Contract Bytecode

2297 bytes
0x608060405234801561001057600080fd5b506004361061007d5760003560e01c80637eb0bdc91161005b5780637eb0bdc9146100e8578063829311d2146100fd5780639528164814610110578063dbdc814b1461013057600080fd5b806301ffc9a714610082578063146a2911146100aa5780637b103999146100bd575b600080fd5b610095610090366004610784565b610143565b60405190151581526020015b60405180910390f35b6100956100b83660046107c3565b61017a565b6000546100d0906001600160a01b031681565b6040516001600160a01b0390911681526020016100a1565b6100fb6100f63660046107c3565b610199565b005b6100fb61010b3660046107f3565b61025e565b61012361011e3660046107f3565b61033a565b6040516100a1919061080c565b6100fb61013e3660046107c3565b610354565b60006001600160e01b0319821663a6bd120960e01b148061017457506001600160e01b031982166301ffc9a760e01b145b92915050565b60008281526033602052604081206101929083610411565b9392505050565b816101a381610433565b60008381526033602052604090206101bb9083610411565b6101fe5760405162461bcd60e51b815260206004820152600f60248201526e10591b5a5b881b9bdd08199bdd5b99608a1b60448201526064015b60405180910390fd5b60008381526033602052604090206102169083610497565b50604051600081526001600160a01b0383169084907f87fd40bb6b885abfecf2754b655e5305fe55362fe513c29f96296ec7068f6c26906020015b60405180910390a3505050565b8061026881610433565b600082815260336020526040812061027f906104ac565b905060005b8151811015610334576102cb8282815181106102a2576102a2610859565b60200260200101516033600087815260200190815260200160002061049790919063ffffffff16565b508181815181106102de576102de610859565b60200260200101516001600160a01b0316847f87fd40bb6b885abfecf2754b655e5305fe55362fe513c29f96296ec7068f6c266000604051610324911515815260200190565b60405180910390a3600101610284565b50505050565b6000818152603360205260409020606090610174906104ac565b8161035e81610433565b60008381526033602052604090206103769083610411565b156103ba5760405162461bcd60e51b815260206004820152601460248201527341646d696e20616c72656164792065786973747360601b60448201526064016101f5565b60008381526033602052604090206103d290836104b9565b50604051600181526001600160a01b0383169084907f87fd40bb6b885abfecf2754b655e5305fe55362fe513c29f96296ec7068f6c2690602001610251565b6001600160a01b03811660009081526001830160205260408120541515610192565b61043c816104ce565b6104945760405162461bcd60e51b8152602060048201526024808201527f4e6f7420746865206f776e6572206f6620746865204f7267616e697a6174696f6044820152631b93919560e21b60648201526084016101f5565b50565b6000610192836001600160a01b0384166104da565b60606000610192836105cd565b6000610192836001600160a01b038416610629565b60006101748233610678565b600081815260018301602052604081205480156105c35760006104fe60018361086f565b85549091506000906105129060019061086f565b905080821461057757600086600001828154811061053257610532610859565b906000526020600020015490508087600001848154811061055557610555610859565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061058857610588610890565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610174565b6000915050610174565b60608160000180548060200260200160405190810160405280929190818152602001828054801561061d57602002820191906000526020600020905b815481526020019060010190808311610609575b50505050509050919050565b600081815260018301602052604081205461067057508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610174565b506000610174565b6000816001600160a01b031661068d8461069e565b6001600160a01b0316149392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b031663cfd8fe0c6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106f2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061071691906108a6565b6001600160a01b0316636352211e836040518263ffffffff1660e01b815260040161074391815260200190565b602060405180830381865afa158015610760573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061017491906108a6565b60006020828403121561079657600080fd5b81356001600160e01b03198116811461019257600080fd5b6001600160a01b038116811461049457600080fd5b600080604083850312156107d657600080fd5b8235915060208301356107e8816107ae565b809150509250929050565b60006020828403121561080557600080fd5b5035919050565b6020808252825182820181905260009190848201906040850190845b8181101561084d5783516001600160a01b031683529284019291840191600101610828565b50909695505050505050565b634e487b7160e01b600052603260045260246000fd5b8181038181111561017457634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603160045260246000fd5b6000602082840312156108b857600080fd5b8151610192816107ae56fea2646970667358221220af663e4d6a6eaea63b9c267a385d9d9c87b3fcee8301a4b58f9ba8abf40a566b64736f6c63430008180033

Verified Source Code Full Match

Compiler: v0.8.24+commit.e11b9ed9 EVM: paris Optimization: Yes (200 runs)
IERC721.sol 135 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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;
    }
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * 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[ERC 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);
}
EnumerableSet.sol 375 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }
}
RegistryEnabled.sol 119 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import {IContractRegistry} from "../registry/IContractRegistry.sol";
import {IOrganizationAdmin} from "../admin/IOrganizationAdmin.sol";

/**
 * @title RegistryEnabled
 * @notice A base contract for contracts that use the central registry to interact with other contracts within the ProductMint system.
 */
abstract contract RegistryEnabled is Context {
    /**
     * @notice The registry contract
     */
    IContractRegistry public registry;

    uint256[50] private __gap;

    constructor(address _registry) {
        registry = IContractRegistry(_registry);
    }

    // Registry

    modifier onlyRegistry(address expectedContract) {
        _checkRegistry(expectedContract);
        _;
    }

    function _checkRegistry(address expectedContract) internal view {
        require(_msgSender() == expectedContract, "Caller not authorized");
    }

    // Org Admin

    modifier onlyOrgAdmin(uint256 organizationId) {
        _checkOrgAdmin(organizationId);
        _;
    }

    function _isOrgAdmin(uint256 organizationId) internal view returns (bool) {
        return _isOrgAdminAddress(organizationId, _msgSender());
    }

    function _isOrgAdminAddress(
        uint256 organizationId,
        address orgAdmin
    ) internal view returns (bool) {
        return
            _isOrgOwnerAddress(organizationId, orgAdmin) ||
            IOrganizationAdmin(registry.orgAdmin()).isAdmin(
                organizationId,
                orgAdmin
            );
    }

    function _checkOrgAdmin(uint256 organizationId) internal view {
        require(
            _isOrgAdmin(organizationId),
            "Not an admin of the organization"
        );
    }

    // Product Pass NFT

    modifier onlyPassOwner(uint256 _productPassId) {
        _checkPassOwner(_productPassId);
        _;
    }

    function _checkPassOwner(uint256 _productPassId) internal view {
        require(
            _isPassOwner(_productPassId),
            "Not the owner of the ProductPassNFT"
        );
    }

    function _isPassOwner(uint256 _productPassId) internal view returns (bool) {
        return _passOwner(_productPassId) == _msgSender();
    }

    function _passOwner(
        uint256 _productPassId
    ) internal view returns (address) {
        return IERC721(registry.productPassNFT()).ownerOf(_productPassId);
    }

    // Organization NFT

    modifier onlyOrgOwner(uint256 organizationId) {
        _checkOrgOwner(organizationId);
        _;
    }

    function _checkOrgOwner(uint256 organizationId) internal view {
        require(
            _isOrgOwner(organizationId),
            "Not the owner of the OrganizationNFT"
        );
    }

    function _isOrgOwner(uint256 organizationId) internal view returns (bool) {
        return _isOrgOwnerAddress(organizationId, _msgSender());
    }

    function _isOrgOwnerAddress(
        uint256 organizationId,
        address orgOwner
    ) internal view returns (bool) {
        return _orgOwner(organizationId) == orgOwner;
    }

    function _orgOwner(uint256 organizationId) internal view returns (address) {
        return IERC721(registry.organizationNFT()).ownerOf(organizationId);
    }
}
IOrganizationAdmin.sol 63 lines
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IOrganizationAdmin {
    /**
     * Organization Admins
     */

    /**
     * @notice Emitted when an admin is added or removed from an organization.
     * @param orgId The ID of the organization.
     * @param admin The address of the admin.
     * @param status The status of the admin. True if added, false if removed.
     */
    event OrgAdminUpdate(
        uint256 indexed orgId,
        address indexed admin,
        bool status
    );

    /**
     * @notice Adds an admin to the organization.
     * Admins are great for delegating the responsibilities of the organization.
     * @dev Only the owner of the organization can add admins.
     * @param organizationId The ID of the organization.
     * @param admin The address of the admin to add.
     */
    function addAdmin(uint256 organizationId, address admin) external;

    /**
     * @notice Removes an admin from the organization.
     * @param organizationId The ID of the organization.
     * @param admin The address of the admin to remove.
     */
    function removeAdmin(uint256 organizationId, address admin) external;

    /**
     * @notice Removes all admins from the organization.
     * @param organizationId The ID of the organization.
     */
    function removeAllAdmins(uint256 organizationId) external;

    /**
     * @notice Returns all admins for an organization.
     * @param organizationId The ID of the organization.
     * @return admins The addresses of the admins for the organization.
     */
    function getAdmins(
        uint256 organizationId
    ) external view returns (address[] memory);

    /**
     * @notice Checks if an address is an admin for an organization.
     * @param organizationId The ID of the organization.
     * @param admin The address of the admin to check.
     * @return status True if the address is an admin, false otherwise.
     */
    function isAdmin(
        uint256 organizationId,
        address admin
    ) external view returns (bool);
}
OrganizationAdmin.sol 105 lines
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

import {
    EnumerableSet
} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import {RegistryEnabled} from "../abstract/RegistryEnabled.sol";
import {IOrganizationAdmin} from "./IOrganizationAdmin.sol";

/*
 ____                 _            _   __  __ _       _   
|  _ \ _ __ ___   __| |_   _  ___| |_|  \/  (_)_ __ | |_ 
| |_) | '__/ _ \ / _` | | | |/ __| __| |\/| | | '_ \| __|
|  __/| | | (_) | (_| | |_| | (__| |_| |  | | | | | | |_ 
|_|   |_|  \___/ \__,_|\__,_|\___|\__|_|  |_|_|_| |_|\__|
 
 NFT based payment system to mint products onchain with one-time payments and 
 recurring permissionless subscriptions.

 https://productmint.io
*/

/**
 * @title OrganizationAdmin
 * @notice A contract that allows an organization to manage its admins.
 *
 * Delegate admins to avoid having to use the owner of the organization to interact with the ProductMint system.
 */
contract OrganizationAdmin is RegistryEnabled, IOrganizationAdmin {
    using EnumerableSet for EnumerableSet.AddressSet;

    // Organization ID => Addresses that can record usages for the organization.
    mapping(uint256 => EnumerableSet.AddressSet) private admins;

    constructor(address _contractRegistry) RegistryEnabled(_contractRegistry) {}

    /**
     * Organization Admins
     */

    function addAdmin(
        uint256 organizationId,
        address admin
    ) external onlyOrgOwner(organizationId) {
        require(
            !admins[organizationId].contains(admin),
            "Admin already exists"
        );

        admins[organizationId].add(admin);

        emit OrgAdminUpdate(organizationId, admin, true);
    }

    function removeAdmin(
        uint256 organizationId,
        address admin
    ) external onlyOrgOwner(organizationId) {
        require(admins[organizationId].contains(admin), "Admin not found");

        admins[organizationId].remove(admin);

        emit OrgAdminUpdate(organizationId, admin, false);
    }

    function removeAllAdmins(
        uint256 organizationId
    ) external onlyOrgOwner(organizationId) {
        address[] memory adminsToRemove = admins[organizationId].values();

        for (uint256 i = 0; i < adminsToRemove.length; i++) {
            admins[organizationId].remove(adminsToRemove[i]);

            emit OrgAdminUpdate(organizationId, adminsToRemove[i], false);
        }
    }

    function getAdmins(
        uint256 organizationId
    ) external view returns (address[] memory) {
        return admins[organizationId].values();
    }

    function isAdmin(
        uint256 organizationId,
        address admin
    ) external view returns (bool) {
        return admins[organizationId].contains(admin);
    }

    /**
     * ERC165
     */

    function supportsInterface(
        bytes4 interfaceId
    ) external pure returns (bool) {
        return
            interfaceId == type(IOrganizationAdmin).interfaceId ||
            interfaceId == type(IERC165).interfaceId;
    }
}
IContractRegistry.sol 196 lines
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IContractRegistry {
    /**
     * @notice Emitted when a contract is updated.
     * @param contractName The name of the contract.
     * @param newAddress The new address of the contract.
     */
    event ContractUpdated(
        string indexed contractName,
        address indexed newAddress
    );

    /**
     * Manager
     */

    /**
     * @notice Purchase manager responsible for facilitating product purchases and managing subscriptions.
     * @return The address of the purchase manager.
     */
    function purchaseManager() external view returns (address);

    /**
     * Admin
     */

    /**
     * @notice Organization admin that can delegate other addresses to help manage the organization and make calls on behalf of the organization.
     * @return The address of the organization admin.
     */
    function orgAdmin() external view returns (address);

    /**
     * NFTs
     */

    /**
     * @notice Product Pass NFT that represents a user's membership to an organization and allows them to purchase products.
     * @return The address of the product pass NFT.
     */
    function productPassNFT() external view returns (address);

    /**
     * @notice Organization NFT that represents a product owner and allows for the creation of products that can be purchased by users via Product Passes.
     * @return The address of the organization NFT.
     */
    function organizationNFT() external view returns (address);

    /**
     * Registry
     */

    /**
     * @notice Product registry that allows for the creation of products that can be purchased by users via Product Passes.
     * @return The address of the product registry.
     */
    function productRegistry() external view returns (address);

    /**
     * @notice Pricing registry that allows for the creation of pricing models that can be linked to products for purchase.
     * @return The address of the pricing registry.
     */
    function pricingRegistry() external view returns (address);

    /**
     * @notice Purchase registry records all purchases made by users via Product Passes.
     * @return The address of the purchase registry.
     */
    function purchaseRegistry() external view returns (address);

    /**
     * @notice Coupon registry that allows for the creation of coupons that can be applied to purchases and subscriptions.
     * @return The address of the coupon registry.
     */
    function couponRegistry() external view returns (address);

    /**
     * @notice Permanent discount registry that allows for the creation of permanent discounts that are minted onto product passes.
     * Discounts minted onto product passes are permanent and used in all future purchases including any subscription renewals.
     * @return The address of the discount registry.
     */
    function discountRegistry() external view returns (address);

    /**
     * Calculator
     */

    /**
     * @notice Pricing calculator that allows performs all the pricing calculations for different pricing models created in the pricing registry.
     * @return The address of the pricing calculator.
     */
    function pricingCalculator() external view returns (address);

    /**
     * Oracles
     */

    /**
     * @notice Responsible for knowing whether the product pass NFT can be transferred from one user to another.
     *  Products created in the registry must be set to transferable to be able to be transferred.
     * @return The address of the product transfer oracle.
     */
    function productTransferOracle() external view returns (address);

    /**
     * @notice Responsible for knowing whether the subscription can be transferred from one user to another.
     *  Subs must be in a paused state to be transferred.
     *  Organizations must enable this feature for subscriptions to be paused.
     * @return The address of the subscription transfer oracle.
     */
    function subscriptionTransferOracle() external view returns (address);

    /**
     * Escrow
     */

    /**
     * @notice Escrow that that holds all the subscriptions that have been created for product passes
     * @return The address of the subscription escrow.
     */
    function subscriptionEscrow() external view returns (address);

    /**
     * @notice Escrow that holds all the payments that have been made for purchases that are withdrawable by the organization.
     * @return The address of the payment escrow.
     */
    function paymentEscrow() external view returns (address);

    /**
     * Usage Recorder
     */

    /**
     * @notice Usage recorder where usage meters can be created for pricing models set to usage based billing.
     *  Once a meter has been created and is active, organizations can begin recording usage for their products.
     * @return The address of the usage recorder.
     */
    function usageRecorder() external view returns (address);

    /**
     * Locks
     */

    /**
     * @notice Lock for the product pass NFT.
     *  This lock prevents the product pass NFT from being changed once it has been set.
     * @return The address of the product pass NFT.
     */
    function PASS_LOCK() external view returns (bytes32);

    /**
     * @notice Lock for the organization NFT.
     *  This lock prevents the organization NFT from being changed once it has been set.
     * @return The address of the organization NFT.
     */
    function ORG_LOCK() external view returns (bytes32);

    /**
     * Batch Setup
     */

    struct BatchSetupContracts {
        // Manager
        address purchaseManager;
        // Admin
        address orgAdmin;
        // NFTs
        address productPassNFT;
        address organizationNFT;
        // Registry
        address productRegistry;
        address pricingRegistry;
        address purchaseRegistry;
        address couponRegistry;
        address discountRegistry;
        // Calculator
        address pricingCalculator;
        // Oracles
        address productTransferOracle;
        address subscriptionTransferOracle;
        // Escrow
        address subscriptionEscrow;
        address paymentEscrow;
        // Usage recorder
        address usageRecorder;
    }

    /**
     * @notice Batch set all the contracts in the registry.
     * @param _contracts The contracts to be set.
     */
    function batchSetContracts(BatchSetupContracts memory _contracts) external;
}

Read Contract

getAdmins 0x95281648 → address[]
isAdmin 0x146a2911 → bool
registry 0x7b103999 → address
supportsInterface 0x01ffc9a7 → bool

Write Contract 3 functions

These functions modify contract state and require a wallet transaction to execute.

addAdmin 0xdbdc814b
uint256 organizationId
address admin
removeAdmin 0x7eb0bdc9
uint256 organizationId
address admin
removeAllAdmins 0x829311d2
uint256 organizationId

Recent Transactions

This address has 1 on-chain transactions, but only 0.6% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →