Skip to content
IRC-CodingIRC-Coding
Web3 Blockchain разработкаSmart ContractsDAppsDeFiEthereumSolidity

Основы Web3 Blockchain: Smart Contracts, DApps и DeFi

Изучите основы Web3 Blockchain разработки: Smart Contracts, DApps, DeFi. Ethereum, Solidity, Web3.js с практическими примерами.

S

schutzgeist

28 min read
Основы Web3 Blockchain: Smart Contracts, DApps и DeFi

Основы разработки Web3 блокчейна: Smart Contracts, DApps и DeFi

Этот материал представляет собой комплексное введение в основы разработки Web3 блокчейна с охватом Smart Contracts, DApps и DeFi, включая практические примеры.

В двух словах

Web3 это следующее поколение интернета, построенное на блокчейне, которое позволяет создавать децентрализованные приложения (DApps), не требующие центральных органов управления.

Краткое описание предметной области

Web3 это децентрализованный интернет-протокол на базе блокчейна, предоставляющий пользователям контроль над своими данными и цифровыми идентичностями.

Ключевые компоненты:

Основы блокчейна

  • Distributed Ledger: децентрализованное хранилище данных
  • Consensus Mechanisms: Proof of Work, Proof of Stake
  • Cryptography: хеш-функции, цифровые подписи
  • Smart Contracts: самоисполняющиеся контракты
  • Gas Fees: комиссии за транзакции

Экосистема Ethereum

  • EVM: виртуальная машина Ethereum
  • Solidity: язык программирования для Smart Contracts
  • Web3.js: библиотека JavaScript для Web3
  • Hardhat: среда разработки
  • OpenZeppelin: библиотеки безопасных Smart Contracts

Децентрализованные финансы (DeFi)

  • DEX: децентрализованные биржи
  • Lending: децентрализованные платформы кредитования
  • Yield Farming: предоставление ликвидности
  • Staking: валидация токенов
  • Governance: децентрализованное принятие решений

Ключевые моменты для понимания

  • Web3: децентрализованный интернет на основе блокчейна
  • Blockchain: децентрализованная база данных с криптографической защитой
  • Smart Contracts: самоисполняющиеся контракты в блокчейне
  • DApps: децентрализованные приложения без центральной инстанции
  • DeFi: децентрализованные финансовые услуги
  • Ethereum: ведущая платформа для Smart Contracts
  • Solidity: язык программирования для Smart Contracts
  • Gas: комиссии за транзакции в блокчейне
  • IHK-relevant: современная разработка и архитектура блокчейна

Основные компоненты

  1. Blockchain Technology: Distributed Ledger, механизмы консенсуса
  2. Smart Contracts: самоисполняющиеся контракты с бизнес-логикой
  3. DApps: децентрализованные фронтенд-приложения
  4. DeFi Protocols: финансовые услуги в блокчейне
  5. Wallet Integration: MetaMask, WalletConnect
  6. Web3 Libraries: Web3.js, Ethers.js
  7. Development Tools: Hardhat, Truffle, Remix
  8. Security: аудиты, best practices, сканирование уязвимостей

Практические примеры

1. Smart Contract на Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

/**
 * @title DeFiToken
 * @dev ERC20 Token с расширенными функциями для DeFi-приложений
 * @author Web3 Developer
 */
