Обучение
July 17, 2022

Как создать DeFi приложение "Купи мне кофе", вторая неделя

Ну что, не будем долго затягивать и преступим ко второму челленджу: созданию DeFi аппки Buy Me A Coffee, скажу в начале лишь что технология блокчейн удивительна, потому что она дает нам возможность программировать деньги с помощью кода и программного обеспечения. С помощью нескольких строк кода можно создавать всевозможные приложения и протоколы, которые могут создать новые возможности для людей по всему миру.

Buy Me A Coffee - это популярный веб-сайт, который создатели, артисты и другие люди используют для создания целевой страницы, на которую любой желающий может отправить некоторую сумму денег в качестве благодарности за свои услуги. Однако для того, чтобы воспользоваться им, у вас должен быть банковский счет и кредитная карта. Не у всех это есть!

Преимущество децентрализованных приложений, построенных на основе блокчейна, заключается в том, что любой человек со всего мира может получить доступ к приложению, используя только кошелек Ethereum, который любой может настроить бесплатно менее чем за 1 минуту. Давайте посмотрим, как мы можем использовать это в наших интересах!

В этом уроке вы узнаете, как разработать и внедрить децентрализованный смарт-контракт "Купи мне кофе", который позволяет посетителям отправлять вам (тестовые) ETH в качестве чаевых и оставлять приятные сообщения, используя Alchemy, Hardhat, Ethers.js , и Ethereum Goerli.

К концу этого урока вы научитесь

  • Используйте среду разработки Hardhat для создания, тестирования и развертывания нашего смарт-контракта.
  • Подключите свой кошелек MetaMask к тестовой сети Goerli с помощью конечной точки Alchemy rpc.
  • Получите бесплатный ETH Goerli от goerlifaucet.com .
  • Использование Ethers.js для взаимодействия с вашим развернутым смарт-контрактом.
  • Создайте веб-сайт с интерфейсом для вашего децентрализованного приложения с помощью Replit.

Версия видеоурока здесь:

Необходимые навыки и компоненты

Чтобы подготовиться к остальной части этого урока, вам необходимо иметь:

  • npm (npx) версии 8.5.5
  • node версии 16.13.1

Следующее не обязательно, но чрезвычайно полезно:

Теперь давайте начнем создавать наш смарт-контракт!

Кодим смарт-контракт BuyMeACoffee.sol

Ссылка на Github: https://github.com/alchemyplatform/RTW3-Week2-BuyMeACoffee-Contracts

Если вы раньше использовали такие инструменты, как Open Zeppelin Wizard и Remix, то вы уже готовы к использованию Hardhat.

Hardhat похож, потому что это среда разработки и инструмент программирования, но он немного более настраиваемый и запускается из интерфейса командной строки вашего собственного компьютера, а не из приложения браузера.

Мы будем использовать Hardhat, чтобы:

  • Создать шаблон проекта
  • Протестировать наш код смарт-контракта
  • Развернуть его в тестовой сети Goerli

Откройте свой терминал и создайте новый каталог. Поехали!

mkdir BuyMeACoffee-contracts
cd BuyMeACoffee-contracts

Внутри этого каталога мы хотим запустить новый проект npm (настройки по умолчанию вполне подойдут):

npm init -y

Эта команда должна создать файл package.json, который выглядит следующим образом:

~/BuyMeACoffee-contracts > npm init -y

