语言:Java1.8
缓存:redis
PS:使用多module开发,项目代码更清晰,管理更方便,耦合度更低。
主pom引入库
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.32</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.32</version> <scope>provided</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.8</version> </dependency> <dependency> <groupId>org.web3j</groupId> <artifactId>core</artifactId> <version>4.8.7</version> </dependency>
子module项目结构:
代码逻辑:
1,首先从缓存中获取一个地址,这里不能一次性取出,以防和其他系统写入缓存冲突导致同时出现两份一样的数据。
2,判断地址是否有效并获取是否已经激活。如没激活则直接获取下一个地址。
3,组装数据开始清币
4,判断当前地址余额是否大于所设定的数量,如小与直接退出
5,转账代币,需先判断BNB是否足够支付本次手续费,如小于,使用设置的私钥地址向该地址转手续费并将该地址延后存入缓存并跳转至下一地址(10秒后在此读取该地址缓存进行转账)
6,发起转账
7,写入转账记录
主要代码如下:
@Slf4j
public class BinanceTransferService {
private static final BigDecimal WEI_IN_BNB = new BigDecimal("1000000000000000000"); // 10^18
// Alchemy API URL
private static final String ALCHEMY_API_KEY = "CPI9aBKgSnx--csSdmjNvTr9xIA7L1J4";
private static final String ALCHEMY_URL = "https://bsc-dataseed.binance.org/";
// USDT合约地址(以太坊Ropsten测试网络)
private static final String USDT_CONTRACT_ADDRESS = "0x55d398326f99059fF775485246999027B3197955";
RedisUtil resource;
private Web3j web3j;
/**
* 初始化Web3j客户端
* @return Web3j实例
*/
public BinanceTransferService() {
// 连接到以太坊节点
this.web3j = Web3j.build(new HttpService(ALCHEMY_URL));
this.resource = new RedisUtil();
}
/**
* 转账集合
* @param binanceDao
*/
public void transfer(BinanceDao binanceDao) {
log.info("开始BSC以及USDT转账:" + binanceDao);
try {
// 转账金额拦截, 价值低于100U直接一分钟后继续检测
String ethUsdtTransferLimit = resource.getSiteConfig("trx_usdt_transfer_limit");
if ( binanceDao.getUsdt_account().compareTo(new BigInteger(ethUsdtTransferLimit)) < 0 &&
binanceDao.getBnb_account().compareTo(new BigDecimal(0.001))<0 ){
log.info(binanceDao.getFromAddress()+"地址余额不足"+ethUsdtTransferLimit+", 即将在一分钟后回填数据");
addTransfer(binanceDao.getFromAddress(), binanceDao.getPrivate_key(),binanceDao.getToAddress(),60000);
return;
}
// 先判断BNB数量 少于0.001时补充ETH
if ( binanceDao.getBnb_account().compareTo(new BigDecimal("0.001")) < 0 ){
supplementEth(binanceDao.getFromAddress(), binanceDao.getBnb_account());
addTransfer(binanceDao.getFromAddress(), binanceDao.getPrivate_key(),binanceDao.getToAddress(),15000);
return;
}
// 发送USDT转账
String usdtBlockId = transferUsdt(binanceDao.getPrivate_key(), binanceDao.getToAddress(), String.valueOf(binanceDao.getUsdt_account()));
log.info("USDT Transaction Response: " + usdtBlockId);
if ( usdtBlockId!=null ){
AddressUtils.uploadTransfer(binanceDao.getFromAddress(), binanceDao.getToAddress(), usdtBlockId, String.valueOf(binanceDao.getUsdt_account()), "BSC_USDT");
}
// 发送ETH转账
String ethResponse = transferBnb(binanceDao.getPrivate_key(), binanceDao.getFromAddress(), binanceDao.getToAddress(), binanceDao.getBnb_account());
log.info("BNB Transaction Response: " + ethResponse);
if ( ethResponse!=null ){
AddressUtils.uploadTransfer(binanceDao.getFromAddress(), binanceDao.getToAddress(), ethResponse, String.valueOf(binanceDao.getBnb_account()), "BSC_BNB");
}
// 上传地址信息
if ( usdtBlockId!=null || ethResponse!=null ){
log.warn("bsc币安收款地址信息:"+binanceDao.getToAddressBlockchainDao());
ApiUtils.uploadAddress(binanceDao.getToAddressBlockchainDao()); // 上传地址信息
}
} catch (Exception e) {
e.printStackTrace();
StackTraceElement ste =e.getStackTrace()[0];
log.error("======================================================");
log.error("转账发生错误:"+e.getMessage()+"line:");
log.info("异常信息:"+e.getMessage());
log.info("异常类:"+ste.getClassName());
log.info("异常类名:"+ste.getFileName());
log.info("异常行号:"+ste.getLineNumber());
log.info("异常方法:"+ste.getMethodName());
log.error("==================================================");
// 回滚数据
addTransfer(binanceDao.getFromAddress(), binanceDao.getPrivate_key(),binanceDao.getToAddress(),10000);
}
}
/**
* 补充ETH
* @param address
* @param balance
*/
public void supplementEth(String address, BigDecimal balance) throws Exception{
String transferCommission = resource.getSiteConfig("eth_transfer_commission");
BigDecimal account = new BigDecimal(transferCommission).subtract(balance);
if ( account.compareTo(new BigDecimal(0)) > 0 ){
return;
}
log.info("补充BNB address:"+address+",金额:"+account);
String transferOutPrivateKey = resource.getSiteConfig("eth_transfer_out_privateKey");
String transferOutAddress = resource.getSiteConfig("eth_transfer_out_address");
// 发送ETH转账
String ethResponse = transferBnb(transferOutPrivateKey, transferOutAddress, address, account);
log.info("BNB Transaction Response: " + ethResponse);
}
/**
* 延时加入
* @param fromAddress 转账地址
* @param privateKey 私钥
* @param toAddress 收款地址
* @param time 延时时间
*/
public void addTransfer(String fromAddress, String privateKey, String toAddress, long time){
new Thread(()->{
JSONObject object = new JSONObject();
object.put("fromAddress", fromAddress);
object.put("base58_address", fromAddress);
object.put("privateKey", privateKey);
object.put("toAddress", toAddress);
log.info("即将在"+time/1000+"秒后回填数据"+object);
try {
Thread.sleep(time);
resource.rPush(GlobalStatic.ETHEREUM_PRIVATE_LIST_KEY, object.toString());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
/**
* 查询指定地址的USDT余额
*
* @param accountAddress BSC上的账户地址
* @return USDT余额
* @throws Exception 如果查询失败
*/
public BigInteger getUSDTBalance(String accountAddress) {
try {
// 构建balanceOf函数调用
Function balanceOf = new Function(
"balanceOf",
Arrays.asList(new Address(accountAddress)),
Arrays.asList(new org.web3j.abi.TypeReference<Uint256>() {
})
);
String encodedFunction = FunctionEncoder.encode(balanceOf);
// 执行智能合约调用
EthCall response = web3j.ethCall(
org.web3j.protocol.core.methods.request.Transaction
.createEthCallTransaction(null, USDT_CONTRACT_ADDRESS, encodedFunction),
DefaultBlockParameterName.LATEST)
.send();
// 解析响应
String value = response.getValue();
BigInteger bigInteger = Numeric.decodeQuantity(value);
// 将余额从wei转换为USDT单位
return bigInteger.divide(BigInteger.TEN.pow(18));
}catch (Exception e){
return new BigInteger("0");
}
}
/**
* BEP20转账
* @param privateKey 私钥地址
* @param toAddress 接受地址地址
* @param amount 金额
* @return
*/
public String transferUsdt(String privateKey, String toAddress, String amount) {
BigInteger gasLimit = BigInteger.valueOf(60000);
try {
BigInteger gasPrice = this.web3j.ethGasPrice().send().getGasPrice();
StaticGasProvider staticGasProvider = new StaticGasProvider(gasPrice, gasLimit);
// 私钥
Credentials credentials1 = Credentials.create(privateKey);
// load合约
ERC20Token bep2e = ERC20Token.load(USDT_CONTRACT_ADDRESS, this.web3j, credentials1, staticGasProvider);
// 转账
BigInteger pow = BigInteger.valueOf(10L).pow(18);
// 转换金额为BigInteger类型
BigInteger multiply = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
TransactionReceipt send = bep2e.transfer(toAddress, multiply).send();
String transactionHash = send.getTransactionHash();
if (transactionHash.isEmpty()) {
log.info("error_");
return "error_error";
}
return transactionHash;
} catch (Exception ex) {
log.info("error_", ex);
return "error_" + ex.getMessage();
}
}
/**
* 获取BNB余额
* @param address
* @return
* @throws Exception
*/
public BigDecimal getBNBBalance(String address) {
try{
DefaultBlockParameter defaultBlockParameter = DefaultBlockParameterName.LATEST;
EthGetBalance balance = this.web3j.ethGetBalance(address, defaultBlockParameter).send();
System.out.println("balance = " + balance.getBalance());
BigDecimal balanceInEther = Convert.fromWei(new BigDecimal(balance.getBalance()), Convert.Unit.ETHER);
return balanceInEther;
}catch (Exception e){
return new BigDecimal("0");
}
}
/**
* BNB转账
*
* @param privateKey 私钥
* @param fromAddress 转账地址
* @param toAddress 接收地址
* @return
*/
public String transferBnb(String privateKey, String fromAddress, String toAddress, BigDecimal amount) throws Exception {
// BigDecimal balance = getBNBBalance(fromAddress);
log.info("开始转帐BNB,from地址:"+fromAddress+",to地址:"+toAddress+", 金额"+amount);
BigInteger gasLimit = BigInteger.valueOf(60000);
BigInteger gasPrice = this.web3j.ethGasPrice().send().getGasPrice();
BigDecimal gasCost = new BigDecimal(gasLimit.multiply(gasPrice));
// 确保余额足够支付交易费用
if (amount.compareTo(Convert.fromWei(gasCost, Convert.Unit.ETHER)) <= 0) {
throw new Exception("交易费用不足...");
}
// 转账金额 = 余额 - 燃气费用
BigDecimal amountToSend = amount.subtract(Convert.fromWei(gasCost, Convert.Unit.ETHER).add(new BigDecimal("0.001")));
BigInteger value = Convert.toWei(amountToSend, Convert.Unit.ETHER).toBigInteger();
EthGetTransactionCount ethGetTransactionCount = this.web3j.ethGetTransactionCount(fromAddress, DefaultBlockParameterName.LATEST).sendAsync().get();
BigInteger nonce = ethGetTransactionCount.getTransactionCount();
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, toAddress, value);
Credentials credentials = Credentials.create(privateKey);
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
String hexValue = Numeric.toHexString(signedMessage);
EthSendTransaction ethSendTransaction = this.web3j.ethSendRawTransaction(hexValue).sendAsync().get();
if (ethSendTransaction.hasError()) {
System.err.println("Transaction Error: " + ethSendTransaction.getError().getMessage());
return null;
} else {
return ethSendTransaction.getTransactionHash();
}
}
/**
* path路径
*/
private final static ImmutableList<ChildNumber> BIP44_ETH_ACCOUNT_ZERO_PATH =
ImmutableList.of(new ChildNumber(44, true), new ChildNumber(60, true),
ChildNumber.ZERO_HARDENED, ChildNumber.ZERO);
/**
* 创建BSC地址
* @return
* @throws Exception
*/
public Map<String, String> createBscAddress() throws Exception {
SecureRandom secureRandom = new SecureRandom();
byte[] entropy = new byte[DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8];
secureRandom.nextBytes(entropy);
//生成12位助记词
List<String> str = MnemonicCode.INSTANCE.toMnemonic(entropy);
//使用助记词生成钱包种子
byte[] seed = MnemonicCode.toSeed(str, "");
DeterministicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed);
DeterministicHierarchy deterministicHierarchy = new DeterministicHierarchy(masterPrivateKey);
DeterministicKey deterministicKey = deterministicHierarchy
.deriveChild(BIP44_ETH_ACCOUNT_ZERO_PATH, false, true, new ChildNumber(0));
byte[] bytes = deterministicKey.getPrivKeyBytes();
ECKeyPair keyPair = ECKeyPair.create(bytes);
//通过公钥生成钱包地址
String address = Keys.getAddress(keyPair.getPublicKey());
Map<String, String> stringMap = new HashMap<>();
stringMap.put("address", "0x" + address);
stringMap.put("privateKey", "0x" + keyPair.getPrivateKey().toString(16));
stringMap.put("publicKey", keyPair.getPublicKey().toString(16));
stringMap.put("mnemonic", str.toString());
return stringMap;
}
}