contract DeFiToken is ERC20, ERC20Burnable, ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    
    // Events
    event TokensMinted(address indexed to, uint256 amount);
    event TokensBurned(address indexed from, uint256 amount);
    event LiquidityAdded(address indexed provider, uint256 tokenAmount, uint256 ethAmount);
    event LiquidityRemoved(address indexed provider, uint256 tokenAmount, uint256 ethAmount);
    event RewardClaimed(address indexed claimant, uint256 rewardAmount);
    
    // State variables
    uint256 public constant MAX_SUPPLY = 1000000000 * 10**18; // 1 Billion tokens
    uint256 public constant INITIAL_SUPPLY = 100000000 * 10**18; // 100 Million tokens
    uint256 public constant REWARD_RATE = 1 * 10**16; // 0.01 tokens per block
    
    uint256 public totalLiquidity;
    uint256 public totalRewardsDistributed;
    uint256 public lastRewardBlock;
    uint256 public rewardPerTokenStored;
    
    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;
    mapping(address => uint256) public liquidityProvided;
    mapping(address => uint256) public depositTimestamp;
    
    // Arrays для Staking-Pools
    struct Pool {
        uint256 totalStaked;
        uint256 rewardRate;
        uint256 lockPeriod;
        uint256 apr;
        bool active;
    }
    
    mapping(uint256 => Pool) public pools;
    mapping(address => mapping(uint256 => uint256)) public userStakes;
    mapping(address => mapping(uint256 => uint256)) public userStakeTimestamp;
    
    uint256 public poolCount;
    
    // Governance
    struct Proposal {
        uint256 id;
        address proposer;
        string description;
        uint256 voteCount;
        uint256 quorum;
        uint256 deadline;
        bool executed;
        mapping(address => bool) hasVoted;
    }
    
    mapping(uint256 => Proposal) public proposals;
    uint256 public proposalCount;
    mapping(address => uint256) public votingPower;
    
    // Modifiers
    modifier validAddress(address _addr) {
        require(_addr != address(0), "Invalid address");
        _;
    }
    
    modifier sufficientBalance(address _account, uint256 _amount) {
        require(balanceOf(_account) >= _amount, "Insufficient balance");
        _;
    }
    
    modifier sufficientLiquidity(uint256 _amount) {
        require(address(this).balance >= _amount, "Insufficient ETH liquidity");
        _;
    }
    
    modifier onlyWhenPaused() {
        require(!paused(), "Contract is paused");
        _;
    }
    
    // Constructor
    constructor(
        string memory _name,
        string memory _symbol,
        uint256 _initialMint
    ) ERC20(_name, _symbol) {
        require(_initialMint <= INITIAL_SUPPLY, "Initial mint exceeds limit");
        
        _mint(msg.sender, _initialMint);
        lastRewardBlock = block.number;
        
        // Создай начальный пул для стейкинга
        _createPool(100, 30 days, 10); // 100% APR, 30 дней блокировки
    }
    
    /**
     * @dev Выпусти новые токены (только владелец)
     */
    function mint(address _to, uint256 _amount) 
        external 
        onlyOwner 
        validAddress(_to)
        returns (bool) 
    {
        require(totalSupply() + _amount <= MAX_SUPPLY, "Exceeds max supply");
        
        _mint(_to, _amount);
        emit TokensMinted(_to, _amount);
        return true;
    }
    
    /**
     * @dev Добавь ликвидность в пул
     */
    function addLiquidity(uint256 _tokenAmount) 
        external 
        payable 
        nonReentrant 
        sufficientBalance(msg.sender, _tokenAmount)
        returns (uint256 liquidityTokens)
    {
        require(_tokenAmount > 0 && msg.value > 0, "Invalid amounts");
        
        // Вычисли токены ликвидности
        uint256 ethReserve = address(this).balance - msg.value;
        uint256 tokenReserve = totalSupply() - _tokenAmount;
        
        if (ethReserve == 0 || tokenReserve == 0) {
            liquidityTokens = _tokenAmount;
        } else {
            liquidityTokens = min(
                (msg.value * tokenReserve) / ethReserve,
                _tokenAmount
            );
        }
        
        require(liquidityTokens > 0, "Insufficient liquidity minted");
        
        // Переведи токены и ETH
        _transfer(msg.sender, address(this), _tokenAmount);
        
        // Обнови ликвидность пользователя
        liquidityProvided[msg.sender] += liquidityTokens;
        depositTimestamp[msg.sender] = block.timestamp;
        
        // Обнови общую ликвидность
        totalLiquidity += liquidityTokens;
        
        // Обнови награды
        _updateReward(msg.sender);
        
        emit LiquidityAdded(msg.sender, _tokenAmount, msg.value);
        
        return liquidityTokens;
    }
    
    /**
     * @dev Удали ликвидность из пула
     */
    function removeLiquidity(uint256 _liquidityAmount) 
        external 
        nonReentrant 
        returns (uint256 ethAmount, uint256 tokenAmount)
    {
        require(_liquidityAmount > 0, "Invalid amount");
        require(liquidityProvided[msg.sender] >= _liquidityAmount, "Insufficient liquidity");
        
        // Вычисли доли
        uint256 totalLiquidityTokens = totalLiquidity;
        uint256 ethReserve = address(this).balance;
        uint256 tokenReserve = balanceOf(address(this));
        
        ethAmount = (ethReserve * _liquidityAmount) / totalLiquidityTokens;
        tokenAmount = (tokenReserve * _liquidityAmount) / totalLiquidityTokens;
        
        require(ethAmount > 0 && tokenAmount > 0, "Insufficient withdraw amount");
        
        // Обнови ликвидность пользователя
        liquidityProvided[msg.sender] -= _liquidityAmount;
        totalLiquidity -= _liquidityAmount;
        
        // Обнови награды
        _updateReward(msg.sender);
        _claimRewards(msg.sender);
        
        // Переведи ETH и токены
        payable(msg.sender).transfer(ethAmount);
        _transfer(address(this), msg.sender, tokenAmount);
        
        emit LiquidityRemoved(msg.sender, tokenAmount, ethAmount);
        
        return (ethAmount, tokenAmount);
    }
    
    /**
     * @dev Создай новый пул для стейкинга
     */
    function createPool(uint256 _apr, uint256 _lockPeriod) external onlyOwner {
        _createPool(_apr, _lockPeriod, 0);
    }
    
    function _createPool(uint256 _apr, uint256 _lockPeriod, uint256 _rewardRate) internal {
        pools[poolCount] = Pool({
            totalStaked: 0,
            rewardRate: _rewardRate > 0 ? _rewardRate : REWARD_RATE,
            lockPeriod: _lockPeriod,
            apr: _apr,
            active: true
        });
        
        poolCount++;
    }
    
    /**
     * @dev Заблокируй токены в пуле
     */
    function stake(uint256 _poolId, uint256 _amount) 
        external 
        nonReentrant 
        sufficientBalance(msg.sender, _amount)
        returns (bool)
    {
        require(_poolId < poolCount, "Invalid pool");
        require(pools[_poolId].active, "Pool not active");
        require(_amount > 0, "Invalid amount");
        
        // Обнови награды перед стейкингом
        _updatePoolRewards(_poolId);
        _updateUserPoolRewards(_poolId, msg.sender);
        
        // Переведи токены в контракт
        _transfer(msg.sender, address(this), _amount);
        
        // Обнови стейкинг
        userStakes[msg.sender][_poolId] += _amount;
        userStakeTimestamp[msg.sender][_poolId] = block.timestamp;
        pools[_poolId].totalStaked += _amount;
        
        // Обнови权力голосования
        votingPower[msg.sender] += _amount;
        
        return true;
    }
    
    /**
     * @dev Разблокируй токены из пула
     */
    function unstake(uint256 _poolId, uint256 _amount) 
        external 
        nonReentrant 
        returns (bool)
    {
        require(_poolId < poolCount, "Invalid pool");
        require(userStakes[msg.sender][_poolId] >= _amount, "Insufficient stake");
        
        Pool storage pool = pools[_poolId];
        
        // Проверь период блокировки
        uint256 stakeTime = userStakeTimestamp[msg.sender][_poolId];
        require(
            block.timestamp >= stakeTime + pool.lockPeriod,
            "Tokens still locked"
        );
        
        // Обнови награды перед разблокировкой
        _updatePoolRewards(_poolId);
        _updateUserPoolRewards(_poolId, msg.sender);
        
        // Обнови стейкинг
        userStakes[msg.sender][_poolId] -= _amount;
        pool.totalStaked -= _amount;
        
        // Обнови权力голосования
        votingPower[msg.sender] -= _amount;
        
        // Переведи токены обратно
        _transfer(address(this), msg.sender, _amount);
        
        return true;
    }
    
    /**
     * @dev Получи награды из пула
     */
    function claimRewards(uint256 _poolId) external nonReentrant returns (uint256) {
        require(_poolId < poolCount, "Invalid pool");
        require(userStakes[msg.sender][_poolId] > 0, "No stake in pool");
        
        // Обнови награды
        _updatePoolRewards(_poolId);
        _updateUserPoolRewards(_poolId, msg.sender);
        
        uint256 reward = rewards[msg.sender];
        require(reward > 0, "No rewards to claim");
        
        // Обнули награды
        rewards[msg.sender] = 0;
        
        // Выпусти новые токены как награду
        _mint(msg.sender, reward);
        
        emit RewardClaimed(msg.sender, reward);
        
        return reward;
    }
    
    /**
     * @dev Создай предложение для голосования
     */
    function createProposal(string memory _description, uint256 _quorum) 
        external 
        returns (uint256)
    {
        require(votingPower[msg.sender] >= 1000 * 10**18, "Insufficient voting power");
        require(bytes(_description).length > 0, "Empty description");
        
        Proposal storage proposal = proposals[proposalCount];
        proposal.id = proposalCount;
        proposal.proposer = msg.sender;
        proposal.description = _description;
        proposal.voteCount = 0;
        proposal.quorum = _quorum;
        proposal.deadline = block.timestamp + 7 days;
        proposal.executed = false;
        
        proposalCount++;
        
        return proposalCount - 1;
    }
    
    /**
     * @dev Проголосуй за предложение
     */
    function vote(uint256 _proposalId) external {
        require(_proposalId < proposalCount, "Invalid proposal");
        require(block.timestamp <= proposals[_proposalId].deadline, "Voting ended");
        require(!proposals[_proposalId].hasVoted[msg.sender], "Already voted");
        require(votingPower[msg.sender] > 0, "No voting power");
        
        proposals[_proposalId].hasVoted[msg.sender] = true;
        proposals[_proposalId].voteCount += votingPower[msg.sender];
    }
    
    /**
     * @dev Выполни предложение
     */
    function executeProposal(uint256 _proposalId) external onlyOwner {
        require(_proposalId < proposalCount, "Invalid proposal");
        require(block.timestamp > proposals[_proposalId].deadline, "Voting not ended");
        require(!proposals[_proposalId].executed, "Already executed");
        require(
            proposals[_proposalId].voteCount >= proposals[_proposalId].quorum,
            "Quorum not reached"
        );
        
        proposals[_proposalId].executed = true;
    }
    
    /**
     * @dev Функции обновления наград
     */
    function _updateReward(address _account) internal {
        if (_account == address(0)) return;
        
        rewardPerTokenStored = rewardPerToken();
        lastRewardBlock = block.number;
        
        rewards[_account] = earned(_account);
        userRewardPerTokenPaid[_account] = rewardPerTokenStored;
    }
    
    function _updatePoolRewards(uint256 _poolId) internal {
        // Реализуй логику обновления наград для конкретного пула
        // Это упрощенная версия
    }
    
    function _updateUserPoolRewards(uint256 _poolId, address _account) internal {
        // Реализуй награды пользователя для конкретного пула
    }
    
    /**
     * @dev Вспомогательные функции
     */
    function rewardPerToken() public view returns (uint256) {
        if (totalLiquidity == 0) {
            return rewardPerTokenStored;
        }
        
        return rewardPerTokenStored.add(
            ((block.number.sub(lastRewardBlock)).mul(REWARD_RATE)).mul(1e18).div(totalLiquidity)
        );
    }
    
    function earned(address _account) public view returns (uint256) {
        return liquidityProvided[_account]
            .mul(rewardPerToken().sub(userRewardPerTokenPaid[_account]))
            .div(1e18)
            .add(rewards[_account]);
    }
    
    function getPoolInfo(uint256 _poolId) 
        external 
        view 
        returns (
            uint256 totalStaked,
            uint256 rewardRate,
            uint256 lockPeriod,
            uint256 apr,
            bool active
        ) 
    {
        require(_poolId < poolCount, "Invalid pool");
        Pool storage pool = pools[_poolId];
        return (pool.totalStaked, pool.rewardRate, pool.lockPeriod, pool.apr, pool.active);
    }
    
    function getUserStake(uint256 _poolId, address _user) 
        external 
        view 
        returns (uint256 amount, uint256 timestamp) 
    {
        require(_poolId < poolCount, "Invalid pool");
        return (userStakes[_user][_poolId], userStakeTimestamp[_user][_poolId]);
    }
    
    function getProposalInfo(uint256 _proposalId) 
        external 
        view 
        returns (
            address proposer,
            string memory description,
            uint256 voteCount,
            uint256 quorum,
            uint256 deadline,
            bool executed,
            bool hasVoted
        ) 
    {
        require(_proposalId < proposalCount, "Invalid proposal");
        Proposal storage proposal = proposals[_proposalId];
        return (
            proposal.proposer,
            proposal.description,
            proposal.voteCount,
            proposal.quorum,
            proposal.deadline,
            proposal.executed,
            proposal.hasVoted[msg.sender]
        );
    }
    
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
    
    /**
     * @dev Функции экстренного управления
     */
    function emergencyPause() external onlyOwner {
        _pause();
    }
    
    function emergencyUnpause() external onlyOwner {
        _unpause();
    }
    
    function emergencyWithdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }
    
    /**
     * @dev Fallback для ETH переводов
     */
    receive() external payable {
        // Принимай ETH для ликвидности
    }
    
    fallback() external payable {
        // Принимай ETH для ликвидности
    }
}

