账户
0x83...d138
0x83...d138

0x83...d138

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.19+commit.7dd6d404
语言
Solidity
合同源代码
文件 1 的 9:Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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;
    }
}
合同源代码
文件 2 的 9:IAdminACLV0.sol
// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

interface IAdminACLV0 {
    /**
     * @notice Token ID `_tokenId` minted to `_to`.
     * @param previousSuperAdmin The previous superAdmin address.
     * @param newSuperAdmin The new superAdmin address.
     * @param genArt721CoreAddressesToUpdate Array of genArt721Core
     * addresses to update to the new superAdmin, for indexing purposes only.
     */
    event SuperAdminTransferred(
        address indexed previousSuperAdmin,
        address indexed newSuperAdmin,
        address[] genArt721CoreAddressesToUpdate
    );

    /// Type of the Admin ACL contract, e.g. "AdminACLV0"
    function AdminACLType() external view returns (string memory);

    /// super admin address
    function superAdmin() external view returns (address);

    /**
     * @notice Calls transferOwnership on other contract from this contract.
     * This is useful for updating to a new AdminACL contract.
     * @dev this function should be gated to only superAdmin-like addresses.
     */
    function transferOwnershipOn(
        address _contract,
        address _newAdminACL
    ) external;

    /**
     * @notice Calls renounceOwnership on other contract from this contract.
     * @dev this function should be gated to only superAdmin-like addresses.
     */
    function renounceOwnershipOn(address _contract) external;

    /**
     * @notice Checks if sender `_sender` is allowed to call function with selector
     * `_selector` on contract `_contract`.
     */
    function allowed(
        address _sender,
        address _contract,
        bytes4 _selector
    ) external returns (bool);
}
合同源代码
文件 3 的 9:IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @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);
}
合同源代码
文件 4 的 9:IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 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 ERC721 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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * 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 caller.
     *
     * 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);
}
合同源代码
文件 5 的 9:IGenArt721CoreContractV3_Base.sol
// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

import "./IAdminACLV0.sol";
/// use the Royalty Registry's IManifold interface for token royalties
import "./IManifold.sol";

/**
 * @title This interface is intended to house interface items that are common
 * across all GenArt721CoreContractV3 flagship and derivative implementations.
 * This interface extends the IManifold royalty interface in order to
 * add support the Royalty Registry by default.
 * @author Art Blocks Inc.
 */
interface IGenArt721CoreContractV3_Base is IManifold {
    /**
     * @notice Token ID `_tokenId` minted to `_to`.
     */
    event Mint(address indexed _to, uint256 indexed _tokenId);

    /**
     * @notice currentMinter updated to `_currentMinter`.
     * @dev Implemented starting with V3 core
     */
    event MinterUpdated(address indexed _currentMinter);

    /**
     * @notice Platform updated on bytes32-encoded field `_field`.
     */
    event PlatformUpdated(bytes32 indexed _field);

