ETH Price: $2,323.35 (+1.03%)

Contract

0x70f80ae910081409DF29c6D779Cd83208B751636
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
0x60a03461184163962023-10-23 23:57:59330 days ago1698105479IN
 Create: BasicMetadataProvider
0 ETH0.0460008333.96542604

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
BasicMetadataProvider

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 100 runs

Other Settings:
shanghai EvmVersion
File 1 of 43 : BasicMetadataProvider.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import { Party } from "../party/Party.sol";
import { MetadataRegistry } from "./MetadataRegistry.sol";
import { MetadataProvider } from "./MetadataProvider.sol";
import { IGlobals } from "../globals/IGlobals.sol";
import { LibGlobals } from "../globals/LibGlobals.sol";
import { PartyNFTRenderer } from "./PartyNFTRenderer.sol";

/// @notice A contract that provides custom metadata for Party Cards.
contract BasicMetadataProvider is MetadataProvider {
    error MetadataTooLarge();

    constructor(IGlobals globals) MetadataProvider(globals) {}

    /// @inheritdoc MetadataProvider
    function getMetadata(address instance, uint256) external view override returns (bytes memory) {
        Metadata memory metadata;

        metadata.name = metadata.collectionName = Party(payable(instance)).name();
        metadata.description = metadata.collectionDescription = retrieveDynamicMetadataInfo(
            instance,
            MetadataFields.DESCRIPTION
        );
        metadata.externalURL = retrieveDynamicMetadataInfo(instance, MetadataFields.EXTERNAL_URL);
        metadata.image = retrieveDynamicMetadataInfo(instance, MetadataFields.IMAGE);
        metadata.banner = retrieveDynamicMetadataInfo(instance, MetadataFields.BANNER);
        metadata.animationURL = retrieveDynamicMetadataInfo(instance, MetadataFields.ANIMATION_URL);
        metadata.collectionExternalURL = retrieveDynamicMetadataInfo(
            instance,
            MetadataFields.COLLECTION_EXTERNAL_URL
        );
        metadata.royaltyReceiver = address(
            uint160(uint256(retrieveValueMetadataInfo(instance, MetadataFields.ROYALTY_RECEIVER)))
        );
        metadata.royaltyAmount = uint256(
            retrieveValueMetadataInfo(instance, MetadataFields.ROYALTY_AMOUNT)
        );
        metadata.renderingMethod = PartyNFTRenderer.RenderingMethod(
            uint256(retrieveValueMetadataInfo(instance, MetadataFields.RENDERING_METHOD))
        );

        return abi.encode(metadata);
    }

    struct Metadata {
        string name;
        bytes description;
        bytes externalURL;
        bytes image;
        bytes banner;
        bytes animationURL;
        string collectionName;
        bytes collectionDescription;
        bytes collectionExternalURL;
        address royaltyReceiver;
        uint256 royaltyAmount;
        PartyNFTRenderer.RenderingMethod renderingMethod;
    }

    /// @notice Set the metadata for a Party instance.
    /// @param instance The address of the instance.
    /// @param metadata The encoded metadata.
    function setMetadata(address instance, bytes calldata metadata) external override {
        if (instance != msg.sender) {
            MetadataRegistry registry = MetadataRegistry(
                _GLOBALS.getAddress(LibGlobals.GLOBAL_METADATA_REGISTRY)
            );

            // Check if the caller is authorized to set metadata for the instance.
            if (!registry.isRegistrar(msg.sender, instance)) {
                revert NotAuthorized(msg.sender, instance);
            }
        }

        Metadata memory decodedMetadata = abi.decode(metadata, (Metadata));

        if (decodedMetadata.description.length != 0) {
            storeMetadataInfo(
                instance,
                MetadataFields.DESCRIPTION,
                decodedMetadata.description,
                false
            );
        }

        if (decodedMetadata.externalURL.length != 0) {
            storeMetadataInfo(
                instance,
                MetadataFields.EXTERNAL_URL,
                decodedMetadata.externalURL,
                false
            );
        }

        if (decodedMetadata.image.length != 0) {
            storeMetadataInfo(instance, MetadataFields.IMAGE, decodedMetadata.image, false);
        }

        if (decodedMetadata.banner.length != 0) {
            storeMetadataInfo(instance, MetadataFields.BANNER, decodedMetadata.banner, false);
        }

        if (decodedMetadata.animationURL.length != 0) {
            storeMetadataInfo(
                instance,
                MetadataFields.ANIMATION_URL,
                decodedMetadata.animationURL,
                false
            );
        }

        if (decodedMetadata.collectionExternalURL.length != 0) {
            storeMetadataInfo(
                instance,
                MetadataFields.COLLECTION_EXTERNAL_URL,
                decodedMetadata.collectionExternalURL,
                false
            );
        }

        if (decodedMetadata.royaltyReceiver != address(0)) {
            storeMetadataInfo(
                instance,
                MetadataFields.ROYALTY_RECEIVER,
                abi.encode(decodedMetadata.royaltyReceiver),
                true
            );
        }

        if (decodedMetadata.royaltyAmount != 0) {
            storeMetadataInfo(
                instance,
                MetadataFields.ROYALTY_AMOUNT,
                abi.encode(decodedMetadata.royaltyAmount),
                true
            );
        }

        if (decodedMetadata.renderingMethod != PartyNFTRenderer.RenderingMethod.ENUM_OFFSET) {
            storeMetadataInfo(
                instance,
                MetadataFields.RENDERING_METHOD,
                abi.encode(decodedMetadata.renderingMethod),
                true
            );
        }

        emit MetadataSet(instance, metadata);
    }

    /// @notice The indexes of the metadata fields in storage (if dynamic contains a start slot)
    enum MetadataFields {
        DESCRIPTION,
        EXTERNAL_URL,
        IMAGE,
        BANNER,
        ANIMATION_URL,
        COLLECTION_EXTERNAL_URL,
        ROYALTY_RECEIVER,
        ROYALTY_AMOUNT,
        RENDERING_METHOD
    }

    /// @notice Stores a metadata field to storage
    /// @param instance The instance to store the metadata for
    /// @param field The field to store
    /// @param data The data to store
    /// @param isValue Whether the data is a value type or a dynamic type
    function storeMetadataInfo(
        address instance,
        MetadataFields field,
        bytes memory data,
        bool isValue
    ) private {
        uint256 metadataSlot;
        assembly {
            metadataSlot := _metadata.slot
        }
        uint256 slot = uint256(keccak256(abi.encode(instance, metadataSlot))) + uint8(field);

        uint256 value;
        assembly {
            value := mload(add(data, 0x20))
        }

        if (!isValue) {
            // Check if we can force the data into a single slot
            uint256 dataLength = data.length;
            // We use the first bit as a signal that the data is an slot number
            if (dataLength > 32 || (dataLength == 32 && value >> 255 == 1)) {
                if (dataLength > type(uint16).max) {
                    revert MetadataTooLarge();
                }
                // Store the slot to the start of this data (first bit 1 indicates its a slot)
                uint256 dynamicSlot = (uint256(1) << 255) |
                    uint256(keccak256(abi.encode(instance, metadataSlot, field)));
                assembly {
                    // Store the dynamic data start slot in the value slot
                    sstore(slot, dynamicSlot)
                }

                // Store first slot with first 16 bits as size
                uint16 length = uint16(data.length);
                assembly {
                    sstore(dynamicSlot, or(shl(240, length), shr(16, mload(add(data, 0x20)))))
                }

                for (uint256 i = 30; i < data.length; i += 32) {
                    uint256 mask = type(uint256).max;
                    if (data.length < i + 32) {
                        // Don't store the whole slot bc less than a slot worth of data
                        mask = mask << (256 - (data.length - i) * 8);
                    }

                    uint256 slotIncrement = i + 2;
                    bytes32 toStore;

                    assembly {
                        toStore := mload(add(i, add(0x20, data)))
                        toStore := and(toStore, mask)
                        sstore(add(dynamicSlot, slotIncrement), toStore)
                    }
                }
                return;
            }

            // Shift data to right side of slot
            value = value >> (256 - dataLength * 8);
        }

        // treating as value type
        assembly {
            sstore(slot, value)
        }
    }

    /// @notice Retrieves a value metadata field from storage
    function retrieveValueMetadataInfo(
        address instance,
        MetadataFields field
    ) private view returns (bytes32 res) {
        uint256 metadataSlotNumber;
        assembly {
            metadataSlotNumber := _metadata.slot
        }
        uint256 slot = uint256(keccak256(abi.encode(instance, metadataSlotNumber))) + uint8(field);
        assembly {
            res := sload(slot)
        }
    }

    /// @notice Retrieves a dynamic metadata field from storage
    function retrieveDynamicMetadataInfo(
        address instance,
        MetadataFields field
    ) private view returns (bytes memory) {
        uint256 metadataSlotNumber;
        assembly {
            metadataSlotNumber := _metadata.slot
        }
        uint256 slot = uint256(keccak256(abi.encode(instance, metadataSlotNumber))) + uint8(field);
        bytes32 slotData;
        assembly {
            slotData := sload(slot)
        }
        if (slotData >> 255 == 0) {
            // Remove extra zeros from the data
            bytes memory returnData;
            assembly {
                let dataSize := 0
                for {
                    let i := 0
                } lt(i, 32) {
                    i := add(i, 1)
                } {
                    if iszero(shr(mul(i, 8), slotData)) {
                        dataSize := i
                        break
                    }
                }
                let freeMem := mload(0x40)
                mstore(freeMem, dataSize)
                mstore(add(freeMem, 0x20), shl(sub(256, mul(dataSize, 8)), slotData))
                mstore(0x40, add(freeMem, 0x40))
                returnData := freeMem
            }
            return returnData;
        }

        // Retrieve dymanic data from dynamic slot
        bytes32 firstSlotDynamic;

        bytes memory res;
        assembly {
            res := mload(0x40)
            firstSlotDynamic := sload(slotData)
            mstore(add(res, 30), firstSlotDynamic)
            mstore(0x40, add(res, add(shr(240, firstSlotDynamic), 0x20)))
        }

        for (uint256 i = 32; i < res.length; i += 32) {
            uint256 slotIncrement = i + 30;
            bytes32 data;
            assembly {
                data := sload(add(slotData, i))
                mstore(add(res, slotIncrement), data)
            }
        }

        return res;
    }
}

File 2 of 43 : Party.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC721.sol";

import "./PartyGovernanceNFT.sol";
import "./PartyGovernance.sol";

/// @notice The governance contract that also custodies the precious NFTs. This
///         is also the Governance NFT 721 contract.
contract Party is PartyGovernanceNFT {
    // Arguments used to initialize the party.
    struct PartyOptions {
        PartyGovernance.GovernanceOpts governance;
        ProposalStorage.ProposalEngineOpts proposalEngine;
        string name;
        string symbol;
        uint256 customizationPresetId;
    }

    // Arguments used to initialize the `PartyGovernanceNFT`.
    struct PartyInitData {
        PartyOptions options;
        IERC721[] preciousTokens;
        uint256[] preciousTokenIds;
        address[] authorities;
        uint40 rageQuitTimestamp;
    }

    /// @notice Version ID of the party implementation contract.
    uint16 public constant VERSION_ID = 1;

    // Set the `Globals` contract.
    constructor(IGlobals globals) PartyGovernanceNFT(globals) {}

    /// @notice Initializer to be delegatecalled by `Proxy` constructor. Will
    ///         revert if called outside the constructor.
    /// @param initData Options used to initialize the party governance.
    function initialize(PartyInitData memory initData) external onlyConstructor {
        PartyGovernanceNFT._initialize(
            initData.options.name,
            initData.options.symbol,
            initData.options.customizationPresetId,
            initData.options.governance,
            initData.options.proposalEngine,
            initData.preciousTokens,
            initData.preciousTokenIds,
            initData.authorities,
            initData.rageQuitTimestamp
        );
    }

    receive() external payable {}
}

File 3 of 43 : MetadataRegistry.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import { IGlobals } from "../globals/IGlobals.sol";
import { LibGlobals } from "../globals/LibGlobals.sol";
import { IMetadataProvider } from "./IMetadataProvider.sol";
import { Multicall } from "../utils/Multicall.sol";

/// @notice A registry of custom metadata providers for Party Cards.
contract MetadataRegistry is Multicall {
    event ProviderSet(address indexed instance, IMetadataProvider indexed provider);
    event RegistrarSet(address indexed registrar, address indexed instance, bool canSetData);

    error NotAuthorized(address caller, address instance);

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and it’s address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice Get the metadata provider for a Party instance.
    mapping(address instance => IMetadataProvider provider) public getProvider;

    /// @notice Whether or not an address is a registar that can set the
    ///         provider and metadata for another instance. If registrar is set
    ///         true for `address(1)`, the address is a universal registar and
    ///         can set data for any instance.
    /// @dev Registrars' ability to set metadata for another instance must also be
    ///      supported by the metadata provider used by that instance, indicated by
    ///      `IMetadataProvider.supportsRegistrars()`.
    mapping(address registrar => mapping(address instance => bool canSetData)) private _isRegistrar;

    /// @param globals The address of the `Globals` contract.
    /// @param registrars The addresses of the initial universal registrars.
    constructor(IGlobals globals, address[] memory registrars) {
        _GLOBALS = globals;

        // Set the initial universal registrars.
        for (uint256 i = 0; i < registrars.length; i++) {
            _isRegistrar[registrars[i]][address(1)] = true;
        }
    }

    /// @notice Set the metadata provider for a Party instance.
    /// @param instance The address of the instance.
    /// @param provider The address of the metadata provider.
    function setProvider(address instance, IMetadataProvider provider) external {
        // Check if the caller is authorized to set the provider for the instance.
        if (!isRegistrar(msg.sender, instance)) revert NotAuthorized(msg.sender, instance);

        getProvider[instance] = provider;

        emit ProviderSet(instance, provider);
    }

    /// @notice Set whether or not an address can set metadata for a Party instance.
    /// @param registrar The address of the possible registrar.
    /// @param instance The address of the instance the registrar can set
    ///                 metadata for.
    /// @param canSetData Whether or not the address can set data for the instance.
    function setRegistrar(address registrar, address instance, bool canSetData) external {
        if (
            msg.sender != instance &&
            msg.sender != _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET)
        ) {
            revert NotAuthorized(msg.sender, instance);
        }

        _isRegistrar[registrar][instance] = canSetData;

        emit RegistrarSet(registrar, instance, canSetData);
    }

    /// @notice Get whether or not an address can set metadata for a Party instance.
    /// @param registrar The address of the possible registrar.
    /// @param instance The address of the instance the registrar can set
    ///                 metadata for.
    /// @return canSetData Whether or not the address can set data for the instance.
    function isRegistrar(address registrar, address instance) public view returns (bool) {
        return
            registrar == instance ||
            _isRegistrar[registrar][address(1)] ||
            _isRegistrar[registrar][instance];
    }

    /// @notice Get the metadata for a Party instance.
    /// @param instance The address of the instance.
    /// @param tokenId The ID of the token to get the metadata for.
    /// @return metadata The encoded metadata.
    function getMetadata(address instance, uint256 tokenId) external view returns (bytes memory) {
        IMetadataProvider provider = getProvider[instance];

        return
            address(provider) != address(0) ? provider.getMetadata(instance, tokenId) : bytes("");
    }
}

File 4 of 43 : MetadataProvider.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import { Multicall } from "../utils/Multicall.sol";
import { MetadataRegistry } from "./MetadataRegistry.sol";
import { IMetadataProvider } from "./IMetadataProvider.sol";
import { IGlobals } from "../globals/IGlobals.sol";
import { LibGlobals } from "../globals/LibGlobals.sol";

/// @notice A contract that provides custom metadata for Party Cards.
contract MetadataProvider is IMetadataProvider, Multicall {
    event MetadataSet(address indexed instance, bytes metadata);

    error NotAuthorized(address caller, address instance);

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and it’s address will never change.
    IGlobals internal immutable _GLOBALS;

    /// @inheritdoc IMetadataProvider
    bool public constant supportsRegistrars = true;

    // The metadata for each Party instance.
    mapping(address instance => bytes metadata) internal _metadata;

    // Set the `Globals` contract.
    constructor(IGlobals globals) {
        _GLOBALS = globals;
    }

    /// @inheritdoc IMetadataProvider
    function getMetadata(
        address instance,
        uint256
    ) external view virtual override returns (bytes memory) {
        return _metadata[instance];
    }

    /// @notice Set the metadata for a Party instance.
    /// @param instance The address of the instance.
    /// @param metadata The encoded metadata.
    function setMetadata(address instance, bytes memory metadata) external virtual {
        if (instance != msg.sender) {
            MetadataRegistry registry = MetadataRegistry(
                _GLOBALS.getAddress(LibGlobals.GLOBAL_METADATA_REGISTRY)
            );

            // Check if the caller is authorized to set metadata for the instance.
            if (!registry.isRegistrar(msg.sender, instance)) {
                revert NotAuthorized(msg.sender, instance);
            }
        }

        _metadata[instance] = metadata;

        emit MetadataSet(instance, metadata);
    }
}

File 5 of 43 : IGlobals.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/Implementation.sol";

// Single registry of global values controlled by multisig.
// See `LibGlobals` for all valid keys.
interface IGlobals {
    function multiSig() external view returns (address);

    function getBytes32(uint256 key) external view returns (bytes32);

    function getUint256(uint256 key) external view returns (uint256);

    function getBool(uint256 key) external view returns (bool);

    function getAddress(uint256 key) external view returns (address);

    function getImplementation(uint256 key) external view returns (Implementation);

    function getIncludesBytes32(uint256 key, bytes32 value) external view returns (bool);

    function getIncludesUint256(uint256 key, uint256 value) external view returns (bool);

    function getIncludesAddress(uint256 key, address value) external view returns (bool);

    function setBytes32(uint256 key, bytes32 value) external;

    function setUint256(uint256 key, uint256 value) external;

    function setBool(uint256 key, bool value) external;

    function setAddress(uint256 key, address value) external;

    function setIncludesBytes32(uint256 key, bytes32 value, bool isIncluded) external;

    function setIncludesUint256(uint256 key, uint256 value, bool isIncluded) external;

    function setIncludesAddress(uint256 key, address value, bool isIncluded) external;
}

File 6 of 43 : LibGlobals.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

// Valid keys in `IGlobals`. Append-only.
library LibGlobals {
    // The Globals commented out below were depreciated in 1.2; factories
    // can now choose the implementation address to deploy and no longer
    // deploy the latest implementation. They will no longer be updated
    // in future releases.
    //
    // See https://github.com/PartyDAO/party-migrations for
    // implementation addresses by release.

    uint256 internal constant GLOBAL_PARTY_IMPL = 1;
    uint256 internal constant GLOBAL_PROPOSAL_ENGINE_IMPL = 2;
    uint256 internal constant GLOBAL_PARTY_FACTORY = 3;
    uint256 internal constant GLOBAL_GOVERNANCE_NFT_RENDER_IMPL = 4;
    uint256 internal constant GLOBAL_CF_NFT_RENDER_IMPL = 5;
    uint256 internal constant GLOBAL_OS_ZORA_AUCTION_TIMEOUT = 6;
    uint256 internal constant GLOBAL_OS_ZORA_AUCTION_DURATION = 7;
    // uint256 internal constant GLOBAL_AUCTION_CF_IMPL = 8;
    // uint256 internal constant GLOBAL_BUY_CF_IMPL = 9;
    // uint256 internal constant GLOBAL_COLLECTION_BUY_CF_IMPL = 10;
    uint256 internal constant GLOBAL_DAO_WALLET = 11;
    uint256 internal constant GLOBAL_TOKEN_DISTRIBUTOR = 12;
    uint256 internal constant GLOBAL_OPENSEA_CONDUIT_KEY = 13;
    uint256 internal constant GLOBAL_OPENSEA_ZONE = 14;
    uint256 internal constant GLOBAL_PROPOSAL_MAX_CANCEL_DURATION = 15;
    uint256 internal constant GLOBAL_ZORA_MIN_AUCTION_DURATION = 16;
    uint256 internal constant GLOBAL_ZORA_MAX_AUCTION_DURATION = 17;
    uint256 internal constant GLOBAL_ZORA_MAX_AUCTION_TIMEOUT = 18;
    uint256 internal constant GLOBAL_OS_MIN_ORDER_DURATION = 19;
    uint256 internal constant GLOBAL_OS_MAX_ORDER_DURATION = 20;
    uint256 internal constant GLOBAL_DISABLE_PARTY_ACTIONS = 21;
    uint256 internal constant GLOBAL_RENDERER_STORAGE = 22;
    uint256 internal constant GLOBAL_PROPOSAL_MIN_CANCEL_DURATION = 23;
    // uint256 internal constant GLOBAL_ROLLING_AUCTION_CF_IMPL = 24;
    // uint256 internal constant GLOBAL_COLLECTION_BATCH_BUY_CF_IMPL = 25;
    uint256 internal constant GLOBAL_METADATA_REGISTRY = 26;
    // uint256 internal constant GLOBAL_CROWDFUND_FACTORY = 27;
    // uint256 internal constant GLOBAL_INITIAL_ETH_CF_IMPL = 28;
    // uint256 internal constant GLOBAL_RERAISE_ETH_CF_IMPL = 29;
    uint256 internal constant GLOBAL_SEAPORT = 30;
    uint256 internal constant GLOBAL_CONDUIT_CONTROLLER = 31;
}

File 7 of 43 : PartyNFTRenderer.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import { LibSafeCast } from "../utils/LibSafeCast.sol";
import { LibRawResult } from "../utils/LibRawResult.sol";
import { LibRenderer, Color, ColorType } from "../utils/LibRenderer.sol";
import { Strings } from "../utils/vendor/Strings.sol";
import { Base64 } from "../utils/vendor/Base64.sol";

import { RendererBase } from "./RendererBase.sol";
import { RendererStorage } from "./RendererStorage.sol";
import { MetadataRegistry } from "./MetadataRegistry.sol";
import { IMetadataRegistry1_1 } from "./IMetadataRegistry1_1.sol";
import { Party, PartyGovernance } from "../party/PartyGovernance.sol";
import { PartyGovernanceNFT } from "../party/PartyGovernanceNFT.sol";
import { TokenDistributor } from "../distribution/TokenDistributor.sol";
import { IGlobals } from "../globals/IGlobals.sol";
import { LibGlobals } from "../globals/LibGlobals.sol";
import { IFont } from "./fonts/IFont.sol";