2. Фронтенд DApp с React и Web3.js

// src/components/Web3Provider.jsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import Web3 from 'web3';
import { Contract } from 'web3-eth-contract';
import DeFiTokenABI from '../contracts/DeFiToken.json';

const Web3Context = createContext();

export const useWeb3 = () => {
  const context = useContext(Web3Context);
  if (!context) {
    throw new Error('useWeb3 must be used within a Web3Provider');
  }
  return context;
};

const Web3Provider = ({ children }) => {
  const [web3, setWeb3] = useState(null);
  const [account, setAccount] = useState(null);
  const [contract, setContract] = useState(null);
  const [networkId, setNetworkId] = useState(null);
  const [balance, setBalance] = useState('0');
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const CONTRACT_ADDRESS = process.env.REACT_APP_CONTRACT_ADDRESS;
  const SUPPORTED_NETWORKS = {
    1: 'Ethereum Mainnet',
    3: 'Ropsten Testnet',
    4: 'Rinkeby Testnet',
    5: 'Goerli Testnet',
    42: 'Kovan Testnet',
    137: 'Polygon Mainnet',
    80001: 'Polygon Mumbai'
  };

  // Initialize Web3
  useEffect(() => {
    const initWeb3 = async () => {
      try {
        // Check if MetaMask is installed
        if (typeof window.ethereum !== 'undefined') {
          const web3Instance = new Web3(window.ethereum);
          setWeb3(web3Instance);

          // Request account access
          const accounts = await window.ethereum.request({
            method: 'eth_requestAccounts'
          });

          if (accounts.length > 0) {
            setAccount(accounts[0]);
            await setupContract(web3Instance);
            await getNetworkInfo(web3Instance);
            await updateBalance(web3Instance, accounts[0]);
          }

          // Setup event listeners
          setupEventListeners(web3Instance);
        } else {
          setError('MetaMask is not installed. Please install MetaMask to use this DApp.');
        }
      } catch (error) {
        console.error('Web3 initialization error:', error);
        setError('Failed to initialize Web3. Please check your wallet connection.');
      } finally {
        setLoading(false);
      }
    };

    initWeb3();
  }, []);

  const setupEventListeners = (web3Instance) => {
    // Account change
    window.ethereum.on('accountsChanged', (accounts) => {
      if (accounts.length === 0) {
        setAccount(null);
        setBalance('0');
      } else {
        setAccount(accounts[0]);
        updateBalance(web3Instance, accounts[0]);
      }
    });

    // Network change
    window.ethereum.on('chainChanged', () => {
      window.location.reload();
    });

    // Connect/Disconnect
    window.ethereum.on('connect', (connectInfo) => {
      console.log('Connected to blockchain:', connectInfo);
    });

    window.ethereum.on('disconnect', (error) => {
      console.error('Disconnected from blockchain:', error);
      setAccount(null);
      setBalance('0');
    });
  };

  const setupContract = async (web3Instance) => {
    try {
      const contractInstance = new web3Instance.eth.Contract(
        DeFiTokenABI.abi,
        CONTRACT_ADDRESS
      );
      setContract(contractInstance);
    } catch (error) {
      console.error('Contract setup error:', error);
      setError('Failed to setup smart contract.');
    }
  };

  const getNetworkInfo = async (web3Instance) => {
    try {
      const networkId = await web3Instance.eth.net.getId();
      setNetworkId(networkId);

      if (!SUPPORTED_NETWORKS[networkId]) {
        setError(`Unsupported network. Please switch to a supported network.`);
      }
    } catch (error) {
      console.error('Network info error:', error);
    }
  };

  const updateBalance = async (web3Instance, accountAddress) => {
    try {
      const balanceWei = await web3Instance.eth.getBalance(accountAddress);
      const balanceEth = web3Instance.utils.fromWei(balanceWei, 'ether');
      setBalance(balanceEth);
    } catch (error) {
      console.error('Balance update error:', error);
    }
  };

  const connectWallet = async () => {
    try {
      if (typeof window.ethereum !== 'undefined') {
        const accounts = await window.ethereum.request({
          method: 'eth_requestAccounts'
        });

        if (accounts.length > 0) {
          setAccount(accounts[0]);
          await updateBalance(web3, accounts[0]);
        }
      }
    } catch (error) {
      console.error('Wallet connection error:', error);
      setError('Failed to connect wallet.');
    }
  };

  const disconnectWallet = () => {
    setAccount(null);
    setBalance('0');
  };

  const switchNetwork = async (networkId) => {
    try {
      await window.ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: `0x${networkId.toString(16)}` }]
      });
    } catch (error) {
      console.error('Network switch error:', error);
      setError('Failed to switch network.');
    }
  };

  const value = {
    web3,
    account,
    contract,
    networkId,
    balance,
    loading,
    error,
    supportedNetworks: SUPPORTED_NETWORKS,
    connectWallet,
    disconnectWallet,
    switchNetwork,
    refreshBalance: () => updateBalance(web3, account)
  };

  return (
    <Web3Context.Provider value={value}>
      {children}
    </Web3Context.Provider>
  );
};

export default Web3Provider;

// src/components/TokenDashboard.jsx
import React, { useState, useEffect } from 'react';
import { useWeb3 } from './Web3Provider';
import { Card, CardHeader, CardContent, CardActions } from './UI/Card';
import Button from './UI/Button';
import Input from './UI/Input';
import Alert from './UI/Alert';
import LoadingSpinner from './UI/LoadingSpinner';

