仿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;
大致效果如下
反思:感觉合约还好,前端还是有好多东西没考虑到啊,有些疏漏