3.Faucet-DAPP

198 阅读3分钟

仿chainlink的样式做了一个自己的miniDAPP

  • 前端:react.js
  • css:tailwind
  • 合约:solidity
  • 交互:ethers.js

配置流程详见文章

solidity:

erc20代币合约

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "./IERC20.sol";

contract DCToken is IERC20 {

    // 代币数据
    string public name;
    string public symbol;
    uint8 public decimals = 18;

    uint256 public override totalSupply ;

    address public owner;
    
    // 余额
    mapping(address=>uint256) public override balanceOf;
    // 授权额度
    mapping(address=>mapping(address=>uint256)) public override allowance;


    constructor(string memory _name,string memory _symbol) {
        name = _name;
        symbol = _symbol;
        owner = msg.sender;
    }
    
    modifier onlyOwner{
        require (msg.sender == owner,"not owner of the erc20");
        _;
    }

    // 直接操作自己代币转账
    function transfer(address to,uint256 amount) external override returns(bool){
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;

        emit Transfer(msg.sender,to,amount);
        return true;
    }

    // 授权操作代币
    function approve(address spender,uint256 amount) external override returns(bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender,spender,amount);
        return true;
    }

    function transferFrom(address sender,address recipient,uint256 amount) external override returns(bool){
       
        require(allowance[sender][msg.sender]>=amount,"allowance is not enough");
        
        allowance[sender][msg.sender] -= amount;
        balanceOf[sender] -= amount;
        balanceOf[recipient] += amount;

        emit Transfer(sender,recipient,amount);
        return true;
    }

    function mint(uint256 amount) external onlyOwner {
        balanceOf[msg.sender] += amount;
        totalSupply += amount;

        emit Transfer(address(0),msg.sender,amount);
    }

     function burn(uint256 amount) external onlyOwner {
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
    }
}

水龙头合约

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "./IERC20.sol";

contract Faucet {
    // 一次申请的代币量
    uint256 public constant requestAmount = 10;

    // 间隔时间
    uint256 public constant intervals = 120 minutes;

    // 代币合约地址
    address public tokenContract;

    // 记录申请的地址
    mapping(address => bool) public isRequested;

    mapping(address => uint256) public requestTime;

    // 接受过代币的地址
    event SendToken(address indexed receiver, uint256 indexed amount);

    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }

    // 给指定地址申请token
    function requestDC(address _targetContract) external {
        // 先检查上一次申请是否在两小时内
        if (
            isRequested[msg.sender] &&
            block.timestamp - requestTime[msg.sender] > intervals
        ) {
            isRequested[msg.sender] = false;
            requestTime[msg.sender] = 0;
        }
        require(
            !isRequested[msg.sender],
            "you have requested in last two hours"
        );
        IERC20 token = IERC20(tokenContract);

        // 合约中的token足够
        require(
            token.balanceOf(address(this)) >= requestAmount,
            "not enough token"
        );
        token.transfer(_targetContract, requestAmount);

        isRequested[msg.sender] = true;
        requestTime[msg.sender] = block.timestamp;
        emit SendToken(_targetContract, requestAmount);
    }

    // 查询水龙头中剩余DC
    function getFaucetToken() external view returns (uint256) {
        IERC20 token = IERC20(tokenContract);
        return token.balanceOf(address(this));
    }

    // 获取合约token余额
    function getToken(
        address _targetContract
    ) external view returns (uint256 tokenAmount) {
        require(
            msg.sender == _targetContract,
            "you have no permissions to see the balance of token"
        );
        IERC20 token = IERC20(tokenContract);
        return token.balanceOf(_targetContract);
    }
}

部署脚本

/*
 * @Author: diana
 * @Date: 2023-05-08 18:21:19
 * @LastEditTime: 2023-05-10 17:55:34
 */
 
const hre = require("hardhat");

async function main() {

  const signer = await hre.ethers.getSigner();
  const signerAddress = signer.getAddress();

  // erc20代币合约
  const Token = await hre.ethers.getContractFactory("DCToken");
  const token = await Token.deploy("DianaCoin","DC");
  await token.deployed();
  
  // 铸造1000个代币
  await token.mint(1000);
  const tokenContract = token.address;
  const balance = await token.balanceOf(signerAddress);

  // 部署水龙头
  const Faucet = await hre.ethers.getContractFactory("Faucet");
  const faucet = await Faucet.deploy(tokenContract);
  await faucet.deployed();

  // 将代币转给水龙头
  await token.transfer(faucet.address,balance);

  // 获取当前水龙头余额
  const faucetBalance = await faucet.getFaucetToken();

  console.log(
    `token is deployed to ${token.address}`,
    `faucet is deployed to ${faucet.address}`,
    faucetBalance.toString()

  );
}

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

React

APP.js

/*
 * @Author: diana
 * @Date: 2023-05-08 18:19:53
 * @LastEditTime: 2023-05-10 20:02:42
 */
import './index.css';

import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';

