ourzora / zora-protocol

Monorepo for Zora Protocol (contracts & sdks)
MIT License
112 stars 70 forks source link

Unique Zora Protocol Minting Use Case #428

Open babyuniverse opened 2 months ago

babyuniverse commented 2 months ago

I'm still new to smart contract architecture and NFT minting through coding and I've been running into an issue with figuring out what are the proper functions I should be running to achieve my team's project needs. I am needing to have a smart contract deployed that allows me to have each user have a unique image of their canvas capture as the image in their metadata at the time of minting which has caused me a bit of trouble. I have tried the createEditionWithReferral & createDropWithReferral functions to no avail and I even tried to deploy a smart contract using the sdk CreatorClient and I've tried the purchase and mintWithRewards functions but I couldn't quite nail it. I tried to have the updateTokenURI function run simultaneously at the time of minting, but I was having ABI issues when using the ERC721DropABI. I wanna know if I could be pointed in the proper direction. I'm so close to figuring it out, but I'm not quite there yet. The implementation is using html2canvas to capture the canvas and it gets uploaded to IPFS and that hash is then being passed to the metadata for the tokenURI so that each user has a unique image as their NFT with Wagmi handling my blockchain interactions. For the most part, I've been experiencing minting blank NFTs and also NFTs that have the contract IPFS image instead of the unique canvas capture. I just need to be pointed in the correct direction of which function usage should be for my use case. I don't need anything done for me per se, I just need to know what specific smart contract deployment function I need to deploy and what purchase or mint function that can accommodate my needs with the corresponding pages in the Zora docs so that I can figure out the proper usage of the Zora Protocol. Thank you.

This is a link to the Github project that shows the code. My apologies, there are a lot of different versions of different tests and the code may not be the best:

updateMetadateBaseWithDetails Version: https://github.com/playgotchi/playground/blob/67370a3fd86614327af24e5b5c21ae9736006ac3/components/canvas.tsx

