Основы блокчейна на примере написания криптовалютного кошелька. Часть 2

Основы блокчейна на примере написания криптовалютного кошелька. Часть 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, мы пойдем через три этапа:

  1. Создание объекта транзакции
  2. Подпись транзакции с использованием приватного ключа
  3. Отправка транзакции в сеть

Создание объекта транзакции

Для того, чтобы понять из чего состоит транзакция, которую нам нужно подписать, обратимся к функции 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]) который как мы видимо принимает объект транзакции и приватный ключ. А возвращает этот метод объект, где есть несколько полей, но нас интересует поле:

Его мы и должны получить:

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, и смотрим что нам нужно сделать:

  1. Зарегистрировать аккаунт
  2. Получить API Key
  3. Использовать нужный URL - нас интересует goerly
  4. Идем в аккаунт и смотрим как запросить список транзакций.
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.

Совет: начинающим в блокчейне стоит идти от простого к сложному и не торопиться — подобно любой другой области, вы будете приобретать знания и навыки по мере своего развития. Если у вас уже давно зрело желание начать свой путь в мире блокчейна, но страхи останавливали вас, я надеюсь, что эта статья помогла вам снизить их.
Автор статьи — Данил Замешаев
Софья Пирогова

Софья Пирогова

Главный редактор / Автор статей