Parcourir la source

Merge remote-tracking branch 'origin/master'

million il y a 1 an
Parent
commit
c5b9a1e70a
35 fichiers modifiés avec 1015 ajouts et 110 suppressions
  1. 10 1
      pom.xml
  2. 1 3
      src/main/java/com/ichaoj/ams/common/util/PoiUtils.java
  3. 28 0
      src/main/java/com/ichaoj/ams/controller/AirdropTaskController.java
  4. 0 9
      src/main/java/com/ichaoj/ams/controller/StatisticsController.java
  5. 4 0
      src/main/java/com/ichaoj/ams/entity/AmsAirdropTask.java
  6. 2 0
      src/main/java/com/ichaoj/ams/entity/AmsExecuteRecord.java
  7. 15 0
      src/main/java/com/ichaoj/ams/mapper/AmsExecuteRecordMapper.java
  8. 8 0
      src/main/java/com/ichaoj/ams/mapper/AmsTradeRecordMapper.java
  9. 5 0
      src/main/java/com/ichaoj/ams/request/execute/CreateExecute.java
  10. 7 0
      src/main/java/com/ichaoj/ams/request/task/CreateAirdropTask.java
  11. 17 1
      src/main/java/com/ichaoj/ams/response/execute/ExecuteResponse.java
  12. 4 0
      src/main/java/com/ichaoj/ams/response/statistics/PredictCostResponse.java
  13. 23 2
      src/main/java/com/ichaoj/ams/response/task/TaskResponse.java
  14. 26 0
      src/main/java/com/ichaoj/ams/script/IScript.java
  15. 30 0
      src/main/java/com/ichaoj/ams/script/ScriptContext.java
  16. 23 0
      src/main/java/com/ichaoj/ams/script/annotation/Script.java
  17. 20 0
      src/main/java/com/ichaoj/ams/script/annotation/ScriptParam.java
  18. 19 0
      src/main/java/com/ichaoj/ams/script/model/AirdropParam.java
  19. 24 0
      src/main/java/com/ichaoj/ams/script/model/AirdropWallet.java
  20. 92 0
      src/main/java/com/ichaoj/ams/script/util/Web3Util.java
  21. 27 0
      src/main/java/com/ichaoj/ams/script/zksync/era/SwapPath.java
  22. 25 0
      src/main/java/com/ichaoj/ams/script/zksync/era/SwapStep.java
  23. 16 0
      src/main/java/com/ichaoj/ams/script/zksync/era/TokenAmount.java
  24. 102 0
      src/main/java/com/ichaoj/ams/script/zksync/era/ZkSyncCrossScript.java
  25. 159 0
      src/main/java/com/ichaoj/ams/script/zksync/era/ZkSyncEraScript.java
  26. 6 0
      src/main/java/com/ichaoj/ams/service/IAmsTradeRecordService.java
  27. 5 0
      src/main/java/com/ichaoj/ams/service/impl/AmsAirdropProjectServiceImpl.java
  28. 60 36
      src/main/java/com/ichaoj/ams/service/impl/AmsExecuteRecordServiceImpl.java
  29. 78 35
      src/main/java/com/ichaoj/ams/service/impl/AmsTradeRecordServiceImpl.java
  30. 14 1
      src/main/resources/application-dev.yml
  31. 14 1
      src/main/resources/application-test.yml
  32. 24 19
      src/main/resources/mapper/AmsAirdropTaskMapper.xml
  33. 23 2
      src/main/resources/mapper/AmsExcuteRecordMapper.xml
  34. 10 0
      src/main/resources/mapper/AmsTradeRecordMapper.xml
  35. 94 0
      src/main/resources/templates/email/ReportEmailTemplate.html

+ 10 - 1
pom.xml

@@ -36,7 +36,7 @@
         <dependency>
             <groupId>org.web3j</groupId>
             <artifactId>core</artifactId>
-            <version>5.0.0</version>
+            <version>4.8.7</version>
         </dependency>
         <dependency>
             <groupId>cn.hutool</groupId>
@@ -90,6 +90,15 @@
             <artifactId>poi-ooxml-schemas</artifactId>
             <version>3.17</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.ichaoj</groupId>
+            <artifactId>super-whale-redisson-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ichaoj</groupId>
+            <artifactId>super-whale-email-starter</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 1 - 3
src/main/java/com/ichaoj/ams/common/util/PoiUtils.java

@@ -5,12 +5,10 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.poi.excel.ExcelUtil;
 import cn.hutool.poi.excel.ExcelWriter;
 import com.ichaoj.ams.response.statistics.SheetDTO;
-import org.apache.poi.ss.usermodel.Workbook;
-import org.apache.poi.util.IOUtils;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
-import java.io.*;
+import java.io.IOException;
 import java.net.URLEncoder;
 import java.util.List;
 

+ 28 - 0
src/main/java/com/ichaoj/ams/controller/AirdropTaskController.java

@@ -1,6 +1,7 @@
 package com.ichaoj.ams.controller;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ListUtil;
 import com.ichaoj.ams.entity.AmsAirdropTask;
 import com.ichaoj.ams.request.airdrop.PageProjectRequest;
 import com.ichaoj.ams.request.task.CreateAirdropTask;
@@ -9,6 +10,11 @@ import com.ichaoj.ams.request.task.UpdateAirdropTask;
 import com.ichaoj.ams.request.task.UpdateTaskStatusRequest;
 import com.ichaoj.ams.response.airdrop.AirdropProjectResponse;
 import com.ichaoj.ams.response.task.TaskResponse;
+import com.ichaoj.ams.script.IScript;
+import com.ichaoj.ams.script.ScriptContext;
+import com.ichaoj.ams.script.annotation.Script;
+import com.ichaoj.ams.script.annotation.ScriptParam;
+import com.ichaoj.ams.script.model.AirdropParam;
 import com.ichaoj.ams.service.IAmsAirdropTaskService;
 import com.ichaoj.common.annotation.AuthResource;
 import com.ichaoj.common.exception.ErrorServiceException;
@@ -21,6 +27,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -92,4 +99,25 @@ public class AirdropTaskController {
         List<TaskResponse> result = taskService.listTask();
         return PublicResult.success(result);
     }
+
+    @GetMapping("/params/{task-code}")
+    @AuthResource
+    @Operation(summary = "查询任务参数")
+    public PublicResult<List<AirdropParam>> getAirdropParam(@PathVariable("task-code") String taskCode) {
+        IScript script = ScriptContext.getScriptByCode(taskCode);
+        Script annotation = script.getClass().getAnnotation(Script.class);
+        ScriptParam[] params = annotation.params();
+        if (params != null && params.length > 0) {
+            List<AirdropParam> airdropParams = new ArrayList<>();
+            for (ScriptParam param : params) {
+                AirdropParam airdropParam = new AirdropParam();
+                airdropParam.setName(param.name());
+                airdropParam.setNote(param.note());
+                airdropParam.setValue(param.defaultValue());
+                airdropParams.add(airdropParam);
+            }
+            return PublicResult.success(airdropParams);
+        }
+        return PublicResult.success(ListUtil.empty());
+    }
 }