contract PartyNFTRenderer is RendererBase {
    using LibSafeCast for uint256;
    using LibRawResult for bytes;
    using Strings for uint256;
    using Strings for address;

    error InvalidTokenIdError();
    error ExternalURLTooLarge();

    // The crowdfund type used to create this party.
    enum CrowdfundType {
        // Party was created using flexible crowdfund configuration which
        // allowed members to contribute an amount in range and variable voting
        // power awarded in proportion to the amount contributed (e.g. 1 ETH
        // contributed with 10 ETH total contributed resulting in 10% voting
        // power for contribution).
        Flexible,
        // Party was created using fixed crowdfund configuration which awarded
        // members with fixed voting power per membership minted with each mint
        // costing a fixed amount (e.g. 1 ETH for 10% voting power per mint,
        // total of 10 mints available).
        Fixed
    }

    enum RenderingMethod {
        ENUM_OFFSET, // Apply an enum offset so that first valid value is 1
        FlexibleCrowdfund,
        FixedCrowdfund
    }

    struct ProposalData {
        uint256 id;
        string status;
    }

    struct Metadata {
        string name;
        string description;
        string externalURL;
        string image;
        string banner;
        string animationURL;
        string collectionName;
        string collectionDescription;
        string collectionExternalURL;
        address royaltyReceiver;
        uint256 royaltyAmount;
        RenderingMethod renderingMethod;
    }

    address immutable IMPL;

    uint256 constant PARTY_CARD_DATA = 1;
    address constant PARTYSTAR_PARTY_ADDRESS = 0x118928CCAc2035B578ae2D35FBFc2c120B6c4B82;
    address constant PARTYSTAR_CROWDFUND_ADDRESS = 0x0Bf08f7b6474C2aCCB9b9e325acb6FbcC682dE82;
    IMetadataRegistry1_1 constant OLD_METADATA_REGISTRY =
        IMetadataRegistry1_1(0x175487875F0318EdbAB54BBA442fF53b36e96015);
    /// @notice The old token distributor contract address.
    address immutable OLD_TOKEN_DISTRIBUTOR;

    /// @notice The base url for external URLs. External URL is BASE_EXTERNAL_URL + PARTY_ADDRESS
    /// @dev First byte is the size of the data, the rest is the data (starting from MSB)
    bytes32 private immutable BASE_EXTERNAL_URL_DATA;

    constructor(
        IGlobals globals,
        RendererStorage rendererStorage,
        IFont font,
        address oldTokenDistributor,
        string memory baseExternalURL
    ) RendererBase(globals, rendererStorage, font) {
        IMPL = address(this);
        OLD_TOKEN_DISTRIBUTOR = oldTokenDistributor;

        bytes memory baseExternalURLBytes = bytes(baseExternalURL);
        if (baseExternalURLBytes.length > 31) {
            // Must be less than or equal to 31 bytes because 1 byte is used for the length.
            revert ExternalURLTooLarge();
        }
        bytes32 baseExternalUrlData = bytes32(baseExternalURLBytes.length) << 248;
        baseExternalUrlData |= bytes32(baseExternalURLBytes) >> 8;
        BASE_EXTERNAL_URL_DATA = baseExternalUrlData;
    }

    function royaltyInfo(
        uint256,
        uint256
    ) external view returns (address receiver, uint256 royaltyAmount) {
        // Get any custom metadata for this party.
        Metadata memory metadata = getCustomMetadata(0);

        // By default, there are no royalties.
        return (metadata.royaltyReceiver, metadata.royaltyAmount);
    }

    function contractURI() external view override returns (string memory) {
        (bool isDarkMode, Color color) = getCustomizationChoices();
        (string memory image, string memory banner) = LibRenderer.getCollectionImageAndBanner(
            color,
            isDarkMode
        );

        // Get any custom metadata for this party.
        Metadata memory metadata = getCustomMetadata(0);

        return
            string.concat(
                "data:application/json;base64,",
                Base64.encode(
                    abi.encodePacked(
                        '{"name":"',
                        bytes(metadata.collectionName).length == 0
                            ? generateCollectionName()
                            : metadata.collectionName,
                        '", "description":"',
                        bytes(metadata.collectionDescription).length == 0
                            ? generateCollectionDescription()
                            : metadata.collectionDescription,
                        '", "external_url":"',
                        bytes(metadata.collectionExternalURL).length == 0
                            ? generateExternalURL()
                            : metadata.collectionExternalURL,
                        '", "image":"',
                        bytes(metadata.image).length == 0 ? image : metadata.image,
                        // Determine which banner to render.
                        bytes(metadata.banner).length == 0
                            ? bytes(metadata.image).length == 0 // No custom banner and no custom image, use the default Party banner.
                                ? string.concat('", "banner":"', banner) // No custom banner but custom image, do not include banner in metadata.
                                : ""
                            : string.concat('", "banner":"', metadata.banner), // Custom banner, use it.
                        '"}'
                    )
                )
            );
    }

    function tokenURI(uint256 tokenId) external view returns (string memory) {
        if (PartyGovernanceNFT(address(this)).ownerOf(tokenId) == address(0)) {
            revert InvalidTokenIdError();
        }

        // Add backward compatibility for rendering custom metadata for
        // Partystar party using old `MetadataRegistry` contract.
        if (address(this) == PARTYSTAR_PARTY_ADDRESS) {
            (
                string memory customName,
                string memory customDescription,
                string memory customImage
            ) = OLD_METADATA_REGISTRY.customPartyMetadataByCrowdfund(PARTYSTAR_CROWDFUND_ADDRESS);

            return
                string.concat(
                    "data:application/json;base64,",
                    Base64.encode(
                        abi.encodePacked(
                            '{"name":"',
                            string.concat(customName, " #", tokenId.toString()),
                            '", "description":"',
                            customDescription,
                            '", "external_url":"',
                            generateExternalURL(),
                            '", "attributes": [',
                            generateAttributes(tokenId),
                            '], "image":"',
                            customImage,
                            '"}'
                        )
                    )
                );
        }

        string memory image = generateSVG(
            PartyGovernanceNFT(address(this)).name(),
            generateVotingPowerPercentage(tokenId),
            getLatestProposalStatuses(),
            PartyGovernance(address(this)).lastProposalId(),
            tokenId
        );

        // Get any custom metadata for this party.
        Metadata memory metadata = getCustomMetadata(tokenId);

        // Construct metadata.
        return
            string.concat(
                "data:application/json;base64,",
                Base64.encode(
                    abi.encodePacked(
                        '{"name":"',
                        generateName(
                            bytes(metadata.name).length == 0
                                ? PartyGovernanceNFT(address(this)).name()
                                : metadata.name,
                            tokenId,
                            metadata.renderingMethod
                        ),
                        '", "description":"',
                        bytes(metadata.description).length == 0
                            ? generateDescription(PartyGovernanceNFT(address(this)).name(), tokenId)
                            : string.concat(
                                metadata.description,
                                " ",
                                // Append default description.
                                generateDescription(
                                    PartyGovernanceNFT(address(this)).name(),
                                    tokenId
                                )
                            ),
                        '", "external_url":"',
                        bytes(metadata.externalURL).length == 0
                            ? generateExternalURL()
                            : metadata.externalURL,
                        bytes(metadata.animationURL).length == 0
                            ? ""
                            : string.concat('", "animation_url":"', metadata.animationURL),
                        '", "party_card_url":"', // Custom metadata field.
                        image,
                        '", "image":"',
                        bytes(metadata.image).length == 0 ? image : metadata.image,
                        hasPartyStarted()
                            ? string.concat('", "attributes": [', generateAttributes(tokenId), "]")
                            : '"',
                        "}"
                    )
                )
            );
    }

    function generateName(
        string memory partyName,
        uint256 tokenId,
        RenderingMethod renderingMethod
    ) private view returns (string memory) {
        if (
            renderingMethod == RenderingMethod.FixedCrowdfund ||
            (renderingMethod == RenderingMethod.ENUM_OFFSET &&
                getCrowdfundType() == CrowdfundType.Fixed)
        ) {
            return string.concat(partyName, " #", tokenId.toString());
        } else {
            if (hasPartyStarted()) {
                return string.concat(generateVotingPowerPercentage(tokenId), "% Voting Power");
            } else {
                return "Party Membership";
            }
        }
    }

    function generateExternalURL() private view returns (string memory) {
        bytes32 baseExternalUrlData = BASE_EXTERNAL_URL_DATA;
        uint8 externalUrlLength = uint8(uint256(baseExternalUrlData >> 248));
        string memory baseExternalUrl;
        assembly {
            let freeMem := mload(0x40)
            baseExternalUrl := freeMem
            // Store the length of the data
            mstore(baseExternalUrl, externalUrlLength)
            // Store the data to memory
            mstore(add(baseExternalUrl, 0x20), shl(8, baseExternalUrlData))
            // Update free mem
            mstore(0x40, add(freeMem, 0x40))
        }
        return string.concat(baseExternalUrl, address(this).toHexString());
    }

    function generateDescription(
        string memory partyName,
        uint256 tokenId
    ) private view returns (string memory) {
        if (hasPartyStarted()) {
            return
                string.concat(
                    "This membership represents ",
                    generateVotingPowerPercentage(tokenId),
                    "% voting power in ",
                    partyName,
                    ". Head to ",
                    generateExternalURL(),
                    " to view the Party's latest activity."
                );
        } else {
            return
                string.concat(
                    "This item represents membership in ",
                    partyName,
                    ". Exact voting power will be determined when the crowdfund ends. Head to ",
                    generateExternalURL(),
                    " to view the Party's latest activity."
                );
        }
    }

    function generateAttributes(uint256 tokenId) private view returns (string memory) {
        string memory votingPowerPercentage = generateVotingPowerPercentage(tokenId);

        if (
            keccak256(abi.encodePacked(votingPowerPercentage)) == keccak256(abi.encodePacked("--"))
        ) {
            votingPowerPercentage = "0";
        }

        return
            string.concat(
                '{"trait_type":"Voting Power", "value": "',
                votingPowerPercentage,
                '", "max_value":100}'
            );
    }

    function generateCollectionName() internal view returns (string memory) {
        return string.concat("Party Memberships: ", PartyGovernanceNFT(address(this)).name());
    }

    function generateCollectionDescription() internal view returns (string memory) {
        return
            string.concat(
                "This collection represents memberships in the following Party: ",
                PartyGovernanceNFT(address(this)).name(),
                ". Head to ",
                generateExternalURL(),
                " to view the Party's latest activity."
            );
    }

    function generateSVG(
        string memory partyName,
        string memory votingPowerPercentage,
        PartyGovernance.ProposalStatus[4] memory proposalStatuses,
        uint256 latestProposalId,
        uint256 tokenId
    ) public view returns (string memory) {
        // Get the customization data for this party.
        (bool isDarkMode, Color color) = getCustomizationChoices();

        return
            generateSVG(
                partyName,
                votingPowerPercentage,
                proposalStatuses,
                latestProposalId,
                tokenId,
                color,
                isDarkMode
            );
    }

    function generateSVG(
        string memory partyName,
        string memory votingPowerPercentage,
        PartyGovernance.ProposalStatus[4] memory proposalStatuses,
        uint256 latestProposalId,
        uint256 tokenId,
        Color color,
        bool isDarkMode
    ) public view returns (string memory) {
        return
            string.concat(
                "data:image/svg+xml;base64,",
                Base64.encode(
                    abi.encodePacked(
                        // Split to avoid stack too deep errors
                        generateSVG1(color, isDarkMode),
                        generateSVG2(partyName, color),
                        generateSVG3(partyName, color),
                        generateSVG4(latestProposalId, proposalStatuses),
                        generateSVG5(votingPowerPercentage, color),
                        generateSVG6(tokenId, color)
                    )
                )
            );
    }

    function generateSVG1(Color color, bool isDarkMode) private pure returns (string memory) {
        return
            string.concat(
                '<svg width="540" height="540" viewBox="0 -10 360 560" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="d" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="',
                isDarkMode ? "#2e3847" : "#ffffff",
                '"/><stop offset="1" stop-color="',
                isDarkMode ? "#000000" : "#e6edf5",
                '"/></linearGradient><linearGradient id="e" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="',
                isDarkMode ? "#8091a8" : "#e6edf6",
                '"/><stop offset="1" stop-color="',
                isDarkMode ? "#2e3848" : "#bccbdd",
                '"/></linearGradient><linearGradient id="f" x1="0" x2="0" y1="1" y2="0"><stop offset="0" stop-color="',
                LibRenderer.generateColorHex(color, ColorType.SECONDARY),
                '"/><stop offset="1" stop-color="',
                LibRenderer.generateColorHex(color, ColorType.PRIMARY),
                '"/></linearGradient><linearGradient id="f2" x1="0" x2="0" y1="-.5" y2="1"><stop offset="0" stop-color="',
                LibRenderer.generateColorHex(color, ColorType.SECONDARY),
                '"/><stop offset="1" stop-color="',
                LibRenderer.generateColorHex(color, ColorType.PRIMARY),
                '"/></linearGradient><linearGradient id="h" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="',
                isDarkMode ? "#ffffff" : "#3f485f",
                '"/><stop offset=".5"  stop-color="',
                isDarkMode ? "#a7b8cf" : "#000000",
                '"/></linearGradient><radialGradient cx="1" cy="-.5" id="i" r="2"><stop offset="0" stop-color="#dce5f0"/><stop offset=".5" stop-color="#dce5f0" stop-opacity="0"/></radialGradient>'
            );
    }

    function generateSVG2(
        string memory partyName,
        Color color
    ) private view returns (string memory) {
        (uint256 duration, uint256 steps, uint256 delay, uint256 translateX) = LibRenderer
            .calcAnimationVariables(partyName);

        return
            string.concat(
                '<symbol id="a" viewBox="0 0 300.15 300"><path d="M6.07 0v300m-3-300v300M.07 0v300m9-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300" style="stroke-width:.15px;stroke:',
                LibRenderer.generateColorHex(color, ColorType.PRIMARY),
                ';fill:none;0"/></symbol><style>.z{animation:x ',
                duration.toString(),
                "s steps(",
                steps.toString(),
                ") infinite;}.y{animation-delay:",
                delay.toString(),
                "s}@keyframes x{to{transform:translateX(-",
                translateX.toString(),
                "px)}}.v{fill:",
                LibRenderer.generateColorHex(color, ColorType.DARK),
                ";font-family:pixeldroidConsoleRegular,Console;font-size:48px}.w{animation:W 1s steps(1, jump-end) infinite;}@keyframes W{50%{fill:",
                LibRenderer.generateColorHex(color, ColorType.LIGHT),
                "}}@font-face{font-family:pixeldroidConsoleRegular;src:url(",
                _font.getFont(),
                ");}</style></defs>"
            );
    }

    function generateSVG3(
        string memory partyName,
        Color color
    ) private pure returns (string memory) {
        return
            string.concat(
                '<rect height="539" rx="29.5" ry="29.5" style="fill:url(#d);stroke:url(#e)" width="359" x=".5" y=".5"/><rect rx="15.5" ry="15.5" style="stroke:url(#f);fill:',
                LibRenderer.generateColorHex(color, ColorType.PRIMARY),
                '" width="331" height="346" x="14.5" y="179.5"/><path d="M321 501H198v-27h123v27Zm9-282H30v27h300v-27Zm0 60H30v27h300v-27Zm0 30H30v27h300v-27Zm0 30H30v27h300v-27Zm0 30H30v27h300v-27Z" style="fill:',
                LibRenderer.generateColorHex(color, ColorType.LIGHT),
                ';"/><clipPath id="clip"><path d="M31 501H198v-27h123v27Zm9-282H30v27h300v-27Zm0"/></clipPath><g clip-path="url(#clip)"><g class="z"><text class="v" x="327" y="240">',
                partyName,
                '</text></g><g class="z y"><text class="v" x="327" y="240">',
                partyName,
                "</text></g></g>"
            );
    }

    function generateSVG4(
        uint256 latestProposalId,
        PartyGovernance.ProposalStatus[4] memory proposalStatuses
    ) private pure returns (string memory part) {
        // Render latest 4 proposals, or up to the latest proposal if there are
        // less than 4 proposals.
        for (uint256 i; i < (latestProposalId < 4 ? latestProposalId : 4); i++) {
            // Should produce something like this:
            //   '<text class="v" x="30" y="300">',
            //   latestProposalStatus1,
            //   '</text><text class="v" x="30" y="330">',
            //   latestProposalStatus2,
            //   '</text><text class="v" x="30" y="360">',
            //   latestProposalStatus3,
            //   '</text><text class="v" x="30" y="390">',
            //   latestProposalStatus4,
            //   '</text><text class="v" x="201" y="495">'
            part = string(
                abi.encodePacked(
                    part,
                    '<text class="v" x="30" y="',
                    (300 + (i * 30)).toString(),
                    '">',
                    generateProposalStatus(latestProposalId - i, proposalStatuses[i]),
                    "</text>"
                )
            );
        }

        part = string(abi.encodePacked(part, '<text class="v" x="201" y="495">'));
    }

    function generateSVG5(
        string memory votingPowerPercentage,
        Color color
    ) private pure returns (string memory) {
        return
            string.concat(
                votingPowerPercentage,
                '</text><text class="v" x="297" y="495">%</text>'
                '<use height="300" x="30" y="210" width="300.15" xlink:href="#a"/><use height="300" transform="rotate(-90 270 240)" width="300.15" xlink:href="#a"/><rect rx="3.5" ry="3.5" style="fill:none;stroke:',
                LibRenderer.generateColorHex(color, ColorType.DARK),
                ';stroke-width:3px" width="138" height="42" x="190.5" y="466.5"/><path fill="',
                LibRenderer.generateColorHex(color, ColorType.DARK)
            );
    }

    function generateSVG6(uint256 tokenId, Color color) private view returns (string memory) {
        return
            string.concat(
                '"',
                _storage.readFile(PARTY_CARD_DATA),
                '<path d="M285 164c-7.72 0-14-6.28-14-14s6.28-14 14-14h45c7.72 0 14 6.28 14 14s-6.28 14-14 14h-45Z" style="fill:none;stroke:',
                LibRenderer.generateColorHex(color, ColorType.PRIMARY),
                ';stroke-width:2px"/><path d="M307.5 68.19c-20.71 0-37.5 11.6-37.5 25.91s16.79 25.91 37.5 25.91S345 108.41 345 94.1s-16.79-25.91-37.5-25.91Zm12.84 41.66v-7.31l9.74-3.17-25.51-8.29 14.01 19.28a43.35 43.35 0 0 1-20.64.38v-8.33l9.74-3.17-25.51-8.29 14.06 19.35c-10.92-3-18.63-10.23-18.63-18.68 0-6.22 4.17-11.78 10.73-15.48l14.24 19.6V85.5l9.74-3.17-22.07-7.17a40.24 40.24 0 0 1 17.25-3.7c11.3 0 21.13 4.23 26.21 10.47l-23.17-7.53 15.76 21.7V85.86l8.32-2.7a14.9 14.9 0 0 1 2.77 8.48c0 8.04-6.97 14.98-17.05 18.22Z" style="fill:#A7B8CF"/><clipPath id="C"><path d="m98.43 483.54 1.39 5.02h-3.77l1.42-5.02.38-2.02h.17l.41 2.02ZM171 470v35a5 5 0 0 1-5 5H35a5 5 0 0 1-5-5v-35a5 5 0 0 1 5-5h131a5 5 0 0 1 5 5Zm-93.49 10.56c-1.71-1.4-3.1-2.06-5.5-2.06-3.82 0-7.39 3.19-7.39 8.64 0 6.35 3.17 9.36 7.34 9.36 2.4 0 4.12-.82 5.47-2.47v-4.06c-1.06 1.58-2.35 2.95-4.54 2.95-3.19 0-4.13-3.44-4.13-5.38 0-3.42 1.76-4.94 4.1-4.94 1.78 0 3.26.71 4.63 2.93v-4.97Zm11.28 11.81h-4.36v-13.54h-4.25v17.3h8.61v-3.77Zm17.27 3.77-5.06-17.3h-6.1l-5.09 17.3h4.27l1.06-4.06h5.59l1.06 4.06h4.27Zm6.21-17.3H108v17.3h4.27v-17.3Zm22.1 0h-6.39l-2.45 9.73-.28 3.64h-.24l-.32-3.67-2.46-9.71h-6.39v17.3h4.15v-10.44c0-.38-.1-2.16-.17-3.07h.24l3.07 13.51h3.96l3.07-13.51h.24l-.19 3.07v10.44h4.15v-17.3Z" /></clipPath><g clip-path="url(#C)"><rect class="w" x="30" y="465" width="142" height="50" fill="',
                hasUnclaimedDistribution(tokenId)
                    ? LibRenderer.generateColorHex(color, ColorType.DARK)
                    : LibRenderer.generateColorHex(color, ColorType.LIGHT),
                '"/></g><rect height="345" rx="15" ry="15" style="fill:url(#i)" width="330" x="15" y="180"/><text text-anchor="middle" style="font-family:ui-monospace,Cascadia Mono,Menlo,Monaco,Segoe UI Mono,Roboto Mono,Oxygen Mono,Ubuntu Monospace,Source Code Pro,Droid Sans Mono,Fira Mono,Courier,monospace;fill:',
                LibRenderer.generateColorHex(color, ColorType.PRIMARY),
                ';font-weight:500;" x="307.5" y="156">',
                // Always render token ID with 3 digits.
                LibRenderer.prependNumWithZeros(tokenId.toString(), 3),
                "</text></svg>"
            );
    }

    function generateVotingPowerPercentage(uint256 tokenId) private view returns (string memory) {
        Party party = Party(payable(address(this)));
        uint256 totalVotingPower = getTotalVotingPower();

        if (totalVotingPower == 0) {
            return "--";
        }

        uint256 intrinsicVotingPowerPercentage = (party.votingPowerByTokenId(tokenId) * 1e18) /
            totalVotingPower;

        if (intrinsicVotingPowerPercentage == 1e18) {
            return "100";
        } else if (intrinsicVotingPowerPercentage < 0.1e18) {
            return LibRenderer.formatAsDecimalString(intrinsicVotingPowerPercentage, 16, 3);
        } else {
            return LibRenderer.formatAsDecimalString(intrinsicVotingPowerPercentage, 16, 4);
        }
    }

    function generateProposalStatus(
        uint256 proposalId,
        PartyGovernance.ProposalStatus status
    ) private pure returns (string memory) {
        string memory statusMessage;
        if (status == PartyGovernance.ProposalStatus.Voting) {
            statusMessage = "Voting now";
        } else if (status == PartyGovernance.ProposalStatus.Passed) {
            statusMessage = "Passing";
        } else if (status == PartyGovernance.ProposalStatus.Ready) {
            statusMessage = "Executable";
        } else if (status == PartyGovernance.ProposalStatus.InProgress) {
            statusMessage = "In progress";
        } else if (status == PartyGovernance.ProposalStatus.Complete) {
            statusMessage = "Complete";
        } else if (status == PartyGovernance.ProposalStatus.Defeated) {
            statusMessage = "Defeated";
        } else if (status == PartyGovernance.ProposalStatus.Cancelled) {
            statusMessage = "Cancelled";
        } else {
            return "";
        }

        return string.concat("#", proposalId.toString(), " - ", statusMessage);
    }

    function getCustomMetadata(uint256 tokenId) private view returns (Metadata memory metadata) {
        MetadataRegistry registry = MetadataRegistry(
            _GLOBALS.getAddress(LibGlobals.GLOBAL_METADATA_REGISTRY)
        );

        bytes memory encodedMetadata = registry.getMetadata(address(this), tokenId);
        return encodedMetadata.length != 0 ? abi.decode(encodedMetadata, (Metadata)) : metadata;
    }

    function getLatestProposalStatuses()
        private
        view
        returns (PartyGovernance.ProposalStatus[4] memory proposalStatuses)
    {
        uint256 latestProposalId = PartyGovernance(address(this)).lastProposalId();
        uint256 numOfProposalsToDisplay = latestProposalId < 4 ? latestProposalId : 4;
        for (uint256 i; i < numOfProposalsToDisplay; ++i) {
            uint256 proposalId = latestProposalId - i;
            PartyGovernance.ProposalStatus proposalStatus;

            // Get the status of the proposal, regardless of the version of the
            // Party contract.
            (bool s, bytes memory r) = address(this).staticcall(
                abi.encodeCall(PartyGovernance.getProposalStateInfo, (proposalId))
            );
            if (!s) {
                r.rawRevert();
            }
            assembly {
                proposalStatus := mload(add(r, 0x20))
            }

            proposalStatuses[i] = proposalStatus;
        }
    }

    function getCrowdfundType() private view returns (CrowdfundType crowdfundType) {
        Party party = Party(payable(address(this)));
        uint256 numOfTokensToCheck = 5;
        uint256 tokenCount = party.tokenCount();
        if (tokenCount < numOfTokensToCheck) {
            // Default to flexible membership.
            return CrowdfundType.Flexible;
        } else {
            // Check the voting power of several tokens. If they are all the
            // same, assume it is a fixed membership.

            uint256 lastTokenId = tokenCount / numOfTokensToCheck;
            for (uint256 i = 2; i <= numOfTokensToCheck; ++i) {
                uint256 tokenId = i * (tokenCount / numOfTokensToCheck);

                if (
                    party.votingPowerByTokenId(tokenId) != party.votingPowerByTokenId(lastTokenId)
                ) {
                    return CrowdfundType.Flexible;
                }

                lastTokenId = tokenId;
            }

            return CrowdfundType.Fixed;
        }
    }

    function getTotalVotingPower() private view returns (uint96 totalVotingPower) {
        // Get the total voting power of the Party regardless of
        // the version of the Party contract.
        (bool s, bytes memory r) = address(this).staticcall(
            abi.encodeCall(PartyGovernance.getGovernanceValues, ())
        );
        if (!s) {
            r.rawRevert();
        }
        assembly {
            totalVotingPower := mload(add(r, 0x80))
        }
    }

    function hasUnclaimedDistribution(uint256 tokenId) private view returns (bool) {
        if (address(this) == IMPL) return false;

        // There will only be one distributor if old token distributor is not set
        TokenDistributor[] memory distributors = new TokenDistributor[](1);
        if (OLD_TOKEN_DISTRIBUTOR != address(0)) {
            distributors = new TokenDistributor[](2);
            distributors[1] = TokenDistributor(OLD_TOKEN_DISTRIBUTOR);
        }
        distributors[0] = TokenDistributor(
            _GLOBALS.getAddress(LibGlobals.GLOBAL_TOKEN_DISTRIBUTOR)
        );

        Party party = Party(payable(address(this)));
        for (uint256 i; i < distributors.length; ++i) {
            TokenDistributor distributor = distributors[i];
            uint256 lastDistributionId = distributor.lastDistributionIdPerParty(party);

            for (
                uint256 distributionId = 1;
                distributionId <= lastDistributionId;
                ++distributionId
            ) {
                if (!distributor.hasPartyTokenIdClaimed(party, tokenId, distributionId)) {
                    return true;
                }
            }
        }

        return false;
    }

    function hasPartyStarted() private view returns (bool) {
        return getTotalVotingPower() != 0;
    }
}

File 8 of 43 : IERC721.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

