News & Blog
Stay informed with the latest insights, trends, and updates from SolanaLink.
Stay informed with the latest insights, trends, and updates from SolanaLink.
# **An Architect's Guide to Production-Grade BEP-721 NFTs on BSC with Foundry and TypeScript**

## **Section 1: The Modern Development Stack: Foundry, Anvil, and TypeScript**
The landscape of smart contract development is undergoing a significant transformation, driven by a demand for higher performance, enhanced security, and more efficient developer workflows. This section introduces a modern, high-performance technology stack centered around the Foundry toolchain and TypeScript, justifying its selection for building production-grade Non-Fungible Tokens (NFTs) on the Binance Smart Chain (BSC). It provides a comprehensive guide to establishing a robust development environment, which is the critical first step in the smart contract lifecycle.
### **1.1 Introduction: The Paradigm Shift Towards Performance and Solidity-Native Tooling**
Historically, Ethereum Virtual Machine (EVM) development has been dominated by JavaScript-based frameworks. However, a new generation of tools is emerging, prioritizing speed and a development experience that is more native to the blockchain environment. Foundry represents the vanguard of this movement. It is a "blazing fast, portable and modular toolkit" for Ethereum application development, written entirely in Rust, which affords it significant performance advantages over its predecessors.1
The power of Foundry lies in its cohesive suite of command-line tools, each designed to address a specific stage of the development lifecycle 2:
* **Forge**: The core of the toolchain, Forge is responsible for compiling, testing, and deploying smart contracts. Its standout feature is the ability to write tests directly in Solidity, which eliminates the context switching required when using JavaScript-based testing frameworks.1
* **Anvil**: A local testnet node, analogous to Ganache or Hardhat Network. Anvil is engineered for speed, enabling rapid iteration and testing in a controlled environment that faithfully mimics a live blockchain.2
* **Cast**: A versatile command-line interface for performing Ethereum RPC calls. It allows developers to interact with contracts, send transactions, and query chain data directly from the terminal, serving as a "Swiss army knife" for on-chain interactions.2
* **Chisel**: A Solidity REPL (Read-Eval-Print Loop) that provides an interactive environment for quickly testing snippets of Solidity code without the overhead of a full project setup.2
The selection of this stack—Foundry for on-chain logic and TypeScript for off-chain orchestration—embodies a sophisticated architectural pattern. It represents a deliberate separation of concerns, leveraging the optimal tool for each domain. Foundry's Rust-based core and Solidity-native testing create a highly secure and performant environment for authoring and verifying the smart contract itself. Concurrently, TypeScript offers a robust, type-safe ecosystem for managing complex off-chain tasks such as interacting with external APIs (like IPFS for metadata storage), orchestrating multi-step deployment processes, and integrating with dApp backends and frontends. This bifurcation allows developers to harness Foundry's unparalleled speed for the compile-test loop while retaining the flexibility and extensive library support of the Node.js ecosystem for all off-chain logic, resulting in a more resilient and maintainable final product.6
### **1.2 System Prerequisites and Environment Configuration**
Before installing the Foundry toolchain, it is essential to configure the host system with the necessary prerequisites. The installation process varies slightly depending on the operating system.
For **Linux and macOS**, the setup is generally straightforward.8 The primary dependency is the Rust programming language toolchain, which includes the Rust compiler (
rustc) and its package manager, Cargo. The recommended method for installing these is via rustup, the official Rust toolchain installer, available at rustup.rs.9
For **Windows**, the process requires an additional step. Foundry's installer, foundryup, does not currently support native Windows terminals like PowerShell or Command Prompt (Cmd). Therefore, Windows users must first install a Unix-like environment. The recommended options are **Windows Subsystem for Linux (WSL)** or **Git BASH**. Once one of these environments is active, the installation process mirrors that of Linux.4
Regardless of the operating system, the following components are required:
1. **Rust Toolchain**: As mentioned, this is the core dependency for Foundry itself.
2. **Node.js and npm**: These are required for the TypeScript portion of the project, which will be used for deployment and interaction scripts. They are essential for managing JavaScript packages and running the scripts.6
3. **Git**: Foundry's default dependency management system relies on git submodules, making Git a mandatory prerequisite for any project that includes external libraries like OpenZeppelin contracts.11
### **1.3 Installing the Foundry Toolchain with foundryup**
With the prerequisites in place, the installation of Foundry is handled by foundryup, its official installer and version manager. This tool simplifies the process by fetching the latest stable, precompiled binaries of the entire toolchain.
To initiate the installation, execute the following command in your terminal (or Git BASH/WSL on Windows) 1:
Bash
curl \-L https://foundry.paradigm.xyz | bash
This command downloads and runs the foundryup installation script. Follow the on-screen prompts, which will typically involve adding the Foundry binaries' location to your system's PATH. After the script completes, close and reopen your terminal session to ensure the PATH changes take effect.
Next, run the foundryup command itself to install the toolchain components 8:
Bash
foundryup
This command downloads the latest stable versions of forge, cast, anvil, and chisel. For developers wishing to work with the most recent features, foundryup also supports installing nightly builds via foundryup \--install nightly.9
To confirm a successful installation, verify that each component is accessible from the command line by checking its version 8:
Bash
forge \--version
anvil \--version
cast \--version
chisel \--version
Each command should return the installed version number, confirming that the Foundry environment is correctly configured and ready for use.
### **1.4 Project Initialization and Structure**
Foundry provides a standardized project structure that promotes consistency and best practices. To create a new project, use the forge init command followed by the desired project name.2
Bash
forge init bsc-nft-project
cd bsc-nft-project
This command creates a new directory named bsc-nft-project, navigates into it, and populates it with a default template. A key aspect of this process is that forge init also initializes a new Git repository, as Foundry's dependency management is tightly integrated with Git.13
The default project structure is organized as follows 4:
* src/: This directory is the designated location for all smart contract source files (e.g., .sol files).
* test/: Contains the Solidity-based test files. By convention, test files are named with a .t.sol extension.
* script/: Intended for Solidity scripting files, which are used for more complex deployment and on-chain interaction scenarios. These typically end in .s.sol.
* lib/: This directory houses project dependencies, which are managed as git submodules. The default template includes forge-std, the standard library required for testing.
* foundry.toml: The project's central configuration file. This is where compiler settings, RPC endpoints, and verification service details are defined.
### **1.5 Configuring foundry.toml for the Binance Smart Chain (BSC)**
The foundry.toml file is the control center for the project, allowing developers to configure how Forge compiles, tests, and deploys contracts.4 For a BSC project, it is crucial to configure the RPC endpoints and the BscScan verification service. This pre-configuration streamlines all subsequent interactions with the BSC network.
Open the foundry.toml file and add the following sections. It is best practice to use environment variables for sensitive information like RPC URLs and API keys to avoid committing them to version control.
Ini, TOML
\[profile.default\]
src \= "src"
out \= "out"
libs \= \["lib"\]
\# Configure RPC endpoints for BSC
\[rpc\_endpoints\]
bsc\_mainnet \= "${BSC\_MAINNET\_RPC\_URL}"
bsc\_testnet \= "${BSC\_TESTNET\_RPC\_URL}"
\# Configure BscScan for automated contract verification
\[etherscan\]
bsc\_mainnet \= { key \= "${BSCSCAN\_API\_KEY}", url \= "https://api.bscscan.com/api", chain \= 56 }
bsc\_testnet \= { key \= "${BSCSCAN\_API\_KEY}", url \= "https://api-testnet.bscscan.com/api", chain \= 97 }
This configuration achieves two key objectives:
1. **RPC Endpoints**: It defines aliases (bsc\_mainnet, bsc\_testnet) that can be used in Foundry commands to specify the target network. Foundry will automatically substitute the values from the corresponding environment variables (BSC\_MAINNET\_RPC\_URL, BSC\_TESTNET\_RPC\_URL) at runtime.14
2. **Etherscan/BscScan Configuration**: It sets up the necessary parameters for forge to communicate with the BscScan API for automated source code verification. This includes the API key (again, from an environment variable) and the specific API endpoint URL for each network.16 By defining these profiles, the verification process becomes a simple flag in the deployment command rather than a manual, multi-step procedure.
## **Section 2: Authoring a Production-Ready BEP-721 Smart Contract**
With the development environment established, the focus shifts to implementing the core on-chain logic. This section details the process of writing a secure, feature-rich, and standards-compliant BEP-721 NFT contract. It leverages the industry-standard OpenZeppelin Contracts library, managed through Foundry's native dependency system, and emphasizes best practices for code organization and clarity.
### **2.1 Dependency Management: Integrating OpenZeppelin Contracts**
Foundry manages external Solidity libraries as git submodules, a decentralized approach that pulls the entire source code of a dependency directly into the project's repository.14 This contrasts with npm-based package management and provides greater transparency and verifiability. The industry standard for reusable and secure smart contract components is OpenZeppelin Contracts.
To add the OpenZeppelin library to the project, execute the forge install command 17:
Bash
forge install OpenZeppelin/openzeppelin-contracts
This command clones the OpenZeppelin contracts repository into the lib/ directory, making its contracts available for import.18 For production environments, it is critical to use a specific, tagged release of the library rather than the default
master branch, which is a development branch and may contain unaudited changes. To install a specific version, you can append the tag to the repository path, for example: OpenZeppelin/openzeppelin-contracts@v5.0.2.
This dependency management strategy reflects a core principle of smart contract development: treating dependencies as integral, auditable source code rather than opaque packages. By including the full, version-controlled source of a library within the project, developers ensure that the code being compiled and deployed is exactly what is expected, which is paramount for security audits and on-chain verification.
### **2.2 The Crucial Role of Remappings**
Remappings are a foundational concept in the Foundry ecosystem that enables clean and portable import paths within Solidity code.20 They function as aliases, instructing the compiler to substitute a prefix in an
import statement with a different, local path. While Foundry can automatically infer some remappings, explicitly defining them in a remappings.txt file at the project's root is a critical best practice for clarity and compatibility with external tools like IDEs and verification services.17
After installing OpenZeppelin, create a remappings.txt file and add the following line 17:
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
This configuration tells the Solidity compiler a simple rule: whenever it encounters an import statement beginning with @openzeppelin/contracts/, it should resolve the rest of the path starting from the lib/openzeppelin-contracts/contracts/ directory within the project.20 This allows for the use of standardized, clean import paths that are common in the wider Ethereum ecosystem, such as
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";, rather than brittle, relative paths like import "../../lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";.
To automatically generate this file with all inferred remappings, one can use the command forge remappings \> remappings.txt.18
### **2.3 Smart Contract Implementation: A Line-by-Line Breakdown**
The BEP-721 standard on BSC is identical to the ERC-721 standard on Ethereum. We will create a contract named BscNft.sol in the src/ directory that implements a basic but secure NFT. This contract will include ownership controls, a mechanism for sequential token ID generation, and metadata management functions.
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract BscNft is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private \_tokenIdCounter;
// The base URI for the collection, can be set by the owner.
string private \_baseURIString;
// Constructor to set the collection name and symbol.
constructor(address initialOwner)
ERC721("BSC NFT Collection", "BNC")
Ownable(initialOwner)
{}
/\*\*
\* @dev Mints a new token and assigns it to \`to\`.
\* The token URI is set to the concatenation of the base URI and the token ID.
\* Can only be called by the contract owner.
\*/
function safeMint(address to) public onlyOwner {
uint256 tokenId \= \_tokenIdCounter.current();
\_tokenIdCounter.increment();
\_safeMint(to, tokenId);
}
/\*\*
\* @dev Sets the base URI for all token metadata.
\* Can only be called by the contract owner.
\*/
function setBaseURI(string memory baseURI\_) public onlyOwner {
\_baseURIString \= baseURI\_;
}
/\*\*
\* @dev Returns the base URI for the collection.
\*/
function \_baseURI() internal view override returns (string memory) {
return \_baseURIString;
}
// The following functions are overrides required by Solidity.
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
**Key Components of the Contract:**
* **Imports**: Utilizes the remappings defined in remappings.txt to import necessary contracts from OpenZeppelin: ERC721 for the core NFT functionality, ERC721URIStorage for per-token metadata, Ownable for access control, and Counters for securely generating sequential token IDs.22
* **Inheritance**: The contract inherits from ERC721, ERC721URIStorage, and Ownable to gain their respective functionalities.
* **State Variables**:
* using Counters for Counters.Counter; attaches the library's functions to the Counter struct.
* Counters.Counter private \_tokenIdCounter; declares a private counter to track the next token ID to be minted, preventing re-minting and other vulnerabilities.22
* string private \_baseURIString; stores the base URI for the collection's metadata.
* **Constructor**: The constructor initializes the ERC721 contract with the collection's name and symbol ("BSC NFT Collection", "BNC") and sets the initial owner of the contract using the Ownable constructor.17
* **safeMint Function**: This function provides the mechanism for creating new NFTs. It is restricted by the onlyOwner modifier, ensuring only the contract deployer can mint. It securely increments the token ID, mints the new token to the specified recipient address to, and implicitly relies on the \_baseURI and tokenURI functions for metadata handling.23
* **URI Management**: The setBaseURI and \_baseURI functions provide a standard way to manage the metadata for the entire collection. Marketplaces and wallets call the public tokenURI function, which in this implementation (by inheriting from ERC721URIStorage) will automatically concatenate the base URI with the specific token ID to form the full metadata URL.25
* **Overrides**: The tokenURI and supportsInterface functions are overridden because they are defined in multiple parent contracts (ERC721 and ERC721URIStorage). This is a standard requirement by the Solidity compiler to resolve ambiguity.
### **2.4 Compilation and Artifacts**
After writing the smart contract, the next step is to compile it into EVM-executable bytecode and generate its Application Binary Interface (ABI). Forge simplifies this process with a single command.
From the project's root directory, run:
Bash
forge build
This command triggers the compilation process.3 Foundry is highly efficient; it automatically detects the required Solidity compiler version specified in the contract (
pragma solidity ^0.8.20;) and downloads it if necessary. It also caches compilation results, so subsequent builds will only recompile files that have changed, dramatically speeding up the development cycle.1
Upon successful compilation, two new directories are created:
* cache/: Contains cached data used by Forge to accelerate future compilations.
* out/: This is the most important directory for off-chain interaction. It contains the compilation artifacts. Inside out/, a directory structure mirroring src/ is created. For our contract, the path will be out/BscNft.sol/. Within this folder, the crucial file is BscNft.json. This JSON artifact contains vital information, including:
* **ABI**: The Application Binary Interface, a JSON array that describes the contract's functions and events, which is essential for any off-chain tool (like ethers.js) to know how to encode calls to the contract.
* **Bytecode**: The compiled EVM bytecode that will be deployed to the blockchain to create the contract instance.
This artifact file is the critical link between the on-chain world of Solidity and the off-chain world of TypeScript. The deployment script will read this file to get the necessary components to deploy and later interact with the contract.6
## **Section 3: Rigorous On-Chain Testing with Forge**
A cornerstone of secure smart contract development is a comprehensive and rigorous testing suite. Foundry excels in this domain by enabling developers to write tests in Solidity, the same language as the contract itself. This approach eliminates the cognitive overhead and potential for subtle bugs that can arise from context switching between Solidity and JavaScript. This section details how to leverage Forge's powerful testing framework and "cheatcodes" to build a robust test suite that ensures the BscNft contract behaves exactly as intended under various conditions.
### **3.1 The Philosophy of Writing Tests in Solidity**
Writing tests in Solidity provides an unparalleled advantage: the tests are executed in the same EVM environment as the contract. This means that data types, arithmetic, and execution semantics are identical, removing a whole class of potential discrepancies that can occur when testing from an external JavaScript environment.1 The testing capabilities are provided by the
forge-std library, which is included by default in new Foundry projects. This library contains the base Test contract, which provides access to assertion functions and the powerful EVM cheatcodes.2
### **3.2 Structuring the Test Suite**
All test files reside in the test/ directory. By convention, they are named after the contract they are testing, with a .t.sol suffix. For our project, we will create test/BscNft.t.sol.
The basic structure of a test file involves:
1. Importing forge-std/Test.sol.
2. Importing the contract to be tested.
3. Defining a new contract that inherits from Test.
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/BscNft.sol";
contract BscNftTest is Test {
BscNft public bscNft;
address public owner \= address(this);
address public user \= address(0x1);
// Test setup and individual test cases will go here.
}
Here, BscNftTest inherits from Test, giving it access to all of Forge's testing utilities. We also declare a public instance of our BscNft contract and define some address variables for convenience in our tests.12
### **3.3 The setUp Function and Test Initialization**
To ensure that each test case runs in a clean, isolated state, Foundry provides a special setUp function. This function is executed before every single test function (any function prefixed with test) within the test contract.2 It is the ideal place to deploy a fresh instance of the contract under test.
Solidity
function setUp() public {
// The 'owner' of the contract will be the test contract itself.
bscNft \= new BscNft(owner);
}
In this setUp function, a new BscNft contract is deployed, and its address is assigned to the bscNft state variable. The deployer and initial owner of the contract is set to owner, which is the address of the BscNftTest contract itself.
### **3.4 Writing Unit Tests with Assertions**
Test functions are public functions whose names are prefixed with test. Inside these functions, assertions are used to verify that the contract's state changes as expected. forge-std provides a rich set of assertion functions, with assertEq (assert equal) being one of the most common.4
Here are examples of basic unit tests for the BscNft contract:
Solidity
// Test that the contract is deployed with the correct name and symbol.
function test\_Initialization() public {
assertEq(bscNft.name(), "BSC NFT Collection");
assertEq(bscNft.symbol(), "BNC");
assertEq(bscNft.owner(), owner);
}
// Test the basic minting functionality.
function test\_SafeMint() public {
bscNft.safeMint(user);
assertEq(bscNft.ownerOf(0), user, "Owner of token 0 should be the user");
assertEq(bscNft.balanceOf(user), 1, "User balance should be 1");
}
The test\_Initialization function verifies that the constructor set the state variables correctly. The test\_SafeMint function calls the safeMint function and then asserts that the new token is owned by the correct user and that the user's token balance has been updated.
### **3.5 Advanced Testing with Foundry Cheatcodes**
Foundry's true power is unlocked through its "cheatcodes," which are special functions accessible via the vm (Virtual Machine) instance available in test contracts. These cheatcodes allow for direct manipulation of the EVM state, enabling the simulation of complex scenarios that would be difficult or impossible to set up otherwise.
**vm.prank(address)**: This cheatcode sets msg.sender for the very next external call. It is indispensable for testing access control logic.26
**vm.expectRevert()**: This cheatcode asserts that the next external call must revert. It can be used with a specific error message to ensure the call fails for the correct reason.23
We can combine these two cheatcodes to test the onlyOwner modifier on our safeMint function:
Solidity
// Test that only the owner can call safeMint.
function testFail\_MintFromNonOwner() public {
// Set the next caller to be 'user', who is not the owner.
vm.prank(user);
// Expect the call to revert with the specific Ownable error message.
vm.expectRevert("Ownable: caller is not the owner");
// Attempt to mint. This call is expected to fail.
bscNft.safeMint(user);
}
This test provides a high degree of confidence in the contract's access control. It simulates an adversarial scenario where an unauthorized user attempts to perform a privileged action and verifies that the contract correctly denies the request. This "state-centric" testing model, where the developer directly manipulates the EVM's execution context, is a fundamental advantage of Foundry. It encourages an adversarial mindset, making it more intuitive to test for security vulnerabilities and edge cases compared to traditional black-box testing approaches.
### **3.6 Running Tests and Interpreting Results**
To execute the entire test suite, run the forge test command from the project's root directory.13
Bash
forge test
For more verbose output, which is useful for debugging and viewing logs from Solidity's console.log, use the \-vv flag 11:
Bash
forge test \-vv
A successful test run will produce output similar to this:
test\_Initialization() (gas: 28800\)
test\_SafeMint() (gas: 123456\)
testFail\_MintFromNonOwner() (gas: 45678\)
Suite result: ok. 3 passed; 0 failed; 0 skipped;
The output clearly indicates which tests passed or failed and provides a gas report for each successful function call, which is invaluable for performance analysis and optimization.12
## **Section 4: NFT Metadata Preparation and Decentralized Storage**
An NFT's on-chain representation is merely a token ID and an owner's address. Its value, identity, and visual appeal are derived from its metadata—an off-chain JSON file that describes the token's name, description, image, and attributes. This section details the process of creating standards-compliant metadata and storing it on a decentralized network, a critical step that bridges the on-chain contract with its off-chain assets.
### **4.1 The ERC-721 Metadata Standard (EIP-721)**
The ERC-721 standard defines a tokenURI function that must return a URL pointing to a JSON document. This document must conform to the "ERC721 Metadata JSON Schema" to ensure interoperability across wallets, marketplaces, and other dApps.27
The primary fields in this JSON structure are 29:
* name: The name of the specific token.
* description: A human-readable description of the token.
* image: A URL pointing to the image file for the token. This URL should be immutable, ideally pointing to a decentralized storage location.
* attributes: An optional array of objects that define the traits of the NFT. This is a crucial field for marketplaces like OpenSea, which use it to display properties, calculate rarity, and provide filtering options. Each attribute object typically contains a trait\_type and a value.27
### **4.2 Crafting the Metadata Files**
For a collection, each token ID will have its own corresponding metadata JSON file. For example, the metadata for token ID 1 would be in a file named 1.json.
Here is an example of a 1.json metadata file:
JSON
{
"name": "BscNft \#1",
"description": "A unique digital collectible on the Binance Smart Chain.",
"image": "ipfs://\<IMAGE\_CID\_WILL\_GO\_HERE\>",
"attributes":
}
Note the image field is a placeholder. Its final value depends on the decentralized storage location of the corresponding image file, creating a dependency that must be resolved before the metadata itself can be finalized.31
### **4.3 Introduction to IPFS**
To ensure the longevity and immutability of NFT assets, storing them on centralized servers is strongly discouraged. The industry standard for decentralized storage is the InterPlanetary File System (IPFS). IPFS is a peer-to-peer network for storing and sharing data in a distributed file system. Unlike location-based addressing (like HTTP URLs), IPFS uses content-based addressing. Files are identified by a unique cryptographic hash of their content, known as a Content Identifier (CID). This means that if the content of a file changes, its CID also changes, providing a verifiable guarantee of immutability.31
### **4.4 Programmatic Upload to IPFS with Pinata and TypeScript**
While one can run a local IPFS node, using a "pinning service" like Pinata is a more practical and reliable solution for production dApps. A pinning service ensures that your files remain available on the IPFS network even when your local node is offline.
The process of preparing metadata reveals a critical dependency loop that necessitates an off-chain orchestration script. The on-chain safeMint function requires a final tokenURI, but this URI (pointing to the metadata file) can only be generated *after* the NFT's image has been uploaded to IPFS and its resulting CID has been embedded within the metadata file. This metadata file is then uploaded to get its own CID. This sequence—Image Upload → Get Image CID → Update Metadata JSON → Metadata Upload → Get Metadata CID → Call safeMint—cannot be executed on-chain or with simple CLI commands. It requires a script capable of handling file I/O and asynchronous API calls, a task for which TypeScript is perfectly suited.
First, set up a free account on Pinata to obtain an API JWT (JSON Web Token) and add it to your .env file as PINATA\_JWT.33 Then, install the Pinata SDK:
Bash
npm install @pinata/sdk
Next, create a TypeScript script, scripts/upload.ts, to automate the upload process:
TypeScript
import { PinataSDK } from "pinata";
import \* as fs from "fs";
import \* as path from "path";
import dotenv from "dotenv";
dotenv.config();
const pinata \= new PinataSDK({ pinataJwt: process.env.PINATA\_JWT\! });
async function uploadAsset() {
const imagePath \= path.join(\_\_dirname, "../assets/1.png");
const metadataTemplatePath \= path.join(\_\_dirname, "../assets/1.json");
try {
// 1\. Upload the image asset
const imageStream \= fs.createReadStream(imagePath);
const imageUploadResult \= await pinata.upload.public.file(imageStream, {
name: "BscNft\_1.png"
});
console.log("Image uploaded successfully. CID:", imageUploadResult.ipfsCid);
const imageIpfsUrl \= \`ipfs://${imageUploadResult.ipfsCid}\`;
// 2\. Read and update the metadata template
const metadataString \= fs.readFileSync(metadataTemplatePath, "utf-8");
const metadata \= JSON.parse(metadataString);
metadata.image \= imageIpfsUrl;
// 3\. Upload the updated metadata JSON
const metadataUploadResult \= await pinata.upload.public.json(metadata, {
name: "BscNft\_1.json"
});
console.log("Metadata uploaded successfully. CID:", metadataUploadResult.ipfsCid);
const metadataIpfsUrl \= \`ipfs://${metadataUploadResult.ipfsCid}\`;
console.log("\\nFinal Token URI for minting:", metadataIpfsUrl);
} catch (error) {
console.error("Error uploading to Pinata:", error);
}
}
uploadAsset();
This script performs the entire dependency chain: it uploads the image, gets its CID, updates the JSON metadata in memory, and then uploads the final metadata file.32 The final logged
metadataIpfsUrl is the tokenURI that will be passed to the smart contract's minting function in the next section.
## **Section 5: Orchestrating Deployment and Minting with TypeScript and ethers.js**
This section provides a detailed walkthrough of the deployment and interaction phase, fulfilling the core requirement of using TypeScript to manage the contract lifecycle. By leveraging the ethers.js library, we can create powerful and flexible scripts that bridge the gap between our local development environment and the live BSC testnet. These scripts will consume the artifacts generated by Foundry and use them to deploy the contract and mint a new NFT.
### **5.1 Setting up the TypeScript Environment**
While the Foundry project is already initialized, a parallel Node.js environment is needed to run our TypeScript scripts.
1. **Initialize npm**: In the root of your Foundry project, run npm init \-y to create a package.json file.12
2. **Install Dependencies**: Install the necessary packages for scripting, blockchain interaction, and environment management.6
Bash
npm install ethers dotenv typescript ts-node @types/node
3. **Configure TypeScript**: Create a tsconfig.json file in the project root to configure the TypeScript compiler.
JSON
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": \["scripts/\*\*/\*.ts"\]
}
4. **Environment Variables**: Create a .env file in the project root to securely store your private key and BSC testnet RPC URL. **Never commit this file to version control.** Add .env to your .gitignore file.14
BSC\_TESTNET\_RPC\_URL="\<YOUR\_BSC\_TESTNET\_RPC\_URL\>"
PRIVATE\_KEY="\<YOUR\_WALLET\_PRIVATE\_KEY\>"
### **5.2 The Deployment Script (scripts/deploy.ts)**
This script will read the compiled contract artifact from Foundry's out directory and deploy it to the BSC testnet.
Create a new file at scripts/deploy.ts:
TypeScript
import { ethers } from "ethers";
import \* as fs from "fs";
import \* as path from "path";
import dotenv from "dotenv";
dotenv.config();
async function main() {
// 1\. Set up provider and wallet
const provider \= new ethers.providers.JsonRpcProvider(process.env.BSC\_TESTNET\_RPC\_URL\!);
const wallet \= new ethers.Wallet(process.env.PRIVATE\_KEY\!, provider);
console.log(\`Using wallet: ${wallet.address}\`);
// 2\. Load contract artifact
const artifactPath \= path.join(\_\_dirname, "../out/BscNft.sol/BscNft.json");
const artifact \= JSON.parse(fs.readFileSync(artifactPath, "utf8"));
const { abi, bytecode } \= artifact;
// 3\. Create contract factory
const factory \= new ethers.ContractFactory(abi, bytecode, wallet);
// 4\. Deploy the contract
console.log("Deploying BscNft contract...");
const contract \= await factory.deploy(wallet.address); // Pass initialOwner to constructor
await contract.deployed();
console.log(\`Contract deployed to: ${contract.address}\`);
console.log(\`Transaction hash: ${contract.deployTransaction.hash}\`);
}
main().catch((error) \=\> {
console.error(error);
process.exitCode \= 1;
});
**Script Breakdown**:
1. **Provider and Wallet Setup**: It initializes a connection to the BSC testnet using the RPC URL from the .env file and creates a wallet instance from the private key.7
2. **Artifact Loading**: It reads the BscNft.json file generated by forge build, parsing it to extract the ABI and bytecode.6
3. **Contract Factory**: An ethers.ContractFactory is instantiated. This is an abstraction used to deploy new smart contracts.6
4. **Deployment**: The factory.deploy() method sends the transaction that creates the contract on the blockchain. contract.deployed() returns a promise that resolves once the contract is mined.7 The deployer's address is passed as an argument to the contract's constructor.
### **5.3 The Minting Script (scripts/mint.ts)**
After deployment, this script will interact with the live contract to mint an NFT.
Create a new file at scripts/mint.ts:
TypeScript
import { ethers } from "ethers";
import \* as fs from "fs";
import \* as path from "path";
import dotenv from "dotenv";
dotenv.config();
// \--- Configuration \---
const CONTRACT\_ADDRESS \= "\<PASTE\_DEPLOYED\_CONTRACT\_ADDRESS\_HERE\>";
const RECIPIENT\_ADDRESS \= "\<RECIPIENT\_WALLET\_ADDRESS\>";
// This should be the IPFS URL from the upload script in Section 4
const TOKEN\_URI \= "ipfs://\<PASTE\_METADATA\_CID\_HERE\>";
// \---------------------
async function main() {
// 1\. Set up provider and wallet
const provider \= new ethers.providers.JsonRpcProvider(process.env.BSC\_TESTNET\_RPC\_URL\!);
const wallet \= new ethers.Wallet(process.env.PRIVATE\_KEY\!, provider);
// 2\. Load ABI and create contract instance
const artifactPath \= path.join(\_\_dirname, "../out/BscNft.sol/BscNft.json");
const artifact \= JSON.parse(fs.readFileSync(artifactPath, "utf8"));
const { abi } \= artifact;
const nftContract \= new ethers.Contract(CONTRACT\_ADDRESS, abi, wallet);
console.log(\`Interacting with contract at: ${nftContract.address}\`);
// 3\. Call the safeMint function
console.log(\`Minting NFT for ${RECIPIENT\_ADDRESS} with URI: ${TOKEN\_URI}\`);
const tx \= await nftContract.safeMint(RECIPIENT\_ADDRESS);
console.log("Transaction sent. Waiting for confirmation...");
const receipt \= await tx.wait();
console.log(\`Transaction confirmed\! Hash: ${receipt.transactionHash}\`);
// Optional: Log the token ID from the event
const transferEvent \= receipt.events?.find(e \=\> e.event \=== 'Transfer');
if (transferEvent && transferEvent.args) {
console.log(\`Minted token ID: ${transferEvent.args.tokenId.toString()}\`);
}
}
main().catch((error) \=\> {
console.error(error);
process.exitCode \= 1;
});
**Script Breakdown**:
1. **Configuration**: The script requires the deployed contract address and the tokenURI from the IPFS upload step.
2. **Contract Instance**: Instead of a factory, it creates an ethers.Contract instance, which represents a specific contract already deployed on the network. This requires the address, ABI, and a signer (the wallet).37
3. **Function Call**: It directly calls the safeMint method on the contract instance, which encodes the function call and sends it as a transaction. tx.wait() pauses the script until the transaction is included in a block.37
### **5.4 Execution and Workflow**
To run the scripts, use ts-node from your terminal:
1. **Deploy the contract**:
Bash
npx ts-node scripts/deploy.ts
Copy the logged contract address from the output.
2. Mint an NFT:
Paste the deployed address into the CONTRACT\_ADDRESS variable in mint.ts. Also, update TOKEN\_URI with the value from the IPFS upload script. Then run:
Bash
npx ts-node scripts/mint.ts
The output will confirm the minting transaction. You can then view the transaction and the newly created NFT on a block explorer like BscScan Testnet.
### **5.5 Table: Comparing Foundry and TypeScript Deployment Approaches**
While this guide focuses on a TypeScript-driven workflow, it is valuable to understand its trade-offs compared to Foundry's native deployment tools. The choice of tool depends on the complexity of the deployment and the need for off-chain interactions.
| Feature | forge create / cast send | forge script | TypeScript (ethers.js) Script |
| :---- | :---- | :---- | :---- |
| **Primary Use Case** | Quick, single-contract deployments and interactions via CLI.24 | Complex, multi-transaction deployments and on-chain scripting.39 | Off-chain automation, dApp backend integration, complex deployment logic.6 |
| **Language** | Command-Line / Bash | Solidity | TypeScript / JavaScript |
| **Environment** | Terminal | On-chain simulation within Foundry's EVM | Node.js runtime |
| **Strengths** | Simplicity, speed for basic tasks. | Atomic, on-chain logic, access to cheatcodes for complex state manipulation. | Maximum flexibility, integration with JS libraries (e.g., IPFS), familiar for web developers. |
| **Limitations** | Limited logic, cumbersome for multi-step processes. | Solidity-only, cannot interact with off-chain APIs (e.g., Pinata). | Requires separate Node.js setup, more boilerplate code for simple deployments. |
For this project, the TypeScript approach is superior because of the requirement to interact with the Pinata API for metadata uploads before minting—a task that is impossible for the Solidity-based forge script.
## **Section 6: Post-Deployment and Advanced Concepts**
Deploying a smart contract is a significant milestone, but the lifecycle of a production-grade NFT project extends far beyond the initial transaction. This final section covers the essential post-deployment step of source code verification and introduces several advanced topics. These concepts—gas optimization for large-scale mints, contract upgradeability for future-proofing, and sophisticated allowlist mechanisms—are critical for building scalable, maintainable, and community-focused NFT projects. They represent the evolution from a basic implementation to a professional, long-term architecture.
### **6.1 Verifying the Contract on BscScan**
Source code verification is a crucial step for transparency and user trust. It publicly associates your deployed bytecode with its original Solidity source code, allowing anyone to inspect and audit the contract's logic on a block explorer like BscScan. Foundry's integration with Etherscan-compatible services makes this process straightforward.
Thanks to the foundry.toml configuration in Section 1, verification can be performed with a single command. You will need the contract address from the deployment step.
Bash
forge verify-contract \--chain bsc\_testnet \<CONTRACT\_ADDRESS\> src/BscNft.sol:BscNft \--constructor-args $(cast abi-encode "constructor(address)" \<INITIAL\_OWNER\_ADDRESS\>)
**Command Breakdown** 40:
* forge verify-contract: The base command for verification.
* \--chain bsc\_testnet: Specifies the network alias defined in foundry.toml. Foundry uses this to find the correct BscScan API URL and chain ID.16
* \<CONTRACT\_ADDRESS\>: The address of the deployed contract.
* src/BscNft.sol:BscNft: The path to the source file and the contract name, in the format \<path\>:\<contract\>.
* \--constructor-args $(...): This flag is used to provide any arguments that were passed to the contract's constructor during deployment. Since our constructor takes an initialOwner address, we must provide it.
* $(cast abi-encode "constructor(address)" \<INITIAL\_OWNER\_ADDRESS\>): We use cast to properly ABI-encode the constructor arguments. This is a common pattern for passing complex arguments to Foundry commands.25 Replace
\<INITIAL\_OWNER\_ADDRESS\> with the address you used during deployment (likely your wallet address).
Upon successful execution, Foundry will submit the source code and compilation settings to the BscScan API, and the contract's page on the block explorer will soon display a green checkmark indicating it is verified.
### **6.2 Advanced Topic: Gas Optimization with ERC721A**
The standard OpenZeppelin ERC721 implementation is secure and robust, but it can be gas-intensive when minting multiple NFTs in a single transaction. For large-scale public mints, this can lead to very high costs for users. The ERC721A standard, developed by the team behind the Azuki collection, is an extension of ERC721 designed specifically to optimize batch minting.43
The core optimization is that ERC721A updates ownership storage only once per batch mint for a given address, rather than once for each individual token. This results in nearly constant gas costs, regardless of the number of NFTs minted in a single transaction.
To implement this, you would:
1. Install the erc721a library: forge install chiru-labs/ERC721A.
2. Update remappings.txt: erc721a/=lib/ERC721A/contracts/.
3. Inherit from ERC721A in your contract instead of ERC721.
4. Use the \_safeMint(address to, uint256 quantity) function in your minting logic.
This pattern is now considered a best practice for projects anticipating high-volume minting events.
### **6.3 Advanced Topic: Upgradeability with UUPS Proxies**
Immutable smart contracts are a core tenet of blockchain technology, but this immutability can be a liability if a bug is discovered or if new features are needed post-deployment. Upgradeable contracts, implemented using a proxy pattern, solve this problem by separating the contract's state from its logic.
The modern, recommended approach is the **UUPS (Universal Upgradeable Proxy Standard)** pattern. In this model, the upgrade logic resides in the implementation contract itself, and a lightweight proxy contract delegates all calls to it.
To make an NFT contract upgradeable with Foundry, you would use OpenZeppelin's dedicated upgradeable contracts library and the openzeppelin-foundry-upgrades plugin.44 The high-level steps are:
1. Install the necessary libraries: forge install OpenZeppelin/openzeppelin-contracts-upgradeable and forge install OpenZeppelin/openzeppelin-foundry-upgrades.45
2. Modify the contract to inherit from ERC721Upgradeable and UUPSUpgradeable.47
3. Replace the constructor with an initialize function, as constructors cannot be used in implementation contracts for proxies.48
4. Use a Solidity script (.s.sol) and the Upgrades.deployUUPSProxy function from the plugin to deploy the proxy and the initial implementation contract in a single, atomic transaction.44
This architecture provides long-term maintainability, allowing the contract logic to be updated without requiring users to migrate their tokens to a new contract address.
### **6.4 Advanced Topic: Allowlist Minting with Merkle Trees**
For exclusive NFT drops, projects often need to restrict minting to a pre-approved list of addresses (an "allowlist" or "whitelist"). Storing a large array of addresses directly on-chain is prohibitively expensive due to gas costs. A far more gas-efficient solution is to use a **Merkle Tree**.50
A Merkle tree is a cryptographic data structure that allows for efficient and secure verification of content in a large dataset. The workflow is split between off-chain preparation and on-chain verification:
1. **Off-Chain (TypeScript)**:
* A list of all whitelisted addresses is compiled.
* Using libraries like merkletreejs and keccak256, a Merkle tree is constructed from these addresses.52
* The script generates two key pieces of data:
* **Merkle Root**: A single 32-byte hash that represents the entire tree. This is the only piece of data that needs to be stored on-chain.51
* **Merkle Proofs**: For each address in the list, a unique proof (an array of hashes) is generated. This proof is given to the user (e.g., via a backend API) and proves that their specific address is part of the tree.52
2. **On-Chain (Solidity)**:
* The smart contract is modified to store the bytes32 public merkleRoot generated off-chain.
* The minting function is updated to accept a bytes32 calldata merkleProof as an argument.
* Inside the function, it uses OpenZeppelin's MerkleProof.verify library. This function takes the user's proof, the stored Merkle root, and a "leaf" node (which is the hash of the msg.sender's address) as input.
* MerkleProof.verify returns true if the proof is valid for the given leaf and root, and false otherwise. The mint is only allowed to proceed if the verification is successful.50
This powerful pattern allows a contract to verify membership in a list of millions of addresses while only storing a single 32-byte hash, representing a massive gas saving and enabling scalable, exclusive access control for NFT collections.
#### **Works cited**
1. foundry-rs/foundry: Foundry is a blazing fast, portable and ... \- GitHub, accessed September 24, 2025, [https://github.com/foundry-rs/foundry](https://github.com/foundry-rs/foundry)
2. Getting started with Foundry \- Chainstack Docs, accessed September 24, 2025, [https://docs.chainstack.com/docs/getting-started-with-foundry](https://docs.chainstack.com/docs/getting-started-with-foundry)
3. Deploying a smart contract with Foundry \- Lisk Documentation, accessed September 24, 2025, [https://docs.lisk.com/building-on-lisk/deploying-smart-contract/with-Foundry/](https://docs.lisk.com/building-on-lisk/deploying-smart-contract/with-Foundry/)
4. Introduction to smart contract development using Foundry \- DEV Community, accessed September 24, 2025, [https://dev.to/abhinavxt/introduction-to-smart-contract-development-using-foundry-502l](https://dev.to/abhinavxt/introduction-to-smart-contract-development-using-foundry-502l)
5. Foundry \- Mint Blockchain, accessed September 24, 2025, [https://docs.mintchain.io/deploy/use-foundry](https://docs.mintchain.io/deploy/use-foundry)
6. Deploy EVM Contracts with Foundry \- Fordefi\!, accessed September 24, 2025, [https://docs.fordefi.com/developers/transaction-types/evm-deploy-contract-foundry](https://docs.fordefi.com/developers/transaction-types/evm-deploy-contract-foundry)
7. How to Deploy Smart Contracts Using ethers.js | by Daniel Alves | Medium, accessed September 24, 2025, [https://medium.com/@DanielAlvesOmnes/how-to-deploy-smart-contracts-using-ethers-js-c43127da8940](https://medium.com/@DanielAlvesOmnes/how-to-deploy-smart-contracts-using-ethers-js-c43127da8940)
8. How to Install Foundry on Windows/macOS/Linux \- Samarth's Web3 Journey, accessed September 24, 2025, [https://awesamarth.hashnode.dev/how-to-install-foundry](https://awesamarth.hashnode.dev/how-to-install-foundry)
9. Installation \- foundry \- Ethereum Development Framework, accessed September 24, 2025, [https://getfoundry.sh/getting-started/installation](https://getfoundry.sh/getting-started/installation)
10. Deploying smart-contract Using Foundry | by McTrick \- Medium, accessed September 24, 2025, [https://mctrick.medium.com/deploying-smart-contract-using-foundry-62379e3af723](https://mctrick.medium.com/deploying-smart-contract-using-foundry-62379e3af723)
11. Deploying Foundry: Complete End-To-End Tutorial for Smart Contract Development, accessed September 24, 2025, [https://www.hackquest.io/articles/deploying-foundry-complete-end-to-end-tutorial-for-smart-contract-development](https://www.hackquest.io/articles/deploying-foundry-complete-end-to-end-tutorial-for-smart-contract-development)
12. Introduction to Foundry | QuickNode Guides, accessed September 24, 2025, [https://www.quicknode.com/guides/ethereum-development/smart-contracts/intro-to-foundry](https://www.quicknode.com/guides/ethereum-development/smart-contracts/intro-to-foundry)
13. Creating a New Project \- foundry \- Ethereum Development Framework, accessed September 24, 2025, [https://getfoundry.sh/projects/creating-a-new-project](https://getfoundry.sh/projects/creating-a-new-project)
14. Deploy a Smart Contract with Foundry \- Hedera Docs, accessed September 24, 2025, [https://docs.hedera.com/hedera/getting-started-evm-developers/deploy-a-smart-contract-with-foundry](https://docs.hedera.com/hedera/getting-started-evm-developers/deploy-a-smart-contract-with-foundry)
15. Deploy smart contract using Foundry | Kaia Docs, accessed September 24, 2025, [https://docs.kaia.io/build/smart-contracts/deployment-and-verification/deploy/foundry/](https://docs.kaia.io/build/smart-contracts/deployment-and-verification/deploy/foundry/)
16. Verify with Foundry | Celo Documentation, accessed September 24, 2025, [https://docs.celo.org/developer/verify/foundry](https://docs.celo.org/developer/verify/foundry)
17. Contracts \- OpenZeppelin Docs, accessed September 24, 2025, [https://docs.openzeppelin.com/contracts/5.x/](https://docs.openzeppelin.com/contracts/5.x/)
18. How to easily import dependencies and use remappings in Foundry, accessed September 24, 2025, [https://awesamarth.hashnode.dev/how-to-easily-import-dependencies-and-use-remappings-in-foundry](https://awesamarth.hashnode.dev/how-to-easily-import-dependencies-and-use-remappings-in-foundry)
19. Launch and verify an NFT with Foundry | By RareSkills, accessed September 24, 2025, [https://rareskills.io/learn-solidity/deploy-nft-foundry](https://rareskills.io/learn-solidity/deploy-nft-foundry)
20. Dependencies \- foundry \- Ethereum Development Framework, accessed September 24, 2025, [https://getfoundry.sh/projects/dependencies](https://getfoundry.sh/projects/dependencies)
21. Remappings | Testing with Foundry \- YouTube, accessed September 24, 2025, [https://www.youtube.com/watch?v=7DK75j8csTA](https://www.youtube.com/watch?v=7DK75j8csTA)
22. ERC721 \- OpenZeppelin Docs, accessed September 24, 2025, [https://docs.openzeppelin.com/contracts/4.x/erc721](https://docs.openzeppelin.com/contracts/4.x/erc721)
23. Testing Smart Contracts with Foundry \- Ethereum Blockchain Developer, accessed September 24, 2025, [https://www.ethereum-blockchain-developer.com/courses/ethereum-course-2024/project-erc721-nft-with-remix-truffle-hardhat-and-foundry/testing-smart-contracts-with-foundry](https://www.ethereum-blockchain-developer.com/courses/ethereum-course-2024/project-erc721-nft-with-remix-truffle-hardhat-and-foundry/testing-smart-contracts-with-foundry)
24. Deploying a smart contract to Abstract using Foundry | by Taylor Caldwell | Medium, accessed September 24, 2025, [https://medium.com/@taycaldwell/deploying-a-smart-contract-to-abstract-using-foundry-6ceb92baa046](https://medium.com/@taycaldwell/deploying-a-smart-contract-to-abstract-using-foundry-6ceb92baa046)
25. How to Verify Smart Contracts Using Foundry \- Kaia Docs, accessed September 24, 2025, [https://docs.kaia.io/build/smart-contracts/deployment-and-verification/verify/foundry/](https://docs.kaia.io/build/smart-contracts/deployment-and-verification/verify/foundry/)
26. Foundry Unit Tests | By RareSkills, accessed September 24, 2025, [https://rareskills.io/post/foundry-testing-solidity](https://rareskills.io/post/foundry-testing-solidity)
27. Metadata standards | Mavis Docs, accessed September 24, 2025, [https://docs.skymavis.com/mavis/ronin-market/reference/metadata](https://docs.skymavis.com/mavis/ronin-market/reference/metadata)
28. Metadata Standards \- OpenSea Docs, accessed September 24, 2025, [https://docs.opensea.io/docs/metadata-standards](https://docs.opensea.io/docs/metadata-standards)
29. ERC721 \- OpenZeppelin Docs, accessed September 24, 2025, [https://docs.openzeppelin.com/contracts/2.x/erc721](https://docs.openzeppelin.com/contracts/2.x/erc721)
30. ERC721 | Immutable Documentation, accessed September 24, 2025, [https://docs.immutable.com/products/zkevm/contracts/erc721/](https://docs.immutable.com/products/zkevm/contracts/erc721/)
31. How to Create and Deploy an ERC-721 (NFT) | QuickNode Guides, accessed September 24, 2025, [https://www.quicknode.com/guides/ethereum-development/nfts/how-to-create-and-deploy-an-erc-721-nft](https://www.quicknode.com/guides/ethereum-development/nfts/how-to-create-and-deploy-an-erc-721-nft)
32. How do I upload files to IPFS? \- Pinata, accessed September 24, 2025, [https://pinata.cloud/blog/how-do-i-upload-files-to-ipfs/](https://pinata.cloud/blog/how-do-i-upload-files-to-ipfs/)
33. How to Mint NFTs with Foundry, Viem, and Pinata, accessed September 24, 2025, [https://pinata.cloud/blog/how-to-mint-nfts-with-foundry-viem-and-pinata/](https://pinata.cloud/blog/how-to-mint-nfts-with-foundry-viem-and-pinata/)
34. Uploading Files \- Pinata Docs, accessed September 24, 2025, [https://docs.pinata.cloud/files/uploading-files](https://docs.pinata.cloud/files/uploading-files)
35. Deploying smart-contract Using Hardhat | by McTrick \- Medium, accessed September 24, 2025, [https://mctrick.medium.com/deploying-smart-contract-using-hardhat-1e71f12000fa](https://mctrick.medium.com/deploying-smart-contract-using-hardhat-1e71f12000fa)
36. How to get deployed contract address with ethers? \- Ethereum Stack Exchange, accessed September 24, 2025, [https://ethereum.stackexchange.com/questions/145074/how-to-get-deployed-contract-address-with-ethers](https://ethereum.stackexchange.com/questions/145074/how-to-get-deployed-contract-address-with-ethers)
37. How to Mint an NFT Using Ethers.js \- Web3 University, accessed September 24, 2025, [https://www.web3.university/article/how-to-mint-an-nft-with-ethers-js](https://www.web3.university/article/how-to-mint-an-nft-with-ethers-js)
38. Deploying \- foundry \- Ethereum Development Framework, accessed September 24, 2025, [https://getfoundry.sh/forge/deploying/](https://getfoundry.sh/forge/deploying/)
39. Deploying a Smart Contract with Foundry \- Ethereum Blockchain Developer, accessed September 24, 2025, [https://www.ethereum-blockchain-developer.com/courses/ethereum-course-2024/project-erc721-nft-with-remix-truffle-hardhat-and-foundry/deploy-smart-contracts-using-foundry](https://www.ethereum-blockchain-developer.com/courses/ethereum-course-2024/project-erc721-nft-with-remix-truffle-hardhat-and-foundry/deploy-smart-contracts-using-foundry)
40. 5 Ways to Verify a Smart Contract | QuickNode Guides, accessed September 24, 2025, [https://www.quicknode.com/guides/ethereum-development/smart-contracts/different-ways-to-verify-smart-contract-code](https://www.quicknode.com/guides/ethereum-development/smart-contracts/different-ways-to-verify-smart-contract-code)
41. forge verify-contract \- foundry \- Ethereum Development Framework, accessed September 24, 2025, [https://getfoundry.sh/reference/forge/forge-verify-contract](https://getfoundry.sh/reference/forge/forge-verify-contract)
42. Deploying \- foundry-zksync, accessed September 24, 2025, [https://foundry-book.zksync.io/forge/deploying/](https://foundry-book.zksync.io/forge/deploying/)
43. How to Mint NFTs Using the ERC721A Implementation | QuickNode Guides, accessed September 24, 2025, [https://www.quicknode.com/guides/other-chains/polygon/how-to-mint-nfts-using-the-erc721a-implementation](https://www.quicknode.com/guides/other-chains/polygon/how-to-mint-nfts-using-the-erc721a-implementation)
44. Using with Foundry \- OpenZeppelin Docs, accessed September 24, 2025, [https://docs.openzeppelin.com/upgrades-plugins/foundry-upgrades](https://docs.openzeppelin.com/upgrades-plugins/foundry-upgrades)
45. OpenZeppelin/openzeppelin-foundry-upgrades: Foundry library for deploying and managing upgradeable contracts \- GitHub, accessed September 24, 2025, [https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades)
46. Smart Contract Foundry Upgrades with the OpenZeppelin Plugin | By RareSkills, accessed September 24, 2025, [https://rareskills.io/post/openzeppelin-foundry-upgrades](https://rareskills.io/post/openzeppelin-foundry-upgrades)
47. How to Upgrade an ERC-721 Token with OpenZeppelin UUPS Proxies and Hardhat (Part 3\) \- Hedera Docs, accessed September 24, 2025, [https://docs.hedera.com/hedera/tutorials/smart-contracts/how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3](https://docs.hedera.com/hedera/tutorials/smart-contracts/how-to-upgrade-an-erc-721-token-with-openzeppelin-uups-proxies-and-hardhat-part-3)
48. Upgradeable Smart Contracts \- OpenZeppelin UUPS Proxies \- Advanced Foundry \- Video, accessed September 24, 2025, [https://updraft.cyfrin.io/courses/advanced-foundry/upgradeable-smart-contracts/introduction-to-uups-proxies](https://updraft.cyfrin.io/courses/advanced-foundry/upgradeable-smart-contracts/introduction-to-uups-proxies)
49. A Quick & Dirty Guide to Smart Contract Upgrades with UUPS | by Coderlabny | CoinsBench, accessed September 24, 2025, [https://coinsbench.com/a-quick-dirty-guide-to-smart-contract-upgrades-with-uups-ca1d60415038](https://coinsbench.com/a-quick-dirty-guide-to-smart-contract-upgrades-with-uups-ca1d60415038)
50. Using Merkle Trees for NFT Whitelists | by Alan \- Medium, accessed September 24, 2025, [https://medium.com/@ItsCuzzo/using-merkle-trees-for-nft-whitelists-523b58ada3f9](https://medium.com/@ItsCuzzo/using-merkle-trees-for-nft-whitelists-523b58ada3f9)
51. Understand Merkle tree by making an NFT minting whitelist \- DEV Community, accessed September 24, 2025, [https://dev.to/peterblockman/understand-merkle-tree-by-making-a-nft-minting-whitelist-1148](https://dev.to/peterblockman/understand-merkle-tree-by-making-a-nft-minting-whitelist-1148)
52. Understand Merkle tree by making an NFT minting whitelist \- Peter Blockman, accessed September 24, 2025, [https://peterblockman.hashnode.dev/understand-merkle-tree-by-making-an-nft-minting-whitelist](https://peterblockman.hashnode.dev/understand-merkle-tree-by-making-an-nft-minting-whitelist)
53. Merkle Trees in Solidity and TypeScript | by Moh. Zar. | Jul, 2025 | CoinsBench, accessed September 24, 2025, [https://coinsbench.com/merkle-trees-in-solidity-and-typescript-ba29d817eaa1](https://coinsbench.com/merkle-trees-in-solidity-and-typescript-ba29d817eaa1)
54. The Ultimate Merkle Tree Guide in Solidity, accessed September 24, 2025, [https://soliditydeveloper.com/merkle-tree](https://soliditydeveloper.com/merkle-tree)
55. OpenZeppelin/merkle-tree: A JavaScript library to generate merkle trees and merkle proofs. \- GitHub, accessed September 24, 2025, [https://github.com/OpenZeppelin/merkle-tree](https://github.com/OpenZeppelin/merkle-tree)