{
  "name": "buymeacoffee-contracts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Теперь мы создаем стандартный проект для примера:

npx hardhat
Устанавливаем Hardhat

Нажмите Enter чтобы принять все настройки по умолчанию и установить зависимости проекта (hardhat, @nomiclabs/hardhat-waffle, ethereum-waffle, chai, @nomiclabs/hardhat-ethers, ethers).

В случае, если что-то пойдет не так с установкой зависимостей, попробуйте переустановить, выполнив эту команду:

npm install --save-dev hardhat@^2.9.3 @nomiclabs/hardhat-waffle@^2.0.0 ethereum-waffle@^3.0.0 chai@^4.2.0 @nomiclabs/hardhat-ethers@^2.0.0 ethers@^5.0.0

Теперь ваш каталог проекта должен выглядеть примерно так (я использую дерево для визуализации):

~/BuyMeACoffee-contracts > tree -C -L 1
.
├── README.md
├── contracts
├── hardhat.config.js
├── node_modules
├── package-lock.json
├── package.json
├── scripts
└── test

Тут важны следующие файлы и папки:

  • contracts - папка содержащая ваши смарт-контракты
    • в этом проекте мы создадим только один, чтобы реализовать нашу логику BuyMeACoffee
  • scripts - папка, в которой хранятся ваши скрипты hardhat javscript
    • мы напишем deploy логику
    • пример скрипта buy-coffee
    • и withdraw скрипт для обналичивания наших чаевых
  • hardhat.config.js - конфигурационный файл с настройками для версии solidity и развертывания

Теперь используйте любой редактор кода, чтобы открыть папку проекта! Как вы заметили я использую Visual Studio Code.

Переименовываем контракт Lock.sol в BuyMeACoffee.sol

Вы можете заметить ряд файлов, автоматически сгенерированных с помощью инструмента Hardhat JavaScript project. Мы заменим их все, начиная с контракта Lock.sol.

  1. Переименуйте файл контракта в BuyMeACoffee.sol
  2. Замените дефолтный код контракта на следующий:
//SPDX-License-Identifier: Unlicense

// contracts/BuyMeACoffee.sol
pragma solidity ^0.8.0;

// Switch this to your own contract address once deployed, for bookkeeping!
// Example Contract Address on Goerli: 0xDBa03676a2fBb6711CB652beF5B7416A53c1421D

contract BuyMeACoffee {
    // Event to emit when a Memo is created.
    event NewMemo(
        address indexed from,
        uint256 timestamp,
        string name,
        string message
    );
    
    // Memo struct.
    struct Memo {
        address from;
        uint256 timestamp;
        string name;
        string message;
    }
    
    // Address of contract deployer. Marked payable so that
    // we can withdraw to this address later.
    address payable owner;

    // List of all memos received from coffee purchases.
    Memo[] memos;

    constructor() {
        // Store the address of the deployer as a payable address.
        // When we withdraw funds, we'll withdraw here.
        owner = payable(msg.sender);
    }

    /**
     * @dev fetches all stored memos
     */
    function getMemos() public view returns (Memo[] memory) {
        return memos;
    }

    /**
     * @dev buy a coffee for owner (sends an ETH tip and leaves a memo)
     * @param _name name of the coffee purchaser
     * @param _message a nice message from the purchaser
     */
    function buyCoffee(string memory _name, string memory _message) public payable {
        // Must accept more than 0 ETH for a coffee.
        require(msg.value > 0, "can't buy coffee for free!");

        // Add the memo to storage!
        memos.push(Memo(
            msg.sender,
            block.timestamp,
            _name,
            _message
        ));

        // Emit a NewMemo event with details about the memo.
        emit NewMemo(
            msg.sender,
            block.timestamp,
            _name,
            _message
        );
    }

    /**
     * @dev send the entire balance stored in this contract to the owner
     */
    function withdrawTips() public {
        require(owner.send(address(this).balance));
    }
}

Потратьте некоторое время, чтобы прочитать комментарии к контракту и посмотреть, сможете ли вы понять, что происходит!

Перечислю здесь основные моменты:

  • Когда мы развертываем контракт, constructor сохраняет адрес кошелька, который был ответственен за развертывание, внутри переменной owner в качестве payable адреса. Это полезно для последующего использования, когда мы захотим отозвать любые чаевые, собранные по контракту.
  • Функция buyCoffee - самая важная функция в контракте. Она принимает две строки: _name, и _message, а также принимает эфир из-за модификатора payable. Она также использует входные данные _name, и _message для создания структуры Memo, которая хранится в блокчейне.
    • Когда пользователи вызывают функциюbuyCoffee, они должны отправить какой-либ запрос из-за инструкции require(msg.value > 0). Затем эфир удерживается на балансе контракта до тех пор, пока он не будет снят.
  • Массив memos содержит все структуры Memo, созданные в результате покупок кофе.
  • NewMemo журнала заметок генерируются каждый раз при покупке кофе. Это позволяет нам отслеживать новые покупки кофе с нашего веб-сайта.
  • withdrawTips - это функция, которую может вызвать любой желающий, но она будет отправлять деньги только первоначальному деплойеру контракта.
    • address(this).balance получает эфир, хранящийся в контракте
    • owner.send(...) это синтаксис для создания транзакции отправки с помощью ether
    • require(...) стейтмент, который оборачивает все, существует для того, чтобы гарантировать, что в случае возникновения каких-либо проблем транзакция будет отменена и ничего не будет потеряно
    • вот что мы получаем require(owner.send(address(this).balance))

Вооружившись этим кодом смарт-контракта, мы можем написать скрипт для проверки нашей логики!

Создаем buy-coffee.js скрипт для теста нашего контракта

В папке scripts должен быть образец скрипта deploy.js. Давайте переименуем этот файл в buy-coffee.js и вставим следующий код:

// scripts/buy-coffee.js

const hre = require("hardhat");

// Returns the Ether balance of a given address.
async function getBalance(address) {
  const balanceBigInt = await hre.waffle.provider.getBalance(address);
  return hre.ethers.utils.formatEther(balanceBigInt);
}

// Logs the Ether balances for a list of addresses.
async function printBalances(addresses) {
  let idx = 0;
  for (const address of addresses) {
    console.log(`Address ${idx} balance: `, await getBalance(address));
    idx ++;
  }
}

// Logs the memos stored on-chain from coffee purchases.
async function printMemos(memos) {
  for (const memo of memos) {
    const timestamp = memo.timestamp;
    const tipper = memo.name;
    const tipperAddress = memo.from;
    const message = memo.message;
    console.log(`At ${timestamp}, ${tipper} (${tipperAddress}) said: "${message}"`);
  }
}

async function main() {
  // Get the example accounts we'll be working with.
  const [owner, tipper, tipper2, tipper3] = await hre.ethers.getSigners();

  // We get the contract to deploy.
  const BuyMeACoffee = await hre.ethers.getContractFactory("BuyMeACoffee");
  const buyMeACoffee = await BuyMeACoffee.deploy();

  // Deploy the contract.
  await buyMeACoffee.deployed();
  console.log("BuyMeACoffee deployed to:", buyMeACoffee.address);

  // Check balances before the coffee purchase.
  const addresses = [owner.address, tipper.address, buyMeACoffee.address];
  console.log("== start ==");
  await printBalances(addresses);

  // Buy the owner a few coffees.
  const tip = {value: hre.ethers.utils.parseEther("1")};
  await buyMeACoffee.connect(tipper).buyCoffee("Carolina", "You're the best!", tip);
  await buyMeACoffee.connect(tipper2).buyCoffee("Vitto", "Amazing teacher", tip);
  await buyMeACoffee.connect(tipper3).buyCoffee("Kay", "I love my Proof of Knowledge", tip);

  // Check balances after the coffee purchase.
  console.log("== bought coffee ==");
  await printBalances(addresses);

  // Withdraw.
  await buyMeACoffee.connect(owner).withdrawTips();

  // Check balances after withdrawal.
  console.log("== withdrawTips ==");
  await printBalances(addresses);

  // Check out the memos.
  console.log("== memos ==");
  const memos = await buyMeACoffee.getMemos();
  printMemos(memos);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

На этом этапе каталог нашего проекта должен выглядеть примерно так:

Переименовываем deploy.js в buy-coffee.js и заменяем код

Не стесняйтесь потратить несколько минут на чтение кода скрипта. Вверху есть некоторые служебные функции, определенные для удобства выполнения таких действий, как получение остатков на кошельке и их отображения.

Основная логика скрипта находится внутри функции main(). Закоментированный код показывает ход выполнения скрипта:

  1. Получаем примеры учетных записей, с которыми мы будем работать.
  2. Получаем контракт на развертывание.
  3. Развертываем контракт.
  4. Проверяем баланс перед покупкой кофе.
  5. Покупаем хозяину несколько чашек кофе.
  6. Проверяем баланс после покупки кофе.
  7. Выводим.
  8. Проверяем баланс после вывода.
  9. Проверяем заметки.

Этот скрипт тестирует все функции, которые мы реализовали в нашем смарт-контракте!

Вы также можете заметить, что мы делаем интересные вызовы, такие как:

hre.waffle.provider.getBalance

hre.ethers.getContractFactory

hre.ethers.utils.parseEther

и т.д.

В этих строках кода мы используем преимущества среды разработки Hardhat (hre) вместе с плагинами Ethers и Waffle SDK для доступа к функциям, которые позволяют считывать остатки на счетах блокчейн-кошельков, развертывать контракты и форматировать значения криптовалюты Ether.

Мы не будем слишком подробно останавливаться на этом коде в этом руководстве, но вы можете узнать о них больше, просмотрев документацию Hardhat и Ethers.js .

Хватит разговоров. А теперь для развлечения давайте запустим сценарий:

npx hardhat run scripts/buy-coffee.js

Вы должны увидеть вывод в своем терминале что-то вроде этого:

~/roadtoweb3/BuyMeACoffee-contracts ❯ npx hardhat run scripts/buy-coffee.js                                                              9m 7s 02:56:16

BuyMeACoffee deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
== start ==
Address 0 balance:  9999.998754619375
Address 1 balance:  10000.0
Address 2 balance:  0.0
== bought coffee ==
Address 0 balance:  9999.998754619375
Address 1 balance:  9998.999752893990255063
Address 2 balance:  3.0
== withdrawTips ==
Address 0 balance:  10002.998708719732606388
Address 1 balance:  9998.999752893990255063
Address 2 balance:  0.0
== memos ==
At 1657991838, Carolina (0x70997970C51812dc3A010C7d01b50e0d17dc79C8) said: "You're the best!"
At 1657991839, Vitto (0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC) said: "Amazing teacher"
At 1657991840, Kay (0x90F79bf6EB2c4f870365E785982E1f101E93b906) said: "I love my Proof of Knowledge"

В начале скрипта (сразу после развертывания контракта) обратите внимание, что адрес 0 имеет 9999.99877086625 ETH. Это связано с тем, что он начинался с 10k ETH в качестве одного из предварительно заполненных адресов hardhat, но для развертывания в локальном блокчейне пришлось потратить небольшую сумму.

На втором шаге == bought coffee == Address 1 покупает один кофе. Два других кошелька, которые не показаны, ТАКЖЕ покупают кофе. В общей сложности было куплено 3 кофе на общую сумму чаевых 3.0 ETH. Вы можете видеть, что Address 2 (который представляет адрес контракта) удерживает 3.0 ETH.

После вызова функции withdrawTips() в == withdrawTips == контракт возвращается к 0 ETH, а исходный деплойер, он же Address 0, теперь заработал немного денег и содержит 10002.998724967892122376 ETH.

Ну как, весело?!?! Можете ли вы представить себе чаевые, которые вы сможете заработать?? Я могу.

Теперь давайте внедрим сценарий изолированного развертывания, чтобы упростить реальное развертывание, а также подготовимся к развертыванию в тестовой сети Goerli!

Развертываем свой BuyMeACoffe.sol смарт-контракт в сети Ethereum Goerly с Alchemy и MetaMack

Let's create a new file scripts/deploy.js that will be super simple, just for deploying our contract to any network we choose later (we'll choose Goerli later if you haven't noticed).

The deploy.js file should look like this:

Давайте создадим новый файл scripts/deploy.js, для развертывания нашего контракта в любой сети, которую мы выберем (мы выберем Goerli, если вы не заметили).

В deploy.js файл должен выглядеть следующим образом:

// scripts/deploy.js

const hre = require("hardhat");

async function main() {
  // We get the contract to deploy.
  const BuyMeACoffee = await hre.ethers.getContractFactory("BuyMeACoffee");
  const buyMeACoffee = await BuyMeACoffee.deploy();

  await buyMeACoffee.deployed();

  console.log("BuyMeACoffee deployed to:", buyMeACoffee.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Чтобы пересмотреть структуру проекта, теперь у нас есть один смарт-контракт и два скрипта hardhat:

Создаем скрипт deploy.js в директории /scripts

Теперь, когда скрипт deploy.js написан и сохранен, если вы выполните следующую команду в терминале:

npx hardhat run scripts/deploy.js

Вы увидите вывод одной единственной строки:

BuyMeACoffee deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

Что интересно, так это то, что если вы будете запускать скрипт снова и снова, вы будете видеть один и тот же точный адрес развертывания каждый раз:

Почему так? Это связано с тем, что при запуске скрипта сеть по умолчанию, которую использует Hardhat - это локальная сеть разработки, расположенная прямо на вашем компьютере. Это быстро и детерминировано, и отлично подходит для быстрой проверки на работоспособность.

Однако для развертывания в тестовой сети, которая работает через Интернет с узлами по всему миру, нам нужно изменить наш конфигурационный файл Hardhat, чтобы предоставить себе такую возможность.

Здесь мы познакомимся с файлом hardhat.config.json.

Небольшое предостережение, прежде чем мы погрузимся тему:

КОНФИГУРАЦИЯ - ЭТО СЛОЖНО! ХРАНИТЕ СВОИ СЕКРЕТЫ В БЕЗОПАСНОСТИ!

Есть множество мелких деталей, которые могут пойти не так, и все постоянно меняется. Самая опасная вещь - это секретные значения, например, ваш закрытый ключ Netmask и ваш URL-адрес Alchemy.

Если у вас что-то не работает, попробуйте найти ответ в Ethereum StackExchange, Alchemy Discord или найдите свои ошибки в Google.

И никогда не делитесь своими секретами! Ваши ключи, ваши монеты!

Когда вы откроете свой hardhat.config.js файл, вы увидите некоторый стандартный код конфига. Удалите его и вставьте следующий код:

// hardhat.config.js

require("@nomiclabs/hardhat-ethers");
require("@nomiclabs/hardhat-waffle");
require("dotenv").config()

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

const GOERLI_URL = process.env.GOERLI_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.4",
  networks: {
    goerli: {
      url: GOERLI_URL,
      accounts: [PRIVATE_KEY]
    }
  }
};

Рассмотрим подробнее что тут происходит:

  • Импортируя hardhat-ethers, hardhat-waffle и dotenv в верхней части файла конфигурации, весь наш проект Hardhat получит доступ к этим зависимостям.
  • Я знаю, что мы еще не внедрили dotenv, это важный инструмент, о котором мы немного поговорим.
  • process.env.GOERLI_URL и process.env.PRIVATE_KEY - это то, как мы можем получить доступ к переменным среды для использования в вашем конфигурационном файле, не раскрывая секретные значения.
  • Inside the modules.exports, we are using solidity compiler version 0.8.4. Different compiler versions support different features and syntax sets, so it's important to match this version with the pragma declaration at the top of our BuyMeACoffee.sol smart contract.
  • Внутри modules.exports мы используем компилятор solidity версии 0.8.9. Разные версии компилятора поддерживают разные функции и наборы синтаксиса, поэтому важно сопоставить эту версию с объявлением pragma в верхней части нашего BuyMeACoffee.solсмарт-контракта.
    • Если вы вернетесь к этому файлу, вы можете перепроверить инструкцию pragma solidity ^0.8.0;. В этом случае, даже если цифры не совпадают точно, это нормально, потому что символ karat ^ означает, что будет работать любая версия, которая больше или равна 0.8.0.
  • Также в файле modules.exports мы определяем настройку сетей, которая содержит одну конфигурацию тестовой сети для goerli.

Теперь, прежде чем мы сможем приступить к развертыванию, нам нужно убедиться, что у нас установлен последний инструмент - модуль dotenv. Как следует из названия, dotenv помогает нам подключить файл .env к остальной части нашего проекта. Давайте займемся этим.

Установим dotenv:

npm install dotenv

Создадим .env файл в корне нашего проекта:

touch .env

Заполните файл .env переменными, которые нам нужны:

GOERLI_URL=https://eth-goerli.alchemyapi.io/v2/<your api key>
GOERLI_API_KEY=<your api key>
PRIVATE_KEY=<your metamask api key>

Вы заметите, что я не выдал ни одного из своих собственных секретов. Ага. Безопасность превыше всего. Однако вы вполне можете поместить этот файл, если у вас также есть .gitignore, который гарантирует, что вы случайно не передадите файл в систему управления версиями. Убедитесь, что файл .envуказан в вашем файле .gitignore

node_modules
.env
coverage
coverage.json
typechain

#Hardhat files
cache
artifacts

Кроме того, чтобы получить то, что нам нужно для переменных среды, вы можете использовать следующие ресурсы:

  • GOERLI_URL - зарегистрируйте учетную запись на Alchemy, создайте приложение Ethereum -> Goerli и используйте HTTP-URL
  • GOERLI_API_KEY - из того же приложения Alchemy Ethereum Gerli вы можете получить последнюю часть URL-адреса, и это будет ваш API KEY
  • PRIVATE_KEY - следуйте этим инструкциям от MetaMask, чтобы экспортировать свой закрытый ключ.

Теперь, когда dotenv установлен и ваш файл .env заполнен, мы ПОЧТИ готовы к развертыванию в Goerli testnet!

Последнее, что нам нужно сделать, это убедиться, что у вас есть немного Goerli ETH. Это тестовый ETH, который позволяет вам практиковаться в выполнении действий в тестовой сети Goerli, которая является своего рода тренировочной зоной для создания приложений Ethereum. Таким образом, вам не придется тратить реальные деньги на основную сеть Ethereum.

Перейти к https://www.goerlifaucet.com и войдите в свою учетную запись Alchemy, чтобы получить бесплатный тестовый эфир.

Теперь мы можем приступать к развертыванию!

Запустите сценарий развертывания, на этот раз добавив специальный флаг для использования сети Goerli:

npx hardhat run scripts/deploy.js --network goerli

Если вы столкнетесь здесь с какими-либо ошибками, например с Error HH8, то я настоятельно рекомендую поискать решения в Google и StackOverflow или Ethereum Stackexchange. Часто приходится сталкиваться с такими проблемами, когда что-то в вашем hardhat.config.js, .env, или ваш модуль dotenv настроен неправильно.

Если все пойдет хорошо, через несколько секунд вы сможете увидеть свой адрес контракта, зарегистрированный в консоли:

~/roadtoweb3/BuyMeACoffee-contracts ❯ npx hardhat run scripts/deploy.js --network goerli                                                   43s 13:50:42

BuyMeACoffee deployed to: 0xa0a094FeF68Eb89dB349825413E485D9B16AB9cd

Поздравляю! 🎉 Теперь у тебя есть контракт, развернутый в тестовой сети Goerli. Вы можете просмотреть его в проводнике блокчейна Goerli etherscan, вставив свой адрес здесь: https://goerli.etherscan.io/

Смотрим на свой контракт в Etherscan

Прежде чем мы перейдем к части руководства по интерфейсу веб-сайтау (dapp), давайте подготовим еще один скрипт, который мы захотим использовать позже - withdraw.js.

Пишем скрипт вывода средств

Позже, когда мы опубликуем наш веб-сайт, нам понадобится способ собрать все коментарии, которые оставляют нам наши друзья и поклонники. Мы можем написать еще один сценарий hardhat, чтобы сделать именно это!

Создаем скрипт scripts/withdraw.js

// scripts/withdraw.js

const hre = require("hardhat");
const abi = require("../artifacts/contracts/BuyMeACoffee.sol/BuyMeACoffee.json");

async function getBalance(provider, address) {
  const balanceBigInt = await provider.getBalance(address);
  return hre.ethers.utils.formatEther(balanceBigInt);
}

async function main() {
  // Get the contract that has been deployed to Goerli.
  const contractAddress="0xDBa03676a2fBb6711CB652beF5B7416A53c1421D";
  const contractABI = abi.abi;

  // Get the node connection and wallet connection.
  const provider = new hre.ethers.providers.AlchemyProvider("goerli", process.env.GOERLI_API_KEY);

  // Ensure that signer is the SAME address as the original contract deployer,
  // or else this script will fail with an error.
  const signer = new hre.ethers.Wallet(process.env.PRIVATE_KEY, provider);

  // Instantiate connected contract.
  const buyMeACoffee = new hre.ethers.Contract(contractAddress, contractABI, signer);

  // Check starting balances.
  console.log("current balance of owner: ", await getBalance(provider, signer.address), "ETH");
  const contractBalance = await getBalance(provider, buyMeACoffee.address);
  console.log("current balance of contract: ", await getBalance(provider, buyMeACoffee.address), "ETH");

  // Withdraw funds if there are funds to withdraw.
  if (contractBalance !== "0.0") {
    console.log("withdrawing funds..")
    const withdrawTxn = await buyMeACoffee.withdrawTips();
    await withdrawTxn.wait();
  } else {
    console.log("no funds to withdraw!");
  }

  // Check ending balance.
  console.log("current balance of owner: ", await getBalance(provider, signer.address), "ETH");
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Структура нашего проекта на данном этапе должна выглядеть следующим образом:

Создаем скрипт withdraw.js в директории /scripts

Сейчас у нас есть 1 смарт-контракт и 3 скрипта hardhat.

Самая важная часть этого скрипта - это когда мы вызываем функцию withdrawTips(), чтобы снять деньги с баланса нашего контракта и отправить их на кошелек владельца:

  // Withdraw funds if there are funds to withdraw.
  if (contractBalance !== "0.0") {
    console.log("withdrawing funds..")
    const withdrawTxn = await buyMeACoffee.withdrawTips();
    await withdrawTxn.wait();
  }

Если в контракте нет средств, мы избегаем попыток вывести средства, чтобы не тратить плату за газ без необходимости.

Когда вы запустите скрипт, вы увидите вывод, подобный этому:

~/roadtoweb3/BuyMeACoffee-contracts ❯ npx hardhat run scripts/withdraw.js                                                                  10s 19:21:05

current balance of owner:  0.048750102785301158 ETH
current balance of contract:  0.0 ETH
no funds to withdraw!
current balance of owner:  0.048750102785301158 ETH

Обратите внимание, что на этот раз мы не добавили флаг --network goerli, и это потому, что наш скрипт жестко кодирует конфигурацию сети непосредственно внутри логики:

const provider = new hre.ethers.providers.AlchemyProvider(
    "goerli",
    process.env.GOERLI_API_KEY
);

Отлично, теперь у нас есть способ получить чаевые из контракта! Давайте перейдем к визуальной части dapp этого проекта, чтобы мы могли поделиться нашей страницей чаевых со всеми нашими друзьями :)

Создаем интерфейс dApp BuyMeACoffee с Replit и Ethers.js

Для фронтенд части веб-сайта, чтобы все было просто и понятно, мы собираемся использовать удивительный инструмент для быстрого запуска демонстрационных проектов, называемый Replit IDE.

Посетите пример проекта из официальной документации здесь и форкните его, чтобы создать свою собственную копию для изменения: https://replit.com/@thatguyintech/BuyMeACoffee-Solidity-DeFi-Tipping-app

Создаем форк интерфейса от Replit

Вы также можете просмотреть полный код веб-сайта здесь: https://github.com/alchemyplatform/RTW3-Week2-BuyMeACoffee-Website

После форка Repl'а вы должны перейти на страницу IDE, где вы можете:

  • Смотрите код Next.js веб-приложения
  • Получите доступ к консоли, оболочке терминала и предварительному просмотру README.md файла
  • Просмотр версии вашего приложения с горячей перезагрузкой

Это должно выглядеть примерно так:

Открываем наш форкнутый Repl

Эта часть урока будет быстрой и увлекательной - мы собираемся обновить пару переменных, чтобы они были подключены к смарт-контракту, который мы развернули в предыдущих частях проекта, и чтобы он отображал ваше собственное имя на веб-сайте!

Давайте сначала все подключим и запустим, а потом я объясню вам, что происходит в каждой части.

Вот изменения, которые нам нужно внести:

  1. Обновите contractAddress в /index.js
  2. Обновите строку имени на ваше в pages/index.js
  3. Убедитесь, что ABI контракта соответствует вашему контракту в utils/BuyMeACoffee.json

Обновляем contractAddress в pages/index.js

Вы можете видеть, что переменная contractAddress уже заполнена адресом. Это пример контракта, который я развернул, который вы можете использовать, но если вы это сделаете ... все деньги, отправленные на ваш сайт, будут отправлены на мой адрес :)

Вы можете исправить это, вставив свой адрес, указанный при развертывании приложения BuyMeACoffee.sol ранее.

Изменяем переменную contractAddress, чтобы она указывала на ваш BuyMeACoffee.sol контракт в сети Goerly

Обновляем имя, на то которое вы хотите в pages/index.js

Прямо сейчас на сайте повсюду написано имя Albert. Найдите все места, где используется Albert , и замените его своим именем / никнеймом / ENS доменом или как вы хотели бы, чтобы люди называли вас.

Нажмите cmd + F или ctrl + F чтобы найти строкуAlbert для замены.

Измените текст имени на свое собственное имя!

Убеждаемся, что ABI совпадает с utils/BuyMeACoffee.json

Это также важно проверить, особенно когда вы будите вносить изменения в свой смарт-контракт позже (после этого урока).

ABI - это двоичный интерфейс приложения, который представляет собой просто причудливый способ сообщить нашему коду интерфейса, какие функции доступны для вызова в смарт-контракте. ABI генерируется внутри файла json при компиляции смарт-контракта. Вы можете найти его в папке смарт-контракта по пути:

artifacts/contracts/BuyMeACoffee.sol/BuyMeACoffee.json

Всякий раз, когда вы меняете код своего смарт-контракта и повторно развертываете его, ваш API также меняется. Скопируйте это и вставьте в файл Replit: utils/BuyMeACoffee.json

Копируем ABI в utils/BuyMeACoffee.json

Теперь, если приложение еще не запущено, вы можете перейти в командную оболочку и использовать npm run dev для запуска локального сервера для проверки внесенных изменений. Веб-сайт должен загрузиться через несколько секунд:

Запускам локальный сервер командой npm run dev

Самое замечательное в Replit то, что после того, как вы запустили веб-сайт, вы можете вернуться в свой профиль, найти ссылку на проект Replit и отправить ее друзьям, чтобы они посетили вашу страницу чаевых.

Теперь давайте совершим экскурсию по веб-сайту и коду. Вы уже можете видеть на скриншоте выше, что при первом посещении приложения оно проверит, установлен ли у вас MetaMask и подключен ли ваш кошелек к сайту. При первом посещении вы не будете подключены, поэтому появится кнопка с просьбой подключить ваш кошелек Connect your wallet.

После того, как вы нажмете Connect your wallet, появится окно MetaMask, в котором вас спросят, хотите ли вы подтвердить подключение, подписав сообщение. Подписание этого сообщения не требует каких-либо сборов или затрат за газ.

Once the signature is complete, the website will acknowledge your connection and you will be able to see the coffee form, as well as any of the previous memos left behind by other visitors.

Как только сообщение будет подписано, веб-сайт подтвердит ваше подключение, и вы сможете увидеть форму для кофе, а также любые предыдущие заметки, оставленные другими посетителями.

Подключаем кошелек и пробуем задонатить самому себе!

Ухх! Вот и все! Вот и весь проект. Потратьте секунду, чтобы похлопать себя по спине и поразмыслить о путешествии, в котором вы только что побывали!

Подведем итог

  • Мы использовали Hardhat и Ethers.js для кодинга, тестирования и развертывания пользовательского смарт-контракта solidity.
  • Мы развернули смарт-контракт в общей тестовой сети, используя Alchemy и Metal Mask.
  • Мы внедрили скрипт вывода средств, чтобы позволить нам принять плоды нашего труда.
  • Мы подключили интерфейс веб-сайта, созданный с помощью Next.js, чтобы он мог реагировать и взаимодействовать со смарт-контрактом с помощью Ethers.js для загрузки контракт ABI.

И это уже ОЧЕНЬ МНОГО!

​Домашнее задание

Ладно, теперь время для самой лучшей части. Я оставлю вам несколько задач, которые вы можете попробовать самостоятельно, чтобы убедиться, что вы полностью понимаете то, чему здесь научились! (Для получения некоторых рекомендаций посмотрите видео на YouTube здесь).

  • Разрешите вашему смарт-контракту обновлять адрес вывода средств.
  • Разрешите вашему смарт-контракту покупать большой кофе за 0,003 ETH и создайте кнопку на интерфейсе веб-сайта, которая показывает кнопку "Buy Large Coffee for 0.003ETH".
  • Как только вы закончите со своим заданием, напишите об этом в Твиттере, отметив @AlchemyPlatform в Twitter и используя хэштег #roadtoweb3!

Заполняем форму

Чтобы получить наш заветный еженедельный PoK (Proof of Knowledge), заполните эту форму, включая адрес развернутого смарт-контракта: https://alchemyapi.typeform.com/roadtoweektwo

Примерно через неделю вам отправят PoK, забираем на https://mintkudos.xyz/, вот так он выглядит!

PoK - Alchemy Road to Web3 - Week Two
Этот непередаваемый NFT доказывает, что вы официально завершили вторую неделю курса Alchemy Road to Web3 Bootcamp. Вы успешно написали, спроектировали и развернули децентрализованное приложение с использованием: Solidity, Hardhat, Ethers.js, Alchemy. Поздравляем!

Ну вот и вторая неделя нашей поездки закончена, подписывайтесть на обновления чтобы не пропустить гайд для следующей недели и залетайте в чатик если есть вопросы !👇 Увидимся ❤️

Третий слой | Telegram| Chat | Twitter