In this tutorial we are going to be deploying a simple NFT contract to Ethereum and then minting new NFT’s using a bot built with Rust and ethers-rs
Let’s start by heading to Remix and deploying this contract to Ethereum’s Sepolia testnet. It has already been deployed here if you want to skip this step: https://sepolia.etherscan.io/address/0xaf3148fe00021529cbdbd6129383119dc16bdf0c#code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract NFT is ERC721 {
uint256 private _nextTokenId;
constructor() ERC721("Generic NFT", "NFT") {}
function mint(address _to) public {
uint256 tokenId = _nextTokenId++;
_safeMint(_to, tokenId);
}
}
This ERC721 contract has a mint function which takes an address as an input, this mints a new NFT to the address provided.
Mint An NFT With Rust
Let’s call this function from within a Rust script/bot.
cargo new nft-minter-rs
cd nft-minter-rs
code .
Edit the cargo.toml file and add the following dependencies
[dependencies]
ethers = "2.0"
tokio = { version = "1.0", features = ["macros"] }
eyre = "0.6"
dotenv = "0.15.0"
Next set up a .env file with two lines:
PRIVATE_KEY=
INFURA_API_KEY=
Now let’s go ahead and edit src/main.rs
use ethers::prelude::*;
use ethers::providers::{Provider, Http};
use ethers::signers::{LocalWallet, Signer};
use std::sync::Arc;
use std::convert::TryFrom;
use std::time::Duration;
use dotenv::dotenv;
use std::env;
abigen!(
NFT,
r#"[
function mint(address _to) external
]"#
);
#[tokio::main(flavor = "current_thread")]
async fn main() -> eyre::Result<()> {
dotenv().ok();
let infura_api_key = env::var("INFURA_API_KEY").expect("INFURA_API_KEY must be set");
let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set");
let provider = Provider::<Http>::try_from(format!(
"https://sepolia.infura.io/v3/{}",
infura_api_key
))?
.interval(Duration::from_millis(10u64));
let wallet: LocalWallet = private_key.parse::<LocalWallet>()?;
let wallet = wallet.with_chain_id(11155111u64); // Sepolia
let client = SignerMiddleware::new(provider.clone(), wallet);
let client = Arc::new(client);
println!("Account Connected: {}", client.address());
let balance = provider.get_balance(client.address(), None).await?;
println!("Account Balance: {} ETH", ethers::utils::format_ether(balance));
let nft_address = "0xaF3148FE00021529cBdBd6129383119dc16bDF0C".parse::<Address>()?;
let nft = NFT::new(nft_address, client.clone());
println!("Preparing transaction...");
println!("NFT address: {:?}", nft_address);
let tx = nft.mint(client.address()).gas(500000);
println!("Sending transaction...");
match tx.send().await {
Ok(pending_tx) => {
println!("Transaction sent successfully. Hash: {:?}", pending_tx.tx_hash());
match pending_tx.await {
Ok(receipt) => {
println!("Transaction successful!");
println!("Receipt: {:?}", receipt);
},
Err(e) => {
println!("Transaction failed: {:?}", e);
}
}
},
Err(e) => println!("Failed to send transaction: {:?}", e),
}
Ok(())
}
The code begins by importing necessary libraries, including ethers-rs for Ethereum interactions, dotenv for environment variable management, and standard Rust libraries. It also uses the abigen!
macro to generate Rust bindings for the NFT smart contract.
Note that abigen! can also take a json file for larger ABI’s in this format:
abigen!(IERC721, "./src/abi.json");
The main
function is asynchronous and uses the Tokio runtime. It serves as the entry point of the program and orchestrates the entire NFT minting process.
The code loads environment variables for the Infura API key and the user’s private key using dotenv
. These are crucial for connecting to the Ethereum network and signing transactions.
Next, it creates an Ethereum provider using the Infura API key, connecting to the Sepolia testnet. It also sets up a local wallet using the provided private key, which will be used to sign transactions.
The code then creates a SignerMiddleware client, which combines the provider and wallet. This client is used for signing transactions which are distributed across the Ethereum test network.
Next an instance of the NFT contract is created using its address and the client. The code then prepares a transaction to mint an NFT to the user’s address. This transaction is sent and the result is handled.
If successful, the transaction hash is printed and the program waits for the transaction to be mined. Once mined, the receipt is printed. If there are any errors, they are printed accordingly.
We can build and run the code using cargo
cargo build
cargo run
We can then pull the transaction hash up in a block explorer to check it went through…