// Minimal ERC721 interface.
interface IERC721 {
    event Transfer(address indexed owner, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed operator, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function transferFrom(address from, address to, uint256 tokenId) external;

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    function approve(address operator, uint256 tokenId) external;

    function setApprovalForAll(address operator, bool isApproved) external;

    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function getApproved(uint256 tokenId) external view returns (address);

    function isApprovedForAll(address owner, address operator) external view returns (bool);

    function ownerOf(uint256 tokenId) external view returns (address);

    function balanceOf(address owner) external view returns (uint256);
}

File 9 of 43 : PartyGovernanceNFT.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/LibSafeCast.sol";
import "../utils/LibAddress.sol";
import "openzeppelin/contracts/interfaces/IERC2981.sol";
import "../globals/IGlobals.sol";
import "../tokens/IERC721.sol";
import "../vendor/solmate/ERC721.sol";
import "./PartyGovernance.sol";
import "../renderers/RendererStorage.sol";

/// @notice ERC721 functionality built on top of `PartyGovernance`.
contract PartyGovernanceNFT is PartyGovernance, ERC721, IERC2981 {
    using LibSafeCast for uint256;
    using LibSafeCast for uint96;
    using LibERC20Compat for IERC20;
    using LibAddress for address payable;

    error OnlyAuthorityError();
    error OnlySelfError();
    error UnauthorizedToBurnError();
    error FixedRageQuitTimestampError(uint40 rageQuitTimestamp);
    error CannotRageQuitError(uint40 rageQuitTimestamp);
    error CannotDisableRageQuitAfterInitializationError();
    error InvalidTokenOrderError();
    error BelowMinWithdrawAmountError(uint256 amount, uint256 minAmount);
    error NothingToBurnError();

    event AuthorityAdded(address indexed authority);
    event AuthorityRemoved(address indexed authority);
    event RageQuitSet(uint40 oldRageQuitTimestamp, uint40 newRageQuitTimestamp);
    event Burn(address caller, uint256 tokenId, uint256 votingPower);
    event RageQuit(address caller, uint256[] tokenIds, IERC20[] withdrawTokens, address receiver);
    event PartyCardIntrinsicVotingPowerSet(uint256 indexed tokenId, uint256 intrinsicVotingPower);

    uint40 private constant ENABLE_RAGEQUIT_PERMANENTLY = 0x6b5b567bfe; // uint40(uint256(keccak256("ENABLE_RAGEQUIT_PERMANENTLY")))
    uint40 private constant DISABLE_RAGEQUIT_PERMANENTLY = 0xab2cb21860; // uint40(uint256(keccak256("DISABLE_RAGEQUIT_PERMANENTLY")))

    // Token address used to indicate ETH.
    address private constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and its address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice The number of tokens that have been minted.
    uint96 public tokenCount;
    /// @notice The total minted voting power.
    ///         Capped to `_governanceValues.totalVotingPower` unless minting
    ///         party cards for initial crowdfund.
    uint96 public mintedVotingPower;
    /// @notice The timestamp until which ragequit is enabled. Can be set to the
    ///         `ENABLE_RAGEQUIT_PERMANENTLY`/`DISABLE_RAGEQUIT_PERMANENTLY`
    ///         values to enable/disable ragequit permanently.
    ///         `DISABLE_RAGEQUIT_PERMANENTLY` can only be set during
    ///         initialization.
    uint40 public rageQuitTimestamp;
    /// @notice The voting power of `tokenId`.
    mapping(uint256 => uint256) public votingPowerByTokenId;
    /// @notice Address with authority to mint cards and update voting power for the party.
    mapping(address => bool) public isAuthority;

    modifier onlyAuthority() {
        if (!isAuthority[msg.sender]) {
            revert OnlyAuthorityError();
        }
        _;
    }

    modifier onlySelf() {
        if (msg.sender != address(this)) {
            revert OnlySelfError();
        }
        _;
    }

    // Set the `Globals` contract. The name or symbol of ERC721 does not matter;
    // it will be set in `_initialize()`.
    constructor(IGlobals globals) payable PartyGovernance(globals) ERC721("", "") {
        _GLOBALS = globals;
    }

    // Initialize storage for proxy contracts.
    function _initialize(
        string memory name_,
        string memory symbol_,
        uint256 customizationPresetId,
        PartyGovernance.GovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds,
        address[] memory authorities,
        uint40 rageQuitTimestamp_
    ) internal {
        PartyGovernance._initialize(
            governanceOpts,
            proposalEngineOpts,
            preciousTokens,
            preciousTokenIds
        );
        name = name_;
        symbol = symbol_;
        rageQuitTimestamp = rageQuitTimestamp_;
        unchecked {
            for (uint256 i; i < authorities.length; ++i) {
                isAuthority[authorities[i]] = true;
            }
        }
        if (customizationPresetId != 0) {
            RendererStorage(_GLOBALS.getAddress(LibGlobals.GLOBAL_RENDERER_STORAGE))
                .useCustomizationPreset(customizationPresetId);
        }
    }

    /// @inheritdoc ERC721
    function ownerOf(uint256 tokenId) public view override returns (address owner) {
        return ERC721.ownerOf(tokenId);
    }

    /// @inheritdoc EIP165
    function supportsInterface(
        bytes4 interfaceId
    ) public pure override(PartyGovernance, ERC721, IERC165) returns (bool) {
        return
            PartyGovernance.supportsInterface(interfaceId) ||
            ERC721.supportsInterface(interfaceId) ||
            interfaceId == type(IERC2981).interfaceId;
    }

    /// @inheritdoc ERC721
    function tokenURI(uint256) public view override returns (string memory) {
        _delegateToRenderer();
        return ""; // Just to make the compiler happy.
    }

    /// @notice Returns a URI for the storefront-level metadata for your contract.
    function contractURI() external view returns (string memory) {
        _delegateToRenderer();
        return ""; // Just to make the compiler happy.
    }

    /// @notice Called with the sale price to determine how much royalty
    //          is owed and to whom.
    function royaltyInfo(uint256, uint256) external view returns (address, uint256) {
        _delegateToRenderer();
        return (address(0), 0); // Just to make the compiler happy.
    }

    /// @notice Return the distribution share amount of a token. Included as an alias
    ///         for `votePowerByTokenId` for backwards compatibility with old
    ///         `TokenDistributor` implementations.
    /// @param tokenId The token ID to query.
    /// @return share The distribution shares of `tokenId`.
    function getDistributionShareOf(uint256 tokenId) public view returns (uint256) {
        return votingPowerByTokenId[tokenId];
    }

    /// @notice Return the voting power share of a token. Denominated
    ///         fractions of 1e18. I.e., 1e18 = 100%.
    /// @param tokenId The token ID to query.
    /// @return share The voting power percentage of `tokenId`.
    function getVotingPowerShareOf(uint256 tokenId) public view returns (uint256) {
        uint256 totalVotingPower = _governanceValues.totalVotingPower;
        return
            totalVotingPower == 0 ? 0 : (votingPowerByTokenId[tokenId] * 1e18) / totalVotingPower;
    }

    /// @notice Mint a governance NFT for `owner` with `votingPower` and
    ///         immediately delegate voting power to `delegate.` Only callable
    ///         by an authority.
    /// @param owner The owner of the NFT.
    /// @param votingPower The voting power of the NFT.
    /// @param delegate The address to delegate voting power to.
    function mint(
        address owner,
        uint256 votingPower,
        address delegate
    ) external onlyAuthority returns (uint256 tokenId) {
        uint96 mintedVotingPower_ = mintedVotingPower;
        uint96 totalVotingPower = _governanceValues.totalVotingPower;

        // Cap voting power to remaining unminted voting power supply.
        uint96 votingPower_ = votingPower.safeCastUint256ToUint96();
        // Allow minting past total voting power if minting party cards for
        // initial crowdfund when there is no total voting power.
        if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower_) {
            unchecked {
                votingPower_ = totalVotingPower - mintedVotingPower_;
            }
        }

        // Update state.
        unchecked {
            tokenId = ++tokenCount;
        }
        mintedVotingPower += votingPower_;
        votingPowerByTokenId[tokenId] = votingPower_;

        emit PartyCardIntrinsicVotingPowerSet(tokenId, votingPower);

        // Use delegate from party over the one set during crowdfund.
        address delegate_ = delegationsByVoter[owner];
        if (delegate_ != address(0)) {
            delegate = delegate_;
        }

        _adjustVotingPower(owner, votingPower_.safeCastUint96ToInt192(), delegate);
        _safeMint(owner, tokenId);
    }

    /// @notice Add voting power to an existing NFT. Only callable by an
    ///         authority.
    /// @param tokenId The ID of the NFT to add voting power to.
    /// @param votingPower The amount of voting power to add.
    function addVotingPower(uint256 tokenId, uint256 votingPower) external onlyAuthority {
        uint96 mintedVotingPower_ = mintedVotingPower;
        uint96 totalVotingPower = _governanceValues.totalVotingPower;
        // Cap voting power to remaining unminted voting power supply.
        uint96 votingPower_ = votingPower.safeCastUint256ToUint96();
        // Allow minting past total voting power if minting party cards for
        // initial crowdfund when there is no total voting power.
        if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower_) {
            unchecked {
                votingPower_ = totalVotingPower - mintedVotingPower_;
            }
        }

        // Update state.
        mintedVotingPower += votingPower_;
        uint256 newIntrinsicVotingPower = votingPowerByTokenId[tokenId] + votingPower_;
        votingPowerByTokenId[tokenId] = newIntrinsicVotingPower;

        emit PartyCardIntrinsicVotingPowerSet(tokenId, newIntrinsicVotingPower);

        _adjustVotingPower(ownerOf(tokenId), votingPower_.safeCastUint96ToInt192(), address(0));
    }

    /// @notice Update the total voting power of the party. Only callable by
    ///         an authority.
    /// @param newVotingPower The new total voting power to add.
    function increaseTotalVotingPower(uint96 newVotingPower) external onlyAuthority {
        _governanceValues.totalVotingPower += newVotingPower;
    }

    /// @notice Burn governance NFTs and remove their voting power. Can only
    ///         be called by an authority before the party has started.
    /// @param tokenIds The IDs of the governance NFTs to burn.
    function burn(uint256[] memory tokenIds) public onlyAuthority {
        // Authority needs to be able to burn cards during the initial
        // crowdfund to process refunds but not after the party has started.
        if (_governanceValues.totalVotingPower != 0) revert UnauthorizedToBurnError();

        // Used to update voting power state of party at the end.
        _burnAndUpdateVotingPower(tokenIds, false);
    }

    function _burnAndUpdateVotingPower(
        uint256[] memory tokenIds,
        bool checkIfAuthorizedToBurn
    ) private returns (uint96 totalVotingPowerBurned) {
        for (uint256 i; i < tokenIds.length; ++i) {
            uint256 tokenId = tokenIds[i];

            // Check if caller is authorized to burn the token.
            address owner = ownerOf(tokenId);
            if (checkIfAuthorizedToBurn) {
                if (
                    msg.sender != owner &&
                    getApproved[tokenId] != msg.sender &&
                    !isApprovedForAll[owner][msg.sender]
                ) {
                    revert UnauthorizedToBurnError();
                }
            }

            // Must be retrieved before updating voting power for token to be burned.
            uint96 votingPower = votingPowerByTokenId[tokenId].safeCastUint256ToUint96();

            totalVotingPowerBurned += votingPower;

            // Update voting power for token to be burned.
            delete votingPowerByTokenId[tokenId];
            emit PartyCardIntrinsicVotingPowerSet(tokenId, 0);
            _adjustVotingPower(owner, -votingPower.safeCastUint96ToInt192(), address(0));

            // Burn token.
            _burn(tokenId);

            emit Burn(msg.sender, tokenId, votingPower);
        }

        // Update minted voting power.
        mintedVotingPower -= totalVotingPowerBurned;
    }

    /// @notice Burn governance NFT and remove its voting power. Can only be
    ///         called by an authority before the party has started.
    /// @param tokenId The ID of the governance NFTs to burn.
    function burn(uint256 tokenId) external {
        uint256[] memory tokenIds = new uint256[](1);
        tokenIds[0] = tokenId;
        burn(tokenIds);
    }

    /// @notice Set the timestamp until which ragequit is enabled.
    /// @param newRageQuitTimestamp The new ragequit timestamp.
    function setRageQuit(uint40 newRageQuitTimestamp) external onlyHost {
        // Prevent disabling ragequit after initialization.
        if (newRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY) {
            revert CannotDisableRageQuitAfterInitializationError();
        }

        uint40 oldRageQuitTimestamp = rageQuitTimestamp;

        // Prevent setting timestamp if it is permanently enabled/disabled.
        if (
            oldRageQuitTimestamp == ENABLE_RAGEQUIT_PERMANENTLY ||
            oldRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY
        ) {
            revert FixedRageQuitTimestampError(oldRageQuitTimestamp);
        }

        emit RageQuitSet(oldRageQuitTimestamp, rageQuitTimestamp = newRageQuitTimestamp);
    }

    /// @notice Burn a governance NFT and withdraw a fair share of fungible tokens from the party.
    /// @param tokenIds The IDs of the governance NFTs to burn.
    /// @param withdrawTokens The fungible tokens to withdraw. Specify the
    ///                       `ETH_ADDRESS` value to withdraw ETH.
    /// @param minWithdrawAmounts The minimum amount of to withdraw for each token.
    /// @param receiver The address to receive the withdrawn tokens.
    function rageQuit(
        uint256[] calldata tokenIds,
        IERC20[] calldata withdrawTokens,
        uint256[] calldata minWithdrawAmounts,
        address receiver
    ) external {
        if (tokenIds.length == 0) revert NothingToBurnError();

        // Check if ragequit is allowed.
        uint40 currentRageQuitTimestamp = rageQuitTimestamp;
        if (currentRageQuitTimestamp != ENABLE_RAGEQUIT_PERMANENTLY) {
            if (
                currentRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY ||
                currentRageQuitTimestamp < block.timestamp
            ) {
                revert CannotRageQuitError(currentRageQuitTimestamp);
            }
        }

        // Used as a reentrancy guard. Will be updated back after ragequit.
        rageQuitTimestamp = DISABLE_RAGEQUIT_PERMANENTLY;

        // Update last rage quit timestamp.
        lastRageQuitTimestamp = uint40(block.timestamp);

        // Sum up total amount of each token to withdraw.
        uint256[] memory withdrawAmounts = new uint256[](withdrawTokens.length);
        {
            IERC20 prevToken;
            for (uint256 i; i < withdrawTokens.length; ++i) {
                IERC20 token = withdrawTokens[i];

                // Check if order of tokens to transfer is valid.
                // Prevent null and duplicate transfers.
                if (prevToken >= token) revert InvalidTokenOrderError();

                prevToken = token;

                // Check token's balance.
                uint256 balance = address(token) == ETH_ADDRESS
                    ? address(this).balance
                    : token.balanceOf(address(this));

                // Add fair share of tokens from the party to total.
                for (uint256 j; j < tokenIds.length; ++j) {
                    // Must be retrieved before burning the token.
                    uint256 shareOfVotingPower = getVotingPowerShareOf(tokenIds[j]);

                    withdrawAmounts[i] += (balance * shareOfVotingPower) / 1e18;
                }
            }
        }
        {
            // Burn caller's party cards. This will revert if caller is not the
            // the owner or approved for any of the card they are attempting to
            // burn or if there are duplicate token IDs.
            uint96 totalVotingPowerBurned = _burnAndUpdateVotingPower(tokenIds, true);

            // Update total voting power of party.
            _governanceValues.totalVotingPower -= totalVotingPowerBurned;
        }
        {
            uint16 feeBps_ = feeBps;
            for (uint256 i; i < withdrawTokens.length; ++i) {
                IERC20 token = withdrawTokens[i];
                uint256 amount = withdrawAmounts[i];

                // Take fee from amount.
                uint256 fee = (amount * feeBps_) / 1e4;

                if (fee > 0) {
                    amount -= fee;

                    // Transfer fee to fee recipient.
                    if (address(token) == ETH_ADDRESS) {
                        payable(feeRecipient).transferEth(fee);
                    } else {
                        token.compatTransfer(feeRecipient, fee);
                    }
                }

                if (amount > 0) {
                    uint256 minAmount = minWithdrawAmounts[i];

                    // Check amount is at least minimum.
                    if (amount < minAmount) {
                        revert BelowMinWithdrawAmountError(amount, minAmount);
                    }

                    // Transfer token from party to recipient.
                    if (address(token) == ETH_ADDRESS) {
                        payable(receiver).transferEth(amount);
                    } else {
                        token.compatTransfer(receiver, amount);
                    }
                }
            }
        }

        // Update ragequit timestamp back to before.
        rageQuitTimestamp = currentRageQuitTimestamp;

        emit RageQuit(msg.sender, tokenIds, withdrawTokens, receiver);
    }

    /// @inheritdoc ERC721
    function transferFrom(address owner, address to, uint256 tokenId) public override {
        // Transfer voting along with token.
        _transferVotingPower(owner, to, votingPowerByTokenId[tokenId]);
        super.transferFrom(owner, to, tokenId);
    }

    /// @inheritdoc ERC721
    function safeTransferFrom(address owner, address to, uint256 tokenId) public override {
        // super.safeTransferFrom() will call transferFrom() first which will
        // transfer voting power.
        super.safeTransferFrom(owner, to, tokenId);
    }

    /// @inheritdoc ERC721
    function safeTransferFrom(
        address owner,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) public override {
        // super.safeTransferFrom() will call transferFrom() first which will
        // transfer voting power.
        super.safeTransferFrom(owner, to, tokenId, data);
    }

    /// @notice Add a new authority.
    /// @dev Used in `AddAuthorityProposal`. Only the party itself can add
    ///      authorities to prevent it from being used anywhere else.
    function addAuthority(address authority) external onlySelf {
        isAuthority[authority] = true;

        emit AuthorityAdded(authority);
    }

    /// @notice Relinquish the authority role.
    function abdicateAuthority() external onlyAuthority {
        delete isAuthority[msg.sender];

        emit AuthorityRemoved(msg.sender);
    }

    function _delegateToRenderer() private view {
        _readOnlyDelegateCall(
            // Instance of IERC721Renderer.
            _GLOBALS.getAddress(LibGlobals.GLOBAL_GOVERNANCE_NFT_RENDER_IMPL),
            msg.data
        );
        assert(false); // Will not be reached.
    }
}

File 10 of 43 : PartyGovernance.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../distribution/ITokenDistributor.sol";
import "../utils/ReadOnlyDelegateCall.sol";
import "../tokens/IERC721.sol";
import "../tokens/IERC20.sol";
import "../tokens/ERC721Receiver.sol";
import "../tokens/ERC1155Receiver.sol";
import "../utils/LibERC20Compat.sol";
import "../utils/LibRawResult.sol";
import "../utils/LibSafeCast.sol";
import "../utils/IERC4906.sol";
import "../globals/IGlobals.sol";
import "../globals/LibGlobals.sol";
import "../proposals/IProposalExecutionEngine.sol";
import "../proposals/LibProposal.sol";
import "../proposals/ProposalStorage.sol";

import "./Party.sol";