    /**
     * @notice Project ID `_projectId` updated on bytes32-encoded field
     * `_update`.
     */
    event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);

    event ProposedArtistAddressesAndSplits(
        uint256 indexed _projectId,
        address _artistAddress,
        address _additionalPayeePrimarySales,
        uint256 _additionalPayeePrimarySalesPercentage,
        address _additionalPayeeSecondarySales,
        uint256 _additionalPayeeSecondarySalesPercentage
    );

    event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);

    // version and type of the core contract
    // coreVersion is a string of the form "0.x.y"
    function coreVersion() external view returns (string memory);

    // coreType is a string of the form "GenArt721CoreV3"
    function coreType() external view returns (string memory);

    // owner (pre-V3 was named admin) of contract
    // this is expected to be an Admin ACL contract for V3
    function owner() external view returns (address);

    // Admin ACL contract for V3, will be at the address owner()
    function adminACLContract() external returns (IAdminACLV0);

    // backwards-compatible (pre-V3) admin - equal to owner()
    function admin() external view returns (address);

    /**
     * Function determining if _sender is allowed to call function with
     * selector _selector on contract `_contract`. Intended to be used with
     * peripheral contracts such as minters, as well as internally by the
     * core contract itself.
     */
    function adminACLAllowed(
        address _sender,
        address _contract,
        bytes4 _selector
    ) external returns (bool);

    /// getter function of public variable
    function startingProjectId() external view returns (uint256);

    // getter function of public variable
    function nextProjectId() external view returns (uint256);

    // getter function of public mapping
    function tokenIdToProjectId(
        uint256 tokenId
    ) external view returns (uint256 projectId);

    // @dev this is not available in V0
    function isMintWhitelisted(address minter) external view returns (bool);

    function projectIdToArtistAddress(
        uint256 _projectId
    ) external view returns (address payable);

    function projectIdToAdditionalPayeePrimarySales(
        uint256 _projectId
    ) external view returns (address payable);

    function projectIdToAdditionalPayeePrimarySalesPercentage(
        uint256 _projectId
    ) external view returns (uint256);

    function projectIdToSecondaryMarketRoyaltyPercentage(
        uint256 _projectId
    ) external view returns (uint256);

    function projectURIInfo(
        uint256 _projectId
    ) external view returns (string memory projectBaseURI);

    // @dev new function in V3
    function projectStateData(
        uint256 _projectId
    )
        external
        view
        returns (
            uint256 invocations,
            uint256 maxInvocations,
            bool active,
            bool paused,
            uint256 completedTimestamp,
            bool locked
        );

    function projectDetails(
        uint256 _projectId
    )
        external
        view
        returns (
            string memory projectName,
            string memory artist,
            string memory description,
            string memory website,
            string memory license
        );

    function projectScriptDetails(
        uint256 _projectId
    )
        external
        view
        returns (
            string memory scriptTypeAndVersion,
            string memory aspectRatio,
            uint256 scriptCount
        );

    function projectScriptByIndex(
        uint256 _projectId,
        uint256 _index
    ) external view returns (string memory);

    function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);

    // function to set a token's hash (must be guarded)
    function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;

    // @dev gas-optimized signature in V3 for `mint`
    function mint_Ecf(
        address _to,
        uint256 _projectId,
        address _by
    ) external returns (uint256 tokenId);
}
合同源代码
文件 6 的 9:IGenArt721CoreContractV3_Engine.sol
// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

import "./IAdminACLV0.sol";
import "./IGenArt721CoreContractV3_Base.sol";

interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base {
    // @dev new function in V3
    function getPrimaryRevenueSplits(
        uint256 _projectId,
        uint256 _price
    )
        external
        view
        returns (
            uint256 renderProviderRevenue_,
            address payable renderProviderAddress_,
            uint256 platformProviderRevenue_,
            address payable platformProviderAddress_,
            uint256 artistRevenue_,
            address payable artistAddress_,
            uint256 additionalPayeePrimaryRevenue_,
            address payable additionalPayeePrimaryAddress_
        );

    // @dev The render provider primary sales payment address
    function renderProviderPrimarySalesAddress()
        external
        view
        returns (address payable);

    // @dev The platform provider primary sales payment address
    function platformProviderPrimarySalesAddress()
        external
        view
        returns (address payable);

    // @dev Percentage of primary sales allocated to the render provider
    function renderProviderPrimarySalesPercentage()
        external
        view
        returns (uint256);

    // @dev Percentage of primary sales allocated to the platform provider
    function platformProviderPrimarySalesPercentage()
        external
        view
        returns (uint256);

    // @dev The render provider secondary sales royalties payment address
    function renderProviderSecondarySalesAddress()
        external
        view
        returns (address payable);

    // @dev The platform provider secondary sales royalties payment address
    function platformProviderSecondarySalesAddress()
        external
        view
        returns (address payable);

    // @dev Basis points of secondary sales allocated to the render provider
    function renderProviderSecondarySalesBPS() external view returns (uint256);

    // @dev Basis points of secondary sales allocated to the platform provider
    function platformProviderSecondarySalesBPS()
        external
        view
        returns (uint256);
}
合同源代码
文件 7 的 9:IManifold.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @dev Royalty Registry interface, used to support the Royalty Registry.
/// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol

/// @author: manifold.xyz

/**
 * @dev Royalty interface for creator core classes
 */
interface IManifold {
    /**
     * @dev Get royalites of a token.  Returns list of receivers and basisPoints
     *
     *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
     *
     *  => 0xbb3bafd6 = 0xbb3bafd6
     */
    function getRoyalties(
        uint256 tokenId
    ) external view returns (address payable[] memory, uint256[] memory);
}
合同源代码
文件 8 的 9:Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
合同源代码
文件 9 的 9:ScribbleTogetherPaletteSelections.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