+ 0 - 9
src/main/java/com/ichaoj/ams/controller/StatisticsController.java

@@ -185,15 +185,6 @@ public class StatisticsController {
         }
     }
 
-    private File createProjectExcelFile() {
-        return null;
-    }
-
-    private File createProjectExcelFile(DailyCostRequest dailyCostRequest) {
-
-        return null;
-    }
-
     private BigDecimal getPrincipalCost(List<String> batchAddresses) {
         List<AmsTradeRecord> list = getTradeRecordList(batchAddresses);
         double sumAmount = list.stream().mapToDouble(t -> Double.parseDouble(t.getCurrentBalance())).sum();

+ 4 - 0
src/main/java/com/ichaoj/ams/entity/AmsAirdropTask.java

@@ -3,6 +3,7 @@ package com.ichaoj.ams.entity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+
 import java.io.Serializable;
 import java.time.LocalDateTime;
 
@@ -41,6 +42,9 @@ public class AmsAirdropTask implements Serializable {
      */
     private String taskName;
 
+
+    private String taskCode;
+
     /**
      * 合约地址
      */

+ 2 - 0
src/main/java/com/ichaoj/ams/entity/AmsExecuteRecord.java

@@ -44,6 +44,8 @@ public class AmsExecuteRecord implements Serializable {
      */
     private String taskId;
 
+    private String airdropParams;
+
     /**
      * 用户id
      */

+ 15 - 0
src/main/java/com/ichaoj/ams/mapper/AmsExecuteRecordMapper.java

@@ -1,7 +1,11 @@
 package com.ichaoj.ams.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ichaoj.ams.entity.AmsExecuteRecord;
+import com.ichaoj.ams.request.execute.PageExecuteRequest;
+import com.ichaoj.ams.response.execute.ExecuteResponse;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * <p>
@@ -13,4 +17,15 @@ import com.ichaoj.ams.entity.AmsExecuteRecord;
  */
 public interface AmsExecuteRecordMapper extends BaseMapper<AmsExecuteRecord> {
 
+    /**
+     * 分页查询执行
+     *
+     * @param buildPageObj 分页对象
+     * @param request      请求参数
+     * @param userId       用户id
+     * @return 分页结果
+     */
+    Page<ExecuteResponse> pageExecute(Page<AmsExecuteRecord> buildPageObj,
+                                      @Param("request") PageExecuteRequest request,
+                                      @Param("userId") String userId);
 }

+ 8 - 0
src/main/java/com/ichaoj/ams/mapper/AmsTradeRecordMapper.java

@@ -7,6 +7,7 @@ import com.ichaoj.ams.request.record.PageTradeRecordRequest;
 import com.ichaoj.ams.response.statistics.DailyCostResponse;
 import com.ichaoj.ams.response.record.TradeRecordResponse;
 import com.ichaoj.ams.response.statistics.ExportResponse;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
 
@@ -42,4 +43,11 @@ public interface AmsTradeRecordMapper extends BaseMapper<AmsTradeRecord> {
      * @return 报表数据
      */
     List<ExportResponse> getExportData(String userId);
+
+    /**
+     * 根据用户id查询用户投入列表
+     * @param userId 用户
+     * @return 列表
+     */
+    List<TradeRecordResponse> listByUserId(@Param("userId") String userId);
 }

+ 5 - 0
src/main/java/com/ichaoj/ams/request/execute/CreateExecute.java

@@ -1,8 +1,11 @@
 package com.ichaoj.ams.request.execute;
 
+import com.ichaoj.ams.script.model.AirdropParam;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.util.List;
+
 /**
  * @author : cjwen
  * @date : 2023/04/26 14:25
@@ -31,6 +34,8 @@ public class CreateExecute {
      */
     private String userId;
 
+    private List<AirdropParam> airdropParams;
+
     /**
      * 地址组名称
      */

+ 7 - 0
src/main/java/com/ichaoj/ams/request/task/CreateAirdropTask.java

@@ -24,6 +24,13 @@ public class CreateAirdropTask {
      */
     private String taskName;
 
+    private String taskCode;
+
+    /**
+     * 计划任务执行次数
+     */
+    private Integer planTimes;
+
     /**
      * 合约地址
      */

+ 17 - 1
src/main/java/com/ichaoj/ams/response/execute/ExecuteResponse.java

@@ -16,10 +16,16 @@ import java.util.List;
 public class ExecuteResponse {
 
     private String executeId;
+
     /**
      * 交互名称
      */
-    private Integer executeName;
+    private Integer executeTimes;
+
+    /**
+     * 空投项目id
+     */
+    private String projectId;
 
     /**
      * 任务id
@@ -60,12 +66,22 @@ public class ExecuteResponse {
      * 交互金额
      */
     private String amount;
+    private String totalAmount;
 
     /**
      * 最大gas
      */
     private String maxGas;
 
+    private String totalGas;
+
+    private String totalCount;
+
+    /**
+     * 执行状态;是否完成(0否,1是)
+     */
+    private Integer executeStatus;
+
     /**
      * 创建时间
      */

+ 4 - 0
src/main/java/com/ichaoj/ams/response/statistics/PredictCostResponse.java

@@ -19,6 +19,10 @@ public class PredictCostResponse {
     private Integer totalCount;
 
     private BigDecimal totalCost;
+    /**
+     * 实际投入
+     */
+    private BigDecimal actualCost;
 
     @Data
     public static class Predict {

+ 23 - 2
src/main/java/com/ichaoj/ams/response/task/TaskResponse.java

@@ -4,8 +4,6 @@ import com.ichaoj.ams.constant.AmsConstant;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import java.time.LocalDateTime;
-
 /**
  * @author : cjwen
  * @date : 2023/05/17 14:57
@@ -21,6 +19,9 @@ public class TaskResponse {
      */
     private String taskName;
 
+
+    private String taskCode;
+
     /**
      * 合约地址
      */
@@ -53,8 +54,15 @@ public class TaskResponse {
      */
     private String projectName;
 
+    /**
+     * 项目相关url
+     */
+    private String projectUrl;
+
     private Integer executeTimes;
 
+    private String executeId;
+
     /**
      * 执行状态;是否完成(0否,1是)
      */
@@ -62,5 +70,18 @@ public class TaskResponse {
 
     private String lastExecuteTime;
 
+    private String groupName;
+    private String maxGas;
+
+    /**
+     * 时间间隔最小分钟数
+     */
+    private Integer intervalMin;
+
+    /**
+     * 时间间隔最大分钟数
+     */
+    private Integer intervalMax;
+
 
 }

+ 26 - 0
src/main/java/com/ichaoj/ams/script/IScript.java

@@ -0,0 +1,26 @@
+package com.ichaoj.ams.script;
+
+
+import com.ichaoj.ams.script.model.AirdropParam;
+import com.ichaoj.ams.script.model.AirdropWallet;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 脚本接口
+ *
+ * @author wren
+ */
+public abstract class IScript {
+
+
+    /**
+     * 运行脚本
+     *
+     * @param params 参数
+     * @return txId
+     */
+    public abstract String run(Map<String, AirdropParam> params, AirdropWallet airdropWallet);
+
+}

+ 30 - 0
src/main/java/com/ichaoj/ams/script/ScriptContext.java

@@ -0,0 +1,30 @@
+package com.ichaoj.ams.script;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.ichaoj.ams.script.annotation.Script;
+import com.ichaoj.common.exception.ErrorServiceException;
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author wren
+ */
+public class ScriptContext {
+
+
+    public static IScript getScriptByCode(String code) {
+        Map<String, IScript> beansOfType = SpringUtil.getBeansOfType(IScript.class);
+        Optional<IScript> optional = beansOfType
+                .values()
+                .stream()
+                .filter(script -> {
+                    Script annotation = script.getClass().getAnnotation(Script.class);
+                    return annotation != null && annotation.name().equals(code);
+                })
+                .findFirst();
+        return optional.orElseThrow(() -> new ErrorServiceException("脚本不存在"));
+    }
+
+
+}

+ 23 - 0
src/main/java/com/ichaoj/ams/script/annotation/Script.java

@@ -0,0 +1,23 @@
+package com.ichaoj.ams.script.annotation;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author wren
+ * @since 2022-06-07
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Component
+public @interface Script {
+    String name() default "";
+
+    ScriptParam[] params() default {};
+
+
+}

+ 20 - 0
src/main/java/com/ichaoj/ams/script/annotation/ScriptParam.java

@@ -0,0 +1,20 @@
+package com.ichaoj.ams.script.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author wren
+ * @since 2022-06-07
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ScriptParam {
+    String name() default "";
+
+    String note() default "";
+
+    String defaultValue() default "";
+}

+ 19 - 0
src/main/java/com/ichaoj/ams/script/model/AirdropParam.java

@@ -0,0 +1,19 @@
+package com.ichaoj.ams.script.model;
+
+import lombok.Data;
+
+/**
+ * airdrop script param
+ *
+ * @author wren
+ */
+@Data
+public class AirdropParam {
+
+
+    private String name;
+    private String note;
+    private String value;
+
+
+}

+ 24 - 0
src/main/java/com/ichaoj/ams/script/model/AirdropWallet.java

@@ -0,0 +1,24 @@
+package com.ichaoj.ams.script.model;
+
+import lombok.Data;
+
+import java.math.BigInteger;
+
+/**
+ * airdrop wallet
+ *
+ * @author wren
+ */
+@Data
+public class AirdropWallet {
+
+    private String address;
+
+    private String privateKey;
+    /**
+     * 公链原生币余额
+     */
+    private BigInteger nativeCoinBalance;
+
+
+}

+ 92 - 0
src/main/java/com/ichaoj/ams/script/util/Web3Util.java

@@ -0,0 +1,92 @@
+package com.ichaoj.ams.script.util;
+
+import com.ichaoj.ams.script.model.AirdropWallet;
+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.protocol.Web3j;
+import org.web3j.protocol.core.DefaultBlockParameterName;
+import org.web3j.protocol.core.methods.request.Transaction;
+import org.web3j.protocol.core.methods.response.EthCall;
+import org.web3j.protocol.core.methods.response.EthEstimateGas;
+import org.web3j.protocol.core.methods.response.EthGetBalance;
+import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
+import org.web3j.utils.Convert;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Web3Util {
+
+    public static BigInteger getNonce(Web3j web3j, String addr) {
+        try {
+            EthGetTransactionCount getNonce = web3j.ethGetTransactionCount(addr, DefaultBlockParameterName.PENDING).send();
+            if (getNonce == null) {
+                throw new RuntimeException("net error");
+            }
+            return getNonce.getTransactionCount();
+        } catch (IOException e) {
+            throw new RuntimeException("net error");
+        }
+    }
+
+    public static BigDecimal getBalance(Web3j web3j, String address) {
+        try {
+            EthGetBalance ethGetBalance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
+            return Convert.fromWei(new BigDecimal(ethGetBalance.getBalance()), Convert.Unit.ETHER);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static BigInteger getTokenBalance(Web3j web3j, String fromAddress, String contractAddress) {
+        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;
+    }
+
+    public static BigInteger getTransactionGasLimit(Web3j web3j, Transaction transaction) {
+        try {
+            EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
+            if (ethEstimateGas.hasError()) {
+                throw new RuntimeException(ethEstimateGas.getError().getMessage());
+            }
+            return ethEstimateGas.getAmountUsed();
+        } catch (IOException e) {
+            throw new RuntimeException("net error");
+        }
+    }
+
+    public static Credentials getCredentials(AirdropWallet wallet) {
+        return Credentials.create(wallet.getPrivateKey());
+    }
+}

+ 27 - 0
src/main/java/com/ichaoj/ams/script/zksync/era/SwapPath.java

@@ -0,0 +1,27 @@
+package com.ichaoj.ams.script.zksync.era;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.web3j.abi.datatypes.Address;
+import org.web3j.abi.datatypes.DynamicArray;
+import org.web3j.abi.datatypes.StaticStruct;
+import org.web3j.abi.datatypes.Uint;
+/**
+ * @author wrenj
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SwapPath extends StaticStruct {
+    public DynamicArray<SwapStep> steps;
+    public Address tokenIn;
+
+    public Uint amountIn;
+
+    public SwapPath(DynamicArray<SwapStep> steps, Address tokenIn, Uint amountIn) {
+        super(steps, tokenIn, amountIn);
+        this.steps = steps;
+        this.tokenIn = tokenIn;
+        this.amountIn = amountIn;
+    }
+
+}

+ 25 - 0
src/main/java/com/ichaoj/ams/script/zksync/era/SwapStep.java

@@ -0,0 +1,25 @@
+package com.ichaoj.ams.script.zksync.era;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.web3j.abi.datatypes.Address;
+import org.web3j.abi.datatypes.DynamicBytes;
+import org.web3j.abi.datatypes.StaticStruct;
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SwapStep extends StaticStruct {
+
+    public Address pool;
+    public DynamicBytes data;
+    public Address callback;
+
+    public DynamicBytes callbackData;
+
+    public SwapStep(Address pool, DynamicBytes data, Address callback, DynamicBytes callbackData) {
+        super(pool, data, callback, callbackData);
+        this.pool = pool;
+        this.data = data;
+        this.callback = callback;
+        this.callbackData = callbackData;
+    }
+}

+ 16 - 0
src/main/java/com/ichaoj/ams/script/zksync/era/TokenAmount.java

@@ -0,0 +1,16 @@
+package com.ichaoj.ams.script.zksync.era;
+
+import org.web3j.abi.datatypes.Address;
+import org.web3j.abi.datatypes.StaticStruct;
+import org.web3j.abi.datatypes.Uint;
+
+public class TokenAmount extends StaticStruct {
+    public Address token;
+   public Uint amount;
+
+
+    public TokenAmount(Address token, Uint amount) {
+        this.token = token;
+        this.amount = amount;
+    }
+}

+ 102 - 0
src/main/java/com/ichaoj/ams/script/zksync/era/ZkSyncCrossScript.java

@@ -0,0 +1,102 @@
+package com.ichaoj.ams.script.zksync.era;
+
+import cn.hutool.core.collection.ListUtil;
+import com.ichaoj.ams.script.IScript;
+import com.ichaoj.ams.script.annotation.Script;
+import com.ichaoj.ams.script.annotation.ScriptParam;
+import com.ichaoj.ams.script.model.AirdropParam;
+import com.ichaoj.ams.script.model.AirdropWallet;
+import com.ichaoj.ams.script.util.Web3Util;
+import lombok.SneakyThrows;
+import org.jetbrains.annotations.NotNull;
+import org.web3j.abi.FunctionEncoder;
+import org.web3j.abi.TypeReference;
+import org.web3j.abi.datatypes.*;
+import org.web3j.abi.datatypes.generated.Bytes32;
+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.Response;
+import org.web3j.protocol.core.methods.request.Transaction;
+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.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author wrenj
+ */
+@Script(name = "zksync-cross",
+        params = {
+                @ScriptParam(name = "amount", note = "跨链金额"),
+                @ScriptParam(name = "value", note = "转账value,要求覆盖L2的手续费"),
+                @ScriptParam(name = "l2GasLimit", note = "L2 gas limit", defaultValue = "901876"),
+
+        })
+public class ZkSyncCrossScript extends IScript {
+
+    @SneakyThrows
+    @Override
+    public String run(Map<String, AirdropParam> params, AirdropWallet airdropWallet) {
+        String mailBox = "0x32400084c286cf3e17e7b677ea9583e60a000324";
+        Web3j web3j = Web3j.build(new HttpService("https://eth-mainnet.g.alchemy.com/v2/zS4UDTUziif2jL82eFGoP_sddA8aVgEV"));
+        List<Type> inputParameters = new ArrayList<>();
+        //recipient
+        inputParameters.add(new Address(airdropWallet.getAddress()));
+        //amount
+        inputParameters.add(new Uint256(Convert.toWei("0.001", Convert.Unit.ETHER).toBigInteger()));
+        //bytes
+        inputParameters.add(DynamicBytes.DEFAULT);
+        //l2 gas limit
+        inputParameters.add(new Uint256(701876));
+        //_l2GasPerPub-dataByteLimit
+        inputParameters.add(new Uint256(800));
+        //data
+        inputParameters.add(new DynamicArray<>(DynamicArray.class));
+        //_refundRecipient
+        inputParameters.add(new Address(airdropWallet.getAddress()));
+        Function requestL2Transaction = new Function("requestL2Transaction", inputParameters, ListUtil.of(new TypeReference<Bytes32>() {
+        }));
+        BigInteger nonce = Web3Util.getNonce(web3j, airdropWallet.getAddress());
+        BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
+        String l2Encoder = FunctionEncoder.encode(requestL2Transaction);
+
+        Transaction callTransaction = Transaction.createFunctionCallTransaction(
+                airdropWallet.getAddress(),
+                nonce,
+                gasPrice,
+                BigInteger.valueOf(0),
+                mailBox,
+                Convert.toWei("0.002", Convert.Unit.ETHER).toBigInteger(),
+                l2Encoder);
+        BigInteger gasLimit = Web3Util.getTransactionGasLimit(web3j, callTransaction);
+
+        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce,
+                gasPrice,
+                gasLimit,
+                mailBox,
+                Convert.toWei("0.002", Convert.Unit.ETHER).toBigInteger(),
+                l2Encoder);
+        //签名
+        Credentials credentials = Web3Util.getCredentials(airdropWallet);
+        byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
+        String hexValue = Numeric.toHexString(signMessage);
+        //发送交易
+        EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
+        Response.Error error = ethSendTransaction.getError();
+        if (error != null) {
+            throw new RuntimeException(error.getMessage());
+        }
+        return ethSendTransaction.getTransactionHash();
+    }
+
+
+}

+ 159 - 0
src/main/java/com/ichaoj/ams/script/zksync/era/ZkSyncEraScript.java

@@ -0,0 +1,159 @@
+package com.ichaoj.ams.script.zksync.era;
+
+import cn.hutool.core.collection.ListUtil;
+import com.ichaoj.ams.script.IScript;
+import com.ichaoj.ams.script.annotation.Script;
+import com.ichaoj.ams.script.annotation.ScriptParam;
+import com.ichaoj.ams.script.model.AirdropParam;
+import com.ichaoj.ams.script.model.AirdropWallet;
+import com.ichaoj.ams.script.util.Web3Util;
+import lombok.SneakyThrows;
+import org.web3j.abi.FunctionEncoder;
+import org.web3j.abi.TypeReference;
+import org.web3j.abi.datatypes.*;
+import org.web3j.abi.datatypes.generated.Uint8;
+import org.web3j.protocol.Web3j;
+import org.web3j.protocol.core.DefaultBlockParameterName;
+import org.web3j.protocol.core.methods.request.Transaction;
+import org.web3j.protocol.core.methods.response.EthGasPrice;
+import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
+import org.web3j.protocol.http.HttpService;
+import org.web3j.utils.Convert;
+import org.web3j.utils.Numeric;
+
+import java.math.BigInteger;
+import java.util.Map;
+
+/**
+ * zkSyncEra script
+ * //todo 假设原生代币已经转入了子钱包
+ *
+ * @author wren
+ */
+@Script(name = "zkSyncEra",
+        params = {
+
+                @ScriptParam(name = "swapAmount", note = "swap金额")
+        }
+)
+public class ZkSyncEraScript extends IScript {
+    private final String POOL_FACTORY = "0xf2DAd89f2788a8CD54625C60b55cD3d2D0ACa7Cb";
+    private final String W_ETH = "0x5aea5775959fbc2557cc8789bc1bf90a239d9a91";
+    private final String USDC = "0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4";
+
+    private final String ROUTER = "0x2da10A1e27bF85cEdD8FFb1AbBe97e53391C0295";
+
+    private final String ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
+
+// 0x2cc4081e
+// 0000000000000000000000000000000000000000000000000000000000000060
+// 00000000000000000000000000000000000000000000000000000000001b6171
+// 00000000000000000000000000000000000000000000000000000000647084d2
+// 0000000000000000000000000000000000000000000000000000000000000001
+// 0000000000000000000000000000000000000000000000000000000000000020
+// 0000000000000000000000000000000000000000000000000000000000000060
+// 0000000000000000000000000000000000000000000000000000000000000000
+// 00000000000000000000000000000000000000000000000000038d7ea4c68000
+// 0000000000000000000000000000000000000000000000000000000000000001
+// 0000000000000000000000000000000000000000000000000000000000000020
+// 00000000000000000000000080115c708e12edd42e504c1cd52aea96c547c05c
+// 0000000000000000000000000000000000000000000000000000000000000080
+// 0000000000000000000000000000000000000000000000000000000000000000
+// 0000000000000000000000000000000000000000000000000000000000000100
+// 0000000000000000000000000000000000000000000000000000000000000060
+// 0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91
+// 000000000000000000000000368715f09c1ab5e0b55bf5ba19cd887189a28dbe  address
+// 0000000000000000000000000000000000000000000000000000000000000002
+// 0000000000000000000000000000000000000000000000000000000000000000
+
+// 0x0054ed6a
+// 0000000000000000000000000000000000000000000000000000000000000060
+// 0000000000000000000000000000000000000000000000000000000000000000
+// 00000000000000000000000000000000000000000000000000000000647570f3
+// 0000000000000000000000000000000000000000000000000000000000000001
+// 0000000000000000000000000000000000000000000000000000000000000001
+// 00000000000000000000000080115c708e12edd42e504c1cd52aea96c547c05c
+// 0000000000000000000000000000000000000000000000000000000000000060
+// 0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91
+// 000000000000000000000000368715f09c1ab5e0b55bf5ba19cd887189a28dbe
+// 0000000000000000000000000000000000000000000000000000000000000001
+// 0000000000000000000000000000000000000000000000000000000000000000
+// 0000000000000000000000000000000000000000000000000000000000000000
+// 0000000000000000000000000000000000000000000000000000000000000000
+// 00000000000000000000000000000000000000000000000000005af3107a4000
+
+    @SneakyThrows
+    @Override
+    public String run(Map<String, AirdropParam> params, AirdropWallet airdropWallet) {
+
+
+        Web3j web3j = Web3j.build(new HttpService("https://mainnet.era.zksync.io"));
+        Address poolAddress = new Address("0x80115c708E12eDd42E504c1cD52Aea96C547c05c");
+
+//        0x0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91000000000000000000000000368715f09c1ab5e0b55bf5ba19cd887189a28dbe0000000000000000000000000000000000000000000000000000000000000001
+//        0x0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91000000000000000000000000368715f09c1ab5e0b55bf5ba19cd887189a28dbe0000000000000000000000000000000000000000000000000000000000000001
+        String data = "0x" + FunctionEncoder.encodeConstructor(ListUtil.of(
+                new Address(W_ETH),
+                new Address(airdropWallet.getAddress()),
+                new Uint8(1)));
+        // build SwapSteps  --step1
+        DynamicArray<StaticStruct> steps = new DynamicArray<>(StaticStruct.class, ListUtil.of(new StaticStruct(
+                poolAddress,
+                new DynamicBytes(Numeric.hexStringToByteArray(data)),
+                new Address(ZERO_ADDRESS),
+                new DynamicBytes(Numeric.hexStringToByteArray("0x")))));
+        BigInteger swapAmount = Convert.toWei(params.get("swapAmount").getValue(), Convert.Unit.ETHER).toBigInteger();
+        // build SwapPaths --step2
+        DynamicArray<StaticStruct> paths = new DynamicArray<>(StaticStruct.class, ListUtil.of(new StaticStruct(
+                steps,
+                new Address(ZERO_ADDRESS),
+                new Uint(swapAmount))));
+
+        Function swap = new Function("swap",
+                ListUtil.of(
+                        paths,
+                        new Uint(BigInteger.ZERO),
+                        new Uint(BigInteger.valueOf(System.currentTimeMillis() / 1000 + 1800))),
+                ListUtil.of(new TypeReference<TokenAmount>() {
+                }));
+        String swapEncoder = FunctionEncoder.encode(swap);
+
+        BigInteger nonce;
+        EthGetTransactionCount transactionCount = web3j.ethGetTransactionCount(airdropWallet.getAddress(), DefaultBlockParameterName.PENDING).send();
+        if (transactionCount == null) {
+            throw new RuntimeException("Failed to get nonce");
+        }
+        nonce = transactionCount.getTransactionCount();
+        BigInteger gasPrice;
+        EthGasPrice ethGasPrice = web3j.ethGasPrice().sendAsync().get();
+        if (ethGasPrice == null) {
+            throw new RuntimeException("Failed to get GasPrice");
+        }
+        gasPrice = ethGasPrice.getGasPrice();
+        Transaction callTransaction = Transaction.createFunctionCallTransaction(
+                airdropWallet.getAddress(),
+                nonce,
+                gasPrice,
+                BigInteger.valueOf(0),
+                ROUTER,
+                swapAmount,
+                swapEncoder);
+
+
+        BigInteger gasLimit = Web3Util.getTransactionGasLimit(web3j, callTransaction);
+
+//        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, ROUTER, swapEncoder);
+//        //签名
+//        Credentials credentials = Web3Util.getCredentials(airdropWallet);
+//        byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
+//        String hexValue = Numeric.toHexString(signMessage);
+//        //发送交易
+//        EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
+//        Response.Error error = ethSendTransaction.getError();
+//        if (error != null) {
+//            throw new RuntimeException(error.getMessage());
+//        }
+        return gasLimit.toString();
+
+    }
+}

+ 6 - 0
src/main/java/com/ichaoj/ams/service/IAmsTradeRecordService.java

@@ -59,4 +59,10 @@ public interface IAmsTradeRecordService extends IService<AmsTradeRecord> {
      */
     List<ExportResponse> getExportData();
 
+    /**
+     * 根据用户id查询用户投入列表
+     * @param userId 用户
+     * @return 列表
+     */
+    List<TradeRecordResponse> listByUserId(String userId);
 }

+ 5 - 0
src/main/java/com/ichaoj/ams/service/impl/AmsAirdropProjectServiceImpl.java

@@ -3,6 +3,7 @@ package com.ichaoj.ams.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.comparator.CompareUtil;
+import cn.hutool.core.math.MathUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -17,6 +18,7 @@ import com.ichaoj.ams.request.airdrop.CreateAirdropProject;
 import com.ichaoj.ams.request.airdrop.PageProjectRequest;
 import com.ichaoj.ams.request.airdrop.UpdateAirdropProject;
 import com.ichaoj.ams.response.airdrop.AirdropProjectResponse;
+import com.ichaoj.ams.response.record.TradeRecordResponse;
 import com.ichaoj.ams.response.statistics.PredictCostResponse;
 import com.ichaoj.ams.service.IAmsAirdropProjectService;
 import com.ichaoj.ams.service.IAmsAirdropTaskService;
@@ -146,6 +148,9 @@ public class AmsAirdropProjectServiceImpl extends SuperWhaleServiceImpl<AmsAirdr
         response.setTotalCost(BigDecimal.valueOf(list.stream().mapToDouble(p -> Double.parseDouble(p.getEstimatedCost())).sum()));
         List<PredictCostResponse.Predict> predicts = BeanUtil.copyToList(list, PredictCostResponse.Predict.class);
         response.setPredicts(predicts.stream().sorted((p1, p2) -> CompareUtil.compare(p2.getEstimatedCost(), p1.getEstimatedCost())).collect(Collectors.toList()));
+        List<TradeRecordResponse> tradeList = tradeRecordService.listByUserId(SuperWhaleContext.getContext(PublicUserInfo.class).getUserId());
+
+        response.setActualCost(BigDecimal.valueOf(tradeList.stream().mapToDouble(t -> Double.sum(Double.parseDouble(t.getGas()), Double.parseDouble(t.getAmount()))).sum()));
         return response;
     }
 

+ 60 - 36
src/main/java/com/ichaoj/ams/service/impl/AmsExecuteRecordServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson2.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -14,6 +15,10 @@ import com.ichaoj.ams.request.execute.CreateExecute;
 import com.ichaoj.ams.request.execute.PageExecuteRequest;
 import com.ichaoj.ams.request.execute.UpdateExecute;
 import com.ichaoj.ams.response.execute.ExecuteResponse;
+import com.ichaoj.ams.script.IScript;
+import com.ichaoj.ams.script.ScriptContext;
+import com.ichaoj.ams.script.model.AirdropParam;
+import com.ichaoj.ams.script.model.AirdropWallet;
 import com.ichaoj.ams.service.*;
 import com.ichaoj.common.exception.ErrorServiceException;
 import com.ichaoj.common.model.PublicPage;
@@ -26,9 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -55,13 +58,14 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
 
     @Override
     public PublicPage<ExecuteResponse> pageExecute(PageExecuteRequest executeRequest) {
-        LambdaQueryWrapper<AmsExecuteRecord> wrapper = Wrappers.lambdaQuery();
-        wrapper
-                .eq(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getExecuteId, executeRequest.getQueryValue())
-                .or(w -> w.like(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getGroupName, executeRequest.getQueryValue())
-                        .or().like(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getTaskId, executeRequest.getQueryValue()))
-                .orderByDesc(AmsExecuteRecord::getCreateTime);
-        Page<AmsExecuteRecord> result = this.page(this.buildPageObj(executeRequest), wrapper);
+//        LambdaQueryWrapper<AmsExecuteRecord> wrapper = Wrappers.lambdaQuery();
+//        wrapper
+//                .eq(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getExecuteId, executeRequest.getQueryValue())
+//                .or(w -> w.like(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getGroupName, executeRequest.getQueryValue())
+//                        .or().like(StrUtil.isNotBlank(executeRequest.getQueryValue()), AmsExecuteRecord::getTaskId, executeRequest.getQueryValue()))
+//                .orderByDesc(AmsExecuteRecord::getCreateTime);
+
+        Page<ExecuteResponse> result = this.baseMapper.pageExecute(this.buildPageObj(executeRequest), executeRequest, SuperWhaleContext.getContext(PublicUserInfo.class).getUserId());
         return this.convertPublicPage(result, s -> BeanUtil.copyProperties(s, ExecuteResponse.class));
     }
 
@@ -86,6 +90,7 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
         amsExecute.setCreateTime(LocalDateTime.now());
         amsExecute.setTaskId(task.getAmsTaskId());
         amsExecute.setProjectId(project.getAmsProjectId());
+        amsExecute.setAirdropParams(JSON.toJSONString(createExecute.getAirdropParams()));
         // todo 查询是第几次执行
         this.save(amsExecute);
         if (StrUtil.isBlank(createExecute.getPassword())) {
@@ -93,12 +98,12 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
         }
         // todo 验证密码是否正确
 
-        ThreadUtil.execute(() -> tradeService.randomTrans(accountList
-                , createExecute.getIntervalMin()
-                , createExecute.getIntervalMax()
-                , createExecute.getAmount()
-                , createExecute.getMaxGas()
-                , amsExecute.getExecuteId()));
+//        ThreadUtil.execute(() -> tradeService.randomTrans(accountList
+//                , createExecute.getIntervalMin()
+//                , createExecute.getIntervalMax()
+//                , createExecute.getAmount()
+//                , createExecute.getMaxGas()
+//                , amsExecute.getExecuteId()));
 
     }
 
@@ -154,31 +159,50 @@ public class AmsExecuteRecordServiceImpl extends SuperWhaleServiceImpl<AmsExecut
 
     @Scheduled(cron = "0/59 * * * * ? ")
     public void scanExecuteStatus() {
-        List<AmsExecuteRecord> list = this.list();
-        for (AmsExecuteRecord executeRecord : list) {
-            // 验证用户执行的状态
+        LambdaQueryWrapper<AmsExecuteRecord> eq = Wrappers.lambdaQuery(AmsExecuteRecord.class)
+                .eq(AmsExecuteRecord::getExecuteStatus, 0);
+        List<AmsExecuteRecord> amsExecuteRecords = this.list(eq);
+//        List<AmsExecuteRecord> list = this.list();
+        for (AmsExecuteRecord executeRecord : amsExecuteRecords) {
             String executeId = executeRecord.getExecuteId();
             String userId = executeRecord.getUserId();
             String groupName = executeRecord.getGroupName();
             List<AmsAddressAccount> accounts = accountService.getByGroupNameAndUserId(groupName, userId);
-            Set<String> predictSet = accounts.stream().map(AmsAddressAccount::getAddress).collect(Collectors.toSet());
-            List<AmsTradeRecord> tradeRecords = tradeService.list(new LambdaQueryWrapper<AmsTradeRecord>()
-                    .eq(AmsTradeRecord::getExecuteId, executeId));
-            Set<String> actualSet = tradeRecords.stream().map(AmsTradeRecord::getAddress).collect(Collectors.toSet());
-            // 判断交易记录的地址是否
-            if (CollectionUtil.containsAll(predictSet, actualSet) && CollectionUtil.containsAll(actualSet, predictSet)) {
-                Set<String> confirmSet = new HashSet<>();
-                for (AmsTradeRecord tradeRecord : tradeRecords) {
-                    if (tradeRecord.getStatus() == 1) {
-                        confirmSet.add(tradeRecord.getAddress());
-                    }
-                }
-                // 修改状态
-                if (CollectionUtil.containsAll(confirmSet, actualSet) && CollectionUtil.containsAll(actualSet, confirmSet)) {
-                    executeRecord.setExecuteStatus(1);
-                    this.updateById(executeRecord);
-                }
+            AmsAirdropTask task = taskService.getById(executeRecord.getTaskId());
+            String taskCode = task.getTaskCode();
+            IScript script = ScriptContext.getScriptByCode(taskCode);
+            String airdropParams = executeRecord.getAirdropParams();
+            List<AirdropParam> params = JSON.parseArray(airdropParams, AirdropParam.class);
+            Map<String, AirdropParam> paramMap = new HashMap<>();
+            for (AirdropParam param : params) {
+                paramMap.put(param.getName(), param);
             }
+//            accounts.stream().map(account->{
+//                AirdropWallet wallet = new AirdropWallet();
+//                wallet.setAddress(account.getAddress());
+//                String keystore = account.getKeystore();
+//                accountService.getPrivateKeyByKeystore(keystore, executeId);
+//            })
+
+
+//            Set<String> predictSet = accounts.stream().map(AmsAddressAccount::getAddress).collect(Collectors.toSet());
+//            List<AmsTradeRecord> tradeRecords = tradeService.list(new LambdaQueryWrapper<AmsTradeRecord>()
+//                    .eq(AmsTradeRecord::getExecuteId, executeId));
+//            Set<String> actualSet = tradeRecords.stream().map(AmsTradeRecord::getAddress).collect(Collectors.toSet());
+//            // 判断交易记录的地址是否
+//            if (CollectionUtil.containsAll(predictSet, actualSet) && CollectionUtil.containsAll(actualSet, predictSet)) {
+//                Set<String> confirmSet = new HashSet<>();
+//                for (AmsTradeRecord tradeRecord : tradeRecords) {
+//                    if (tradeRecord.getStatus() == 1) {
+//                        confirmSet.add(tradeRecord.getAddress());
+//                    }
+//                }
+//                // 修改状态
+//                if (CollectionUtil.containsAll(confirmSet, actualSet) && CollectionUtil.containsAll(actualSet, confirmSet)) {
+//                    executeRecord.setExecuteStatus(1);
+//                    this.updateById(executeRecord);
+//                }
+//            }
         }
     }
 }

+ 78 - 35
src/main/java/com/ichaoj/ams/service/impl/AmsTradeRecordServiceImpl.java

@@ -1,11 +1,17 @@
 package com.ichaoj.ams.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.RandomUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ichaoj.ams.entity.AmsAddressAccount;
+import com.ichaoj.ams.entity.AmsExecuteRecord;
 import com.ichaoj.ams.entity.AmsTradeRecord;
 import com.ichaoj.ams.mapper.AmsTradeRecordMapper;
 import com.ichaoj.ams.request.record.CreateTradeRecordRequest;
@@ -21,6 +27,7 @@ import com.ichaoj.mybatis.service.SuperWhaleServiceImpl;
 import com.ichaoj.web.context.SuperWhaleContext;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -43,7 +50,7 @@ import java.util.stream.Collectors;
 @Slf4j
 public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRecordMapper, AmsTradeRecord> implements IAmsTradeRecordService {
 
-    private final static ScheduledExecutorService AMS_SCHEDULER = Executors.newScheduledThreadPool(5);
+//    private final static ScheduledExecutorService AMS_SCHEDULER = Executors.newScheduledThreadPool(5);
 
     @Resource
     @Lazy
@@ -65,49 +72,66 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
         for (String address : addresses) {
             addressMap.put(address, false);
         }
-
-        doTrans(intervalMin, intervalMax, amount, maxGas, executeId, addressMap, set, addresses);
+        prepareTrans(intervalMin, intervalMax, amount, maxGas, executeId, addressMap, set, addresses);
     }
 
     @Transactional(rollbackFor = Exception.class)
-    protected void doTrans(Integer intervalMin, Integer intervalMax, String amount, String maxGas, String executeId, Map<String, Boolean> addressMap, Set<String> set, List<String> addresses) {
-        while (set.size() < addresses.size() - 1) {
-            for (int i = 0; i < addresses.size(); i++) {
-                BigDecimal gas = getCurrentGasPrice(maxGas);
-                log.info("当前gas: {}", gas);
-                BigDecimal currenAmount = getCurrenAmount(amount);
-                log.info("amount: {}", currenAmount);
-                String address = getUnusedAddress(addressMap, addresses);
-                int sleepSeconds = RandomUtil.randomInt(intervalMin, intervalMax);
-                long sleep = sleepSeconds * 60L + RandomUtil.randomInt(1, 30);
+    protected synchronized void prepareTrans(Integer intervalMin, Integer intervalMax, String amount, String maxGas, String executeId, Map<String, Boolean> addressMap, Set<String> set, List<String> addresses) {
+        int i = 0;
+        while (set.size() < addresses.size()) {
+            BigDecimal gas = getCurrentGasPrice(maxGas);
+            BigDecimal currenAmount = getCurrenAmount(amount);
+            String address = getUnusedAddress(addressMap, addresses);
+
+            AmsTradeRecord record = this.getOne(
+                    new LambdaQueryWrapper<AmsTradeRecord>()
+                            .eq(AmsTradeRecord::getExecuteId, executeId)
+                            .eq(AmsTradeRecord::getAddress, address)
+            );
+            if (record != null) {
+                break;
+            }
+            addressMap.put(address, true);
+            set.add(address);
+            int sleepSeconds = RandomUtil.randomInt(intervalMin, intervalMax);
+            long sleep = sleepSeconds * 60L + RandomUtil.randomInt(1, 30);
+            if (i == 0) {
                 log.info("执行器{} ,当前总共有{} 条交易需要执行,当前是第{} 笔交易,第{}笔交易需要等待{} 分钟",
                         executeId,
                         addresses.size(),
                         i + 1,
                         i + 2,
                         sleep / 60);
-                if (i == 0) {
-                    executeTrans(executeId, address, gas, currenAmount);
-                } else {
-                    AmsTradeRecord record = this.getOne(
-                            new LambdaQueryWrapper<AmsTradeRecord>()
-                                    .eq(AmsTradeRecord::getExecuteId, executeId)
-                                    .eq(AmsTradeRecord::getAddress, address)
-                    );
-
-                    if (record == null) {
-                        AMS_SCHEDULER.schedule(() -> {
-                            // todo 调用链上交易
-
-                            //交易入库
-                            executeTrans(executeId, address, gas, currenAmount);
-
-                        }, sleep, TimeUnit.SECONDS);
-                    }
+                log.info("当前gas: {}", gas);
+                log.info("amount: {}", currenAmount);
+                executeTrans(executeId, address, gas, currenAmount);
+                ThreadUtil.safeSleep(sleep);
+                i++;
+            } else {
+                AmsTradeRecord tradeRecord = this.getOne(
+                        new LambdaQueryWrapper<AmsTradeRecord>()
+                                .eq(AmsTradeRecord::getExecuteId, executeId)
+                                .eq(AmsTradeRecord::getAddress, address)
+                );
+
+                if (tradeRecord == null) {
+                    log.info("执行器{} ,当前总共有{} 条交易需要执行,当前是第{} 笔交易,第{}笔交易需要等待{} 分钟",
+                            executeId,
+                            addresses.size(),
+                            i + 1,
+                            i + 2,
+                            sleep / 60);
+                    log.info("当前gas: {}", gas);
+                    log.info("amount: {}", currenAmount);
+//                    AMS_SCHEDULER.schedule(() -> {
+//                        // todo 调用链上交易
+//
+//                        //交易入库
+//                        executeTrans(executeId, address, gas, currenAmount);
+//
+//                    }, sleep, TimeUnit.SECONDS);
+                    i++;
                 }
-
-                addressMap.put(address, true);
-                set.add(address);
             }
         }
     }
@@ -139,6 +163,11 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
         return this.baseMapper.getExportData(userId);
     }
 
+    @Override
+    public List<TradeRecordResponse> listByUserId(String userId) {
+        return this.baseMapper.listByUserId(userId);
+    }
+
     private BigDecimal getCurrenAmount(String amount) {
         return RandomUtil.randomBigDecimal(BigDecimal.ZERO, new BigDecimal(amount));
     }
@@ -179,8 +208,22 @@ public class AmsTradeRecordServiceImpl extends SuperWhaleServiceImpl<AmsTradeRec
     }
 
     private String getRandomAddress(List<String> accountList) {
-        int randomInt = RandomUtil.randomInt(0, accountList.size() - 1);
+        int randomInt = RandomUtil.randomInt(0, accountList.size());
         return accountList.get(randomInt);
     }
 
+
+    @Scheduled(cron = "0/59 * * * * ? ")
+    public void scanTradeStatus() {
+        List<AmsTradeRecord> list = this.list(new LambdaQueryWrapper<AmsTradeRecord>()
+                .eq(AmsTradeRecord::getStatus, 0));
+        for (AmsTradeRecord record : list) {
+            DateTime dateTime = DateUtil.offsetMinute(DateUtil.date(record.getCreateTime()), 1);
+            if (new DateTime().isAfterOrEquals(dateTime)) {
+                record.setStatus(1);
+                this.updateById(record);
+            }
+        }
+    }
+
 }

+ 14 - 1
src/main/resources/application-dev.yml

@@ -9,4 +9,17 @@ spring:
     driver-class-name: com.mysql.cj.jdbc.Driver
     url: jdbc:mysql://122.9.135.98:3306/ams_test?useUnicode=true&characterEncoding=UTF-8
     password: zRJqkCeSDpWUmL5I
-    username: user-swb
+    username: user-swb
+super-whale:
+  redisson:
+    config:
+      address: redis://redis.ichaoj.com:6379
+      password: OpRxb5xB1E8Ejh2v
+  email:
+    config:
+      email-configs:
+        - mail-key: 7b5ba883d3c2bf15819a1deec685cb34
+          username: notify@nextsign.cc
+          host: smtp.ym.163.com
+          password: 3kz4xev1VOOU1
+          path: templates/email

+ 14 - 1
src/main/resources/application-test.yml

@@ -9,4 +9,17 @@ spring:
     driver-class-name: com.mysql.cj.jdbc.Driver
     url: jdbc:mysql://122.9.135.98:3306/ams_test?useUnicode=true&characterEncoding=UTF-8
     password: zRJqkCeSDpWUmL5I
-    username: user-swb
+    username: user-swb
+super-whale:
+  redisson:
+    config:
+      address: redis://redis.ichaoj.com:6379
+      password: OpRxb5xB1E8Ejh2v
+  email:
+    config:
+      email-configs:
+        - mail-key: 90703bb0ee2883f1a0c70bd28cf261cd
+          username: notify@nextsign.cc
+          host: smtp.ym.163.com
+          password: 3kz4xev1VOOU1
+          path: templates/email

+ 24 - 19
src/main/resources/mapper/AmsAirdropTaskMapper.xml

@@ -6,6 +6,7 @@
                a.ams_task_id,
                a.airdrop_project_id              amsProjectId,
                a.task_name,
+               a.task_code,
                a.contract_address,
                a.task_type,
                a.task_status,
@@ -13,28 +14,32 @@
                a.estimated_gas,
                b.project_name,
                b.project_logo,
+               b.project_url,
+               er.*,
+               er.max_gas,
                (SELECT COUNT(*)
                 FROM ams_execute_record er
                 WHERE er.flag = 0
-                  and er.task_id = a.ams_task_id
-                  and er.user_id = #{userId}) AS executeTimes,
-
-               (SELECT aer1.execute_status
-                FROM ams_execute_record aer1
-                WHERE aer1.flag = 0
-                  and aer1.task_id = a.ams_task_id
-                  and aer1.user_id = #{userId}
-                ORDER BY aer1.create_time DESC
-                LIMIT 1)                      AS executeStatus,
-               (SELECT aer.create_time
-                FROM ams_execute_record aer
-                WHERE aer.flag = 0
-                  and aer.task_id = a.ams_task_id
-                  and aer.user_id = #{userId}
-                ORDER BY aer.create_time DESC
-                LIMIT 1)                      AS lastExecuteTime
+                  AND er.task_id = a.ams_task_id
+                  AND er.user_id = #{userId}) AS executeTimes
         FROM ams_airdrop_task a
-                 LEFT JOIN ams_airdrop_project b ON a.airdrop_project_id = b.ams_project_id and b.flag = 0
-        where a.flag = 0
+                 LEFT JOIN ams_airdrop_project b ON a.airdrop_project_id = b.ams_project_id
+            AND b.flag = 0
+                 LEFT JOIN (SELECT aer.execute_status,
+                                   aer.execute_id,
+                                   aer.create_time AS lastExecuteTime,
+                                   aer.group_name,
+                                   aer.interval_max,
+                                   aer.interval_min,
+                                   aer.max_gas,
+                                   aer.task_id,
+                                   aer.project_id
+                            FROM ams_execute_record aer
+                            WHERE aer.flag = 0
+                              AND aer.user_id = #{userId}
+                            ORDER BY aer.create_time DESC
+                            LIMIT 1) er ON er.task_id = a.ams_task_id
+            AND er.project_id = b.ams_project_id
+        WHERE a.flag = 0
     </select>
 </mapper>

+ 23 - 2
src/main/resources/mapper/AmsExcuteRecordMapper.xml

@@ -1,5 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.ichaoj.ams.mapper.AmsExcuteRecordMapper">
-
+<mapper namespace="com.ichaoj.ams.mapper.AmsExecuteRecordMapper">
+    <select id="pageExecute" resultType="com.ichaoj.ams.response.execute.ExecuteResponse">
+        SELECT count(*) totalCount,
+        er.execute_id, er.execute_times, er.project_id, er.task_id, er.user_id, er.group_name, er.from_asset,
+        er.to_asset, er.interval_min,
+        er.interval_max, er.amount, er.max_gas, er.execute_status, er.create_time, er.update_time,
+        (
+        SELECT sum(r1.gas) from ams_trade_record r1 where r1.flag = 0 and r1.execute_id = er.execute_id
+        ) totalGas,
+        (
+        SELECT sum(r2.amount) from ams_trade_record r2 where r2.flag = 0 and r2.execute_id = er.execute_id
+        ) totalAmount
+        FROM ams_execute_record er
+        WHERE er.flag = 0
+        and er.user_id = #{userId}
+        <if test="request.queryValue != null and request.queryValue != ''">
+            and (
+            er.task_id = #{request.queryValue}
+            or er.execute_id = #{request.queryValue}
+            or er.group_name = #{request.queryValue}
+            )
+        </if>
+    </select>
 </mapper>

+ 10 - 0
src/main/resources/mapper/AmsTradeRecordMapper.xml

@@ -25,6 +25,7 @@
             and tr.execute_id = #{request.queryValue}
             or tr.tx_id = #{request.queryValue}
             or tr.address = #{request.queryValue}
+            or er.task_id = #{request.queryValue}
         </if>
         ORDER BY tr.create_time DESC
     </select>
@@ -59,4 +60,13 @@
           and er.user_id = #{userId}
         group by tr.address, task_name, project_name
     </select>
+
+    <select id="listByUserId" resultType="com.ichaoj.ams.response.record.TradeRecordResponse">
+        select *
+        from ams_trade_record tr
+                 inner join ams_execute_record er
+                            on tr.execute_id = er.execute_id and er.user_id = #{userId} and
+                               er.flag = 0
+        where tr.flag = 0
+    </select>
 </mapper>

Fichier diff supprimé car celui-ci est trop grand
+ 94 - 0
src/main/resources/templates/email/ReportEmailTemplate.html


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff