pom.xml
@@ -28,6 +28,11 @@ </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.web3j</groupId> <artifactId>core</artifactId> src/main/java/cc/mrbird/febs/common/utils/AppContants.java
@@ -67,5 +67,8 @@ public static final String AGENT_LEVEL = "AGENT_LEVEL"; public static final String AGENT_LEVEL_REQUIRE = "AGENT_LEVEL_REQUIRE"; public static final String REDIS_KEY_BLOCK_USDT_NUM = "BLOCK_USDT_NUM"; public static final String REDIS_KEY_BLOCK_ETH_INCREMENT_NUM = "BLOCK_ETH_INCREMENT_NUM"; public static final String REDIS_KEY_BLOCK_ETH_NEWEST_NUM = "BLOCK_ETH_NEWEST_NUM"; } src/main/java/cc/mrbird/febs/mall/chain/enums/ChainEnum.java
New file @@ -0,0 +1,54 @@ package cc.mrbird.febs.mall.chain.enums; import lombok.Getter; /** * 链类型 */ @Getter public enum ChainEnum { /** * 币安 usdt合约 * 0x55d398326f99059fF775485246999027B3197955 * 测试链 0x337610d27c682E347C9cD60BD4b3b107C9d34dDd */ BSC_USDT("BSC", "0x7FC948E091C4b71fC063cE59B8Dad1062B1c5065", "0xbf6f11f5689961d5351375bebbae751de0d0d5c2e2095c1017368485dc909ff8", "https://bsc-dataseed1.ninicoin.io", "0x55d398326f99059fF775485246999027B3197955", ""); private String chain; private String address; private String privateKey; private String url; private String contractAddress; private String apiKey; ChainEnum(String chain, String address, String privateKey, String url, String contractAddress, String apiKey) { this.chain = chain; this.address = address; this.privateKey = privateKey; this.url = url; this.contractAddress = contractAddress; this.apiKey = apiKey; } public static ChainEnum getValueByName(String name) { ChainEnum[] values = values(); for (ChainEnum value : values) { if (value.name().equals(name)) { return value; } } return null; } } src/main/java/cc/mrbird/febs/mall/chain/enums/EthService.java
New file @@ -0,0 +1,433 @@ package cc.mrbird.febs.mall.chain.enums; import cc.mrbird.febs.mall.chain.service.ContractChainService; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import org.web3j.abi.FunctionEncoder; import org.web3j.abi.FunctionReturnDecoder; import org.web3j.abi.TypeReference; import org.web3j.abi.datatypes.Address; import org.web3j.abi.datatypes.Function; import org.web3j.abi.datatypes.Type; import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.crypto.Credentials; import org.web3j.crypto.RawTransaction; import org.web3j.crypto.TransactionEncoder; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.DefaultBlockParameterName; import org.web3j.protocol.core.Request; import org.web3j.protocol.core.methods.request.Transaction; import org.web3j.protocol.core.methods.response.EthBlockNumber; import org.web3j.protocol.core.methods.response.EthCall; import org.web3j.protocol.core.methods.response.EthGetTransactionCount; import org.web3j.protocol.core.methods.response.EthSendTransaction; import org.web3j.protocol.http.HttpService; import org.web3j.utils.Convert; import org.web3j.utils.Numeric; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; /** * @author wzy * @date 2022-04-15 **/ public class EthService implements ContractChainService { private Web3j web3j; private String url; private String ownerAddress; private String privateKey; private String contractAddress; public EthService(String url, String address, String privateKey, String contractAddress) { this.url = url; this.ownerAddress = address; this.privateKey = privateKey; this.contractAddress = contractAddress; HttpService service = new HttpService(url); web3j = Web3j.build(service); } public static void main(String[] args) throws IOException { HttpService service = new HttpService("https://bsc-dataseed1.ninicoin.io"); Web3j web3j = Web3j.build(service); long start = System.currentTimeMillis(); Request<?, EthBlockNumber> request = web3j.ethBlockNumber(); EthBlockNumber send = request.send(); BigInteger bigInteger = Numeric.decodeQuantity(send.getResult()); long end = System.currentTimeMillis(); System.out.println(end - start); // String address = "0x971c09aa9735eb98459b17ec8b48932d24cbb931"; // String nonce = "0x1d5f7444107bc02e980deda39d0fce21b06c9da4233a19cb11124cb5bfefc9ec"; // String sign = "0x8f92cee24906122e26c3cc6cbd72f851cfe2c9574aa03bf3371e5d506fbec68b2ad22bbbc19b00ed21d26ab5a6871507831e2c902d8ed8c33301addc2b57a7731b"; // // String result = address + ":" + nonce + ":" + sign; // System.out.println(Hash.sha3(result)); // Web3Sha3 send = web3j.web3Sha3(result).send(); // System.out.println(1); } @Override public BigInteger balanceOfUnDecimal(String fromAddress) { try { String methodName = "balanceOf"; List<Type> inputParameters = new ArrayList<>(); List<TypeReference<?>> outputParameters = new ArrayList<>(); Address address = new Address(fromAddress); inputParameters.add(address); TypeReference<Uint256> typeReference = new TypeReference<Uint256>() { }; outputParameters.add(typeReference); Function function = new Function(methodName, inputParameters, outputParameters); String data = FunctionEncoder.encode(function); Transaction transaction = Transaction.createEthCallTransaction(fromAddress, contractAddress, data); EthCall ethCall; BigInteger balanceValue = BigInteger.ZERO; try { ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send(); List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters()); balanceValue = (BigInteger) results.get(0).getValue(); } catch (IOException e) { e.printStackTrace(); } return balanceValue; } catch (Exception e) { e.printStackTrace(); } return BigInteger.ZERO; } @Override public int decimals() { try { String methodName = "decimals"; List<Type> inputParameters = new ArrayList<>(); List<TypeReference<?>> outputParameters = new ArrayList<>(); TypeReference<Uint256> typeReference = new TypeReference<Uint256>() { }; outputParameters.add(typeReference); Function function = new Function(methodName, inputParameters, outputParameters); String data = FunctionEncoder.encode(function); Transaction transaction = Transaction.createEthCallTransaction(null, contractAddress, data); EthCall ethCall; BigInteger decimals = BigInteger.ZERO; try { ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send(); List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters()); decimals = (BigInteger) results.get(0).getValue(); return decimals.intValue(); } catch (IOException e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } return 0; } @Override public BigDecimal balanceOf(String fromAddress) { int decimal = decimals(); BigInteger balanceValue = balanceOfUnDecimal(fromAddress); double res = new BigDecimal(balanceValue).divide(BigDecimal.valueOf(Math.pow(10, decimal)), 8, RoundingMode.HALF_DOWN).doubleValue(); if (res > 0) { return new BigDecimal(res); } return BigDecimal.ZERO; } @Override public BigInteger allowance(String address) { String methodName = "allowance"; List<TypeReference<?>> outputParameters = new ArrayList<>(); TypeReference<Uint256> typeReference = new TypeReference<Uint256>() { }; outputParameters.add(typeReference); Function function = new Function(methodName, Arrays.asList(new Address(address), new Address(ownerAddress)) , outputParameters); String data = FunctionEncoder.encode(function); Transaction transaction = Transaction.createEthCallTransaction(ownerAddress, contractAddress, data); EthCall ethCall = null; try { ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send(); List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters()); return (BigInteger) results.get(0).getValue(); } catch (IOException e) { e.printStackTrace(); } return BigInteger.ZERO; } @Override public boolean isAllowance(String address) { return allowance(address).intValue() != 0; } public String getGas() { String gas; if (url.contains("infura.io")) { String resp = HttpUtil.get("https://etherscan.io/autoUpdateGasTracker.ashx?sid=75f30b765180f29e2b7584b8501c9124"); JSONObject data = JSONObject.parseObject(resp); gas = data.getString("avgPrice"); } else { String resp = HttpUtil.get("https://gbsc.blockscan.com/gasapi.ashx?apikey=key&method=gasoracle"); JSONObject data = JSONObject.parseObject(resp); gas = data.getString("FastGasPrice"); } return StrUtil.isBlank(gas) ? "35" : gas; } @Override public String transfer(String address) { BigDecimal balance = balanceOf(address); return transfer(address, balance); } @Override public String transfer(String address, BigDecimal amount) { try { return tokenTransfer(privateKey, ownerAddress, address, amount.toPlainString()); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); return ""; } } public String tokenTransferFrom(String privateKey, String fromAddress, String toAddress, String amount) throws ExecutionException, InterruptedException { String gas = getGas(); BigDecimal amountPow = new BigDecimal(amount).multiply(BigDecimal.TEN.pow(decimals())); amount = amountPow.toPlainString(); if (amount.contains(".")) { amount = amount.substring(0, amount.lastIndexOf(".")); } Credentials credentials = Credentials.create(privateKey); EthGetTransactionCount ethGetTransactionCount = web3j .ethGetTransactionCount(fromAddress, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger nonce = ethGetTransactionCount.getTransactionCount(); Function function = new Function("transferFrom", Arrays.asList(new Address(fromAddress), new Address(toAddress), new Uint256(new BigInteger(amount))), Arrays.asList(new TypeReference<Type>() { })); String encodedFunction = FunctionEncoder.encode(function); RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, Convert.toWei(gas, Convert.Unit.GWEI).toBigInteger(),// 给矿工开的转账单价 单价越高越快 Convert.toWei("100000", Convert.Unit.WEI).toBigInteger(), contractAddress, encodedFunction);//里程上限 byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signedMessage); CompletableFuture<EthSendTransaction> ethSendTransactionCompletableFuture = web3j.ethSendRawTransaction(hexValue).sendAsync(); EthSendTransaction ethSendTransaction = ethSendTransactionCompletableFuture.get(); if (ethSendTransaction.hasError()) { return ""; } else { return ethSendTransaction.getTransactionHash(); } } public String tokenTransfer(String privateKey, String fromAddress, String toAddress, String amount) throws ExecutionException, InterruptedException { String gas = getGas(); BigDecimal amountPow = new BigDecimal(amount).multiply(BigDecimal.TEN.pow(decimals())); amount = amountPow.toPlainString(); if (amount.contains(".")) { amount = amount.substring(0, amount.lastIndexOf(".")); } Credentials credentials = Credentials.create(privateKey); EthGetTransactionCount ethGetTransactionCount = web3j .ethGetTransactionCount(fromAddress, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger nonce = ethGetTransactionCount.getTransactionCount(); Function function = new Function("transfer", Arrays.asList(new Address(toAddress), new Uint256(new BigInteger(amount))), Arrays.asList(new TypeReference<Type>() { })); String encodedFunction = FunctionEncoder.encode(function); RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, Convert.toWei(gas, Convert.Unit.GWEI).toBigInteger(),// 给矿工开的转账单价 单价越高越快 Convert.toWei("100000", Convert.Unit.WEI).toBigInteger(), contractAddress, encodedFunction);//里程上限 byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signedMessage); CompletableFuture<EthSendTransaction> ethSendTransactionCompletableFuture = web3j.ethSendRawTransaction(hexValue).sendAsync(); EthSendTransaction ethSendTransaction = ethSendTransactionCompletableFuture.get(); if (ethSendTransaction.hasError()) { return ""; } else { return ethSendTransaction.getTransactionHash(); } } @Override public int allowanceCnt(String address) { String allowanceUrl; if (url.contains("infura.io")) { allowanceUrl = "https://etherscan.io/tokenapprovalchecker.aspx/GetApprovedContract"; } else { allowanceUrl = "https://bscscan.com/tokenapprovalchecker.aspx/GetApprovedContract"; } String baseData = "{\"dataTableModel\":{\"draw\":3,\"columns\":[{\"data\":\"TxnHash\",\"name\":\"\",\"searchable\":true,\"orderable\":false,\"search\":{\"value\":\"\",\"regex\":false}},{\"data\":\"Block\",\"name\":\"\",\"searchable\":true,\"orderable\":false,\"search\":{\"value\":\"\",\"regex\":false}},{\"data\":\"Token\",\"name\":\"\",\"searchable\":true,\"orderable\":false,\"search\":{\"value\":\"\",\"regex\":false}},{\"data\":\"ApprovedSpender\",\"name\":\"\",\"searchable\":true,\"orderable\":false,\"search\":{\"value\":\"\",\"regex\":false}},{\"data\":\"ApprovedAmount\",\"name\":\"\",\"searchable\":true,\"orderable\":false,\"search\":{\"value\":\"\",\"regex\":false}},{\"data\":\"LastUpdated\",\"name\":\"\",\"searchable\":true,\"orderable\":false,\"search\":{\"value\":\"\",\"regex\":false}},{\"data\":\"Action\",\"name\":\"\",\"searchable\":true,\"orderable\":false,\"search\":{\"value\":\"\",\"regex\":false}}],\"order\":[],\"start\":0,\"length\":25,\"search\":{\"value\":\"\",\"regex\":false}},\"model\":{\"address\":\"{address}\",\"filteredContract\":\"\"}}"; String data = baseData.replace("{address}", address); String resp = HttpUtil.post(allowanceUrl, data); JSONObject jsonObject = JSONObject.parseObject(resp); JSONObject result = JSONObject.parseObject(jsonObject.getString("d")); return result.getIntValue("recordsTotal"); } @Override public BigInteger blockNumber() { Request<?, EthBlockNumber> request = web3j.ethBlockNumber(); EthBlockNumber send = null; try { send = request.send(); } catch (IOException e) { e.printStackTrace(); } if (send != null) { return Numeric.decodeQuantity(send.getResult()); } else { throw new NullPointerException(); } } @Override public BigInteger totalSupply() { try { String methodName = "totalSupply"; List<Type> inputParameters = new ArrayList<>(); List<TypeReference<?>> outputParameters = new ArrayList<>(); TypeReference<Uint256> typeReference = new TypeReference<Uint256>() { }; outputParameters.add(typeReference); Function function = new Function(methodName, inputParameters, outputParameters); String data = FunctionEncoder.encode(function); Transaction transaction = Transaction.createEthCallTransaction(null, contractAddress, data); EthCall ethCall; BigInteger totalSupply = BigInteger.ZERO; try { ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send(); List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters()); totalSupply = (BigInteger) results.get(0).getValue(); return totalSupply.divide(BigInteger.TEN.pow(decimals())); } catch (IOException e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } return BigInteger.ZERO; } @Override public BigInteger totalSupplyNFT() { try { String methodName = "totalSupply"; List<Type> inputParameters = new ArrayList<>(); List<TypeReference<?>> outputParameters = new ArrayList<>(); TypeReference<Uint256> typeReference = new TypeReference<Uint256>() { }; outputParameters.add(typeReference); Function function = new Function(methodName, inputParameters, outputParameters); String data = FunctionEncoder.encode(function); Transaction transaction = Transaction.createEthCallTransaction(null, contractAddress, data); EthCall ethCall; BigInteger totalSupply = BigInteger.ZERO; try { ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send(); List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters()); totalSupply = (BigInteger) results.get(0).getValue(); return totalSupply; } catch (IOException e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } return BigInteger.ZERO; } @Override public String safeMintNFT(String toAddress) { String gas = getGas(); try { Credentials credentials = Credentials.create(privateKey); EthGetTransactionCount ethGetTransactionCount = web3j .ethGetTransactionCount(ownerAddress, DefaultBlockParameterName.LATEST).sendAsync().get(); BigInteger nonce = ethGetTransactionCount.getTransactionCount(); Function function = new Function("safeMint", Arrays.asList(new Address(toAddress)), Arrays.asList(new TypeReference<Type>() { })); String encodedFunction = FunctionEncoder.encode(function); RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, Convert.toWei(gas, Convert.Unit.GWEI).toBigInteger(), Convert.toWei("1000000", Convert.Unit.WEI).toBigInteger(), contractAddress, encodedFunction); byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); String hexValue = Numeric.toHexString(signedMessage); CompletableFuture<EthSendTransaction> ethSendTransactionCompletableFuture = web3j.ethSendRawTransaction(hexValue).sendAsync(); EthSendTransaction ethSendTransaction = ethSendTransactionCompletableFuture.get(); if (ethSendTransaction.hasError()) { return ""; } else { return ethSendTransaction.getTransactionHash(); } } catch (Exception e) { e.printStackTrace(); return ""; } } } src/main/java/cc/mrbird/febs/mall/chain/job/ChainListenerJob.java
New file @@ -0,0 +1,88 @@ package cc.mrbird.febs.mall.chain.job; import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.common.utils.RedisUtils; import cc.mrbird.febs.mall.chain.enums.ChainEnum; import cc.mrbird.febs.mall.chain.service.BaseCoinService; import cc.mrbird.febs.mall.chain.service.BscUsdtContractEvent; import cc.mrbird.febs.mall.chain.service.ChainService; import cc.mrbird.febs.mall.chain.service.ContractEventService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.math.BigInteger; @Slf4j @Component @ConditionalOnProperty(prefix = "system", name = "chain-listener", havingValue = "true") public class ChainListenerJob implements ApplicationRunner { @Autowired private ContractEventService bscUsdtContractEvent; @Autowired private RedisUtils redisUtils; @Autowired private BaseCoinService baseCoinService; @Scheduled(cron = "0 0/5 * * * ? ") public void chainBlockUpdate() { BigInteger blockNumber = ChainService.getInstance(ChainEnum.BSC_USDT.name()).blockNumber(); redisUtils.set(AppContants.REDIS_KEY_BLOCK_ETH_NEWEST_NUM, blockNumber); } @Scheduled(cron = "0/2 * * * * ? ") public void chainIncrementBlock() { Object newestBlockObj = redisUtils.get(AppContants.REDIS_KEY_BLOCK_ETH_NEWEST_NUM); BigInteger newestBlock; if (newestBlockObj == null) { newestBlock = ChainService.getInstance(ChainEnum.BSC_USDT.name()).blockNumber(); } else { newestBlock = (BigInteger) newestBlockObj; } Object incrementObj = redisUtils.get(AppContants.REDIS_KEY_BLOCK_ETH_INCREMENT_NUM); BigInteger toIncrement; if (incrementObj == null) { toIncrement = newestBlock; } else { BigInteger incrementBlock = (BigInteger) incrementObj; // 最新区块小于增加区块 if (newestBlock.compareTo(incrementBlock) <= 0) { return; } toIncrement = incrementBlock.add(BigInteger.ONE); } redisUtils.set(AppContants.REDIS_KEY_BLOCK_ETH_INCREMENT_NUM, toIncrement); } @Override public void run(ApplicationArguments args) throws Exception { long start = System.currentTimeMillis(); log.info("区块链监听开始启动"); Object incrementObj = redisUtils.get(AppContants.REDIS_KEY_BLOCK_ETH_INCREMENT_NUM); BigInteger newest = ChainService.getInstance(ChainEnum.BSC_USDT.name()).blockNumber(); BigInteger block; if (incrementObj == null) { block = newest; } else { block = (BigInteger) incrementObj; } // ChainService.wssBaseCoinEventListener(block, baseCoinService); // ChainService.wssBaseCoinEventListener(block, bscUsdtContractEvent); ChainService.wssContractEventListener(block, bscUsdtContractEvent, ChainEnum.BSC_USDT.name()); long end = System.currentTimeMillis(); log.info("区块链监听启动完成, 消耗时间{}", end - start); } } src/main/java/cc/mrbird/febs/mall/chain/service/BaseCoinService.java
New file @@ -0,0 +1,135 @@ package cc.mrbird.febs.mall.chain.service; import cc.mrbird.febs.common.enumerates.FlowTypeEnum; import cc.mrbird.febs.common.enumerates.MallMoneyFlowTypeEnum; import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.common.utils.RedisUtils; import cc.mrbird.febs.mall.chain.enums.ChainEnum; import cc.mrbird.febs.mall.chain.enums.CoinTypeEnum; import cc.mrbird.febs.mall.entity.MallMemberWallet; import cc.mrbird.febs.mall.entity.MallMoneyFlow; import cc.mrbird.febs.mall.entity.MemberCoinAddressEntity; import cc.mrbird.febs.mall.entity.MemberCoinChargeEntity; import cc.mrbird.febs.mall.mapper.MallMemberWalletMapper; import cc.mrbird.febs.mall.mapper.MemberCoinAddressDao; import cc.mrbird.febs.mall.mapper.MemberCoinChargeDao; import cc.mrbird.febs.mall.service.IMallMoneyFlowService; import cn.hutool.core.util.ObjectUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; import org.web3j.protocol.core.methods.response.Transaction; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Service public class BaseCoinService { @Resource private RedisUtils redisUtils; @Resource private MemberCoinChargeDao memberCoinChargeDao; @Resource private MemberCoinAddressDao memberCoinAddressDao; @Resource private MallMemberWalletMapper mallMemberWalletMapper; @Resource private IMallMoneyFlowService mallMoneyFlowService; public void compile(Transaction e) { if (e.getTo() == null) { return; } redisUtils.set(AppContants.REDIS_KEY_BLOCK_USDT_NUM, e.getBlockNumber()); String address = e.getTo(); String hash = e.getHash(); MemberCoinAddressEntity coinAddressEntity = memberCoinAddressDao.selectCoinAddressByAddressAndSymbol(address, CoinTypeEnum.USDT.toString()); if (coinAddressEntity == null) { return; } // 判断对方打款地址是否为源池地址 if(ObjectUtil.isNotEmpty(coinAddressEntity)){ // if (ChainEnum.BSC_USDT.getAddress().toLowerCase().equals(e.to)) { log.info("触发USDT合约监听事件"); redisUtils.set(AppContants.REDIS_KEY_BLOCK_ETH_INCREMENT_NUM, e.getBlockNumber()); // hash没有用过 Map<String, Object> param = new HashMap<>(); param.put("hash", hash); param.put("address", address); List<MemberCoinChargeEntity> memberCoinChargeEntities = memberCoinChargeDao.selectByMap(param); if (CollectionUtils.isNotEmpty(memberCoinChargeEntities)) { return; } // MemberCoinAddressEntity coinAddressEntity = memberCoinAddressDao.selectCoinAddressByAddressAndSymbol(address, CoinTypeEnum.USDT.toString()); // if (coinAddressEntity == null) { // return; // } Long memberId = coinAddressEntity.getMemberId(); BigDecimal amount = new BigDecimal(e.getValue()).divide(BigDecimal.TEN.pow(18), 8, RoundingMode.HALF_DOWN); BigDecimal balance = amount; if (balance != null && balance.compareTo(new BigDecimal("0.1")) > 0) { balance = balance.setScale(8, RoundingMode.CEILING); BigDecimal early = BigDecimal.ZERO; // 查询钱包 并更新 MallMemberWallet mallMemberWallet = mallMemberWalletMapper.selectWalletByMemberId(memberId); if (mallMemberWallet == null) { return; } mallMemberWalletMapper.updateBlockBalanceById(mallMemberWallet.getId(), balance); String orderNo = insertCoinCharge(address, memberId, balance, CoinTypeEnum.USDT.name(), "ERC20", BigDecimal.ZERO, hash); // 插入财务记录 Long chargeFlowId = mallMoneyFlowService.addMoneyFlow( memberId, orderNo, balance.setScale(4, BigDecimal.ROUND_DOWN), MallMoneyFlowTypeEnum.CHARGE.getCode(), MallMoneyFlow.STATUS_SUCCESS, MallMoneyFlow.IS_RETURN_Y, memberId, FlowTypeEnum.BALANCE.getValue(), MallMoneyFlowTypeEnum.CHARGE.getName() ); // 同步 // BigDecimal bigDecimal = ChainService.getInstance(ChainEnum.BSC_USDT.name()).balanceOf(e.to); } } } public String insertCoinCharge(String address, Long memberId, BigDecimal newBalance, String symbol, String tag, BigDecimal lastAmount, String hash) { MemberCoinChargeEntity memberCoinChargeEntity = new MemberCoinChargeEntity(); memberCoinChargeEntity.setAddress(address); memberCoinChargeEntity.setMemberId(memberId); memberCoinChargeEntity.setAmount(newBalance); memberCoinChargeEntity.setSymbol(symbol); memberCoinChargeEntity.setTag(tag); memberCoinChargeEntity.setStatus(1); memberCoinChargeEntity.setLastAmount(lastAmount); memberCoinChargeEntity.setHash(hash); String orderNo = generateNo(); memberCoinChargeEntity.setOrderCode(orderNo); memberCoinChargeDao.insert(memberCoinChargeEntity); return orderNo; } private String generateNo() { // 生成订单号 Long timestamp = System.currentTimeMillis(); // 随机数 int random = (int) (Math.random() * 10); return String.valueOf(timestamp).substring(2) + random; } } src/main/java/cc/mrbird/febs/mall/chain/service/BlockCoinServiceImpl.java
@@ -114,12 +114,12 @@ return; } // 校验这个交易是否成功 EthService ethService = new EthService(); boolean b = ethService.checkTransferResult(hash); if (!b) { log.info("#USDT假充值:{}#", hash); return; } // EthService ethService = new EthService(); // boolean b = ethService.checkTransferResult(hash); // if (!b) { // log.info("#USDT假充值:{}#", hash); // return; // } MemberCoinAddressEntity coinAddressEntity = memberCoinAddressDao.selectCoinAddressByAddressAndSymbol(address, CoinTypeEnum.USDT.toString()); if (coinAddressEntity == null) { return; src/main/java/cc/mrbird/febs/mall/chain/service/BscUsdtContractEvent.java
New file @@ -0,0 +1,138 @@ package cc.mrbird.febs.mall.chain.service; import cc.mrbird.febs.common.enumerates.FlowTypeEnum; import cc.mrbird.febs.common.enumerates.MallMoneyFlowTypeEnum; import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.common.utils.RedisUtils; import cc.mrbird.febs.mall.chain.enums.ChainEnum; import cc.mrbird.febs.mall.chain.enums.CoinTypeEnum; import cc.mrbird.febs.mall.entity.MallMemberWallet; import cc.mrbird.febs.mall.entity.MallMoneyFlow; import cc.mrbird.febs.mall.entity.MemberCoinAddressEntity; import cc.mrbird.febs.mall.entity.MemberCoinChargeEntity; import cc.mrbird.febs.mall.mapper.MallMemberWalletMapper; import cc.mrbird.febs.mall.mapper.MemberCoinAddressDao; import cc.mrbird.febs.mall.mapper.MemberCoinChargeDao; import cc.mrbird.febs.mall.service.IMallMoneyFlowService; import cn.hutool.core.util.ObjectUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Service public class BscUsdtContractEvent implements ContractEventService { @Resource private RedisUtils redisUtils; @Resource private MemberCoinChargeDao memberCoinChargeDao; @Resource private MemberCoinAddressDao memberCoinAddressDao; @Resource private MallMemberWalletMapper mallMemberWalletMapper; @Resource private IMallMoneyFlowService mallMoneyFlowService; @Override public void compile(EthUsdtContract.TransferEventResponse e) { if (e.to == null) { return; } redisUtils.set(AppContants.REDIS_KEY_BLOCK_USDT_NUM, e.log.getBlockNumber()); String address = e.to; String hash = e.log.getTransactionHash(); MemberCoinAddressEntity coinAddressEntity = memberCoinAddressDao.selectCoinAddressByAddressAndSymbol(address, CoinTypeEnum.USDT.toString()); if (coinAddressEntity == null) { return; } // 判断对方打款地址是否为源池地址 if(ObjectUtil.isNotEmpty(coinAddressEntity)){ // if (ChainEnum.BSC_USDT.getAddress().toLowerCase().equals(e.to)) { log.info("触发USDT合约监听事件"); redisUtils.set(AppContants.REDIS_KEY_BLOCK_ETH_INCREMENT_NUM, e.log.getBlockNumber()); // hash没有用过 Map<String, Object> param = new HashMap<>(); param.put("hash", hash); param.put("address", address); List<MemberCoinChargeEntity> memberCoinChargeEntities = memberCoinChargeDao.selectByMap(param); if (CollectionUtils.isNotEmpty(memberCoinChargeEntities)) { return; } // MemberCoinAddressEntity coinAddressEntity = memberCoinAddressDao.selectCoinAddressByAddressAndSymbol(address, CoinTypeEnum.USDT.toString()); // if (coinAddressEntity == null) { // return; // } Long memberId = coinAddressEntity.getMemberId(); ContractChainService sourceUsdtInstance = ChainService.getInstance(ChainEnum.BSC_USDT.name()); int decimals = sourceUsdtInstance.decimals(); BigInteger tokens = e.tokens; BigDecimal amount = new BigDecimal(tokens.toString()).divide(BigDecimal.TEN.pow(decimals), decimals, RoundingMode.HALF_DOWN); BigDecimal balance = amount; if (balance != null && balance.compareTo(new BigDecimal("0.1")) > 0) { balance = balance.setScale(8, RoundingMode.CEILING); BigDecimal early = BigDecimal.ZERO; // 查询钱包 并更新 MallMemberWallet mallMemberWallet = mallMemberWalletMapper.selectWalletByMemberId(memberId); if (mallMemberWallet == null) { return; } mallMemberWalletMapper.updateBlockBalanceById(mallMemberWallet.getId(), balance); String orderNo = insertCoinCharge(address, memberId, balance, CoinTypeEnum.USDT.name(), "ERC20", BigDecimal.ZERO, hash); // 插入财务记录 Long chargeFlowId = mallMoneyFlowService.addMoneyFlow( memberId, orderNo, balance.setScale(4, BigDecimal.ROUND_DOWN), MallMoneyFlowTypeEnum.CHARGE.getCode(), MallMoneyFlow.STATUS_SUCCESS, MallMoneyFlow.IS_RETURN_Y, memberId, FlowTypeEnum.BALANCE.getValue(), MallMoneyFlowTypeEnum.CHARGE.getName() ); // 同步 // BigDecimal bigDecimal = ChainService.getInstance(ChainEnum.BSC_USDT.name()).balanceOf(e.to); } } } public String insertCoinCharge(String address, Long memberId, BigDecimal newBalance, String symbol, String tag, BigDecimal lastAmount, String hash) { MemberCoinChargeEntity memberCoinChargeEntity = new MemberCoinChargeEntity(); memberCoinChargeEntity.setAddress(address); memberCoinChargeEntity.setMemberId(memberId); memberCoinChargeEntity.setAmount(newBalance); memberCoinChargeEntity.setSymbol(symbol); memberCoinChargeEntity.setTag(tag); memberCoinChargeEntity.setStatus(1); memberCoinChargeEntity.setLastAmount(lastAmount); memberCoinChargeEntity.setHash(hash); String orderNo = generateNo(); memberCoinChargeEntity.setOrderCode(orderNo); memberCoinChargeDao.insert(memberCoinChargeEntity); return orderNo; } private String generateNo() { // 生成订单号 Long timestamp = System.currentTimeMillis(); // 随机数 int random = (int) (Math.random() * 10); return String.valueOf(timestamp).substring(2) + random; } } src/main/java/cc/mrbird/febs/mall/chain/service/ChainService.java
New file @@ -0,0 +1,185 @@ package cc.mrbird.febs.mall.chain.service; import cc.mrbird.febs.common.exception.FebsException; import cc.mrbird.febs.mall.chain.enums.ChainEnum; import cc.mrbird.febs.mall.chain.enums.EthService; import io.reactivex.Flowable; import io.reactivex.disposables.Disposable; import lombok.extern.slf4j.Slf4j; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.DefaultBlockParameter; import org.web3j.protocol.core.DefaultBlockParameterName; import org.web3j.protocol.core.DefaultBlockParameterNumber; import org.web3j.protocol.core.methods.request.EthFilter; import org.web3j.protocol.http.HttpService; import org.web3j.protocol.websocket.WebSocketClient; import org.web3j.protocol.websocket.WebSocketService; import org.web3j.tx.gas.StaticGasProvider; import java.math.BigInteger; import java.net.URI; import java.util.HashMap; import java.util.Map; /** * @author * @date 2022-03-23 **/ @Slf4j public class ChainService { private final static Map<String, ContractChainService> contractMap = new HashMap<>(); static { for (ChainEnum chain : ChainEnum.values()) { contractMap.put(chain.name(), new EthService(chain.getUrl(), chain.getAddress(), chain.getPrivateKey(), chain.getContractAddress())); } } private ChainService() { } public final static ChainService INSTANCE = new ChainService(); public static ContractChainService getInstance(String chainType) { ContractChainService contract = contractMap.get(chainType); if (contract == null) { throw new FebsException("参数错误"); } return contract; } /** * 监听合约事件 * * @param startBlock 开始区块 */ public static void contractEventListener(BigInteger startBlock, ContractEventService event, String type) { contractEventListener(startBlock, null, event, type); } public static void contractEventListener(BigInteger startBlock, BigInteger endBlock, ContractEventService event, String type) { ChainEnum chain = ChainEnum.getValueByName(type); assert chain != null; EthUsdtContract contract = contract(chain.getPrivateKey(), chain.getContractAddress(), chain.getUrl()); EthFilter filter = getFilter(startBlock, endBlock, chain.getContractAddress()); Flowable<EthUsdtContract.TransferEventResponse> eventFlowable = contract.transferEventFlowable(filter); eventFlowable.subscribe(e -> { event.compile(e); }, error -> { log.error("合约监听启动报错", error); }); } public static void wssContractEventListener(BigInteger startBlock, ContractEventService event, String type) { WebSocketService ws = null; WebSocketClient webSocketClient = null; Web3j web3j = null; try { webSocketClient = new WebSocketClient(new URI("wss://bsc-mainnet.nodereal.io/ws/v1/78074065950e4915aef4f12b6f357d16")); ws = new WebSocketService(webSocketClient, true); ws.connect(); web3j = Web3j.build(ws); ChainEnum chain = ChainEnum.getValueByName(type); assert chain != null; EthUsdtContract ethUsdtContract = wssContract(chain.getPrivateKey(), chain.getContractAddress(), web3j); EthFilter filter = getFilter(startBlock, startBlock, chain.getContractAddress()); Flowable<EthUsdtContract.TransferEventResponse> eventFlowable = ethUsdtContract.transferEventFlowable(filter); Disposable subscribe = eventFlowable.subscribe(event::compile, error -> { log.error("币安监听异常", error); }); } catch (Exception e) { e.printStackTrace(); } } public static void wssBaseCoinEventListener(BigInteger startBlock, BaseCoinService event) { WebSocketService ws = null; WebSocketClient webSocketClient = null; Web3j web3j = null; try { webSocketClient = new WebSocketClient(new URI("wss://bsc-mainnet.nodereal.io/ws/v1/78074065950e4915aef4f12b6f357d16")); ws = new WebSocketService(webSocketClient, true); ws.connect(); web3j = Web3j.build(ws); Disposable subscribe = web3j.replayPastAndFutureTransactionsFlowable(new DefaultBlockParameterNumber(startBlock)).subscribe(event::compile, error ->{ log.error("监听链上异常", error); }); }catch (Exception e) { e.printStackTrace(); } } private static EthUsdtContract contract(String privateKey, String contractAddress, String url) { Credentials credentials = Credentials.create(privateKey); HttpService httpService = new HttpService(url); // httpService.addHeader("Authorization", "Bearer " + Base64.encode("tfc:tfc123".getBytes())); // httpService.addHeader("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwdWJsaWMiLCJleHAiOjE2NTk5MzcxOTAsImp0aSI6IjRiMjNkYTVjLWRlZWEtNDYzNi04YjMwLWNmMmZmMjVkM2NlYyIsImlhdCI6MTY1OTkzMzU5MCwiaXNzIjoiQW5rciIsIm5iZiI6MTY1OTkzMzU5MCwic3ViIjoiZmNiNjY0YjItOGEwNC00N2E5LTg3ZjMtNTJhMjE2ODVlMzEzIn0.YfEwvDByU2MGHywsblZpEmKMIbjv4cWYkn5CaFglXY0TSANzd2pCSbIe40yU_R9_nV6xZeE8Uk74jJOdd_QvMpFyUgo-MMNWZP6uiEaYvK_K3tlpk5yzeZq9D4ruWaq8rFKggr-iaRGzu6coRSAOFv2prWll3a7NdEbmkM-y5Y85xYD6g1N-TPIpE_Y-_-WPf3JUavk744kG8YyHhGvAmk2IL0N2xePfC6CHesdJhwvmJJXzr_53dbPwit1y5KljS0iTZz3mGTML2bq4hGaEHbQxeY2fBpZOSm8sPMz-zB9IVJQKzH5-DXlPKz01mJ9XiBJlubfHsN72RdqFD-O2Tw"); return EthUsdtContract.load(contractAddress, Web3j.build(httpService), credentials, new StaticGasProvider(BigInteger.valueOf(4500000L), BigInteger.valueOf(200000L))); } private static EthUsdtContract wssContract(String privateKey, String contractAddress, Web3j web3j) { Credentials credentials = Credentials.create(privateKey); // httpService.addHeader("Authorization", "Bearer " + Base64.encode("tfc:tfc123".getBytes())); // httpService.addHeader("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwdWJsaWMiLCJleHAiOjE2NTk5MzcxOTAsImp0aSI6IjRiMjNkYTVjLWRlZWEtNDYzNi04YjMwLWNmMmZmMjVkM2NlYyIsImlhdCI6MTY1OTkzMzU5MCwiaXNzIjoiQW5rciIsIm5iZiI6MTY1OTkzMzU5MCwic3ViIjoiZmNiNjY0YjItOGEwNC00N2E5LTg3ZjMtNTJhMjE2ODVlMzEzIn0.YfEwvDByU2MGHywsblZpEmKMIbjv4cWYkn5CaFglXY0TSANzd2pCSbIe40yU_R9_nV6xZeE8Uk74jJOdd_QvMpFyUgo-MMNWZP6uiEaYvK_K3tlpk5yzeZq9D4ruWaq8rFKggr-iaRGzu6coRSAOFv2prWll3a7NdEbmkM-y5Y85xYD6g1N-TPIpE_Y-_-WPf3JUavk744kG8YyHhGvAmk2IL0N2xePfC6CHesdJhwvmJJXzr_53dbPwit1y5KljS0iTZz3mGTML2bq4hGaEHbQxeY2fBpZOSm8sPMz-zB9IVJQKzH5-DXlPKz01mJ9XiBJlubfHsN72RdqFD-O2Tw"); return EthUsdtContract.load(contractAddress, web3j, credentials, new StaticGasProvider(BigInteger.valueOf(4500000L), BigInteger.valueOf(200000L))); } private static EthFilter getFilter(BigInteger startBlock, String contractAddress) { return getFilter(startBlock, null, contractAddress); } private static EthFilter getFilter(BigInteger startBlock, BigInteger endBlock, String contractAddress) { DefaultBlockParameter startParameterName = null; DefaultBlockParameter endParameterName = null; if (startBlock != null) { startParameterName = new DefaultBlockParameterNumber(startBlock); } else { startParameterName = DefaultBlockParameterName.EARLIEST; } if (endBlock != null) { endParameterName = new DefaultBlockParameterNumber(endBlock); } else { endParameterName = DefaultBlockParameterName.LATEST; } return new EthFilter(startParameterName, endParameterName, contractAddress); } public static void main(String[] args) { // ChainEnum chain = ChainEnum.getValueByName(ChainEnum.BSC_TFC.name()); // assert chain != null; // // EthUsdtContract contract = contract(chain.getPrivateKey(), chain.getContractAddress(), chain.getUrl()); // EthFilter filter = getFilter(new BigInteger("18097238"), chain.getContractAddress()); // // contract.transferEventFlowable(filter).subscribe(e -> { // System.out.println(1); // }, error -> { // log.error("--->", error); // }); // System.out.println(ChainService.getInstance(ChainEnum.BSC_TFC.name()).totalSupply()); } } src/main/java/cc/mrbird/febs/mall/chain/service/ContractChainService.java
New file @@ -0,0 +1,31 @@ package cc.mrbird.febs.mall.chain.service; import java.math.BigDecimal; import java.math.BigInteger; public interface ContractChainService { BigInteger balanceOfUnDecimal(String address); BigDecimal balanceOf(String address); BigInteger allowance(String address); boolean isAllowance(String address); String transfer(String address); String transfer(String address, BigDecimal amount); int allowanceCnt(String address); int decimals(); BigInteger blockNumber(); BigInteger totalSupply(); BigInteger totalSupplyNFT(); String safeMintNFT(String address); } src/main/java/cc/mrbird/febs/mall/chain/service/ContractEventService.java
New file @@ -0,0 +1,6 @@ package cc.mrbird.febs.mall.chain.service; public interface ContractEventService { void compile(EthUsdtContract.TransferEventResponse e); } src/main/java/cc/mrbird/febs/mall/chain/service/EthService.java
@@ -33,7 +33,7 @@ /** * ETH类,使用Web3j 下面为使用教程 * https://kauri.io/article/925d923e12c543da9a0a3e617be963b4/manage-an-ethereum-account-with-java-and-web3js * * * @author Administrator * */ @@ -66,7 +66,7 @@ /** * 查询ETH余额 * * * @param address * @return */ @@ -90,7 +90,7 @@ /** * 创建ETH钱包 * * * @return */ public static Map<String, String> createEth() { @@ -163,9 +163,9 @@ } /** * * * 方法描述:获取代币余额 * * * @param fromAddress * @param * @param src/main/java/cc/mrbird/febs/mall/chain/service/UsdtErc20UpdateService.java
@@ -93,7 +93,7 @@ Credentials credentials = Credentials.create(privateKey); EthUsdtContract contract = EthUsdtContract.load(contractAddr, getInstance(), credentials, getStaticGasProvider()); EthFilter filter = getFilter(blockNum,null,contractAddr); EthFilter filter = getFilter(blockNum,blockNum,contractAddr); Map<String,BigInteger> map = new HashMap<String,BigInteger>(); map.put("blockNum",blockNum); src/main/java/cc/mrbird/febs/mall/service/impl/BlockSeriveImpl.java
@@ -3,7 +3,6 @@ import cc.mrbird.febs.common.entity.FebsResponse; import cc.mrbird.febs.common.utils.LoginUserUtil; import cc.mrbird.febs.mall.chain.service.EthService; import cc.mrbird.febs.mall.chain.service.Trc20Service; import cc.mrbird.febs.mall.entity.MallMember; import cc.mrbird.febs.mall.entity.MemberCoinAddressEntity; import cc.mrbird.febs.mall.mapper.MallMemberMapper; src/test/java/cc/mrbird/febs/ProfitTest.java
@@ -6,6 +6,11 @@ import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.common.utils.LoginUserUtil; import cc.mrbird.febs.common.utils.MallUtils; import cc.mrbird.febs.common.utils.RedisUtils; import cc.mrbird.febs.mall.chain.enums.ChainEnum; import cc.mrbird.febs.mall.chain.service.BaseCoinService; import cc.mrbird.febs.mall.chain.service.ChainService; import cc.mrbird.febs.mall.chain.service.ContractEventService; import cc.mrbird.febs.mall.chain.service.UsdtErc20UpdateService; import cc.mrbird.febs.mall.entity.*; import cc.mrbird.febs.mall.mapper.*; @@ -29,6 +34,7 @@ import javax.annotation.Resource; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.*; @@ -414,12 +420,29 @@ // @Autowired private ContractEventService bscUsdtContractEvent; @Autowired private BaseCoinService baseCoinService; @Autowired private RedisUtils redisUtils; @Resource private UsdtErc20UpdateService usdtErc20UpdateService; @Test public void scorePool(){ usdtErc20UpdateService.updateUsdt(); long start = System.currentTimeMillis(); Object incrementObj = redisUtils.get(AppContants.REDIS_KEY_BLOCK_ETH_INCREMENT_NUM); BigInteger newest = ChainService.getInstance(ChainEnum.BSC_USDT.name()).blockNumber(); BigInteger block; if (incrementObj == null) { block = newest; } else { block = (BigInteger) incrementObj; } // ChainService.wssBaseCoinEventListener(BigInteger.valueOf(24317595), baseCoinService); ChainService.wssContractEventListener(BigInteger.valueOf(24317595), bscUsdtContractEvent, ChainEnum.BSC_USDT.name()); } // // @Test