UpdateTokenURI version(minted blank NFT image: https://github.com/playgotchi/playground/blob/dee14438397c6f9f408b259c49a17679afd2767f/components/canvas.tsx

mintWithRewards test (was never prompted to sign transaction in Metamask: https://github.com/playgotchi/playground/blob/9cf21dadae4f590707a4e4ee92177dbe392110c5/components/canvas.tsx

createEditionWithReferral purchase function successful mint: I've had a successful mint but it didn't incorporate the unique canvas capture functionality that I needed. https://zora.co/collect/base:0x6458804cd6868b1edf8e61f267e626ffd57d51ec https://github.com/playgotchi/playground/blob/62ea1970578783b842eeb5038d955d90c55b2682/components/canvas.tsx

iainnash commented 2 months ago

It'd be really helpful if you had any links to the onchain interactions or tests so we can verify the metadata rather than reading the front-end code.

babyuniverse commented 2 months ago

Here are the basescan links to see the onchain interactions:

Contract Addresses( test purchases can be viewed at bottom of page: https://basescan.org/address/0x2506012d406cd451735e78ff5bcea35dc7ee1505 https://basescan.org/address/0x6458804cd6868b1edf8e61f267e626ffd57d51ec

Relevant Transaction Hash of createEditionWithReferral with logs: https://basescan.org/tx/0xb003b14b2e31384df96c0c87903ead055be092e3254676eae6787ef1182c5ac4

Transaction Hash of createDropWithReferral with logs: https://basescan.org/tx/0xb34dedc5024415bb9da2baf119cc516f611d9e691fea0ca39bde10d18f5583cc

Latest contract creation Transaction Hash: https://basescan.org/tx/0xdc2481949909f8c3fc80f295efb8e112dd17dcf8e27708062a33a364fc60a932

Please let me know if this is the required information you meant or if more information is needed about the interactions.

iainnash commented 2 months ago

Your contract itself looks fine

https://mint.fun/base/0x2506012d406Cd451735e78Ff5Bcea35dC7ee1505 and https://zora.co/collect/base:0x2506012d406Cd451735e78Ff5Bcea35dC7ee1505

However, your URI for each token is incorrect: https://base.ether.actor/0x2506012d406cd451735e78ff5bcea35dc7ee1505/tokenURI/1 -> https://ipfs.io/ipfs/QmV9qP5wrzw1YRGMPmxrpcqsMJMijgSKzYyDxEMtxu47X8/1

Returns not found https://ipfs.io/ipfs/QmV9qP5wrzw1YRGMPmxrpcqsMJMijgSKzYyDxEMtxu47X8 is a valid metadata but it's for an edition not a drop. You can switch the renderer to the EditionMetadataRenderer with the URI to fix it: https://docs.zora.co/contracts/EditionMetadataRenderer

babyuniverse commented 1 month ago

Thank you for looking things over. I was under the impression that using the EditionMetadataRenderer would change the metadata for the entire edition, but for my use case, each user would need their own specific unique image metadata to be updated for their specific tokens, that's why I was going with DropMetadataRenderer. I only did the createEditionReferral function as a test to see if I could successfully call Zora functions. I really need the createDropWithReferral which allows for each token to have its own unique image. My team said that I could circumvent the need for having the metadata have to be updated altogether if the user mints their own smart contract and token simultaneously at the same time when they click the mint button on our web app similar to how Uplink by Base Management or Forage and other platforms have one click minting experiences with each token coming from its own unique respective smart contract address. Is it possible for me to combine smart contract creation and NFT minting of the token from the contract address that was just created in my scenario? Would that be possible with my updated code below which incorporates the canvas capture, the upload to IPFS, and the createDropWithReferral smart contract and mintWithRewards minting functions? I tried to combine and batch the transaction so it's a seamless one button click experience for the user, but I only get prompted to deploy the smart contract and I seen that I may need a custom a smart contract instead of a Factory contract to allow for both transactions to happen simultaneously , though I'm not very proficient in solidity:

const captureWhiteboard = async (aspectRatio: number = 1.91): Promise<string> => {
    if (!fabricRef.current) throw new Error('Canvas not initialized');

    try {
        const canvas = fabricRef.current;
        const originalWidth = canvas.getWidth();
        const originalHeight = canvas.getHeight();

        let newWidth, newHeight;
        if (originalWidth / originalHeight > aspectRatio) {
            newHeight = originalHeight;
            newWidth = newHeight * aspectRatio;
        } else {
            newWidth = originalWidth;
            newHeight = newWidth / aspectRatio;
        }

        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = newWidth;
        tempCanvas.height = newHeight;

        const ctx = tempCanvas.getContext('2d');
        if (!ctx) throw new Error('Failed to get 2D context');

        ctx.fillStyle = '#020817';
        ctx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);

        canvas.renderAll();
        const fabricCanvas = canvas.getElement();

        const scale = Math.min(newWidth / originalWidth, newHeight / originalHeight);
        const x = (newWidth - originalWidth * scale) / 2;
        const y = (newHeight - originalHeight * scale) / 2;

        ctx.drawImage(fabricCanvas, x, y, originalWidth * scale, originalHeight * scale);

        return tempCanvas.toDataURL('image/png');
    } catch (error) {
        console.error('Failed to capture whiteboard:', error);
        throw error;
    }
};

const handleCapture = async () => {
    setIsExporting(true);
    try {
        const imageDataUrl = await captureWhiteboard();
        setCapturedImage(imageDataUrl);
    } catch (error) {
        console.error('Failed to capture whiteboard:', error);
        alert('Failed to capture whiteboard. Please try again.');
    } finally {
        setIsExporting(false);
    }
};

const exportWhiteboard = async () => {
    setIsExporting(true);
    try {
        const imageDataUrl = await captureWhiteboard();

        const link = document.createElement('a');
        link.href = imageDataUrl;
        link.download = 'playground-export.png';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    } catch (error) {
        console.error('Failed to export whiteboard:', error);
        alert('Failed to export whiteboard. Please try again.');
    } finally {
        setIsExporting(false);
    }
};

const uploadToIPFS = async (blob: Blob): Promise<string> => {
    const reader = new FileReader();
    reader.readAsDataURL(blob);
    return new Promise((resolve, reject) => {
      reader.onloadend = async () => {
        const base64data = typeof reader.result === 'string' ? reader.result.split(',')[1] : '';
        try {
          const response = await fetch('/api/upload-to-ipfs', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ content: base64data, filename: 'playground.png' }),
          });
          const { ipfsHash } = await response.json();
          resolve(ipfsHash);
        } catch (error) {
          reject(error);
        }
      };
    });
  };

