一、说明
在dapp开发过程中经常会遇到ETH,BNB,Matic,ARB,OP,USDT等代币充值后自动归集的问题。充值WETH和USDT的归集方式是不同的,虽然都得采用特定的充提币接口。主要因为每个公链都可以自动识别本链上的WETH主币,但是却不能识别其他ERC20代币,比如USDT作为ERC20代币如果直接充值到充币接口中是不能被合约识别并自动归集的。
dapp充币接口设计主要考虑三方面的因素:
- 用户通过充币接口完成WETH或者USDT的充值后,需要自动实时的归集到归集钱包地址,并且最大可能的降低归集gas费用。
- 用户直接向合约地址充值WETH或者usdt后,dapp需要能正常识别出每个地址的充值数量,然后将充值金额同步链上数据保存到数据库中。
- WETH或者usdt充值和归集两个步骤最大化的降低交易gas费用。同时,避免小额gas费用残留问题。
二、充提币及自动归集接口合约代码实现方式
针对每个公链上的主币WETH和ERC20代币,充提币及自动归集接口实现方式是不同的。充币及自动归集接口相对要简单,提币接口需要根据所属公链的要求以及dapp程序设置中的业务要求确定采用的实现方式,常规的提币接口为签名验签withdraw和直接claim赎回方式。
1. 充值ERC20(USDT为例)及自动归集的接口实现方式源码:
function collect(address collector, uint256 collCount) external onlyOwner returns (bool, address, uint256) {
require(isCollector(collector), "COLL: invalid collector");
address collAdr = collector;
uint256 collNum = collCount;
uint256 tokenBalance = BUSD.balanceOf(collAdr);
if(collNum > tokenBalance) {
collNum = tokenBalance;
}
if (tokenBalance > 0) {
BUSD.transferFrom(collAdr, rec, collNum);
historyReceords.push(Receord({depositer: collAdr, amount: collNum}));
uint256 counter = historyReceords.length - 1;
indexs[collAdr].push(counter);
}
emit Collect(collAdr, collNum);
return(true, collAdr, collNum);
}
function collectAll(address collector) external onlyOwner returns (bool, address, uint256) {
require(isCollector(collector), "COLL: invalid collector");
address collAdr = collector;
uint256 tokenBalance = BUSD.balanceOf(collAdr);
if (tokenBalance > 0) {
BUSD.transferFrom(collAdr, rec, tokenBalance);
historyReceords.push(Receord({depositer: collAdr, amount: tokenBalance}));
uint256 counter = historyReceords.length - 1;
indexs[collAdr].push(counter);
}
emit CollectAll(collAdr, tokenBalance);
return(true, collAdr, tokenBalance);
}
2. 签名验签方式的提币接口实现代码如下:
function withdraw(
uint256 amount,
address to,
uint256 nonce,
bytes memory signature
) external {
require(nonce > userNonce[to], "nonce");
require(
verify(signer, amount, to, nonce, signature),
"signature not match"
);
record[to] = UserWithdraw({
amount: amount + record[to].amount,
lastRewardTime: block.timestamp
});
userNonce[to] = nonce + 1;
DWT.transfer(to, amount);
emit Withdraw(address(this), to, amount, block.timestamp);
}
由dapp前端使用系统钱包私钥对提币数据进行签名,将签名消息发送到链端,由链端对签名消息进行verify拆分出签名的公钥地址,最终验证签名的公钥地址为系统内置钱包地址,说明提币请求是来自于dapp的合法提币请求,而不是非法的模拟提币请求。
3. 通过claim方式统一将充提币数据完全交由链端处理的实现方式:
function claim(address currency,uint256 _amount) public {
require(tokenAmount[currency][msg.sender] >= _amount, "Claim: not balance");
require(claimFrozen[msg.sender] ==0, "Claim: userAmount must > 0");
Config memory config =configMap[currency];
require(config.minAmount <= _amount&&config.maxAmount >= _amount, "Claim: _amount error!");
require(config.status==0, "Claim: status is error!");
uint256 fee=0;
uint256 reserveAmount=0;
if(currency==WETH){
reserveAmount= SafeMath.div(SafeMath.mul(_amount,MineDetail[withdrawRate[msg.sender]].rate),100);
uint256 amount=SafeMath.sub(_amount,reserveAmount);
fee =SafeMath.div(SafeMath.mul(amount,config.fee),100);
uint256 realamount=SafeMath.sub(amount,fee);
safeTransferETH(msg.sender, realamount);
}else{
fee =SafeMath.div(SafeMath.mul(_amount,config.fee),100);
uint256 realamount=SafeMath.sub(_amount,fee);
safeTransferToken(currency,address(msg.sender), realamount);
}
tokenAmount[currency][msg.sender] = tokenAmount[currency][msg.sender].sub(_amount);
emit Claim(currency,msg.sender, _amount,reserveAmount,fee);
}
该方式支持WETH,ERC20(usdt)方式任意代币的充提要求。完全由链端处理代币的充提,dapp所有数据都需要实时的与链端进行同步保存到数据库中。该种方式虽然有限的规避了签名验签带来的繁琐校验要求,却增加了本地dapp数据库与链端数据频繁同步的业务负载。
三、WETH及USDT充币接口自动归集降低gas费用的几种实现方式
方法1. ETH 由充值的人支付归集矿工费,交易平台完全不用花矿工费
如图一的简图,藉由智能合约中的fallback 功能,让所有充值到合约地址上的ETH 自动转移到指定地址,也就是由充值的客户来支付矿工费的意思,在这个方法中,我们需要给每个用户部署一个智能合约,智能合约地址就是用户的充值地址,所有打进这个地址的交易,都会自动触发转帐到另一个钱包。由于充值跟归帐的行为是在一次交易中发生的,因此在一笔交易中,我们就完成了充值跟归集,矿工费会由发起充值交易的对象来支付。
图一
方法2. ERC20 代币合约中提供ABI,让呼叫者支付归帐矿工费,节省水池打燃料给充值地址的矿工费,同时避免小额燃料费残留问题
ETH 代币中最知名的ERC20 代币当属USDT,参考USDT-ERC20 合约地址可以看到USDT-ERC20 的所有交易,由于USDT 的交易其实是对发行USDT 的合约进行交易,在交易中会让USDT合约把登记在合约中A 地址的USDT 转移登记到B 地址底下,这是合约里的固定行为,在无法修改合约内容的情况下,我们无法像第一个方法中,让充值的人支付归集矿工费。
方法3. ETH 或ERC20 代币可自动化设定矿工费低点进行归帐
由于不是每个人都知道怎么撰写智能合约,同时部署智能合约也需要成本,因此针对ETH 原生地址,你也可以使用一些方法来节省矿工费,如果将ETH 矿工费历史纪录翻开,可以发现即便是矿工费最高(500-600 gwei) 的那段时间,每天或每周都会有一两个时段,矿工费会维持低档一段时间,因为交易毕竟是人在进行的,很多时候进入休息时段,交易没这么热络就会出现矿工费降低的空挡,因此设定好你的矿工费上限,超过上限就暂时不要交易,当矿工费低于上限一段时间就赶紧归帐吧!
方法4. 利用ERC20 合约标准接口的approve 跟transferFrom ABI,节省水池打燃料给充值地址的矿工费,同时避免小额燃料费残留问题
由于ERC20 合约都提供有approve 及tansferFrom 两个ABI,善用这两个ABI 就能够让每次归集ERC20 代币时,免去从水池打矿工费给归帐地址的那笔交易费用,直接呼叫transferFrom 进行归帐,也免去会有小额燃料费没有用完又不想回收的问题。
方法5. BTC/LTC 等UTXO 类型的币种,可以将充值跟提币钱包合并,不做归集,直接使用用户充值的多个UTXO 组合,满足客户的提币请求
对于UTXO 类型的币种如BTC / LTC / BCH / BSV / DASH / DOGE 等币种,由于币种本身支援multi-in & multi-out,客户充值在不同地址的资产就能够合并使用,比如有三个客户分别充值了0.1、0.3、0.06 BTC,当你需要进行一笔提币金额是0.35 BTC,系统就可以直接拿出0.1 跟0.3 BTC 组合成一笔交易提出,省掉了归集的过程,当然,因为提币钱包跟充值钱包合并了,资安风险自然会增加,用户在创建这种类型的钱包时务必要注意安全风险,进行风控降低风险系数。
方法6. USDT-Omni 由归帐地址支付矿工费,节省水池打燃料给充值地址的矿工费,同时还可以用上USDT-Omni 交易中小额的Dust
最后则是USDT-Omni 归帐的方式,由于USDT-Omni 交易时,交易中都会带上一个0.00000546 或0.0000054 BTC 的Dust,这笔如灰尘般的BTC 实在让人想收下也不是,不收下一直放在那也碍眼,此外当要归集USDT-Omni 时,也必须打一笔矿工费进到这个地址才能进行归帐,在Omni 的协议中其实是允许对方帮他付款的,因此利用Omni 协议中的这项功能,就不用先把矿工费打给归帐地址,而是可以直接进行归帐,由归帐地址直接支付矿工费,同时把USDT-Omni 当初充值时小额的Dust 作为Input 来产生一笔Dust 的Output。
至此,完成怎样自动归集用户充值的ETH或者usdt到归集地址并最优化归集交易gas费所有操作流程。
pdf+视频币安智能链BSC发币教程及多模式组合合约源代码下载:
币安智能链BSC发币(合约部署、开源、锁仓、LP、参数配置、开发、故障处理、工具使用)教程下载:
多模式(燃烧、回流指定营销地址、分红本币及任意币种,邀请推广八代收益,LP加池分红、交易分红、复利分红、NFT分红、自动筑池、动态手续费、定时开盘、回购)组合合约源代码下载:
pdf+视频币安智能链BSC发币教程及多模式组合合约源代码下载地址:
添加VX或者telegram获取全程线上免费指导
评论前必须登录!
注册