/* @title ScribbleTogetherPaletteSelections
 * @author minimizer <me@minimizer.art>; https://www.minimizer.art/
 * 
 * This is a helper contract for Scribble Together, a project to be released on Plottables.
 * Scribble Together is an experiment where minimizer has created the scribbling algorithm
 * and invited friends to create the palettes. 
 * 
 * During mint, palettes are randomly assigned. Once all mints are concluded, collectors can
 * then select an alternative palette for their piece.
 * 
 * This contract is responsible for:
 *   - collecting primary sales proceeds for the project (the "palette pool")
 *   - allowing collectors to change the palette of their piece, and keeping track of the selections
 *   - dividing up the proceeds of the palette pool based on collector's selections
 * 
 * Inputs for this contract are:
 *   - ArtBlocks contract address and project id
 *   - Array of artist addresses for payment. The order of this list must match the order of palettes.
 * 
 * Palette pool division calculation is:
 *   - proceedsPerPiece = funds stored in contract / number of pieces minted (from ArtBlocks contract)
 *   - prorataPercentage = 1 / # of palettes
 *   - proceedsPerArtist = 
 *            proceedsPerPiece * prorataPercentage * (count of pieces collector did not change a palette)
 *          + proceedsPerPiece * (count of pieces collecor changed palette to that artist)
 * 
 *
 *
 * Stages of the process are:
 * 1) Initialization - the contract is deployed pointing to the specific ArtBlocks contract and 
 *    project id, and includes the payment addresses of all artists (none of these settings can be changed)
 * 2) Primaries collection & palette selection - while the primary sales occur, this contract can
 *    receive funds and also perform palette selection
 *    For any given piece, the owner of the token may select 'change palette'. The override is recorded, 
 *       as well as counts updated per artist
 * 3) Palette selection only - contract owner (minimizer) will pause primary mints on Plottables. At this point
 *    no further funds are expected. 
 * 4) Palette selection concluded - contract owner will flip the contract to stop further collection decisions.
 *    Two operations become available:
 *    a) GetPaletteDecisions: a summary of collector choices, to be re-integrated back into the art code
 *    b) DisperseFunds: a one-time operation to perform a full transfer of the palette pool to palette creators
 * 
 */


import "@openzeppelin-4.7/contracts/access/Ownable.sol";
import "@openzeppelin-4.7/contracts/token/ERC721/IERC721.sol";
import '@artblocks/contracts/contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol';


