Rust ethers-rs: How to send ERC-20 tokens

Hi everyone,

We’ve previously seen how to send transactions with value in Rust ethers-rs: How to send transactions but what is we want to send some other tokens ?
In this tutorial we’ll send ERC-20 tokens from one address to another which essentially consists of calling the Transfer method of that token contract.
If you’re new to Rust make sure to check the previous article on how to install and set up the project.

Before jumping into the code:

  1. Let’s get some test ERC-20 tokens - we’ll be using some test LINK from https://faucets.chain.link/ and use the Sepolia chain.

  2. Create a ct.json file in your project, and paste the ABI from ChainLink Token (LINK) Token Tracker | Etherscan

Now, full code below, hopefully the comments would make enough sense. :slight_smile:

use ethers::{
    middleware::SignerMiddleware,
    providers::{Http, Middleware, Provider},
    signers::{LocalWallet, Signer},
    types::{Address, U256},
    contract::abigen
};
use eyre::Result;
use std::convert::TryFrom;
use std::sync::Arc;
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<()> {

    // connect to the network, don't forget to replace your INFURA_API_KEY
    let provider = Provider::<Http>::try_from("https://sepolia.infura.io/v3/INFURA_API_KEY")?;

    let chain_id = provider.get_chainid().await?;

    let contract_address = "0x779877A7B0D9E8603169DdbD7836e478b4624789".parse::<Address>()?;

    // define a `ERC20Contract` struct from the ABI
    abigen!(ERC20Contract, "./ct.json",);

    let to_address = "0xF1B792820b52e6503208CAb98ec0B7b89ac64D6A".parse::<Address>()?;

    // Create the contract instance to let us call methods of the contract and let it sign transactions with the sender wallet.
    // for simplicity replace the private key (without 0x), ofc it always recommended to load it from an .env file or external vault
    let wallet: LocalWallet = "SIGNER_PRIVATE_KEY"
        .parse::<LocalWallet>()?
        .with_chain_id(chain_id.as_u64());

    let signer = Arc::new(SignerMiddleware::new(provider, wallet.with_chain_id(chain_id.as_u64())));
    let contract = ERC20Contract::new(contract_address, signer);

    // Fetch the decimals used by the contract so we can compute the decimal amount to send.
    let whole_amount: u64 = 1;
    let decimals = contract.decimals().call().await?;
    let decimal_amount = U256::from(whole_amount) * U256::exp10(decimals as usize);

    // Transfer the desired amount of tokens to the `to_address`
    let tx = contract.transfer(to_address, decimal_amount);
    let pending_tx = tx.send().await?;
    let _mined_tx = pending_tx.await?;

    println!("Transaction Receipt: {}", serde_json::to_string(&_mined_tx)?);

    // Extract the tx hash for printing
    let json_str = serde_json::to_string(&_mined_tx)?;
    let json: Value = serde_json::from_str(&json_str)?;

    if let Some(transaction_hash) = json["transactionHash"].as_str() {
        println!("\n URL: https://sepolia.etherscan.io/tx/{}", transaction_hash);
    } else {
        println!("Transaction Hash not found");
    }

   Ok(())
}
3 Likes