Ethers' various Classes and Functions are available to import manually from sub-packages under the@ethersproject organization but for most projects, the umbrella package is the easiest way to get started.
/home/ricmoo> npm install --save ethers
const { ethers } = require("ethers");
import { ethers } from "ethers";
It is generally better practice (for security reasons) to copy theethers library to your own webserver and serve it yourself.
For quick demos or prototyping though, you can load it in your Web Applications from our CDN.
<script type="module"> import { ethers } from "https://cdn.ethers.io/lib/ethers-5.2.esm.min.js"; // Your code here...</script>
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>
This section needs work...
Provider | A Provider (in ethers) is a class which provides an abstraction for a connection to the Ethereum Network. It provides read-only access to the Blockchain and its status. | |
Signer | A Signer is a class which (usually) in some way directly or indirectly has access to a private key, which can sign messages and transactions to authorize the network to charge your account ether to perform operations. | |
Contract | A Contract is an abstraction which represents a connection to a specific contract on the Ethereum Network, so that applications can use it like a normal JavaScript object. | |
Common Terms | |
Connecting to Ethereum: MetaMask
The quickest and easiest way to experiment and begin developing on Ethereum is to useMetaMask, which is a browser extension that provides:
- A connection to the Ethereum network (aProvider)
- Holds your private key and can sign things (aSigner)
// A Web3Provider wraps a standard Web3 provider, which is// what MetaMask injects as window.ethereum into each pageconst provider = new ethers.providers.Web3Provider(window.ethereum)// MetaMask requires requesting permission to connect users accountsawait provider.send("eth_requestAccounts", []);// The MetaMask plugin also allows signing transactions to// send ether and pay to change state within the blockchain.// For this, you need the account signer...const signer = provider.getSigner()
Connecting to Ethereum: RPC
TheJSON-RPC API is another popular method for interacting with Ethereum and is available in all major Ethereum node implementations (e.g.Geth andParity) as well as many third-party web services (e.g.INFURA). It typically provides:
- A connection to the Ethereum network (aProvider)
- Holds your private key and can sign things (aSigner)
Connecting to an RPC client
// If you don't specify a //url//, Ethers connects to the default// (i.e. ``http:/\/localhost:8545``)const provider = new ethers.providers.JsonRpcProvider();// The provider also allows signing transactions to// send ether and pay to change state within the blockchain.// For this, we need the account signer...const signer = provider.getSigner()
Once you have aProvider, you have a read-only connection to the blockchain, which you can use to query the current state, fetch historic logs, look up deployed code and so on.
// Look up the current block numberawait provider.getBlockNumber()// 16987688// Get the balance of an account (by address or ENS name, if supported by network)balance = await provider.getBalance("ethers.eth")// { BigNumber: "182334002436162568" }// Often you need to format the output to something more user-friendly,// such as in ether (instead of wei)ethers.utils.formatEther(balance)// '0.182334002436162568'// If a user enters a string in an input field, you may need// to convert it from ether (as a string) to wei (as a BigNumber)ethers.utils.parseEther("1.0")// { BigNumber: "1000000000000000000" }
Writing to the Blockchain
// Send 1 ether to an ens name.const tx = signer.sendTransaction({ to: "ricmoo.firefly.eth", value: ethers.utils.parseEther("1.0")});
A Contract is an abstraction of program code which lives on the Ethereum blockchain.
TheContract object makes it easier to use an on-chain Contract as a normal JavaScript object, with the methods mapped to encoding and decoding data for you.
If you are familiar with Databases, this is similar to anObject Relational Mapper (ORM).
In order to communicate with the Contract on-chain, this class needs to know what methods are available and how to encode and decode the data, which is what theApplication Binary Interface (ABI) provides.
This class is ameta-class, which means its methods are constructed at runtime, and when you pass in the ABI to the constructor it uses it to determine which methods to add.
While an on-chain Contract may have many methods available, you can safely ignore any methods you don't need or use, providing a smaller subset of the ABI to the contract.
An ABI often comes from the Solidity or Vyper compiler, but you can use the Human-Readable ABI in code, which the following examples use.
Connecting to the DAI Contract
// You can also use an ENS name for the contract addressconst daiAddress = "dai.tokens.ethers.eth";// The ERC-20 Contract ABI, which is a common contract interface// for tokens (this is the Human-Readable ABI format)const daiAbi = [ // Some details about the token "function name() view returns (string)", "function symbol() view returns (string)", // Get the account balance "function balanceOf(address) view returns (uint)", // Send some of your tokens to someone else "function transfer(address to, uint amount)", // An event triggered whenever anyone transfers to someone else "event Transfer(address indexed from, address indexed to, uint amount)"];// The Contract objectconst daiContract = new ethers.Contract(daiAddress, daiAbi, provider);
Querying the DAI Contract
// Get the ERC-20 token nameawait daiContract.name()// 'Dai Stablecoin'// Get the ERC-20 token symbol (for tickers and UIs)await daiContract.symbol()// 'DAI'// Get the balance of an addressbalance = await daiContract.balanceOf("ricmoo.firefly.eth")// { BigNumber: "19862540059122458201631" }// Format the DAI for displaying to the userethers.utils.formatUnits(balance, 18)// '19862.540059122458201631'
// The DAI Contract is currently connected to the Provider,// which is read-only. You need to connect to a Signer, so// that you can pay to send state-changing transactions.const daiWithSigner = contract.connect(signer);// Each DAI has 18 decimal placesconst dai = ethers.utils.parseUnits("1.0", 18);// Send 1 DAI to "ricmoo.firefly.eth"tx = daiWithSigner.transfer("ricmoo.firefly.eth", dai);
// Receive an event when ANY transfer occursdaiContract.on("Transfer", (from, to, amount, event) => { console.log(`${ from } sent ${ formatEther(amount) } to ${ to}`); // The event object contains the verbatim log data, the // EventFragment and functions to fetch the block, // transaction and receipt and event functions});// A filter for when a specific address receives tokensmyAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";filter = daiContract.filters.Transfer(null, myAddress)// {// address: 'dai.tokens.ethers.eth',// topics: [// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',// null,// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'// ]// }// Receive an event when that filter occursdaiContract.on(filter, (from, to, amount, event) => { // The to will always be "address" console.log(`I got ${ formatEther(amount) } from ${ from }.`);});
Filtering Historic Events
// Get the address of the SignermyAddress = await signer.getAddress()// '0x8ba1f109551bD432803012645Ac136ddd64DBA72'// Filter for all token transfers from mefilterFrom = daiContract.filters.Transfer(myAddress, null);// {// address: 'dai.tokens.ethers.eth',// topics: [// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'// ]// }// Filter for all token transfers to mefilterTo = daiContract.filters.Transfer(null, myAddress);// {// address: 'dai.tokens.ethers.eth',// topics: [// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',// null,// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'// ]// }// List all transfers sent from me in a specific block rangeawait daiContract.queryFilter(filterFrom, 9843470, 9843480)// [// {// address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',// args: [// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',// '0x8B3765eDA5207fB21690874B722ae276B96260E0',// { BigNumber: "4750000000000000000" },// amount: { BigNumber: "4750000000000000000" },// from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',// to: '0x8B3765eDA5207fB21690874B722ae276B96260E0'// ],// blockHash: '0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78',// blockNumber: 9843476,// data: '0x00000000000000000000000000000000000000000000000041eb63d55b1b0000',// decode: [Function (anonymous)],// event: 'Transfer',// eventSignature: 'Transfer(address,address,uint256)',// getBlock: [Function (anonymous)],// getTransaction: [Function (anonymous)],// getTransactionReceipt: [Function (anonymous)],// logIndex: 69,// removeListener: [Function (anonymous)],// removed: false,// topics: [// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72',// '0x0000000000000000000000008b3765eda5207fb21690874b722ae276b96260e0'// ],// transactionHash: '0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1',// transactionIndex: 81// },// {// address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',// args: [// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',// '0x00De4B13153673BCAE2616b67bf822500d325Fc3',// { BigNumber: "250000000000000000" },// amount: { BigNumber: "250000000000000000" },// from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',// to: '0x00De4B13153673BCAE2616b67bf822500d325Fc3'// ],// blockHash: '0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78',// blockNumber: 9843476,// data: '0x00000000000000000000000000000000000000000000000003782dace9d90000',// decode: [Function (anonymous)],// event: 'Transfer',// eventSignature: 'Transfer(address,address,uint256)',// getBlock: [Function (anonymous)],// getTransaction: [Function (anonymous)],// getTransactionReceipt: [Function (anonymous)],// logIndex: 70,// removeListener: [Function (anonymous)],// removed: false,// topics: [// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72',// '0x00000000000000000000000000de4b13153673bcae2616b67bf822500d325fc3'// ],// transactionHash: '0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1',// transactionIndex: 81// }// ]//// The following have had the results omitted due to the// number of entries; but they provide some useful examples//// List all transfers sent in the last 10,000 blocksawait daiContract.queryFilter(filterFrom, -10000)// List all transfers ever sent to meawait daiContract.queryFilter(filterTo)
// To sign a simple string, which are used for// logging into a service, such as CryptoKitties,// pass the string in.signature = await signer.signMessage("Hello World");// '0x550a28a0fab4e30de31ed651851bb2908ff4dd1b7c6a5cdcf62a8e2accbec1d53e27a6081538759a971d0c71d35ab571eda1e3a565da9948ac83361aa28566e21c'//// A common case is also signing a hash, which is 32// bytes. It is important to note, that to sign binary// data it MUST be an Array (or TypedArray)//// This string is 66 characters longmessage = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"// This array representation is 32 bytes longmessageBytes = ethers.utils.arrayify(message);// Uint8Array [ 221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239 ]// To sign a hash, you most often want to sign the bytessignature = await signer.signMessage(messageBytes)// '0x471250f1080eb2686c3cdc94491eb52e56e45a69225f3ca82b72e5c2ad7195ff3146ce8ec2445d8593dface9526a180e9df35ac043135f40fe8cdd64278bdd591c'