Quickstart

Overview

Modular contracts are a composable set of contracts that can be combined to create a feature complete protocol. To learn more, refer to How it works

In this Quickstart, learn how to go over how to create an ERC-721 modular contract with fixed mint pricing

  • Create a new forge project and install the modular-contracts package

    Install Forge from this guide from Foundry

    forge init
    forge install https://github.com/thirdweb-dev/modular-contracts.git
    forge remappings > remappings.txt
  • Create the core contract

    In the /src folder, create a new file called ERC721Start.sol with the following code. This will inherit the prebuilt ERC721Core contract.

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.0;
    import {ERC721Core} from "modular-contracts/src/core/token/ERC721Core.sol";
    contract ERC721Start is ERC721Core {
    constructor(
    string memory _name,
    string memory _symbol,
    string memory _contractURI,
    address _owner
    )
    ERC721Core(
    _name,
    _symbol,
    _contractURI,
    _owner,
    new address[](0),
    new bytes[](0)
    )
    {}
    }
  • Deploy the core contract

    In your terminal, run the following command

    npx thirdweb deploy

    Then select ERC721Start After signing in, it should open up to the following page to deploy the ERC721Start contract

    After filling in the fields, select the Sepolia testnet chain and then hit Deploy

    If you need funds to deploy the contract, head over to the Sepolia faucet here

    After deploying the contract, you should be redirected to the deployed contract page

    Hold onto this page as you will need it for later

  • Create the extension contract

    Back in your forge project, in the /src folder, create a file called PricedExtension.sol and paste in the following code.

    For a better understanding of how extension contracts work, refer to the starter template code

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.0;
    import {ModularExtension} from "modular-contracts/src/ModularExtension.sol";
    import {BeforeMintCallbackERC721} from "modular-contracts/src/callback/BeforeMintCallbackERC721.sol";
    contract PricedMint is ModularExtension, BeforeMintCallbackERC721 {
    uint256 public constant mintPrice = 0.01 ether;
    function getExtensionConfig()
    external
    pure
    override
    returns (ExtensionConfig memory config)
    {
    config.callbackFunctions = new CallbackFunction[](1);
    config.callbackFunctions[0] = CallbackFunction(
    this.beforeMintERC721.selector
    );
    config.requiredInterfaces = new bytes4[](1);
    config.requiredInterfaces[0] = 0x80ac58cd; // ERC721.
    }
    function beforeMintERC721(
    address _to,
    uint256 _startTokenId,
    uint256 _quantity,
    bytes memory _data
    ) external payable virtual override returns (bytes memory) {
    require(msg.value == mintPrice * _quantity, "Insufficient ETH sent");
    }
    }
  • Publish the extension contract

    In your terminal, run the following command

    npx thirdweb publish
    

    Then select PricedMint it should open up to the following page to publish the PricedMint contract

    Accept the defaults and then hit "next" It should then redirect you to choose which chain to deploy on. Here we'll leave it on the Sepolia testnet Afterwards, hit "Publish Contract"

  • Install the Extension onto the Core contract

    Back to the deployed core contract page, then go and click on the "Manage" tab

    It should then redirect to the edit extensions page

    Here, fill out the info as needed and then hit "Install"

    After it has finished installing, it should then show up under the "Installed Extension" section

  • Test the modular contract

    Now with the modular contract fully up and ready to go, we can test that it setup properly by running the following script

    // Pre-requisite: install thirdweb <https://portal.thirdweb.com/typescript/v5>
    import {
    createThirdwebClient,
    getContract,
    prepareContractCall,
    sendTransaction,
    waitForReceipt,
    toUnits,
    } from "thirdweb";
    import { privateKeyToAccount } from "thirdweb/wallets";
    import { sepolia } from "thirdweb/chains";
    const PRIVATE_KEY = "<"; // Paste your private key here
    const SECRET_KEY = ""; // Paste your secret key here
    const TARGET_TOKEN_CORE_ADDRESS = ""; // Paste your target token core address here
    const MINTER_RECIPIENT_ADDRESS = ""; // Paste your minter recipient address here
    const client = createThirdwebClient({
    secretKey: SECRET_KEY,
    });
    const account = privateKeyToAccount({
    client,
    privateKey: PRIVATE_KEY,
    });
    const contract = getContract({
    client,
    address: TARGET_TOKEN_CORE_ADDRESS,
    chain: sepolia,
    });
    const transaction = prepareContractCall({
    contract,
    method: {
    type: "function",
    name: "mint",
    inputs: [
    { name: "to", type: "address", internalType: "address" },
    { name: "amount", type: "uint256", internalType: "uint256" },
    { name: "data", type: "bytes", internalType: "bytes" },
    ],
    value: toUnits("0.01", 18),
    outputs: [],
    stateMutability: "payable",
    },
    params: [MINTER_RECIPIENT_ADDRESS, 1, ""],
    });
    async function main() {
    // Send transaction
    const result = await sendTransaction({
    transaction: transaction,
    account: account,
    });
    const receipt = await waitForReceipt(result);
    console.log("NFT minted tx: ", receipt.transactionHash);
    }
    main();