contract ScribbleTogetherPaletteSelections is Ownable {
    
    string public constant CONFIRMATION_FOR_STOPPING_PALETTE_SELECTION = 'yes conclude palette selection';
    string public constant CONFIRMATION_FOR_DISTRIBUTING_PAYOUTS = 'yes distribute payouts';
    
    
    IGenArt721CoreContractV3_Engine public plottablesContract;
    uint256 public projectId;
    address payable[] public paletteCreators;
    bool public paletteSelectionConcluded; 
    bool public payoutsDistributed; 
    
    uint256 private numberOfTokensWithChangedPalettesTotal;
    uint256[] private numberOfTokensWithChangedPalettesPerPalette;
    mapping(uint => uint) private tokenToOverriddenPalette; //zero means no override, otherwise the value is overridden palette - 1
    
    
    struct PaletteSelection {
        uint256 tokenId;
        uint256 defaultPalette;
        uint256 selectedPalette;
    }
    
    
    constructor(IGenArt721CoreContractV3_Engine _plottablesContract, uint256 _projectId, address payable[] memory _paletteCreators) 
    Ownable() 
    {
        plottablesContract = _plottablesContract;
        projectId = _projectId;
        paletteCreators = _paletteCreators;
        paletteSelectionConcluded = false;
        payoutsDistributed = false;
        numberOfTokensWithChangedPalettesPerPalette = new uint256[](paletteCreators.length);
        
        require(plottablesContract.projectIdToArtistAddress(projectId) == msg.sender, 'Deployer must be project artist');
        require(numInvocations() == 0, 'Project cannot have any mints yet');
        require(paletteCreators.length > 0, 'Need at least one palette');
    }
    
    receive() external payable requirePayoutsNotDistributed {}
    
    function plottablesTokenIdFor(uint256 _tokenId) private view returns (uint256) {
        return projectId * 1e6 + _tokenId;
    }
    
    function numPalettes() public view returns (uint256) {
        return paletteCreators.length;
    }
    
    function numInvocations() public view returns (uint256) {
        (uint256 invocations, , , , , ) = plottablesContract.projectStateData(projectId);
        return invocations;
    }
    
    function hashForTokenId(uint256 _tokenId) public view returns (bytes32) {
        require(_tokenId < numInvocations(), 'Invalid token');
        return plottablesContract.tokenIdToHash(plottablesTokenIdFor(_tokenId));
    }
    
    function defaultPaletteForTokenId(uint256 _tokenId) public view returns (uint256) {
        return calculateDefaultPaletteForHash(hashForTokenId(_tokenId), paletteCreators.length);
    }
    
    function selectedPaletteForTokenId(uint256 _tokenId) public view returns (uint256) {
        return paletteSelectionForTokenId(_tokenId).selectedPalette;
    }
    
    function paletteSelectionForTokenId(uint256 _tokenId) private view returns (PaletteSelection memory selection) {
        selection.tokenId = _tokenId;
        selection.defaultPalette = defaultPaletteForTokenId(_tokenId);
        
        uint256 paletteSelection = tokenToOverriddenPalette[_tokenId];
        if(paletteSelection == 0) {
            selection.selectedPalette = selection.defaultPalette;
        }
        else {
            selection.selectedPalette = paletteSelection-1;
        }
    }
    
    function paletteSelectionsInRange(uint256 _firstTokenId, uint256 _numTokens) public view returns (PaletteSelection[] memory selections) {
        selections = new PaletteSelection[](_numTokens);
        
        for(uint i = 0; i < _numTokens; i++) {
            selections[i] = paletteSelectionForTokenId(i + _firstTokenId);
        }
    }
    
    function allPaletteSelections() public view returns (PaletteSelection[] memory selections) {
        return paletteSelectionsInRange(0, numInvocations());
    }
    
    function numTokensOverridingByPalette() public view returns (uint256[] memory result) {
        return numberOfTokensWithChangedPalettesPerPalette;
    }
    
    function selectPaletteForTokenId(uint256 _tokenId, uint256 _newPalette) public {
        
        require(IERC721(address(plottablesContract)).ownerOf(plottablesTokenIdFor(_tokenId)) == msg.sender, 'Not owner of token');
        
        uint256 defaultPalette = defaultPaletteForTokenId(_tokenId);
        uint256 currentPalette = selectedPaletteForTokenId(_tokenId);
        
        if(_newPalette != currentPalette) {
            if(currentPalette != defaultPalette) {
                numberOfTokensWithChangedPalettesPerPalette[currentPalette]--;
                numberOfTokensWithChangedPalettesTotal--;
            }
            
            if(_newPalette != defaultPalette) {
                tokenToOverriddenPalette[_tokenId] = _newPalette+1;
                numberOfTokensWithChangedPalettesPerPalette[_newPalette]++;
                numberOfTokensWithChangedPalettesTotal++;
            }
            else {
                tokenToOverriddenPalette[_tokenId] = 0;
            }
        }
    }
    
    
    function numDefaultInvocations() public view returns (uint256) {
        return numInvocations() - numberOfTokensWithChangedPalettesTotal;
    }
    
    function numChangedInvocations() public view returns (uint256) {
        return numberOfTokensWithChangedPalettesTotal;
    }
    
    function calculatePayouts() public view returns (uint256[] memory payouts) {
        uint256 tokensCount = numInvocations();
        
        require(tokensCount > 0, 'No tokens');
        
        uint256 palettesCount = paletteCreators.length;
        uint256 totalDistribution = address(this).balance;
        uint256 cumulativeDistribution = 0;
        
        uint256 proRataDistributionPerPalette = totalDistribution * numDefaultInvocations() / tokensCount / palettesCount;
        
        payouts = new uint256[](palettesCount);
        
        for(uint256 thisPalette = 0; thisPalette < palettesCount - 1; thisPalette++) {
            uint256 thisDistributionForTokensSelectingThisPalette = totalDistribution * numberOfTokensWithChangedPalettesPerPalette[thisPalette] / tokensCount;
            uint256 thisDistribution = proRataDistributionPerPalette + thisDistributionForTokensSelectingThisPalette;
            payouts[thisPalette] = thisDistribution;
            cumulativeDistribution += thisDistribution;
        }
        
        payouts[payouts.length-1] = totalDistribution - cumulativeDistribution;
    }
    
    
    function concludePaletteSelection(string memory _confirmation) public 
    onlyOwner
    requireValidConfirmation(CONFIRMATION_FOR_STOPPING_PALETTE_SELECTION, _confirmation) {
        (,,,bool paused,,) = plottablesContract.projectStateData(projectId);
        require(paused == true, 'Project is not paused');
        paletteSelectionConcluded = true;
    }
    
    
    function distributePayouts(string memory _confirmation) public 
    onlyOwner 
    requireValidConfirmation(CONFIRMATION_FOR_DISTRIBUTING_PAYOUTS, _confirmation)
    requirePayoutsNotDistributed {
        
        require(paletteSelectionConcluded, 'Palette selection not concluded');
        
        uint256[] memory payouts = calculatePayouts();
        
        for(uint256 i=0; i<payouts.length; i++) {
            (bool sent, ) = payable(paletteCreators[i]).call{value: payouts[i]}("");
            require(sent, "Failed to send");
        }
        
        payoutsDistributed = true;
    }
    
    
    
    
    
    /* logic to match this in JavaScript: 
     *
     *     const hashToInt = hashBytes32 => parseInt(hashBytes32.slice(-8), 16)
     *     const hashToPalette = (hashBytes32, numPalettes) => hashToInt(hashBytes32) % numPalettes
     */
    
    function calculateDefaultPaletteForHash(bytes32 _hash, uint256 _numPalettes) public pure returns (uint256) {
        uint256 intOfHash = uint256(uint32(uint256(_hash)));
        return intOfHash % _numPalettes;
    }
    
    
    
    
    modifier requireValidConfirmation(string memory expectedConfirmation, string memory actualConfirmation) {
        require(
            keccak256(abi.encodePacked(expectedConfirmation)) == keccak256(abi.encodePacked(actualConfirmation)),
            "Invalid confirmation string provided"
        );
        
        _; // continue execution
    }
    
    modifier requirePayoutsNotDistributed() {
        require(!payoutsDistributed, 'Payouts already distributed');
        
        _; // continue execution
    }
    
    
}
设置
{
  "compilationTarget": {
    "contracts/ScribbleTogetherPaletteSelections.sol": "ScribbleTogetherPaletteSelections"
  },
  "evmVersion": "paris",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": false,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"contract IGenArt721CoreContractV3_Engine","name":"_plottablesContract","type":"address"},{"internalType":"uint256","name":"_projectId","type":"uint256"},{"internalType":"address payable[]","name":"_paletteCreators","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"CONFIRMATION_FOR_DISTRIBUTING_PAYOUTS","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONFIRMATION_FOR_STOPPING_PALETTE_SELECTION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allPaletteSelections","outputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"defaultPalette","type":"uint256"},{"internalType":"uint256","name":"selectedPalette","type":"uint256"}],"internalType":"struct ScribbleTogetherPaletteSelections.PaletteSelection[]","name":"selections","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_hash","type":"bytes32"},{"internalType":"uint256","name":"_numPalettes","type":"uint256"}],"name":"calculateDefaultPaletteForHash","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"calculatePayouts","outputs":[{"internalType":"uint256[]","name":"payouts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_confirmation","type":"string"}],"name":"concludePaletteSelection","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"defaultPaletteForTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_confirmation","type":"string"}],"name":"distributePayouts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"hashForTokenId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numChangedInvocations","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numDefaultInvocations","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numInvocations","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numPalettes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numTokensOverridingByPalette","outputs":[{"internalType":"uint256[]","name":"result","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"paletteCreators","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paletteSelectionConcluded","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_firstTokenId","type":"uint256"},{"internalType":"uint256","name":"_numTokens","type":"uint256"}],"name":"paletteSelectionsInRange","outputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"defaultPalette","type":"uint256"},{"internalType":"uint256","name":"selectedPalette","type":"uint256"}],"internalType":"struct ScribbleTogetherPaletteSelections.PaletteSelection[]","name":"selections","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"payoutsDistributed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"plottablesContract","outputs":[{"internalType":"contract IGenArt721CoreContractV3_Engine","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"projectId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_newPalette","type":"uint256"}],"name":"selectPaletteForTokenId","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"selectedPaletteForTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]