/// @notice Base contract for a Party encapsulating all governance functionality.
abstract contract PartyGovernance is
    ERC721Receiver,
    ERC1155Receiver,
    ProposalStorage,
    Implementation,
    IERC4906,
    ReadOnlyDelegateCall
{
    using LibERC20Compat for IERC20;
    using LibRawResult for bytes;
    using LibSafeCast for uint256;
    using LibSafeCast for int192;
    using LibSafeCast for uint96;

    // States a proposal can be in.
    enum ProposalStatus {
        // The proposal does not exist.
        Invalid,
        // The proposal has been proposed (via `propose()`), has not been vetoed
        // by a party host, and is within the voting window. Members can vote on
        // the proposal and party hosts can veto the proposal.
        Voting,
        // The proposal has either exceeded its voting window without reaching
        // `passThresholdBps` of votes or was vetoed by a party host.
        Defeated,
        // The proposal reached at least `passThresholdBps` of votes but is still
        // waiting for `executionDelay` to pass before it can be executed. Members
        // can continue to vote on the proposal and party hosts can veto at this time.
        Passed,
        // Same as `Passed` but now `executionDelay` has been satisfied. Any member
        // may execute the proposal via `execute()`, unless `maxExecutableTime`
        // has arrived.
        Ready,
        // The proposal has been executed at least once but has further steps to
        // complete so it needs to be executed again. No other proposals may be
        // executed while a proposal is in the `InProgress` state. No voting or
        // vetoing of the proposal is allowed, however it may be forcibly cancelled
        // via `cancel()` if the `cancelDelay` has passed since being first executed.
        InProgress,
        // The proposal was executed and completed all its steps. No voting or
        // vetoing can occur and it cannot be cancelled nor executed again.
        Complete,
        // The proposal was executed at least once but did not complete before
        // `cancelDelay` seconds passed since the first execute and was forcibly cancelled.
        Cancelled
    }

    struct GovernanceOpts {
        // Address of initial party hosts.
        address[] hosts;
        // How long people can vote on a proposal.
        uint40 voteDuration;
        // How long to wait after a proposal passes before it can be
        // executed.
        uint40 executionDelay;
        // Minimum ratio of accept votes to consider a proposal passed,
        // in bps, where 10,000 == 100%.
        uint16 passThresholdBps;
        // Total voting power of governance NFTs.
        uint96 totalVotingPower;
        // Fee bps for distributions.
        uint16 feeBps;
        // Fee recipeint for distributions.
        address payable feeRecipient;
    }

    // Subset of `GovernanceOpts` that are commonly read together for
    // efficiency.
    struct GovernanceValues {
        uint40 voteDuration;
        uint40 executionDelay;
        uint16 passThresholdBps;
        uint96 totalVotingPower;
    }

    // A snapshot of voting power for a member.
    struct VotingPowerSnapshot {
        // The timestamp when the snapshot was taken.
        uint40 timestamp;
        // Voting power that was delegated to this user by others.
        uint96 delegatedVotingPower;
        // The intrinsic (not delegated from someone else) voting power of this user.
        uint96 intrinsicVotingPower;
        // Whether the user was delegated to another at this snapshot.
        bool isDelegated;
    }

    // Proposal details chosen by proposer.
    struct Proposal {
        // Time beyond which the proposal can no longer be executed.
        // If the proposal has already been executed, and is still InProgress,
        // this value is ignored.
        uint40 maxExecutableTime;
        // The minimum seconds this proposal can remain in the InProgress status
        // before it can be cancelled.
        uint40 cancelDelay;
        // Encoded proposal data. The first 4 bytes are the proposal type, followed
        // by encoded proposal args specific to the proposal type. See
        // ProposalExecutionEngine for details.
        bytes proposalData;
    }

    // Accounting and state tracking values for a proposal.
    struct ProposalStateValues {
        // When the proposal was proposed.
        uint40 proposedTime;
        // When the proposal passed the vote.
        uint40 passedTime;
        // When the proposal was first executed.
        uint40 executedTime;
        // When the proposal completed.
        uint40 completedTime;
        // Number of accept votes.
        uint96 votes; // -1 == vetoed
        // Number of total voting power at time proposal created.
        uint96 totalVotingPower;
    }

    // Storage states for a proposal.
    struct ProposalState {
        // Accounting and state tracking values.
        ProposalStateValues values;
        // Hash of the proposal.
        bytes32 hash;
        // Whether a member has voted for (accepted) this proposal already.
        mapping(address => bool) hasVoted;
    }

    event Proposed(uint256 proposalId, address proposer, Proposal proposal);
    event ProposalAccepted(uint256 proposalId, address voter, uint256 weight);
    event EmergencyExecute(address target, bytes data, uint256 amountEth);

    event ProposalPassed(uint256 indexed proposalId);
    event ProposalVetoed(uint256 indexed proposalId, address host);
    event ProposalExecuted(uint256 indexed proposalId, address executor, bytes nextProgressData);
    event ProposalCancelled(uint256 indexed proposalId);
    event DistributionCreated(
        ITokenDistributor.TokenType tokenType,
        address token,
        uint256 tokenId
    );
    event PartyDelegateUpdated(address indexed owner, address indexed delegate);
    event HostStatusTransferred(address oldHost, address newHost);
    event EmergencyExecuteDisabled();
    event PartyVotingSnapshotCreated(
        address indexed voter,
        uint40 timestamp,
        uint96 delegatedVotingPower,
        uint96 intrinsicVotingPower,
        bool isDelegated
    );

    error MismatchedPreciousListLengths();
    error BadProposalStatusError(ProposalStatus status);
    error BadProposalHashError(bytes32 proposalHash, bytes32 actualHash);
    error ExecutionTimeExceededError(uint40 maxExecutableTime, uint40 timestamp);
    error OnlyPartyHostError();
    error OnlyActiveMemberError();
    error OnlyTokenDistributorOrSelfError();
    error InvalidDelegateError();
    error BadPreciousListError();
    error OnlyPartyDaoError(address notDao, address partyDao);
    error OnlyPartyDaoOrHostError(address notDao, address partyDao);
    error OnlyWhenEmergencyActionsAllowedError();
    error OnlyWhenEnabledError();
    error AlreadyVotedError(address voter);
    error InvalidNewHostError();
    error ProposalCannotBeCancelledYetError(uint40 currentTime, uint40 cancelTime);
    error InvalidBpsError(uint16 bps);
    error DistributionsRequireVoteError();
    error PartyNotStartedError();
    error CannotRageQuitAndAcceptError();

    uint256 private constant UINT40_HIGH_BIT = 1 << 39;
    uint96 private constant VETO_VALUE = type(uint96).max;

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and it’s address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice Whether the DAO has emergency powers for this party.
    bool public emergencyExecuteDisabled;
    /// @notice Distribution fee bps.
    uint16 public feeBps;
    /// @notice Distribution fee recipient.
    address payable public feeRecipient;
    /// @notice The timestamp of the last time `rageQuit()` was called.
    uint40 public lastRageQuitTimestamp;
    /// @notice The hash of the list of precious NFTs guarded by the party.
    bytes32 public preciousListHash;
    /// @notice The last proposal ID that was used. 0 means no proposals have been made.
    uint256 public lastProposalId;
    /// @notice Whether an address is a party host.
    mapping(address => bool) public isHost;
    /// @notice The last person a voter delegated its voting power to.
    mapping(address => address) public delegationsByVoter;
    // Governance parameters for this party.
    GovernanceValues internal _governanceValues;
    // ProposalState by proposal ID.
    mapping(uint256 => ProposalState) private _proposalStateByProposalId;
    // Snapshots of voting power per user, each sorted by increasing time.
    mapping(address => VotingPowerSnapshot[]) private _votingPowerSnapshotsByVoter;

    modifier onlyHost() {
        if (!isHost[msg.sender]) {
            revert OnlyPartyHostError();
        }
        _;
    }

    // Caller must have voting power at the current time.
    modifier onlyActiveMember() {
        {
            VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender);
            // Must have either delegated voting power or intrinsic voting power.
            if (snap.intrinsicVotingPower == 0 && snap.delegatedVotingPower == 0) {
                revert OnlyActiveMemberError();
            }
        }
        _;
    }

    // Only the party DAO multisig can call.
    modifier onlyPartyDao() {
        {
            address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
            if (msg.sender != partyDao) {
                revert OnlyPartyDaoError(msg.sender, partyDao);
            }
        }
        _;
    }

    // Only the party DAO multisig or a party host can call.
    modifier onlyPartyDaoOrHost() {
        address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
        if (msg.sender != partyDao && !isHost[msg.sender]) {
            revert OnlyPartyDaoOrHostError(msg.sender, partyDao);
        }
        _;
    }

    // Only if `emergencyExecuteDisabled` is not true.
    modifier onlyWhenEmergencyExecuteAllowed() {
        if (emergencyExecuteDisabled) {
            revert OnlyWhenEmergencyActionsAllowedError();
        }
        _;
    }

    modifier onlyWhenNotGloballyDisabled() {
        if (_GLOBALS.getBool(LibGlobals.GLOBAL_DISABLE_PARTY_ACTIONS)) {
            revert OnlyWhenEnabledError();
        }
        _;
    }

    // Set the `Globals` contract.
    constructor(IGlobals globals) {
        _GLOBALS = globals;
    }

    // Initialize storage for proxy contracts and initialize the proposal execution engine.
    function _initialize(
        GovernanceOpts memory govOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) internal virtual {
        // Check BPS are valid.
        if (govOpts.feeBps > 1e4) {
            revert InvalidBpsError(govOpts.feeBps);
        }
        if (govOpts.passThresholdBps > 1e4) {
            revert InvalidBpsError(govOpts.passThresholdBps);
        }
        // Initialize the proposal execution engine.
        _initProposalImpl(
            IProposalExecutionEngine(_GLOBALS.getAddress(LibGlobals.GLOBAL_PROPOSAL_ENGINE_IMPL)),
            abi.encode(proposalEngineOpts)
        );
        // Set the governance parameters.
        _governanceValues = GovernanceValues({
            voteDuration: govOpts.voteDuration,
            executionDelay: govOpts.executionDelay,
            passThresholdBps: govOpts.passThresholdBps,
            totalVotingPower: govOpts.totalVotingPower
        });
        // Set fees.
        feeBps = govOpts.feeBps;
        feeRecipient = govOpts.feeRecipient;
        // Set the precious list.
        _setPreciousList(preciousTokens, preciousTokenIds);
        // Set the party hosts.
        for (uint256 i = 0; i < govOpts.hosts.length; ++i) {
            isHost[govOpts.hosts[i]] = true;
        }
    }

    /// @dev Forward all unknown read-only calls to the proposal execution engine.
    ///      Initial use case is to facilitate eip-1271 signatures.
    fallback() external {
        _readOnlyDelegateCall(address(_getSharedProposalStorage().engineImpl), msg.data);
    }

    /// @inheritdoc EIP165
    /// @dev Combined logic for `ERC721Receiver` and `ERC1155Receiver`.
    function supportsInterface(
        bytes4 interfaceId
    ) public pure virtual override(ERC721Receiver, ERC1155Receiver) returns (bool) {
        return
            ERC721Receiver.supportsInterface(interfaceId) ||
            ERC1155Receiver.supportsInterface(interfaceId) ||
            // ERC4906 interface ID
            interfaceId == 0x49064906;
    }

    /// @notice Get the current `ProposalExecutionEngine` instance.
    function getProposalExecutionEngine() external view returns (IProposalExecutionEngine) {
        return _getSharedProposalStorage().engineImpl;
    }

    /// @notice Get the current `ProposalEngineOpts` options.
    function getProposalEngineOpts() external view returns (ProposalEngineOpts memory) {
        return _getSharedProposalStorage().opts;
    }

    /// @notice Get the total voting power of `voter` at a `timestamp`.
    /// @param voter The address of the voter.
    /// @param timestamp The timestamp to get the voting power at.
    /// @return votingPower The total voting power of `voter` at `timestamp`.
    function getVotingPowerAt(
        address voter,
        uint40 timestamp
    ) external view returns (uint96 votingPower) {
        return getVotingPowerAt(voter, timestamp, type(uint256).max);
    }

    /// @notice Get the total voting power of `voter` at a snapshot `snapIndex`, with checks to
    ///         make sure it is the latest voting snapshot =< `timestamp`.
    /// @param voter The address of the voter.
    /// @param timestamp The timestamp to get the voting power at.
    /// @param snapIndex The index of the snapshot to get the voting power at.
    /// @return votingPower The total voting power of `voter` at `timestamp`.
    function getVotingPowerAt(
        address voter,
        uint40 timestamp,
        uint256 snapIndex
    ) public view returns (uint96 votingPower) {
        VotingPowerSnapshot memory snap = _getVotingPowerSnapshotAt(voter, timestamp, snapIndex);
        return (snap.isDelegated ? 0 : snap.intrinsicVotingPower) + snap.delegatedVotingPower;
    }

    /// @notice Get the state of a proposal.
    /// @param proposalId The ID of the proposal.
    /// @return status The status of the proposal.
    /// @return values The state of the proposal.
    function getProposalStateInfo(
        uint256 proposalId
    ) external view returns (ProposalStatus status, ProposalStateValues memory values) {
        values = _proposalStateByProposalId[proposalId].values;
        status = _getProposalStatus(values);
    }

    /// @notice Retrieve fixed governance parameters.
    /// @return gv The governance parameters of this party.
    function getGovernanceValues() external view returns (GovernanceValues memory gv) {
        return _governanceValues;
    }

    /// @notice Get the hash of a proposal.
    /// @dev Proposal details are not stored on-chain so the hash is used to enforce
    ///      consistency between calls.
    /// @param proposal The proposal to hash.
    /// @return proposalHash The hash of the proposal.
    function getProposalHash(Proposal memory proposal) public pure returns (bytes32 proposalHash) {
        // Hash the proposal in-place. Equivalent to:
        // keccak256(abi.encode(
        //   proposal.maxExecutableTime,
        //   proposal.cancelDelay,
        //   keccak256(proposal.proposalData)
        // ))
        bytes32 dataHash = keccak256(proposal.proposalData);
        assembly {
            // Overwrite the data field with the hash of its contents and then
            // hash the struct.
            let dataPos := add(proposal, 0x40)
            let t := mload(dataPos)
            mstore(dataPos, dataHash)
            proposalHash := keccak256(proposal, 0x60)
            // Restore the data field.
            mstore(dataPos, t)
        }
    }

    /// @notice Get the index of the most recent voting power snapshot <= `timestamp`.
    /// @param voter The address of the voter.
    /// @param timestamp The timestamp to get the snapshot index at.
    /// @return index The index of the snapshot.
    function findVotingPowerSnapshotIndex(
        address voter,
        uint40 timestamp
    ) public view returns (uint256 index) {
        VotingPowerSnapshot[] storage snaps = _votingPowerSnapshotsByVoter[voter];

        // Derived from Open Zeppelin binary search
        // ref: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Checkpoints.sol#L39
        uint256 high = snaps.length;
        uint256 low = 0;
        while (low < high) {
            uint256 mid = (low + high) / 2;
            if (snaps[mid].timestamp > timestamp) {
                // Entry is too recent.
                high = mid;
            } else {
                // Entry is older. This is our best guess for now.
                low = mid + 1;
            }
        }

        // Return `type(uint256).max` if no valid voting snapshots found.
        return high == 0 ? type(uint256).max : high - 1;
    }

    /// @notice Pledge your intrinsic voting power to a new delegate, removing it from
    ///         the old one (if any).
    /// @param delegate The address to delegating voting power to.
    function delegateVotingPower(address delegate) external {
        _adjustVotingPower(msg.sender, 0, delegate);
    }

    /// @notice Transfer party host status to another.
    /// @param newPartyHost The address of the new host.
    function abdicateHost(address newPartyHost) external onlyHost {
        // 0 is a special case burn address.
        if (newPartyHost != address(0)) {
            // Cannot transfer host status to an existing host.
            if (isHost[newPartyHost]) {
                revert InvalidNewHostError();
            }
            isHost[newPartyHost] = true;
        }
        isHost[msg.sender] = false;
        emit HostStatusTransferred(msg.sender, newPartyHost);
    }

    /// @notice Create a token distribution by moving the party's entire balance
    ///         to the `TokenDistributor` contract and immediately creating a
    ///         distribution governed by this party.
    /// @dev The `feeBps` and `feeRecipient` this party was created with will be
    ///      propagated to the distribution. Party members are entitled to a
    ///      share of the distribution's tokens proportionate to their relative
    ///      voting power in this party (less the fee).
    /// @dev Allow this to be called by the party itself for `FractionalizeProposal`.
    /// @param tokenType The type of token to distribute.
    /// @param token The address of the token to distribute.
    /// @param tokenId The ID of the token to distribute. Currently unused but
    ///                may be used in the future to support other distribution types.
    /// @return distInfo The information about the created distribution.
    function distribute(
        uint256 amount,
        ITokenDistributor.TokenType tokenType,
        address token,
        uint256 tokenId
    )
        external
        onlyWhenNotGloballyDisabled
        returns (ITokenDistributor.DistributionInfo memory distInfo)
    {
        // Ignore if the party is calling functions on itself, like with
        // `FractionalizeProposal` and `DistributionProposal`.
        if (msg.sender != address(this)) {
            // Must not require a vote to create a distribution, otherwise
            // distributions can only be created through a distribution
            // proposal.
            if (_getSharedProposalStorage().opts.distributionsRequireVote) {
                revert DistributionsRequireVoteError();
            }
            // Must be an active member.
            VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender);
            if (snap.intrinsicVotingPower == 0 && snap.delegatedVotingPower == 0) {
                revert OnlyActiveMemberError();
            }
        }
        // Prevent creating a distribution if the party has not started.
        if (_governanceValues.totalVotingPower == 0) {
            revert PartyNotStartedError();
        }
        // Get the address of the token distributor.
        ITokenDistributor distributor = ITokenDistributor(
            _GLOBALS.getAddress(LibGlobals.GLOBAL_TOKEN_DISTRIBUTOR)
        );
        emit DistributionCreated(tokenType, token, tokenId);
        // Notify third-party platforms that the governance NFT metadata has
        // updated for all tokens.
        emit BatchMetadataUpdate(0, type(uint256).max);
        // Create a native token distribution.
        address payable feeRecipient_ = feeRecipient;
        uint16 feeBps_ = feeBps;
        if (tokenType == ITokenDistributor.TokenType.Native) {
            return
                distributor.createNativeDistribution{ value: amount }(
                    Party(payable(address(this))),
                    feeRecipient_,
                    feeBps_
                );
        }
        // Otherwise must be an ERC20 token distribution.
        assert(tokenType == ITokenDistributor.TokenType.Erc20);
        IERC20(token).compatTransfer(address(distributor), amount);
        return
            distributor.createErc20Distribution(
                IERC20(token),
                Party(payable(address(this))),
                feeRecipient_,
                feeBps_
            );
    }

    /// @notice Make a proposal for members to vote on and cast a vote to accept it
    ///         as well.
    /// @dev Only an active member (has voting power) can call this.
    ///      Afterwards, members can vote to support it with `accept()` or a party
    ///      host can unilaterally reject the proposal with `veto()`.
    /// @param proposal The details of the proposal.
    /// @param latestSnapIndex The index of the caller's most recent voting power
    ///                        snapshot before the proposal was created. Should
    ///                        be retrieved off-chain and passed in.
    function propose(
        Proposal memory proposal,
        uint256 latestSnapIndex
    ) external onlyActiveMember returns (uint256 proposalId) {
        proposalId = ++lastProposalId;
        // Store the time the proposal was created and the proposal hash.
        (
            _proposalStateByProposalId[proposalId].values,
            _proposalStateByProposalId[proposalId].hash
        ) = (
            ProposalStateValues({
                proposedTime: uint40(block.timestamp),
                passedTime: 0,
                executedTime: 0,
                completedTime: 0,
                votes: 0,
                totalVotingPower: _governanceValues.totalVotingPower
            }),
            getProposalHash(proposal)
        );
        emit Proposed(proposalId, msg.sender, proposal);
        accept(proposalId, latestSnapIndex);

        // Notify third-party platforms that the governance NFT metadata has
        // updated for all tokens.
        emit BatchMetadataUpdate(0, type(uint256).max);
    }

    /// @notice Vote to support a proposed proposal.
    /// @dev The voting power cast will be the effective voting power of the caller
    ///      just before `propose()` was called (see `getVotingPowerAt()`).
    ///      If the proposal reaches `passThresholdBps` acceptance ratio then the
    ///      proposal will be in the `Passed` state and will be executable after
    ///      the `executionDelay` has passed, putting it in the `Ready` state.
    /// @param proposalId The ID of the proposal to accept.
    /// @param snapIndex The index of the caller's last voting power snapshot
    ///                  before the proposal was created. Should be retrieved
    ///                  off-chain and passed in.
    /// @return totalVotes The total votes cast on the proposal.
    function accept(uint256 proposalId, uint256 snapIndex) public returns (uint256 totalVotes) {
        // Get the information about the proposal.
        ProposalState storage info = _proposalStateByProposalId[proposalId];
        ProposalStateValues memory values = info.values;

        // Can only vote in certain proposal statuses.
        {
            ProposalStatus status = _getProposalStatus(values);
            // Allow voting even if the proposal is passed/ready so it can
            // potentially reach 100% consensus, which unlocks special
            // behaviors for certain proposal types.
            if (
                status != ProposalStatus.Voting &&
                status != ProposalStatus.Passed &&
                status != ProposalStatus.Ready
            ) {
                revert BadProposalStatusError(status);
            }
        }

        // Prevent voting in the same block as the last rage quit timestamp.
        // This is to prevent an exploit where a member can rage quit to reduce
        // the total voting power of the party, then propose and vote in the
        // same block since `getVotingPowerAt()` uses `values.proposedTime - 1`.
        // This would allow them to use the voting power snapshot just before
        // their card was burned to vote, potentially passing a proposal that
        // would have otherwise not passed.
        if (lastRageQuitTimestamp == block.timestamp) {
            revert CannotRageQuitAndAcceptError();
        }

        // Cannot vote twice.
        if (info.hasVoted[msg.sender]) {
            revert AlreadyVotedError(msg.sender);
        }
        // Mark the caller as having voted.
        info.hasVoted[msg.sender] = true;

        // Increase the total votes that have been cast on this proposal.
        uint96 votingPower = getVotingPowerAt(msg.sender, values.proposedTime - 1, snapIndex);
        values.votes += votingPower;
        info.values = values;
        emit ProposalAccepted(proposalId, msg.sender, votingPower);

        // Update the proposal status if it has reached the pass threshold.
        if (
            values.passedTime == 0 &&
            _areVotesPassing(
                values.votes,
                values.totalVotingPower,
                _governanceValues.passThresholdBps
            )
        ) {
            info.values.passedTime = uint40(block.timestamp);
            emit ProposalPassed(proposalId);
            // Notify third-party platforms that the governance NFT metadata has
            // updated for all tokens.
            emit BatchMetadataUpdate(0, type(uint256).max);
        }
        return values.votes;
    }

    /// @notice As a party host, veto a proposal, unilaterally rejecting it.
    /// @dev The proposal will never be executable and cannot be voted on anymore.
    ///      A proposal that has been already executed at least once (in the `InProgress` status)
    ///      cannot be vetoed.
    /// @param proposalId The ID of the proposal to veto.
    function veto(uint256 proposalId) external onlyHost {
        // Setting `votes` to -1 indicates a veto.
        ProposalState storage info = _proposalStateByProposalId[proposalId];
        ProposalStateValues memory values = info.values;

        {
            ProposalStatus status = _getProposalStatus(values);
            // Proposal must be in one of the following states.
            if (
                status != ProposalStatus.Voting &&
                status != ProposalStatus.Passed &&
                status != ProposalStatus.Ready
            ) {
                revert BadProposalStatusError(status);
            }
        }

        // -1 indicates veto.
        info.values.votes = VETO_VALUE;
        emit ProposalVetoed(proposalId, msg.sender);
        // Notify third-party platforms that the governance NFT metadata has
        // updated for all tokens.
        emit BatchMetadataUpdate(0, type(uint256).max);
    }

    /// @notice Executes a proposal that has passed governance.
    /// @dev The proposal must be in the `Ready` or `InProgress` status.
    ///      A `ProposalExecuted` event will be emitted with a non-empty `nextProgressData`
    ///      if the proposal has extra steps (must be executed again) to carry out,
    ///      in which case `nextProgressData` should be passed into the next `execute()` call.
    ///      The `ProposalExecutionEngine` enforces that only one `InProgress` proposal
    ///      is active at a time, so that proposal must be completed or cancelled via `cancel()`
    ///      in order to execute a different proposal.
    ///      `extraData` is optional, off-chain data a proposal might need to execute a step.
    /// @param proposalId The ID of the proposal to execute.
    /// @param proposal The details of the proposal.
    /// @param preciousTokens The tokens that the party considers precious.
    /// @param preciousTokenIds The token IDs associated with each precious token.
    /// @param progressData The data returned from the last `execute()` call, if any.
    /// @param extraData Off-chain data a proposal might need to execute a step.
    function execute(
        uint256 proposalId,
        Proposal memory proposal,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds,
        bytes calldata progressData,
        bytes calldata extraData
    ) external payable onlyActiveMember onlyWhenNotGloballyDisabled onlyDelegateCall {
        // Get information about the proposal.
        ProposalState storage proposalState = _proposalStateByProposalId[proposalId];
        // Proposal details must remain the same from `propose()`.
        _validateProposalHash(proposal, proposalState.hash);
        ProposalStateValues memory values = proposalState.values;
        ProposalStatus status = _getProposalStatus(values);
        // The proposal must be executable or have already been executed but still
        // has more steps to go.
        if (status != ProposalStatus.Ready && status != ProposalStatus.InProgress) {
            revert BadProposalStatusError(status);
        }
        if (status == ProposalStatus.Ready) {
            // If the proposal has not been executed yet, make sure it hasn't
            // expired. Note that proposals that have been executed
            // (but still have more steps) ignore `maxExecutableTime`.
            if (proposal.maxExecutableTime < block.timestamp) {
                revert ExecutionTimeExceededError(
                    proposal.maxExecutableTime,
                    uint40(block.timestamp)
                );
            }
            proposalState.values.executedTime = uint40(block.timestamp);
        }
        // Check that the precious list is valid.
        if (!_isPreciousListCorrect(preciousTokens, preciousTokenIds)) {
            revert BadPreciousListError();
        }
        // Preemptively set the proposal to completed to avoid it being executed
        // again in a deeper call.
        proposalState.values.completedTime = uint40(block.timestamp);
        // Execute the proposal.
        bool completed = _executeProposal(
            proposalId,
            proposal,
            preciousTokens,
            preciousTokenIds,
            _getProposalFlags(values),
            progressData,
            extraData
        );
        if (!completed) {
            // Proposal did not complete.
            proposalState.values.completedTime = 0;
        }
    }

    /// @notice Cancel a (probably stuck) InProgress proposal.
    /// @dev `proposal.cancelDelay` seconds must have passed since it was first
    ///      executed for this to be valid. The currently active proposal will
    ///      simply be yeeted out of existence so another proposal can execute.
    ///      This is intended to be a last resort and can leave the party in a
    ///      broken state. Whenever possible, active proposals should be
    ///      allowed to complete their lifecycle.
    /// @param proposalId The ID of the proposal to cancel.
    /// @param proposal The details of the proposal to cancel.
    function cancel(uint256 proposalId, Proposal calldata proposal) external onlyActiveMember {
        // Get information about the proposal.
        ProposalState storage proposalState = _proposalStateByProposalId[proposalId];
        // Proposal details must remain the same from `propose()`.
        _validateProposalHash(proposal, proposalState.hash);
        ProposalStateValues memory values = proposalState.values;
        {
            // Must be `InProgress`.
            ProposalStatus status = _getProposalStatus(values);
            if (status != ProposalStatus.InProgress) {
                revert BadProposalStatusError(status);
            }
        }
        {
            // Limit the `cancelDelay` to the global max and min cancel delay
            // to mitigate parties accidentally getting stuck forever by setting an
            // unrealistic `cancelDelay` or being reckless with too low a
            // cancel delay.
            uint256 cancelDelay = proposal.cancelDelay;
            uint256 globalMaxCancelDelay = _GLOBALS.getUint256(
                LibGlobals.GLOBAL_PROPOSAL_MAX_CANCEL_DURATION
            );
            uint256 globalMinCancelDelay = _GLOBALS.getUint256(
                LibGlobals.GLOBAL_PROPOSAL_MIN_CANCEL_DURATION
            );
            if (globalMaxCancelDelay != 0) {
                // Only if we have one set.
                if (cancelDelay > globalMaxCancelDelay) {
                    cancelDelay = globalMaxCancelDelay;
                }
            }
            if (globalMinCancelDelay != 0) {
                // Only if we have one set.
                if (cancelDelay < globalMinCancelDelay) {
                    cancelDelay = globalMinCancelDelay;
                }
            }
            uint256 cancelTime = values.executedTime + cancelDelay;
            // Must not be too early.
            if (block.timestamp < cancelTime) {
                revert ProposalCannotBeCancelledYetError(
                    uint40(block.timestamp),
                    uint40(cancelTime)
                );
            }
        }
        // Mark the proposal as cancelled by setting the completed time to the current
        // time with the high bit set.
        proposalState.values.completedTime = uint40(block.timestamp | UINT40_HIGH_BIT);
        {
            // Delegatecall into the proposal engine impl to perform the cancel.
            (bool success, bytes memory resultData) = (
                address(_getSharedProposalStorage().engineImpl)
            ).delegatecall(abi.encodeCall(IProposalExecutionEngine.cancelProposal, (proposalId)));
            if (!success) {
                resultData.rawRevert();
            }
        }
        emit ProposalCancelled(proposalId);
        // Notify third-party platforms that the governance NFT metadata has
        // updated for all tokens.
        emit BatchMetadataUpdate(0, type(uint256).max);
    }

    /// @notice As the DAO, execute an arbitrary function call from this contract.
    /// @dev Emergency actions must not be revoked for this to work.
    /// @param targetAddress The contract to call.
    /// @param targetCallData The data to pass to the contract.
    /// @param amountEth The amount of ETH to send to the contract.
    function emergencyExecute(
        address targetAddress,
        bytes calldata targetCallData,
        uint256 amountEth
    ) external payable onlyPartyDao onlyWhenEmergencyExecuteAllowed onlyDelegateCall {
        (bool success, bytes memory res) = targetAddress.call{ value: amountEth }(targetCallData);
        if (!success) {
            res.rawRevert();
        }
        emit EmergencyExecute(targetAddress, targetCallData, amountEth);
    }

    /// @notice Revoke the DAO's ability to call emergencyExecute().
    /// @dev Either the DAO or the party host can call this.
    function disableEmergencyExecute() external onlyPartyDaoOrHost {
        emergencyExecuteDisabled = true;
        emit EmergencyExecuteDisabled();
    }

    function _executeProposal(
        uint256 proposalId,
        Proposal memory proposal,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds,
        uint256 flags,
        bytes memory progressData,
        bytes memory extraData
    ) private returns (bool completed) {
        // Setup the arguments for the proposal execution engine.
        IProposalExecutionEngine.ExecuteProposalParams
            memory executeParams = IProposalExecutionEngine.ExecuteProposalParams({
                proposalId: proposalId,
                proposalData: proposal.proposalData,
                progressData: progressData,
                extraData: extraData,
                preciousTokens: preciousTokens,
                preciousTokenIds: preciousTokenIds,
                flags: flags
            });
        // Get the progress data returned after the proposal is executed.
        bytes memory nextProgressData;
        {
            // Execute the proposal.
            (bool success, bytes memory resultData) = address(
                _getSharedProposalStorage().engineImpl
            ).delegatecall(
                    abi.encodeCall(IProposalExecutionEngine.executeProposal, (executeParams))
                );
            if (!success) {
                resultData.rawRevert();
            }
            nextProgressData = abi.decode(resultData, (bytes));
        }
        emit ProposalExecuted(proposalId, msg.sender, nextProgressData);
        // Notify third-party platforms that the governance NFT metadata has
        // updated for all tokens.
        emit BatchMetadataUpdate(0, type(uint256).max);
        // If the returned progress data is empty, then the proposal completed
        // and it should not be executed again.
        return nextProgressData.length == 0;
    }

    // Get the most recent voting power snapshot <= timestamp using `hintindex` as a "hint".
    function _getVotingPowerSnapshotAt(
        address voter,
        uint40 timestamp,
        uint256 hintIndex
    ) internal view returns (VotingPowerSnapshot memory snap) {
        VotingPowerSnapshot[] storage snaps = _votingPowerSnapshotsByVoter[voter];
        uint256 snapsLength = snaps.length;
        if (snapsLength != 0) {
            if (
                // Hint is within bounds.
                hintIndex < snapsLength &&
                // Snapshot is not too recent.
                snaps[hintIndex].timestamp <= timestamp &&
                // Snapshot is not too old.
                (hintIndex == snapsLength - 1 || snaps[hintIndex + 1].timestamp > timestamp)
            ) {
                return snaps[hintIndex];
            }

            // Hint was wrong, fallback to binary search to find snapshot.
            hintIndex = findVotingPowerSnapshotIndex(voter, timestamp);
            // Check that snapshot was found.
            if (hintIndex != type(uint256).max) {
                return snaps[hintIndex];
            }
        }

        // No snapshot found.
        return snap;
    }

    // Transfers some voting power of `from` to `to`. The total voting power of
    // their respective delegates will be updated as well.
    function _transferVotingPower(address from, address to, uint256 power) internal {
        int192 powerI192 = power.safeCastUint256ToInt192();
        _adjustVotingPower(from, -powerI192, address(0));
        _adjustVotingPower(to, powerI192, address(0));
    }

    // Increase `voter`'s intrinsic voting power and update their delegate if delegate is nonzero.
    function _adjustVotingPower(address voter, int192 votingPower, address delegate) internal {
        VotingPowerSnapshot memory oldSnap = _getLastVotingPowerSnapshotForVoter(voter);
        address oldDelegate = delegationsByVoter[voter];
        // If `oldDelegate` is zero and `voter` never delegated, then have
        // `voter` delegate to themself.
        oldDelegate = oldDelegate == address(0) ? voter : oldDelegate;
        // If the new `delegate` is zero, use the current (old) delegate.
        delegate = delegate == address(0) ? oldDelegate : delegate;

        VotingPowerSnapshot memory newSnap = VotingPowerSnapshot({
            timestamp: uint40(block.timestamp),
            delegatedVotingPower: oldSnap.delegatedVotingPower,
            intrinsicVotingPower: (oldSnap.intrinsicVotingPower.safeCastUint96ToInt192() +
                votingPower).safeCastInt192ToUint96(),
            isDelegated: delegate != voter
        });
        _insertVotingPowerSnapshot(voter, newSnap);
        delegationsByVoter[voter] = delegate;

        // This event is emitted even if the delegate did not change.
        emit PartyDelegateUpdated(msg.sender, delegate);

        // Handle rebalancing delegates.
        _rebalanceDelegates(voter, oldDelegate, delegate, oldSnap, newSnap);
    }

    // Update the delegated voting power of the old and new delegates delegated to
    // by `voter` based on the snapshot change.
    function _rebalanceDelegates(
        address voter,
        address oldDelegate,
        address newDelegate,
        VotingPowerSnapshot memory oldSnap,
        VotingPowerSnapshot memory newSnap
    ) private {
        if (newDelegate == address(0) || oldDelegate == address(0)) {
            revert InvalidDelegateError();
        }
        if (oldDelegate != voter && oldDelegate != newDelegate) {
            // Remove past voting power from old delegate.
            VotingPowerSnapshot memory oldDelegateSnap = _getLastVotingPowerSnapshotForVoter(
                oldDelegate
            );
            VotingPowerSnapshot memory updatedOldDelegateSnap = VotingPowerSnapshot({
                timestamp: uint40(block.timestamp),
                delegatedVotingPower: oldDelegateSnap.delegatedVotingPower -
                    oldSnap.intrinsicVotingPower,
                intrinsicVotingPower: oldDelegateSnap.intrinsicVotingPower,
                isDelegated: oldDelegateSnap.isDelegated
            });
            _insertVotingPowerSnapshot(oldDelegate, updatedOldDelegateSnap);
        }
        if (newDelegate != voter) {
            // Not delegating to self.
            // Add new voting power to new delegate.
            VotingPowerSnapshot memory newDelegateSnap = _getLastVotingPowerSnapshotForVoter(
                newDelegate
            );
            uint96 newDelegateDelegatedVotingPower = newDelegateSnap.delegatedVotingPower +
                newSnap.intrinsicVotingPower;
            if (newDelegate == oldDelegate) {
                // If the old and new delegate are the same, subtract the old
                // intrinsic voting power of the voter, or else we will double
                // count a portion of it.
                newDelegateDelegatedVotingPower -= oldSnap.intrinsicVotingPower;
            }
            VotingPowerSnapshot memory updatedNewDelegateSnap = VotingPowerSnapshot({
                timestamp: uint40(block.timestamp),
                delegatedVotingPower: newDelegateDelegatedVotingPower,
                intrinsicVotingPower: newDelegateSnap.intrinsicVotingPower,
                isDelegated: newDelegateSnap.isDelegated
            });
            _insertVotingPowerSnapshot(newDelegate, updatedNewDelegateSnap);
        }
    }

    // Append a new voting power snapshot, overwriting the last one if possible.
    function _insertVotingPowerSnapshot(address voter, VotingPowerSnapshot memory snap) private {
        VotingPowerSnapshot[] storage voterSnaps = _votingPowerSnapshotsByVoter[voter];
        uint256 n = voterSnaps.length;
        // If same timestamp as last entry, overwrite the last snapshot, otherwise append.
        if (n != 0) {
            VotingPowerSnapshot memory lastSnap = voterSnaps[n - 1];
            if (lastSnap.timestamp == snap.timestamp) {
                voterSnaps[n - 1] = snap;
                return;
            }
        }
        voterSnaps.push(snap);

        emit PartyVotingSnapshotCreated(
            voter,
            snap.timestamp,
            snap.delegatedVotingPower,
            snap.intrinsicVotingPower,
            snap.isDelegated
        );
    }

    function _getLastVotingPowerSnapshotForVoter(
        address voter
    ) private view returns (VotingPowerSnapshot memory snap) {
        VotingPowerSnapshot[] storage voterSnaps = _votingPowerSnapshotsByVoter[voter];
        uint256 n = voterSnaps.length;
        if (n != 0) {
            snap = voterSnaps[n - 1];
        }
    }

    function _getProposalFlags(ProposalStateValues memory pv) private pure returns (uint256) {
        if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) {
            return LibProposal.PROPOSAL_FLAG_UNANIMOUS;
        }
        return 0;
    }

    function _getProposalStatus(
        ProposalStateValues memory pv
    ) private view returns (ProposalStatus status) {
        // Never proposed.
        if (pv.proposedTime == 0) {
            return ProposalStatus.Invalid;
        }
        // Executed at least once.
        if (pv.executedTime != 0) {
            if (pv.completedTime == 0) {
                return ProposalStatus.InProgress;
            }
            // completedTime high bit will be set if cancelled.
            if (pv.completedTime & UINT40_HIGH_BIT == UINT40_HIGH_BIT) {
                return ProposalStatus.Cancelled;
            }
            return ProposalStatus.Complete;
        }
        // Vetoed.
        if (pv.votes == type(uint96).max) {
            return ProposalStatus.Defeated;
        }
        uint40 t = uint40(block.timestamp);
        GovernanceValues memory gv = _governanceValues;
        if (pv.passedTime != 0) {
            // Ready.
            if (pv.passedTime + gv.executionDelay <= t) {
                return ProposalStatus.Ready;
            }
            // If unanimous, we skip the execution delay.
            if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) {
                return ProposalStatus.Ready;
            }
            // Passed.
            return ProposalStatus.Passed;
        }
        // Voting window expired.
        if (pv.proposedTime + gv.voteDuration <= t) {
            return ProposalStatus.Defeated;
        }
        return ProposalStatus.Voting;
    }

    function _isUnanimousVotes(
        uint96 totalVotes,
        uint96 totalVotingPower
    ) private pure returns (bool) {
        uint256 acceptanceRatio = (totalVotes * 1e4) / totalVotingPower;
        // If >= 99.99% acceptance, consider it unanimous.
        // The minting formula for voting power is a bit lossy, so we check
        // for slightly less than 100%.
        return acceptanceRatio >= 0.9999e4;
    }

    function _areVotesPassing(
        uint96 voteCount,
        uint96 totalVotingPower,
        uint16 passThresholdBps
    ) private pure returns (bool) {
        return (uint256(voteCount) * 1e4) / uint256(totalVotingPower) >= uint256(passThresholdBps);
    }

    function _setPreciousList(
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) private {
        if (preciousTokens.length != preciousTokenIds.length) {
            revert MismatchedPreciousListLengths();
        }
        preciousListHash = _hashPreciousList(preciousTokens, preciousTokenIds);
    }

    function _isPreciousListCorrect(
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) private view returns (bool) {
        return preciousListHash == _hashPreciousList(preciousTokens, preciousTokenIds);
    }

    function _hashPreciousList(
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) internal pure returns (bytes32 h) {
        assembly {
            mstore(0x00, keccak256(add(preciousTokens, 0x20), mul(mload(preciousTokens), 0x20)))
            mstore(0x20, keccak256(add(preciousTokenIds, 0x20), mul(mload(preciousTokenIds), 0x20)))
            h := keccak256(0x00, 0x40)
        }
    }

    // Assert that the hash of a proposal matches expectedHash.
    function _validateProposalHash(Proposal memory proposal, bytes32 expectedHash) private pure {
        bytes32 actualHash = getProposalHash(proposal);
        if (expectedHash != actualHash) {
            revert BadProposalHashError(actualHash, expectedHash);
        }
    }
}

