James Bachini

Automating Web3 Interactions In Rust | Minting Ethereum NFT’s With Ethers-rs

web3 Rust

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.

Deploying NFT Remix

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
ethereum and rust

We can then pull the transaction hash up in a block explorer to check it went through…

tx etherscan success

Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.


by