合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/**
* @title DepositWithdraw
* @dev 以太坊主网出入金合约
* 功能:
* - 用户可以向合约充值ETH
* - 合约所有者可以提取ETH
* - 支持查询余额和充值记录
*/
contract DepositWithdraw is Ownable, ReentrancyGuard {
// 充值事件
event Deposit(address indexed user, uint256 amount, uint256 timestamp);
// 提款事件
event Withdraw(address indexed to, uint256 amount, uint256 timestamp);
// 用户充值记录
mapping(address => uint256) public deposits;
// 用户充值次数
mapping(address => uint256) public depositCount;
// 总充值金额
uint256 public totalDeposits;
// 总提款金额
uint256 public totalWithdraws;
// 最小充值金额(可选,设置为0表示无限制)
uint256 public minDepositAmount;
// 最大单次提款金额(可选,设置为0表示无限制)
uint256 public maxWithdrawAmount;
/**
* @dev 构造函数
* @param _minDepositAmount 最小充值金额(wei)
* @param _maxWithdrawAmount 最大单次提款金额(wei)
*/
constructor(uint256 _minDepositAmount, uint256 _maxWithdrawAmount) Ownable(msg.sender) {
minDepositAmount = _minDepositAmount;
maxWithdrawAmount = _maxWithdrawAmount;
}
/**
* @dev 充值函数,接收ETH
*/
function deposit() external payable nonReentrant {
require(msg.value > 0, "Deposit amount must be greater than 0");
if (minDepositAmount > 0) {
require(msg.value >= minDepositAmount, "Deposit amount is below minimum");
}
deposits[msg.sender] += msg.value;
depositCount[msg.sender] += 1;
totalDeposits += msg.value;
emit Deposit(msg.sender, msg.value, block.timestamp);
}
/**
* @dev 合约所有者提取ETH
* @param to 接收地址
* @param amount 提取金额(wei)
*/
function withdraw(address payable to, uint256 amount) external onlyOwner nonReentrant {
require(to != address(0), "Invalid address");
require(amount > 0, "Withdraw amount must be greater than 0");
require(address(this).balance >= amount, "Insufficient contract balance");
if (maxWithdrawAmount > 0) {
require(amount <= maxWithdrawAmount, "Withdraw amount exceeds maximum");
}
totalWithdraws += amount;
(bool success, ) = to.call{value: amount}("");
require(success, "Withdraw failed");
emit Withdraw(to, amount, block.timestamp);
}
/**
* @dev 批量提取ETH(仅所有者)
* @param recipients 接收地址数组
* @param amounts 提取金额数组(wei)
*/
function batchWithdraw(address payable[] calldata recipients, uint256[] calldata amounts)
external
onlyOwner
nonReentrant
{
require(recipients.length == amounts.length, "Arrays length mismatch");
require(recipients.length > 0, "Empty arrays");
uint256 totalAmount = 0;
for (uint256 i = 0; i < amounts.length; i++) {
totalAmount += amounts[i];
}
require(address(this).balance >= totalAmount, "Insufficient contract balance");
for (uint256 i = 0; i < recipients.length; i++) {
require(recipients[i] != address(0), "Invalid address");
require(amounts[i] > 0, "Amount must be greater than 0");
(bool success, ) = recipients[i].call{value: amounts[i]}("");
require(success, "Batch withdraw failed");
emit Withdraw(recipients[i], amounts[i], block.timestamp);
}
totalWithdraws += totalAmount;
}
/**
* @dev 查询用户总充值金额
* @param user 用户地址
* @return 总充值金额(wei)
*/
function getUserDeposit(address user) external view returns (uint256) {
return deposits[user];
}
/**
* @dev 查询合约总余额
* @return 合约余额(wei)
*/
function getContractBalance() external view returns (uint256) {
return address(this).balance;
}
/**
* @dev 更新最小充值金额(仅所有者)
* @param _minDepositAmount 新的最小充值金额(wei)
*/
function setMinDepositAmount(uint256 _minDepositAmount) external onlyOwner {
minDepositAmount = _minDepositAmount;
}
/**
* @dev 更新最大提款金额(仅所有者)
* @param _maxWithdrawAmount 新的最大提款金额(wei)
*/
function setMaxWithdrawAmount(uint256 _maxWithdrawAmount) external onlyOwner {
maxWithdrawAmount = _maxWithdrawAmount;
}
/**
* @dev 接收ETH的回退函数
*/
receive() external payable {
deposit();
}
/**
* @dev 回退函数
*/
fallback() external payable {
deposit();
}
}
入金
6f2190e792ddc11b3d4d63d9e1930cf deposit 0.01
操作账户: 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf
合约地址: 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf
操作: deposit
网络: mainnet
正在充值 0.01 APT (1000000 octas)...
交易哈希: 0x4a2b762b65fa26a337ca5da5fad16e6cb8690b347872546afb6cb430fd746967
✅ 充值成功!
区块链浏览器中的方法是deposit

