Смарт-контракты
December 19, 2023

Смарт-контракт для airdrop events.

Скажем некое сообщество X планирует сделать раздачу eth для своих подписчиков. Чтобы каждый из них в течении месяца после определенных дат смог забрать свой дроп равный сумме, которую владелец паблика внесет на депозит (смарт-контракта) xBank, поделенное на количество участников события.

// SPDX-License-Identifier: MIT  
pragma solidity >=0.8.17;
contract AirdropEvent {
    uint8 public totalEligible; /* всего eligible участников (переменная 
    uint8 говорит о том, что возможно до 255 человек*/
    uint256 public xBank; /* внесенная общая сумма средств (владельцем), доступная 
    для распределения (может отличаться от текущего баланса контракта)*/
    address public owner; // адрес владельца контракта (владельца сообщества тг)
Cтруктура для хранения данных о каждом участнике, включая их username в telegram, дату и время разблокировки eth и информацию о том, получили ли они уже средства.
    struct EligibleMember {
        string userNameTelegram;
        uint256 unlocking;
        bool alreadyGotMoney;
        bool exist;
    }
    address[] public arrEligibleMembers; /* массив для хранения адресов участников, 
    имеющих право на получение токенов.*/
    mapping(address => EligibleMember) public eligibleMembers; 
    /* использует для хранения информации об участниках, 
    имеющих право на получение токенов, на основе их адреса кошелька.*/
    constructor(){
        owner = msg.sender;
        totalEligible = 0;
    }
    function addEligibleMember( /* функция используется владельцем контракта 
    для добавления участников, имеющих право на получение дропа.*/
        address walletAddress, // адрес кошелька участника
        string memory userNameTelegram, // username в телеграме
        uint256 unlocking // дата и время разблокировки в Unix Timestamp https://www.unixtimestamp.com/
    ) public onlyOwner {
        require(unlocking > 0, "The unlock date has not arrived");
        require( /* проверка что дата разблокировки больше нуля 
        и что пользователя еще нет в списке участников. 
        Если обе проверки пройдены успешно, участник добавляется в 
        список и общее количество увеличивается*/
            eligibleMembers[walletAddress].exist == false,
            "Error! There is such a user"
        );
        eligibleMembers[walletAddress] = (
            EligibleMember(userNameTelegram, unlocking, false, true)
        );
        arrEligibleMembers.push(walletAddress);
        totalEligible++;
        emit NewEligibleMember(walletAddress, userNameTelegram, unlocking);
    } /*генерируется событие "NewEligibleMember"о добавлении нового участника. 
    Модификатор "onlyOwner" гарантирует, что только владелец контракта 
    может вызвать эту функцию. */
Функция для чтения определенного диапазона участников из массива.
       function readEligibleMembersArray(uint cursor, uint length) public view returns (address[] memory) { 
        address[] memory array = new address[](length);
        uint totalEligible2 = 0;
        for (uint i = cursor; i < cursor+length; i++) {
            array[totalEligible2] = arrEligibleMembers[i];
            totalEligible2++;
        }
        return array;
    }
Функция claim эфира для участников, имеющих право на получение токенов после разблокировки.
  function claim() public { 
        address payable walletAddress = payable(msg.sender);
        require(
            eligibleMembers[walletAddress].exist == true,
            "No such participant!"
        ); /* Проверка - существует ли участник с данным кошельком. 
        Если параметр exist (существует) равен false, 
        то будет выведено сообщение "Нет такого участника!" 
        и выполнение функции будет остановлено.*/
        require(
            block.timestamp > eligibleMembers[walletAddress].unlocking,
            "Unlocking hasn't happened yet!"
        ); /* Проверяет произошло ли уже разблокирование для данного участника. 
        Если текущее время (block.timestamp) не наступило, то будет выведено сообщение 
        "Разблокирование ещё не произошло!" и выполнение функции будет остановлено.*/
        require(
            eligibleMembers[walletAddress].alreadyGotMoney == false,
            "You have already received eth!"
        ); /* Получил ли участник уже эфир. Если параметр 
        alreadyGotMoney (уже получил) для участника с заданным кошельком 
        равен true, то будет выведено сообщение "Вы уже получили эфир!" 
        и выполнение функции будет остановлено.
        
        Таким образом, эти три проверки обеспечивают, что участник имеет право 
        получить свой дроп, иначе функция вызовет ошибку 
        и завершит своё выполнение.*/
        uint256 amount = xBank / totalEligible;
        eligibleMembers[walletAddress].alreadyGotMoney = true;
        (bool success, ) = walletAddress.call{value: amount}("");
        require(success);
        emit GotMoney(walletAddress);
    }
Проверка текущего баланса контракта.
function currentBalance() public view returns (uint256) {
        return address(this).balance; 
    }
    modifier onlyOwner() { /* модификатор для ограничения доступа к определенным функциям 
    (только для владельца контракта)*/
        require(msg.sender == owner, "You are not the owner");
        _;
    }
    receive() external payable { /*"запасная" функция для приема и сохранения поступающего эфира в xBank 
    для последующего распределения.*/
        xBank += msg.value;
    }
    //события
    event NewEligibleMember(address indexed walletAddress, string userNameTelegram, uint256 unlocking);
    event GotMoney(address indexed walletAddress);
    }
Конструкция event в Solidity используется для объявления событий, которые могут быть инициированы в ходе выполнения функций в смарт-контракте.

1. event NewEligibleMember(address indexed walletAddress, string userNameTelegram, uint256 unlocking);
* Это событие NewEligibleMember инициируется при добавлении нового участника в список.
* Он принимает три параметра: адрес кошелька, имя пользователя в Telegram и дату разблокировки.
* Ключевое слово indexed используется для обеспечения возможности эффективного поиска и фильтрации событий по указанному параметру.

2. event GotMoney(address indexed walletAddress);
* Это событие GotMoney, которое будет вызвано в случае, если участник получит деньги.
* Он принимает один параметр - адрес кошелька.
* Также используется ключевое слово indexed для этого параметра.

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

[1] Компиляция, деплой и функционал.

Видим оранжевые и синие кнопки, через addEligibleMember можем добавить данные подходящих участников: адрес кошелька, telegram username и дату unlocking например 18.12.2023 17:21:01 преобразованную в Unix Timestamp 1702909261 т.к. все вносится в ручную количество участников небольшое до 255.

[2] Добавим 3х человек с наступившей датой и 2х с разлоком на 25.12.2023

username1
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2

18.12.2023 17:21:01
1702909261
______________________________________
username2
0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db

18.12.2023 17:21:01
1702909261

______________________________________
username3
0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB

18.12.2023 17:21:01
1702909261

_______________________________________
username4
0x617F2E2fD72FD9D5503197092aC168c91465E7f2

25.12.2023 01.11.11
1703455871
________________________________________
username5
0x17F6AD8Ef982297579C203069C1DbfFE4348c372

25.12.2023 01.11.11
1703455871

*Функция для чтения определенного диапазона участников из массива.

[3] Сlaim

Username1 успешно заклеймил 0,2 eth, на балансе контракта осталось 0,8. username4 и username5 смогут это сделать (25.12.)

[4] Вывод: Смарт-контракт предоставляет базовую структуру для проведения аирдропа среди подписчиков (также возможно адаптировать под различные активности и мероприятия, добавлять больше данных о пользователях / ранжировать на группы по анлоку), где участники могут получить свои токены после определенной даты разблокировки.