const [deployedContractAddress, setDeployedContractAddress] = useState<string | null>(null);

const { writeContractAsync } = useWriteContract();
const { isLoading: isWaitingForTransaction, isSuccess: transactionSuccess } = useWaitForTransactionReceipt({
    hash: mintData ? (mintData as `0x${string}`) : undefined,
  });

  const chainId = useChainId();
  const publicClient = usePublicClient()!;
  const { address } = useAccount();

  const createMetadata = (imageHash: string) => ({
    name: "Playground Capture",
    description: "A captured Playground session",
    image: `ipfs://${imageHash}`
});

  const handleMint = async () => {
    setIsMinting(true);
    setMintingError(null);
    setMintingSuccess(false);
    setMintingStep('Capturing image');

    try {
        console.log("Capturing whiteboard");
      const imageDataUrl = await captureWhiteboard();

      console.log("Uploading to IPFS");
      setMintingStep('Uploading to IPFS');
        // Capture and upload image
        const blob = await (await fetch(imageDataUrl)).blob();
        const imageHash = await uploadToIPFS(blob);

        // Create and upload metadata
        const metadata = createMetadata(imageHash);
        const metadataBlob = new Blob([JSON.stringify(metadata)], { type: 'application/json' });
        const metadataHash = await uploadToIPFS(metadataBlob);
        console.log(`Pinned image to IPFS: ${imageHash}`);
        console.log(`Pinned image to IPFS: ${metadataHash}`);

      setMintingStep('Creating metadata...');

      console.log("Preparing to deploy Contract");

      setMintingStep('Deploying contract');
      const deployConfig = {
        address: '0x899ce31dF6C6Af81203AcAaD285bF539234eF4b8' as `0x${string}`,
        abi: ZoraAbi,
        functionName: 'createDropWithReferral' as const, // Define as literal type
        args: [
            "Playground Pic", // Edition name
            "PP", // Edition reference code
            address as `0x${string}`, // Default admin
            BigInt(1), // Edition size (1 for a single mint)
            3, // Royalty BPS (changed to number)
            address as `0x${string}`, // Funds recipient address
            {
              publicSalePrice: BigInt(0),
              maxSalePurchasePerAddress: 1,
              publicSaleStart: BigInt(0),
              publicSaleEnd: BigInt("0xFFFFFFFFFFFFFFFF"),
              presaleStart: BigInt(0),
              presaleEnd: BigInt(0),
              presaleMerkleRoot: "0x0000000000000000000000000000000000000000000000000000000000000000"
            },
            `ipfs://${metadataHash}`, // Pass the metadata IPFS hash here
            "", // animation URI (optional, replace with "" if not used)
            "0x124F3eB5540BfF243c2B57504e0801E02696920E" as `0x${string}`, // Referral address
          ] as const,
          maxFeePerBlobGas: BigInt(0), // Placeholder value, replace as needed
          blobs: [], // Placeholder value, replace as needed

      };

      const deployHash = await writeContractAsync(deployConfig);

      if (deployHash) {
        const deployReceipt = await publicClient.waitForTransactionReceipt({ hash: deployHash });
        if (deployReceipt.contractAddress) {
          const contractAddress = deployReceipt.contractAddress;
          setDeployedContractAddress(contractAddress);

          console.log(contractAddress);

          setMintingStep('Minting NFT');
          // Prepare the mintWithRewards transaction 
          const quantity = 1n;
          const mintReferral = "0x124F3eB5540BfF243c2B57504e0801E02696920E"; // Replace with your referral address
          const minterArguments = "0x"; // Empty bytes for no additional arguments

          const mintConfig = {
            address: contractAddress,
            abi: ZoraAbi,
            functionName: 'mintWithRewards',
            args: [address, quantity, minterArguments, mintReferral],
            value: parseEther("0.000777"),
          } as any;  // Add 'as any' to bypass the TypeScript type check

          const mintHash = await writeContractAsync(mintConfig);
          if (mintHash) {
            setMintData(mintHash);
          } else {
            throw new Error("Failed to get transaction hash for minting");
          }
        }
      } else {
        throw new Error("Failed to get transaction hash from contract deployment");
      }

      setMintingStep('Waiting for transaction confirmation');
    } catch (error) {
      console.error("Error while minting:", error);
      setMintingError(error instanceof Error ? error.message : String(error));
    } finally {
      setIsMinting(false);
    }
  useEffect(() => {
    if (transactionSuccess) {
      setMintingSuccess(true);
      setMintingStep('NFT minted successfully!');
    }
  }, [transactionSuccess]);     
};
babyuniverse commented 1 month ago

