Ethers.js и web3.js — это две библиотеки JavaScript с открытым исходным кодом, которые позволяют разработчикам взаимодействовать с блокчейном Ethereum и выполнять разные задачи.
Данная диаграмма отображает тенденции npm
между ethers.js и web3.js:
Web3.js разработана Ethereum Foundation при участии 281 специалиста. Библиотека широко используется во многих проектах.
В статье мы внимательно изучим ethers.js, которую создал и поддерживает канадский разработчик Rick Moore (Рик Мур). На данный момент она насчитывает 14 участников разработки. У библиотеки небольшой пакет установки, она протестирована, задокументирована и отлично обслуживается.
Рабочая среда
Remix, полностековый фреймворк React, послужит основой для изучения ethers.js. Создаем проект Remix
, выполняя следующую команду:
% npx create-remix my-remix-app
% cd my-remix-app
Устанавливаем ethers
вместе с react-json-pretty
:
npm i ethers react-json-pretty
Эти пакеты становятся частью dependencies
в package.json
:
"dependencies": {
"@remix-run/node": "^1.3.5",
"@remix-run/react": "^1.3.5",
"@remix-run/serve": "^1.3.5",
"ethers": "^5.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-json-pretty": "^2.2.0"
}
Рабочая среда Remix
готова для работы с ethers.js
.
Классы Provider, Signer и Contract
Тремя основными классами в ether.js являются Provider
(Провайдер), Signer
(Подписант) и Contract
(Контракт). Они применяются для взаимодействия со смарт-контрактом.
Provider
Provider
— это класс, который обеспечивает подключение к сети Ethereum Network, а также предоставляет доступ только для чтения к блокчейну и его состоянию.
Для создания Provider
используется getDefaultProvider
, который экспортируется из ethers
. Он принимает два параметра, первый из которых — network
с одним из следующих значений.
homestead
— основная сеть Mainnet.ropsten
— тестовая сеть Ropsten по принципу proof-of-work (с подтверждением выполнения работы).rinkeby
— тестовая сеть Rinkeby по принципу proof-of-authority (с подтверждением полномочий).goerli
— тестовая сеть Görli, работающая с клиентами.kovan
— тестовая сеть Kovan по принципу proof-of-authority (с подтверждением полномочий).
Второй параметр — options
. Он поддерживает нижеуказанные пары “ключ-значение”:
etherscan
:ETHERSCAN_API_KEY
;infura
:INFURA_PROJECT_ID
;alchemy
:ALCHEMY_API_KEY
;pocket
:POCKET_APPLICATION_KEY
;ankr
:ANKR_API_KEY
.
Выбираем сеть infura
вместе с INFURA_PROJECT_ID
, 57e665ef67b44c4687ad529b8b89397c
, созданным в проекте web3.js
.
import { getDefaultProvider } from "ethers";
export const provider = new getDefaultProvider("homestead", {
infura: "57e665ef67b44c4687ad529b8b89397c",
});
Как вариант, можно создать Provider
, импортируя provides
из ethers
, и new providers.InfuraProvider
с теми же параметрами.
import { providers } from "ethers";
export const provider = new providers.InfuraProvider("homestead", "57e665ef67b44c4687ad529b8b89397c");
Signer
Signer
— это класс, который применяет приватный ключ для подписания сообщений/транзакций с целью авторизации операций. Он представляет собой абстракцию адреса пользовательского кошелька. Его можно инстанцировать посредством статического метода Wallet
.
import { Wallet } from "ethers";
export const signer = Wallet.createRandom();
Contract
Contract
— это класс, представляющий собой подключение к конкретному контракту в сети Ethereum.
Мы можем импортировать Contract
из ethers
и создать экземпляр с new Contract(daiAddress, daiAbi, provider)
.
daiAddress
— служба именования адресов Ethereum (англ. Ethereum Name Service, ENS). Dai является стабильной криптовалютой, которая стремится поддерживать свою стоимость как можно ближе к одному доллару США (USD).daiAbi
определяет контракт с двоичным интерфейсом приложения (англ. Application Binary Interface, ABI), который описывает имеющиеся у него методы и события. Эти методы нужны для взаимодействия с контрактом в блокчейне (управляемый контракт, англ. on-chain), а также для кодирования и декодирования данных.
const daiAbi = [
// информация о токене
"function name() view returns (string)",
"function symbol() view returns (string)",
// получение баланса счета
"function balanceOf(address) view returns (uint)",
// отправка токенов другим лицам
"function transfer(address to, uint amount)",
// запуск события при выполнении транзакций другим лицам
"event Transfer(address indexed from, address indexed to, uint amount)"
];
provider
— подключение к сети Ethereum.
Provider, Signer и Contract в Remix
Remix
— наша рабочая среда. app/entry.server.jsx
— первый код JavaScript, который выполняется при поступлении запроса на сервер. Он загружает только необходимые данные, но разработчики должны обработать ответ. Этот файл рендерит приложение React в строку/поток, который отправляется клиенту в качестве ответа.
Ниже представлен измененный app/entry.server.jsx
, который создает provider
(строка 6), signer
(строка 11) и contract
(строка 33).
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
import { Contract, Wallet, getDefaultProvider } from "ethers";
// создание provider
export const provider = new getDefaultProvider("homestead", {
infura: "57e665ef67b44c4687ad529b8b89397c",
});
// создание signer
export const signer = Wallet.createRandom();
// создание первого параметра Contract
const daiAddress = "dai.tokens.ethers.eth";
// создание второго параметра Contract
const daiAbi = [
// информация о токене
"function name() view returns (string)",
"function symbol() view returns (string)",
// получение баланса счета
"function balanceOf(address) view returns (uint)",
// отправка токенов другим лицам
"function transfer(address to, uint amount)",
// запуск события при выполнении транзакций другим лицам
"event Transfer(address indexed from, address indexed to, uint amount)"
];
// создание Contract
export const daiContract = new Contract(daiAddress, daiAbi, provider);
export default function handleRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
) {
let markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
Эти экземпляры импортируются в индексный маршрут app/routes/index.jsx
, который вызывается по умолчанию.
import { useLoaderData } from "@remix-run/react";
import JSONPretty from "react-json-pretty";
import { utils } from "ethers";
import { daiContract, provider, signer } from "../entry.server";
export const loader = async () => {
const privateKey = signer.privateKey;
console.log('privateKey =', privateKey);
// privateKey = 0x54662899fea8d6fcd983549e9f17e725ef11d415cd715eaead74e1fa3ceb599a
const publicKey = signer.publicKey;
console.log('publicKey =', publicKey);
// publicKey = 0x044214736f4b64f7061edde89e78298d47bb91b67ae713edd1accb4601c8f513f0f37218d343a2a974d8e46e7075b39c9c51ce3df6b5a223e5633fda410b039c85
const address = await signer.getAddress();
console.log('address =', address);
// address = 0xf9911429fA0A7BACA28C9093398AFD527f73e6fF
const signatureForString = await signer.signMessage('Hello World');
console.log('signatureForString =', signatureForString);
// signatureForString = 0x43c669b9b08a835c922f265a00d185966ff97ad9fb0385e72711e6f54d521872003ff80c68f02e2e56d9b9e73400a5e8841c70f4eee515158991cc2d222178f91c
const messageHash = utils.id("Hello World"); // 0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba
const messageHashBytes = utils.arrayify(messageHash)
// Uint8Array(32) [
// 89, 47, 167, 67, 136, 159, 199, 249,
// 42, 194, 163, 123, 177, 245, 186, 29,
// 175, 42, 92, 132, 116, 28, 160, 224,
// 6, 29, 36, 58, 46, 103, 7, 186
// ]
const signatureForHash = await signer.signMessage(messageHashBytes);
console.log('signatureForHash =', signatureForHash);
// signatureForHash = 0x6f2f0911228927a9e6e54050a4c9ef4eb44df2de375ea5c057066008fc8d9dce77d035e82dc6fa7a6928d9b4063c45b49d56f3b55f2f43802e2d985539217c3f1c
const contractName = await daiContract.name();
console.log('contractName =', contractName);
// contractName = Dai Stablecoin
const contractSymbol = await daiContract.symbol();
console.log('contractSymbol =', contractSymbol);
// contractSymbol = DAI
const contractBalance = await daiContract.balanceOf('dai.tokens.ethers.eth');
console.log('contractBalance =', contractBalance);
// contractBalance = BigNumber { _hex: '0x758d5512acfe9a662174', _isBigNumber: true }
console.log('in Wei =', utils.formatUnits(contractBalance, 'wei')); // in Wei = 555123999562393853501812
console.log('in Kwei =', utils.formatUnits(contractBalance, 'kwei')); // in Kwei = 555123999562393853501.812
console.log('in Mwei =', utils.formatUnits(contractBalance, 'mwei')); // in Mwei = 555123999562393853.501812
console.log('in Gwei =', utils.formatUnits(contractBalance, 'gwei')); // in Gwei = 555123999562393.853501812
console.log('in Szabo =', utils.formatUnits(contractBalance, 'szabo')); // in Szabo = 555123999562.393853501812
console.log('in Finney =', utils.formatUnits(contractBalance, 'finney')); // in Finney = 555123999.562393853501812
console.log('in Ether =', utils.formatUnits(contractBalance, 'ether')); // in Ether = 555123.999562393853501812
return new Promise((resolve) => resolve(provider));
};
export default function Index() {
const result = useLoaderData();
return <JSONPretty data={result} />;
}
Строка 2. Импорт react-json-pretty
как JSONPretty
, который придает привлекательный вид данным JSON
. Этот компонент используется в строке 60.
Строка 3. Импорт коллекции утилит utils
из ethers
.
Строка 4. Импорт daiContract
, provider
и signer
из app/entry.server.jsx
.
Функция loader
(строки 6–56) — это специальный API. Она экспортируется для вызова на сервере перед рендерингом. Нужна для отображения содержимого signer
, daiContract
и provider
.
В отношении signer
мы можем просмотреть приватный ключ (строка 7), публичный ключ (строка 11) и адрес (строка 15).
Подписание сообщений применяется для различных методов аутентификации и операций вне блокчейна (неуправляемый контракт, англ. off-chain), которые при необходимости можно переместить в блокчейн.
Строка 19. Подписание строкового сообщения.
Строка 31. Подписание хеш-суммы.
Строка 35. Извлечение имени контракта.
Строка 39. Извлечение имени символа.
Строка 43. Извлечение баланса в формате BigNumber
, который является объектом, позволяющим безопасно выполнять математические операции с числами любой величины.
Нативная криптовалюта Ethereum — это эфир (англ. ether, ETH), а ее наименьшей единицей по умолчанию является Wei. Ниже представлен курс конвертации между разными единицами:
1 Ether = 10³Finney = 10⁶Szabo = 10⁹Gwei = 10¹²Mwei = 10¹⁵Kwei = 10¹⁸Wei
Строки 47–53. Форматирование баланса по различным единицам.
Строка 55. Выполнение функции loader
с объектом provider
.
Строка 59. Вызов useLoaderData
для извлечения загруженных данных result
.
Строка 60. JSONPretty
отображает result
в браузере.
Запускаем приложение Remix
с помощью npm run dev
, переходим в окно браузера и видим длинный список объекта provider
:
Подожмем объект provider
с помощью JSON Viewer по ссылке http://jsonviewer.stack.hu/
:
Он отображает свойства и методы первого уровня.
По сравнению с web3.js структура данных в ethers.js более компактная. Кроме того, отсутствует проблема циклических ссылок. В остальном же они выполняют похожие операции.
Заключение
Мы рассмотрели принцип использования ethers.js для взаимодействия с виртуальной машиной Ethereum через Infura.
Ethers.js и web3.js — две библиотеки JavaScript, позволяющие разработчикам взаимодействовать с блокчейном Ethereum. В процессе разработки Web3 они обе активно наращивают свой функционал. Разработка этих пакетов JavaScript нацелена на проектирование будущего интернета.
Читайте также:
- Взгляд на RedwoodJS
- Получение доступа к ID элементов в DOM в качестве переменных window/global
- Объектно-ориентированное программирование для самых маленьких
Читайте нас в Telegram, VK и Дзен
Перевод статьи Jennifer Fu: Use Ethers.js to Interact With the Ethereum Virtual Machine in Remix