Основы блокчейна на примере написания криптовалютного кошелька. Часть 2
Я помогу вам написать приложение, которое будет получать информацию о балансе и транзакциях, а также отправлять монеты с одного кошелька на другой.
Познакомиться со мной, почитать первую часть и теорию можно тут.
Подготовка кошельков
Перед тем, как писать эту статью, я создал два кошелька Goerli Eth. Сделать это можно вручную — в этой статье останавливаться на этом моменте не будем, на Хабре есть достаточно туториалов. Для генерации кошельков можно использовать, например, MetaMask.
Второй вопрос, который у вас может возникнуть, это где взять монеты для тестирования. Обычно есть источники, их называют форсеты, которые готовы отправлять монеты для тестовых сред, и они просто гуглятся. Я например использовал GOERLI FAUCET.
Шаг 1. Создаем шаблон
Шаблон верстки для приложения я собрал на основе create-react-app. Выглядит он так:
В шаблоне есть поле для адреса, приватного ключ, кнопка запроса баланса, поле для адреса получателя и суммы для отправки, поле с комиссией, кнопка Send, поле для отображения хеша транзакции и кнопка запроса списка транзакций.
* Приложение лежит тут.
Шаг 2. Подключение Web3.js
На официальном сайте Web3.js есть инструкция по созданию экземпляра: туда нужно передать URL, предоставленный Infura:
const INFURA_URL = `https://goerli.infura.io/v3/${process.env.REACT_APP_INFURA_PROJECT_ID}`;
const web3 = new Web3(INFURA_URL);
Шаг 3. Запрос баланса
Для запроса баланса кошелька используем метод web3.eth.getBalance(address [, defaultBlock] [, callback]).
На вход он принимает адрес, баланс которого нужно получить, и два необязательных параметра:
- defaultBlock: блок, от которого нужно рассчитать баланс (по умолчанию — последний)
- callback: коллбэк функция, которая возвращает ошибку в случае ее возникновения, и баланс в случае успеха.
Ошибка возвращается первым параметром, а результат запроса — вторым:
export const getBalance = async ({ addressFrom }: {addressFrom: string}): Promise<string> => {
const balance = await web3.eth.getBalance(addressFrom, function(error, result) {
if(!error) {
return result;
}
else {
throw Error(error.message);
}
});
return web3.utils.fromWei(balance, "ether");
};
В метод можете передать три или два параметра. Вторым параметром служит блок или функция — это называется перегрузка функций. Если это для вас звучит незнакомо, то советую вам прочитать мою статью на эту тему.
Прежде, чем продолжить, разберемся в сути метода web3.utils.fromWei(balance, "ether"). У криптовалют тоже есть аналог копеек, но знаков после запятой в них значительно больше, чем два.
Например, у Ethereum 18 знаков после запятой, а минимальная единица называется wei. Один wei равен 0,00000000000000001 eth. При работе с блокчейном в большинстве случаев нужно указывать суммы в самых маленьких единицах без запятых, чтобы сумма была целочисленной. Например, вы хотите отправить 1,51 Eth, то в метод отправки нужно указывать сумму в wei — 1510000000000000000. Метод fromWei помогает конвертировать сумму из wei в Eth.
В нашем случае метод web3.utils.fromWei(balance, "ether") позволяет отобразить баланс кошелька в Goerli Eth. Второй параметр «ether» можно не указывать, так как является параметром по умолчанию, но я его пишу, так-как это делает код более читаемым.
Шаг 4. Отправка транзакции
Дальше на очереди функция sendTransaction. Есть несколько способов отправить транзакцию в сети Eth, мы пойдем через три этапа:
- Создание объекта транзакции
- Подпись транзакции с использованием приватного ключа
- Отправка транзакции в сеть
Создание объекта транзакции
Для того, чтобы понять из чего состоит транзакция, которую нам нужно подписать, обратимся к функции web3.eth.accounts.signTransaction(tx, privateKey [, callback]). Первым аргументом функция принимает tx, это и есть объект транзакции, и ниже описано то, из чего он состоит:
Обязательный параметр
- gas - String: The gas provided by the transaction.
Параметры, которые понадобятся нам для отправки средств
- to - String: (optional) The recevier of the transaction, can be empty when deploying a contract.
- value - String: (optional) The value of the transaction in wei.
Сформируем объект транзакции:
const transactionObject: { to: string, value: string, gas?: number } = {
to: addressTo,
value: web3.utils.toWei(amount, "ether"),
};
С «to» все понятно: функция будет принимать на вход адрес получателя и подставлять его в объект транзакции. «Value» мы будем принимать в «ether» разрядности и трансформировать в «wei», так как это требование сети. Остается разобраться в том, что такое gas.
У gas есть два основных предназначения — это комиссия, которую нужно заплатить за выполнение транзакции, и ограничение количества транзакций в блоке. Нас для цели отправки транзакции интересует комиссия.
Чтобы получить значение gas мы можем использовать функцию web3.eth.estimateGas(callObject [, callback]), которая принимает на вход объект — в нашем случае объект транзакции, который мы начали делать:
transactionObject.gas = await web3.eth.estimateGas(transactionObject);
Теперь наш объект транзакции готов — там есть получатель, сумма и размер комиссии.
* Я написал функцию getFee — в рамках этой статьи я не буду на ней останавливаться, но вы можете ознакомиться с ней самостоятельно в репозитории проекта.
Подпись транзакции
Дальше мы переходим ко второму этапу — подпись нашей транзакции используя приватный ключ. Для этого нам нужен метод web3.eth.accounts.signTransaction(tx, privateKey [, callback]) который как мы видимо принимает объект транзакции и приватный ключ. А возвращает этот метод объект, где есть несколько полей, но нас интересует поле:
- rawTransaction - String: The RLP encoded transaction, ready to be send using web3.eth.sendSignedTransaction.
Его мы и должны получить:
const { rawTransaction: signedTransaction } = await web3.eth.accounts.signTransaction(transactionObject, privateKey);
Отправка транзакции в сеть
Получив подписанную транзу, мы должны отправить ее в блокчейн, используя метод web3.eth.sendSignedTransaction(signedTransactionData [, callback]).
Теперь хочу рассказать еще об одной особенности библиотеки Web3js — это так называемые Callbacks Promises Events или PromiEvent. Библиотека доработала обычные промисы, добавив к ним дополнительные функции:
- on, добавляет слушатель на определенное событие, который будет отрабатывать каждый раз, когда событие будет происходить
- once, добавляет слушатель на определенное событие, который будет отрабатывать только при первом событии
- off, удаляет ранее добавленный слушатель.
Мы сделаем следующим образом — добавим слушатель на события "sending", "sent" и "transactionHash", чтобы отображать статус нашей отправки. А возвращать функцию receipt, или квитанцию в момент, когда транзакция была проверена и добавлена в конкретный блок.
const receipt = await web3.eth
.sendSignedTransaction(signedTransaction)
.once("sending", () => {
setSendingStatus("The transaction is broadcast to the blockchain")
})
.once("sent", () => {
setSendingStatus("Expecting a hash")
})
.once("transactionHash", (transactionHash) => {
setSendingStatus(`Transaction verified, hash is: ${transactionHash}`)
});
Когда мы получим квитанцию, то можно считать что отправка средств произошла, хотя формально стоит дождаться подтверждения.
* Подтверждение происходит, когда поверх блока, в котором записана наша транзакция, будут добавлены еще блоки.
Шаг 5. Запрос списка транзакций
* Нам осталось поработать с эксплорером.
Давайте запросим и отобразим список транзакций по адресу. Тут все невероятно просто — мы имеем дело с обычной API, в которую мы отправляем запрос и получаем ответ. Идем на официальный сайт etherscan, и смотрим что нам нужно сделать:
- Зарегистрировать аккаунт
- Получить API Key
- Использовать нужный URL - нас интересует goerly
- Идем в аккаунт и смотрим как запросить список транзакций.
export const getTransactionList = async ({addressFrom}:{addressFrom: string}): Promise<EthTx[]> => {
const url = `https://api-goerli.etherscan.io/api?module=account&action=txlist&address=${addressFrom}&startblock=0&endblock=99999999&page=1&offset=10&sort=asc&apikey=${ETHERSCAN_API_KEY}`
const response = await fetch(url)
const { result }: {result: EthTx[]} = await response.json();
return result.map(tx => (
{
.. .tx,
value: web3.utils.fromWei(tx.value, "ether"),
timeStamp: new Date(Math.ceil(Number(tx.timeStamp) * 1000)).toLocaleDateString() + " " + new Date(Math.ceil(Number(tx.timeStamp) * 1000)).toLocaleTimeString()
}
));
};
P.S. (Post Scriptum)
Криптоиндустрия может показаться сложной и запутанной на первый взгляд, но я надеюсь, что в ходе этой статьи я смог показать вам, что она на самом деле не так уж и страшна. Вам потребуется ознакомиться с новыми концепциями, такими как приватные и публичные ключи, мемпул и валидаторы. Вы столкнетесь с ограничениями, например, работой с числами с 18 знаками после запятой. Кроме того, вы обнаружите уникальные решения в конкретных блокчейнах, таких как TRON или DOT.
Совет: начинающим в блокчейне стоит идти от простого к сложному и не торопиться — подобно любой другой области, вы будете приобретать знания и навыки по мере своего развития. Если у вас уже давно зрело желание начать свой путь в мире блокчейна, но страхи останавливали вас, я надеюсь, что эта статья помогла вам снизить их.