测试网部署入金,出金合约
合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title DepositWithdraw
* @dev 一个简单的充值和提款合约
* 用户可以存入 ETH,也可以提取他们存入的 ETH
*/
contract DepositWithdraw {
// 合约所有者
address public owner;
// 存储每个地址的余额
mapping(address => uint256) public balances;
// 存储每个地址的总存款金额
mapping(address => uint256) public totalDeposits;
// 存储每个地址的总提款金额
mapping(address => uint256) public totalWithdrawals;
// 事件:存款
event Deposit(address indexed user, uint256 amount, uint256 timestamp);
// 事件:提款
event Withdraw(address indexed user, uint256 amount, uint256 timestamp);
// 修饰符:只有所有者可以调用
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
// 构造函数
constructor() {
owner = msg.sender;
}
/**
* @dev 充值函数 - 接收 ETH
*/
function deposit() public payable {
require(msg.value > 0, "Deposit amount must be greater than 0");
balances[msg.sender] += msg.value;
totalDeposits[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value, block.timestamp);
}
/**
* @dev 提款函数 - 提取 ETH
* @param amount 要提取的金额(单位:wei)
*/
function withdraw(uint256 amount) public {
require(amount > 0, "Withdrawal amount must be greater than 0");
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
totalWithdrawals[msg.sender] += amount;
// 转账 ETH 给用户
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
emit Withdraw(msg.sender, amount, block.timestamp);
}
/**
* @dev 提取全部余额
*/
function withdrawAll() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance to withdraw");
withdraw(amount);
}
/**
* @dev 查询用户余额
* @param user 用户地址
* @return 用户余额
*/
function getBalance(address user) public view returns (uint256) {
return balances[user];
}
/**
* @dev 查询合约总余额
* @return 合约总余额
*/
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
/**
* @dev 接收 ETH 的回退函数(也可以用于充值)
*/
receive() external payable {
deposit();
}
/**
* @dev 回退函数
*/
fallback() external payable {
deposit();
}
}
部署代码
const hre = require("hardhat");
// 重试函数
async function retryOperation(operation, maxRetries = 3, delay = 5000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`\n⚠️ 尝试 ${i + 1}/${maxRetries} 失败,${delay / 1000} 秒后重试...`);
console.log(`错误信息: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 测试 RPC 连接
async function testRpcConnection() {
const networkName = hre.network.name;
const currentUrl = hre.network.config.url;
console.log("🔍 测试 RPC 连接...");
console.log(` 当前 RPC: ${currentUrl}`);
try {
// 设置超时
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error("连接超时(30秒)")), 30000)
);
const blockNumberPromise = hre.ethers.provider.getBlockNumber();
const blockNumber = await Promise.race([blockNumberPromise, timeoutPromise]);
console.log(`✅ RPC 连接成功,当前区块: ${blockNumber}`);
return true;
} catch (error) {
console.error(`❌ RPC 连接失败: ${error.message}`);
// 提供备用 RPC URL 建议
const sepoliaAlternatives = [
"https://ethereum-sepolia-rpc.publicnode.com",
"https://sepolia.drpc.org",
"https://rpc2.sepolia.org"
];
const goerliAlternatives = [
"https://ethereum-goerli-rpc.publicnode.com",
"https://rpc.ankr.com/eth_goerli"
];
const alternatives = networkName === "sepolia" ? sepoliaAlternatives : goerliAlternatives;
console.error("\n💡 建议尝试以下操作:");
console.error(" 1. 检查网络连接");
console.error(" 2. 在 .env 文件中设置备用 RPC URL:");
if (networkName === "sepolia") {
console.error(` SEPOLIA_RPC_URL=${alternatives[0]}`);
} else if (networkName === "goerli") {
console.error(` GOERLI_RPC_URL=${alternatives[0]}`);
}
console.error(" 3. 或使用 Infura/Alchemy 的 RPC URL(更稳定)");
console.error(" - Infura: https://infura.io (免费注册)");
console.error(" - Alchemy: https://alchemy.com (免费注册)");
throw new Error("无法连接到 RPC 节点");
}
}
async function main() {
console.log("🚀 开始部署合约...\n");
// 测试 RPC 连接
await testRpcConnection();
// 获取部署账户
const [deployer] = await hre.ethers.getSigners();
console.log("📝 部署账户地址:", deployer.address);
// 检查账户余额
let balance;
try {
balance = await hre.ethers.provider.getBalance(deployer.address);
console.log("💰 账户余额:", hre.ethers.formatEther(balance), "ETH");
if (balance < hre.ethers.parseEther("0.01")) {
console.warn("⚠️ 警告: 账户余额可能不足以支付 gas 费用");
console.warn(" 建议至少准备 0.01 ETH 用于部署");
}
} catch (error) {
console.error("❌ 无法获取账户余额:", error.message);
throw error;
}
// 部署合约(带重试机制)
console.log("\n📦 正在部署 DepositWithdraw 合约...");
let contract;
let contractAddress;
try {
const DepositWithdraw = await hre.ethers.getContractFactory("DepositWithdraw");
// 使用重试机制部署
const deploymentResult = await retryOperation(async () => {
console.log(" 发送部署交易...");
const contractInstance = await DepositWithdraw.deploy();
console.log(" 等待部署确认...");
await contractInstance.waitForDeployment();
return contractInstance;
}, 3, 10000); // 最多重试 3 次,每次间隔 10 秒
contract = deploymentResult;
contractAddress = await contract.getAddress();
console.log("✅ 合约部署成功!");
console.log("📍 合约地址:", contractAddress);
console.log("🌐 部署网络:", hre.network.name);
} catch (error) {
console.error("\n❌ 部署失败:", error.message);
if (error.message.includes("timeout") || error.message.includes("Headers Timeout")) {
console.error("\n💡 建议:");
console.error(" 1. 检查网络连接");
console.error(" 2. 尝试更换 RPC URL(在 .env 文件中设置 SEPOLIA_RPC_URL)");
console.error(" 3. 稍后重试");
}
throw error;
}
// 等待区块确认
if (hre.network.name !== "hardhat") {
try {
console.log("\n⏳ 等待区块确认...");
const deploymentTx = contract.deploymentTransaction();
if (deploymentTx) {
await deploymentTx.wait(2); // 等待 2 个区块确认
console.log("✅ 已确认 2 个区块");
}
} catch (error) {
console.warn("⚠️ 等待确认时出错(合约可能已部署):", error.message);
}
}
// 验证合约信息
try {
console.log("\n🔍 验证合约信息...");
const owner = await contract.owner();
console.log("👤 合约所有者:", owner);
const contractBalance = await hre.ethers.provider.getBalance(contractAddress);
console.log("💵 合约余额:", hre.ethers.formatEther(contractBalance), "ETH");
} catch (error) {
console.warn("⚠️ 验证合约信息时出错:", error.message);
}
// 输出部署摘要
console.log("\n" + "=".repeat(60));
console.log("📋 部署信息摘要");
console.log("=".repeat(60));
console.log("合约地址:", contractAddress);
console.log("网络:", hre.network.name);
console.log("部署账户:", deployer.address);
console.log("=".repeat(60));
// 如果在测试网,提供 Etherscan 链接
if (hre.network.name === "sepolia") {
console.log("\n🔍 在 Etherscan 查看合约:");
console.log(` https://sepolia.etherscan.io/address/${contractAddress}`);
} else if (hre.network.name === "goerli") {
console.log("\n🔍 在 Etherscan 查看合约:");
console.log(` https://goerli.etherscan.io/address/${contractAddress}`);
}
console.log("\n✅ 部署完成!");
console.log("\n💡 下一步:");
console.log(" 1. 保存合约地址:", contractAddress);
console.log(" 2. 运行测试脚本: npx hardhat run scripts/test.js --network " + hre.network.name);
console.log(" 3. 在 Etherscan 上查看合约详情");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error("\n❌ 部署失败:", error.message);
if (error.stack) {
console.error("\n详细错误信息:");
console.error(error.stack);
}
process.exit(1);
});
测试代码
const hre = require("hardhat");
async function main() {
// ⚠️ 请替换为你的合约地址
const contractAddress = process.env.CONTRACT_ADDRESS || "0xef610Fc6C8eF7f42B387E9e0961A4790563a3416";
if (contractAddress === "YOUR_CONTRACT_ADDRESS_HERE") {
console.error("❌ 请先设置合约地址!");
console.log("方法 1: 设置环境变量 CONTRACT_ADDRESS");
console.log("方法 2: 编辑此文件,替换 contractAddress 变量");
process.exit(1);
}
const [signer] = await hre.ethers.getSigners();
console.log("当前账户:", signer.address);
// 检查账户余额
const accountBalance = await hre.ethers.provider.getBalance(signer.address);
console.log("账户余额:", hre.ethers.formatEther(accountBalance), "ETH");
const DepositWithdraw = await hre.ethers.getContractFactory("DepositWithdraw");
const contract = DepositWithdraw.attach(contractAddress);
console.log("\n连接到合约:", contractAddress);
// 查询当前余额
const currentBalance = await contract.getBalance(signer.address);
console.log("合约中的余额:", hre.ethers.formatEther(currentBalance), "ETH");
// 充值测试
console.log("\n📥 测试充值功能...");
const depositAmount = hre.ethers.parseEther("0.01");
console.log("充值金额: 0.01 ETH");
try {
const depositTx = await contract.deposit({ value: depositAmount });
console.log("交易哈希:", depositTx.hash);
await depositTx.wait();
console.log("✅ 充值成功!");
// 查询新余额
const newBalance = await contract.getBalance(signer.address);
console.log("充值后余额:", hre.ethers.formatEther(newBalance), "ETH");
// 提款测试
console.log("\n📤 测试提款功能...");
const withdrawAmount = hre.ethers.parseEther("0.005");
console.log("提款金额: 0.005 ETH");
const withdrawTx = await contract.withdraw(withdrawAmount);
console.log("交易哈希:", withdrawTx.hash);
await withdrawTx.wait();
console.log("✅ 提款成功!");
// 查询最终余额
const finalBalance = await contract.getBalance(signer.address);
console.log("提款后余额:", hre.ethers.formatEther(finalBalance), "ETH");
// 查询合约总余额
const contractBalance = await contract.getContractBalance();
console.log("合约总余额:", hre.ethers.formatEther(contractBalance), "ETH");
// 查询总存款和总提款
const totalDeposits = await contract.totalDeposits(signer.address);
const totalWithdrawals = await contract.totalWithdrawals(signer.address);
console.log("\n📊 统计信息:");
console.log("总存款:", hre.ethers.formatEther(totalDeposits), "ETH");
console.log("总提款:", hre.ethers.formatEther(totalWithdrawals), "ETH");
} catch (error) {
console.error("❌ 操作失败:", error.message);
if (error.message.includes("insufficient funds")) {
console.log("💡 提示: 账户余额不足,请确保有足够的 ETH 支付 gas 费用");
}
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
部署结果
> deposit-withdraw-contract@1.0.0 deploy:sepolia
> hardhat run scripts/deploy.js --network sepolia
WARNING: You are currently using Node.js v25.4.0, which is not supported by Hardhat. This can lead to unexpected behavior. See https://v2.hardhat.org/nodejs-versions
🚀 开始部署合约...
🔍 测试 RPC 连接...
当前 RPC: https://ethereum-sepolia-rpc.publicnode.com
✅ RPC 连接成功,当前区块: 10156419
📝 部署账户地址: 0x41a886A528bBD09A4Fe0ED51e345781dc4D087c6
💰 账户余额: 0.05 ETH
📦 正在部署 DepositWithdraw 合约...
发送部署交易...
等待部署确认...
✅ 合约部署成功!
📍 合约地址: 0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
🌐 部署网络: sepolia
⏳ 等待区块确认...
✅ 已确认 2 个区块
🔍 验证合约信息...
👤 合约所有者: 0x41a886A528bBD09A4Fe0ED51e345781dc4D087c6
💵 合约余额: 0.0 ETH
============================================================
📋 部署信息摘要
============================================================
合约地址: 0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
网络: sepolia
部署账户: 0x41a886A528bBD09A4Fe0ED51e345781dc4D087c6
============================================================
🔍 在 Etherscan 查看合约:
https://sepolia.etherscan.io/address/0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
✅ 部署完成!
💡 下一步:
1. 保存合约地址: 0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
2. 运行测试脚本: npx hardhat run scripts/test.js --network sepolia
3. 在 Etherscan 上查看合约详情

测试结果
WARNING: You are currently using Node.js v25.4.0, which is not supported by Hardhat. This can lead to unexpected behavior. See https://v2.hardhat.org/nodejs-versions
当前账户: 0x41a886A528bBD09A4Fe0ED51e345781dc4D087c6
账户余额: 0.04949953127704832 ETH
连接到合约: 0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
合约中的余额: 0.0 ETH
📥 测试充值功能...
充值金额: 0.01 ETH
交易哈希: 0x54c78226f1ec407adbb8d7ea44bf2d9bb74ae39349394a342752a8bdad43aee9
✅ 充值成功!
充值后余额: 0.01 ETH
📤 测试提款功能...
提款金额: 0.005 ETH
交易哈希: 0x99162845b86d95facfb2130f475abb0bf8487a2f0709e490472f0302ff2e6bf8
✅ 提款成功!
提款后余额: 0.005 ETH
合约总余额: 0.005 ETH
📊 统计信息:
总存款: 0.01 ETH
总提款: 0.005 ETH

部署readme
# 🎉 合约部署成功!
## ✅ 部署信息
- **合约地址**: `0xef610Fc6C8eF7f42B387E9e0961A4790563a3416`
- **网络**: Sepolia 测试网
- **部署账户**: `0x41a886A528bBD09A4Fe0ED51e345781dc4D087c6`
- **Etherscan**: https://sepolia.etherscan.io/address/0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
## 🧪 测试结果
✅ **充值功能**: 成功充值 0.01 ETH
✅ **提款功能**: 成功提款 0.005 ETH
✅ **余额查询**: 正常工作
## 🚀 如何使用合约
### 方法 1: 使用测试脚本(最简单)
```bash
npx hardhat run scripts/test.js --network sepolia
方法 2: 使用 Hardhat Console(交互式)
npx hardhat console --network sepolia
然后在控制台中:
// 连接到合约
const contractAddress = "0xef610Fc6C8eF7f42B387E9e0961A4790563a3416";
const DepositWithdraw = await ethers.getContractFactory("DepositWithdraw");
const contract = DepositWithdraw.attach(contractAddress);
// 查看当前账户
const [signer] = await ethers.getSigners();
console.log("账户:", signer.address);
// 充值 0.1 ETH
await contract.deposit({ value: ethers.parseEther("0.1") });
// 查询余额
const balance = await contract.getBalance(signer.address);
console.log("余额:", ethers.formatEther(balance), "ETH");
// 提款 0.05 ETH
await contract.withdraw(ethers.parseEther("0.05"));
// 提取全部余额
await contract.withdrawAll();
方法 3: 使用 Etherscan(可视化界面)
- 访问: https://sepolia.etherscan.io/address/0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
- 点击 “Contract” 标签
- 点击 “Write Contract” 或 “Read Contract”
- 连接你的 MetaMask 钱包(确保切换到 Sepolia 网络)
- 使用以下功能:
- 充值: 在 “Write Contract” 中找到
deposit函数,点击 “Write”,在 MetaMask 中输入金额 - 查询余额: 在 “Read Contract” 中找到
getBalance函数,输入你的地址 - 提款: 在 “Write Contract” 中找到
withdraw函数,输入金额(单位:wei)
- 充值: 在 “Write Contract” 中找到
方法 4: 直接发送 ETH 到合约地址
你也可以直接向合约地址发送 ETH,这会自动触发充值:
- 打开 MetaMask
- 确保切换到 Sepolia 测试网
- 点击 “发送”
- 粘贴合约地址:
0xef610Fc6C8eF7f42B387E9e0961A4790563a3416 - 输入要充值的金额
- 确认交易
📋 合约函数说明
充值函数
deposit()- 充值 ETH 到合约- 也可以直接向合约地址发送 ETH(会自动调用 deposit)
提款函数
withdraw(uint256 amount)- 提取指定数量的 ETH(单位:wei)withdrawAll()- 提取全部余额
查询函数
getBalance(address user)- 查询指定用户的余额getContractBalance()- 查询合约总余额balances(address)- 查询用户余额(public mapping)totalDeposits(address)- 查询用户总存款金额totalWithdrawals(address)- 查询用户总提款金额
💡 金额转换
- 1 ETH = 1,000,000,000,000,000,000 wei (10^18)
- 0.1 ETH = 100,000,000,000,000,000 wei
- 0.01 ETH = 10,000,000,000,000,000 wei
在 Hardhat Console 中使用:
ethers.parseEther("0.1") // 将 0.1 ETH 转换为 wei
ethers.formatEther(balance) // 将 wei 转换为 ETH
🔍 查看交易记录
所有交易都可以在 Etherscan 上查看:
- 合约地址: https://sepolia.etherscan.io/address/0xef610Fc6C8eF7f42B387E9e0961A4790563a3416
- 你的账户: https://sepolia.etherscan.io/address/你的地址
⚠️ 注意事项
- 测试网 ETH: 确保你的账户有足够的 Sepolia 测试网 ETH
- Gas 费用: 每次操作都需要支付 gas 费用
- 余额检查: 提款前确保合约中有足够的余额
- 网络选择: 确保 MetaMask 连接到 Sepolia 测试网
🎯 下一步
现在你可以:
- ✅ 测试充值功能
- ✅ 测试提款功能
- ✅ 查看交易记录
- ✅ 在 Etherscan 上查看合约详情
祝你使用愉快!🚀
评论区