Thanks for pointing me in the direction of the EditionMetadataRenderer. I believed that I found a solution in the Zora docs: https://docs.zora.co/contracts/events#creating-a-new-token-1 Specifically, it says: We also support a multicall pattern with the setupCalls argument where the factory is granted temporary admin permissions to execute multiple commands on the contract after deployment allowing for setting additional settings or minting upon deployment.

There appears to be a mistake in the Zora docs with the function though Right below the above statement, it says:Creating an Edition: function createEditionWithReferral( string memory name, string memory symbol, address defaultAdmin, uint64 editionSize, uint16 royaltyBPS, address payable fundsRecipient, bytes[] memory setupCalls, IMetadataRenderer metadataRenderer, bytes memory metadataInitializer, address createReferral ) However , when I review the zoraNftCreatorV1Config.abi, the only function with those parameters are createAndConfigureDrop & initialize, correct me if I'm wrong but shouldn't it say createAndConfigureDrop in the function spot?

Nonetheless, I've been trying to mint upon deployment by using the multicall pattern with setupCalls according to the docs, but I keep running into minting errors. First, I tried the setupCalls array empty to run a simulation, it was successful. Then I added the setSalesConfiguration to the setupCalls, and that simulation was successful, however as soon as I tried to add mintWithRewards or adminMint to the setupCalls , I get the error in my console: Simulation error: ContractFunctionExecutionError at s (364-e4dec215d410c892.js:123:31053) at c (364-e4dec215d410c892.js:101:1171526) at async ev (406-e503efa79a19861a.js:1:44486)Caused by: ContractFunctionRevertedError at s (364-e4dec215d410c892.js:123:30965) at c (364-e4dec215d410c892.js:101:1171526) at async ev (406-e503efa79a19861a.js:1:44486)Caused by: AbiErrorSignatureNotFoundError at c (364-e4dec215d410c892.js:123:9815) at new b (364-e4dec215d410c892.js:117:2003) at s (364-e4dec215d410c892.js:123:30965) at c (364-e4dec215d410c892.js:101:1171526) at async ev (406-e503efa79a19861a.js:1:44486) window.console.error @ 23-c914f9deaa529732.js:1 ev @ 406-e503efa79a19861a.js:1 await in ev (async) a_ @ fd9d1056-1be539a0b9d0a31c.js:1 aR @ fd9d1056-1be539a0b9d0a31c.js:1 (anonymous) @ fd9d1056-1be539a0b9d0a31c.js:1 sF @ fd9d1056-1be539a0b9d0a31c.js:1 sM @ fd9d1056-1be539a0b9d0a31c.js:1 (anonymous) @ fd9d1056-1be539a0b9d0a31c.js:1 o4 @ fd9d1056-1be539a0b9d0a31c.js:1 iV @ fd9d1056-1be539a0b9d0a31c.js:1 sU @ fd9d1056-1be539a0b9d0a31c.js:1 uR @ fd9d1056-1be539a0b9d0a31c.js:1 uM @ fd9d1056-1be539a0b9d0a31c.js:1 23-c914f9deaa529732.js:1 Error minting token: Error: Transaction simulation failed: The contract function "createAndConfigureDrop" reverted with the following signature: 0x717c5130

Unable to decode signature "0x717c5130" as it was not found on the provided ABI. Make sure you are using the correct ABI and that the error exists on it. You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x717c5130. When I visit the openchain url , it says:

Hash | Name 0x717c5130 | Mint_SoldOut() I don't understand how the mint could be sold out and I have yet to even mint anything from the contract prior to deployment and it is a fresh contract being deployed every time so it should always have a token available to mint. There may be a function I'm missing in the setupCalls to account for this but none of the other ones I tried have been successful, please assist me with the correct structure of setupCalls to achieve minting the token upon smart contract deployment in the same transaction per the Zora docs recommendations.