File 11 of 43 : IMetadataProvider.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

interface IMetadataProvider {
    /// @notice Whether or not the metadata provider supports registrars that can
    ///         set metadata for other instances.
    /// @dev See `MetadataRegistry` for more information on the registrar role.
    function supportsRegistrars() external view returns (bool);

    /// @notice Get the metadata for a Party instance.
    /// @param instance The address of the instance.
    /// @param tokenId The ID of the token to get the metadata for.
    /// @return metadata The encoded metadata.
    function getMetadata(
        address instance,
        uint256 tokenId
    ) external view returns (bytes memory metadata);
}

File 12 of 43 : Multicall.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/LibRawResult.sol";

abstract contract Multicall {
    using LibRawResult for bytes;

    /// @notice Perform multiple delegatecalls on ourselves.
    function multicall(bytes[] calldata multicallData) external {
        for (uint256 i; i < multicallData.length; ++i) {
            (bool s, bytes memory r) = address(this).delegatecall(multicallData[i]);
            if (!s) {
                r.rawRevert();
            }
        }
    }
}

File 13 of 43 : Implementation.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

// Base contract for all contracts intended to be delegatecalled into.
abstract contract Implementation {
    error OnlyDelegateCallError();
    error OnlyConstructorError();

    address public immutable IMPL;

    constructor() {
        IMPL = address(this);
    }

    // Reverts if the current function context is not inside of a delegatecall.
    modifier onlyDelegateCall() virtual {
        if (address(this) == IMPL) {
            revert OnlyDelegateCallError();
        }
        _;
    }

    // Reverts if the current function context is not inside of a constructor.
    modifier onlyConstructor() {
        if (address(this).code.length != 0) {
            revert OnlyConstructorError();
        }
        _;
    }
}

File 14 of 43 : LibSafeCast.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

library LibSafeCast {
    error Uint256ToUint96CastOutOfRange(uint256 v);
    error Uint256ToInt192CastOutOfRange(uint256 v);
    error Int192ToUint96CastOutOfRange(int192 i192);
    error Uint256ToInt128CastOutOfRangeError(uint256 u256);
    error Uint256ToUint128CastOutOfRangeError(uint256 u256);
    error Uint256ToUint40CastOutOfRangeError(uint256 u256);

    function safeCastUint256ToUint96(uint256 v) internal pure returns (uint96) {
        if (v > uint256(type(uint96).max)) {
            revert Uint256ToUint96CastOutOfRange(v);
        }
        return uint96(v);
    }

    function safeCastUint256ToUint128(uint256 v) internal pure returns (uint128) {
        if (v > uint256(type(uint128).max)) {
            revert Uint256ToUint128CastOutOfRangeError(v);
        }
        return uint128(v);
    }

    function safeCastUint256ToInt192(uint256 v) internal pure returns (int192) {
        if (v > uint256(uint192(type(int192).max))) {
            revert Uint256ToInt192CastOutOfRange(v);
        }
        return int192(uint192(v));
    }

    function safeCastUint96ToInt192(uint96 v) internal pure returns (int192) {
        return int192(uint192(v));
    }

    function safeCastInt192ToUint96(int192 i192) internal pure returns (uint96) {
        if (i192 < 0 || i192 > int192(uint192(type(uint96).max))) {
            revert Int192ToUint96CastOutOfRange(i192);
        }
        return uint96(uint192(i192));
    }

    function safeCastUint256ToInt128(uint256 x) internal pure returns (int128) {
        if (x > uint256(uint128(type(int128).max))) {
            revert Uint256ToInt128CastOutOfRangeError(x);
        }
        return int128(uint128(x));
    }

    function safeCastUint256ToUint40(uint256 x) internal pure returns (uint40) {
        if (x > uint256(type(uint40).max)) {
            revert Uint256ToUint40CastOutOfRangeError(x);
        }
        return uint40(x);
    }
}

File 15 of 43 : LibRawResult.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

library LibRawResult {
    // Revert with the data in `b`.
    function rawRevert(bytes memory b) internal pure {
        assembly {
            revert(add(b, 32), mload(b))
        }
    }

    // Return with the data in `b`.
    function rawReturn(bytes memory b) internal pure {
        assembly {
            return(add(b, 32), mload(b))
        }
    }
}

File 16 of 43 : LibRenderer.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import { Strings } from "../utils/vendor/Strings.sol";

enum Color {
    DEFAULT,
    GREEN,
    CYAN,
    BLUE,
    PURPLE,
    PINK,
    ORANGE,
    RED
}

enum ColorType {
    PRIMARY,
    SECONDARY,
    LIGHT,
    DARK
}

library LibRenderer {
    using Strings for uint256;
    using Strings for string;

    function calcAnimationVariables(
        string memory partyName
    ) external pure returns (uint256 duration, uint256 steps, uint256 delay, uint256 translateX) {
        translateX = bytes(partyName).length * 30 + 300;
        duration = translateX / 56;

        // Make duration even so that the animation delay is always exactly
        // half of the duration.
        if (duration % 2 != 0) duration += 1;

        delay = duration / 2;
        steps = translateX / 6;
    }

    function formatAsDecimalString(
        uint256 n,
        uint256 decimals,
        uint256 maxChars
    ) external pure returns (string memory) {
        string memory str = n.toString();
        uint256 oneUnit = 10 ** decimals;
        if (n < 10 ** (decimals - 2)) {
            return "&lt;0.01";
        } else if (n < oneUnit) {
            // Preserve leading zeros for decimals.
            // (e.g. if 0.01, `n` will "1" so we need to prepend a "0").
            return
                string.concat("0.", prependNumWithZeros(str, decimals).substring(0, maxChars - 1));
        } else if (n >= 1000 * oneUnit) {
            return str.substring(0, maxChars);
        } else {
            uint256 i = bytes((n / oneUnit).toString()).length;
            return string.concat(str.substring(0, i), ".", str.substring(i, maxChars));
        }
    }

    function prependNumWithZeros(
        string memory numStr,
        uint256 expectedLength
    ) public pure returns (string memory) {
        uint256 length = bytes(numStr).length;
        if (length < expectedLength) {
            for (uint256 i; i < expectedLength - length; ++i) {
                numStr = string.concat("0", numStr);
            }
        }

        return numStr;
    }

    function generateColorHex(
        Color color,
        ColorType colorType
    ) external pure returns (string memory colorHex) {
        if (color == Color.DEFAULT) {
            if (colorType == ColorType.PRIMARY) {
                return "#A7B8CF";
            } else if (colorType == ColorType.SECONDARY) {
                return "#DCE5F0";
            } else if (colorType == ColorType.LIGHT) {
                return "#91A6C3";
            } else if (colorType == ColorType.DARK) {
                return "#50586D";
            }
        } else if (color == Color.GREEN) {
            if (colorType == ColorType.PRIMARY) {
                return "#10B173";
            } else if (colorType == ColorType.SECONDARY) {
                return "#93DCB7";
            } else if (colorType == ColorType.LIGHT) {
                return "#00A25A";
            } else if (colorType == ColorType.DARK) {
                return "#005E3B";
            }
        } else if (color == Color.CYAN) {
            if (colorType == ColorType.PRIMARY) {
                return "#00C1FA";
            } else if (colorType == ColorType.SECONDARY) {
                return "#B1EFFD";
            } else if (colorType == ColorType.LIGHT) {
                return "#00B4EA";
            } else if (colorType == ColorType.DARK) {
                return "#005669";
            }
        } else if (color == Color.BLUE) {
            if (colorType == ColorType.PRIMARY) {
                return "#2C78F3";
            } else if (colorType == ColorType.SECONDARY) {
                return "#B3D4FF";
            } else if (colorType == ColorType.LIGHT) {
                return "#0E70E0";
            } else if (colorType == ColorType.DARK) {
                return "#00286A";
            }
        } else if (color == Color.PURPLE) {
            if (colorType == ColorType.PRIMARY) {
                return "#9B45DF";
            } else if (colorType == ColorType.SECONDARY) {
                return "#D2ACF2";
            } else if (colorType == ColorType.LIGHT) {
                return "#832EC9";
            } else if (colorType == ColorType.DARK) {
                return "#47196B";
            }
        } else if (color == Color.PINK) {
            if (colorType == ColorType.PRIMARY) {
                return "#FF6BF3";
            } else if (colorType == ColorType.SECONDARY) {
                return "#FFC8FB";
            } else if (colorType == ColorType.LIGHT) {
                return "#E652E2";
            } else if (colorType == ColorType.DARK) {
                return "#911A96";
            }
        } else if (color == Color.ORANGE) {
            if (colorType == ColorType.PRIMARY) {
                return "#FF8946";
            } else if (colorType == ColorType.SECONDARY) {
                return "#FFE38B";
            } else if (colorType == ColorType.LIGHT) {
                return "#E47B2F";
            } else if (colorType == ColorType.DARK) {
                return "#732700";
            }
        } else if (color == Color.RED) {
            if (colorType == ColorType.PRIMARY) {
                return "#EC0000";
            } else if (colorType == ColorType.SECONDARY) {
                return "#FFA6A6";
            } else if (colorType == ColorType.LIGHT) {
                return "#D70000";
            } else if (colorType == ColorType.DARK) {
                return "#6F0000";
            }
        }
    }

    function getCollectionImageAndBanner(
        Color color,
        bool isDarkMode
    ) external pure returns (string memory image, string memory banner) {
        if (isDarkMode) {
            if (color == Color.GREEN) {
                image = "QmdcjXrxj7EimjuNTLQp1uKM2zYhuF1WVkVjF6TpfNNXrf";
                banner = "QmR3vqAV17SJiwksiCHV1cLQuf9TKZuar8NQu8GmKkHRXM";
            } else if (color == Color.CYAN) {
                image = "QmS678DTkTTzFQEDiqj3AsW6wt6bi4bNhWbKcBM29HBhhB";
                banner = "QmYSbXyPh9Lx2wmv6Z7SK8iFD9kyADxtaohV1gv6ZVKFj4";
            } else if (color == Color.BLUE) {
                image = "QmX2k8beAjyVhPk1ZrK6KrwbqLk3fRNPPmknRti7zEtGQa";
                banner = "QmaXN8MbcrjkHPt97Z7xeuTCx6wPkJ9xZN7ExZZwMabspy";
            } else if (color == Color.PURPLE) {
                image = "Qmf8SrxKH3QZQCEzcMbA3UoJGZ1j2coTLaQFptWhZZvqhg";
                banner = "QmWhpo9kN2Nf8ioWb7BKwqsCQKt3M2TduHPNqMqXz9jUK4";
            } else if (color == Color.PINK) {
                image = "QmV5eT9DWvU5BJa4LVSemkoDKyjJBC56adk2JLWMXYEQfn";
                banner = "QmP2NTyvMQ5yN1RY4nH1HfoTX2r6Ug6WZfN9GFf9toak8M";
            } else if (color == Color.ORANGE) {
                image = "QmPirB7VFaao2ZUxLtM5WTCwZhE7c9Uy2heyNZF5t9PgsS";
                banner = "QmUMgkjhrxedcLUvWt8VKrZdvrCGmjxWDSDXfm2M96nKUx";
            } else if (color == Color.RED) {
                image = "QmNRZ3syuEiiAkWYRFs9BpQ5M38wv8tEu17J2sYwmMdeta";
                banner = "QmQV1EjzwQsXdgi6C6ubfopZrprk6Ab9LVnggZBgJDg5C2";
            } else {
                image = "QmNwGtGyYwDfS6ghQbDw5a9buv7auFXz63W3rDhzmxjVhw";
                banner = "QmYUujTBgH6RTswZSiSzuy2GUojE6H9edaXeoSwwvh2T7o";
            }
        } else {
            if (color == Color.GREEN) {
                image = "QmR7t2g2hrkMYyzhUMEANzEGX74FQcbg3c7eTvCcQucMst";
                banner = "Qmc3zRfT6nC2G1KgoWpCvLDVjNi7x7KFgwButecj3sq9qg";
            } else if (color == Color.CYAN) {
                image = "QmeiBRb9muNXej3dn4usjjdtbUpgYASfA5jmWqJRshivGH";
                banner = "QmcsvMB2xiBKKMyKsBkC51TjWnyk42nNJnsudoGFsvvXCt";
            } else if (color == Color.BLUE) {
                image = "QmaErgGsanUTo73RMgvizMg3c7x1d1X4t76Tti22Pc1xan";
                banner = "QmSjhHF994xBd7wavV4mhj7GpZmw5euQUBxpiPbbmxExqp";
            } else if (color == Color.PURPLE) {
                image = "QmeVJTcUpKQFSz5aBsVpQk8quoXEEZBNPAAMo3wHvdRzHa";
                banner = "QmYx6aHYGitr6p8dHUa7n4nyew3pWy1Mfdn8fpFLejmhHC";
            } else if (color == Color.PINK) {
                image = "QmY4JJkBEeHVYHdfCCXPd7bWkAhNRxuKTWdf9MssGxSmCG";
                banner = "QmQH5CFf3qXG2oymGzwTDgUmPFBsaTw2Qbfhjk58VHcABd";
            } else if (color == Color.ORANGE) {
                image = "QmYhB3vjBLPwPTC5SBidNbZhB5oBMBMpKy4g6ejTxmGLkK";
                banner = "QmRP8cVjJyPRV7wXs5ApwbCFyTBpHXmHsDexbcN4o7aomd";
            } else if (color == Color.RED) {
                image = "QmfG8HPMEsKwKJ8xX3i2JhJtCskuiUjZLe8NhvXFdYyFR2";
                banner = "QmazXbqfFtQexkwFDbYkyvLF4xRSDrmEVQS4PSAs2ZtxDn";
            } else {
                image = "QmZKE4XkPvU7Z8CdgK2Cn7gLQ4t8CDkkfnR1j5bZ2AfRJu";
                banner = "QmTKCqLUQJt3VxGuUqLMj1jcCRRsaZwY4k757Wb7YPzmH2";
            }
        }

        image = string.concat("ipfs://", image);
        banner = string.concat("ipfs://", banner);
    }
}

File 17 of 43 : Strings.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8;

// Modified from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Strings.sol
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    function substring(
        string memory str,
        uint startIndex,
        uint endIndex
    ) internal pure returns (string memory) {
        bytes memory strBytes = bytes(str);
        bytes memory result = new bytes(endIndex - startIndex);
        for (uint i = startIndex; i < endIndex; i++) {
            result[i - startIndex] = strBytes[i];
        }
        return string(result);
    }
}

File 18 of 43 : Base64.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8;

/// [MIT License]
/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <[email protected]>
library Base64 {
    bytes internal constant TABLE =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /// @notice Encodes some bytes to the base64 representation
    function encode(bytes memory data) internal pure returns (string memory) {
        uint256 len = data.length;
        if (len == 0) return "";

        // multiply by 4/3 rounded up
        uint256 encodedLen = 4 * ((len + 2) / 3);

        // Add some extra buffer at the end
        bytes memory result = new bytes(encodedLen + 32);

        bytes memory table = TABLE;

        assembly {
            let tablePtr := add(table, 1)
            let resultPtr := add(result, 32)

            for {
                let i := 0
            } lt(i, len) {

            } {
                i := add(i, 3)
                let input := and(mload(add(data, i)), 0xffffff)

                let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
                out := shl(8, out)
                out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF))
                out := shl(8, out)
                out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF))
                out := shl(8, out)
                out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF))
                out := shl(224, out)

                mstore(resultPtr, out)

                resultPtr := add(resultPtr, 4)
            }

            switch mod(len, 3)
            case 1 {
                mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
            }
            case 2 {
                mstore(sub(resultPtr, 1), shl(248, 0x3d))
            }

            mstore(result, encodedLen)
        }

        return string(result);
    }
}

File 19 of 43 : RendererBase.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import { Strings } from "../utils/vendor/Strings.sol";
import { Color } from "../utils/LibRenderer.sol";
import { Party } from "contracts/party/Party.sol";
import { IGlobals } from "../globals/IGlobals.sol";
import { IFont } from "./fonts/IFont.sol";
import { IERC721Renderer } from "./IERC721Renderer.sol";
import { RendererStorage } from "./RendererStorage.sol";

abstract contract RendererBase is IERC721Renderer {
    using Strings for uint256;
    using Strings for string;

    IGlobals immutable _GLOBALS;
    RendererStorage immutable _storage;
    IFont immutable _font;

    constructor(IGlobals globals, RendererStorage rendererStorage, IFont font) {
        _GLOBALS = globals;
        _storage = rendererStorage;
        _font = font;
    }

    function contractURI() external view virtual returns (string memory);

    function getCustomizationChoices() internal view returns (bool isDarkMode, Color color) {
        // Get the customization preset ID chosen by the crowdfund or party instance.
        uint256 presetId = _storage.getPresetFor(address(this));

        if (presetId == 0) {
            // Preset ID 0 is reserved. It is used to indicate to party instances
            // to use the same customization preset as the crowdfund.
            (bool success, bytes memory result) = address(this).staticcall(
                // Call mintAuthority
                abi.encodeWithSignature("mintAuthority()")
            );

            if (success && result.length == 32) {
                address crowdfund = abi.decode(result, (address));
                // Should return the crowdfund used to create the party, if the
                // party was created conventionally. Use the customization
                // preset chosen during crowdfund initialization.
                presetId = _storage.getPresetFor(crowdfund);
                // If the preset ID is still 0 (this shouldn't happen), fallback
                // to the default customization options.
                if (presetId == 0) return (false, Color.DEFAULT);
            } else {
                // Fallback to the default customization options. May happen if
                // called from a non-party contract (e.g. a crowdfund contract,
                // although this shouldn't happen).
                return (false, Color.DEFAULT);
            }
        }

        // Get the customization data for the preset chosen.
        bytes memory customizationData = _storage.customizationPresets(presetId);
        if (customizationData.length == 0) {
            // If the customization preset doesn't exist, fallback to the
            // default customization options.
            return (false, Color.DEFAULT);
        }

        // Check version number. Fallback to default if using different version.
        if (abi.decode(customizationData, (uint8)) == 1) {
            (, isDarkMode, color) = abi.decode(customizationData, (uint8, bool, Color));
        } else {
            // Fallback to the default customization options.
            return (false, Color.DEFAULT);
        }
    }
}

File 20 of 43 : RendererStorage.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "solmate/utils/SSTORE2.sol";
import "../utils/Multicall.sol";

