2020-06-12 10:38:55 +03:00
|
|
|
_section: Getting Started @<getting-started>
|
2019-08-21 08:53:47 +03:00
|
|
|
|
|
|
|
|
2020-06-12 10:38:55 +03:00
|
|
|
_subsection: Installing @<installing>
|
2019-08-21 08:53:47 +03:00
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
Ethers' various Classes and Functions are available to import
|
2020-02-02 15:58:29 +03:00
|
|
|
manually from sub-packages under the [@ethersproject](link-ethers-npm)
|
2020-06-12 10:38:55 +03:00
|
|
|
organization but for most projects, the umbrella package is the
|
|
|
|
easiest way to get started.
|
2019-08-21 08:53:47 +03:00
|
|
|
|
2020-04-19 09:18:20 +03:00
|
|
|
_code: @lang<shell>
|
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
/home/ricmoo> npm install --save ethers
|
2019-08-21 08:53:47 +03:00
|
|
|
|
|
|
|
|
2020-06-12 10:38:55 +03:00
|
|
|
_subsection: Importing @<importing>
|
2019-08-21 08:53:47 +03:00
|
|
|
|
|
|
|
_heading: Node.js
|
|
|
|
|
2020-05-08 10:24:40 +03:00
|
|
|
_code: node.js require @lang<script>
|
2020-04-19 09:18:20 +03:00
|
|
|
|
|
|
|
const { ethers } = require("ethers");
|
|
|
|
|
2020-05-08 10:24:40 +03:00
|
|
|
_code: ES6 or TypeScript @lang<script>
|
|
|
|
|
2020-04-19 09:18:20 +03:00
|
|
|
import { ethers } from "ethers";
|
|
|
|
|
2019-08-21 08:53:47 +03:00
|
|
|
|
|
|
|
_heading: Web Browser
|
|
|
|
|
|
|
|
It is generally better practice (for security reasons) to copy the
|
2020-02-02 15:58:29 +03:00
|
|
|
[ethers library](link-ethers-js) to your own webserver and serve it
|
|
|
|
yourself.
|
2019-08-21 08:53:47 +03:00
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
For quick demos or prototyping though, you can load it in your
|
2019-08-21 08:53:47 +03:00
|
|
|
Web Applications from our CDN.
|
|
|
|
|
2020-05-08 10:24:40 +03:00
|
|
|
|
|
|
|
_code: ES6 in the Browser @lang<html>
|
2020-04-19 09:18:20 +03:00
|
|
|
|
2020-07-05 05:46:47 +03:00
|
|
|
<script type="module">
|
|
|
|
import { ethers } from "https://cdn.ethers.io/lib/ethers-5.0.esm.min.js";
|
|
|
|
// Your code here...
|
|
|
|
</script>
|
2020-05-08 10:24:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
_code: ES3 (UMD) in the Browser @lang<html>
|
|
|
|
|
|
|
|
<script src="https://cdn.ethers.io/lib/ethers-5.0.umd.min.js"
|
|
|
|
type="application/javascipt"></script>
|
2020-06-12 10:38:55 +03:00
|
|
|
|
|
|
|
|
|
|
|
_subsection: Common Terminology @<getting-started--glossary>
|
|
|
|
|
|
|
|
This section needs work...
|
|
|
|
|
|
|
|
_table: Common Terms
|
|
|
|
|
|
|
|
$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
|
2020-07-07 15:41:23 +03:00
|
|
|
specific contract on the Ethereum Network, so that applications
|
|
|
|
can use it like a normal JavaScipt object.
|
2020-06-12 10:38:55 +03:00
|
|
|
|
|
|
|
|
|
|
|
| **Provider** | $Provider |
|
|
|
|
| **Signer** | $Signer |
|
|
|
|
| **Contract** | $Contract |
|
|
|
|
|
|
|
|
|
|
|
|
_subsection: Connecting to Ethereum: Metamask @<getting-started--connecting>
|
|
|
|
|
|
|
|
The quickest and easiest way to experiment and begin developing on
|
|
|
|
Ethereum is to use [[link-metamask]], which is a browser extension
|
|
|
|
that provides:
|
|
|
|
|
|
|
|
- A connection to the Ethereum network (a [[Provider]])
|
2020-07-03 08:44:17 +03:00
|
|
|
- Holds your private key and can sign things (a [[Signer]])
|
2020-06-12 10:38:55 +03:00
|
|
|
|
|
|
|
_code: Connecting to Metamask @lang<script>
|
|
|
|
|
|
|
|
// A Web3Provider wraps a standard Web3 provider, which is
|
|
|
|
// what Metamask injects as window.ethereum into each page
|
|
|
|
const provider = new ethers.providers.Web3Provider(window.ethereum)
|
|
|
|
|
|
|
|
// The Metamask plugin also allows signing transactions to
|
|
|
|
// send ether and pay to change state within the blockchain.
|
2020-07-07 15:41:23 +03:00
|
|
|
// For this, you need the account signer...
|
2020-06-12 10:38:55 +03:00
|
|
|
const signer = provider.getSigner()
|
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
_subsection: Connecting to Ethereum: RPC @<getting-started--connecting-rpc>
|
|
|
|
|
|
|
|
The [JSON-RPC API](link-jsonrpc) is another popular method for interacting
|
|
|
|
with Ethereum and is available in all major Ethereum node implementations
|
|
|
|
(e.g. [[link-geth]] and [[link-parity]]) as well as many
|
|
|
|
third-party web services (e.g. [[link-infura]]). It typically provides:
|
|
|
|
|
|
|
|
- A connection to the Ethereum network (a [[Provider]])
|
|
|
|
- Holds your private key and can sign thing (a [[Signer]])
|
|
|
|
|
|
|
|
_code: Connecting to an RPC client @lang<script>
|
|
|
|
|
|
|
|
// 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()
|
2020-06-12 10:38:55 +03:00
|
|
|
|
|
|
|
_heading: Querying the Blockchain @<getting-started--querying>
|
|
|
|
|
|
|
|
Once you have a [[Provider]], you have a read-only connection to the
|
2020-07-07 15:41:23 +03:00
|
|
|
blockchain, which you can use to query the current state, fetch historic
|
2020-06-12 10:38:55 +03:00
|
|
|
logs, look up deployed code and so on.
|
|
|
|
|
|
|
|
_code: Basic Queries @lang<javascript>
|
|
|
|
|
|
|
|
// Look up the current block number
|
|
|
|
provider.getBlockNumber()
|
|
|
|
//!
|
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
// Get the balance of an account (by address or ENS name, if supported by network)
|
2020-06-12 10:38:55 +03:00
|
|
|
balance = await provider.getBalance("ethers.eth")
|
|
|
|
//! async balance
|
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
// Often you need to format the output to something more user-friendly,
|
|
|
|
// such as in ether (instead of wei)
|
2020-06-12 10:38:55 +03:00
|
|
|
ethers.utils.formatEther(balance)
|
|
|
|
//!
|
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
// If a user enters a string in an input field, you may need
|
2020-06-12 10:38:55 +03:00
|
|
|
// to convert it from ether (as a string) to wei (as a BigNumber)
|
|
|
|
ethers.utils.parseEther("1.0")
|
|
|
|
//!
|
|
|
|
|
|
|
|
|
|
|
|
_heading: Writing to the Blockchain @<getting-started--sending>
|
|
|
|
|
|
|
|
_code: Sending Ether @lang<script>
|
|
|
|
|
|
|
|
// Send 1 ether to an ens name.
|
|
|
|
const tx = signer.sendTransaction({
|
|
|
|
to: "ricmoo.firefly.eth",
|
|
|
|
value: ethers.utils.parseEther("1.0")
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_subsection: Contracts @<getting-started--contracts>
|
|
|
|
|
|
|
|
A Contract is an abstraction of program code which lives on the
|
|
|
|
Ethereum blockchain.
|
|
|
|
|
|
|
|
The [[Contract]] object makes it easier to use an on-chain
|
2020-07-07 15:41:23 +03:00
|
|
|
Contract as a normal JavaScript object, with the methods
|
2020-06-12 10:38:55 +03:00
|
|
|
mapped to encoding and decoding data for you.
|
|
|
|
|
2020-07-14 15:05:55 +03:00
|
|
|
If you are familiar with Databases, this is similar to an ////Object Relational Mapper// (ORM).
|
2020-06-12 10:38:55 +03:00
|
|
|
|
|
|
|
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 the //Application Binary Interface// (API)
|
|
|
|
provides.
|
|
|
|
|
2020-07-14 15:05:55 +03:00
|
|
|
This class is a //meta-class//, which means its methods are constructed
|
|
|
|
at runtime, and when you pass in the ABI to the constructor it uses it
|
2020-06-12 10:38:55 +03:00
|
|
|
to determine which methods to add.
|
|
|
|
|
2020-07-14 15:05:55 +03:00
|
|
|
While an on-chain Contract may have many methods available, you can safely ignore
|
2020-06-12 10:38:55 +03:00
|
|
|
any methods you don't need or use, providing a smaller subset of the ABI to
|
|
|
|
the contract.
|
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
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.
|
2020-06-12 10:38:55 +03:00
|
|
|
|
|
|
|
_code: Connecting to the DAI Contract @lang<javascript>
|
|
|
|
|
2020-07-07 15:41:23 +03:00
|
|
|
// You can also use an ENS name for the contract address
|
2020-06-12 10:38:55 +03:00
|
|
|
const 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 = [
|
2020-07-07 15:41:23 +03:00
|
|
|
// Some details about the token
|
2020-06-12 10:38:55 +03:00
|
|
|
"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 object
|
|
|
|
const daiContract = new ethers.Contract(daiAddress, daiAbi, provider);
|
|
|
|
|
|
|
|
|
|
|
|
_heading: Read-Only Methods @<getting-started--reading>
|
|
|
|
|
|
|
|
_code: Querying the DAI Contract @lang<javascript>
|
|
|
|
|
|
|
|
// <hide>
|
|
|
|
const daiAbi = [
|
|
|
|
// Some simple details about the token
|
|
|
|
"function name() view returns (string)",
|
|
|
|
"function symbol() view returns (string)",
|
|
|
|
|
|
|
|
// Get the account balance
|
|
|
|
"function balanceOf(address) view returns (uint)",
|
|
|
|
];
|
|
|
|
const daiContract = new ethers.Contract("dai.tokens.ethers.eth", daiAbi, provider);
|
|
|
|
// </hide>
|
|
|
|
|
|
|
|
// Get the ERC-20 token name
|
|
|
|
daiContract.name()
|
|
|
|
//!
|
|
|
|
|
|
|
|
// Get the ERC-20 token synbol (for tickers and UIs)
|
|
|
|
daiContract.symbol()
|
|
|
|
//!
|
|
|
|
|
|
|
|
// Get the balance of an address
|
|
|
|
balance = await daiContract.balanceOf("ricmoo.firefly.eth")
|
|
|
|
//! async balance
|
|
|
|
|
|
|
|
// Format the DAI for displaying to the user
|
|
|
|
ethers.utils.formatUnits(balance, 18)
|
|
|
|
//!
|
|
|
|
|
|
|
|
|
|
|
|
_heading: State Changing Methods @<getting-started--writing>
|
|
|
|
|
|
|
|
_code: Sending DAI @lang<script>
|
|
|
|
|
|
|
|
// The DAI Contract is currently connected to the Provider,
|
2020-07-14 15:05:55 +03:00
|
|
|
// which is read-only. You need to connect to a Signer, so
|
|
|
|
// that you can pay to send state-changing transactions.
|
2020-06-12 10:38:55 +03:00
|
|
|
const daiWithSigner = contract.connect(signer);
|
|
|
|
|
|
|
|
// Each DAI has 18 decimal places
|
|
|
|
const dai = ethers.utils.parseUnits("1.0", 18);
|
|
|
|
|
|
|
|
// Send 1 DAI to "ricmoo.firefly.eth"
|
|
|
|
tx = daiWithSigner.transfer("ricmoo.firefly.eth", dai);
|
|
|
|
|
|
|
|
|
|
|
|
_heading: Listening to Events @<getting-started--events>
|
|
|
|
|
|
|
|
_code: Listening to Events @lang<javascript>
|
|
|
|
|
|
|
|
// <hide>
|
|
|
|
const daiAbi = [
|
|
|
|
"event Transfer(address indexed, address indexed, uint256)"
|
|
|
|
];
|
|
|
|
const daiContract = new ethers.Contract("dai.tokens.ethers.eth", daiAbi, provider);
|
|
|
|
const formatEther = ethers.utils.formatEther;
|
|
|
|
// </hide>
|
|
|
|
|
|
|
|
// Receive an event when ANY transfer occurs
|
|
|
|
daiContract.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 tokens
|
|
|
|
myAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
|
|
|
|
filter = daiContract.filters.Transfer(null, myAddress)
|
|
|
|
// <hide>
|
|
|
|
filter
|
|
|
|
// </hide>
|
|
|
|
//!
|
|
|
|
|
|
|
|
// Receive an event when that filter occurs
|
|
|
|
daiContract.on(filter, (from, to, amount, event) => {
|
|
|
|
// The to will always be "address"
|
|
|
|
console.log(`I got ${ formatEther(amount) } from ${ from }.`);
|
|
|
|
});
|
|
|
|
|
|
|
|
// <hide>
|
|
|
|
// Don't want to block the docs from compiling...
|
|
|
|
daiContract.removeAllListeners();
|
|
|
|
// </hide>
|
|
|
|
|
|
|
|
|
|
|
|
_heading: Query Historic Events @<getting-started--history>
|
|
|
|
|
|
|
|
_code: Filtering Historic Events @lang<javascript>
|
|
|
|
|
|
|
|
// <hide>
|
|
|
|
const signer = new ethers.VoidSigner("0x8ba1f109551bD432803012645Ac136ddd64DBA72");
|
|
|
|
const daiAbi = [
|
|
|
|
"event Transfer(address indexed, address indexed, uint256)"
|
|
|
|
];
|
|
|
|
const daiContract = new ethers.Contract("dai.tokens.ethers.eth", daiAbi, provider);
|
|
|
|
//!
|
|
|
|
// </hide>
|
|
|
|
|
|
|
|
// Get the address of the Signer
|
|
|
|
myAddress = await signer.getAddress()
|
|
|
|
//! async myAddress
|
|
|
|
|
|
|
|
// Filter for all token transfers to me
|
|
|
|
filterFrom = daiContract.filters.Transfer(myAddress, null);
|
|
|
|
// <hide>
|
|
|
|
filterFrom
|
|
|
|
// </hide>
|
|
|
|
//!
|
|
|
|
|
|
|
|
// Filter for all token transfers from me
|
|
|
|
filterTo = daiContract.filters.Transfer(null, myAddress);
|
|
|
|
// <hide>
|
|
|
|
filterTo
|
|
|
|
// </hide>
|
|
|
|
//!
|
|
|
|
|
|
|
|
// List all transfers sent from me a specific block range
|
|
|
|
daiContract.queryFilter(filterFrom, 9843470, 9843480)
|
|
|
|
//!
|
|
|
|
|
|
|
|
//
|
|
|
|
// The following have had the results omitted due to the
|
|
|
|
// number of entries; but they provide some useful examples
|
|
|
|
//
|
|
|
|
|
2020-07-14 15:05:55 +03:00
|
|
|
// List all transfers sent in the last 10,000 blocks
|
2020-06-12 10:38:55 +03:00
|
|
|
daiContract.queryFilter(filterFrom, -10000)
|
|
|
|
|
|
|
|
// List all transfers ever sent to me
|
|
|
|
daiContract.queryFilter(filterTo)
|
|
|
|
|
|
|
|
|
|
|
|
_subsection: Signing Messages @<getting-started--signing>
|
|
|
|
|
|
|
|
_code: Signing Messages @lang<javascript>
|
|
|
|
|
|
|
|
// <hide>
|
|
|
|
const signer = ethers.Wallet.createRandom();
|
|
|
|
//!
|
|
|
|
// </hide>
|
|
|
|
|
2020-07-14 15:05:55 +03:00
|
|
|
// To sign a simple string, which are used for
|
|
|
|
// logging into a service, such as CryptoKitties,
|
2020-06-12 10:38:55 +03:00
|
|
|
// pass the string in.
|
|
|
|
signature = await signer.signMessage("Hello World");
|
|
|
|
//! async signature
|
|
|
|
|
|
|
|
//
|
|
|
|
// 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)
|
|
|
|
//
|
|
|
|
|
2020-07-14 15:05:55 +03:00
|
|
|
// This string is 66 characters long
|
2020-06-12 10:38:55 +03:00
|
|
|
message = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
|
|
|
|
|
|
|
// This array representation is 32 bytes long
|
|
|
|
messageBytes = ethers.utils.arrayify(message);
|
|
|
|
//!
|
|
|
|
|
|
|
|
// To sign a hash, you most often want to sign the bytes
|
|
|
|
signature = await signer.signMessage(messageBytes)
|
|
|
|
//! async signature
|