Ethers.js JavaScript Library¶
Introduction¶
The Ethers.js library provides a set of tools to interact with Ethereum Nodes with JavaScript, similar to Web3.js. Moonbeam 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.js library to interact with a Moonbeam node as if they were doing so on Ethereum. For more information on Ethers.js, check their documentation site.
In this guide, you'll learn how to use the Ethers.js library to send a transaction and deploy a contract on Moonbase Alpha. This guide can be adapted for Moonbeam, Moonriver, or a Moonbeam development node.
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 Moonbase Alpha once every 24 hours from the Moonbase Alpha Faucet
- To test out the examples in this guide on Moonbeam or Moonriver, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers
Note
The examples in this guide assume you have a MacOS or Ubuntu 22.04-based environment and will need to be adapted accordingly for Windows.
Installing Ethers.js¶
To get started, you'll need to start a basic JavaScript project. First, create a directory to store all of the files you'll be creating throughout this guide and initialize the project with the following command:
mkdir ethers-examples && cd ethers-examples && npm init --y
For this guide, you'll need to install the Ethers.js library and the Solidity compiler. To install both NPM packages, you can run the following command:
npm install ethers solc@0.8.30
yarn add ethers solc@0.8.30
Setting up the Ethers Provider¶
Throughout this guide, you'll be creating a bunch of scripts 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 create an Ethers provider to interact with the network.
To configure your project for Moonbeam or Moonriver, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.
To create a provider, you can take the following steps:
- Import the
etherslibrary - Define the
providerRPCobject, which can include the network configurations for any of the networks you want to send a transaction on. You'll include thename,rpc, andchainIdfor each network - Create the
providerusing theethers.JsonRpcProvidermethod
// 1. Import ethers
const ethers = require('ethers');
// 2. Define network configurations
const providerRPC = {
moonbeam: {
name: 'moonbeam',
rpc: 'INSERT_RPC_API_ENDPOINT', // Insert your RPC URL here
chainId: 1284, // 0x504 in hex,
},
};
// 3. Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonbeam.rpc, {
chainId: providerRPC.moonbeam.chainId,
name: providerRPC.moonbeam.name,
});
// 1. Import ethers
const ethers = require('ethers');
// 2. Define network configurations
const providerRPC = {
moonriver: {
name: 'moonriver',
rpc: 'INSERT_RPC_API_ENDPOINT', // Insert your RPC URL here
chainId: 1285, // 0x505 in hex,
},
};
// 3. Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonriver.rpc, {
chainId: providerRPC.moonriver.chainId,
name: providerRPC.moonriver.name,
});
// 1. Import ethers
const ethers = require('ethers');
// 2. Define network configurations
const providerRPC = {
moonbase: {
name: 'moonbase-alpha',
rpc: 'https://rpc.api.moonbase.moonbeam.network',
chainId: 1287, // 0x507 in hex,
},
};
// 3. Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonbase.rpc, {
chainId: providerRPC.moonbase.chainId,
name: providerRPC.moonbase.name,
});
// 1. Import ethers
const ethers = require('ethers');
// 2. Define network configurations
const providerRPC = {
dev: {
name: 'moonbeam-development',
rpc: 'http://127.0.0.1:9944',
chainId: 1281, // 0x501 in hex,
},
};
// 3. Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.dev.rpc, {
chainId: providerRPC.dev.chainId,
name: providerRPC.dev.name,
});
Save this code snippet as you'll need it for the scripts that are used in the following sections.
Send a Transaction¶
During this section, you'll be creating a couple of scripts. The first one will be to check the balances of your accounts before trying to send a transaction. The second script will actually send the transaction.
You can also use the balance script to check the account balances after the transaction has been sent.
Check Balances Script¶
You'll only need one file to check the balances of both addresses before and after the transaction is sent. To get started, you can create a balances.js file by running:
touch balances.js
Next, you will create the script for this file and complete the following steps:
- Set up the Ethers provider
- Define the
addressFromandaddressTovariables - Create the asynchronous
balancesfunction which wraps theprovider.getBalancemethod - Use the
provider.getBalancefunction to fetch the balances for theaddressFromandaddressToaddresses. You can also leverage theethers.formatEtherfunction to transform the balance into a more readable number in ETH - Lastly, run the
balancesfunction
// 1. Add the Ethers provider logic here:
// {...}
// 2. Create address variables
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// 3. Create balances function
const balances = async () => {
// 4. Fetch balances
const balanceFrom = ethers.formatEther(await provider.getBalance(addressFrom));
const balanceTo = ethers.formatEther(await provider.getBalance(addressTo));
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// 5. Call the balances function
balances();
View the complete script
// Import ethers
const ethers = require('ethers');
// Define network configurations
const providerRPC = {
development: {
name: 'moonbeam-development',
rpc: 'http://localhost:9944',
chainId: 1281,
},
moonbase: {
name: 'moonbase-alpha',
rpc: 'https://rpc.api.moonbase.moonbeam.network',
chainId: 1287,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonbase.rpc, {
chainId: providerRPC.moonbase.chainId,
name: providerRPC.moonbase.name,
}); // Change to correct network
// Define addresses
const addressFrom = 'INSERT_FROM_ADDRESS';
const addressTo = 'INSERT_TO_ADDRESS';
// Create balances function
const balances = async () => {
// Fetch balances
const balanceFrom = ethers.formatEther(
await provider.getBalance(addressFrom)
);
const balanceTo = ethers.formatEther(await provider.getBalance(addressTo));
console.log(`The balance of ${addressFrom} is: ${balanceFrom} DEV`);
console.log(`The balance of ${addressTo} is: ${balanceTo} DEV`);
};
// Call the balances function
balances();
To run the script and fetch the account balances, you can run the following command:
node balances.js
If successful, the balances for the origin and receiving address will be displayed in your terminal in DEV.
Send Transaction Script¶
You'll only need one file for executing a transaction between accounts. For this example, you'll be transferring 1 DEV token from an origin address (from which you hold the private key) to another address. To get started, you can create a transaction.js file by running:
touch transaction.js
Next, you will create the script for this file and complete the following steps:
- Set up the Ethers provider
- Define the
privateKeyand theaddressTovariables. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file - Create a wallet using the
privateKeyandproviderfrom the previous steps. The wallet instance is used to sign transactions - Create the asynchronous
sendfunction which wraps the transaction object and thewallet.sendTransactionmethod - Create the transaction object which only requires the recipient's address and the amount to send. Note that
ethers.parseEthercan be used, which handles the necessary unit conversions from Ether to Wei - similar to usingethers.parseUnits(value, 'ether') - Send the transaction using the
wallet.sendTransactionmethod and then useawaitto wait until the transaction is processed and the transaction receipt is returned - Lastly, run the
sendfunction
// 1. Add the Ethers provider logic here:
// {...}
// 2. Create account variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const addressTo = 'INSERT_TO_ADDRESS';
// 3. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 4. Create send function
const send = async () => {
console.log(`Attempting to send transaction from ${wallet.address} to ${addressTo}`);
// 5. Create tx object
const tx = {
to: addressTo,
value: ethers.parseEther('1'),
};
// 6. Sign and send tx - wait for receipt
const createReceipt = await wallet.sendTransaction(tx);
await createReceipt.wait();
console.log(`Transaction successful with hash: ${createReceipt.hash}`);
};
// 7. Call the send function
send();
View the complete script
// Import ethers
const ethers = require('ethers');
// Define network configurations
const providerRPC = {
development: {
name: 'moonbeam-development',
rpc: 'http://localhost:9944',
chainId: 1281,
},
moonbase: {
name: 'moonbase-alpha',
rpc: 'https://rpc.api.moonbase.moonbeam.network',
chainId: 1287,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonbase.rpc, {
chainId: providerRPC.moonbase.chainId,
name: providerRPC.moonbase.name,
}); // Change to correct network
// Define accounts and wallet
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const addressTo = 'INSERT_TO_ADDRESS';
const wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Create send function
const send = async () => {
console.log(
`Attempting to send transaction from ${wallet.address} to ${addressTo}`
);
// Create transaction
const tx = {
to: addressTo,
value: ethers.parseEther('1'),
};
// Send transaction and get hash
const createReceipt = await wallet.sendTransaction(tx);
await createReceipt.wait();
console.log(`Transaction successful with hash: ${createReceipt.hash}`);
};
// Call the send function
send();
To run the script, you can run the following command in your terminal:
node transaction.js
If the transaction was successful, in your terminal you'll see the transaction hash has been printed out.
You can also use the balances.js script to check that the balances for the origin and receiving accounts have changed. The entire workflow would look like this:
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:
touch Incrementer.sol
Next, you can add the Solidity code to the file:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() 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.
Compile Contract Script¶
In this section, you'll create a script that uses the Solidity compiler to output the bytecode and interface (ABI) for the Incrementer.sol contract. To get started, you can create a compile.js file by running:
touch compile.js
Next, you will create the script for this file and complete the following steps:
- Import the
fsandsolcpackages - Using the
fs.readFileSyncfunction, you'll read and save the file contents ofIncrementer.soltosource - Build the
inputobject for the Solidity compiler by specifying thelanguage,sources, andsettingsto be used - Using the
inputobject, you can compile the contract usingsolc.compile - Extract the compiled contract file and export it to be used in the deployment script
// 1. Import packages
const fs = require('fs');
const solc = require('solc');
// 2. Get path and load contract
const source = fs.readFileSync('Incrementer.sol', 'utf8');
// 3. Create input object
const input = {
language: 'Solidity',
sources: {
'Incrementer.sol': {
content: source,
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
};
// 4. Compile the contract
const tempFile = JSON.parse(solc.compile(JSON.stringify(input)));
const contractFile = tempFile.contracts['Incrementer.sol']['Incrementer'];
// 5. Export contract data
module.exports = contractFile;
Deploy Contract Script¶
With the script for compiling the Incrementer.sol contract in place, you can then use the results to send a signed transaction that deploys it. To do so, you can create a file for the deployment script called deploy.js:
touch deploy.js
Next, you will create the script for this file and complete the following steps:
- Import the contract file from
compile.js - Set up the Ethers provider
- Define the
privateKeyfor the origin account. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file - Create a wallet using the
privateKeyandproviderfrom the previous steps. The wallet instance is used to sign transactions - Load the contract
bytecodeandabifor the compiled contract - Create a contract instance with signer using the
ethers.ContractFactoryfunction, providing theabi,bytecode, andwalletas parameters - Create the asynchronous
deployfunction that will be used to deploy the contract - Within the
deployfunction, use theincrementercontract instance to calldeployand pass in the initial value. For this example, you can set the initial value to5. This will send the transaction for contract deployment. To wait for a transaction receipt you can use thedeployedmethod of the contract deployment transaction - Lastly, run the
deployfunction
// 1. Import the contract file
const contractFile = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Create account variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
// 4. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 5. Load contract information
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
// 6. Create contract instance with signer
const incrementer = new ethers.ContractFactory(abi, bytecode, wallet);
// 7. Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account: ${wallet.address}`);
// 8. Send tx (initial value set to 5) and wait for receipt
const contract = await incrementer.deploy(5);
const txReceipt = await contract.deploymentTransaction().wait();
console.log(`Contract deployed at address: ${txReceipt.contractAddress}`);
};
// 9. Call the deploy function
deploy();
View the complete script
// Import ethers and compile
const ethers = require('ethers');
const contractFile = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'moonbeam-development',
rpc: 'http://localhost:9944',
chainId: 1281,
},
moonbase: {
name: 'moonbase-alpha',
rpc: 'https://rpc.api.moonbase.moonbeam.network',
chainId: 1287,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonbase.rpc, {
chainId: providerRPC.moonbase.chainId,
name: providerRPC.moonbase.name,
}); // Change to correct network
// Define accounts and wallet
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Load contract info
const bytecode = contractFile.evm.bytecode.object;
const abi = contractFile.abi;
// Create contract instance with signer
const incrementer = new ethers.ContractFactory(abi, bytecode, wallet);
// Create deploy function
const deploy = async () => {
console.log(`Attempting to deploy from account: ${wallet.address}`);
// Send tx (initial value set to 5) and wait for receipt
const contract = await incrementer.deploy(5);
const txReceipt = await contract.deploymentTransaction().wait();
console.log(`Contract deployed at address: ${txReceipt.contractAddress}`);
};
// Call the deploy function
deploy();
To run the script, you can enter the following command into your terminal:
node deploy.js
If successful, the contract's address will be displayed in the terminal.
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.
To get started, you can create a file and name it get.js:
touch get.js
Then you can take the following steps to create the script:
- Import the
abifrom thecompile.jsfile - Set up the Ethers provider
- Create the
contractAddressvariable using the address of the deployed contract - Create an instance of the contract using the
ethers.Contractfunction and passing in thecontractAddress,abi, andprovider - Create the asynchronous
getfunction - Use the contract instance to call one of the contract's methods and pass in any inputs if necessary. For this example, you will call the
numbermethod which doesn't require any inputs. You can useawaitwhich will return the value requested once the request promise resolves - Lastly, call the
getfunction
// 1. Import the ABI
const { abi } = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Contract address variable
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create contract instance
const incrementer = new ethers.Contract(contractAddress, abi, provider);
// 5. Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// 6. Call contract
const data = await incrementer.number();
console.log(`The current number stored is: ${data}`);
};
// 7. Call get function
get();
View the complete script
// Import ethers and compile
const ethers = require('ethers');
const { abi } = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'moonbeam-development',
rpc: 'http://localhost:9944',
chainId: 1281,
},
moonbase: {
name: 'moonbase-alpha',
rpc: 'https://rpc.api.moonbase.moonbeam.network',
chainId: 1287,
},
};
const provider = new ethers.JsonRpcProvider(providerRPC.moonbase.rpc, {
chainId: providerRPC.moonbase.chainId,
name: providerRPC.moonbase.name,
}); // Change to correct network
// Contract address variable
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// Create contract instance
const incrementer = new ethers.Contract(contractAddress, abi, provider);
// Create get function
const get = async () => {
console.log(`Making a call to contract at address: ${contractAddress}`);
// Call contract
const data = await incrementer.number();
console.log(`The current number stored is: ${data}`);
};
// Call get function
get();
To run the script, you can enter the following command in your terminal:
node get.js
If successful, the value will be displayed in the terminal.
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 scripts: one to increment and one to reset the incrementer. To get started, you can create a file for each script and name them increment.js and reset.js:
touch increment.js reset.js
Open the increment.js file and take the following steps to create the script:
- Import the
abifrom thecompile.jsfile - Set up the Ethers provider
- Define the
privateKeyfor the origin account, thecontractAddressof the deployed contract, and the_valueto increment by. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file - Create a wallet using the
privateKeyandproviderfrom the previous steps. The wallet instance is used to sign transactions - Create an instance of the contract using the
ethers.Contractfunction and passing in thecontractAddress,abi, andprovider - Create the asynchronous
incrementfunction - Use the contract instance to call one of the contract's methods and pass in any inputs if necessary. For this example, you will call the
incrementmethod which requires the value to increment by as an input. You can useawaitwhich will return the value requested once the request promise resolves - Lastly, call the
incrementfunction
// 1. Import the contract ABI
const { abi } = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const _value = 3;
// 4. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 5. Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// 6. Create increment function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// 7. Sign and send tx and wait for receipt
const createReceipt = await incrementer.increment(_value);
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// 8. Call the increment function
increment();
View the complete script
// Import ethers and compile
const ethers = require('ethers');
const { abi } = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'moonbeam-development',
rpc: 'http://localhost:9944',
chainId: 1281,
},
moonbase: {
name: 'moonbase-alpha',
rpc: 'https://rpc.api.moonbase.moonbeam.network',
chainId: 1287,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonbase.rpc, {
chainId: providerRPC.moonbase.chainId,
name: providerRPC.moonbase.name,
}); // Change to correct network
// Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const _value = 3;
// Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// Create reset function
const increment = async () => {
console.log(
`Calling the increment by ${_value} function in contract at address: ${contractAddress}`
);
// Sign and send tx and wait for receipt
const createReceipt = await incrementer.increment(_value);
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// Call the reset function
increment();
To run the script, you can enter the following command in your terminal:
node increment.js
If successful, the transaction hash will be displayed in the terminal. You can use the get.js script alongside the increment.js script to make sure that value is changing as expected:
Next you can open the reset.js file and take the following steps to create the script:
- Import the
abifrom thecompile.jsfile - Set up the Ethers provider
- Define the
privateKeyfor the origin account and thecontractAddressof the deployed contract. The private key is required to create a wallet instance. Note: This is for example purposes only. Never store your private keys in a JavaScript file - Create a wallet using the
privateKeyandproviderfrom the previous steps. The wallet instance is used to sign transactions - Create an instance of the contract using the
ethers.Contractfunction and passing in thecontractAddress,abi, andprovider - Create the asynchronous
resetfunction - Use the contract instance to call one of the contract's methods and pass in any inputs if necessary. For this example, you will call the
resetmethod which doesn't require any inputs. You can useawaitwhich will return the value requested once the request promise resolves - Lastly, call the
resetfunction
// 1. Import the contract ABI
const { abi } = require('./compile');
// 2. Add the Ethers provider logic here:
// {...}
// 3. Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// 4. Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// 5. Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// 6. Create reset function
const reset = async () => {
console.log(
`Calling the reset function in contract at address: ${contractAddress}`
);
// 7. sign and send tx and wait for receipt
const createReceipt = await incrementer.reset();
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// 8. Call the reset function
reset();
View the complete script
// Import ethers and compile
const ethers = require('ethers');
const { abi } = require('./compile');
// Define network configurations
const providerRPC = {
development: {
name: 'moonbeam-development',
rpc: 'http://localhost:9944',
chainId: 1281,
},
moonbase: {
name: 'moonbase-alpha',
rpc: 'https://rpc.api.moonbase.moonbeam.network',
chainId: 1287,
},
};
// Create ethers provider
const provider = new ethers.JsonRpcProvider(providerRPC.moonbase.rpc, {
chainId: providerRPC.moonbase.chainId,
name: providerRPC.moonbase.name,
}); // Change to correct network
// Create variables
const accountFrom = {
privateKey: 'INSERT_YOUR_PRIVATE_KEY',
};
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
// Create wallet
let wallet = new ethers.Wallet(accountFrom.privateKey, provider);
// Create contract instance with signer
const incrementer = new ethers.Contract(contractAddress, abi, wallet);
// Create reset function
const reset = async () => {
console.log(
`Calling the reset function in contract at address: ${contractAddress}`
);
// Sign and send tx and wait for receipt
const createReceipt = await incrementer.reset();
await createReceipt.wait();
console.log(`Tx successful with hash: ${createReceipt.hash}`);
};
// Call the reset function
reset();
To run the script, you can enter the following command in your terminal:
node reset.js
If successful, the transaction hash will be displayed in the terminal. You can use the get.js script alongside the reset.js script to make sure that value is changing as expected:
| Created: March 1, 2022