contract RendererStorage is Multicall {
    error AlreadySetError();
    error NotOwnerError(address caller, address owner);

    event OwnershipTransferred(address previousOwner, address newOwner);

    uint256 constant CROWDFUND_CARD_DATA = 0;
    uint256 constant PARTY_CARD_DATA = 1;

    /// @notice Address allowed to store new data.
    address public owner;

    /// @notice Customization presets by ID, used for rendering cards. Begins at
    ///         1, 0 is reserved to indicate in `getPresetFor()` that a
    ///         party instance use the preset set by the crowdfund instance that
    ///         created it.
    mapping(uint256 => bytes) public customizationPresets;
    /// @notice Customization preset used by a crowdfund or party instance.
    mapping(address => uint256) public getPresetFor;
    /// @notice Addresses where URI data chunks are stored.
    mapping(uint256 => address) public files;

    modifier onlyOwner() {
        address owner_ = owner;
        if (msg.sender != owner_) {
            revert NotOwnerError(msg.sender, owner_);
        }

        _;
    }

    constructor(address _owner) {
        // Set the address allowed to write new data.
        owner = _owner;

        // Write URI data used by V1 of the renderers:

        files[CROWDFUND_CARD_DATA] = SSTORE2.write(
            bytes(
                '<path class="o" d="M118.4 419.5h5.82v1.73h-4.02v1.87h3.74v1.73h-3.74v1.94h4.11v1.73h-5.91v-9Zm9.93 1.76h-2.6v-1.76h7.06v1.76h-2.61v7.24h-1.85v-7.24Zm6.06-1.76h1.84v3.55h3.93v-3.55H142v9h-1.84v-3.67h-3.93v3.67h-1.84v-9Z"/><path class="o" d="M145 413a4 4 0 0 1 4 4v14a4 4 0 0 1-4 4H35a4 4 0 0 1-4-4v-14a4 4 0 0 1 4-4h110m0-1H35a5 5 0 0 0-5 5v14a5 5 0 0 0 5 5h110a5 5 0 0 0 5-5v-14a5 5 0 0 0-5-5Z"/><path d="M239.24 399.83h3.04c1.7 0 2.82 1 2.82 2.55 0 2.1-1.27 3.32-3.57 3.32h-1.97l-.71 3.3h-1.56l1.96-9.17Zm2.34 4.38c1.23 0 1.88-.58 1.88-1.68 0-.73-.49-1.2-1.48-1.2h-1.51l-.6 2.88h1.7Zm3.57 1.86c0-2.27 1.44-3.83 3.57-3.83 1.82 0 3.06 1.25 3.06 3.09 0 2.28-1.43 3.83-3.57 3.83-1.82 0-3.06-1.25-3.06-3.09Zm3.13 1.74c1.19 0 1.93-1.02 1.93-2.52 0-1.06-.62-1.69-1.56-1.69-1.19 0-1.93 1.02-1.93 2.52 0 1.06.62 1.69 1.56 1.69Zm4.74-5.41h1.49l.28 4.73 2.25-4.73h1.64l.23 4.77 2.25-4.77h1.56l-3.3 6.61h-1.62l-.25-5.04-2.42 5.04h-1.63l-.48-6.61Zm9.54 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm3.46-2.59h1.55l-.28 1.28c.81-1.7 2.56-1.36 2.77-1.29l-.35 1.46c-.18-.06-2.3-.63-2.82 1.68l-.74 3.48h-1.55l1.42-6.61Zm3.91 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm2.25 1.36c0-2.44 1.36-4.1 3.26-4.1 1 0 1.76.53 2.05 1.31l.79-3.72h1.55l-1.96 9.17h-1.55l.2-.92a2.15 2.15 0 0 1-1.92 1.08c-1.49 0-2.43-1.18-2.43-2.82Zm3 1.51c.88 0 1.51-.58 1.73-1.56l.17-.81c.24-1.1-.31-1.93-1.36-1.93-1.19 0-1.94 1.08-1.94 2.59 0 1.06.55 1.71 1.4 1.71Zm9.6-.01-.25 1.16h-1.55l1.96-9.17h1.55l-.73 3.47a2.35 2.35 0 0 1 1.99-1.05c1.49 0 2.35 1.16 2.35 2.76 0 2.52-1.36 4.16-3.21 4.16-.98 0-1.81-.53-2.1-1.32Zm1.83.01c1.16 0 1.87-1.06 1.87-2.61 0-1.04-.5-1.69-1.39-1.69s-1.52.56-1.73 1.55l-.17.79c-.24 1.14.34 1.97 1.42 1.97Zm5.68 1.16-1.04-6.62h1.52l.66 4.75 2.66-4.75h1.69l-5.31 9.13h-1.73l1.55-2.51Zm23.48-6.8a42.14 42.14 0 0 0-.75 6.01 43.12 43.12 0 0 0 5.58 2.35 42.54 42.54 0 0 0 5.58-2.35 45.32 45.32 0 0 0-.75-6.01c-.91-.79-2.6-2.21-4.83-3.66a42.5 42.5 0 0 0-4.83 3.66Zm13.07-7.95s.82-.29 1.76-.45a14.9 14.9 0 0 0-9.53-3.81c.66.71 1.28 1.67 1.84 2.75 1.84.22 4.07.7 5.92 1.51Zm-2.71 18.36c-2.06-.4-4.05-.97-5.53-1.51a38.65 38.65 0 0 1-5.53 1.51c.12 1.5.35 3.04.76 4.58 0 0 1.54 1.82 4.78 2.8 3.23-.98 4.78-2.8 4.78-2.8.4-1.53.64-3.08.76-4.58Zm-13.77-18.37a22.3 22.3 0 0 1 5.93-1.51 12.4 12.4 0 0 1 1.84-2.75 14.97 14.97 0 0 0-9.53 3.81c.95.16 1.76.45 1.76.45Zm-4.72 8.77a25.74 25.74 0 0 0 3.58 2.94 37.48 37.48 0 0 1 4.08-4.04c.27-1.56.77-3.57 1.46-5.55a25.24 25.24 0 0 0-4.34-1.63s-2.35.42-4.81 2.74c-.77 3.29.04 5.54.04 5.54Zm25.92 0s.81-2.25.04-5.54c-2.46-2.31-4.81-2.74-4.81-2.74-1.53.42-2.99.99-4.34 1.63a37.79 37.79 0 0 1 1.46 5.55 37.44 37.44 0 0 1 4.08 4.04 25.86 25.86 0 0 0 3.58-2.94Zm-26.38.2s-.66-.56-1.27-1.3c-.7 3.34-.27 6.93 1.46 10.16.28-.93.8-1.94 1.46-2.97a22.32 22.32 0 0 1-1.66-5.88Zm8.24 14.27a22.07 22.07 0 0 1-4.27-4.38c-1.22.06-2.36 0-3.3-.22a14.91 14.91 0 0 0 8.07 6.34c-.34-.9-.5-1.75-.5-1.75Zm18.6-14.27s.66-.56 1.27-1.3c.7 3.34.27 6.93-1.46 10.16-.28-.93-.8-1.94-1.46-2.97a22.32 22.32 0 0 0 1.66-5.88Zm-8.24 14.27a22.07 22.07 0 0 0 4.27-4.38c1.22.06 2.36 0 3.3-.22a14.91 14.91 0 0 1-8.07 6.34c.34-.9.5-1.75.5-1.75ZM330 391.84l-4.12 2.45 1.26 3.91h5.72l1.26-3.91-4.12-2.45Zm-11.4 19.74 4.18 2.35 2.75-3.05-2.86-4.95-4.02.86-.06 4.79Zm22.79 0-.06-4.79-4.02-.86-2.86 4.95 2.75 3.05 4.18-2.35Z" style="fill:#00c1fa"/><use height="300" transform="matrix(1 0 0 .09 29.85 444)" width="300.15" xlink:href="#a"/><use height="21.15" transform="translate(30 446.92)" width="300" xlink:href="#b"/><g><path d="m191.54 428.67-28.09-24.34A29.98 29.98 0 0 0 143.8 397H30a15 15 0 0 0-15 15v98a15 15 0 0 0 15 15h300a15 15 0 0 0 15-15v-59a15 15 0 0 0-15-15H211.19a30 30 0 0 1-19.65-7.33Z" style="fill:url(#i)"/></g></svg>'
            )
        );

        files[PARTY_CARD_DATA] = SSTORE2.write(
            bytes(
                ' d="M188 444.3h2.4l2.6 8.2 2.7-8.2h2.3l-3.7 10.7h-2.8l-3.5-10.7zm10.5 5.3c0-3.2 2.2-5.6 5.3-5.6 3.1 0 5.3 2.3 5.3 5.6 0 3.2-2.2 5.5-5.3 5.5-3.1.1-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.4 0-2.1-1.1-3.5-3-3.5s-3 1.3-3 3.5c0 2.1 1.1 3.4 3 3.4zm8.7-6.7h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm6.9-2.1h2.2V455h-2.2v-10.7zm4.3 0h2.9l4 8.2v-8.2h2.1V455h-2.9l-4-8.2v8.2h-2.1v-10.7zm10.6 5.4c0-3.4 2.3-5.6 6-5.6 1.2 0 2.3.2 3.1.6v2.3c-.9-.6-1.9-.8-3.1-.8-2.4 0-3.8 1.3-3.8 3.5 0 2.1 1.3 3.4 3.5 3.4.5 0 .9-.1 1.3-.2v-2.2h-2.2v-1.9h4.3v5.6c-1 .5-2.2.8-3.4.8-3.5 0-5.7-2.2-5.7-5.5zm15.1-5.4h4.3c2.3 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7h-2.2v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.5-1.5-1.6-1.5h-1.9v2.9h1.9zm4.8.3c0-3.2 2.2-5.6 5.3-5.6 3.1 0 5.3 2.3 5.3 5.6 0 3.2-2.2 5.5-5.3 5.5-3.1.1-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.4 0-2.1-1.1-3.5-3-3.5s-3 1.3-3 3.5c0 2.1 1.1 3.4 3 3.4zm5.8-8.8h2.3l1.7 7.8 1.9-7.8h2.4l1.8 7.8 1.8-7.8h2.3l-2.7 10.7h-2.5l-1.9-8.2-1.8 8.2h-2.5l-2.8-10.7zm15.4 0h6.9v2.1H287v2.2h4.5v2.1H287v2.3h4.9v2.1h-7v-10.8zm9 0h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4h-2.1v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4 0-.9-.6-1.4-1.5-1.4h-2v2.9h2zM30 444.3h4.3c3 0 5.2 2.1 5.2 5.4s-2.1 5.4-5.2 5.4H30v-10.8zm4 8.6c2.1 0 3.2-1.2 3.2-3.2s-1.2-3.3-3.2-3.3h-1.8v6.5H34zm7.7-8.6h2.2V455h-2.2v-10.7zm4.8 10V452c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm12-7.9h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm7.5-2.1h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4H66v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4s-.6-1.4-1.5-1.4h-2v2.9h2zm6.1-4.8h2.2V455h-2.2v-10.7zm5 0h4.5c2 0 3.2 1.1 3.2 2.8 0 1.1-.5 1.9-1.4 2.3 1.1.3 1.8 1.3 1.8 2.5 0 1.9-1.3 3.1-3.5 3.1h-4.6v-10.7zm4.2 4.4c.9 0 1.4-.5 1.4-1.3s-.5-1.3-1.4-1.3h-2.1v2.5l2.1.1zm.3 4.4c.9 0 1.5-.5 1.5-1.3s-.6-1.3-1.5-1.3h-2.4v2.6h2.4zm5.7-2.5v-6.3h2.2v6.3c0 1.6.9 2.5 2.3 2.5s2.3-.9 2.3-2.5v-6.3h2.2v6.3c0 2.9-1.7 4.6-4.5 4.6s-4.6-1.7-4.5-4.6zm14.2-4.2h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm7.5-2.1h2.2V455h-2.2v-10.7zm4.5 5.3c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.5-8.8h2.9l4 8.2v-8.2h2.1V455h-2.9l-4-8.2v8.2h-2.1v-10.7zm11.7 10V452c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.6-1.5-2.6-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4a9.7 9.7 0 0 1-3.4-1.1zM30 259.3h4.3c2.2 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7H30v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.5-1.5-1.6-1.5h-1.9v2.9h1.9zm6.1-5h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4h-2.1v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4s-.6-1.4-1.5-1.4h-2v2.9h2zm5.4.5c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.6-8.8h4.3c2.2 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7h-2.2v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.6-1.5-1.6-1.5h-1.9v2.9h1.9zm5.4.4c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.3-5.3-5.5zm5.4 3.4c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.2 1.2V267c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm12.2-10h2.8l3.7 10.7h-2.3l-.8-2.5h-4l-.8 2.5h-2.2l3.6-10.7zm2.8 6.3-1.4-4.2-1.4 4.2h2.8zm5.7-6.3h2.2v8.6h4.7v2.1h-6.9v-10.7zm9.1 10V267c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm-84.5-70h2.9l4 8.2v-8.2H39V210h-2.9l-4-8.2v8.2H30v-10.7zm14.7 0h2.8l3.7 10.7h-2.3l-.8-2.6h-4l-.8 2.6H41l3.7-10.7zm2.8 6.2-1.4-4.2-1.4 4.2h2.8zm5.7-6.2h3.3l2.5 8.2 2.5-8.2h3.3V210h-2v-8.6L60 210h-2.1l-2.7-8.5v8.5h-2v-10.7zm14.4 0h6.9v2.1h-4.8v2.2h4.4v2.1h-4.4v2.3h4.9v2.1h-7v-10.8z" /><path d="M239.24 24.83h3.04c1.7 0 2.82 1 2.82 2.55 0 2.1-1.27 3.32-3.57 3.32h-1.97l-.71 3.3h-1.56l1.96-9.17Zm2.34 4.38c1.23 0 1.88-.58 1.88-1.68 0-.73-.49-1.2-1.48-1.2h-1.51l-.6 2.88h1.7Zm3.57 1.86c0-2.27 1.44-3.83 3.57-3.83 1.82 0 3.06 1.25 3.06 3.09 0 2.28-1.43 3.83-3.57 3.83-1.82 0-3.06-1.25-3.06-3.09Zm3.13 1.74c1.19 0 1.93-1.02 1.93-2.52 0-1.06-.62-1.69-1.56-1.69-1.19 0-1.93 1.02-1.93 2.52 0 1.06.62 1.69 1.56 1.69Zm4.74-5.41h1.49l.28 4.73 2.25-4.73h1.64l.23 4.77 2.25-4.77h1.56l-3.3 6.61h-1.62l-.25-5.04-2.42 5.04h-1.63l-.48-6.61Zm9.54 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm3.46-2.59h1.55l-.28 1.28c.81-1.7 2.56-1.36 2.77-1.29l-.35 1.46c-.18-.06-2.3-.63-2.82 1.68l-.74 3.48h-1.55l1.42-6.61Zm3.91 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm2.25 1.36c0-2.44 1.36-4.1 3.26-4.1 1 0 1.76.53 2.05 1.31l.79-3.72h1.55l-1.96 9.17h-1.55l.2-.92a2.15 2.15 0 0 1-1.92 1.08c-1.49 0-2.43-1.18-2.43-2.82Zm3 1.51c.88 0 1.51-.58 1.73-1.56l.17-.81c.24-1.1-.31-1.93-1.36-1.93-1.19 0-1.94 1.08-1.94 2.59 0 1.06.55 1.71 1.4 1.71Zm9.6-.01-.25 1.16h-1.55l1.96-9.17h1.55l-.73 3.47a2.35 2.35 0 0 1 1.99-1.05c1.49 0 2.35 1.16 2.35 2.76 0 2.52-1.36 4.16-3.21 4.16-.98 0-1.81-.53-2.1-1.32Zm1.83.01c1.16 0 1.87-1.06 1.87-2.61 0-1.04-.5-1.69-1.39-1.69s-1.52.56-1.73 1.55l-.17.79c-.24 1.14.34 1.97 1.42 1.97Zm5.68 1.16-1.04-6.62h1.52l.66 4.75 2.66-4.75h1.69l-5.31 9.13h-1.73l1.55-2.51Zm23.47-6.8c.91-.79 2.6-2.21 4.83-3.66a42.5 42.5 0 0 1 4.83 3.66c.23 1.18.62 3.36.75 6.01a43.12 43.12 0 0 1-5.58 2.35 42.54 42.54 0 0 1-5.58-2.35c.14-2.65.53-4.83.75-6.01Zm13.07-7.95s.82-.29 1.76-.45a14.9 14.9 0 0 0-9.53-3.81c.66.71 1.28 1.67 1.84 2.75 1.84.22 4.07.7 5.92 1.51Zm-2.71 18.36c-2.06-.4-4.05-.97-5.53-1.51a38.65 38.65 0 0 1-5.53 1.51c.12 1.5.35 3.04.76 4.58 0 0 1.54 1.82 4.78 2.8 3.23-.98 4.78-2.8 4.78-2.8.4-1.53.64-3.08.76-4.58Zm-13.77-18.37a22.3 22.3 0 0 1 5.93-1.51 12.4 12.4 0 0 1 1.84-2.75 14.97 14.97 0 0 0-9.53 3.81c.95.16 1.76.45 1.76.45Zm-4.72 8.77a25.74 25.74 0 0 0 3.58 2.94 37.48 37.48 0 0 1 4.08-4.04c.27-1.56.77-3.57 1.46-5.55a25.24 25.24 0 0 0-4.34-1.63s-2.35.42-4.81 2.74c-.77 3.29.04 5.54.04 5.54Zm25.92 0s.81-2.25.04-5.54c-2.46-2.31-4.81-2.74-4.81-2.74-1.53.42-2.99.99-4.34 1.63a37.79 37.79 0 0 1 1.46 5.55 37.44 37.44 0 0 1 4.08 4.04 25.86 25.86 0 0 0 3.58-2.94Zm-26.38.2s-.66-.56-1.27-1.3c-.7 3.34-.27 6.93 1.46 10.16.28-.93.8-1.94 1.46-2.97a22.32 22.32 0 0 1-1.66-5.88Zm8.24 14.27a22.07 22.07 0 0 1-4.27-4.38c-1.22.06-2.36 0-3.3-.22a14.91 14.91 0 0 0 8.07 6.34c-.34-.9-.5-1.75-.5-1.75Zm18.6-14.27s.66-.56 1.27-1.3c.7 3.34.27 6.93-1.46 10.16-.28-.93-.8-1.94-1.46-2.97a22.32 22.32 0 0 0 1.66-5.88Zm-8.24 14.27a22.07 22.07 0 0 0 4.27-4.38c1.22.06 2.36 0 3.3-.22a14.91 14.91 0 0 1-8.07 6.34c.34-.9.5-1.75.5-1.75Zm-5.18-25.66-4.12 2.45 1.26 3.91h5.72l1.26-3.91-4.12-2.45Zm-11.4 19.74 4.18 2.35 2.75-3.05-2.86-4.95-4.02.86-.06 4.79Zm22.79 0-.06-4.79-4.02-.86-2.86 4.95 2.75 3.05 4.18-2.35Z" style="fill:#00c1fa"/><path d="M106.67 109.1a304.9 304.9 0 0 0-3.72-10.89c5.04-5.53 35.28-40.74 24.54-68.91 10.57 10.67 8.19 28.85 3.59 41.95-4.79 13.14-13.43 26.48-24.4 37.84Zm30.89 20.82c-5.87 6.12-20.46 17.92-21.67 18.77a99.37 99.37 0 0 0 7.94 6.02 133.26 133.26 0 0 0 20.09-18.48 353.47 353.47 0 0 0-6.36-6.31Zm-29.65-16.74a380.9 380.9 0 0 1 3.13 11.56c-4.8-1.37-8.66-2.53-12.36-3.82a123.4 123.4 0 0 1-21.16 13.21l15.84 5.47c14.83-8.23 28.13-20.82 37.81-34.68 0 0 8.56-12.55 12.42-23.68 2.62-7.48 4.46-16.57 3.49-24.89-2.21-12.27-6.95-15.84-9.32-17.66 6.16 5.72 3.25 27.8-2.79 39.89-6.08 12.16-15.73 24.27-27.05 34.59Zm59.05-37.86c-.03 7.72-3.05 15.69-6.44 22.69 1.7 2.2 3.18 4.36 4.42 6.49 7.97-16.51 3.74-26.67 2.02-29.18ZM61.18 128.51l12.5 4.3a101.45 101.45 0 0 0 21.42-13.19 163.26 163.26 0 0 1-10.61-4.51 101.28 101.28 0 0 1-23.3 13.4Zm87.78-42.73c.86.77 5.44 5.18 6.75 6.59 6.39-16.61.78-28.86-1.27-30.56.72 8.05-2.02 16.51-5.48 23.98Zm-14.29 40.62-2.47-15.18a142.42 142.42 0 0 1-35.74 29.45c6.81 2.36 12.69 4.4 15.45 5.38a115.98 115.98 0 0 0 22.75-19.66Zm-42.62 34.73c4.48 2.93 12.94 4.24 18.8 1.23 6.03-3.84-.6-8.34-8.01-9.88-9.8-2.03-16.82 1.22-13.4 6.21.41.6 1.19 1.5 2.62 2.44m-1.84.4c-3.56-2.37-6.77-7.2-.23-10.08 10.41-3.43 28.39 3.2 24.99 9.22-.58 1.04-1.46 1.6-2.38 2.19h-.03v.02h-.03v.02h-.03c-7.04 3.65-17.06 2.13-22.3-1.36m5.48-3.86a4.94 4.94 0 0 0 5.06.49l1.35-.74-4.68-2.38-1.47.79c-.38.22-1.53.88-.26 1.84m-1.7.59c-2.35-1.57-.78-2.61-.02-3.11 1.09-.57 2.19-1.15 3.28-1.77 6.95 3.67 7.22 3.81 13.19 6.17l-1.38.81c-1.93-.78-4.52-1.82-6.42-2.68.86 1.4 1.99 3.27 2.9 4.64l-1.68.87c-.75-1.28-1.76-2.99-2.47-4.29-3.19 2.06-6.99-.36-7.42-.64" style="fill:url(#f2)"/><path d="M159.13 52.37C143.51 24.04 119.45 15 103.6 15c-11.92 0-25.97 5.78-36.84 13.17 9.54 4.38 21.86 15.96 22.02 16.11-7.94-3.05-17.83-6.72-33.23-7.87a135.1 135.1 0 0 0-19.77 20.38c.77 7.66 2.88 15.68 2.88 15.68-6.28-4.75-11.02-4.61-18 9.45-5.4 12.66-6.93 24.25-4.65 33.18 0 0 4.72 26.8 36.23 40.07-1.3-4.61-1.58-9.91-.93-15.73a87.96 87.96 0 0 1-15.63-9.87c.79-6.61 2.79-13.82 6-21.36 4.42-10.66 4.35-15.14 4.35-15.19.03.07 5.48 12.43 12.95 22.08 4.23-8.84 9.46-16.08 13.67-21.83l-3.77-6.75a143.73 143.73 0 0 1 18.19-18.75c2.05 1.07 4.79 2.47 6.84 3.58 8.68-7.27 19.25-14.05 30.56-18.29-7-11.49-16.02-19.27-16.02-19.27s27.7 2.74 42.02 15.69a25.8 25.8 0 0 1 8.65 2.89ZM28.58 107.52a70.1 70.1 0 0 0-2.74 12.52 55.65 55.65 0 0 1-6.19-8.84 69.17 69.17 0 0 1 2.65-12.1c1.77-5.31 3.35-5.91 5.86-2.23v-.05c2.14 3.07 1.81 6.14.42 10.7ZM61.69 72.2l-.05.05a221.85 221.85 0 0 1-7.77-18.1l.14-.14a194.51 194.51 0 0 1 18.56 6.98 144.44 144.44 0 0 0-10.88 11.22Zm54.84-47.38c-4.42.7-9.02 1.95-13.67 3.72a65.03 65.03 0 0 0-7.81-5.31 66.04 66.04 0 0 1 13.02-3.54c1.53-.19 6.23-.79 10.32 2.42v-.05c2.47 1.91.14 2.37-1.86 2.75Z" style="fill:url(#h)"/>'
            )
        );
    }

    /// @notice Transfer ownership to a new owner.
    /// @param newOwner The address to transfer ownership to.
    function transferOwnership(address newOwner) external onlyOwner {
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /// @notice Write data to be accessed by a given file key.
    /// @param key The key to access the written data.
    /// @param data The data to be written.
    function writeFile(uint256 key, string memory data) external onlyOwner {
        files[key] = SSTORE2.write(bytes(data));
    }

    /// @notice Read data using a given file key.
    /// @param key The key to access the stored data.
    /// @return data The data stored at the given key.
    function readFile(uint256 key) external view returns (string memory data) {
        return string(SSTORE2.read(files[key]));
    }

    /// @notice Create or set a customization preset for renderers to use.
    /// @param id The ID of the customization preset.
    /// @param customizationData Data decoded by renderers used to render the SVG according to the preset.
    function createCustomizationPreset(
        uint256 id,
        bytes memory customizationData
    ) external onlyOwner {
        customizationPresets[id] = customizationData;
    }

    /// @notice For crowdfund or party instances to set the customization preset they want to use.
    /// @param id The ID of the customization preset.
    function useCustomizationPreset(uint256 id) external {
        getPresetFor[msg.sender] = id;
    }
}

File 21 of 43 : IMetadataRegistry1_1.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.20;

// Interface for `MetadataRegistry` contract from v1.1 of the protocol.
interface IMetadataRegistry1_1 {
    function customPartyMetadataByCrowdfund(
        address crowdfundAddress
    )
        external
        view
        returns (
            string memory customName,
            string memory customDescription,
            string memory customImage
        );
}

File 22 of 43 : TokenDistributor.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../globals/IGlobals.sol";
import "../globals/LibGlobals.sol";
import "../tokens/IERC20.sol";
import "../utils/LibAddress.sol";
import "../utils/LibERC20Compat.sol";
import "../utils/LibRawResult.sol";
import "../utils/LibSafeCast.sol";

import "./ITokenDistributor.sol";

/// @notice Creates token distributions for parties.
contract TokenDistributor is ITokenDistributor {
    using LibAddress for address payable;
    using LibERC20Compat for IERC20;
    using LibRawResult for bytes;
    using LibSafeCast for uint256;

    struct DistributionState {
        // The hash of the `DistributionInfo`.
        bytes32 distributionHash;
        // The remaining member supply.
        uint128 remainingMemberSupply;
        // Whether the distribution's feeRecipient has claimed its fee.
        bool wasFeeClaimed;
        // Whether a governance token has claimed its distribution share.
        mapping(uint256 => bool) hasPartyTokenClaimed;
    }

    // Arguments for `_createDistribution()`.
    struct CreateDistributionArgs {
        Party party;
        TokenType tokenType;
        address token;
        uint256 currentTokenBalance;
        address payable feeRecipient;
        uint16 feeBps;
    }

    event EmergencyExecute(address target, bytes data);

    error OnlyPartyDaoError(address notDao, address partyDao);
    error InvalidDistributionInfoError(DistributionInfo info);
    error DistributionAlreadyClaimedByPartyTokenError(uint256 distributionId, uint256 partyTokenId);
    error DistributionFeeAlreadyClaimedError(uint256 distributionId);
    error MustOwnTokenError(address sender, address expectedOwner, uint256 partyTokenId);
    error EmergencyActionsNotAllowedError();
    error InvalidDistributionSupplyError(uint128 supply);
    error OnlyFeeRecipientError(address caller, address feeRecipient);
    error InvalidFeeBpsError(uint16 feeBps);

    // Token address used to indicate a native distribution (i.e. distribution of ETH).
    address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /// @notice The `Globals` contract storing global configuration values. This contract
    ///         is immutable and it’s address will never change.
    IGlobals public immutable GLOBALS;
    /// @notice Timestamp when the DAO is no longer allowed to call emergency functions.
    uint40 public immutable EMERGENCY_DISABLED_TIMESTAMP;

    /// @notice Last distribution ID for a party.
    mapping(Party => uint256) public lastDistributionIdPerParty;
    /// Last known balance of a token, identified by an ID derived from the token.
    /// Gets lazily updated when creating and claiming a distribution (transfers).
    /// Allows one to simply transfer and call `createDistribution()` without
    /// fussing with allowances.
    mapping(bytes32 => uint256) private _storedBalances;
    // tokenDistributorParty => distributionId => DistributionState
    mapping(Party => mapping(uint256 => DistributionState)) private _distributionStates;

    // msg.sender == DAO
    modifier onlyPartyDao() {
        {
            address partyDao = GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
            if (msg.sender != partyDao) {
                revert OnlyPartyDaoError(msg.sender, partyDao);
            }
        }
        _;
    }

    // emergencyActionsDisabled == false
    modifier onlyIfEmergencyActionsAllowed() {
        if (block.timestamp > EMERGENCY_DISABLED_TIMESTAMP) {
            revert EmergencyActionsNotAllowedError();
        }
        _;
    }

    // Set the `Globals` contract.
    constructor(IGlobals globals, uint40 emergencyDisabledTimestamp) {
        GLOBALS = globals;
        EMERGENCY_DISABLED_TIMESTAMP = emergencyDisabledTimestamp;
    }

    /// @inheritdoc ITokenDistributor
    function createNativeDistribution(
        Party party,
        address payable feeRecipient,
        uint16 feeBps
    ) external payable returns (DistributionInfo memory info) {
        info = _createDistribution(
            CreateDistributionArgs({
                party: party,
                tokenType: TokenType.Native,
                token: NATIVE_TOKEN_ADDRESS,
                currentTokenBalance: address(this).balance,
                feeRecipient: feeRecipient,
                feeBps: feeBps
            })
        );
    }

    /// @inheritdoc ITokenDistributor
    function createErc20Distribution(
        IERC20 token,
        Party party,
        address payable feeRecipient,
        uint16 feeBps
    ) external returns (DistributionInfo memory info) {
        info = _createDistribution(
            CreateDistributionArgs({
                party: party,
                tokenType: TokenType.Erc20,
                token: address(token),
                currentTokenBalance: token.balanceOf(address(this)),
                feeRecipient: feeRecipient,
                feeBps: feeBps
            })
        );
    }

    /// @inheritdoc ITokenDistributor
    function claim(
        DistributionInfo calldata info,
        uint256 partyTokenId
    ) public returns (uint128 amountClaimed) {
        // Caller must own the party token.
        {
            address ownerOfPartyToken = info.party.ownerOf(partyTokenId);
            if (msg.sender != ownerOfPartyToken) {
                revert MustOwnTokenError(msg.sender, ownerOfPartyToken, partyTokenId);
            }
        }
        // DistributionInfo must be correct for this distribution ID.
        DistributionState storage state = _distributionStates[info.party][info.distributionId];
        if (state.distributionHash != _getDistributionHash(info)) {
            revert InvalidDistributionInfoError(info);
        }
        // The partyTokenId must not have claimed its distribution yet.
        if (state.hasPartyTokenClaimed[partyTokenId]) {
            revert DistributionAlreadyClaimedByPartyTokenError(info.distributionId, partyTokenId);
        }
        // Mark the partyTokenId as having claimed their distribution.
        state.hasPartyTokenClaimed[partyTokenId] = true;

        // Compute amount owed to partyTokenId.
        amountClaimed = getClaimAmount(info, partyTokenId);

        // Cap at the remaining member supply. Otherwise a malicious
        // party could drain more than the distribution supply.
        uint128 remainingMemberSupply = state.remainingMemberSupply;
        amountClaimed = amountClaimed > remainingMemberSupply
            ? remainingMemberSupply
            : amountClaimed;
        state.remainingMemberSupply = remainingMemberSupply - amountClaimed;

        // Transfer tokens owed.
        _transfer(info.tokenType, info.token, payable(msg.sender), amountClaimed);
        emit DistributionClaimedByPartyToken(
            info.party,
            partyTokenId,
            msg.sender,
            info.tokenType,
            info.token,
            amountClaimed
        );
    }

    /// @inheritdoc ITokenDistributor
    function claimFee(DistributionInfo calldata info, address payable recipient) public {
        // DistributionInfo must be correct for this distribution ID.
        DistributionState storage state = _distributionStates[info.party][info.distributionId];
        if (state.distributionHash != _getDistributionHash(info)) {
            revert InvalidDistributionInfoError(info);
        }
        // Caller must be the fee recipient.
        if (info.feeRecipient != msg.sender) {
            revert OnlyFeeRecipientError(msg.sender, info.feeRecipient);
        }
        // Must not have claimed the fee yet.
        if (state.wasFeeClaimed) {
            revert DistributionFeeAlreadyClaimedError(info.distributionId);
        }
        // Mark the fee as claimed.
        state.wasFeeClaimed = true;
        // Transfer the tokens owed.
        _transfer(info.tokenType, info.token, recipient, info.fee);
        emit DistributionFeeClaimed(
            info.party,
            info.feeRecipient,
            info.tokenType,
            info.token,
            info.fee
        );
    }

    /// @inheritdoc ITokenDistributor
    function batchClaim(
        DistributionInfo[] calldata infos,
        uint256[] calldata partyTokenIds
    ) external returns (uint128[] memory amountsClaimed) {
        amountsClaimed = new uint128[](infos.length);
        for (uint256 i = 0; i < infos.length; ++i) {
            amountsClaimed[i] = claim(infos[i], partyTokenIds[i]);
        }
    }

    /// @inheritdoc ITokenDistributor
    function batchClaimFee(
        DistributionInfo[] calldata infos,
        address payable[] calldata recipients
    ) external {
        for (uint256 i = 0; i < infos.length; ++i) {
            claimFee(infos[i], recipients[i]);
        }
    }

    /// @inheritdoc ITokenDistributor
    function getClaimAmount(
        DistributionInfo calldata info,
        uint256 partyTokenId
    ) public view returns (uint128) {
        Party party = info.party;

        // Check which method to use for calculating claim amount based on
        // version of Party contract.
        (bool success, bytes memory response) = address(party).staticcall(
            abi.encodeCall(party.VERSION_ID, ())
        );

        // Check the version ID.
        if (success && abi.decode(response, (uint16)) >= 1) {
            uint256 shareOfSupply = ((info.party.getDistributionShareOf(partyTokenId)) * 1e18) /
                info.totalShares;

            return
                // We round up here to prevent dust amounts getting trapped in this contract.
                ((shareOfSupply * info.memberSupply + (1e18 - 1)) / 1e18)
                    .safeCastUint256ToUint128();
        } else {
            // Use method of calculating claim amount for backwards
            // compatibility with older parties where getDistributionShareOf()
            // returned the fraction of the memberSupply partyTokenId is
            // entitled to, scaled by 1e18.
            uint256 shareOfSupply = party.getDistributionShareOf(partyTokenId);

            return
                // We round up here to prevent dust amounts getting trapped in this contract.
                ((shareOfSupply * info.memberSupply + (1e18 - 1)) / 1e18)
                    .safeCastUint256ToUint128();
        }
    }

    /// @inheritdoc ITokenDistributor
    function wasFeeClaimed(Party party, uint256 distributionId) external view returns (bool) {
        return _distributionStates[party][distributionId].wasFeeClaimed;
    }

    /// @inheritdoc ITokenDistributor
    function hasPartyTokenIdClaimed(
        Party party,
        uint256 partyTokenId,
        uint256 distributionId
    ) external view returns (bool) {
        return _distributionStates[party][distributionId].hasPartyTokenClaimed[partyTokenId];
    }

    /// @inheritdoc ITokenDistributor
    function getRemainingMemberSupply(
        Party party,
        uint256 distributionId
    ) external view returns (uint128) {
        return _distributionStates[party][distributionId].remainingMemberSupply;
    }

    /// @notice As the DAO, execute an arbitrary delegatecall from this contract.
    /// @dev Emergency actions must not be revoked for this to work.
    /// @param targetAddress The contract to delegatecall into.
    /// @param targetCallData The data to pass to the call.
    function emergencyExecute(
        address targetAddress,
        bytes calldata targetCallData
    ) external onlyPartyDao onlyIfEmergencyActionsAllowed {
        (bool success, bytes memory res) = targetAddress.delegatecall(targetCallData);
        if (!success) {
            res.rawRevert();
        }
        emit EmergencyExecute(targetAddress, targetCallData);
    }

    function _createDistribution(
        CreateDistributionArgs memory args
    ) private returns (DistributionInfo memory info) {
        if (args.feeBps > 1e4) {
            revert InvalidFeeBpsError(args.feeBps);
        }
        uint128 supply;
        {
            bytes32 balanceId = _getBalanceId(args.tokenType, args.token);
            supply = (args.currentTokenBalance - _storedBalances[balanceId])
                .safeCastUint256ToUint128();
            // Supply must be nonzero.
            if (supply == 0) {
                revert InvalidDistributionSupplyError(supply);
            }
            // Update stored balance.
            _storedBalances[balanceId] = args.currentTokenBalance;
        }

        // Create a distribution.
        uint128 fee = (supply * args.feeBps) / 1e4;
        uint128 memberSupply = supply - fee;

        info = DistributionInfo({
            tokenType: args.tokenType,
            distributionId: ++lastDistributionIdPerParty[args.party],
            token: args.token,
            party: args.party,
            memberSupply: memberSupply,
            feeRecipient: args.feeRecipient,
            fee: fee,
            totalShares: args.party.getGovernanceValues().totalVotingPower
        });
        (
            _distributionStates[args.party][info.distributionId].distributionHash,
            _distributionStates[args.party][info.distributionId].remainingMemberSupply
        ) = (_getDistributionHash(info), memberSupply);
        emit DistributionCreated(args.party, info);
    }

    function _transfer(
        TokenType tokenType,
        address token,
        address payable recipient,
        uint256 amount
    ) private {
        bytes32 balanceId = _getBalanceId(tokenType, token);
        // Reduce stored token balance.
        uint256 storedBalance = _storedBalances[balanceId] - amount;
        // Temporarily set to max as a reentrancy guard. An interesing attack
        // could occur if we didn't do this where an attacker could `claim()` and
        // reenter upon transfer (e.g. in the `tokensToSend` hook of an ERC777) to
        // `createERC20Distribution()`. Since the `balanceOf(address(this))`
        // would not of been updated yet, the supply would be miscalculated and
        // the attacker would create a distribution that essentially steals from
        // the last distribution they were claiming from. Here, we prevent that
        // by causing an arithmetic underflow with the supply calculation if
        // this were to be attempted.
        _storedBalances[balanceId] = type(uint256).max;
        if (tokenType == TokenType.Native) {
            recipient.transferEth(amount);
        } else {
            assert(tokenType == TokenType.Erc20);
            IERC20(token).compatTransfer(recipient, amount);
        }
        _storedBalances[balanceId] = storedBalance;
    }

    function _getDistributionHash(
        DistributionInfo memory info
    ) internal pure returns (bytes32 hash) {
        assembly {
            hash := keccak256(info, 0x100)
        }
    }

    function _getBalanceId(
        TokenType tokenType,
        address token
    ) private pure returns (bytes32 balanceId) {
        if (tokenType == TokenType.Native) {
            return bytes32(uint256(uint160(NATIVE_TOKEN_ADDRESS)));
        }
        assert(tokenType == TokenType.Erc20);
        return bytes32(uint256(uint160(token)));
    }
}

File 23 of 43 : IFont.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @title The interface on-chain font contracts must implement to be added to the registry.
///
/// Uploading fonts to chain is open ended and up to the dev (SSTORE2 or hardcoded string or etc).
///
/// As long as the font contract implements this interface and has immutable font data, it can be added
/// to the registry.
///
/// @author @0x_beans
interface IFont {
    /// @notice Address that uploaded font for credits
    function fontUploader() external returns (address);

    /// @notice Format type of font (e.g. ttf, woff, otf, etc). Must be lowercase.
    ///         This info is necessary so projects know how to properly render the fonts.
    function fontFormatType() external returns (string memory);

    /// @notice Font name (ie. 'space-grotesk'). Must be lowercase.
    function fontName() external returns (string memory);

    /// @notice Weight used by the font (e.g. bold, medium, light, etc). Must be lowercase.
    ///         Necessary to differentiate uploaded fonts that are the same but different weights.
    function fontWeight() external returns (string memory);

    /// @notice Style used by the font (e.g. lowercase normal, italic, oblique, etc). Must be lowercase.
    //          Necessary to differentiate uploaded fonts that are the same but different style.
    function fontStyle() external returns (string memory);

    /// @notice The full base64 encoded font with data URI scheme prefix
    ///         (e.g. 'data:font/ttf;charset=utf-8;base64,').
    function getFont() external view returns (string memory);
}

File 24 of 43 : LibAddress.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

library LibAddress {
    error EthTransferFailed(address receiver, bytes errData);

    // Transfer ETH with full gas stipend.
    function transferEth(address payable receiver, uint256 amount) internal {
        if (amount == 0) return;

        (bool s, bytes memory r) = receiver.call{ value: amount }("");
        if (!s) {
            revert EthTransferFailed(receiver, r);
        }
    }
}

File 25 of 43 : IERC2981.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol)

