The Ethers.rs library provides a set of tools to interact with Ethereum Nodes via the Rust programming language that works similar to Ethers.js. Phron has an Ethereum-like API available that is fully compatible with Ethereum-style JSON-RPC invocations. Therefore, developers can leverage this compatibility and use the Ethers.rs library to interact with a Phron node as if they were doing so on Ethereum. You can read more about how to use Ethers.rs on their official crate documentation.
In this guide, you'll learn how to use the Ethers.rs library to send a transaction and deploy a contract on Phron.
Checking Prerequisites
For the examples in this guide, you will need to have the following:
An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet
To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
The examples in this guide assumes you have a MacOS or Ubuntu 20.04-based environment and will need to be adapted accordingly for Windows.
Create a Rust Project
To get started, you can create a new Rust project with the Cargo tool:
cargoinitethers-examples&&cdethers-examples
For this guide, you'll need to install the Ethers.rs library among others. To tell the Rust project to install it, you must edit the Cargo.toml file that's included with the document to include it under dependencies:
[package]name ="ethers-examples"version ="0.1.0"edition ="2021"[dependencies]ethers ="1.0.2"ethers-solc ="1.0.2"tokio = { version ="1", features = ["full"] }serde_json ="1.0.89"serde ="1.0.149"
This example is using the ethers and ethers-solc crate versions 1.0.2 for RPC interactions and Solidity compiling. It also includes the tokio crate to run asynchronous Rust environments, since interacting with RPCs requires asynchronous code. Finally, it includes the serde_json and serde crates to help serialize/deserialize this example's code.
If this is your first time using solc-select, you'll need to install and configure the Solidity version using the following commands:
solc-selectinstall0.8.17&&solc-selectuse0.8.17
Setting up the Ethers Provider and Client
Throughout this guide, you'll be writing multiple functions that provide different functionality such as sending a transaction, deploying a contract, and interacting with a deployed contract. In most of these scripts you'll need to use an Ethers provider or an Ethers signer client to interact with the network.
To configure your project for Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
There are multiple ways to create a provider and signer, but the easiest way is through try_from. In the src/main.rs file, you can take the following steps:
Import Provider and Http from the ethers crate
Add a Client type for convenience, which will be used once you start to create the functions for sending a transaction and deploying a contract
Add a tokio attribute above async fn main() for asynchronous excution
Use try_from to attempt to instantiate a JSON-RPC provider object from an RPC endpoint
Use a private key to create a wallet object (the private key will be used to sign transactions). Note: This is for example purposes only. Never store your private keys in a plain Rust file
Wrap the provider and wallet together into a client by providing them to a SignerMiddleware object
// 1. Import ethers crateuse ethers::providers::{Provider, Http};// 2. Add client typetypeClient=SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;// 3. Add annotation#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> {// 4. Use try_from with RPC endpointlet provider =Provider::<Http>::try_from("INSERT_RPC_API_ENDPOINT" )?;// 5. Use a private key to create a wallet// Do not include the private key in plain text in any production code// This is just for demonstration purposes// Do not include '0x' at the start of the private keylet wallet:LocalWallet="INSERT_YOUR_PRIVATE_KEY".parse::<LocalWallet>()?.with_chain_id(Chain::Phron);// 6. Wrap the provider and wallet together to create a signer clientlet client =SignerMiddleware::new(provider.clone(), wallet.clone());Ok(())}
Send a Transaction
During this section, you'll be creating a couple of functions, which will be contained in the same main.rs file to avoid additional complexity from implementing modules. The first function will be to check the balances of your accounts before trying to send a transaction. The second function will actually send the transaction. To run each of these functions, you will edit the main function and run the main.rs script.
You should already have your provider and client set up in main.rs in the way described in the previous section. In order to send a transaction, you'll need to add a few more lines of code:
Add use ethers::{utils, prelude::*}; to your imports, which will provide you access to utility functions and the prelude imports all of the necessary data types and traits
As you'll be sending a transaction from one address to another, you can specify the sending and receiving addresses in the main function. Note: the address_from value should correspond to the private key that is used in the main function
// ...// 1. Add to importsuse ethers::{utils, prelude::*};// ...#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> {// ...// 2. Add from and to addresslet address_from ="YOUR_FROM_ADDRESS".parse::<Address>()?let address_to ="YOUR_TO_ADDRESS".parse::<Address>()?}
Check Balances Function
Next, you will create the function for getting the sending and receiving accounts' balances by completing the following steps:
Create a new asynchronous function named print_balances that takes a provider object's reference and the sending and receiving addresses as input
Use the provider object's get_balance function to get the balances of the sending and receiving addresses of the transaction
Print the resultant balances for the sending and receiving addresses
Call the print_balances function in the main function
// ...// 1. Create an asynchronous function that takes a provider reference and from and to address as inputasync fn print_balances(provider: &Provider<Http>, address_from: Address, address_to: Address) -> Result<(), Box<dyn std::error::Error>> {
// 2. Use the get_balance functionlet balance_from = provider.get_balance(address_from, None).await?;let balance_to = provider.get_balance(address_to, None).await?;// 3. Print the resultant balanceprintln!("{} has {}", address_from, balance_from);println!("{} has {}", address_to, balance_to);Ok(())}#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> {// ...// 4. Call print_balances function in mainprint_balances(&provider).await?;Ok(())}
Send Transaction Script
For this example, you'll be transferring 1 DEV from an origin address (of which you hold the private key) to another address.
Create a new asynchronous function named send_transaction that takes a client object's reference and the sending and receiving addresses as input
Create the transaction object, and include the to, value, and from. When writing the value input, use the ethers::utils::parse_ether function
Use the client object to send the transaction
Print the transaction after it is confirmed
Call the send_transaction function in the main function
// ...// 1. Define an asynchronous function that takes a client provider and the from and to addresses as inputasync fn send_transaction(client: &Client, address_from: Address, address_to: Address) -> Result<(), Box<dyn std::error::Error>> {
println!("Beginning transfer of 1 native currency from {} to {}.", address_from, address_to );// 2. Create a TransactionRequest objectlet tx =TransactionRequest::new().to(address_to).value(U256::from(utils::parse_ether(1)?)).from(address_from);// 3. Send the transaction with the clientlet tx = client.send_transaction(tx, None).await?.await?;// 4. Print out the resultprintln!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> {// ...// 5. Call send_transaction function in mainsend_transaction(&client, address_from, address_to).await?;Ok(())}
View the complete script
use ethers::providers::{Provider, Http};use ethers::{utils, prelude::*};typeClient=SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> { let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposeslet wallet:LocalWallet="INSERT_PRIVATE_KEY".parse::<LocalWallet>()?.with_chain_id(Chain::Phron); // Change to correct networklet client =SignerMiddleware::new(provider.clone(), wallet.clone());let address_from ="INSERT_FROM_ADDRESS".parse::<Address>()?;let address_to ="INSERT_TO_ADDRESS".parse::<Address>()?;send_transaction(&client, &address_from, &address_to).await?;print_balances(&provider, &address_from, &address_to).await?;Ok(())}// Print the balance of a walletasync fn print_balances(provider: &Provider<Http>, address_from: &Address, address_to: &Address) -> Result<(), Box<dyn std::error::Error>> {
let balance_from = provider.get_balance(address_from.clone(), None).await?;let balance_to = provider.get_balance(address_to.clone(), None).await?;println!("{} has {}", address_from, balance_from);println!("{} has {}", address_to, balance_to);Ok(())}// Sends some native currencyasync fn send_transaction(client: &Client, address_from: &Address, address_to: &Address) -> Result<(), Box<dyn std::error::Error>> {
println!("Beginning transfer of 1 native currency {} to {}.", address_from, address_to );let tx =TransactionRequest::new().to(address_to.clone()).value(U256::from(utils::parse_ether(1)?)).from(address_from.clone());let tx = client.send_transaction(tx, None).await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}
To run the script, which will send the transaction and then check the balances once the transaction has been sent, you can run the following command:
cargorun
If the transaction was succesful, in your terminal you'll see the transaction details printed out along with the balance of your address.
cargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 32.76sRunning `target/debug/ethers-examples`Beginning transfer of 1 native currency 0x3b93…421e to 0xe773…8dde.Transaction Receipt: {"transactionHash":"0x6f2338c63286f8b27951ddb6748191149d82647b44a00465f1f776624f490ce9","transactionIndex":"0x0","blockHash":"0x8234eb2083e649ab45c7c5fcdf2026d8f47676f7e29305023d1d00cc349ba215","blockNumber":"0x7ac12d","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xe773f740828a968c8a9e1e8e05db486937768dde","cumulativeGasUsed":"0x5208","gasUsed":"0x5208","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x0","effectiveGasPrice":"0x7735940"}0x3b93…421e has 36017039844708655891250xe773…8dde has 1000000000000000000
Deploy a Contract
The contract you'll be compiling and deploying in the next couple of sections is a simple incrementer contract, arbitrarily named Incrementer.sol. You can get started by creating a file for the contract:
touchIncrementer.sol
Next, you can add the Solidity code to the file:
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;contract Incrementer {uint256public number;constructor(uint256_initialNumber) { number = _initialNumber; }functionincrement(uint256_value) public { number = number + _value; }functionreset() public { number =0; }}
The constructor function, which runs when the contract is deployed, sets the initial value of the number variable stored on-chain (the default is 0). The increment function adds the _value provided to the current number, but a transaction needs to be sent, which modifies the stored data. Lastly, the reset function resets the stored value to zero.
Note
This contract is a simple example for illustration purposes only and does not handle values wrapping around.
During the rest of this section, you'll be creating a couple of functions, which will be contained in the main.rs file to avoid additional complexity from implementing modules. The first function will be to compile and deploy the contract. The remaining functions will interact with the deployed contract.
You should already have your provider and client set up in main.rs in the way described in the Setting up the Ethers Provider and Client section.
Before getting started with the contract deployment, you'll need to add a few more imports to your main.rs file:
use ethers_solc::Solc;use ethers::{prelude::*};use std::{path::Path, sync::Arc};
The ethers_solc import will be used to compile the smart contract. The prelude from Ethers imports some necessary data types and traits. Lastly, the std imports will enables you to store your smart contracts and wrap the client into an Arc type for thread safety.
Compile and Deploy Contract Script
This example function will compile and deploy the Incrementer.sol smart contract you created in the previous section. The Incrementer.sol smart contract should be in the root directory. In the main.rs file, you can take the following steps:
Create a new asynchronous function named compile_deploy_contract that takes a client object's reference as input, and returns an address in the form of H160
Define a variable named source as the path for the directory that hosts all of the smart contracts that should be compiled, which is the root directory
Use the Solc crate to compile all of the smart contracts in the root directory
Get the ABI and bytecode from the compiled result, searching for the Incrementer.sol contract
Create a contract factory for the smart contract using the ABI, bytecode, and client. The client must be wrapped into an Arc type for thread safety
Use the factory to deploy. For this example, the value 5 is used as the initial value in the constructor
Print out the address after the deployment
Return the address
Call the compile_deploy_contract function in main
// ...// 1. Define an asynchronous function that takes a client provider as input and returns H160asyncfncompile_deploy_contract(client:&Client) ->Result<H160, Box<dyn std::error::Error>> {// 2. Define a path as the directory that hosts the smart contracts in the projectlet source =Path::new(&env!("CARGO_MANIFEST_DIR"));// 3. Compile all of the smart contractslet compiled =Solc::default().compile_source(source).expect("Could not compile contracts");// 4. Get ABI & Bytecode for Incrementer.sollet (abi, bytecode, _runtime_bytecode) = compiled.find("Incrementer").expect("could not find contract").into_parts_or_default();// 5. Create a contract factory which will be used to deploy instances of the contractlet factory =ContractFactory::new(abi, bytecode, Arc::new(client.clone()));// 6. Deploylet contract = factory.deploy(U256::from(5))?.send().await?;// 7. Print out the addresslet addr = contract.address();println!("Incrementer.sol has been deployed to {:?}", addr);// 8. Return the addressOk(addr)}#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> {// ...// 9. Call compile_deploy_contract function in mainlet addr =compile_deploy_contract(&client).await?;Ok(())}
Read Contract Data (Call Methods)
Call methods are the type of interaction that don't modify the contract's storage (change variables), meaning no transaction needs to be sent. They simply read various storage variables of the deployed contract.
Rust is typesafe, which is why the ABI for the Incrementer.sol contract is required to generate a typesafe Rust struct. For this example, you should create a new file in the root of the Cargo project called Incrementer_ABI.json:
touchIncrementer_ABI.json
The ABI for Incrementer.sol is below, which should be copied and pasted into the Incrementer_ABI.json file:
Then you can take the following steps to create a function that reads and returns the number method of the Incrementer.sol contract:
Generate a type-safe interface for the Incrementer smart contract with the abigen macro
Create a new asynchronous function named read_number that takes a client object's reference and a contract address reference as input, and returns a U256
Create a new instance of the Incrementer object generated by the abigen macro with the client and contract address values
Call the number function in the new Incrementer object
Print out the resultant value
Return the resultant value
Call the read_number function in main
// ...// 1. Generate a type-safe interface for the Incrementer smart contractabigen!( Incrementer,"./Incrementer_ABI.json",event_derives(serde::Deserialize, serde::Serialize));// 2. Define an asynchronous function that takes a client provider and address as input and returns a U256async fn read_number(client:&Client, contract_addr:&H160) -> Result<U256, Box<dyn std::error::Error>> {// 3. Create contract instanceletcontract= Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// 4. Call contract's number functionlet value =contract.number().call().await?;// 5. Print out number println!("Incrementer's number is {}", value);// 6. Return the numberOk(value)}// ...#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> {// ...// 7. Call read_number function in mainread_number(&client,&addr).await?;Ok(())}
View the complete script
use ethers::providers::{Provider, Http};use ethers::{prelude::*};use ethers_solc::Solc;use std::{path::Path, sync::Arc};typeClient=SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> { let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes// Do not include '0x' at the start of the private keylet wallet:LocalWallet="INSERT_PRIVATE_KEY".parse::<LocalWallet>()?.with_chain_id(Chain::Phron);let client =SignerMiddleware::new(provider.clone(), wallet.clone());// Deploy contract and read initial incrementer valuelet addr =compile_deploy_contract(&client).await?;read_number(&client, &addr).await?;// Increment and read the incremented numberincrement_number(&client, &addr).await?;read_number(&client, &addr).await?;// Reset the incremented number and read itreset(&client, &addr).await?;read_number(&client, &addr).await?;Ok(())}// Need to install solc for this tutorial: https://github.com/crytic/solc-selectasyncfncompile_deploy_contract(client:&Client) ->Result<H160, Box<dyn std::error::Error>> {// Incrementer.sol is located in the root directorylet source =Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));// Compile itlet compiled =Solc::default().compile_source(source).expect("Could not compile contracts");// Get ABI & Bytecode for Incrementer.sollet (abi, bytecode, _runtime_bytecode) = compiled.find("Incrementer").expect("could not find contract").into_parts_or_default();// Create a contract factory which will be used to deploy instances of the contractlet factory =ContractFactory::new(abi, bytecode, Arc::new(client.clone()));// Deploylet contract = factory.deploy(U256::from(5))?.send().await?;let addr = contract.address();println!("Incrementer.sol has been deployed to {:?}", addr);Ok(addr)}// Generates a type-safe interface for the Incrementer smart contractabigen!(Incrementer,"./Incrementer_ABI.json",event_derives(serde::Deserialize, serde::Serialize));asyncfnread_number(client:&Client, contract_addr:&H160) ->Result<U256, Box<dyn std::error::Error>> {// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Call contract's number functionlet value = contract.number().call().await?;// Print out valueprintln!("Incrementer's number is {}", value);Ok(value)}asyncfnincrement_number(client:&Client, contract_addr:&H160) ->Result<(), Box<dyn std::error::Error>> {println!("Incrementing number...");// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Send contract transactionlet tx = contract.increment(U256::from(5)).send().await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}asyncfnreset(client:&Client, contract_addr:&H160) ->Result<(), Box<dyn std::error::Error>> {println!("Resetting number...");// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Send contract transactionlet tx = contract.reset().send().await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}
To run the script, which will deploy the contract and return the current value stored in the Incrementer contract, you can enter the following command into your terminal:
cargorun
If successful, you'll see the deployed contract's address and initial value set, which should be 5, displayed in the terminal.
cargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 1.09sRunning `/Users/phron/workspace/ethers-examples/target/debug/ethers-examples`Incrementer.sol has been deployed to 0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5dIncrementer's number is 5
Interact with Contract (Send Methods)
Send methods are the type of interaction that modify the contract's storage (change variables), meaning a transaction needs to be signed and sent. In this section, you'll create two functions: one to increment and one to reset the incrementer. This section will also require the Incrementer_ABI.json file initialized when reading from the smart contract.
Take the following steps to create the function to increment:
Ensure that the abigen macro is called for the Incrementer_ABI.json somewhere in the main.rs file (if it is already in the main.rs file, you do not have to have a second one)
Create a new asynchronous function named increment_number that takes a client object's reference and an address as input
Create a new instance of the Incrementer object generated by the abigen macro with the client and contract address values
Call the increment function in the new Incrementer object by including a U256 object as input. In this instance, the value provided is 5
Call the read_number function in main
// ...// 1. Generate a type-safe interface for the Incrementer smart contractabigen!( Incrementer,"./Incrementer_ABI.json",event_derives(serde::Deserialize, serde::Serialize));// 2. Define an asynchronous function that takes a client provider and address as inputasync fn increment_number(client:&Client, contract_addr:&H160) -> Result<(), Box<dyn std::error::Error>> { println!("Incrementing number...");// 3. Create contract instanceletcontract= Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// 4. Send contract transactionlet tx =contract.increment(U256::from(5)).send().await?.await?; println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}// ...#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> {// ...// 5. Call increment_number function in mainincrement_number(&client,&addr).await?;Ok(())}
View the complete script
use ethers::providers::{Provider, Http};use ethers::{prelude::*};use ethers_solc::Solc;use std::{path::Path, sync::Arc};typeClient=SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> { let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes// Do not include '0x' at the start of the private keylet wallet:LocalWallet="INSERT_PRIVATE_KEY".parse::<LocalWallet>()?.with_chain_id(Chain::Phron);let client =SignerMiddleware::new(provider.clone(), wallet.clone());// Deploy contract and read initial incrementer valuelet addr =compile_deploy_contract(&client).await?;read_number(&client, &addr).await?;// Increment and read the incremented numberincrement_number(&client, &addr).await?;read_number(&client, &addr).await?;// Reset the incremented number and read itreset(&client, &addr).await?;read_number(&client, &addr).await?;Ok(())}// Need to install solc for this tutorial: https://github.com/crytic/solc-selectasyncfncompile_deploy_contract(client:&Client) ->Result<H160, Box<dyn std::error::Error>> {// Incrementer.sol is located in the root directorylet source =Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));// Compile itlet compiled =Solc::default().compile_source(source).expect("Could not compile contracts");// Get ABI & Bytecode for Incrementer.sollet (abi, bytecode, _runtime_bytecode) = compiled.find("Incrementer").expect("could not find contract").into_parts_or_default();// Create a contract factory which will be used to deploy instances of the contractlet factory =ContractFactory::new(abi, bytecode, Arc::new(client.clone()));// Deploylet contract = factory.deploy(U256::from(5))?.send().await?;let addr = contract.address();println!("Incrementer.sol has been deployed to {:?}", addr);Ok(addr)}// Generates a type-safe interface for the Incrementer smart contractabigen!(Incrementer,"./Incrementer_ABI.json",event_derives(serde::Deserialize, serde::Serialize));asyncfnread_number(client:&Client, contract_addr:&H160) ->Result<U256, Box<dyn std::error::Error>> {// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Call contract's number functionlet value = contract.number().call().await?;// Print out valueprintln!("Incrementer's number is {}", value);Ok(value)}asyncfnincrement_number(client:&Client, contract_addr:&H160) ->Result<(), Box<dyn std::error::Error>> {println!("Incrementing number...");// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Send contract transactionlet tx = contract.increment(U256::from(5)).send().await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}asyncfnreset(client:&Client, contract_addr:&H160) ->Result<(), Box<dyn std::error::Error>> {println!("Resetting number...");// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Send contract transactionlet tx = contract.reset().send().await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}
To run the script, you can enter the following command into your terminal:
cargorun
If successful, the transaction receipt will be displayed in the terminal. You can use the read_number function in the main function to make sure that value is changing as expected. If you're using the read_number function after incrementing, you'll also see the incremented number, which should be 10.
cargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 1.09sRunning `/Users/phron/workspace/ethers-examples/target/debug/ethers-examples`Incrementer.sol has been deployed to 0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5dIncrementer's number is 5Incrementing number...Transaction Receipt: {"transactionHash":"0x6f5c204e74b96b6cf6057512ba142ad727718646d4ebb7abe8bbabada198dafb","transactionIndex":"0x0","blockHash":"0x635a8a234b30c6ee907198ddda3a1478ae52c6adbcc4a67353dd9597ee626950","blockNumber":"0x7ac238","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5d","cumulativeGasUsed":"0x68a6","gasUsed":"0x68a6","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x2","effectiveGasPrice":"0xba43b740"}Incrementer's number is 10
Next you can interact with the reset function:
Ensure that the abigen macro is called for the Incrementer_ABI.json somewhere in the main.rs file (if it is already in the main.rs file, you do not have to have a second one)
Create a new asynchronous function named reset that takes a client object's reference and an address as input
Create a new instance of the Incrementer object generated by the abigen macro with the client and contract address values
Call the reset function in the new Incrementer object
Call the reset function in main
// ...// 1. Generate a type-safe interface for the Incrementer smart contractabigen!(Incrementer,"./Incrementer_ABI.json",event_derives(serde::Deserialize, serde::Serialize));// 2. Define an asynchronous function that takes a client provider and address as inputasyncfnreset(client:&Client, contract_addr:&H160) ->Result<(), Box<dyn std::error::Error>> {println!("Resetting number...");// 3. Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// 4. Send contract transactionlet tx = contract.reset().send().await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}// ...#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> {// ...// 5. Call reset function in mainreset(&client, &addr).await?;Ok(())}
If successful, the transaction receipt will be displayed in the terminal. You can use the read_number function in the main function to make sure that value is changing as expected. If you're using the read_number function after resetting the number, you should see 0 printed to the terminal.
cargo runCompiling ethers-examples v0.1.0 (/Users/phron/workspace/ethers-examples)Finished dev [unoptimized + debuginfo] target(s) in 1.09sRunning `/Users/phron/workspace/ethers-examples/target/debug/ethers-examples`Incrementer.sol has been deployed to 0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5dIncrementer's number is 5Incrementing number...Transaction Receipt: {"transactionHash":"0x6f5c204e74b96b6cf6057512ba142ad727718646d4ebb7abe8bbabada198dafb","transactionIndex":"0x0","blockHash":"0x635a8a234b30c6ee907198ddda3a1478ae52c6adbcc4a67353dd9597ee626950","blockNumber":"0x7ac238","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5d","cumulativeGasUsed":"0x68a6","gasUsed":"0x68a6","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x2","effectiveGasPrice":"0xba43b740"}Incrementer's number is 10Resetting number...Transaction Receipt: {"transactionHash":"0xf1010597c6ab3d3cfcd6e8e68bf2eddf4ed38eb93a3052591c88b675ed1e83a4","transactionIndex":"0x0","blockHash":"0x5d4c09abf104cbd88e80487c170d8709aae7475ca84c1f3396f3e35222fbe87f","blockNumber":"0x7ac23b","from":"0x3b939fead1557c741ff06492fd0127bd287a421e","to":"0xeb8a4d5c7cd56c65c9dbd25f793b50a2c917bb5d","cumulativeGasUsed":"0x53c4","gasUsed":"0x53c4","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x2","effectiveGasPrice":"0xba43b740"}Incrementer's number is 0
View the complete script
use ethers::providers::{Provider, Http};use ethers::{prelude::*};use ethers_solc::Solc;use std::{path::Path, sync::Arc};typeClient=SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;#[tokio::main]asyncfnmain() ->Result<(), Box<dyn std::error::Error>> { let provider: Provider<Http> = Provider::<Http>::try_from("https://testnet.phron.ai")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes// Do not include '0x' at the start of the private keylet wallet:LocalWallet="INSERT_PRIVATE_KEY".parse::<LocalWallet>()?.with_chain_id(Chain::Phron);let client =SignerMiddleware::new(provider.clone(), wallet.clone());// Deploy contract and read initial incrementer valuelet addr =compile_deploy_contract(&client).await?;read_number(&client, &addr).await?;// Increment and read the incremented numberincrement_number(&client, &addr).await?;read_number(&client, &addr).await?;// Reset the incremented number and read itreset(&client, &addr).await?;read_number(&client, &addr).await?;Ok(())}// Need to install solc for this tutorial: https://github.com/crytic/solc-selectasyncfncompile_deploy_contract(client:&Client) ->Result<H160, Box<dyn std::error::Error>> {// Incrementer.sol is located in the root directorylet source =Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));// Compile itlet compiled =Solc::default().compile_source(source).expect("Could not compile contracts");// Get ABI & Bytecode for Incrementer.sollet (abi, bytecode, _runtime_bytecode) = compiled.find("Incrementer").expect("could not find contract").into_parts_or_default();// Create a contract factory which will be used to deploy instances of the contractlet factory =ContractFactory::new(abi, bytecode, Arc::new(client.clone()));// Deploylet contract = factory.deploy(U256::from(5))?.send().await?;let addr = contract.address();println!("Incrementer.sol has been deployed to {:?}", addr);Ok(addr)}// Generates a type-safe interface for the Incrementer smart contractabigen!(Incrementer,"./Incrementer_ABI.json",event_derives(serde::Deserialize, serde::Serialize));asyncfnread_number(client:&Client, contract_addr:&H160) ->Result<U256, Box<dyn std::error::Error>> {// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Call contract's number functionlet value = contract.number().call().await?;// Print out valueprintln!("Incrementer's number is {}", value);Ok(value)}asyncfnincrement_number(client:&Client, contract_addr:&H160) ->Result<(), Box<dyn std::error::Error>> {println!("Incrementing number...");// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Send contract transactionlet tx = contract.increment(U256::from(5)).send().await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}asyncfnreset(client:&Client, contract_addr:&H160) ->Result<(), Box<dyn std::error::Error>> {println!("Resetting number...");// Create contract instancelet contract =Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));// Send contract transactionlet tx = contract.reset().send().await?.await?;println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);Ok(())}
This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.