出金
✅ 出金操作成功!
交易信息:
交易哈希: 0x859dac290fd7a6e29afb7c6ed7801bef639dbf5776236b97ed9b3da1d79f3d0d
提取金额: 0.005 APT
接收地址: 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf
区块链浏览器中的方法是withdraw

主网合约部署,入金,合约地址出金
# Aptos链出入金合约
这是一个用于Aptos链的出入金智能合约,支持用户充值和合约所有者提款功能。
## 功能特性
- ✅ **APT 充值功能**: 用户可以向合约充值APT
- ✅ **APT 提款功能**: 合约所有者可以提取APT
- ✅ **通用代币功能**: 支持任意代币类型(包括自定义USDT),使用泛型实现
- ✅ **灵活配置**: 不依赖官方代币地址,支持您自己部署的代币合约
- ✅ **批量提款**: 支持批量提取到多个地址
- ✅ **安全保护**: Move语言内置安全特性
- ✅ **权限控制**: 只有所有者可以提款
- ✅ **事件记录**: 记录所有充值和提款事件
- ✅ **查询功能**: 可以查询用户充值金额和合约余额
## 合约参数
### APT 参数
- **最小充值金额**: 1 octas (0.00000001 APT)
- **最小提款金额**: 1 octas (0.00000001 APT)
### 通用代币参数(包括USDT等)
- **最小充值金额**: 1 最小单位
- **最小提款金额**: 1 最小单位
⚠️ **重要提示**:
1. 在 Aptos 链上,所有交易的 gas 费用必须使用 APT 支付。即使您只有其他代币,也需要少量 APT 来支付 gas 费用。
2. 合约使用泛型设计,**不依赖官方代币地址**,支持您自己部署的代币合约。
3. 使用代币功能时,需要提供完整的代币类型路径,例如:`0x您的地址::模块名::代币类型名`
## 前置要求
1. **安装Aptos CLI**
```bash
# macOS
brew install aptos
# 或使用脚本安装
curl -fsSL "https://aptos.dev/scripts/install_cli.py" | python3
- 安装Node.js依赖
npm install
配置环境变量
- 复制
.env.example为.env - 填写以下配置:
# Aptos网络(mainnet/testnet/devnet)
NETWORK=mainnet
# 部署账户私钥(必须)
PRIVATE_KEY=0x你的私钥
获取私钥
你可以使用Aptos CLI生成新账户:
aptos key generate --output-file key.json
然后从生成的 key.json 文件中获取私钥。
编译合约
npm run compile
或者直接使用Aptos CLI:
aptos move compile
部署合约
部署到主网
npm run deploy:mainnet
部署到测试网
npm run deploy:testnet
部署到开发网
npm run deploy:devnet
测试合约
运行测试脚本
测试脚本会自动执行入金和出金操作,并验证结果:
# 使用npm脚本
npm run test:contract <合约地址>
# 或直接使用node
node scripts/test.js <合约地址>
测试脚本会:
- ✅ 检查初始状态(账户余额、合约余额、用户充值记录)
- ✅ 执行入金操作(充值 0.01 APT)
- ✅ 验证入金结果(检查余额变化)
- ✅ 执行出金操作(提取 0.005 APT)
- ✅ 验证出金结果(检查余额变化)
- ✅ 显示测试总结
注意:
- 确保账户有足够的APT支付测试费用(至少 0.02 APT + gas费用)
- 测试账户必须是合约所有者才能执行出金操作
- 测试会在主网/测试网/开发网上执行,取决于
.env中的NETWORK配置
合约使用
充值APT
用户可以通过调用 deposit 函数充值:
// 使用交互脚本
node scripts/interact.js <合约地址> deposit 1.0
或者使用Aptos CLI:
aptos move run \
--function-id <合约地址>::deposit_withdraw::deposit \
--args u64:100000000 \
--private-key-file key.json
充值代币(支持任意代币,包括自定义USDT)
用户可以通过调用 deposit_coin 函数充值任意代币:
// 使用交互脚本
// 格式: depositCoin <代币类型> <金额>
// 示例:使用您部署的USDT合约
node scripts/interact.js <合约地址> depositCoin 0x您的地址::usdt::USDT 100.0
或者使用Aptos CLI:
aptos move run \
--function-id <合约地址>::deposit_withdraw::deposit_coin \
--type-args <代币类型完整路径> \
--args u64:100000000 \
--private-key-file key.json
⚠️ 注意:
- 代币类型需要提供完整路径,格式:
<地址>::<模块名>::<类型名> - 例如:
0x123::usdt::USDT(使用您部署的USDT合约地址) - 金额根据代币的小数位数计算(USDT通常使用6位小数)
提款APT(仅所有者)
// 使用交互脚本
node scripts/interact.js <合约地址> withdraw <接收地址> 1.0
提款代币(仅所有者,支持任意代币)
// 使用交互脚本
// 格式: withdrawCoin <代币类型> <接收地址> <金额>
node scripts/interact.js <合约地址> withdrawCoin 0x您的地址::usdt::USDT <接收地址> 100.0
查询功能
// 查询合约 APT 余额
node scripts/interact.js <合约地址> balance
// 查询合约代币余额(支持任意代币)
// 格式: balanceCoin <代币类型>
node scripts/interact.js <合约地址> balanceCoin 0x您的地址::usdt::USDT
// 查询用户 APT 充值金额
node scripts/interact.js <合约地址> userDeposit <用户地址>
合约函数
公共函数 (APT)
deposit(amount: u64)- 充值APT到合约get_user_deposit(user: address): u64- 查询用户APT充值金额get_user_deposit_count(user: address): u64- 查询用户APT充值次数get_contract_balance(): u64- 查询合约APT余额get_total_deposits(): u64- 查询总充值金额get_total_withdraws(): u64- 查询总提款金额get_owner(): address- 查询合约所有者get_min_deposit_amount(): u64- 查询最小APT充值金额get_min_withdraw_amount(): u64- 查询最小APT提款金额
公共函数 (通用代币,支持任意代币类型)
deposit_coin<CoinType>(amount: u64)- 充值代币到合约(CoinType为代币类型)withdraw_coin<CoinType>(to: address, amount: u64)- 提取代币到指定地址(仅所有者)batch_withdraw_coin<CoinType>(recipients: vector<address>, amounts: vector<u64>)- 批量提取代币get_contract_balance_coin<CoinType>(): u64- 查询合约代币余额get_min_deposit_amount_coin(): u64- 查询最小代币充值金额get_min_withdraw_amount_coin(): u64- 查询最小代币提款金额
⚠️ 注意: 这些函数使用泛型类型参数 <CoinType>,可以支持任意代币类型,包括您自己部署的USDT合约。
仅所有者函数
withdraw(to: address, amount: u64)- 提取APT到指定地址withdraw_coin<CoinType>(to: address, amount: u64)- 提取代币到指定地址(支持任意代币类型)batch_withdraw(recipients: vector<address>, amounts: vector<u64>)- 批量提取APTbatch_withdraw_coin<CoinType>(recipients: vector<address>, amounts: vector<u64>)- 批量提取代币
使用Aptos CLI交互
查看合约资源
aptos account list --account <合约地址>
调用函数
# 充值 (示例: 0.01 APT = 1000000 octas)
aptos move run \
--function-id <合约地址>::deposit_withdraw::deposit \
--args u64:1000000 \
--private-key-file key.json
# 提款(仅所有者)(示例: 0.01 APT = 1000000 octas)
aptos move run \
--function-id <合约地址>::deposit_withdraw::withdraw \
--args address:<接收地址> u64:1000000 \
--private-key-file key.json
查看函数
# 查询用户充值金额
aptos move view \
--function-id <合约地址>::deposit_withdraw::get_user_deposit \
--args address:<用户地址>
网络信息
主网 (Mainnet)
- RPC URL:
https://fullnode.mainnet.aptoslabs.com/v1 - Explorer: https://explorer.aptoslabs.com/?network=mainnet
- Chain ID: 1
测试网 (Testnet)
- RPC URL:
https://fullnode.testnet.aptoslabs.com/v1 - Faucet: https://faucet.testnet.aptoslabs.com
- Explorer: https://explorer.aptoslabs.com/?network=testnet
开发网 (Devnet)
- RPC URL:
https://fullnode.devnet.aptoslabs.com/v1 - Faucet: https://faucet.devnet.aptoslabs.com
- Explorer: https://explorer.aptoslabs.com/?network=devnet
安全注意事项
⚠️ 重要提示:
- 私钥安全: 永远不要将私钥提交到代码仓库
- 测试先行: 建议先在测试网或开发网部署和测试
- Gas费用: 确保账户有足够的APT支付gas费用(通常很少)
- ⚠️ 即使您只有USDT,也需要少量APT来支付gas费用
- 建议至少保留 0.01 APT 用于支付交易费用
- 权限管理: 合约所有者拥有提款权限,请妥善保管私钥
- 金额限制:
- APT最小充值: 1 octas (0.00000001 APT)
- APT最小提款: 1 octas (0.00000001 APT)
- 代币最小充值: 1 最小单位
- 代币最小提款: 1 最小单位
- 代币类型: 合约使用泛型设计,不依赖官方代币地址
- 支持任意代币类型,包括您自己部署的USDT合约
- 使用时需要提供完整的代币类型路径:
<地址>::<模块名>::<类型名> - 示例:
0x您的地址::usdt::USDT
常见问题
1. 编译错误
如果遇到编译错误,确保:
- 已安装Aptos CLI
- Move.toml中的地址配置正确
- 依赖项版本正确
2. 部署失败
检查:
- 账户余额是否足够支付gas
- 私钥格式是否正确(0x开头)
- 网络连接是否正常
3. 函数调用失败
确保:
- 调用者权限正确(提款需要所有者)
- 金额满足最小限制
- 合约地址正确
如何调用
# 如何给合约充值
## 重要说明
虽然合约地址和钱包地址相同(`0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf`),但这是**正常的**!
在Aptos中:
- 合约模块部署在账户地址下
- 合约地址 = 部署账户地址
- 但合约有**独立的资源存储**
## 余额的区别
### 1. 钱包账户余额
- 这是您个人账户的APT余额
- 存储在账户的 `CoinStore` 资源中
- 可以直接转账使用
### 2. 合约余额
- 这是合约管理的APT余额
- 也存储在 `CoinStore` 资源中,但是合约的资源
- 只能通过合约函数操作
## 如何给合约充值
### 方法一:使用交互脚本(推荐)
```bash
# 充值 0.01 APT
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf deposit 0.01
# 充值 1 APT
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf deposit 1.0
方法二:使用Aptos CLI
# 充值 0.01 APT (1000000 octas)
aptos move run \
--function-id 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf::deposit_withdraw::deposit \
--args u64:1000000 \
--private-key-file key.json
充值流程
当您调用 deposit 函数时:
-
从您的账户提取APT
- 从您的钱包账户的
CoinStore中提取指定金额
- 从您的钱包账户的
-
存入合约
- 将APT存入合约地址的
CoinStore资源中 - 这是合约管理的资源,不是您个人账户的资源
- 将APT存入合约地址的
-
更新记录
- 更新您的充值记录(
UserDeposit资源) - 更新合约的总充值金额
- 更新您的充值记录(
查询余额
查询钱包账户余额
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf balance
查询合约余额
# 同样使用balance命令,它会查询合约地址的CoinStore
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf balance
查询您的充值记录
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf userDeposit 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf
重要提示
⚠️ 不要直接转账到合约地址!
- 直接转账到地址只会增加账户余额,不会触发合约逻辑
- 必须通过调用
deposit函数来充值 - 这样合约才能正确记录您的充值并更新状态
示例
# 1. 先查询钱包余额
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf balance
# 2. 给合约充值 0.01 APT
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf deposit 0.01
# 3. 再次查询合约余额(应该增加了)
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf balance
# 4. 查询您的充值记录
node scripts/interact.js 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf userDeposit 0xf711b12d67879da3584c636c6a7125cac6f2190e792ddc11b3d4d63d9e1930cf
评论区