pragma solidity ^0.8.19;

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

/**
 * @dev Interface for the NFT Royalty Standard.
 *
 * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
 * support for royalty payments across all NFT marketplaces and ecosystem participants.
 *
 * _Available since v4.5._
 */
interface IERC2981 is IERC165 {
    /**
     * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
     * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
     */
    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) external view returns (address receiver, uint256 royaltyAmount);
}

File 26 of 43 : ERC721.sol
// SPDX-License-Identifier: AGPL-3.0-only
// Based on solmate commit 1681dc505f4897ef636f0435d01b1aa027fdafaf (v6.4.0)
//  @ https://github.com/Rari-Capital/solmate/blob/1681dc505f4897ef636f0435d01b1aa027fdafaf/src/tokens/ERC1155.sol
// Only modified to inherit IERC721 and EIP165.
pragma solidity >=0.8.0;

// NOTE: Only modified to inherit IERC20 and EIP165
import "../../tokens/IERC721.sol";
import "../../utils/EIP165.sol";

/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 is IERC721, EIP165 {
    /*//////////////////////////////////////////////////////////////
                         METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id /* view */) public virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                      ERC721 BALANCE/OWNER STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) internal _ownerOf;

    mapping(address => uint256) internal _balanceOf;

    function ownerOf(uint256 id) public view virtual returns (address owner) {
        require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return _balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = _ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(address from, address to, uint256 id) public virtual {
        require(from == _ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from ||
                isApprovedForAll[from][msg.sender] ||
                msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            _balanceOf[from]--;

            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(address from, address to, uint256 id) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
        // NOTE: modified from original to call super.
        return
            super.supportsInterface(interfaceId) ||
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(_ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = _ownerOf[id];

        require(owner != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            _balanceOf[owner]--;
        }

        delete _ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC721TokenReceiver.onERC721Received.selector;
    }
}

File 27 of 43 : ITokenDistributor.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC20.sol";

import "../party/Party.sol";

/// @notice Creates token distributions for parties.
interface ITokenDistributor {
    enum TokenType {
        Native,
        Erc20
    }

    // Info on a distribution, created by createDistribution().
    struct DistributionInfo {
        // Type of distribution/token.
        TokenType tokenType;
        // ID of the distribution. Assigned by createDistribution().
        uint256 distributionId;
        // The party whose members can claim the distribution.
        Party party;
        // Who can claim `fee`.
        address payable feeRecipient;
        // The token being distributed.
        address token;
        // Total amount of `token` that can be claimed by party members.
        uint128 memberSupply;
        // Amount of `token` to be redeemed by `feeRecipient`.
        uint128 fee;
        // Total shares at time distribution was created.
        uint96 totalShares;
    }

    event DistributionCreated(Party indexed party, DistributionInfo info);
    event DistributionFeeClaimed(
        Party indexed party,
        address indexed feeRecipient,
        TokenType tokenType,
        address token,
        uint256 amount
    );
    event DistributionClaimedByPartyToken(
        Party indexed party,
        uint256 indexed partyTokenId,
        address indexed owner,
        TokenType tokenType,
        address token,
        uint256 amountClaimed
    );

    /// @notice Create a new distribution for an outstanding native token balance
    ///         governed by a party.
    /// @dev Native tokens should be transferred directly into this contract
    ///      immediately prior (same tx) to calling `createDistribution()` or
    ///      attached to the call itself.
    /// @param party The party whose members can claim the distribution.
    /// @param feeRecipient Who can claim `fee`.
    /// @param feeBps Percentage (in bps) of the distribution `feeRecipient` receives.
    /// @return info Information on the created distribution.
    function createNativeDistribution(
        Party party,
        address payable feeRecipient,
        uint16 feeBps
    ) external payable returns (DistributionInfo memory info);

    /// @notice Create a new distribution for an outstanding ERC20 token balance
    ///         governed by a party.
    /// @dev ERC20 tokens should be transferred directly into this contract
    ///      immediately prior (same tx) to calling `createDistribution()` or
    ///      attached to the call itself.
    /// @param token The ERC20 token to distribute.
    /// @param party The party whose members can claim the distribution.
    /// @param feeRecipient Who can claim `fee`.
    /// @param feeBps Percentage (in bps) of the distribution `feeRecipient` receives.
    /// @return info Information on the created distribution.
    function createErc20Distribution(
        IERC20 token,
        Party party,
        address payable feeRecipient,
        uint16 feeBps
    ) external returns (DistributionInfo memory info);

    /// @notice Claim a portion of a distribution owed to a `partyTokenId` belonging
    ///         to the party that created the distribution. The caller
    ///         must own this token.
    /// @param info Information on the distribution being claimed.
    /// @param partyTokenId The ID of the party token to claim for.
    /// @return amountClaimed The amount of the distribution claimed.
    function claim(
        DistributionInfo calldata info,
        uint256 partyTokenId
    ) external returns (uint128 amountClaimed);

    /// @notice Claim the fee for a distribution. Only a distribution's `feeRecipient`
    ///         can call this.
    /// @param info Information on the distribution being claimed.
    /// @param recipient The address to send the fee to.
    function claimFee(DistributionInfo calldata info, address payable recipient) external;

    /// @notice Batch version of `claim()`.
    /// @param infos Information on the distributions being claimed.
    /// @param partyTokenIds The ID of the party tokens to claim for.
    /// @return amountsClaimed The amount of the distributions claimed.
    function batchClaim(
        DistributionInfo[] calldata infos,
        uint256[] calldata partyTokenIds
    ) external returns (uint128[] memory amountsClaimed);

    /// @notice Batch version of `claimFee()`.
    /// @param infos Information on the distributions to claim fees for.
    /// @param recipients The addresses to send the fees to.
    function batchClaimFee(
        DistributionInfo[] calldata infos,
        address payable[] calldata recipients
    ) external;

    /// @notice Compute the amount of a distribution's token are owed to a party
    ///         member, identified by the `partyTokenId`.
    /// @param info Information on the distribution being claimed.
    /// @param partyTokenId The ID of the party token to claim for.
    /// @return claimAmount The amount of the distribution owed to the party member.
    function getClaimAmount(
        DistributionInfo calldata info,
        uint256 partyTokenId
    ) external view returns (uint128);

    /// @notice Check whether the fee has been claimed for a distribution.
    /// @param party The party to use for checking whether the fee has been claimed.
    /// @param distributionId The ID of the distribution to check.
    /// @return feeClaimed Whether the fee has been claimed.
    function wasFeeClaimed(Party party, uint256 distributionId) external view returns (bool);

    /// @notice Check whether a `partyTokenId` has claimed their share of a distribution.
    /// @param party The party to use for checking whether the `partyTokenId` has claimed.
    /// @param partyTokenId The ID of the party token to check.
    /// @param distributionId The ID of the distribution to check.
    /// @return hasClaimed Whether the `partyTokenId` has claimed.
    function hasPartyTokenIdClaimed(
        Party party,
        uint256 partyTokenId,
        uint256 distributionId
    ) external view returns (bool);

    /// @notice Get how much unclaimed member tokens are left in a distribution.
    /// @param party The party to use for checking the unclaimed member tokens.
    /// @param distributionId The ID of the distribution to check.
    /// @return remainingMemberSupply The amount of distribution supply remaining.
    function getRemainingMemberSupply(
        Party party,
        uint256 distributionId
    ) external view returns (uint128);
}

File 28 of 43 : ReadOnlyDelegateCall.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "./LibRawResult.sol";

interface IReadOnlyDelegateCall {
    // Marked `view` so that `_readOnlyDelegateCall` can be `view` as well.
    function delegateCallAndRevert(address impl, bytes memory callData) external view;
}

// Inherited by contracts to perform read-only delegate calls.
abstract contract ReadOnlyDelegateCall {
    using LibRawResult for bytes;

    // Delegatecall into implement and revert with the raw result.
    function delegateCallAndRevert(address impl, bytes memory callData) external {
        // Attempt to gate to only `_readOnlyDelegateCall()` invocations.
        require(msg.sender == address(this));
        (bool s, bytes memory r) = impl.delegatecall(callData);
        // Revert with success status and return data.
        abi.encode(s, r).rawRevert();
    }

    // Perform a `delegateCallAndRevert()` then return the raw result data.
    function _readOnlyDelegateCall(address impl, bytes memory callData) internal view {
        try IReadOnlyDelegateCall(address(this)).delegateCallAndRevert(impl, callData) {
            // Should never happen.
            assert(false);
        } catch (bytes memory r) {
            (bool success, bytes memory resultData) = abi.decode(r, (bool, bytes));
            if (!success) {
                resultData.rawRevert();
            }
            resultData.rawReturn();
        }
    }
}

File 29 of 43 : IERC20.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

// Minimal ERC20 interface.
interface IERC20 {
    event Transfer(address indexed owner, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 allowance);

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

    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    function approve(address spender, uint256 allowance) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function balanceOf(address owner) external view returns (uint256);
}

File 30 of 43 : ERC721Receiver.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

import "./IERC721Receiver.sol";
import "../utils/EIP165.sol";
import "../vendor/solmate/ERC721.sol";

/// @notice Mixin for contracts that want to receive ERC721 tokens.
/// @dev Use this instead of solmate's ERC721TokenReceiver because the
///      compiler has issues when overriding EIP165/IERC721Receiver functions.
abstract contract ERC721Receiver is IERC721Receiver, EIP165, ERC721TokenReceiver {
    /// @inheritdoc IERC721Receiver
    function onERC721Received(
        address,
        address,
        uint256,
        bytes memory
    ) public virtual override(IERC721Receiver, ERC721TokenReceiver) returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    /// @inheritdoc EIP165
    function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
        return
            EIP165.supportsInterface(interfaceId) ||
            interfaceId == type(IERC721Receiver).interfaceId;
    }
}

File 31 of 43 : ERC1155Receiver.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

import "../vendor/solmate/ERC1155.sol";
import "../utils/EIP165.sol";

abstract contract ERC1155Receiver is EIP165, ERC1155TokenReceiverBase {
    /// @inheritdoc EIP165
    function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
        return
            super.supportsInterface(interfaceId) ||
            interfaceId == type(ERC1155TokenReceiverBase).interfaceId;
    }
}

File 32 of 43 : LibERC20Compat.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC20.sol";

// Compatibility helpers for ERC20s.
library LibERC20Compat {
    error NotATokenError(IERC20 token);
    error TokenTransferFailedError(IERC20 token, address to, uint256 amount);
    error TokenApprovalFailed(IERC20 token, address spender, uint256 amount);

    // Perform an `IERC20.transfer()` handling non-compliant implementations.
    function compatTransfer(IERC20 token, address to, uint256 amount) internal {
        (bool s, bytes memory r) = address(token).call(
            abi.encodeCall(IERC20.transfer, (to, amount))
        );
        if (s) {
            if (r.length == 0) {
                uint256 cs;
                assembly {
                    cs := extcodesize(token)
                }
                if (cs == 0) {
                    revert NotATokenError(token);
                }
                return;
            }
            if (abi.decode(r, (bool))) {
                return;
            }
        }
        revert TokenTransferFailedError(token, to, amount);
    }

    // Perform an `IERC20.transferFrom()` handling non-compliant implementations.
    function compatTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
        (bool s, bytes memory r) = address(token).call(
            abi.encodeCall(IERC20.transferFrom, (from, to, amount))
        );
        if (s) {
            if (r.length == 0) {
                uint256 cs;
                assembly {
                    cs := extcodesize(token)
                }
                if (cs == 0) {
                    revert NotATokenError(token);
                }
                return;
            }
            if (abi.decode(r, (bool))) {
                return;
            }
        }
        revert TokenTransferFailedError(token, to, amount);
    }

    function compatApprove(IERC20 token, address spender, uint256 amount) internal {
        (bool s, bytes memory r) = address(token).call(
            abi.encodeCall(IERC20.approve, (spender, amount))
        );
        if (s) {
            if (r.length == 0) {
                uint256 cs;
                assembly {
                    cs := extcodesize(token)
                }
                if (cs == 0) {
                    revert NotATokenError(token);
                }
                return;
            }
            if (abi.decode(r, (bool))) {
                return;
            }
        }
        revert TokenApprovalFailed(token, spender, amount);
    }
}

File 33 of 43 : IERC4906.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

interface IERC4906 {
    event MetadataUpdate(uint256 _tokenId);

    event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
}

File 34 of 43 : IProposalExecutionEngine.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC721.sol";

// Upgradeable proposals logic contract interface.
interface IProposalExecutionEngine {
    struct ExecuteProposalParams {
        uint256 proposalId;
        bytes proposalData;
        bytes progressData;
        bytes extraData;
        uint256 flags;
        IERC721[] preciousTokens;
        uint256[] preciousTokenIds;
    }

    function initialize(address oldImpl, bytes memory initData) external;

    /// @notice Execute a proposal.
    /// @dev Must be delegatecalled into by PartyGovernance.
    ///      If the proposal is incomplete, continues its next step (if possible).
    ///      If another proposal is incomplete, this will fail. Only one
    ///      incomplete proposal is allowed at a time.
    /// @param params The data needed to execute the proposal.
    /// @return nextProgressData Bytes to be passed into the next `execute()` call,
    ///         if the proposal execution is incomplete. Otherwise, empty bytes
    ///         to indicate the proposal is complete.
    function executeProposal(
        ExecuteProposalParams memory params
    ) external returns (bytes memory nextProgressData);

    /// @notice Forcibly cancel an incomplete proposal.
    /// @param proposalId The ID of the proposal to cancel.
    /// @dev This is intended to be a last resort as it can leave a party in a
    ///      broken step. Whenever possible, proposals should be allowed to
    ///      complete their entire lifecycle.
    function cancelProposal(uint256 proposalId) external;
}

File 35 of 43 : LibProposal.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC721.sol";

library LibProposal {
    uint256 internal constant PROPOSAL_FLAG_UNANIMOUS = 0x1;

    function isTokenPrecious(
        IERC721 token,
        IERC721[] memory preciousTokens
    ) internal pure returns (bool) {
        for (uint256 i; i < preciousTokens.length; ++i) {
            if (token == preciousTokens[i]) {
                return true;
            }
        }
        return false;
    }

    function isTokenIdPrecious(
        IERC721 token,
        uint256 tokenId,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) internal pure returns (bool) {
        for (uint256 i; i < preciousTokens.length; ++i) {
            if (token == preciousTokens[i] && tokenId == preciousTokenIds[i]) {
                return true;
            }
        }
        return false;
    }
}

File 36 of 43 : ProposalStorage.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "./IProposalExecutionEngine.sol";
import "../utils/LibRawResult.sol";

// The storage bucket shared by `PartyGovernance` and the `ProposalExecutionEngine`.
// Read this for more context on the pattern motivating this:
// https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/explicit-storage-buckets
abstract contract ProposalStorage {
    using LibRawResult for bytes;

    struct SharedProposalStorage {
        IProposalExecutionEngine engineImpl;
        ProposalEngineOpts opts;
    }

    struct ProposalEngineOpts {
        // Whether the party can add new authorities with the add authority proposal.
        bool enableAddAuthorityProposal;
        // Whether the party can spend ETH from the party's balance with
        // arbitrary call proposals.
        bool allowArbCallsToSpendPartyEth;
        // Whether operators can be used.
        bool allowOperators;
        // Whether distributions require a vote or can be executed by any active member.
        bool distributionsRequireVote;
    }

    uint256 internal constant PROPOSAL_FLAG_UNANIMOUS = 0x1;
    uint256 private constant SHARED_STORAGE_SLOT =
        uint256(keccak256("ProposalStorage.SharedProposalStorage"));

    function _initProposalImpl(IProposalExecutionEngine impl, bytes memory initData) internal {
        SharedProposalStorage storage stor = _getSharedProposalStorage();
        IProposalExecutionEngine oldImpl = stor.engineImpl;
        stor.engineImpl = impl;
        (bool s, bytes memory r) = address(impl).delegatecall(
            abi.encodeCall(IProposalExecutionEngine.initialize, (address(oldImpl), initData))
        );
        if (!s) {
            r.rawRevert();
        }
    }

    function _getSharedProposalStorage()
        internal
        pure
        returns (SharedProposalStorage storage stor)
    {
        uint256 s = SHARED_STORAGE_SLOT;
        assembly {
            stor.slot := s
        }
    }
}

File 37 of 43 : IERC721Renderer.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.20;

interface IERC721Renderer {
    function tokenURI(uint256 tokenId) external view returns (string memory);

    function contractURI() external view returns (string memory);
}

