一个简单的Token银行DApp

上一篇文章中,我们发行了自己的代币 MFT。

这里再创建一个存储MFT币的银行,合约代码如下:

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

import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract DBank {
    // 存储账户余额
    mapping(address => uint) private balance;

    // 不可变变量
    address private immutable token;

    constructor(address _token) {
        token = _token;
    } 

    // 注意:所有amount应该是用户(或前端应用)直接向合约提供的最小单位数量(wei,或带10^18小数位的单位)
    //      单位转换在应用层进行
    modifier requireBalance(uint amount) {
        require(amount <= balance[msg.sender], "Your balance is not enough.");
        _;
    }

    // 查询余额
    function myBalance() public view returns (uint) {
        return balance[msg.sender];
    }

    // 存款 -----> 将个人账户的代币转移到 DBank 合约账户
    function deposit(uint amount) public {
        // 在代币合约中,将个人账户的 token 转移到 DBank 账户(合约地址)
        require(IERC20(token).transferFrom(msg.sender, address(this), amount), "deposit failed");
        // 记录
        balance[msg.sender] += amount;
    }

    // 取款 -----> 将 DBank 合约账户的代币转移到个人账户
    function withdraw(uint amount) external requireBalance(amount) {
        // 在代币合约中,将 DBank 合约账户的 token 转移到个人账户中
        // require(IERC20(token).transfer(msg.sender, amount), "withdraw failed");
        SafeERC20.safeTransfer(IERC20(token), msg.sender, amount);
        // 记录
        balance[msg.sender] -= amount;
    }

    // 转账 -----> 银行内的转账
    function bankTransfer(address to, uint amount) public requireBalance(amount) {
        // 直接记录
        balance[msg.sender] -= amount;
        balance[to] += amount;
    }
}

然后部署到测试网上,部署的过程不再赘述,这里部署后的合约地址是 0xcf33936D93CFda55e0e0B041B2C0a93817E2fdB1

二、使用react创建前端

这里我在VSCode中使用 Gemini AI 直接生成了一个页面,如下:
image

当然这还只是一个静态页面。我们需要使用一些 js 库去连接以太坊,与区块链进行交互。

常用的库比如ethers.jsweb3.jsviem.sh等,在Dapp的开发中使用这些库的API功能大部分都是相同的,你可以选择其中的一个即可。相对而言,更推荐 ethers.js、viem.sh,接口也更简洁。

这里我们选择 ethers.js 与区块链进行交互。

1、安装

npm install --save ethers

2、连接钱包,并操作合约

import {ethers} from 'ethers';
import dbankAbi from './ABI.json';

function App() {
  const [balance, setBalance] = useState(0);
  const [account, setAccount] = useState(null);
  const [bankContract, setBankContract] = useState(0);
  const [depositAmount, setDepositAmount] = useState('');
  const [withdrawAmount, setWithdrawAmount] = useState('');
  const [transferAddress, setTransferAddress] = useState('');
  const [transferAmount, setTransferAmount] = useState('');

  const connectWallet = async() => {
    try {
      // 1、检查浏览器是否安装了以太坊钱包(如MetaMask)
      if (!window.ethereum) {
        alert('请安装MetaMask等以太坊钱包插件!');
        return;
      }
      // 2、请求连接钱包,获取账户
      const provider = new ethers.BrowserProvider(window.ethereum);
      const signer = await provider.getSigner();
      const accounts = await provider.send("eth_requestAccounts", []);
      setAccount(accounts[0]);
      
      // 3、访问合约
      const dbankAddress = "0xcf33936D93CFda55e0e0B041B2C0a93817E2fdB1";
      const theBankContract = new ethers.Contract(dbankAddress, dbankAbi, signer);
      setBankContract(theBankContract);

      // 首次调用合约的myBalance方法,显示余额
      const firstBalance = await theBankContract.myBalance();
      setBalance(ethers.formatUnits(firstBalance, 18));
    } catch (error) {
      console.error("连接钱包失败:", error);
      alert("连接钱包失败!");
    }
  }

  const getMyBalance = async() => {
    if (!bankContract) {
      console.log("银行合约未连接!");
      return;
    }
    try {
      // 调用合约的myBalance方法
      const myBalance = await bankContract.myBalance();
      setBalance(ethers.formatUnits(myBalance, 18));
    } catch (error) {
      console.error("获取余额失败:", error);
      alert("获取余额失败!");
    }
  }

  const deposit = async() => {
    if (!bankContract || !depositAmount) {
      alert("请输入存款金额!");
      return;
    }
    try {
      const tx = await bankContract.deposit(ethers.parseUnits(depositAmount, 18));
      await tx.wait(); // 等待交易被打包
      alert("存款成功!");
      setDepositAmount(''); // 清空输入框
      getMyBalance(); // 更新余额
    } catch (error) {
      console.error("存款失败:", error);
      alert("存款失败!");
    }
  }

  const withdraw = async() => {
    if (!bankContract || !withdrawAmount) {
      alert("请输入取款金额!");
      return;
    }
    try {
      const tx = await bankContract.withdraw(ethers.parseUnits(withdrawAmount, 18));
      await tx.wait(); // 等待交易被打包
      alert("取款成功!");
      setWithdrawAmount(''); // 清空输入框
      getMyBalance(); // 更新余额
    } catch (error) {
      console.error("取款失败:", error);
      alert("取款失败!");
    }
  }

  const bankTransfer = async() => {
    if (!bankContract || !transferAmount) {
      alert("请输入转账金额!");
      return;
    }
    try {
      const tx = await bankContract.bankTransfer(transferAddress,ethers.parseUnits(transferAmount, 18));
      await tx.wait(); // 等待交易被打包
      alert("转账成功!");
      setTransferAmount(''); // 清空输入框
      getMyBalance(); // 更新余额
    } catch (error) {
      console.error("转账失败:", error);
      alert("转账失败!");
    }
  }
}

优化后最终页面如下:
image

注意:请事先调用 MyFirstToken (MFT) 合约的approve接口,授权批准 DBank 合约账户可以从你的账户中多次提取,最高可达 25000 MFT币。
image

image

posted @ 2025-11-20 23:42  songlee  阅读(10)  评论(0)    收藏  举报