import abi from "./artifacts/contracts/faucet.sol/Faucet.json"

function App() {

  // 合约地址
  const contractAddress = "";
  const contractABI = abi.abi;

  const [currentAccount, setCurrentAccount] = useState();
  const [targetAccount, setTargetAccount] = useState();

  const [currentBalance, setCurrentBalance] = useState();
  const [targetBalance, setTargetBalance] = useState();

  const [faucetToken, setFaucetToken] = useState();

  // 钱包是否连接
  const isWalletConnected = async () => {

    try {

      const { ethereum } = window

      if (ethereum) {
        const accounts = await ethereum.request({ method: 'eth_accounts' });
        if (accounts.length !== 0) {
          const currentAc = accounts[0];
          setCurrentAccount(currentAc);
        } else {
          console.log("please connect your wallet");
        }
      } else {
        console.log("make sure you have metamask")
      }
    } catch (err) {
      console.log(err);
    }
  }

  // 连接钱包弹窗
  const connectWallet = async () => {
    try {
      const { ethereum } = window;
      if (!ethereum) {
        return;
      }
      const accounts = ethereum.request({ method: 'eth_requestAccounts' });
      setCurrentAccount(accounts[0]);

    } catch (e) {
      console.log(e)
    }



  }

  // 申请代币
  const requestToken = async () => {

    const { ethereum } = window;
    console.log("连接状态:", ethereum.isConnected());

    try {
      if (ethereum) {
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();

        const faucetContract = new ethers.Contract(contractAddress,
          contractABI,
          signer);

        const tokenTx = await faucetContract.requestDC(targetAccount);
        tokenTx.wait();

        getToken();
      } else {
        console.log("你没连")
      }
    } catch (e) { console.log(e) }
  }

  // 查询余额
  const getToken = async () => {
    const { ethereum } = window;

    try {
      if (ethereum) {
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();

        const faucetContract = new ethers.Contract(contractAddress,
          contractABI,
          signer);

        const fTokenTx = await faucetContract.getFaucetToken();
        const faucetToken = fTokenTx.toString();
        setFaucetToken(faucetToken);

        const cAB = await faucetContract.getToken(currentAccount);
        const currentAccountB = cAB.toString();
        setCurrentBalance(currentAccountB);

        const tAB = await faucetContract.getToken(targetAccount);
        const targetAccountB = tAB.toString();
        setTargetBalance(targetAccountB);

        console.log(faucetToken);
      } else {
        console.log("你没连")
      }
    } catch (e) { console.log(e) }
  }

  const handleTargetAccount = (e) => {
    const { value } = e.target;
    setTargetAccount(value)
  }


  useEffect(() => {
    isWalletConnected();
    connectWallet();
  }, [])



  return (
    // min-h可以撑开div高度
    <div className="flex flex-col bg-faucet-bg w-full items-center min-h-screen px-2 py-2">
      {/* title */}
      <div className='flex flex-row w-full justify-end h-12'>
        <div className='bg-white border border-gray-300 rounded-md hover:border-black text-text-color py-3 lg:w-1/3 sm:w-full  xs:w-full px-3'>
          {currentAccount}
        </div>
      </div>

      <div className="flex flex-row bg-faucet-bg mb-1 w-full h-24 items-center">
        <div className='text-text-color text-6xl lg:w-1/2 sm:w-full xs:w-full'>
          Get DC Tokens
        </div>
        <div className='text-text-color text-2xl text-right mr-2 mt-2 lg:w-1/2 sm:w-full xs:w-full'>
          there are {faucetToken} tokens remain
        </div>

      </div>

      {/* form */}
      <div className="flex flex-col bg-white border-2 rounded-lg w-full mb-1 px-2 py-2 h-96">
        {/* info */}
        <div className='bg-faucet-bg mb-5 py-4 px-2 mt-2 text-text-color'>Your wallet is connected! </div>

        {/* address */}
        <div className="flex flex-col mb-5">
          <h1 className='text-xl text-text-color'>Wallet address</h1>
          <div className="px-2 py-2 border rounded-md boder-gray-300  hover:border hover:border-gray-400 focus:border focus:border-gray-400  lg:w-1/3 md:w-full">
            <input className='w-full appearance-none hover:appearance-none  focus:outline-none' onChange={handleTargetAccount} />
          </div>

        </div>

        {/* requestToken */}
        <div className='bg-faucet-bg border border-blue-100 rounded-md px-5 py-5 w-1/4 mb-6'>
          <input type="checkbox" className='mr-2'/>10 DC
        </div>

        <div>
          <button className='bg-faucet-bg border border-blue-100 rounded-md px-2 py-5  hover:bg-blue-100' onClick={requestToken}>Send Me Token</button>
        </div>

        <div className='mt-3 px-1'>
          now the targetAccount have {targetBalance} tokens
        </div>


      </div>

    </div>
  );
}

export default App;

大致效果如下

faucet.png

反思:感觉合约还好,前端还是有好多东西没考虑到啊,有些疏漏