File 38 of 43 : SSTORE2.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
library SSTORE2 {
    uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called.

    /*//////////////////////////////////////////////////////////////
                               WRITE LOGIC
    //////////////////////////////////////////////////////////////*/

    function write(bytes memory data) internal returns (address pointer) {
        // Prefix the bytecode with a STOP opcode to ensure it cannot be called.
        bytes memory runtimeCode = abi.encodePacked(hex"00", data);

        bytes memory creationCode = abi.encodePacked(
            //---------------------------------------------------------------------------------------------------------------//
            // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
            //---------------------------------------------------------------------------------------------------------------//
            // 0x60    |  0x600B             | PUSH1 11     | codeOffset                                                     //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
            // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
            // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
            // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
            // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
            // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
            // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
            // 0xf3    |  0xf3               | RETURN       |                                                                //
            //---------------------------------------------------------------------------------------------------------------//
            hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes.
            runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit.
        );

        /// @solidity memory-safe-assembly
        assembly {
            // Deploy a new contract with the generated creation code.
            // We start 32 bytes into the code to avoid copying the byte length.
            pointer := create(0, add(creationCode, 32), mload(creationCode))
        }

        require(pointer != address(0), "DEPLOYMENT_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                               READ LOGIC
    //////////////////////////////////////////////////////////////*/

    function read(address pointer) internal view returns (bytes memory) {
        return readBytecode(pointer, DATA_OFFSET, pointer.code.length - DATA_OFFSET);
    }

    function read(address pointer, uint256 start) internal view returns (bytes memory) {
        start += DATA_OFFSET;

        return readBytecode(pointer, start, pointer.code.length - start);
    }

    function read(address pointer, uint256 start, uint256 end) internal view returns (bytes memory) {
        start += DATA_OFFSET;
        end += DATA_OFFSET;

        require(pointer.code.length >= end, "OUT_OF_BOUNDS");

        return readBytecode(pointer, start, end - start);
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function readBytecode(address pointer, uint256 start, uint256 size) private view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            data := mload(0x40)

            // Update the free memory pointer to prevent overriding our data.
            // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)).
            // Adding 31 to size and running the result through the logic above ensures
            // the memory pointer remains word-aligned, following the Solidity convention.
            mstore(0x40, add(data, and(add(add(size, 32), 31), not(31))))

            // Store the size of the data in the first 32 byte chunk of free memory.
            mstore(data, size)

            // Copy the code into memory right after the 32 bytes we used to store the size.
            extcodecopy(pointer, add(data, 32), start, size)
        }
    }
}

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

pragma solidity ^0.8.19;

/**
 * @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);
}

File 40 of 43 : EIP165.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

abstract contract EIP165 {
    /// @notice Query if a contract implements an interface.
    /// @param interfaceId The interface identifier, as specified in ERC-165
    /// @return `true` if the contract implements `interfaceId` and
    ///         `interfaceId` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
        return interfaceId == this.supportsInterface.selector;
    }
}

File 41 of 43 : IERC721Receiver.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes memory data
    ) external returns (bytes4);
}

File 42 of 43 : ERC1155.sol
// SPDX-License-Identifier: AGPL-3.0-only
// Based on solmate commit 1681dc505f4897ef636f0435d01b1aa027fdafaf (v6.4.0)
//  @ https://github.com/Rari-Capital/solmate/blob/1681dc505f4897ef636f0435d01b1aa027fdafaf/src/tokens/ERC1155.sol
// Only modified to inherit IERC1155 and rename ERC1155TokenReceiver -> ERC1155TokenReceiverBase.
pragma solidity ^0.8;

import "../../tokens/IERC1155.sol";

/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 is IERC1155 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event URI(string value, uint256 indexed id);

    /*//////////////////////////////////////////////////////////////
                             ERC1155 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address => mapping(uint256 => uint256)) public balanceOf;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                             METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id) public view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                              ERC1155 LOGIC
    //////////////////////////////////////////////////////////////*/

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) public virtual {
        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        balanceOf[from][id] -= amount;
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, from, to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155Received(
                    msg.sender,
                    from,
                    id,
                    amount,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) public virtual {
        require(ids.length == amounts.length, "LENGTH_MISMATCH");

        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        // Storing these outside the loop saves ~15 gas per iteration.
        uint256 id;
        uint256 amount;

        for (uint256 i; i < ids.length; ) {
            id = ids[i];
            amount = amounts[i];

            balanceOf[from][id] -= amount;
            balanceOf[to][id] += amount;

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155BatchReceived(
                    msg.sender,
                    from,
                    ids,
                    amounts,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function balanceOfBatch(
        address[] calldata owners,
        uint256[] calldata ids
    ) public view virtual returns (uint256[] memory balances) {
        require(owners.length == ids.length, "LENGTH_MISMATCH");

        balances = new uint256[](owners.length);

        // Unchecked because the only math done is incrementing
        // the array index counter which cannot possibly overflow.
        unchecked {
            for (uint256 i; i < owners.length; ++i) {
                balances[i] = balanceOf[owners[i]][ids[i]];
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
            interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, address(0), to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155Received(
                    msg.sender,
                    address(0),
                    id,
                    amount,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchMint(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i; i < idsLength; ) {
            balanceOf[to][ids[i]] += amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, address(0), to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155BatchReceived(
                    msg.sender,
                    address(0),
                    ids,
                    amounts,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchBurn(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i; i < idsLength; ) {
            balanceOf[from][ids[i]] -= amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, address(0), ids, amounts);
    }

    function _burn(address from, uint256 id, uint256 amount) internal virtual {
        balanceOf[from][id] -= amount;

        emit TransferSingle(msg.sender, from, address(0), id, amount);
    }
}

/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155TokenReceiverBase {
    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiverBase.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] calldata,
        uint256[] calldata,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiverBase.onERC1155BatchReceived.selector;
    }
}

File 43 of 43 : IERC1155.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

// Minimal ERC1155 interface.
interface IERC1155 {
    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 amount
    );
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] amounts
    );
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function setApprovalForAll(address operator, bool approved) external;

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;

    function balanceOf(address owner, uint256 tokenId) external view returns (uint256);

    function isApprovedForAll(address owner, address spender) external view returns (bool);

    function balanceOfBatch(
        address[] calldata owners,
        uint256[] calldata ids
    ) external view returns (uint256[] memory balances);
}

Settings
{
  "remappings": [
    "forge-std/=lib/forge-std/src/",
    "openzeppelin/=lib/openzeppelin-contracts/",
    "solmate/=lib/solmate/src/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 100
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "shanghai",
  "libraries": {},
  "viaIR": true
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract IGlobals","name":"globals","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"MetadataTooLarge","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"},{"internalType":"address","name":"instance","type":"address"}],"name":"NotAuthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"instance","type":"address"},{"indexed":false,"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"MetadataSet","type":"event"},{"inputs":[{"internalType":"address","name":"instance","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"multicallData","type":"bytes[]"}],"name":"multicall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"instance","type":"address"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"setMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"supportsRegistrars","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]

60a03461006a57601f6117fe38819003918201601f19168301916001600160401b0383118484101761006e5780849260209460405283398101031261006a57516001600160a01b038116810361006a5760805260405161177b9081610083823960805181610a6e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f803560e01c9081639fd8b5fa1461005757508063ac9650d814610052578063e28f5e521461004d5763f8501fec14610048575f80fd5b610228565b6101b3565b6100e4565b346100bf5760403660031901126100bf57600435610074816100c2565b602435906001600160401b03908183116100bb57366023840112156100bb5782600401359182116100bb5736602483850101116100bb5760246100b8930190610866565b80f35b8380fd5b80fd5b6001600160a01b038116036100d357565b5f80fd5b35906100e2826100c2565b565b346100d35760203660031901126100d3576004356001600160401b038082116100d357366023830112156100d35781600401358181116100d357602490600593368383871b830101116100d3579093368290036042190191905f5b86811061014857005b8481831b84010135848112156100d357830185810135908782116100d35760440181360381136100d3575f91829161018560405180938193611701565b0390305af461019261170e565b90156101ae57505f1981146101a95760010161013f565b610b8a565b61173d565b346100d3575f3660031901126100d357602060405160018152f35b5f5b8381106101df5750505f910152565b81810151838201526020016101d0565b90602091610208815180928185528580860191016101ce565b601f01601f1916010190565b9060206102259281815201906101ef565b90565b346100d35760403660031901126100d357600435610245816100c2565b61024d61040f565b6040516306fdde0360e01b815290915f826004816001600160a01b0385165afa928315610395576103476103346103649361037096610356965f91610374575b508060c0860152845261029f81611385565b8060e086015260208501526102b381611431565b60408501526102c1816114c1565b60608501526102cf81611551565b60808501526102dd816115e1565b60a08501526102eb81611671565b61010085015261032061030f610303610303846112e0565b6001600160a01b031690565b6001600160a01b0316610120860152565b61032981611317565b61014085015261134e565b61033d81610510565b610160830161051f565b60405192839160208301610538565b03601f1981018352826103ce565b60405191829182610214565b0390f35b61038f913d8091833e61038781836103ce565b810190610493565b5f61028d565b6104f1565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b038211176103c957604052565b61039a565b90601f801991011681019081106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c9576040525f61016083606080825280602083015280604083015280808301528060808301528060a08301528060c08301528060e083015261010082015282610120820152826101408201520152565b6001600160401b0381116103c957601f01601f191660200190565b6020818303126100d3578051906001600160401b0382116100d3570181601f820112156100d35780516104c581610478565b926104d360405194856103ce565b818452602082840101116100d35761022591602080850191016101ce565b6040513d5f823e3d90fd5b634e487b7160e01b5f52602160045260245ffd5b6003111561051a57565b6104fc565b600382101561051a5752565b90600382101561051a5752565b6102259060208152825161055a610180918260208501526101a08401906101ef565b9361061d6105ed6105d96105c56105b161059d61058960208801519b601f199c8d8c83030160408d01526101ef565b60408801518c8b83030160608c01526101ef565b60608701518b8a83030160808b01526101ef565b60808601518a8983030160a08a01526101ef565b60a0850151898883030160c08901526101ef565b60c0840151888783030160e08801526101ef565b61060860e084015191610100928988830301848901526101ef565b908301516101209786830301888701526101ef565b948101516001600160a01b031661014084810191909152810151906101609182850152015191019061052b565b908160209103126100d35751610225816100c2565b908160209103126100d3575180151581036100d35790565b81601f820112156100d35780359061068e82610478565b9261069c60405194856103ce565b828452602083830101116100d357815f926020809301838601378301015290565b359060038210156100d357565b906020828203126100d35781356001600160401b03928382116100d3570190610180828203126100d3576106fc6103ef565b9282358181116100d35782610712918501610677565b845260208301358181116100d3578261072c918501610677565b602085015260408301358181116100d35782610749918501610677565b604085015260608301358181116100d35782610766918501610677565b606085015260808301358181116100d35782610783918501610677565b608085015260a08301358181116100d357826107a0918501610677565b60a085015260c08301358181116100d357826107bd918501610677565b60c085015260e08301358181116100d357826107da918501610677565b60e085015261010091828401359182116100d3576107f9918401610677565b9083015261012061080b8183016100d7565b9083015261014080820135908301526108286101608092016106bd565b9082015290565b6020810192916100e2919061052b565b90918060409360208452816020850152848401375f828201840152601f01601f1916010190565b92916001600160a01b038085169290338403610a50575b946101607feb93c52781456784fc831a72052881602fbd4b0251d0715f7e0852cdec233d1e9495966108b1858501856106ca565b9060208201518051610a40575b5060408201518051610a30575b5060608201518051610a20575b5060808201518051610a10575b5060a08201518051610a00575b5061010082015180516109f0575b506101208201516001600160a01b03169081166109be575b5061014081015180610994575b5001805161093281610510565b61093b81610510565b610956575b50506109516040519283928361083f565b0390a2565b61098d9161098861097a925161096b81610510565b6040519384916020830161082f565b03601f1981018452836103ce565b6112b4565b5f80610940565b6103566109b26109b892604051928391602083019190602083019252565b84611288565b5f610925565b604080516001600160a01b0390921660208301526109ea91906109e49082908101610356565b8461125c565b5f610918565b6109fa9085611165565b5f610900565b610a0a908561106e565b5f6108f2565b610a1a9085610f77565b5f6108e5565b610a2a9085610e80565b5f6108d8565b610a3a9085610d89565b5f6108cb565b610a4a9085610c33565b5f6108be565b604051635c9fcd8560e11b8152601a600482015260209081816024817f000000000000000000000000000000000000000000000000000000000000000087165afa801561039557610ad7839185935f91610b44575b5060405163ef5d39d360e01b81523360048201526001600160a01b038c16602482015293849283919082906044820190565b0392165afa918215610395575f92610b17575b505061087d5760405163c55ddc9760e01b81523360048201526001600160a01b0387166024820152604490fd5b610b369250803d10610b3d575b610b2e81836103ce565b81019061065f565b5f80610aea565b503d610b24565b610b649150833d8511610b6a575b610b5c81836103ce565b81019061064a565b5f610aa5565b503d610b52565b6001600160a01b0390911681525f602082015260400190565b634e487b7160e01b5f52601160045260245ffd5b90602082018092116101a957565b90600282018092116101a957565b90601e82018092116101a957565b90600182018092116101a957565b90600382018092116101a957565b90600482018092116101a957565b90600582018092116101a957565b906101009182039182116101a957565b919082039182116101a957565b908160031b91808304600814901517156101a957565b604051602080820182610c468583610b71565b0392610c5a601f19948581018352826103ce565b519020938181018051948251948486118015610d72575b610c90575050505050610c86610c8b91610c1d565b610c00565b1c9055565b9193969550919361ffff10610d6057610cdb6040519182610ccf898201958660405f91939293606081019460018060a01b031681528260208201520152565b039081018352826103ce565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610d38915f1990610d1883610b9e565b11610d3d575b610d2782610bac565b908685840101511690850155610b9e565b610cfd565b610d53610c86610d4e848751610c10565b610c1d565b1b610d1e565b5050505050565b60405163a197fa0160e01b8152600490fd5b508486148015610c71575060018760ff1c14610c71565b6040516020610dbb81830183610d9f8683610b71565b0393610db3601f19958681018352826103ce565b519020610bc8565b938181018051948251948486118015610e69575b610de4575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526001918101919091529190610e27908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610e64915f1990610d1883610b9e565b610e49565b508486148015610dcf575060018760ff1c14610dcf565b6040516020610eb281830183610e968683610b71565b0393610eaa601f19958681018352826103ce565b519020610bac565b938181018051948251948486118015610f60575b610edb575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526002918101919091529190610f1e908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610f5b915f1990610d1883610b9e565b610f40565b508486148015610ec6575060018760ff1c14610ec6565b6040516020610fa981830183610f8d8683610b71565b0393610fa1601f19958681018352826103ce565b519020610bd6565b938181018051948251948486118015611057575b610fd2575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526003918101919091529190611015908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611052915f1990610d1883610b9e565b611037565b508486148015610fbd575060018760ff1c14610fbd565b60405160206110a0818301836110848683610b71565b0393611098601f19958681018352826103ce565b519020610be4565b93818101805194825194848611801561114e575b6110c9575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f6020820152600491810191909152919061110c908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611149915f1990610d1883610b9e565b61112e565b5084861480156110b4575060018760ff1c146110b4565b60405160206111978183018361117b8683610b71565b039361118f601f19958681018352826103ce565b519020610bf2565b938181018051948251948486118015611245575b6111c0575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526005918101919091529190611203908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611240915f1990610d1883610b9e565b611225565b5084861480156111ab575060018760ff1c146111ab565b60405161127181610356602082019485610b71565b51902090600682018092116101a957602001519055565b60405161129d81610356602082019485610b71565b51902090600782018092116101a957602001519055565b6040516112c981610356602082019485610b71565b51902090600882018092116101a957602001519055565b604051602081019160018060a01b031682525f604082015260408152611305816103ae565b519020600681018091116101a9575490565b604051602081019160018060a01b031682525f60408201526040815261133c816103ae565b519020600781018091116101a9575490565b604051602081019160018060a01b031682525f604082015260408152611373816103ae565b519020600881018091116101a9575490565b604080519160209261139e816103568682019485610b71565b51902054918260ff1c156113ef5781519181845480601e86015260f01c84010190525b81518110156113e957806113d76113e492610bba565b8185015490840152610b9e565b6113c1565b50905090565b5f92835b828110611416575b5082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016113f3565b93505f6113fb565b604061144d815192602093610db3816103568782019485610b71565b54918260ff1c156114885781519181845480601e86015260f01c84010190525b81518110156113e957806113d761148392610bba565b61146d565b5f92835b8281106114ae575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161148c565b60406114dd815192602093610eaa816103568782019485610b71565b54918260ff1c156115185781519181845480601e86015260f01c84010190525b81518110156113e957806113d761151392610bba565b6114fd565b5f92835b82811061153e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161151c565b604061156d815192602093610fa1816103568782019485610b71565b54918260ff1c156115a85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76115a392610bba565b61158d565b5f92835b8281106115ce575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016115ac565b60406115fd815192602093611098816103568782019485610b71565b54918260ff1c156116385781519181845480601e86015260f01c84010190525b81518110156113e957806113d761163392610bba565b61161d565b5f92835b82811061165e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161163c565b604061168d81519260209361118f816103568782019485610b71565b54918260ff1c156116c85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76116c392610bba565b6116ad565b5f92835b8281106116ee575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016116cc565b908092918237015f815290565b3d15611738573d9061171f82610478565b9161172d60405193846103ce565b82523d5f602084013e565b606090565b602081519101fdfea26469706673582212202431600c12897116883bf2d4d9efdbed042462cbd72793d0089d96bacf623c1864736f6c634300081400330000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade

Deployed Bytecode

0x60806040526004361015610011575f80fd5b5f803560e01c9081639fd8b5fa1461005757508063ac9650d814610052578063e28f5e521461004d5763f8501fec14610048575f80fd5b610228565b6101b3565b6100e4565b346100bf5760403660031901126100bf57600435610074816100c2565b602435906001600160401b03908183116100bb57366023840112156100bb5782600401359182116100bb5736602483850101116100bb5760246100b8930190610866565b80f35b8380fd5b80fd5b6001600160a01b038116036100d357565b5f80fd5b35906100e2826100c2565b565b346100d35760203660031901126100d3576004356001600160401b038082116100d357366023830112156100d35781600401358181116100d357602490600593368383871b830101116100d3579093368290036042190191905f5b86811061014857005b8481831b84010135848112156100d357830185810135908782116100d35760440181360381136100d3575f91829161018560405180938193611701565b0390305af461019261170e565b90156101ae57505f1981146101a95760010161013f565b610b8a565b61173d565b346100d3575f3660031901126100d357602060405160018152f35b5f5b8381106101df5750505f910152565b81810151838201526020016101d0565b90602091610208815180928185528580860191016101ce565b601f01601f1916010190565b9060206102259281815201906101ef565b90565b346100d35760403660031901126100d357600435610245816100c2565b61024d61040f565b6040516306fdde0360e01b815290915f826004816001600160a01b0385165afa928315610395576103476103346103649361037096610356965f91610374575b508060c0860152845261029f81611385565b8060e086015260208501526102b381611431565b60408501526102c1816114c1565b60608501526102cf81611551565b60808501526102dd816115e1565b60a08501526102eb81611671565b61010085015261032061030f610303610303846112e0565b6001600160a01b031690565b6001600160a01b0316610120860152565b61032981611317565b61014085015261134e565b61033d81610510565b610160830161051f565b60405192839160208301610538565b03601f1981018352826103ce565b60405191829182610214565b0390f35b61038f913d8091833e61038781836103ce565b810190610493565b5f61028d565b6104f1565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b038211176103c957604052565b61039a565b90601f801991011681019081106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c9576040525f61016083606080825280602083015280604083015280808301528060808301528060a08301528060c08301528060e083015261010082015282610120820152826101408201520152565b6001600160401b0381116103c957601f01601f191660200190565b6020818303126100d3578051906001600160401b0382116100d3570181601f820112156100d35780516104c581610478565b926104d360405194856103ce565b818452602082840101116100d35761022591602080850191016101ce565b6040513d5f823e3d90fd5b634e487b7160e01b5f52602160045260245ffd5b6003111561051a57565b6104fc565b600382101561051a5752565b90600382101561051a5752565b6102259060208152825161055a610180918260208501526101a08401906101ef565b9361061d6105ed6105d96105c56105b161059d61058960208801519b601f199c8d8c83030160408d01526101ef565b60408801518c8b83030160608c01526101ef565b60608701518b8a83030160808b01526101ef565b60808601518a8983030160a08a01526101ef565b60a0850151898883030160c08901526101ef565b60c0840151888783030160e08801526101ef565b61060860e084015191610100928988830301848901526101ef565b908301516101209786830301888701526101ef565b948101516001600160a01b031661014084810191909152810151906101609182850152015191019061052b565b908160209103126100d35751610225816100c2565b908160209103126100d3575180151581036100d35790565b81601f820112156100d35780359061068e82610478565b9261069c60405194856103ce565b828452602083830101116100d357815f926020809301838601378301015290565b359060038210156100d357565b906020828203126100d35781356001600160401b03928382116100d3570190610180828203126100d3576106fc6103ef565b9282358181116100d35782610712918501610677565b845260208301358181116100d3578261072c918501610677565b602085015260408301358181116100d35782610749918501610677565b604085015260608301358181116100d35782610766918501610677565b606085015260808301358181116100d35782610783918501610677565b608085015260a08301358181116100d357826107a0918501610677565b60a085015260c08301358181116100d357826107bd918501610677565b60c085015260e08301358181116100d357826107da918501610677565b60e085015261010091828401359182116100d3576107f9918401610677565b9083015261012061080b8183016100d7565b9083015261014080820135908301526108286101608092016106bd565b9082015290565b6020810192916100e2919061052b565b90918060409360208452816020850152848401375f828201840152601f01601f1916010190565b92916001600160a01b038085169290338403610a50575b946101607feb93c52781456784fc831a72052881602fbd4b0251d0715f7e0852cdec233d1e9495966108b1858501856106ca565b9060208201518051610a40575b5060408201518051610a30575b5060608201518051610a20575b5060808201518051610a10575b5060a08201518051610a00575b5061010082015180516109f0575b506101208201516001600160a01b03169081166109be575b5061014081015180610994575b5001805161093281610510565b61093b81610510565b610956575b50506109516040519283928361083f565b0390a2565b61098d9161098861097a925161096b81610510565b6040519384916020830161082f565b03601f1981018452836103ce565b6112b4565b5f80610940565b6103566109b26109b892604051928391602083019190602083019252565b84611288565b5f610925565b604080516001600160a01b0390921660208301526109ea91906109e49082908101610356565b8461125c565b5f610918565b6109fa9085611165565b5f610900565b610a0a908561106e565b5f6108f2565b610a1a9085610f77565b5f6108e5565b610a2a9085610e80565b5f6108d8565b610a3a9085610d89565b5f6108cb565b610a4a9085610c33565b5f6108be565b604051635c9fcd8560e11b8152601a600482015260209081816024817f0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade87165afa801561039557610ad7839185935f91610b44575b5060405163ef5d39d360e01b81523360048201526001600160a01b038c16602482015293849283919082906044820190565b0392165afa918215610395575f92610b17575b505061087d5760405163c55ddc9760e01b81523360048201526001600160a01b0387166024820152604490fd5b610b369250803d10610b3d575b610b2e81836103ce565b81019061065f565b5f80610aea565b503d610b24565b610b649150833d8511610b6a575b610b5c81836103ce565b81019061064a565b5f610aa5565b503d610b52565b6001600160a01b0390911681525f602082015260400190565b634e487b7160e01b5f52601160045260245ffd5b90602082018092116101a957565b90600282018092116101a957565b90601e82018092116101a957565b90600182018092116101a957565b90600382018092116101a957565b90600482018092116101a957565b90600582018092116101a957565b906101009182039182116101a957565b919082039182116101a957565b908160031b91808304600814901517156101a957565b604051602080820182610c468583610b71565b0392610c5a601f19948581018352826103ce565b519020938181018051948251948486118015610d72575b610c90575050505050610c86610c8b91610c1d565b610c00565b1c9055565b9193969550919361ffff10610d6057610cdb6040519182610ccf898201958660405f91939293606081019460018060a01b031681528260208201520152565b039081018352826103ce565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610d38915f1990610d1883610b9e565b11610d3d575b610d2782610bac565b908685840101511690850155610b9e565b610cfd565b610d53610c86610d4e848751610c10565b610c1d565b1b610d1e565b5050505050565b60405163a197fa0160e01b8152600490fd5b508486148015610c71575060018760ff1c14610c71565b6040516020610dbb81830183610d9f8683610b71565b0393610db3601f19958681018352826103ce565b519020610bc8565b938181018051948251948486118015610e69575b610de4575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526001918101919091529190610e27908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610e64915f1990610d1883610b9e565b610e49565b508486148015610dcf575060018760ff1c14610dcf565b6040516020610eb281830183610e968683610b71565b0393610eaa601f19958681018352826103ce565b519020610bac565b938181018051948251948486118015610f60575b610edb575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526002918101919091529190610f1e908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610f5b915f1990610d1883610b9e565b610f40565b508486148015610ec6575060018760ff1c14610ec6565b6040516020610fa981830183610f8d8683610b71565b0393610fa1601f19958681018352826103ce565b519020610bd6565b938181018051948251948486118015611057575b610fd2575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526003918101919091529190611015908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611052915f1990610d1883610b9e565b611037565b508486148015610fbd575060018760ff1c14610fbd565b60405160206110a0818301836110848683610b71565b0393611098601f19958681018352826103ce565b519020610be4565b93818101805194825194848611801561114e575b6110c9575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f6020820152600491810191909152919061110c908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611149915f1990610d1883610b9e565b61112e565b5084861480156110b4575060018760ff1c146110b4565b60405160206111978183018361117b8683610b71565b039361118f601f19958681018352826103ce565b519020610bf2565b938181018051948251948486118015611245575b6111c0575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526005918101919091529190611203908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611240915f1990610d1883610b9e565b611225565b5084861480156111ab575060018760ff1c146111ab565b60405161127181610356602082019485610b71565b51902090600682018092116101a957602001519055565b60405161129d81610356602082019485610b71565b51902090600782018092116101a957602001519055565b6040516112c981610356602082019485610b71565b51902090600882018092116101a957602001519055565b604051602081019160018060a01b031682525f604082015260408152611305816103ae565b519020600681018091116101a9575490565b604051602081019160018060a01b031682525f60408201526040815261133c816103ae565b519020600781018091116101a9575490565b604051602081019160018060a01b031682525f604082015260408152611373816103ae565b519020600881018091116101a9575490565b604080519160209261139e816103568682019485610b71565b51902054918260ff1c156113ef5781519181845480601e86015260f01c84010190525b81518110156113e957806113d76113e492610bba565b8185015490840152610b9e565b6113c1565b50905090565b5f92835b828110611416575b5082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016113f3565b93505f6113fb565b604061144d815192602093610db3816103568782019485610b71565b54918260ff1c156114885781519181845480601e86015260f01c84010190525b81518110156113e957806113d761148392610bba565b61146d565b5f92835b8281106114ae575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161148c565b60406114dd815192602093610eaa816103568782019485610b71565b54918260ff1c156115185781519181845480601e86015260f01c84010190525b81518110156113e957806113d761151392610bba565b6114fd565b5f92835b82811061153e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161151c565b604061156d815192602093610fa1816103568782019485610b71565b54918260ff1c156115a85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76115a392610bba565b61158d565b5f92835b8281106115ce575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016115ac565b60406115fd815192602093611098816103568782019485610b71565b54918260ff1c156116385781519181845480601e86015260f01c84010190525b81518110156113e957806113d761163392610bba565b61161d565b5f92835b82811061165e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161163c565b604061168d81519260209361118f816103568782019485610b71565b54918260ff1c156116c85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76116c392610bba565b6116ad565b5f92835b8281106116ee575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016116cc565b908092918237015f815290565b3d15611738573d9061171f82610478565b9161172d60405193846103ce565b82523d5f602084013e565b606090565b602081519101fdfea26469706673582212202431600c12897116883bf2d4d9efdbed042462cbd72793d0089d96bacf623c1864736f6c63430008140033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade

-----Decoded View---------------
Arg [0] : globals (address): 0x1cA20040cE6aD406bC2A6c89976388829E7fbAde

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

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.