# Gasless NFT minting app w/ScaffoldETH + Coinbase Smart Wallet *For Devcon 24' workshop, built by [Shiv Bhonde](https://github.com/technophile-04) & Eda Akturk* In this workshop, we’ll build a **gasless NFT minting app** using **Scaffold-ETH 2**. By the end, you'll have a NFT minting app that allows users to mint NFTs without paying for gas, using the Coinbase Smart Wallet. ![Screenshot 2024-11-21 at 11.29.56](https://hackmd.io/_uploads/SkCyxdhzke.png) ### Tools & Resources We'll Use - [Scaffold-ETH 2](https://scaffoldeth.io/): A developer toolset for building decentralized apps quickly. - Foundry: A blazing-fast, modular toolkit for Ethereum smart contract development. - [EIP-721](https://eips.ethereum.org/EIPS/eip-721): Standard for NFT contracts. - [EIP-4337](https://www.erc4337.io/) (Account Abstraction): This EIP allows smart contracts to function as wallets, enabling advanced features like batching and gas sponsorship, which are essential for user-friendly dApps. - [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) (Wallet call api): Methods for batching transactions and using paymasters to cover gas fees, streamlining wallet interactions for better efficiency and experience. - [EIP-7677](https://eips.ethereum.org/EIPS/eip-7677) (Paymaster Contract): Standardizes gas sponsorship, making it easier for dApps to cover user transaction fees, enhancing the user experience. - DaisyUI: A Tailwind CSS-based component library for styling. - [Coinbase Smart Wallet](https://www.coinbase.com/en-tr/wallet/smart-wallet) --- ## Step-by-step ### 1. Set Up an SE2 App - **Prerequisites**: Make sure you have **Foundry** installed. ``` npx create-eth@latest ``` - **Note**: When creating the app, **do not add extra spaces** in the title. - Choose foundry as your smart contract development environment > ? What solidity framework do you want to use? foundry" Once downloaded, run your app: ``` yarn chain // start your local anvil env yarn deploy // deploys a test smart contract to the local network yarn start // start your NextJS app ``` Visit: http://localhost:3000 _Features to explore:_ - Burner wallets - Built in faucet - Debug contracts tab ### 2. Add a new NFT Contract - Go to "https://wizard.openzeppelin.com/#erc721" to get a template for an ERC721 contract. - Make the contract Mintable and Enumerable - Fill the baseURI with an image for your NFT. (idea: generate an image with Dall-e) - Create a new file called `NFT.sol` in the `packages/foundry/contracts` folder. Add you contract here. Some changes to the wizard generated contract: - Make `_nextTokenId` public so that we count remaining supply - Add `uint256 public maxSupply = 10;` to limit the number of NFTs that can be minted. - Create `mapping(address => bool) public alreadyMinted;` - Update safeMint with extra require statements + make alreadyMinted[to] = true ```solidity function safeMint(address to) public { require(_nextTokenId < maxSupply, "Max supply reached"); require(!alreadyMinted[to], "Already minted"); alreadyMinted[to] = true; // add this uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); } ``` ### 3. Create a new deployment script and deploy the NFT Contract - Create a new deployment script in the `packages/foundry/script` folder. Check the contents of the `DeployYourContract.s` and make the adjusments for the new NFT contract. - Change import instead import NFT.sol - Change the line of YourContract ⇒ `NFT nft = new NFT()` (we had removed the deployer from constructor) - Add the new deployment script to `Deploy.s.sol`. And deploy the contracts by running: ``` yarn deploy --reset ``` - Go to the **Debug** tab in your SE2 app to review the contract details and confirm the deployment. ### 4. Start Building the Frontend - Create a new folder named `/nft` in `packages/nextjs/app` and a page.tsx file inside it. _We'll use **DaisyUI components** to build a pretty NFT minting interface. Check them out here: https://daisyui.com/components/card/_ Steps: - Get max supply for the NFT collection and tokenID with the useScaffoldReadContract Hooks. ``` const { data: supply } = useScaffoldReadContract({ contractName: "NFT", functionName: "maxSupply", }); const { data: tokenID } = useScaffoldReadContract({ contractName: "NFT", functionName: "_nextTokenId", }); ``` - Get the NFT's left to mint. ``` const [remaining, setRemaining] = useState<number | null>(null); useEffect(() => { if (supply !== undefined) { setRemaining(Number(supply) - (tokenID ? Number(tokenID) : 0)); } }, [tokenID, supply]); ``` - Create a mint button with the useScaffoldWriteContract hooks. ``` const { writeContractAsync: writeScaffoldContractAsync } = useScaffoldWriteContract("NFT"); <button className="btn btn-primary" onClick={async () => { try { await writeScaffoldContractAsync({ functionName: "safeMint", args: [connectedAddress], }); } catch (e) { console.error("Error minting NFT:", e); } }} > Mint NFT </button> ``` - Create a new component to display the NFT's that the user has. Here is how the page should be: `app/nft/_components/MyHoldings.tsx`. We're using the same [component](https://github.com/scaffold-eth/se-2-challenges/blob/challenge-0-simple-nft/packages/nextjs/app/myNFTs/_components/MyHoldings.tsx) from the simple NFT challange of [speedrunethereum](https://speedrunethereum.com/). Import this into `app/nft/page.tsx` Run your app to make sure all is working. ### 5. Create a link in the header Create a link in `Header.tsx` to navigate to the NFT minting page. ``` { label: "NFT", href: "/nft", }, ``` ### 6. Add coinbase smart wallet + gasless transaction - Modify your SE2 app to support only the **Coinbase Smart Wallet**. Go `wagmiConnectors.ts` remove other wallet settings, and add the following: ``` coinbaseWallet.preference = "smartWalletOnly"; const wallets = [coinbaseWallet]; ``` - We will use wagmis experimental hook called [`useWriteContracts`](https://wagmi.sh/react/api/hooks/useWriteContracts), and add it to our `page.tsx` ``` // wagmi hook to batch write to multiple contracts (EIP-5792 specific) const { writeContractsAsync } = useWriteContracts(); // add this later to read address and contract abi const { data: NFT } = useDeployedContractInfo("NFT"); ``` - Create a new `Gasless mint button`. This will use a paymaster to cover the transaction cost. ``` <button className="btn btn-primary w-45" onClick={async () => { try { if (!NFT) return; // TODO: update the ENV const paymasterURL = process.env.NEXT_PUBLIC_PAYMASTER_URL; await writeContractsAsync({ contracts: [ { address: NFT.address, abi: NFT.abi, functionName: "safeMint", args: [connectedAddress], }, ], capabilities: { paymasterService: { url: paymasterURL, }, }, }); notification.success("NFT minted"); } catch (e) { console.error("Error minting NFT:", e); } }} > Gasless Mint </button> ``` - Get the PAYMASTER URL from [Coinbase CDP](https://www.coinbase.com/en-tr/developer-platform) and add it to env `NEXT_PUBLIC_PAYMASTER_URL` - onchain tools ⇒ Paymaster ⇒ configuration (make sure you switch to base sepolia) - Create a `.env.local` and add `NEXT_PUBLIC_PAYMASTER_URL` ### 7. Deploy your contract to Base Sepolia - Run `yarn generate` to create an account for deployment. - Update the `.env` inside foundry with `scaffold-eth-custom` - Send some base sepolia funds to the deployer account `yarn account` - Deploy your contract with `yarn deploy --network baseSepolia` ### 8. Update your app to point to Base Sepolia and Ship it - Update `scaffold.config.ts`: ``` targetNetworks: [chains.baseSepolia], pollingInterval: 3000, ``` - Ship to Vercel: `yarn vercel:yolo` - Go to vercel deployment UI -> add enviornment variable -> paste the `NEXT_PUBLIC_PAYMASTER_URL` and then run `yarn vercel:yolo` again There you go, a gassless nft minting app! 🚀