const TokenDashboard = () => {
  const { web3, account, contract, balance, refreshBalance } = useWeb3();
  const [tokenBalance, setTokenBalance] = useState('0');
  const [totalSupply, setTotalSupply] = useState('0');
  const [stakingData, setStakingData] = useState([]);
  const [liquidityData, setLiquidityData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [success, setSuccess] = useState(null);

  // Form states
  const [stakeAmount, setStakeAmount] = useState('');
  const [liquidityTokenAmount, setLiquidityTokenAmount] = useState('');
  const [liquidityEthAmount, setLiquidityEthAmount] = useState('');

  // Load token data
  useEffect(() => {
    if (contract && account) {
      loadTokenData();
    }
  }, [contract, account]);

  const loadTokenData = async () => {
    try {
      setLoading(true);
      setError(null);

      const [
        tokenBalanceWei,
        totalSupplyWei,
        poolCount,
        userLiquidity,
        userRewards
      ] = await Promise.all([
        contract.methods.balanceOf(account).call(),
        contract.methods.totalSupply().call(),
        contract.methods.poolCount().call(),
        contract.methods.liquidityProvided(account).call(),
        contract.methods.earned(account).call()
      ]);

      setTokenBalance(web3.utils.fromWei(tokenBalanceWei, 'ether'));
      setTotalSupply(web3.utils.fromWei(totalSupplyWei, 'ether'));
      setLiquidityData({
        provided: web3.utils.fromWei(userLiquidity, 'ether'),
        rewards: web3.utils.fromWei(userRewards, 'ether')
      });

      // Load staking data
      const pools = [];
      for (let i = 0; i < poolCount; i++) {
        const [poolInfo, userStake] = await Promise.all([
          contract.methods.getPoolInfo(i).call(),
          contract.methods.getUserStake(i, account).call()
        ]);

        pools.push({
          id: i,
          ...poolInfo,
          userStake: {
            amount: web3.utils.fromWei(userStake.amount, 'ether'),
            timestamp: userStake.timestamp
          }
        });
      }
      setStakingData(pools);

    } catch (error) {
      console.error('Token data load error:', error);
      setError('Failed to load token data.');
    } finally {
      setLoading(false);
    }
  };

  const handleStake = async (poolId, amount) => {
    try {
      setLoading(true);
      setError(null);

      const amountWei = web3.utils.toWei(amount, 'ether');
      
      const tx = await contract.methods.stake(poolId, amountWei).send({
        from: account,
        gas: '300000'
      });

      setSuccess(`Successfully staked ${amount} tokens! Transaction: ${tx.transactionHash}`);
      setStakeAmount('');
      await loadTokenData();
      await refreshBalance();

    } catch (error) {
      console.error('Stake error:', error);
      setError('Failed to stake tokens.');
    } finally {
      setLoading(false);
    }
  };

  const handleUnstake = async (poolId, amount) => {
    try {
      setLoading(true);
      setError(null);

      const amountWei = web3.utils.toWei(amount, 'ether');
      
      const tx = await contract.methods.unstake(poolId, amountWei).send({
        from: account,
        gas: '300000'
      });

      setSuccess(`Successfully unstaked ${amount} tokens! Transaction: ${tx.transactionHash}`);
      await loadTokenData();
      await refreshBalance();

    } catch (error) {
      console.error('Unstake error:', error);
      setError('Failed to unstake tokens.');
    } finally {
      setLoading(false);
    }
  };

  const handleClaimRewards = async (poolId) => {
    try {
      setLoading(true);
      setError(null);

      const tx = await contract.methods.claimRewards(poolId).send({
        from: account,
        gas: '200000'
      });

      const rewards = await contract.methods.claimRewards(poolId).call({ from: account });
      setSuccess(`Successfully claimed ${web3.utils.fromWei(rewards, 'ether')} tokens!`);
      await loadTokenData();

    } catch (error) {
      console.error('Claim rewards error:', error);
      setError('Failed to claim rewards.');
    } finally {
      setLoading(false);
    }
  };

  const handleAddLiquidity = async () => {
    try {
      setLoading(true);
      setError(null);

      const tokenAmountWei = web3.utils.toWei(liquidityTokenAmount, 'ether');
      const ethAmountWei = web3.utils.toWei(liquidityEthAmount, 'ether');

      // Approve token transfer
      await contract.methods.approve(CONTRACT_ADDRESS, tokenAmountWei).send({
        from: account,
        gas: '100000'
      });

      // Add liquidity
      const tx = await contract.methods.addLiquidity(tokenAmountWei).send({
        from: account,
        value: ethAmountWei,
        gas: '300000'
      });

      setSuccess(`Successfully added liquidity! Transaction: ${tx.transactionHash}`);
      setLiquidityTokenAmount('');
      setLiquidityEthAmount('');
      await loadTokenData();
      await refreshBalance();

    } catch (error) {
      console.error('Add liquidity error:', error);
      setError('Failed to add liquidity.');
    } finally {
      setLoading(false);
    }
  };

  const handleRemoveLiquidity = async (amount) => {
    try {
      setLoading(true);
      setError(null);

      const amountWei = web3.utils.toWei(amount, 'ether');

      const tx = await contract.methods.removeLiquidity(amountWei).send({
        from: account,
        gas: '300000'
      });

      setSuccess(`Successfully removed liquidity! Transaction: ${tx.transactionHash}`);
      await loadTokenData();
      await refreshBalance();

    } catch (error) {
      console.error('Remove liquidity error:', error);
      setError('Failed to remove liquidity.');
    } finally {
      setLoading(false);
    }
  };

  if (loading && !tokenBalance) {
    return <LoadingSpinner />;
  }

  return (
    <div className="token-dashboard">
      <div className="dashboard-header">
        <h1>DeFi Token Dashboard</h1>
        <div className="account-info">
          <p><strong>Account:</strong> {account}</p>
          <p><strong>ETH Balance:</strong> {balance} ETH</p>
          <p><strong>Token Balance:</strong> {tokenBalance} DEFI</p>
        </div>
      </div>

      {error && <Alert type="error" message={error} />}
      {success && <Alert type="success" message={success} />}

      {/* Token Info */}
      <Card className="token-info-card">
        <CardHeader>
          <h2>Token Information</h2>
        </CardHeader>
        <CardContent>
          <div className="info-grid">
            <div className="info-item">
              <label>Total Supply</label>
              <span>{totalSupply} DEFI</span>
            </div>
            <div className="info-item">
              <label>Your Balance</label>
              <span>{tokenBalance} DEFI</span>
            </div>
            <div className="info-item">
              <label>Liquidity Provided</label>
              <span>{liquidityData?.provided || '0'} DEFI</span>
            </div>
            <div className="info-item">
              <label>Pending Rewards</label>
              <span>{liquidityData?.rewards || '0'} DEFI</span>
            </div>
          </div>
        </CardContent>
      </Card>

      {/* Staking Pools */}
      <Card className="staking-card">
        <CardHeader>
          <h2>Staking Pools</h2>
        </CardHeader>
        <CardContent>
          {stakingData.map((pool) => (
            <div key={pool.id} className="staking-pool">
              <div className="pool-header">
                <h3>Pool #{pool.id + 1}</h3>
                <span className={`pool-status ${pool.active ? 'active' : 'inactive'}`}>
                  {pool.active ? 'Active' : 'Inactive'}
                </span>
              </div>
              
              <div className="pool-info">
                <div className="pool-stat">
                  <label>APR</label>
                  <span>{pool.apr}%</span>
                </div>
                <div className="pool-stat">
                  <label>Lock Period</label>
                  <span>{pool.lockPeriod / 86400} days</span>
                </div>
                <div className="pool-stat">
                  <label>Total Staked</label>
                  <span>{web3.utils.fromWei(pool.totalStaked, 'ether')} DEFI</span>
                </div>
                <div className="pool-stat">
                  <label>Your Stake</label>
                  <span>{pool.userStake.amount} DEFI</span>
                </div>
              </div>

              <div className="pool-actions">
                {pool.active && (
                  <>
                    <div className="stake-form">
                      <Input
                        type="number"
                        placeholder="Amount to stake"
                        value={stakeAmount}
                        onChange={(e) => setStakeAmount(e.target.value)}
                        min="0"
                        step="0.01"
                      />
                      <Button
                        onClick={() => handleStake(pool.id, stakeAmount)}
                        disabled={!stakeAmount || parseFloat(stakeAmount) <= 0}
                      >
                        Stake
                      </Button>
                    </div>

                    {pool.userStake.amount > '0' && (
                      <>
                        <Button
                          variant="secondary"
                          onClick={() => handleUnstake(pool.id, pool.userStake.amount)}
                        >
                          Unstake All
                        </Button>
                        <Button
                          variant="outline"
                          onClick={() => handleClaimRewards(pool.id)}
                        >
                          Claim Rewards
                        </Button>
                      </>
                    )}
                  </>
                )}
              </div>
            </div>
          ))}
        </CardContent>
      </Card>

      {/* Liquidity Management */}
      <Card className="liquidity-card">
        <CardHeader>
          <h2>Liquidity Management</h2>
        </CardHeader>
        <CardContent>
          <div className="liquidity-form">
            <h3>Add Liquidity</h3>
            <div className="form-row">
              <Input
                type="number"
                placeholder="Token amount"
                value={liquidityTokenAmount}
                onChange={(e) => setLiquidityTokenAmount(e.target.value)}
                min="0"
                step="0.01"
              />
              <Input
                type="number"
                placeholder="ETH amount"
                value={liquidityEthAmount}
                onChange={(e) => setLiquidityEthAmount(e.target.value)}
                min="0"
                step="0.01"
              />
            </div>
            <Button
              onClick={handleAddLiquidity}
              disabled={!liquidityTokenAmount || !liquidityEthAmount || loading}
            >
              Add Liquidity
            </Button>
          </div>

          {liquidityData?.provided > '0' && (
            <div className="remove-liquidity">
              <h3>Remove Liquidity</h3>
              <div className="form-row">
                <Input
                  type="number"
                  placeholder="Amount to remove"
                  min="0"
                  max={liquidityData.provided}
                  step="0.01"
                />
                <Button
                  variant="danger"
                  onClick={() => handleRemoveLiquidity(liquidityData.provided)}
                >
                  Remove All
                </Button>
              </div>
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
};

export default TokenDashboard;

// src/components/GovernancePanel.jsx
import React, { useState, useEffect } from 'react';
import { useWeb3 } from './Web3Provider';
import { Card, CardHeader, CardContent, CardActions } from './UI/Card';
import Button from './UI/Button';
import Input from './UI/Input';
import Alert from './UI/Alert';

const GovernancePanel = () => {
  const { web3, account, contract } = useWeb3();
  const [proposals, setProposals] = useState([]);
  const [votingPower, setVotingPower] = useState('0');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [success, setSuccess] = useState(null);

  // Form states
  const [proposalDescription, setProposalDescription] = useState('');
  const [proposalQuorum, setProposalQuorum] = useState('');

  useEffect(() => {
    if (contract && account) {
      loadGovernanceData();
    }
  }, [contract, account]);

  const loadGovernanceData = async () => {
    try {
      setLoading(true);
      setError(null);

      const [proposalCount, userVotingPower] = await Promise.all([
        contract.methods.proposalCount().call(),
        contract.methods.votingPower(account).call()
      ]);

      setVotingPower(web3.utils.fromWei(userVotingPower, 'ether'));

      // Load proposals
      const proposalList = [];
      for (let i = 0; i < proposalCount; i++) {
        const proposalInfo = await contract.methods.getProposalInfo(i).call();
        proposalList.push({
          id: i,
          proposer: proposalInfo[0],
          description: proposalInfo[1],
          voteCount: web3.utils.fromWei(proposalInfo[2], 'ether'),
          quorum: web3.utils.fromWei(proposalInfo[3], 'ether'),
          deadline: new Date(proposalInfo[4] * 1000),
          executed: proposalInfo[5],
          hasVoted: proposalInfo[6]
        });
      }

      setProposals(proposalList.reverse()); // Newest first

    } catch (error) {
      console.error('Governance data load error:', error);
      setError('Failed to load governance data.');
    } finally {
      setLoading(false);
    }
  };

  const handleCreateProposal = async () => {
    try {
      setLoading(true);
      setError(null);

      const quorumWei = web3.utils.toWei(proposalQuorum, 'ether');

      const tx = await contract.methods.createProposal(
        proposalDescription,
        quorumWei
      ).send({
        from: account,
        gas: '200000'
      });

      setSuccess(`Proposal created successfully! Transaction: ${tx.transactionHash}`);
      setProposalDescription('');
      setProposalQuorum('');
      await loadGovernanceData();

    } catch (error) {
      console.error('Create proposal error:', error);
      setError('Failed to create proposal.');
    } finally {
      setLoading(false);
    }
  };

  const handleVote = async (proposalId) => {
    try {
      setLoading(true);
      setError(null);

      const tx = await contract.methods.vote(proposalId).send({
        from: account,
        gas: '100000'
      });

      setSuccess(`Vote cast successfully! Transaction: ${tx.transactionHash}`);
      await loadGovernanceData();

    } catch (error) {
      console.error('Vote error:', error);
      setError('Failed to cast vote.');
    } finally {
      setLoading(false);
    }
  };

  const handleExecuteProposal = async (proposalId) => {
    try {
      setLoading(true);
      setError(null);

      const tx = await contract.methods.executeProposal(proposalId).send({
        from: account,
        gas: '100000'
      });

      setSuccess(`Proposal executed successfully! Transaction: ${tx.transactionHash}`);
      await loadGovernanceData();

    } catch (error) {
      console.error('Execute proposal error:', error);
      setError('Failed to execute proposal.');
    } finally {
      setLoading(false);
    }
  };

  const canVote = (proposal) => {
    return !proposal.hasVoted && 
           votingPower > '0' && 
           new Date() < proposal.deadline && 
           !proposal.executed;
  };

  const canExecute = (proposal) => {
    return !proposal.executed && 
           new Date() > proposal.deadline && 
           proposal.voteCount >= proposal.quorum;
  };

  return (
    <div className="governance-panel">
      <div className="governance-header">
        <h1>Governance</h1>
        <div className="voting-info">
          <p><strong>Your Voting Power:</strong> {votingPower} DEFI</p>
        </div>
      </div>

      {error && <Alert type="error" message={error} />}
      {success && <Alert type="success" message={success} />}

      {/* Create Proposal */}
      <Card className="create-proposal-card">
        <CardHeader>
          <h2>Create Proposal</h2>
        </CardHeader>
        <CardContent>
          <div className="proposal-form">
            <div className="form-group">
              <label>Proposal Description</label>
              <textarea
                value={proposalDescription}
                onChange={(e) => setProposalDescription(e.target.value)}
                placeholder="Describe your proposal..."
                rows={4}
              />
            </div>
            <div className="form-group">
              <label>Quorum (tokens required)</label>
              <Input
                type="number"
                value={proposalQuorum}
                onChange={(e) => setProposalQuorum(e.target.value)}
                placeholder="1000000"
                min="0"
                step="0.01"
              />
            </div>
          </div>
        </CardContent>
        <CardActions>
          <Button
            onClick={handleCreateProposal}
            disabled={!proposalDescription || !proposalQuorum || votingPower < '1000'}
          >
            Create Proposal
          </Button>
        </CardActions>
      </Card>

      {/* Proposals List */}
      <Card className="proposals-card">
        <CardHeader>
          <h2>Proposals</h2>
        </CardHeader>
        <CardContent>
          {proposals.length === 0 ? (
            <p>No proposals yet.</p>
          ) : (
            <div className="proposals-list">
              {proposals.map((proposal) => (
                <div key={proposal.id} className="proposal-item">
                  <div className="proposal-header">
                    <h3>Proposal #{proposal.id + 1}</h3>
                    <div className="proposal-status">
                      {proposal.executed ? (
                        <span className="status-executed">Executed</span>
                      ) : new Date() > proposal.deadline ? (
                        <span className="status-expired">Expired</span>
                      ) : (
                        <span className="status-active">Active</span>
                      )}
                    </div>
                  </div>

                  <div className="proposal-content">
                    <p className="proposal-description">{proposal.description}</p>
                    
                    <div className="proposal-meta">
                      <div className="meta-item">
                        <label>Proposer:</label>
                        <span>{proposal.proposer}</span>
                      </div>
                      <div className="meta-item">
                        <label>Deadline:</label>
                        <span>{proposal.deadline.toLocaleDateString()}</span>
                      </div>
                      <div className="meta-item">
                        <label>Vote Count:</label>
                        <span>{proposal.voteCount} / {proposal.quorum}</span>
                      </div>
                    </div>

                    <div className="vote-progress">
                      <div className="progress-bar">
                        <div 
                          className="progress-fill"
                          style={{
                            width: `${Math.min((parseFloat(proposal.voteCount) / parseFloat(proposal.quorum)) * 100, 100)}%`
                          }}
                        />
                      </div>
                      <span className="progress-text">
                        {Math.round((parseFloat(proposal.voteCount) / parseFloat(proposal.quorum)) * 100)}%
                      </span>
                    </div>
                  </div>

                  <div className="proposal-actions">
                    {canVote(proposal) && (
                      <Button onClick={() => handleVote(proposal.id)}>
                        Vote
                      </Button>
                    )}
                    
                    {canExecute(proposal) && (
                      <Button variant="success" onClick={() => handleExecuteProposal(proposal.id)}>
                        Execute
                      </Button>
                    )}

                    {proposal.hasVoted && (
                      <span className="voted-indicator">✓ Voted</span>
                    )}
                  </div>
                </div>
              ))}
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  );
};

export default GovernancePanel;

// src/App.jsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Web3Provider from './components/Web3Provider';
import TokenDashboard from './components/TokenDashboard';
import GovernancePanel from './components/GovernancePanel';
import Navbar from './components/Navbar';
import './App.css';

function App() {
  return (
    <Web3Provider>
      <Router>
        <div className="App">
          <Navbar />
          <main className="main-content">
            <Routes>
              <Route path="/" element={<TokenDashboard />} />
              <Route path="/governance" element={<GovernancePanel />} />
            </Routes>
          </main>
        </div>
      </Router>
    </Web3Provider>
  );
}

export default App;

Основной компонент Web3Provider инициализирует подключение к блокчейну через MetaMask. При загрузке приложения проверяется наличие инжекции Ethereum, запрашивается доступ к аккаунтам пользователя и загружаются данные о сети. Компонент отслеживает изменения аккаунта и сети, автоматически перезагружая страницу при смене блокчейна.

Контекст Web3 предоставляет доступ ко всем компонентам приложения через хук useWeb3, что позволяет работать с веб3-функциями и состоянием кошелька на любом уровне иерархии компонентов.

TokenDashboard отображает основную информацию о токене и пулах стейкинга. Компонент загружает данные о балансе токена, общем предложении, информации о пулах и в

3. DeFi протокол на Python и Web3.py

# defi_protocol.py
import json
import time
from web3 import Web3
from web3.contract import Contract
from web3.middleware import geth_poa_middleware
from eth_account import Account
from eth_utils import to_checksum_address
from decimal import Decimal
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class DeFiProtocol:
    """
    DeFi Protocol implementation for automated trading and liquidity management
    """
    
    def __init__(self, provider_url, private_key=None):
        """
        Initialize DeFi Protocol
        
        Args:
            provider_url: Web3 provider URL
            private_key: Private key for transactions (optional)
        """
        self.w3 = Web3(Web3.HTTPProvider(provider_url))
        
        # Inject POA middleware for testnets
        if self.w3.eth.chain_id in [3, 4, 5, 42, 80001]:
            self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
        
        self.account = None
        if private_key:
            self.account = Account.from_key(private_key)
            self.w3.eth.default_account = self.account.address
        
        # Contract addresses and ABIs
        self.contracts = {}
        self.load_contracts()
        
        # Gas settings
        self.gas_price = self.w3.eth.gas_price
        self.gas_limit = 300000
        
    def load_contracts(self):
        """Load contract ABIs and addresses"""
        # Example contract ABIs (would normally load from files)
        erc20_abi = [
            {
                "constant": True,
                "inputs": [{"name": "_owner", "type": "address"}],
                "name": "balanceOf",
                "outputs": [{"name": "balance", "type": "uint256"}],
                "type": "function"
            },
            {
                "constant": False,
                "inputs": [
                    {"name": "_to", "type": "address"},
                    {"name": "_value", "type": "uint256"}
                ],
                "name": "transfer",
                "outputs": [{"name": "", "type": "bool"}],
                "type": "function"
            },
            {
                "constant": False,
                "inputs": [
                    {"name": "_spender", "type": "address"},
                    {"name": "_value", "type": "uint256"}
                ],
                "name": "approve",
                "outputs": [{"name": "", "type": "bool"}],
                "type": "function"
            }
        ]
        
        uniswap_router_abi = [
            {
                "constant": False,
                "inputs": [
                    {"name": "amountOutMin", "type": "uint256"},
                    {"name": "path", "type": "address[]"},
                    {"name": "to", "type": "address"},
                    {"name": "deadline", "type": "uint256"}
                ],
                "name": "swapExactETHForTokens",
                "outputs": [{"name": "amounts", "type": "uint256[]"}],
                "type": "function"
            },
            {
                "constant": False,
                "inputs": [
                    {"name": "amountIn", "type": "uint256"},
                    {"name": "amountOutMin", "type": "uint256"},
                    {"name": "path", "type": "address[]"},
                    {"name": "to", "type": "address"},
                    {"name": "deadline", "type": "uint256"}
                ],
                "name": "swapExactTokensForTokens",
                "outputs": [{"name": "amounts", "type": "uint256[]"}],
                "type": "function"
            },
            {
                "constant": True,
                "inputs": [
                    {"name": "amountIn", "type": "uint256"},
                    {"name": "reserveIn", "type": "uint256"},
                    {"name": "reserveOut", "type": "uint256"}
                ],
                "name": "getAmountOut",
                "outputs": [{"name": "amountOut", "type": "uint256"}],
                "type": "function"
            }
        ]
        
        # Contract addresses (example for Ethereum Mainnet)
        self.contract_addresses = {
            'WETH': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
            'USDC': '0xA0b86a33E6441E7C1A785D1A97b7327c2c0c0c0c',
            'DAI': '0x6B175474E89094C44Da98b954EedeAC495271d0F',
            'UNISWAP_ROUTER': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
            'UNISWAP_FACTORY': '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
        }
        
        # Initialize contracts
        for name, address in self.contract_addresses.items():
            if name in ['WETH', 'USDC', 'DAI']:
                self.contracts[name] = self.w3.eth.contract(
                    address=address,
                    abi=erc20_abi
                )
            elif name == 'UNISWAP_ROUTER':
                self.contracts[name] = self.w3.eth.contract(
                    address=address,
                    abi=uniswap_router_abi
                )
    
    def get_balance(self, token_symbol, address=None):
        """
        Get token balance for address
        
        Args:
            token_symbol: Token symbol (e.g., 'ETH', 'USDC', 'DAI')
            address: Address to check (default: current account)
        
        Returns:
            Token balance as Decimal
        """
        if address is None:
            if not self.account:
                raise ValueError("No account specified")
            address = self.account.address
        
        if token_symbol == 'ETH':
            balance_wei = self.w3.eth.get_balance(address)
            return Decimal(balance_wei) / Decimal(10**18)
        
        if token_symbol not in self.contracts:
            raise ValueError(f"Unknown token: {token_symbol}")
        
        contract = self.contracts[token_symbol]
        balance_wei = contract.functions.balanceOf(address).call()
        decimals = self.get_token_decimals(token_symbol)
        
        return Decimal(balance_wei) / Decimal(10**decimals)
    
    def get_token_decimals(self, token_symbol):
        """Get token decimals"""
        decimals_map = {
            'WETH': 18,
            'USDC': 6,
            'DAI': 18
        }
        return decimals_map.get(token_symbol, 18)
    
    def approve_token(self, token_symbol, spender_address, amount):
        """
        Approve token spending
        
        Args:
            token_symbol: Token symbol to approve
            spender_address: Address to approve for
            amount: Amount to approve (in human-readable format)
        
        Returns:
            Transaction hash
        """
        if not self.account:
            raise ValueError("No account configured")
        
        if token_symbol not in self.contracts:
            raise ValueError(f"Unknown token: {token_symbol}")
        
        contract = self.contracts[token_symbol]
        decimals = self.get_token_decimals(token_symbol)
        amount_wei = int(Decimal(amount) * Decimal(10**decimals))
        
        # Build transaction
        transaction = contract.functions.approve(
            spender_address,
            amount_wei
        ).build_transaction({
            'from': self.account.address,
            'gas': self.gas_limit,
            'gasPrice': self.gas_price,
            'nonce': self.w3.eth.get_transaction_count(self.account.address)
        })
        
        # Sign and send transaction
        signed_txn = self.w3.eth.account.sign_transaction(transaction, self.account.key)
        tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
        
        # Wait for confirmation
        receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
        
        if receipt.status == 1:
            logger.info(f"Token approved: {tx_hash.hex()}")
            return tx_hash.hex()
        else:
            raise Exception("Transaction failed")
    
    def swap_eth_for_tokens(self, eth_amount, token_symbol, slippage_percent=1):
        """
        Swap ETH for tokens on Uniswap
        
        Args:
            eth_amount: Amount of ETH to swap
            token_symbol: Target token symbol
            slippage_percent: Maximum slippage percentage
        
        Returns:
            Transaction hash
        """
        if not self.account:
            raise ValueError("No account configured")
        
        if token_symbol not in self.contract_addresses:
            raise ValueError(f"Unknown token: {token_symbol}")
        
        router = self.contracts['UNISWAP_ROUTER']
        token_address = self.contract_addresses[token_symbol]
        
        # Calculate minimum amount out with slippage
        amount_out = self.get_swap_amount_out(eth_amount, 'ETH', token_symbol)
        min_amount_out = int(amount_out * (1 - slippage_percent / 100))
        
        # Build transaction
        eth_amount_wei = int(Decimal(eth_amount) * Decimal(10**18))
        deadline = int(time.time()) + 300  # 5 minutes
        
        transaction = router.functions.swapExactETHForTokens(
            min_amount_out,
            [self.contract_addresses['WETH'], token_address],
            self.account.address,
            deadline
        ).build_transaction({
            'from': self.account.address,
            'value': eth_amount_wei,
            'gas': self.gas_limit,
            'gasPrice': self.gas_price,
            'nonce': self.w3.eth.get_transaction_count(self.account.address)
        })
        
        # Sign and send transaction
        signed_txn = self.w3.eth.account.sign_transaction(transaction, self.account.key)
        tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
        
        # Wait for confirmation
        receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
        
        if receipt.status == 1:
            logger.info(f"ETH swapped for {token_symbol}: {tx_hash.hex()}")
            return tx_hash.hex()
        else:
            raise Exception("Transaction failed")
    
    def swap_tokens_for_eth(self, token_symbol, token_amount, slippage_percent=1):
        """
        Swap tokens for ETH on Uniswap
        
        Args:
            token_symbol: Token symbol to swap
            token_amount: Amount of tokens to swap
            slippage_percent: Maximum slippage percentage
        
        Returns:
            Transaction hash
        """
        if not self.account:
            raise ValueError("No account configured")
        
        if token_symbol not in self.contract_addresses:
            raise ValueError(f"Unknown token: {token_symbol}")
        
        router = self.contracts['UNISWAP_ROUTER']
        token_address = self.contract_addresses[token_symbol]
        
        # Approve token spending
        self.approve_token(token_symbol, self.contract_addresses['UNISWAP_ROUTER'], token_amount)
        
        # Calculate minimum amount out with slippage
        amount_out = self.get_swap_amount_out(token_amount, token_symbol, 'ETH')
        min_amount_out = int(amount_out * (1 - slippage_percent / 100))
        
        # Build transaction
        decimals = self.get_token_decimals(token_symbol)
        token_amount_wei = int(Decimal(token_amount) * Decimal(10**decimals))
        deadline = int(time.time()) + 300  # 5 minutes
        
        transaction = router.functions.swapExactTokensForTokens(
            token_amount_wei,
            min_amount_out,
            [token_address, self.contract_addresses['WETH']],
            self.account.address,
            deadline
        ).build_transaction({
            'from': self.account.address,
            'gas': self.gas_limit,
            'gasPrice': self.gas_price,
            'nonce': self.w3.eth.get_transaction_count(self.account.address)
        })
        
        # Sign and send transaction
        signed_txn = self.w3.eth.account.sign_transaction(transaction, self.account.key)
        tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
        
        # Wait for confirmation
        receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
        
        if receipt.status == 1:
            logger.info(f"{token_symbol} swapped for ETH: {tx_hash.hex()}")
            return tx_hash.hex()
        else:
            raise Exception("Transaction failed")
    
    def get_swap_amount_out(self, amount_in, token_in, token_out):
        """
        Calculate amount out for swap (simplified)
        
        Args:
            amount_in: Amount to swap
            token_in: Input token symbol
            token_out: Output token symbol
        
        Returns:
            Expected amount out
        """
        # This is a simplified calculation
        # In production, you would query the actual pool reserves
        
        # Example exchange rates (would be fetched from DEX)
        exchange_rates = {
            ('ETH', 'USDC'): 2000,  # 1 ETH = 2000 USDC
            ('ETH', 'DAI'): 2000,    # 1 ETH = 2000 DAI
            ('USDC', 'ETH'): 0.0005, # 1 USDC = 0.0005 ETH
            ('DAI', 'ETH'): 0.0005,  # 1 DAI = 0.0005 ETH
        }
        
        rate = exchange_rates.get((token_in, token_out))
        if rate is None:
            raise ValueError(f"No exchange rate for {token_in} -> {token_out}")
        
        return Decimal(amount_in) * Decimal(rate)
    
    def add_liquidity(self, token_symbol, token_amount, eth_amount):
        """
        Add liquidity to Uniswap pool
        
        Args:
            token_symbol: Token symbol to add
            token_amount: Amount of tokens to add
            eth_amount: Amount of ETH to add
        
        Returns:
            Transaction hash
        """
        if not self.account:
            raise ValueError("No account configured")
        
        # This would implement actual Uniswap liquidity addition
        # For now, it's a placeholder
        logger.info(f"Adding liquidity: {token_amount} {token_symbol} + {eth_amount} ETH")
        
        # In production, you would:
        # 1. Approve token spending
        # 2. Call Uniswap router's addLiquidityETH function
        # 3. Handle transaction and receipt
        
        return "0xplaceholder"
    
    def remove_liquidity(self, token_symbol, liquidity_amount):
        """
        Remove liquidity from Uniswap pool
        
        Args:
            token_symbol: Token symbol of the pool
            liquidity_amount: Amount of liquidity tokens to remove
        
        Returns:
            Transaction hash
        """
        if not self.account:
            raise ValueError("No account configured")
        
        # This would implement actual Uniswap liquidity removal
        logger.info(f"Removing liquidity: {liquidity_amount} from {token_symbol} pool")
        
        # In production, you would:
        # 1. Call Uniswap router's removeLiquidityETH function
        # 2. Handle transaction and receipt
        
        return "0xplaceholder"
    
    def get_portfolio_value(self, address=None):
        """
        Calculate total portfolio value in USD
        
        Args:
            address: Address to calculate for (default: current account)
        
        Returns:
            Portfolio value in USD
        """
        if address is None:
            if not self.account:
                raise ValueError("No account specified")
            address = self.account.address
        
        # Get balances
        eth_balance = self.get_balance('ETH', address)
        usdc_balance = self.get_balance('USDC', address)
        dai_balance = self.get_balance('DAI', address)
        
        # Calculate USD value (simplified)
        # In production, you would fetch real prices from oracles
        eth_price_usd = 2000  # Example price
        dai_price_usd = 1.0    # DAI is pegged to USD
        
        portfolio_value = (
            eth_balance * eth_price_usd +
            usdc_balance +
            dai_balance * dai_price_usd
        )
        
        return {
            'total_value_usd': portfolio_value,
            'breakdown': {
                'ETH': {'amount': eth_balance, 'value_usd': eth_balance * eth_price_usd},
                'USDC': {'amount': usdc_balance, 'value_usd': usdc_balance},
                'DAI': {'amount': dai_balance, 'value_usd': dai_balance * dai_price_usd}
            }
        }
    
    def monitor_gas_prices(self):
        """
        Monitor current gas prices
        
        Returns:
            Current gas price in Gwei
        """
        gas_price_wei = self.w3.eth.gas_price
        gas_price_gwei = gas_price_wei / 10**9
        
        return {
            'gas_price_wei': gas_price_wei,
            'gas_price_gwei': gas_price_gwei,
            'estimated_eth_cost': gas_price_wei * self.gas_limit / 10**18
        }
    
    def execute_arbitrage(self, token_symbol, min_profit_percent=1):
        """
        Execute arbitrage opportunity (simplified)
        
        Args:
            token_symbol: Token to arbitrage
            min_profit_percent: Minimum profit percentage required
        
        Returns:
            Transaction hash if arbitrage executed
        """
        # This is a simplified arbitrage implementation
        # In production, you would:
        # 1. Monitor prices across multiple DEXs
        # 2. Calculate profit opportunities
        # 3. Execute trades when profitable
        
        logger.info(f"Checking arbitrage opportunities for {token_symbol}")
        
        # Example: Check price difference between two DEXs
        # This is placeholder logic
        price_dex1 = self.get_swap_amount_out(1, 'ETH', token_symbol)
        price_dex2 = self.get_swap_amount_out(1, 'ETH', token_symbol) * 1.02  # 2% difference
        
        profit_percent = ((price_dex2 - price_dex1) / price_dex1) * 100
        
        if profit_percent >= min_profit_percent:
            logger.info(f"Arbitrage opportunity found: {profit_percent:.2f}% profit")
            # Execute arbitrage trades
            return "0xarbitrage_placeholder"
        
        return None

# Usage example
def main():
    """Example usage of DeFi Protocol"""
    
    # Initialize protocol
    provider_url = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
    private_key = "YOUR_PRIVATE_KEY"  # Be careful with private keys!
    
    protocol = DeFiProtocol(provider_url, private_key)
    
    # Get portfolio value
    portfolio = protocol.get_portfolio_value()
    print(f"Portfolio Value: ${portfolio['total_value_usd']:.2f}")
    
    # Check gas prices
    gas_info = protocol.monitor_gas_prices()
    print(f"Gas Price: {gas_info['gas_price_gwei']:.2f} Gwei")
    
    # Swap ETH for USDC
    try:
        tx_hash = protocol.swap_eth_for_tokens(0.1, 'USDC', slippage_percent=1)
        print(f"Swap transaction: {tx_hash}")
    except Exception as e:
        print(f"Swap failed: {e}")
    
    # Check for arbitrage opportunities
    arbitrage_tx = protocol.execute_arbitrage('USDC', min_profit_percent=1)
    if arbitrage_tx:
        print(f"Arbitrage executed: {arbitrage_tx}")

if __name__ == "__main__":
    main()

Этот класс DeFiProtocol предоставляет полный набор функций для управления портфелем DeFi. Он использует Web3.py для взаимодействия с блокчейном Ethereum и реализует основные операции, такие как обмены через Uniswap, управление ликвидностью и мониторинг портфеля.

Ключевые функции:

  • get_balance() получает баланс любого токена для указанного адреса. Корректно преобразует значения из wei с учётом количества decimal мест каждого токена.

  • approve_token() выдаёт разрешение контракту потратить токены от вашего имени. Это необходимый шаг перед любым своп-транзакшеном с токенами.

  • swap_eth_for_tokens() и swap_tokens_for_eth() выполняют обмены на Uniswap. Они автоматически рассчитывают минимальный выход с учётом слиппажа и устанавливают deadline для транзакции.

  • get_portfolio_value() складывает все балансы и конвертирует их в USD на основе текущих цен. В боевых условиях вы бы использовали Chainlink oracle для получения актуальных цен.

  • monitor_gas_prices() показывает текущие цены на газ и позволяет оценить стоимость операций в ETH.

  • execute_arbitrage() проверяет возможности арбитража между DEX-ами. Это упрощённая реализация, но в реальности можно построить сложные боты, которые мониторят множество источников цен.

Все транзакции подписываются приватным ключом и отправляются на блокчейн. После отправки код ждёт подтверждения (одного блока), прежде чем вернуть хеш или рейз исключения при ошибке.

Архитектура Web3

Экосистема DeFi

graph TD
    A[User] --> B[Wallet]
    B --> C[DApp Frontend]
    C --> D[Web3.js/Ethers.js]
    D --> E[Smart Contracts]
    
    E --> F[DeFi Protocols]
    F --> G[DEX]
    F --> H[Lending]
    F --> I[Yield Farming]
    F --> J[Liquidity Pools]
    
    E --> K[Blockchain]
    K --> L[Ethereum]
    K --> M[Polygon]
    K --> N[BSC]
    
    O[Oracles] --> E
    P[Chainlink] --> O
    Q[Band Protocol] --> O
    
    R[Governance] --> E
    S[DAO] --> R
    T[Token Holders] --> R

Безопасность Smart Contracts

Распространённые уязвимости

УязвимостьОписаниеЗащита
ReentrancyРекурсивные вызовы до обновления состоянияReentrancyGuard, Checks-Effects-Interactions
Integer OverflowПереполнение/недополнение арифметических операцийSafeMath, Solidity 0.8+
Access ControlНесанкционированные вызовы функцийModifiers, Role-based access
Front-RunningМанипуляция порядком транзакцийCommit-reveal schemes
Oracle ManipulationМанипуляция источниками ценДецентрализованные oracles

Сравнение DeFi протоколов

Ведущие категории

КатегорияПротоколыФункцияTVL (млрд $)
DEXUniswap, SushiSwapОбмен токенов25+
LendingAave, CompoundКредитование20+
YieldYearn, HarvestОптимизация доходности10+
DerivativesSynthetix, PerpetualДеривативы5+
OracleChainlink, BandИсточники цен15+

Плюсы и минусы

Преимущества Web3/DeFi

  • Децентрализация: не требуется центральный орган
  • Прозрачность: все транзакции открыты для просмотра
  • Permissionless: любой может участвовать без разрешения
  • Программируемость: Smart Contracts позволяют реализовать сложную логику
  • Композируемость: протоколы можно комбинировать между собой

Недостатки

  • Масштабируемость: ограниченная пропускная способность сети
  • Комиссии за газ: высокие затраты на транзакции при перегрузке сети
  • Пользовательский опыт: сложная интеграция с кошельками
  • Риски безопасности: уязвимости в Smart Contracts
  • Неопределённость регулирования: нечёткая нормативная база

Типичные вопросы на экзамене

  1. В чём разница между Web2 и Web3? Web2 построен на централизованных серверах, Web3 использует децентрализованные блокчейны и Smart Contracts.

  2. Объясните, что такое Smart Contracts. Smart Contracts - это самоисполняющиеся соглашения на блокчейне, которые выполняют бизнес-логику без посредников.

  3. Когда применяют DeFi протоколы? DeFi протоколы используют для децентрализованных финансовых услуг: торговля, кредитование и фермерство доходности.

  4. Какие основные преимущества блокчейна? Децентрализация, прозрачность, неизменяемость и криптографическая безопасность.

Ключевые источники

  1. https://ethereum.org/
  2. https://docs.soliditylang.org/
  3. https://uniswap.org/
  4. https://aave.com/
Назад к блогу
Share:

Похожие статьи