Ethers.js入门
术语解释
Provider: Provider 是一个为连接到以太坊网络提供抽象的类,提供对blockchain及其状态的只读访问
Signer: Signer是一个类,通常以某种方式直接或间接地接触到一个私钥,它可以签署消息和交易,以授权网络向你的账户收取以太坊来执行操作
Contract: Contract是一个抽象概念,它代表了与以太坊网络上特定合约的连接,因此,应用程序可以像普通的JavaScript对象一样使用它
连接方式及操作
Connecting to Ethereum: MetaMask
在Ethereum上进行实验和开始开发的最快速和最简单的方法是使用MetaMask,它是一个浏览器扩展
// Web3Provider包装了一个标准的Web3 provider,MetaMask为每个页面注入了window.ethereum
const provider = new ethers.providers.Web3Provider(window.ethereum);
// MetaMask Plugin 还允许签名transactions 来发送ether和支付以改变链上的状态,所以需要 signer
const signer = provider.getSigner();
Connecting to Ethereum: RPC
JSON-RPC API是另一种常用的与Ethereum交互的方式,在所有主要的以太坊节点实现(Geth, Parity)以及许多第三方网络服务(Infura)中都有
// 如果你不指定一个 url地址,会默认为: http://localhost:8545
const provider = new ethers.providers.JsonRpcProvider();
// 也允许签名transactions
const signer = provider.getSigner();
Querying the Blockchain
一旦你有了Provider,你就有了与Blockchain的只读连接,可以用它来查询当前状态,获取历史日志,查询部署的代码等等
// Look up the current block number
await provider.getBlockNumber(); // 137223
// Get the balance of an account (by address or ENS name, if supported by network)
balance = await provider.getBalance("ethers.eth");
// { BigNumber: "2337132817842795605" }
ethers.utils.formatEther(balance);
// '2.337132817842795605'
ethers.utils.parseEther("1.0");
// { BigNumber: "1000000000000000000" }
Note: ENS(Ethereum Name Service)之于以太坊就像是DNS对于因特网,ENS允许用户使用个性化的以太坊域名为自己注册,比如用“vitalik.eth”来替代密码地址‘0x32b724f073ec346edd64b0cc67757e4f6fe42950’。ENS一直在迭代开发中,ENS不仅仅用于以太坊地址,未来ENS将覆盖以太坊的各个方面
Writing to the Blockchain
// Send 1 ether to an ens name.
const tx = signer.sendTransaction({
to: "marktang.firefly.eth",
value: ethers.utils.parseEther("1.0")
});
Contracts
合约是在Ethereum blockchain上运行的代码的一种抽象
Contract类为了与链上合约进行通信,需要知道有哪些方法可以调用,这需要传入ABI文件
这个类是一个meta-class,在运行时构造的,传入ABI给构造函数时,它用ABI来决定要添加哪些方法
ABI由 Solidity或Vyper编译成,但你可以在代码中使用人类可读的ABI,如:
// You can also use an ENS name for the contract address
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 = [
// 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 object
const daiContract = new ethers.Contract(daiAddress, daiAbi, provider);
Read-Only Methods
// Get the ERC-20 token name
await daiContract.name()
// 'Dai Stablecoin'
// Get the ERC-20 token symbol (for tickers and UIs)
await daiContract.symbol()
// 'DAI'
// Get the balance of an address
balance = await daiContract.balanceOf("ricmoo.firefly.eth")
// { BigNumber: "18190624174838529547383" }
// Format the DAI for displaying to the user
ethers.utils.formatUnits(balance, 18)
// '18190.624174838529547383'
State Changing Methods
// DAI 合约目前连接到Provider,但是Provider是 read-only的
// 需要连接到一个Signer,这样就可以付费发送一些改变状态的交易
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);
Listening to Events
// Receive an event when ANY transfer occurs
daiContract.on("Transfer", (from, to, amount, event) => {
console.log(`${ from } sent ${ formatEther(amount) } to ${ to}`);
// 事件对象包含逐字记录数据、EventFragment和获取块、交易和接收的函数以及事件函数
});
// @Todo: I don't know if Can I get all transactions here.
// A filter for when a specific address receives tokens
myAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
filter = daiContract.filters.Transfer(null, myAddress)
// {
// address: 'dai.tokens.ethers.eth',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
// 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 }.`);
});
Query Historic Events
// Get the address of the Signer
myAddress = await signer.getAddress()
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72'
// Filter for all token transfers from me
filterFrom = daiContract.filters.Transfer(myAddress, null);
// {
// address: 'dai.tokens.ethers.eth',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
// Filter for all token transfers to me
filterTo = daiContract.filters.Transfer(null, myAddress);
// {
// address: 'dai.tokens.ethers.eth',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
// List all transfers sent from me a specific block range
await 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],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function],
// getTransaction: [Function],
// getTransactionReceipt: [Function],
// logIndex: 69,
// removeListener: [Function],
// 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],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function],
// getTransaction: [Function],
// getTransactionReceipt: [Function],
// logIndex: 70,
// removeListener: [Function],
// removed: false,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72',
// '0x00000000000000000000000000de4b13153673bcae2616b67bf822500d325fc3'
// ],
// transactionHash: '0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1',
// transactionIndex: 81
// }
// ]
// List all transfers sent in the last 10,000 blocks
await daiContract.queryFilter(filterFrom, -10000)
// List all transfers ever sent to me
await daiContract.queryFilter(filterTo)
Signing Messages
// 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");
// '0x5a77beb84677b221d7110b08605a2658dd6c1e88a2ba9293436e587dbf6479d4798d0ca34ba2113bdb51ad97cd831975ccaccaf5f6fbd5d566fe662f11e2ca411b'
//
// 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 long
message = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
// This array representation is 32 bytes long
messageBytes = 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 bytes
signature = await signer.signMessage(messageBytes)
// '0xe099cce5e80dec1d8464d00c4156855d1c14cc4f83473deee7ab8e60be770f4b5ea04e90e7b7102ac5b493f7822b0ad10dc26bfda0761530a58e5f461f90b2fd1b'
\