This is my updated code I seem to be missing something: const handleMint = async () => { if (!address) { throw new Error("User address is not available"); }

    setIsMinting(true);
    setMintingError(null);
    setMintingSuccess(false);
    setMintingStep('Capturing image');

    try {
        console.log("Capturing image from whiteboard...");
        const imageDataUrl = await captureWhiteboard();
        setMintingStep('Uploading to IPFS');
        console.log("Image captured, fetching blob...");
        const blob = await (await fetch(imageDataUrl)).blob();
        console.log("Blob fetched, uploading to IPFS...");
        const imageHash = await uploadToIPFS(blob);
        console.log("Image uploaded to IPFS, hash:", imageHash);

        // Create and upload contract metadata
        console.log("Creating and uploading contract metadata...");
        const contractMetadata = createMetadata(imageHash);
        const contractMetadataHash = await uploadToIPFS(new Blob([JSON.stringify(contractMetadata)], { type: 'application/json' }));
        const contractURI = `ipfs://${contractMetadataHash}`;
        console.log("Contract metadata uploaded to IPFS, hash:", contractMetadataHash);

        const baseURI = `ipfs://${imageHash}/`;
        setMintingStep('Preparing transaction');

        // Prepare metadata initialization
        console.log("Creating metadataInitializer...");
        const abiCoder = new ethers.AbiCoder();

        const metadataInitializer = abiCoder.encode(
            ['string', 'string', 'string'],
            [baseURI, contractURI, "0x"] 
        );

        console.log("Preparing setupCalls...");
        const erc721DropInterface = new ethers.Interface(erc721DropABI);

// Set up sale configuration const saleConfig = { publicSalePrice: BigInt(0), maxSalePurchasePerAddress: 1, publicSaleStart: BigInt(0), publicSaleEnd: BigInt("0xFFFFFFFFFFFFFFFF"), presaleStart: BigInt(0), presaleEnd: BigInt(0),
presaleMerkleRoot: "0x0000000000000000000000000000000000000000000000000000000000000000" };

        const setSaleConfigCall = erc721DropInterface.encodeFunctionData(
            'setSaleConfiguration',
            Object.values(saleConfig)
        );            

        // 2. Prepare mintWithRewards call
        const mintWithRewardsCall = erc721DropInterface.encodeFunctionData('mintWithRewards', [
            address, // recipient
            BigInt(1), // quantity
            "", // comment (empty string)
            "0x124F3eB5540BfF243c2B57504e0801E02696920E" // mintReferral
        ]);

        // Combine setupCalls
        const setupCalls: readonly `0x${string}`[] = [
            setSaleConfigCall as `0x${string}`, 
            mintWithRewardsCall as `0x${string}`
        ];    
        console.log("setupCalls prepared successfully");

        setMintingStep('Creating metadata...');
        // Prepare the createAndConfigureDrop function call
        console.log("Preparing createAndConfigureDrop function call...");
        setMintingStep('Creating metadata...');

        // Create Drop contract
        const args = [
            "Playground Pic", // name
            "PP", // symbol
            address as `0x${string}`, // defaultAdmin
            BigInt(2), // editionSize (1 for a single mint)
            300, // royaltyBPS (3%)
            address as `0x${string}`, // fundsRecipient
            setupCalls, // setupCalls with mintWithRewards
            '0x7d1a46c6e614A0091c39E102F2798C27c1fA8892' as `0x${string}`, // metadataRenderer (EDITION_METADATA_RENDERER)
            metadataInitializer as `0x${string}`,
            "0x124F3eB5540BfF243c2B57504e0801E02696920E" as `0x${string}`, // createReferral
        ] as const;

        console.log("Args for createAndConfigureDrop:", args);

        // Simulate the transaction
        setMintingStep('Simulating transaction...');
        console.log("Simulating Transaction");

        try {
            const { request } = await publicClient.simulateContract({
                account: address,
                address: zoraNftCreatorV1Config.address[base.id],
                abi: zoraNftCreatorV1Config.abi,
                functionName: "createAndConfigureDrop",
                args,
            });
            console.log("Transaction simulation successful", request);
        } catch (error) {
            console.error("Transaction simulation failed:", error);
            if (error instanceof Error) {
                throw new Error(`Transaction simulation failed: ${error.message}`);
            } else {
                throw new Error('Transaction simulation failed with an unknown error');
            }
        }

        setMintingStep('Deploying smart contract...');
        const hash = await writeContractAsync({
            address: zoraNftCreatorV1Config.address[base.id], 
            abi: zoraNftCreatorV1Config.abi,
            functionName: "createAndConfigureDrop",
            args,
        });

        console.log("Transaction hash:", hash);
        setMintingStep('Waiting for transaction confirmation...');

        // Wait for transaction confirmation
        const receipt = await waitForTransactionReceipt(publicClient, { hash });

        console.log("Transaction receipt:", receipt);
iainnash commented 1 month ago

mintWithRewards will not work since it requires a payable value to mint.

adminMint should work in this context if you try to mint a token that's been setup.

iainnash commented 1 month ago

You're also setting BigInt(2), // editionSize (1 for a single mint) 2 as the edition size and 1 as the maxCanMintPerAddress.

babyuniverse commented 1 month ago

mintWithRewards will not work since it requires a payable value to mint.

adminMint should work in this context if you try to mint a token that's been setup.

I tried adminMint with editionSize:BigInt(1), and 1 as the maxCanMintPerAddress first and I was getting the Mint_SoldOut error which led me to change the code to editionSize:BigInt(2), I thought maybe I was getting the error because I was trying to mint the only available token in the smart contract or I was getting the error because the it wasn't recognizing 1 for something but I tried that first, same error.

I understand why you say mintWithRewards would not work since it needed a payable value & adminMint would theoretically bypass the need for that so I get why that code above didn't work but I tried adminMint first and I was getting the same simulation error as you see above over and over with each different approach I had , I tried both adminMint & adminMintAirdrop functions and they all lead to the same: ContractFunctionError ContractFunctionRevertedError

Error minting token: Error: Transaction simulation failed: The contract function "createAndConfigureDrop" reverted with the following signature: Hash | Name 0x717c5130 | Mint_SoldOut()

I thought maybe it had something to do with trying to mint & handle token creation simultaneously with the address of the deployed contract not being able to be properly accessed during the deployment to mint in the same instance of createAndConfigureDrop function. Maybe I was missing something with the multicall pattern functionality or a function I was missing, I tried almost all of the ones that made sense and none of them worked. How should it be used properly? What would the correct way to use the setupCalls in the createAndConfigureDrop function to mint upon deployment like the docs say?

babyuniverse commented 1 month ago

adminMint should work in this context if you try to mint a token that's been setup.

I tried it again for good measure and it didn't work, I got the the same Mint_SoldOut signature error. When I remove adminMint from setup calls , it prompts me with a contract interaction sign transaction to deploy the contract and when I added it back, the transaction reverted and it didn't even prompt me to sign the transaction at all.

       const erc721DropInterface = new ethers.Interface(erc721DropABI);

        const saleConfig = {
            publicSalePrice: BigInt(0),
            maxSalePurchasePerAddress: 1,
            publicSaleStart: BigInt(0),
            publicSaleEnd: BigInt("0xFFFFFFFFFFFFFFFF"),
            presaleStart: BigInt(0),
            presaleEnd: BigInt(0),  
            presaleMerkleRoot: "0x0000000000000000000000000000000000000000000000000000000000000000"
        };

        const setSaleConfigCall = erc721DropInterface.encodeFunctionData(
            'setSaleConfiguration',
            Object.values(saleConfig)
        );

        const recipientAddress = address; 
        const mintQuantity = 1;
        const adminMintCall = erc721DropInterface.encodeFunctionData(
            'adminMint',
            [recipientAddress, mintQuantity]
        );

        const setupCalls: readonly `0x${string}`[] = [
            setSaleConfigCall as `0x${string}`,
            adminMintCall as `0x${string}`

        ];

        const args = [
            "Playground Pic", // name
            "PP", // symbol
            address as `0x${string}`, // defaultAdmin
            BigInt(1), // editionSize (1 for a single mint)
            300, // royaltyBPS (3%)
            address as `0x${string}`, // fundsRecipient
            setupCalls, // setupCalls
            '0x7d1a46c6e614A0091c39E102F2798C27c1fA8892' as `0x${string}`, // (EDITION_METADATA_RENDERER)
            metadataInitializer as `0x${string}`,
            "0x124F3eB5540BfF243c2B57504e0801E02696920E" as `0x${string}`, // createReferral
        ] as const;

        setMintingStep('Deploying contract...');
        const deployTx = await writeContractAsync({
            address: zoraNftCreatorV1Config.address[base.id],
            abi: zoraNftCreatorV1Config.abi,
            functionName: "createAndConfigureDrop",
            args,
        });

            const receipt = await waitForTransactionReceipt(publicClient, { hash: deployTx });
iainnash commented 1 month ago

Are you able to publish the failing txn so i can take a look?

On Wed, Aug 7, 2024 at 15:30 babyuniverse @.***> wrote:

adminMint should work in this context if you try to mint a token that's been setup.

I tried it again for good measure and it didn't work, I got the the same Mint_SoldOut signature error. When I remove adminMint from setup calls , it prompts me with a contract interaction sign transaction to deploy the contract and when I added it back, the transaction reverted and it didn't even prompt me to sign the transaction at all.

   const erc721DropInterface = new ethers.Interface(erc721DropABI);

    const saleConfig = {
        publicSalePrice: BigInt(0),
        maxSalePurchasePerAddress: 1,
        publicSaleStart: BigInt(0),
        publicSaleEnd: BigInt("0xFFFFFFFFFFFFFFFF"),
        presaleStart: BigInt(0),
        presaleEnd: BigInt(0),
        presaleMerkleRoot: "0x0000000000000000000000000000000000000000000000000000000000000000"
    };

    const setSaleConfigCall = erc721DropInterface.encodeFunctionData(
        'setSaleConfiguration',
        Object.values(saleConfig)
    );

    const recipientAddress = address;
    const mintQuantity = 1;
    const adminMintCall = erc721DropInterface.encodeFunctionData(
        'adminMint',
        [recipientAddress, mintQuantity]
    );

    const setupCalls: readonly `0x${string}`[] = [
        setSaleConfigCall as `0x${string}`,
        adminMintCall as `0x${string}`

    ];

    const args = [
        "Playground Pic", // name
        "PP", // symbol
        address as `0x${string}`, // defaultAdmin
        BigInt(1), // editionSize (1 for a single mint)
        300, // royaltyBPS (3%)
        address as `0x${string}`, // fundsRecipient
        setupCalls, // setupCalls
        '0x7d1a46c6e614A0091c39E102F2798C27c1fA8892' as `0x${string}`, // (EDITION_METADATA_RENDERER)
        metadataInitializer as `0x${string}`,
        "0x124F3eB5540BfF243c2B57504e0801E02696920E" as `0x${string}`, // createReferral
    ] as const;

    setMintingStep('Deploying contract...');
    const deployTx = await writeContractAsync({
        address: zoraNftCreatorV1Config.address[base.id],
        abi: zoraNftCreatorV1Config.abi,
        functionName: "createAndConfigureDrop",
        args,
    });

        const receipt = await waitForTransactionReceipt(publicClient, { hash: deployTx });

— Reply to this email directly, view it on GitHub https://github.com/ourzora/zora-protocol/issues/428#issuecomment-2274200337, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAGMCOEN34WKDH2SPX6EC6LZQJYU3AVCNFSM6AAAAABK7ASWMWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZUGIYDAMZTG4 . You are receiving this because you commented.Message ID: @.***>

babyuniverse commented 1 month ago

Are you able to publish the failing txn so i can take a look?

I use Tenderly to simulate transactions, I have a shared link for the latest attempt at a transaction, you can see the error Mint_SoldOut persists: https://www.tdly.co/shared/simulation/986c85eb-112e-474b-8edf-a9e0743ca943

babyuniverse commented 1 month ago

The simulation shows the transaction is being reverted specifically at the ERC721Drop.sol: /// @notice Allows user to mint tokens at a quantity modifier canMintTokens(uint256 quantity) { if (quantity + _totalMinted() > config.editionSize) { revert Mint_SoldOut();

  Which is being called from the adminMint function:
      function adminMint(address recipient, uint256 quantity) external onlyRoleOrAdmin(MINTER_ROLE) canMintTokens(quantity) returns (uint256) {
    _mintNFTs(recipient, quantity);

    return _lastMintedTokenId();
}
function _mintNFTs(address to, uint256 quantity) internal {
    do {
        uint256 toMint = quantity > MAX_MINT_BATCH_SIZE ? MAX_MINT_BATCH_SIZE : quantity;
        _mint({to: to, quantity: toMint});
        quantity -= toMint;
    } while (quantity > 0);
}
https://github.com/ourzora/zora-721-contracts/blob/main/src/ERC721Drop.sol#L123

So the issue isn't with the createAndConfigureDrop and the ZoraNFTCreatorV1 or the ABIs. 
Tenderly says that the revert is happening at_requireCanMintQuantity where the value that is being passed is (0x).
My editionSize and my mint quantity are both set to 1 so I'm confused as to where 0x comes from , what part of my code determines what value gets passed to _requireCanMintQuantity?

It appears to happen right as the salesConfig data is being encoded when I examine this part of the simulation :

ERC721Drop . SalesConfigChanged (changedBy = 0x899ce31df6c6af81203acaad285bf539234ef4b8 ) JUMP 39

ERC721Drop . verifyCallResult (success, returndata, errorMessage) => (0x) JUMP 5,316

ERC721Drop . functionDelegateCall (target, data) D·CALL 4,493

0x175b0092075748fb3234e04ebc3c7521aba485ba . 0xe58306f9 (recipient = 0x0bfd03a69ffe09004981339cdfbba8b4b4222c14 , quantity = 1) SLOAD 100

0x175b0092075748fb3234e04ebc3c7521aba485ba [ 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc = 0x00000000000000000000000094eb9ea7639886a0436126742778e949bfbce33a ] D·CALL 4,158

( 0x175b0092075748fb3234e04ebc3c7521aba485ba => ERC721Drop ) . adminMint (recipient = 0x0bfd03a69ffe09004981339cdfbba8b4b4222c14 , quantity = 1) SLOAD 100

ERC721Drop [ 0xd07751141ebea5cf0db13a5ce603cbd45cba08ae3f547f30b6e69a71e138c63c = 0x0000000000000000000000000000000000000000000000000000000000000001 ] JUMP 2,319

ERC721Drop . _requireCanMintQuantity (quantity) SLOAD 100

ERC721Drop [ 0x0000000000000000000000000000000000000000000000000000000000000065 = 0x0000000000000000000000000000000000000000000000000000000000000001 ] SLOAD 2,100

ERC721Drop [ 0x0000000000000000000000000000000000000000000000000000000000000160 = 0x0000000000000000000000000000000000000000000000000000000000000000 ] REVERT 0

ERC721Drop . _requireCanMintQuantity (0x) JUMP 66

ERC721Drop . verifyCallResult (success, returndata, errorMessage) REVERT 0

ERC721Drop . verifyCallResult (0x)

This is what is logged as the arguments being passed in the console when I try to mint: Args for createAndConfigureDrop: (10) ['Playground Pic', 'PP', '0x0BfD03A69Ffe09004981339CdfbBA8b4b4222C14', 1n, 300, '0x0BfD03A69Ffe09004981339CdfbBA8b4b4222C14', Array(2), '0x7d1a46c6e614A0091c39E102F2798C27c1fA8892', '0x000000000000000000000000000000000000000000000000…0000000000000000000000000000000000000000000000000', '0x124F3eB5540BfF243c2B57504e0801E02696920E'] 0 : "Playground Pic" 1 : "PP" 2 : "0x0BfD03A69Ffe09004981339CdfbBA8b4b4222C14" 3 : 1n 4 : 300 5 : "0x0BfD03A69Ffe09004981339CdfbBA8b4b4222C14" 6 : (2) ['0xffdb71630000000000000000000000000000000000000000…0000000000000000000000000000000000000000000000000', '0xe58306f90000000000000000000000000bfd03a69ffe0900…0000000000000000000000000000000000000000000000001'] 7 : "0x7d1a46c6e614A0091c39E102F2798C27c1fA8892" 8 : "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000036697066733a2f2f516d597742457456554150376a73337a66765843746d69705353594c336e466e6b34337156557971424b365070462f000000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d59414e34734d614650564d6f6956354e384d68686b773348376a4d7370757238786d34615947673874714532000000000000000000000000000000000000000000000000000000000000000000000000000000000000023078000000000000000000000000000000000000000000000000000000000000" 9 : "0x124F3eB5540BfF243c2B57504e0801E02696920E" length : 10 [[Prototype]] : Array(0)

iainnash commented 1 day ago

Hi sorry for the delay – have you figured this out?