語言: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; } }