├── src ├── main │ └── java │ │ └── org │ │ └── tron │ │ └── easywork │ │ ├── enums │ │ ├── TransactionStatus.java │ │ └── TransferType.java │ │ ├── exception │ │ ├── FunctionSelectorException.java │ │ └── SmartParamDecodeException.java │ │ ├── util │ │ ├── BaseConvert.java │ │ ├── BlockUtil.java │ │ ├── AccountUtils.java │ │ ├── TransactionUtil.java │ │ ├── TronConverter.java │ │ ├── TransferUtil.java │ │ └── Trc20ContractUtil.java │ │ ├── model │ │ ├── TransferFunctionParam.java │ │ ├── BlockInfo.java │ │ ├── BlockHeaderInfo.java │ │ ├── AccountInfo.java │ │ ├── Trc20ContractInfo.java │ │ ├── ReferenceBlock.java │ │ └── Transfer.java │ │ ├── handler │ │ └── transfer │ │ │ ├── TransferHandlerContext.java │ │ │ ├── TransferHandler.java │ │ │ ├── TrxTransferHandler.java │ │ │ ├── Trc10TransferHandler.java │ │ │ ├── Trc20TransferHandler.java │ │ │ └── BaseTransferHandler.java │ │ ├── factory │ │ └── ApiWrapperFactory.java │ │ └── constant │ │ └── TronConstants.java └── test │ ├── resources │ └── logback.xml │ └── java │ └── org │ └── tron │ └── easywork │ ├── rate_limit │ ├── RateLimitTest.java │ └── GrpcRateLimitInterceptor.java │ ├── handler │ └── collection │ │ ├── FundCollectionConfig.java │ │ ├── CollectionTest.java │ │ └── FundCollection.java │ ├── BaseTest.java │ ├── local │ ├── ParseTransactionTest.java │ └── LocalTransferTest.java │ ├── TransferTest.java │ ├── ReadBlockTest.java │ ├── GetInfoTest.java │ ├── MultiSignatureTest.java │ └── SomeTest.java ├── .gitignore ├── README.md └── pom.xml /src/main/java/org/tron/easywork/enums/TransactionStatus.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.enums; 2 | 3 | /** 4 | * 交易状态 5 | * 6 | * @author Admin 7 | * @version 1.0 8 | * @time 2022-10-14 22:03 9 | */ 10 | public enum TransactionStatus { 11 | 12 | /** 13 | * 未知 14 | */ 15 | UNKNOWN, 16 | /** 17 | * 成功 18 | */ 19 | SUCCESS, 20 | /** 21 | * 失败 22 | */ 23 | ERROR, 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/exception/FunctionSelectorException.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.exception; 2 | 3 | /** 4 | * 智能合约 函数选择器 错误异常 5 | * 6 | * @author Admin 7 | * @version 1.0 8 | * @time 2022-11-01 18:51 9 | */ 10 | public class FunctionSelectorException extends Exception { 11 | 12 | public FunctionSelectorException(String message) { 13 | super(message); 14 | } 15 | 16 | public FunctionSelectorException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/exception/SmartParamDecodeException.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.exception; 2 | 3 | /** 4 | * 智能合约 转账参数 数据解析异常 5 | * ABI解码错误 6 | * 7 | * @author Admin 8 | * @version 1.0 9 | * @time 2022-04-21 04:06 10 | */ 11 | public class SmartParamDecodeException extends Exception { 12 | 13 | public SmartParamDecodeException(String message) { 14 | super(message); 15 | } 16 | 17 | public SmartParamDecodeException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/util/BaseConvert.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.util; 2 | 3 | /** 4 | * 进制转换 5 | * 6 | * @author Admin 7 | * @version 1.0 8 | * @time 2022-03-25 16:10 9 | */ 10 | public class BaseConvert { 11 | 12 | /** 13 | * 转16进制补零 14 | * 15 | * @param value 10 进制 16 | * @return 16 17 | */ 18 | public static String toBase16StringWithZero(long value) { 19 | String blockIdStart = Long.toHexString(value); 20 | String zeros = "0".repeat(16 - blockIdStart.length()); 21 | return zeros + blockIdStart; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/model/TransferFunctionParam.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.model; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.math.BigDecimal; 7 | 8 | /** 9 | * @author Admin 10 | * @version 1.0 11 | * @time 2022-04-01 13:38 12 | */ 13 | @Data 14 | @ToString 15 | public class TransferFunctionParam { 16 | 17 | /** 18 | * 到账地址 19 | */ 20 | private String toAddress; 21 | 22 | /** 23 | * 金额 24 | */ 25 | private BigDecimal amount; 26 | 27 | public TransferFunctionParam(String toAddress, BigDecimal amount) { 28 | this.toAddress = toAddress; 29 | this.amount = amount; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### gradle ### 2 | .gradle 3 | /build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | 6 | ### STS ### 7 | .settings/ 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | bin/ 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | rebel.xml 22 | 23 | ### NetBeans ### 24 | nbproject/private/ 25 | build/ 26 | nbbuild/ 27 | nbdist/ 28 | .nb-gradle/ 29 | 30 | ### maven ### 31 | target/ 32 | *.war 33 | *.ear 34 | *.zip 35 | *.tar 36 | *.tar.gz 37 | *.versionsBackup 38 | 39 | ### vscode ### 40 | .vscode 41 | 42 | ### logs ### 43 | /logs/ 44 | *.log 45 | 46 | ### temp ignore ### 47 | *.cache 48 | *.diff 49 | *.patch 50 | *.tmp 51 | *.java~ 52 | *.properties~ 53 | *.xml~ 54 | 55 | 56 | ### system ignore ### 57 | .DS_Store 58 | Thumbs.db 59 | Servers 60 | .metadata -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/model/BlockInfo.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Admin 11 | * @version 1.0 12 | * @time 2022-09-20 16:05 13 | */ 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class BlockInfo { 18 | 19 | /** 20 | * 区块信息头 21 | */ 22 | private BlockHeaderInfo header; 23 | 24 | /** 25 | * 转账交易信息(仅包含转账交易) 26 | */ 27 | List transfers; 28 | 29 | public BlockInfo() { 30 | } 31 | 32 | public BlockInfo(BlockHeaderInfo blockHeaderInfo) { 33 | this.header = blockHeaderInfo; 34 | } 35 | 36 | public BlockInfo(BlockHeaderInfo blockHeaderInfo, List transfers) { 37 | this.header = blockHeaderInfo; 38 | this.transfers = transfers; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/enums/TransferType.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.enums; 2 | 3 | import org.tron.trident.proto.Chain; 4 | 5 | /** 6 | * 转账类型 7 | * 8 | * @author Admin 9 | * @version 1.0 10 | * @time 2022-09-21 14:27 11 | */ 12 | public enum TransferType { 13 | /** 14 | * TRX 15 | */ 16 | TRX(Chain.Transaction.Contract.ContractType.TransferContract), 17 | /** 18 | * TRC20 19 | */ 20 | TRC20(Chain.Transaction.Contract.ContractType.TriggerSmartContract), 21 | /** 22 | * TRC10 23 | */ 24 | TRC10(Chain.Transaction.Contract.ContractType.TransferAssetContract); 25 | 26 | /** 27 | * 支持的合约类型 28 | */ 29 | public final Chain.Transaction.Contract.ContractType supportContractType; 30 | 31 | TransferType(Chain.Transaction.Contract.ContractType supportContractType) { 32 | this.supportContractType = supportContractType; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/model/BlockHeaderInfo.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @author Admin 11 | * @version 1.0 12 | * @time 2022-10-15 21:22 13 | */ 14 | @Getter 15 | @Setter 16 | @ToString 17 | public class BlockHeaderInfo { 18 | 19 | /** 20 | * 区块哈希 21 | */ 22 | private String id; 23 | 24 | /** 25 | * 区块高度 26 | */ 27 | private Long height; 28 | 29 | /** 30 | * 区块交易总数 31 | */ 32 | private Integer count; 33 | 34 | /** 35 | * 创建时间 36 | */ 37 | private Date createTime; 38 | 39 | public BlockHeaderInfo() { 40 | } 41 | 42 | public BlockHeaderInfo(String id, Long height, Integer count, Date createTime) { 43 | this.id = id; 44 | this.height = height; 45 | this.count = count; 46 | this.createTime = createTime; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/model/AccountInfo.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.model; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | import org.tron.trident.core.key.KeyPair; 6 | 7 | /** 8 | * Tron 账户模型 9 | * 10 | * @author Admin 11 | * @version 1.0 12 | * @time 2022-03-29 14:37 13 | */ 14 | @Getter 15 | @ToString 16 | public class AccountInfo { 17 | 18 | /** 19 | * 私钥 20 | */ 21 | private final String hexPrivateKey; 22 | /** 23 | * 公钥 24 | */ 25 | private final String publicKey; 26 | /** 27 | * base58地址 28 | */ 29 | private final String base58CheckAddress; 30 | /** 31 | * hex地址 32 | */ 33 | private final String hexAddress; 34 | 35 | private final KeyPair keyPair; 36 | 37 | public AccountInfo(String hexPrivateKey) { 38 | this(new KeyPair(hexPrivateKey)); 39 | } 40 | 41 | public AccountInfo(KeyPair keyPair) { 42 | this.keyPair = keyPair; 43 | this.hexPrivateKey = keyPair.toPrivateKey(); 44 | this.publicKey = keyPair.toPublicKey(); 45 | this.base58CheckAddress = keyPair.toBase58CheckAddress(); 46 | this.hexAddress = keyPair.toHexAddress(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/util/BlockUtil.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.util; 2 | 3 | import org.bouncycastle.util.encoders.Hex; 4 | import org.tron.trident.crypto.Hash; 5 | import org.tron.trident.proto.Chain; 6 | 7 | /** 8 | * 区块工具类 9 | * 10 | * @author Admin 11 | * @version 1.0 12 | * @time 2022-04-21 03:40 13 | */ 14 | public class BlockUtil { 15 | 16 | /** 17 | * 解析区块ID(区块hash) 18 | * 19 | * @param block 块 20 | * @return hash 21 | */ 22 | public static String parseBlockId(Chain.Block block) { 23 | return parseBlockId(block.getBlockHeader()); 24 | } 25 | 26 | /** 27 | * 解析区块ID(区块hash) 28 | * 29 | * @param blockHeader 头信息 30 | * @return hash 31 | */ 32 | public static String parseBlockId(Chain.BlockHeader blockHeader) { 33 | // 块高度 34 | long number = blockHeader.getRawData().getNumber(); 35 | // 块header 36 | byte[] headerBytes = blockHeader.getRawData().toByteArray(); 37 | byte[] bytes = Hash.sha256(headerBytes); 38 | String blockIdEnd = Hex.toHexString(bytes).substring(16); 39 | String blockStart = BaseConvert.toBase16StringWithZero(number); 40 | return blockStart + blockIdEnd; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/handler/transfer/TransferHandlerContext.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.transfer; 2 | 3 | import org.tron.easywork.util.TransactionUtil; 4 | import org.tron.trident.proto.Chain; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author Admin 11 | * @version 1.0 12 | * @time 2023-02-12 07:30 13 | */ 14 | public class TransferHandlerContext { 15 | 16 | private final HashMap handlers = new HashMap<>(); 17 | 18 | public TransferHandlerContext() { 19 | } 20 | 21 | public TransferHandlerContext(Map handlers) { 22 | this.handlers.putAll(handlers); 23 | } 24 | 25 | public void addHandler(String name, TransferHandler handler) { 26 | handlers.put(name, handler); 27 | } 28 | 29 | public TransferHandler getHandler(String name) { 30 | return handlers.get(name); 31 | } 32 | 33 | public TransferHandler getHandler(Chain.Transaction.Contract.ContractType contractType) { 34 | return handlers.values().stream().filter(handler -> handler.supportContractType(contractType)).findFirst().orElse(null); 35 | } 36 | 37 | public TransferHandler getHandler(Chain.Transaction transaction) { 38 | Chain.Transaction.Contract.ContractType contractType = TransactionUtil.getFirstContractType(transaction); 39 | return handlers.values().stream().filter(handler -> handler.supportContractType(contractType)).findFirst().orElse(null); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/handler/transfer/TransferHandler.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.transfer; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | import org.tron.easywork.exception.FunctionSelectorException; 5 | import org.tron.easywork.exception.SmartParamDecodeException; 6 | import org.tron.easywork.model.ReferenceBlock; 7 | import org.tron.easywork.model.Transfer; 8 | import org.tron.trident.proto.Chain; 9 | 10 | /** 11 | * @author Admin 12 | * @version 1.0 13 | * @time 2023-02-11 10:31 14 | */ 15 | public interface TransferHandler { 16 | 17 | /** 18 | * 判断是否支持处理传入的合约类型 19 | * 20 | * @param contractType 合约类型 21 | * @return 是否支持 22 | */ 23 | boolean supportContractType(Chain.Transaction.Contract.ContractType contractType); 24 | 25 | /** 26 | * 构建本地转账交易 27 | * 28 | * @param transfer 转账信息 29 | * @param referenceBlock 引用区块,范围最新区块 65535 以内 30 | * @return 交易信息 31 | */ 32 | Chain.Transaction buildLocalTransfer(Transfer transfer, ReferenceBlock referenceBlock); 33 | 34 | /** 35 | * 转账信息解析 36 | * 37 | * @param transaction 交易信息 38 | * @return 转账信息 39 | * @throws InvalidProtocolBufferException unpack解包异常(大概率合约输入类型有误) 40 | * @throws SmartParamDecodeException 转账解析异常(ABI解码错误) 41 | * @throws FunctionSelectorException 智能合约 函数选择器 错误异常 42 | */ 43 | Transfer parse(Chain.Transaction transaction) throws InvalidProtocolBufferException, SmartParamDecodeException, FunctionSelectorException; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/util/AccountUtils.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.util; 2 | 3 | import org.bouncycastle.util.encoders.Hex; 4 | import org.tron.easywork.constant.TronConstants; 5 | import org.tron.easywork.model.AccountInfo; 6 | import org.tron.trident.core.key.KeyPair; 7 | import org.tron.trident.utils.Base58Check; 8 | 9 | /** 10 | * Tron 账户工具类 11 | * 12 | * @author Admin 13 | * @version 1.0 14 | * @time 2022-03-29 14:23 15 | */ 16 | public class AccountUtils { 17 | 18 | /** 19 | * 生成Tron账户 20 | * 21 | * @return tron account 22 | */ 23 | public static AccountInfo generateAccount() { 24 | KeyPair keyPair = KeyPair.generate(); 25 | return new AccountInfo(keyPair); 26 | } 27 | 28 | /** 29 | * 检查是否为正确的Tron地址 30 | * 非严谨的方法 31 | * 32 | * @param address 地址 33 | * @return b 34 | */ 35 | public static boolean isTronAddress(String address) { 36 | try { 37 | if (address.startsWith(TronConstants.ADDRESS_BASE58_PREFIX) && address.length() == TronConstants.ADDRESS_BASE58_LENGTH) { 38 | Base58Check.base58ToBytes(address); 39 | return true; 40 | } 41 | else if (address.startsWith(TronConstants.ADDRESS_HEX_PREFIX) && address.length() == TronConstants.ADDRESS_HEX_LENGTH) { 42 | Hex.decode(address); 43 | return true; 44 | } 45 | return false; 46 | } catch (Exception e) { 47 | return false; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/model/Trc20ContractInfo.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.model; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.RoundingMode; 8 | 9 | /** 10 | * 智能合约 模型 11 | * 12 | * @author Admin 13 | * @version 1.0 14 | * @time 2022-04-01 14:36 15 | */ 16 | @Getter 17 | @ToString 18 | public class Trc20ContractInfo { 19 | 20 | /** 21 | * 合约地址 22 | */ 23 | private final String address; 24 | 25 | /** 26 | * 合约名称 27 | */ 28 | private final String symbol; 29 | 30 | /** 31 | * 合约精度 32 | */ 33 | private final BigDecimal decimals; 34 | 35 | /** 36 | * 比值: 10的合约精度次方 37 | */ 38 | private final BigDecimal rate; 39 | 40 | public Trc20ContractInfo(String address, String symbol, BigDecimal decimals) { 41 | this.address = address; 42 | this.symbol = symbol; 43 | this.decimals = decimals; 44 | this.rate = BigDecimal.TEN.pow(decimals.intValue()); 45 | } 46 | 47 | /** 48 | * 获取转账金额 49 | * 50 | * @param realAmount 真实金额 单位个 51 | * @return 转账金额 52 | */ 53 | public BigDecimal getTransferAmount(BigDecimal realAmount) { 54 | return realAmount.multiply(rate); 55 | } 56 | 57 | /** 58 | * 获取真实金额 59 | * 60 | * @param transferAmount 转账金额 61 | * @return 真实金额 62 | */ 63 | public BigDecimal getRealAmount(BigDecimal transferAmount) { 64 | return transferAmount.divide(rate, decimals.intValue(), RoundingMode.DOWN); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/model/ReferenceBlock.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.model; 2 | 3 | import com.google.protobuf.ByteString; 4 | import lombok.Getter; 5 | import org.tron.trident.crypto.Hash; 6 | import org.tron.trident.proto.Chain; 7 | 8 | import java.nio.ByteBuffer; 9 | 10 | /** 11 | * 引用区块,要求范围最新区块 65535 以内 12 | * 文档搜索:本地构建交易 13 | *

14 | * 本地构建交易 15 | * bytes ref_block_bytes = 1; //最新块高度的第6到8(不包含)之间的字节 16 | * int64 ref_block_num = 3; //区块高度,可选 17 | * bytes ref_block_hash = 4; //最新块的hash的第8到16(不包含)之间的字节 18 | * 19 | * @author Admin 20 | * @version 1.0 21 | * @time 2022-12-13 17:41 22 | */ 23 | @Getter 24 | public class ReferenceBlock { 25 | 26 | /** 27 | * 引用区块头,要求范围最新区块 65535 以内 28 | */ 29 | private final Chain.BlockHeader blockHeader; 30 | 31 | /** 32 | * 最新块的hash的第8到16(不包含)之间的字节 33 | */ 34 | private final ByteString refBlockHash; 35 | 36 | /** 37 | * 最新块高度的第6到8(不包含)之间的字节 38 | */ 39 | private final ByteString refBlockBytes; 40 | 41 | public ReferenceBlock(Chain.BlockHeader blockHeader) { 42 | this.blockHeader = blockHeader; 43 | 44 | byte[] blockHash = Hash.sha256(blockHeader.getRawData().toByteArray()); 45 | refBlockHash = ByteString.copyFrom(blockHash, 8, 8); 46 | 47 | byte[] refBlockNum = ByteBuffer 48 | .allocate(Long.BYTES) 49 | .putLong(blockHeader.getRawData().getNumber()) 50 | .array(); 51 | refBlockBytes = ByteString.copyFrom(refBlockNum, 6, 2); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/handler/transfer/TrxTransferHandler.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.transfer; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | import org.tron.easywork.model.Transfer; 6 | import org.tron.easywork.util.TransferUtil; 7 | import org.tron.trident.core.ApiWrapper; 8 | import org.tron.trident.proto.Chain; 9 | import org.tron.trident.proto.Contract; 10 | 11 | /** 12 | * @author Admin 13 | * @version 1.0 14 | * @time 2023-02-11 10:38 15 | */ 16 | public class TrxTransferHandler extends BaseTransferHandler { 17 | 18 | @Override 19 | protected Chain.Transaction.Contract.ContractType getContractType() { 20 | return Chain.Transaction.Contract.ContractType.TransferContract; 21 | } 22 | 23 | @Override 24 | protected Any createContractParameter(Transfer transfer) { 25 | Contract.TransferContract contract = Contract.TransferContract.newBuilder() 26 | .setAmount(transfer.getAmount().longValue()) 27 | .setOwnerAddress(ApiWrapper.parseAddress(transfer.getFrom())) 28 | .setToAddress(ApiWrapper.parseAddress(transfer.getTo())) 29 | .build(); 30 | return Any.pack(contract); 31 | } 32 | 33 | @Override 34 | protected Contract.TransferContract unpack(Any any) throws InvalidProtocolBufferException { 35 | return any.unpack(Contract.TransferContract.class); 36 | } 37 | 38 | @Override 39 | protected Transfer getTransferInfo(Contract.TransferContract transferContract) { 40 | return TransferUtil.getTransferInfo(transferContract); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/rate_limit/RateLimitTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.rate_limit; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.tron.trident.proto.Response; 6 | 7 | /** 8 | * @author Admin 9 | * @version 1.0 10 | * @time 2023-02-16 14:16 11 | */ 12 | @Slf4j 13 | public class RateLimitTest { 14 | 15 | /* @Test 16 | public void test() throws InterruptedException { 17 | String key = "151b073fe0a76e0eb4e57b9a1cba94abd5cffb46202cb0cf6cf8b0b6296fc7ef"; 18 | GrpcRateLimitInterceptor grpcRateLimitInterceptor = new GrpcRateLimitInterceptor(1, 1); 19 | LimitApiWrapper wrapper = LimitApiWrapper.ofShasta(key,grpcRateLimitInterceptor); 20 | 21 | for (int i = 0; i < 50; i++) { 22 | final int x = i; 23 | new Thread(() -> { 24 | Response.Account account = wrapper.getAccount("TTTTT4YrkRB5kc3SxEReBLiBDa89B1oGda"); 25 | log.debug("{}", x); 26 | }).start(); 27 | } 28 | 29 | Thread.sleep(60000); 30 | } 31 | 32 | @Test 33 | public void testx() throws InterruptedException { 34 | String key = "151b073fe0a76e0eb4e57b9a1cba94abd5cffb46202cb0cf6cf8b0b6296fc7ef"; 35 | GrpcRateLimitInterceptor grpcRateLimitInterceptor = new GrpcRateLimitInterceptor(1, 1); 36 | LimitApiWrapper wrapper = LimitApiWrapper.ofShasta(key,grpcRateLimitInterceptor); 37 | 38 | for (int i = 0; i < 10; i++) { 39 | Response.Account account = wrapper.getAccount("TTTTT4YrkRB5kc3SxEReBLiBDa89B1oGda"); 40 | long balance = account.getBalance(); 41 | log.debug("{}", balance); 42 | } 43 | }*/ 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/factory/ApiWrapperFactory.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.factory; 2 | 3 | import lombok.NonNull; 4 | import org.tron.trident.core.ApiWrapper; 5 | 6 | /** 7 | * @author Admin 8 | * @version 1.0 9 | * @time 2022-04-18 09:24 10 | */ 11 | public class ApiWrapperFactory { 12 | 13 | public static ApiWrapper create(@NonNull NetType netType, @NonNull String privateKey, String apiKey) { 14 | if (NetType.Mainnet == netType) { 15 | if (null == apiKey || "".equals(apiKey.trim())) { 16 | throw new NullPointerException("主网 apiKey 参数不能为空!"); 17 | } 18 | return ApiWrapper.ofMainnet(privateKey, apiKey); 19 | } else if (NetType.Shasta == netType) { 20 | return ApiWrapper.ofShasta(privateKey); 21 | } else if (NetType.Nile == netType) { 22 | return ApiWrapper.ofNile(privateKey); 23 | } 24 | throw new IllegalArgumentException("不支持的网络类型!"); 25 | } 26 | 27 | public static ApiWrapper create(String grpcEndpoint, String grpcEndpointSolidity, 28 | String hexPrivateKey, String apiKey) { 29 | if (null != apiKey) { 30 | return new ApiWrapper(grpcEndpoint, grpcEndpointSolidity, hexPrivateKey, apiKey); 31 | } 32 | return new ApiWrapper(grpcEndpoint, grpcEndpointSolidity, hexPrivateKey); 33 | } 34 | 35 | 36 | /** 37 | * Tron 网络类型 38 | */ 39 | public enum NetType { 40 | /** 41 | * 主网 42 | */ 43 | Mainnet, 44 | /** 45 | * 水龙头测试网 46 | */ 47 | Shasta, 48 | /** 49 | * 尼罗测试网 50 | */ 51 | Nile 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/handler/transfer/Trc10TransferHandler.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.transfer; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.ByteString; 5 | import com.google.protobuf.InvalidProtocolBufferException; 6 | import org.tron.easywork.model.Transfer; 7 | import org.tron.easywork.util.TransferUtil; 8 | import org.tron.trident.core.ApiWrapper; 9 | import org.tron.trident.proto.Chain; 10 | import org.tron.trident.proto.Contract; 11 | 12 | /** 13 | * @author Admin 14 | * @version 1.0 15 | * @time 2023-02-11 10:58 16 | */ 17 | public class Trc10TransferHandler extends BaseTransferHandler { 18 | 19 | @Override 20 | protected Chain.Transaction.Contract.ContractType getContractType() { 21 | return Chain.Transaction.Contract.ContractType.TransferAssetContract; 22 | } 23 | 24 | @Override 25 | protected Any createContractParameter(Transfer transfer) { 26 | Contract.TransferAssetContract contract = Contract.TransferAssetContract.newBuilder() 27 | .setAmount(transfer.getAmount().longValue()) 28 | .setOwnerAddress(ApiWrapper.parseAddress(transfer.getFrom())) 29 | .setToAddress(ApiWrapper.parseAddress(transfer.getTo())) 30 | .setAssetName(ByteString.copyFrom(transfer.getAssetName().toByteArray())) 31 | .build(); 32 | return Any.pack(contract); 33 | } 34 | 35 | @Override 36 | protected Contract.TransferAssetContract unpack(Any any) throws InvalidProtocolBufferException { 37 | return any.unpack(Contract.TransferAssetContract.class); 38 | } 39 | 40 | @Override 41 | protected Transfer getTransferInfo(Contract.TransferAssetContract transferAssetContract) { 42 | return TransferUtil.getTransferInfo(transferAssetContract); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/handler/collection/FundCollectionConfig.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.collection; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.tron.easywork.model.Trc20ContractInfo; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.List; 9 | 10 | /** 11 | * @author Admin 12 | * @version 1.0 13 | * @time 2022-11-08 17:07 14 | */ 15 | @Getter 16 | @Setter 17 | public class FundCollectionConfig { 18 | 19 | /** 20 | * Trc20合约地址 21 | */ 22 | private final Trc20ContractInfo trc20ContractInfo; 23 | /** 24 | * Trc20目标地址 25 | */ 26 | private final String targetAddressOfTrc20; 27 | /** 28 | * Trx目标地址 29 | */ 30 | private final String targetAddressOfTrx; 31 | /** 32 | * 矿工费派发者地址 33 | */ 34 | private final String handingFeeProviderAddress; 35 | /** 36 | * 矿工费派发者使用权限ID 37 | */ 38 | private int handingFeeProviderPermissionId; 39 | /** 40 | * 矿工费派发者的私钥 41 | *

42 | * 多签则为多个key 43 | */ 44 | private final List handingFeeProviderKeys; 45 | 46 | /** 47 | * 矿工费 - 单位 trx 48 | */ 49 | private final BigDecimal handingFeeWithTrx; 50 | 51 | 52 | public FundCollectionConfig(Trc20ContractInfo trc20ContractInfo, String targetAddressOfTrc20, 53 | String targetAddressOfTrx, String handingFeeProviderAddress, 54 | List handingFeeProviderKeys, BigDecimal handingFeeWithTrx) { 55 | this.trc20ContractInfo = trc20ContractInfo; 56 | this.targetAddressOfTrc20 = targetAddressOfTrc20; 57 | this.targetAddressOfTrx = targetAddressOfTrx; 58 | this.handingFeeProviderAddress = handingFeeProviderAddress; 59 | this.handingFeeProviderKeys = handingFeeProviderKeys; 60 | this.handingFeeWithTrx = handingFeeWithTrx; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/constant/TronConstants.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.constant; 2 | 3 | import org.bouncycastle.jcajce.provider.digest.Keccak; 4 | import org.bouncycastle.util.encoders.Hex; 5 | 6 | import java.math.BigDecimal; 7 | 8 | /** 9 | * Tron 常量 10 | * 11 | * @author Admin 12 | * @version 1.0 13 | * @time 2022-04-03 17:39 14 | */ 15 | public final class TronConstants { 16 | 17 | /** 18 | * 1trx = 1000000 sun 19 | */ 20 | public static final BigDecimal TRX_SUN_RATE = new BigDecimal(1000000); 21 | 22 | /** 23 | * trx 精度 24 | */ 25 | public static final int TRX_DECIMAL = 6; 26 | 27 | /** 28 | * Transfer事件 - Event Log 29 | * LOG 解码 30 | * 通过keccak256计算后的结果 31 | * 等于【ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef】 32 | */ 33 | public static final String TRANSFER_EVENT_BY_KECCAK256 = 34 | Hex.toHexString(new Keccak.Digest256().digest("Transfer(address,address,uint256)".getBytes())) 35 | .substring(0, 8); 36 | 37 | /** 38 | * 智能合约转账函数ID 39 | * 通过keccak256计算后的结果 - 前四个字节 40 | * 等于【a9059cbb】 41 | * 文档描述:用于虚拟机对函数的寻址 42 | */ 43 | public static final String TRANSFER_FUNC_ID_BY_KECCAK256 = 44 | Hex.toHexString( 45 | new Keccak.Digest256().digest("transfer(address,uint256)".getBytes()) 46 | ).substring(0, 8); 47 | 48 | /** 49 | * Hex格式地址开头 50 | */ 51 | public static final String ADDRESS_HEX_PREFIX = "41"; 52 | 53 | /** 54 | * Base58格式地址开头 55 | */ 56 | public static final String ADDRESS_BASE58_PREFIX = "T"; 57 | 58 | /** 59 | * Hex格式地址长度 60 | */ 61 | public static final int ADDRESS_HEX_LENGTH = 42; 62 | 63 | /** 64 | * Base58格式地址长度 65 | */ 66 | public static final int ADDRESS_BASE58_LENGTH = 34; 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/util/TransactionUtil.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.util; 2 | 3 | import org.bouncycastle.util.encoders.Hex; 4 | import org.tron.trident.core.ApiWrapper; 5 | import org.tron.trident.proto.Chain; 6 | 7 | /** 8 | * @author Admin 9 | * @version 1.0 10 | * @time 2022-04-01 16:52 11 | */ 12 | public class TransactionUtil { 13 | 14 | /** 15 | * 解析交易ID(交易hash) 16 | * 17 | * @param transaction 交易 18 | * @return hash 19 | */ 20 | public static String getTransactionId(Chain.Transaction transaction) { 21 | /* 22 | byte[] rawData = transaction.getRawData().toByteArray(); 23 | byte[] rawDataHash256 = Hash.sha256(rawData); 24 | return Hex.toHexString(rawDataHash256); 25 | */ 26 | byte[] bytes = ApiWrapper.calculateTransactionHash(transaction); 27 | return Hex.toHexString(bytes); 28 | } 29 | 30 | /** 31 | * 检查交易是否成功 32 | * 33 | * @param transaction 交易 34 | * @return 是否成功 35 | */ 36 | public static boolean isTransactionSuccess(Chain.Transaction transaction) { 37 | return transaction.getRet(0).getContractRet().getNumber() == 1; 38 | } 39 | 40 | /** 41 | * 获取交易中的第一个合约 42 | * 43 | * @param transaction 交易 44 | * @return 合约 45 | */ 46 | public static Chain.Transaction.Contract getFirstContract(Chain.Transaction transaction) { 47 | return transaction.getRawData().getContract(0); 48 | } 49 | 50 | 51 | /** 52 | * 获取交易中第一个合约的类型 53 | * 54 | * @param transaction 交易 55 | * @return 合约类型 56 | */ 57 | public static Chain.Transaction.Contract.ContractType getFirstContractType(Chain.Transaction transaction) { 58 | return getFirstContract(transaction).getType(); 59 | } 60 | 61 | /** 62 | * 获取交易中第一个合约的权限ID 63 | * 64 | * @param transaction 交易 65 | * @return 权限ID 66 | */ 67 | public static int getFirstPermissionId(Chain.Transaction transaction) { 68 | return getFirstContract(transaction).getPermissionId(); 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/util/TronConverter.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.util; 2 | 3 | import com.google.protobuf.ByteString; 4 | import org.bouncycastle.util.encoders.Hex; 5 | import org.tron.trident.utils.Base58Check; 6 | 7 | import java.math.BigDecimal; 8 | import java.math.RoundingMode; 9 | 10 | /** 11 | * 转换工具类 12 | * 13 | * @author Admin 14 | * @version 1.0 15 | * @time 2022-04-13 21:20 16 | */ 17 | public class TronConverter { 18 | 19 | public static String hexToBase58(String hex) { 20 | byte[] decode = Hex.decode(hex); 21 | return Base58Check.bytesToBase58(decode); 22 | } 23 | 24 | public static String base58ToHex(String base58) { 25 | byte[] base58ToBytes = Base58Check.base58ToBytes(base58); 26 | return Hex.toHexString(base58ToBytes); 27 | } 28 | 29 | public static Integer hexToInt(String hex) { 30 | byte[] decode = Hex.decode(hex); 31 | String tokenId = new String(decode); 32 | return Integer.parseInt(tokenId); 33 | } 34 | 35 | /** 36 | * 获取真实金额 37 | * 单位:个 38 | * 39 | * @param transferAmount 余额原始值 40 | * @param decimals 精度 41 | * @return 真实金额 42 | */ 43 | public static BigDecimal getRealAmount(BigDecimal transferAmount, int decimals) { 44 | return transferAmount.divide(BigDecimal.TEN.pow(decimals), decimals, RoundingMode.DOWN); 45 | } 46 | 47 | /** 48 | * 获取系统转账时发送的金额 49 | * 精度完全的金额 50 | * 单位:最小合约单位 51 | * 52 | * @param realAmount 真实余额 53 | * @param decimals 精度 54 | * @return 系统转账金额 55 | */ 56 | public static BigDecimal getTransferAmount(BigDecimal realAmount, int decimals) { 57 | return realAmount.multiply(BigDecimal.TEN.pow(decimals)); 58 | } 59 | 60 | /** 61 | * 从 log topic 当中获取 tron hex 地址 62 | * 63 | * @param addressTopic topic 64 | * @return tron hex 地址 65 | */ 66 | public static String getTronHexAddressFromTopic(ByteString addressTopic) { 67 | String hex = Hex.toHexString(addressTopic.toByteArray()); 68 | return "41" + hex.substring(hex.length() - 40); 69 | } 70 | 71 | /** 72 | * 从 log topic 当中获取 tron base58 地址 73 | * 74 | * @param addressTopic topic 75 | * @return tron base58 地址 76 | */ 77 | public static String getTronBase58AddressFromTopic(ByteString addressTopic) { 78 | String hexAddress = getTronHexAddressFromTopic(addressTopic); 79 | return hexToBase58(hexAddress); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyTron 2 | **娱乐项目** 3 | 4 | [trident-java](https://github.com/tronprotocol/trident) 的轻量扩展,将常用解码步骤做了进一步的封装。 5 | 6 | 7 | 本库封装了 Trx、Trc10、Trc20 常用功能,本地构造交易转账、读取解码后的交易,变得十分简单。 8 | 同时,不影响使用 trident 原有的各种功能。 9 | 10 | 请勿生产环境中直接使用,建议仅Copy有需要的代码,有问题可提issue。 11 | 12 | 测试用例(包含各种方法运用思路): core/src/test/java/org/tron/easywork 13 | 14 | 测试基类: org.tron.easywork.BaseTest 15 | 16 | --- 17 | 18 | 由于测试代码包含许多需要鉴权、有速率限制的网络API,打包时请跳过测试,调试请在 IDE 中进行。 19 | 20 | 编译依赖java17,在其他地方使用这个库,local-path形式,或jar包导入本地maven: 21 | 22 | ```xml 23 | 24 | 25 | 0.9.1 26 | 1.66.0 27 | 28 | 29 | 30 | 31 | 32 | org.tron 33 | easywork 34 | 2.5 35 | 36 | 37 | 38 | org.tron.trident 39 | abi 40 | ${trident.version} 41 | 42 | 43 | 44 | org.tron.trident 45 | utils 46 | ${trident.version} 47 | 48 | 49 | org.tron.trident 50 | core 51 | ${trident.version} 52 | 53 | 54 | 55 | 56 | org.bouncycastle 57 | bcprov-jdk18on 58 | 1.76 59 | 60 | 61 | io.grpc 62 | grpc-netty-shaded 63 | ${grpc.version} 64 | 65 | 66 | io.grpc 67 | grpc-netty 68 | ${grpc.version} 69 | 70 | 71 | io.grpc 72 | grpc-okhttp 73 | ${grpc.version} 74 | 75 | 76 | io.grpc 77 | grpc-protobuf 78 | ${grpc.version} 79 | 80 | 81 | io.grpc 82 | grpc-stub 83 | ${grpc.version} 84 | 85 | 86 | 87 | ``` -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/rate_limit/GrpcRateLimitInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.rate_limit; 2 | 3 | import com.google.common.util.concurrent.RateLimiter; 4 | import io.grpc.*; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.util.concurrent.locks.Condition; 8 | import java.util.concurrent.locks.ReentrantLock; 9 | 10 | @Slf4j 11 | public class GrpcRateLimitInterceptor implements ClientInterceptor { 12 | private final RateLimiter rateLimiter; 13 | private final ReentrantLock lock; 14 | private final Condition condition; 15 | private final int maxRequests; 16 | private final int periodSeconds; 17 | private int currentRequests; 18 | 19 | public GrpcRateLimitInterceptor(int maxRequests, int periodSeconds) { 20 | log.warn("创建"); 21 | this.maxRequests = maxRequests; 22 | this.periodSeconds = periodSeconds; 23 | this.currentRequests = 0; 24 | this.rateLimiter = RateLimiter.create(maxRequests / (double) periodSeconds); 25 | this.lock = new ReentrantLock(); 26 | this.condition = lock.newCondition(); 27 | } 28 | 29 | @Override 30 | public ClientCall interceptCall(MethodDescriptor method, 31 | CallOptions callOptions, Channel next) { 32 | ClientCall call; 33 | try { 34 | lock.lock(); 35 | while (currentRequests >= maxRequests) { 36 | condition.await(); 37 | } 38 | currentRequests++; 39 | rateLimiter.acquire(); 40 | 41 | call = next.newCall(method, callOptions); 42 | 43 | call = new ForwardingClientCall.SimpleForwardingClientCall(call) { 44 | @Override 45 | public void start(Listener responseListener, Metadata headers) { 46 | super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener(responseListener) { 47 | @Override 48 | public void onClose(Status status, Metadata trailers) { 49 | lock.lock(); 50 | currentRequests--; 51 | condition.signal(); 52 | lock.unlock(); 53 | super.onClose(status, trailers); 54 | } 55 | }, headers); 56 | } 57 | }; 58 | } catch (InterruptedException e) { 59 | Thread.currentThread().interrupt(); 60 | throw new RuntimeException(e); 61 | } finally { 62 | lock.unlock(); 63 | } 64 | return call; 65 | } 66 | } -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/BaseTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.tron.easywork.handler.transfer.TransferHandlerContext; 5 | import org.tron.easywork.handler.transfer.Trc10TransferHandler; 6 | import org.tron.easywork.handler.transfer.Trc20TransferHandler; 7 | import org.tron.easywork.handler.transfer.TrxTransferHandler; 8 | import org.tron.easywork.model.ReferenceBlock; 9 | import org.tron.easywork.model.Transfer; 10 | import org.tron.trident.core.ApiWrapper; 11 | import org.tron.trident.core.exceptions.IllegalException; 12 | import org.tron.trident.proto.Chain; 13 | import org.tron.trident.utils.Convert; 14 | 15 | import java.math.BigDecimal; 16 | 17 | /** 18 | * @author Admin 19 | * @version 1.0 20 | * @time 2023-02-12 08:56 21 | */ 22 | @Slf4j 23 | public class BaseTest { 24 | 25 | /** 26 | * 私钥 27 | */ 28 | protected String key = "your private key"; 29 | /** 30 | * trident API 包装器 31 | */ 32 | protected ApiWrapper wrapper = ApiWrapper.ofShasta(key); 33 | 34 | /** 35 | * 转出地址 36 | */ 37 | protected String from = wrapper.keyPair.toBase58CheckAddress(); 38 | /** 39 | * 到账地址 40 | */ 41 | protected String to = "TVvSx8zgvqcc8nWuYArLfdkqerAWHibf78"; 42 | /** 43 | * TRC20合约地址 44 | */ 45 | protected String contractAddress = "TFd1piJ8iXmJQicTicq4zChDSNSMLPFR4w"; 46 | 47 | 48 | /** 49 | * 获取引用区块 50 | * 51 | * @param wrapper trident api 52 | * @return 引用区块信息 53 | * @throws IllegalException 参数错误 54 | */ 55 | protected ReferenceBlock getReferenceBlock(ApiWrapper wrapper) throws IllegalException { 56 | // api 获取最新块 57 | Chain.Block nowBlock = wrapper.getNowBlock(); 58 | return new ReferenceBlock(nowBlock.getBlockHeader()); 59 | } 60 | 61 | 62 | /** 63 | * 创建TRC20转账 64 | */ 65 | protected Transfer createTrc20Transfer(String from, String to, BigDecimal amount) { 66 | // 手续费限制,单位sum 67 | long feeLimit = Convert.toSun(BigDecimal.valueOf(50), Convert.Unit.TRX).longValue(); 68 | return Transfer.trc20TransferBuilder(from, to, amount, contractAddress).feeLimit(feeLimit).memo("备注:TRC20转账").build(); 69 | } 70 | 71 | /** 72 | * 发送构造好的交易 73 | * 74 | * @param transaction 交易 75 | */ 76 | protected void sendTransaction(Chain.Transaction transaction) { 77 | // 签名 78 | Chain.Transaction signTransaction = wrapper.signTransaction(transaction); 79 | // 广播 80 | String tid = wrapper.broadcastTransaction(signTransaction); 81 | // 打印交易ID 82 | log.debug("交易ID:{}", tid); 83 | } 84 | 85 | /** 86 | * 转账处理器上下文 87 | */ 88 | protected TransferHandlerContext createTransferHandlerContext() { 89 | TransferHandlerContext context = new TransferHandlerContext(); 90 | context.addHandler(TrxTransferHandler.class.getName(), new TrxTransferHandler()); 91 | context.addHandler(Trc20TransferHandler.class.getName(), new Trc20TransferHandler()); 92 | context.addHandler(Trc10TransferHandler.class.getName(), new Trc10TransferHandler()); 93 | return context; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/handler/collection/CollectionTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.collection; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.tron.easywork.BaseTest; 6 | import org.tron.easywork.handler.transfer.Trc20TransferHandler; 7 | import org.tron.easywork.handler.transfer.TrxTransferHandler; 8 | import org.tron.easywork.model.ReferenceBlock; 9 | import org.tron.easywork.util.Trc20ContractUtil; 10 | import org.tron.trident.core.exceptions.IllegalException; 11 | import org.tron.trident.proto.Chain; 12 | 13 | import java.io.BufferedReader; 14 | import java.io.File; 15 | import java.io.FileReader; 16 | import java.io.IOException; 17 | import java.math.BigDecimal; 18 | import java.util.List; 19 | 20 | /** 21 | * @author Admin 22 | * @version 1.0 23 | * @time 2022-11-08 20:12 24 | */ 25 | @Slf4j 26 | public class CollectionTest extends BaseTest { 27 | 28 | // 资金归集 29 | @Test 30 | public void fundCollection() throws IllegalException { 31 | // 私钥列表 32 | List keys; 33 | // 读取需要归集的私钥 - 一行一个 34 | File file = new File("D:/Admin/Desktop/tron.txt"); 35 | try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) { 36 | keys = bufferedReader.lines().filter(line -> line.length() == 64).toList(); 37 | keys.forEach(log::debug); 38 | } catch (IOException e) { 39 | throw new RuntimeException(e); 40 | } 41 | 42 | // Trx转账处理器 43 | TrxTransferHandler trxTransferHandler = new TrxTransferHandler(); 44 | // Trc20转账处理器 45 | Trc20TransferHandler trc20TransferHandler = new Trc20TransferHandler(); 46 | 47 | // 归集配置 48 | FundCollectionConfig config = new FundCollectionConfig( 49 | Trc20ContractUtil.readTrc20ContractInfo("TFd1piJ8iXmJQicTicq4zChDSNSMLPFR4w", wrapper), 50 | "TBkg2JK18tY2YZArQgiS9DygzhVKptFirn", 51 | "TBkg2JK18tY2YZArQgiS9DygzhVKptFirn", 52 | "TP6QorvxAJ4bXg21LterCpGi5oZ2PxybCZ", 53 | List.of("--------******-----"), 54 | BigDecimal.TEN 55 | ); 56 | // 默认值为0,可忽略 57 | config.setHandingFeeProviderPermissionId(0); 58 | 59 | 60 | FundCollection collection = new FundCollection(config, trxTransferHandler, trc20TransferHandler, wrapper); 61 | 62 | // 引用区块 63 | Chain.Block nowBlock = wrapper.getNowBlock(); 64 | ReferenceBlock referenceBlock = new ReferenceBlock(nowBlock.getBlockHeader()); 65 | 66 | for (String key : keys) { 67 | try { 68 | log.debug("--------------------------------"); 69 | collection.collection(key, referenceBlock); 70 | } catch (InterruptedException e) { 71 | log.error("Sleep睡眠异常"); 72 | e.printStackTrace(); 73 | } catch (RuntimeException e) { 74 | log.debug(e.getMessage()); 75 | e.printStackTrace(); 76 | } catch (Exception e) { 77 | log.debug("兜底:{}", e.getMessage()); 78 | e.printStackTrace(); 79 | } 80 | } 81 | 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/util/TransferUtil.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.util; 2 | 3 | import com.google.protobuf.ByteString; 4 | import org.tron.easywork.exception.FunctionSelectorException; 5 | import org.tron.easywork.exception.SmartParamDecodeException; 6 | import org.tron.easywork.model.Transfer; 7 | import org.tron.easywork.model.TransferFunctionParam; 8 | import org.tron.trident.proto.Contract; 9 | import org.tron.trident.utils.Base58Check; 10 | 11 | import java.math.BigDecimal; 12 | import java.math.BigInteger; 13 | 14 | /** 15 | * @author Admin 16 | * @version 1.0 17 | * @time 2023-02-12 01:14 18 | */ 19 | public class TransferUtil { 20 | 21 | 22 | /** 23 | * 获取转账信息 24 | * 25 | * @param transferContract trx合约 26 | * @return 转账信息 27 | */ 28 | public static Transfer getTransferInfo(Contract.TransferContract transferContract) { 29 | // 到账地址 30 | ByteString toAddressBs = transferContract.getToAddress(); 31 | String toAddress = Base58Check.bytesToBase58(toAddressBs.toByteArray()); 32 | // 转账(发起人)地址 33 | ByteString fromAddressBs = transferContract.getOwnerAddress(); 34 | String fromAddress = Base58Check.bytesToBase58(fromAddressBs.toByteArray()); 35 | // 转账金额 36 | long amount = transferContract.getAmount(); 37 | return Transfer.trxTransferBuilder(fromAddress, toAddress, BigDecimal.valueOf(amount)).build(); 38 | } 39 | 40 | /** 41 | * 获取转账信息 42 | * 43 | * @param transferAssetContract trc10合约 44 | * @return 转账信息 45 | */ 46 | public static Transfer getTransferInfo(Contract.TransferAssetContract transferAssetContract) { 47 | // 到账地址 48 | ByteString toAddressBs = transferAssetContract.getToAddress(); 49 | String toAddress = Base58Check.bytesToBase58(toAddressBs.toByteArray()); 50 | // 转账(发起人)地址 51 | ByteString fromAddressBs = transferAssetContract.getOwnerAddress(); 52 | String fromAddress = Base58Check.bytesToBase58(fromAddressBs.toByteArray()); 53 | // 转账金额 54 | long amount = transferAssetContract.getAmount(); 55 | // tokenId 56 | ByteString assetNameBs = transferAssetContract.getAssetName(); 57 | String assetName = assetNameBs.toStringUtf8(); 58 | return Transfer.trc10TransferBuilder(fromAddress, toAddress, BigDecimal.valueOf(amount), new BigInteger(assetName)).build(); 59 | } 60 | 61 | 62 | /** 63 | * 获取转账信息 64 | * 65 | * @param triggerSmartContract trc20 合约 66 | * @return 转账信息 67 | */ 68 | public static Transfer getTransferInfo(Contract.TriggerSmartContract triggerSmartContract) throws SmartParamDecodeException, FunctionSelectorException { 69 | // 转账数据:到账地址、交易金额 70 | TransferFunctionParam transferFunctionParam = Trc20ContractUtil.getTransferFunctionParam(triggerSmartContract); 71 | // 发送人 72 | byte[] fromAddressBs = triggerSmartContract.getOwnerAddress().toByteArray(); 73 | String fromAddress = Base58Check.bytesToBase58(fromAddressBs); 74 | // 合约地址 75 | byte[] contractAddressBs = triggerSmartContract.getContractAddress().toByteArray(); 76 | String contractAddress = Base58Check.bytesToBase58(contractAddressBs); 77 | return Transfer.trc20TransferBuilder(fromAddress, transferFunctionParam.getToAddress(), 78 | transferFunctionParam.getAmount(), contractAddress).build(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/local/ParseTransactionTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.local; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.Test; 6 | import org.tron.easywork.BaseTest; 7 | import org.tron.easywork.exception.FunctionSelectorException; 8 | import org.tron.easywork.exception.SmartParamDecodeException; 9 | import org.tron.easywork.handler.transfer.TransferHandler; 10 | import org.tron.easywork.handler.transfer.TransferHandlerContext; 11 | import org.tron.easywork.handler.transfer.TrxTransferHandler; 12 | import org.tron.easywork.model.Transfer; 13 | import org.tron.trident.core.exceptions.IllegalException; 14 | import org.tron.trident.proto.Chain; 15 | 16 | /** 17 | * 解析转账交易内容 18 | * 19 | * @author Admin 20 | * @version 1.0 21 | * @time 2023-02-12 09:02 22 | */ 23 | @Slf4j 24 | public class ParseTransactionTest extends BaseTest { 25 | 26 | 27 | /** 28 | * 解析转账交易信息(通过Context) 29 | * 30 | * @throws IllegalException api参数错误 31 | * @throws FunctionSelectorException TRC20转账函数选择器错误 32 | * @throws InvalidProtocolBufferException unpack解包异常 33 | * @throws SmartParamDecodeException ABI解码错误 34 | */ 35 | @Test 36 | public void parseTransactionByContext() throws IllegalException, FunctionSelectorException, InvalidProtocolBufferException, SmartParamDecodeException { 37 | // 转账处理器上下文 38 | TransferHandlerContext context = this.createTransferHandlerContext(); 39 | 40 | // 交易ID 41 | String tid = "c2cc785e07a75b6d62d6f6b0b3bb69369dbbce1c9d780163740a48cb8efa9393"; 42 | // 交易 43 | Chain.Transaction transaction = wrapper.getTransactionById(tid); 44 | // 获取转账处理器 45 | TransferHandler handler = context.getHandler(transaction); 46 | if (handler == null) { 47 | return; 48 | } 49 | // 转账信息 50 | Transfer transfer = handler.parse(transaction); 51 | // 打印转账信息 52 | log.debug(transfer.toString()); 53 | 54 | 55 | // 交易ID 56 | tid = "4b29d9ad2aef43a4ec772f68b4b91e342e083d2a6492fa151ead8553bab63357"; 57 | // 交易 58 | transaction = wrapper.getTransactionById(tid); 59 | // 获取转账处理器 60 | handler = context.getHandler(transaction); 61 | if (handler == null) { 62 | return; 63 | } 64 | // 转账信息 65 | transfer = handler.parse(transaction); 66 | // 打印转账信息 67 | log.debug(transfer.toString()); 68 | } 69 | 70 | /** 71 | * 指定解析转账交易,不推荐使用,除非业务需求仅处理某一种类型的转账。 72 | */ 73 | @Test 74 | public void parseTransaction() throws IllegalException, FunctionSelectorException, InvalidProtocolBufferException, SmartParamDecodeException { 75 | // 系统中定义了一个单例TRX转账处理器 76 | TrxTransferHandler handler = new TrxTransferHandler(); 77 | // 交易ID 78 | String tid = "220e268edc6dbca2b100cb63d2cab0bf2f0f36ad7cb746fee699985c0d1f438a"; 79 | // 查询交易 80 | Chain.Transaction transaction = wrapper.getTransactionById(tid); 81 | // 检查这个交易是否被 TRX转账处理器 所支持 82 | if (handler.supportContractType(transaction)) { 83 | Transfer transfer = handler.parse(transaction); 84 | log.debug(transfer.toString()); 85 | } 86 | // 如果此处要搞 else if 建议查看上面使用Context的例子 87 | else { 88 | log.warn("仅支持 TRX转账 !"); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/model/Transfer.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.tron.easywork.enums.TransactionStatus; 8 | import org.tron.easywork.enums.TransferType; 9 | 10 | import java.math.BigDecimal; 11 | import java.math.BigInteger; 12 | 13 | /** 14 | * @author Admin 15 | * @version 1.0 16 | * @time 2023-02-12 01:00 17 | */ 18 | @Getter 19 | @Setter 20 | @ToString 21 | @Builder(toBuilder = true) 22 | public class Transfer { 23 | 24 | 25 | /** 26 | * 交易哈希 27 | */ 28 | private String id; 29 | 30 | /** 31 | * 转出地址 32 | */ 33 | private String from; 34 | 35 | /** 36 | * 转入地址 37 | */ 38 | private String to; 39 | 40 | /** 41 | * 转账金额 42 | */ 43 | private BigDecimal amount; 44 | 45 | /** 46 | * 备注 47 | */ 48 | private String memo; 49 | 50 | /** 51 | * 转账类型 52 | */ 53 | private TransferType transferType; 54 | 55 | /** 56 | * 交易状态 57 | */ 58 | private TransactionStatus status; 59 | 60 | /** 61 | * 权限ID 62 | */ 63 | private Integer permissionId; 64 | 65 | /** 66 | * 创建时间 67 | */ 68 | private Long timestamp; 69 | 70 | /** 71 | * 过期时间 72 | */ 73 | private Long expiration; 74 | 75 | /** 76 | * TRC10 资源名称 77 | */ 78 | private BigInteger assetName; 79 | 80 | /** 81 | * TRC20合约地址 82 | */ 83 | private String contractAddress; 84 | 85 | /** 86 | * TRC20 矿工费限制 - 单位sum 87 | */ 88 | private Long feeLimit; 89 | 90 | 91 | /** 92 | * 上链时间 93 | */ 94 | private Long broadcastTime; 95 | 96 | /** 97 | * 区块哈希 98 | */ 99 | private String blockId; 100 | 101 | /** 102 | * 区块高度 103 | */ 104 | private Long blockHeight; 105 | 106 | 107 | /** 108 | * 创建TRC20转账构建者 109 | * 110 | * @param from 来源地址/转出地址 111 | * @param to 到账地址 112 | * @param amount 金额 113 | * @param contractAddress 合约地址 114 | * @return TRC20转账构建者 115 | */ 116 | public static TransferBuilder trc20TransferBuilder(String from, String to, BigDecimal amount, String contractAddress) { 117 | return Transfer.builder().from(from).to(to).amount(amount).transferType(TransferType.TRC20).contractAddress(contractAddress); 118 | } 119 | 120 | /** 121 | * 创建TRX转账构建者 122 | * 123 | * @param from 来源地址/转出地址 124 | * @param to 到账地址 125 | * @param amount 金额 126 | * @return TRX转账构建者 127 | */ 128 | public static TransferBuilder trxTransferBuilder(String from, String to, BigDecimal amount) { 129 | return Transfer.builder().from(from).to(to).amount(amount).transferType(TransferType.TRX); 130 | } 131 | 132 | /** 133 | * 创建TRC10转账构建者 134 | * 135 | * @param from 来源地址/转出地址 136 | * @param to 到账地址 137 | * @param amount 金额 138 | * @param assetName TRC10 资源名称 139 | * @return TRC10转账构建者 140 | */ 141 | public static TransferBuilder trc10TransferBuilder(String from, String to, BigDecimal amount, BigInteger assetName) { 142 | return Transfer.builder().from(from).to(to).amount(amount).transferType(TransferType.TRX).assetName(assetName); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/TransferTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.tron.trident.abi.TypeReference; 6 | import org.tron.trident.abi.datatypes.Address; 7 | import org.tron.trident.abi.datatypes.Bool; 8 | import org.tron.trident.abi.datatypes.Function; 9 | import org.tron.trident.abi.datatypes.generated.Uint256; 10 | import org.tron.trident.core.contract.Trc20Contract; 11 | import org.tron.trident.core.exceptions.IllegalException; 12 | import org.tron.trident.core.transaction.TransactionBuilder; 13 | import org.tron.trident.proto.Chain; 14 | import org.tron.trident.proto.Response; 15 | import org.tron.trident.utils.Convert; 16 | 17 | import java.math.BigDecimal; 18 | import java.math.BigInteger; 19 | import java.util.Arrays; 20 | 21 | /** 22 | * @author Admin 23 | * @version 1.0 24 | * @time 2022-11-02 17:47 25 | */ 26 | @Slf4j 27 | public class TransferTest extends BaseTest { 28 | 29 | /** 30 | * trc20 转账 - trident原生 - 非本地构造交易 31 | */ 32 | @Test 33 | public void transferTrc20_trident() { 34 | // 真实金额 - 单位个 35 | long realAmount = 11; 36 | 37 | // 根据合约地址获取合约信息 38 | org.tron.trident.core.contract.Contract contract = wrapper.getContract(contractAddress); 39 | // 构造trc20合约 40 | Trc20Contract trc20Contract = new Trc20Contract(contract, from, wrapper); 41 | 42 | // trc20 合约转账 43 | String tid = trc20Contract.transfer( 44 | to, 45 | realAmount, 46 | // 精度 47 | trc20Contract.decimals().intValue(), 48 | "备注", 49 | // feeLimit 50 | Convert.toSun("50", Convert.Unit.TRX).longValue() 51 | ); 52 | log.debug(tid); 53 | } 54 | 55 | /** 56 | * trx 转账 - trident原生 - 非本地构造交易 57 | */ 58 | @Test 59 | public void transferTrx_trident() throws IllegalException { 60 | // trx 个数 61 | BigDecimal realAmount = BigDecimal.valueOf(30); 62 | // sun 个数 63 | BigDecimal sun = Convert.toSun(realAmount, Convert.Unit.TRX); 64 | 65 | // 远程构造交易 66 | Response.TransactionExtention transfer = 67 | wrapper.transfer(from, to, sun.longValue()); 68 | // 签名 69 | Chain.Transaction signTransaction = wrapper.signTransaction(transfer); 70 | // 广播 71 | String tid = wrapper.broadcastTransaction(signTransaction); 72 | log.debug(tid); 73 | } 74 | 75 | 76 | /** 77 | * trc20 转账 - trident原生 - 非本地构造交易 78 | * 小数点转账,复制出trident源码,加以修改 79 | */ 80 | @Test 81 | public void transferTrc20_trident_custom() { 82 | // 真实金额 - 单位个 83 | String realAmount = "2.333"; 84 | 85 | // 根据合约地址获取合约信息 86 | org.tron.trident.core.contract.Contract contract = wrapper.getContract(contractAddress); 87 | // 构造trc20合约 88 | Trc20Contract trc20Contract = new Trc20Contract(contract, from, wrapper); 89 | 90 | // 精度 91 | int decimals = trc20Contract.decimals().intValue(); 92 | // 手续费 93 | long feeLimit = Convert.toSun("50", Convert.Unit.TRX).longValue(); 94 | // 实际转账金额(trc20不可分割单位) 95 | BigInteger transferAmount = new BigDecimal(realAmount) 96 | .multiply(BigDecimal.TEN.pow(decimals)) 97 | .toBigInteger(); 98 | 99 | Function transfer = new Function("transfer", 100 | Arrays.asList(new Address(to), 101 | new Uint256(transferAmount)), 102 | Arrays.asList(new TypeReference() { 103 | })); 104 | 105 | TransactionBuilder builder = wrapper.triggerCall(from, contractAddress, transfer); 106 | builder.setFeeLimit(feeLimit); 107 | builder.setMemo("备注"); 108 | 109 | Chain.Transaction signedTxn = wrapper.signTransaction(builder.build()); 110 | String tid = wrapper.broadcastTransaction(signedTxn); 111 | log.debug(tid); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/handler/transfer/Trc20TransferHandler.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.transfer; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | import org.tron.easywork.exception.FunctionSelectorException; 6 | import org.tron.easywork.exception.SmartParamDecodeException; 7 | import org.tron.easywork.model.ReferenceBlock; 8 | import org.tron.easywork.model.Transfer; 9 | import org.tron.easywork.util.TransferUtil; 10 | import org.tron.trident.abi.FunctionEncoder; 11 | import org.tron.trident.abi.TypeReference; 12 | import org.tron.trident.abi.datatypes.Address; 13 | import org.tron.trident.abi.datatypes.Bool; 14 | import org.tron.trident.abi.datatypes.Function; 15 | import org.tron.trident.abi.datatypes.generated.Uint256; 16 | import org.tron.trident.core.ApiWrapper; 17 | import org.tron.trident.proto.Chain; 18 | import org.tron.trident.proto.Contract; 19 | import org.tron.trident.utils.Convert; 20 | 21 | import java.math.BigDecimal; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | /** 26 | * @author Admin 27 | * @version 1.0 28 | * @time 2023-02-11 09:18 29 | */ 30 | public class Trc20TransferHandler extends BaseTransferHandler { 31 | /** 32 | * 默认矿工费限制,单位sum 33 | */ 34 | private final Long defaultFeeLimit; 35 | 36 | public Trc20TransferHandler() { 37 | // 设置默认矿工费限制为 50 TRX 38 | this.defaultFeeLimit = Convert.toSun(BigDecimal.valueOf(50), Convert.Unit.TRX).longValue(); 39 | } 40 | 41 | public Trc20TransferHandler(Long defaultFeeLimit) { 42 | this.defaultFeeLimit = defaultFeeLimit; 43 | } 44 | 45 | @Override 46 | protected Chain.Transaction.Contract.ContractType getContractType() { 47 | return Chain.Transaction.Contract.ContractType.TriggerSmartContract; 48 | } 49 | 50 | 51 | @Override 52 | protected Any createContractParameter(Transfer transfer) { 53 | // 构造trc20转账函数 54 | Function function = new Function( 55 | "transfer", 56 | Arrays.asList( 57 | new Address(transfer.getTo()), 58 | new Uint256(transfer.getAmount().toBigInteger())), 59 | List.of(new TypeReference() { 60 | }) 61 | ); 62 | // 编码 63 | String encodedHex = FunctionEncoder.encode(function); 64 | // 构造trc20合约信息 65 | Contract.TriggerSmartContract contract = Contract.TriggerSmartContract.newBuilder() 66 | .setOwnerAddress(ApiWrapper.parseAddress(transfer.getFrom())) 67 | .setContractAddress(ApiWrapper.parseAddress(transfer.getContractAddress())) 68 | .setData(ApiWrapper.parseHex(encodedHex)) 69 | .build(); 70 | return Any.pack(contract); 71 | } 72 | 73 | @Override 74 | protected Chain.Transaction.raw.Builder initTransactionRawBuilder(Transfer transferInfo, ReferenceBlock referenceBlock) { 75 | Chain.Transaction.raw.Builder rawBuilder = super.initTransactionRawBuilder(transferInfo, referenceBlock); 76 | // 设置智能合约手续费限制 77 | if (null != transferInfo.getFeeLimit()) { 78 | rawBuilder.setFeeLimit(transferInfo.getFeeLimit()); 79 | } else { 80 | rawBuilder.setFeeLimit(defaultFeeLimit); 81 | } 82 | return rawBuilder; 83 | } 84 | 85 | @Override 86 | public Transfer parse(Chain.Transaction transaction) throws InvalidProtocolBufferException, SmartParamDecodeException, FunctionSelectorException { 87 | Transfer parse = super.parse(transaction); 88 | parse.setFeeLimit(transaction.getRawData().getFeeLimit()); 89 | return parse; 90 | } 91 | 92 | @Override 93 | protected Contract.TriggerSmartContract unpack(Any any) throws InvalidProtocolBufferException { 94 | return any.unpack(Contract.TriggerSmartContract.class); 95 | } 96 | 97 | @Override 98 | protected Transfer getTransferInfo(Contract.TriggerSmartContract triggerSmartContract) throws SmartParamDecodeException, FunctionSelectorException { 99 | return TransferUtil.getTransferInfo(triggerSmartContract); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.tron 8 | easywork 9 | 2.5 10 | 11 | 12 | 17 13 | 17 14 | UTF-8 15 | 0.9.1 16 | 1.66.0 17 | 18 | 19 | 20 | 21 | org.projectlombok 22 | lombok 23 | 1.18.30 24 | provided 25 | 26 | 27 | 28 | io.grpc 29 | grpc-netty-shaded 30 | ${grpc.version} 31 | provided 32 | 33 | 34 | io.grpc 35 | grpc-netty 36 | ${grpc.version} 37 | provided 38 | 39 | 40 | io.grpc 41 | grpc-okhttp 42 | ${grpc.version} 43 | provided 44 | 45 | 46 | io.grpc 47 | grpc-protobuf 48 | ${grpc.version} 49 | provided 50 | 51 | 52 | io.grpc 53 | grpc-stub 54 | ${grpc.version} 55 | provided 56 | 57 | 58 | 59 | org.bouncycastle 60 | bcprov-jdk18on 61 | 1.76 62 | provided 63 | 64 | 65 | 66 | org.tron.trident 67 | abi 68 | ${trident.version} 69 | provided 70 | 71 | 72 | 73 | org.tron.trident 74 | utils 75 | ${trident.version} 76 | provided 77 | 78 | 79 | org.tron.trident 80 | core 81 | ${trident.version} 82 | provided 83 | 84 | 85 | 86 | ch.qos.logback 87 | logback-classic 88 | 1.4.12 89 | test 90 | 91 | 92 | 93 | junit 94 | junit 95 | 4.13.2 96 | test 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-source-plugin 106 | 3.3.0 107 | 108 | true 109 | 110 | 111 | 112 | compile 113 | 114 | jar 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/util/Trc20ContractUtil.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.util; 2 | 3 | import org.bouncycastle.util.encoders.Hex; 4 | import org.tron.easywork.constant.TronConstants; 5 | import org.tron.easywork.exception.FunctionSelectorException; 6 | import org.tron.easywork.exception.SmartParamDecodeException; 7 | import org.tron.easywork.model.TransferFunctionParam; 8 | import org.tron.easywork.model.Trc20ContractInfo; 9 | import org.tron.trident.abi.FunctionEncoder; 10 | import org.tron.trident.abi.FunctionReturnDecoder; 11 | import org.tron.trident.abi.TypeDecoder; 12 | import org.tron.trident.abi.TypeReference; 13 | import org.tron.trident.abi.datatypes.Address; 14 | import org.tron.trident.abi.datatypes.Function; 15 | import org.tron.trident.abi.datatypes.NumericType; 16 | import org.tron.trident.abi.datatypes.generated.Uint256; 17 | import org.tron.trident.core.ApiWrapper; 18 | import org.tron.trident.core.contract.Trc20Contract; 19 | import org.tron.trident.proto.Contract; 20 | import org.tron.trident.proto.Response; 21 | import org.tron.trident.utils.Numeric; 22 | 23 | import java.math.BigDecimal; 24 | import java.math.BigInteger; 25 | import java.util.List; 26 | 27 | /** 28 | * 智能合约解码 29 | * 30 | * @author Admin 31 | * @version 1.0 32 | * @time 2022-04-01 13:36 33 | */ 34 | public class Trc20ContractUtil { 35 | 36 | 37 | /** 38 | * 读取 trc20 信息 39 | * 40 | * @param concatAddress 合约地址 41 | * @param wrapper wrapper 42 | * @return trc20 信息 43 | */ 44 | public static Trc20ContractInfo readTrc20ContractInfo(String concatAddress, ApiWrapper wrapper) { 45 | org.tron.trident.core.contract.Contract contract = wrapper.getContract(concatAddress); 46 | Trc20Contract trc20Contract = new Trc20Contract(contract, concatAddress, wrapper); 47 | BigInteger decimals = trc20Contract.decimals(); 48 | String symbol = trc20Contract.symbol(); 49 | return new Trc20ContractInfo(concatAddress, symbol, new BigDecimal(decimals)); 50 | } 51 | 52 | /** 53 | * 查询trc20余额 54 | * 55 | * @param contractAddress 合约地址 56 | * @param address 账户地址 57 | * @return 余额 58 | */ 59 | public static BigDecimal trc20BalanceOf(String contractAddress, String address, ApiWrapper wrapper) { 60 | // 构造trc20查余额函数 61 | Function balanceOf = new Function( 62 | "balanceOf", 63 | List.of(new Address(address)), 64 | List.of(new TypeReference() { 65 | }) 66 | ); 67 | // 编码 68 | String encodedHex = FunctionEncoder.encode(balanceOf); 69 | // 构造trc20合约信息 70 | org.tron.trident.proto.Contract.TriggerSmartContract contract = org.tron.trident.proto.Contract.TriggerSmartContract.newBuilder() 71 | .setContractAddress(ApiWrapper.parseAddress(contractAddress)) 72 | .setData(ApiWrapper.parseHex(encodedHex)) 73 | .build(); 74 | // 查询余额 75 | Response.TransactionExtention tx = wrapper.blockingStub.triggerConstantContract(contract); 76 | // 余额 77 | String result = Numeric.toHexString(tx.getConstantResult(0).toByteArray()); 78 | BigInteger balance = (BigInteger) FunctionReturnDecoder.decode(result, balanceOf.getOutputParameters()).get(0).getValue(); 79 | return new BigDecimal(balance); 80 | } 81 | 82 | /** 83 | * 转账事件解析 84 | * 85 | * @param triggerSmartContract 智能合约 86 | * @return 到账地址+金额 87 | * @throws FunctionSelectorException 函数选择器错误 88 | * @throws SmartParamDecodeException 转账数据解析错误 89 | */ 90 | public static TransferFunctionParam getTransferFunctionParam(Contract.TriggerSmartContract triggerSmartContract) throws FunctionSelectorException, SmartParamDecodeException { 91 | String data = Hex.toHexString(triggerSmartContract.getData().toByteArray()); 92 | return getTransferFunctionParam(data); 93 | } 94 | 95 | /** 96 | * 智能合约转账函数数据解析 97 | * 98 | * @param data triggerSmartContract.data 99 | * @return 转账数据(到账地址 、 金额) 100 | * @throws FunctionSelectorException 函数选择器错误 101 | * @throws SmartParamDecodeException 转账数据解析错误 102 | */ 103 | public static TransferFunctionParam getTransferFunctionParam(String data) throws FunctionSelectorException, SmartParamDecodeException { 104 | // 函数选择器,必须为【a9059cbb】 105 | String funcId = data.substring(0, 8); 106 | if (!TronConstants.TRANSFER_FUNC_ID_BY_KECCAK256.equals(funcId)) { 107 | throw new FunctionSelectorException(funcId + "不是标准转账函数!"); 108 | } 109 | // 收款人地址 110 | String toAddress = data.substring(8, 72); 111 | // 发送金额 112 | String amount = data.substring(72, 136); 113 | try { 114 | Address address = TypeDecoder.decodeAddress(toAddress); 115 | NumericType numericType = TypeDecoder.decodeNumeric(amount,Uint256.class); 116 | return new TransferFunctionParam(address.getValue(), new BigDecimal(numericType.getValue())); 117 | } catch (Exception e) { 118 | throw new SmartParamDecodeException("智能合约转账函数参数异常(ABI解码错误):" + data, e.getCause()); 119 | } 120 | } 121 | 122 | /** 123 | * 智能合约转账函数数据解析 124 | * 125 | * @param data triggerSmartContract.data 126 | * @return 转账数据(到账地址 、 金额) 127 | * @throws FunctionSelectorException 函数选择器错误 128 | * @throws SmartParamDecodeException 转账数据解析错误 129 | */ 130 | public static TransferFunctionParam getTransferFunctionParam2(String data) throws FunctionSelectorException, SmartParamDecodeException { 131 | // 函数选择器,必须为【a9059cbb】 132 | String funcId = data.substring(0, 8); 133 | if (!TronConstants.TRANSFER_FUNC_ID_BY_KECCAK256.equals(funcId)) { 134 | throw new FunctionSelectorException(funcId + "不是标准转账函数!"); 135 | } 136 | // 收款人地址 137 | String toAddress = data.substring(32, 72); 138 | // 发送金额 139 | String amount = data.substring(72, 136); 140 | try { 141 | Address addressType = (Address) TypeDecoder.instantiateType(new TypeReference

() { 142 | }, toAddress); 143 | NumericType amountType = (NumericType) TypeDecoder.instantiateType(new TypeReference() { 144 | }, amount); 145 | return new TransferFunctionParam(addressType.getValue(), new BigDecimal(amountType.getValue())); 146 | } catch (Exception e) { 147 | throw new SmartParamDecodeException("智能合约转账函数参数异常(ABI解码错误):" + data, e.getCause()); 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/org/tron/easywork/handler/transfer/BaseTransferHandler.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.transfer; 2 | 3 | 4 | import com.google.protobuf.Any; 5 | import com.google.protobuf.ByteString; 6 | import com.google.protobuf.GeneratedMessageV3; 7 | import com.google.protobuf.InvalidProtocolBufferException; 8 | import io.netty.util.internal.StringUtil; 9 | import org.tron.easywork.enums.TransactionStatus; 10 | import org.tron.easywork.exception.FunctionSelectorException; 11 | import org.tron.easywork.exception.SmartParamDecodeException; 12 | import org.tron.easywork.model.ReferenceBlock; 13 | import org.tron.easywork.model.Transfer; 14 | import org.tron.easywork.util.TransactionUtil; 15 | import org.tron.trident.proto.Chain; 16 | 17 | import java.util.Calendar; 18 | import java.util.Date; 19 | 20 | /** 21 | * @author Admin 22 | * @version 1.0 23 | * @time 2023-02-11 09:07 24 | */ 25 | public abstract class BaseTransferHandler implements TransferHandler { 26 | 27 | @Override 28 | public boolean supportContractType(Chain.Transaction.Contract.ContractType contractType) { 29 | return contractType == this.getContractType(); 30 | } 31 | 32 | public boolean supportContractType(Chain.Transaction transaction) { 33 | return this.supportContractType(TransactionUtil.getFirstContractType(transaction)); 34 | } 35 | 36 | /** 37 | * 支持处理的合约类型 38 | * 39 | * @return 合约类型 40 | */ 41 | abstract protected Chain.Transaction.Contract.ContractType getContractType(); 42 | 43 | 44 | /** 45 | * 创建交易原数据构造器,并进行基础配置 46 | *

47 | * 本地构建交易 48 | * bytes ref_block_bytes = 1; //最新块高度的第6到8(不包含)之间的字节 49 | * int64 ref_block_num = 3; //区块高度,可选 50 | * bytes ref_block_hash = 4; //最新块的hash的第8到16(不包含)之间的字节 51 | * 52 | * @param transfer 交易信息 53 | * @param referenceBlock 引用区块 54 | * @return 交易原数据 55 | */ 56 | protected Chain.Transaction.raw.Builder initTransactionRawBuilder(Transfer transfer, ReferenceBlock referenceBlock) { 57 | Date now = new Date(); 58 | Calendar calendar = Calendar.getInstance(); 59 | calendar.setTime(now); 60 | calendar.add(Calendar.HOUR, 8); 61 | 62 | // 备注 63 | ByteString memo = StringUtil.isNullOrEmpty(transfer.getMemo()) ? ByteString.empty() : ByteString.copyFromUtf8(transfer.getMemo()); 64 | 65 | return Chain.Transaction.raw.newBuilder() 66 | // 创建时间 67 | .setTimestamp(now.getTime()) 68 | // 过期时间 69 | .setExpiration(calendar.getTimeInMillis()) 70 | // 参考区块 RefBlockHash 71 | .setRefBlockHash(referenceBlock.getRefBlockHash()) 72 | // 参考区块 RefBlockBytes 73 | .setRefBlockBytes(referenceBlock.getRefBlockBytes()) 74 | // 备注 75 | .setData(memo); 76 | } 77 | 78 | /** 79 | * 初始化交易建造者 80 | * 81 | * @param transfer 交易信息 82 | * @param referenceBlock 引用区块信息 83 | * @return 交易建造者 84 | */ 85 | protected Chain.Transaction.Builder initTransactionBuilder(Transfer transfer, ReferenceBlock referenceBlock) { 86 | 87 | // 交易原数据 88 | Chain.Transaction.raw.Builder rawBuilder = this.initTransactionRawBuilder(transfer, referenceBlock); 89 | 90 | // 添加合约信息 91 | rawBuilder.addContract(this.contractBuilder(transfer)); 92 | 93 | // 构造交易信息 94 | Chain.Transaction.Builder transactionBuilder = Chain.Transaction.newBuilder(); 95 | // 设置原数据 96 | return transactionBuilder.setRawData( 97 | rawBuilder 98 | ); 99 | } 100 | 101 | /** 102 | * 创建合约构造器,并在子类中进行合约配置 103 | * 104 | * @param transfer 交易信息 105 | * @return 合约构造器 106 | */ 107 | protected Chain.Transaction.Contract.Builder contractBuilder(Transfer transfer) { 108 | // 创建合约类容 109 | Any parameter = this.createContractParameter(transfer); 110 | // 合约构造器 111 | return Chain.Transaction.Contract.newBuilder() 112 | // 设置合约类型 113 | .setType(this.getContractType()) 114 | // 合约内容 115 | .setParameter(parameter) 116 | // 权限ID 117 | .setPermissionId(null == transfer.getPermissionId() ? 0 : transfer.getPermissionId()); 118 | } 119 | 120 | @Override 121 | public Chain.Transaction buildLocalTransfer(Transfer transfer, ReferenceBlock referenceBlock) { 122 | return this.initTransactionBuilder(transfer, referenceBlock).build(); 123 | } 124 | 125 | @Override 126 | public Transfer parse(Chain.Transaction transaction) 127 | throws InvalidProtocolBufferException, SmartParamDecodeException, FunctionSelectorException { 128 | 129 | // 检查交易是否成功 130 | boolean status = TransactionUtil.isTransactionSuccess(transaction); 131 | 132 | // 合约 133 | Chain.Transaction.Contract contract = transaction.getRawData().getContract(0); 134 | 135 | // parameter 136 | Any any = contract.getParameter(); 137 | 138 | // 解包 139 | Contract unpack = this.unpack(any); 140 | 141 | // 交易ID 142 | String tid = TransactionUtil.getTransactionId(transaction); 143 | 144 | // 备注 145 | ByteString memoData = transaction.getRawData().getData(); 146 | 147 | // 获取交易信息 148 | Transfer transfer = this.getTransferInfo(unpack); 149 | // 完善已知交易信息 150 | transfer.setId(tid); 151 | transfer.setStatus(status ? TransactionStatus.SUCCESS : TransactionStatus.ERROR); 152 | transfer.setMemo(memoData == ByteString.EMPTY ? null : memoData.toStringUtf8()); 153 | transfer.setPermissionId(TransactionUtil.getFirstPermissionId(transaction)); 154 | transfer.setTimestamp(transaction.getRawData().getTimestamp()); 155 | transfer.setExpiration(transaction.getRawData().getExpiration()); 156 | 157 | return transfer; 158 | } 159 | 160 | /** 161 | * 创建合约内容 - 入参数据 162 | * 163 | * @param transfer 交易信息 164 | * @return 合约内容 165 | */ 166 | abstract protected Any createContractParameter(Transfer transfer); 167 | 168 | 169 | /** 170 | * 合约数据解包 171 | * 172 | * @param any 合约数据 173 | * @return 解码后的合约数据 174 | * @throws InvalidProtocolBufferException 数据解码异常 175 | */ 176 | abstract protected Contract unpack(Any any) throws InvalidProtocolBufferException; 177 | 178 | 179 | /** 180 | * 获取交易信息 181 | * 182 | * @param contract 合约数据 183 | * @return 交易信息 184 | * @throws SmartParamDecodeException 转账解析失败 185 | * @throws FunctionSelectorException 智能合约 函数选择器 错误异常 186 | */ 187 | abstract protected Transfer getTransferInfo(Contract contract) throws SmartParamDecodeException, FunctionSelectorException; 188 | } 189 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/ReadBlockTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.junit.Test; 7 | import org.tron.easywork.exception.FunctionSelectorException; 8 | import org.tron.easywork.exception.SmartParamDecodeException; 9 | import org.tron.easywork.handler.transfer.*; 10 | import org.tron.easywork.model.Transfer; 11 | import org.tron.easywork.util.BlockUtil; 12 | import org.tron.easywork.util.TransactionUtil; 13 | import org.tron.easywork.util.TransferUtil; 14 | import org.tron.trident.core.ApiWrapper; 15 | import org.tron.trident.core.Constant; 16 | import org.tron.trident.core.exceptions.IllegalException; 17 | import org.tron.trident.proto.Chain; 18 | import org.tron.trident.proto.Contract; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * 读取区块信息 24 | * 25 | * @author Admin 26 | * @version 1.0 27 | * @time 2022-11-02 17:30 28 | */ 29 | @Slf4j 30 | public class ReadBlockTest extends BaseTest { 31 | 32 | 33 | /** 34 | * 获取区块中的交易信息 (trident原生思路参考) 35 | */ 36 | @Test 37 | public void blockReadTest_trident() throws IllegalException { 38 | // 此处使用主网 - 测试网读取区块看不出效果,交易数量太少 39 | wrapper = new ApiWrapper(Constant.TRONGRID_MAIN_NET, Constant.TRONGRID_MAIN_NET_SOLIDITY, key); 40 | // 获取最新区块 41 | Chain.Block nowBlock = wrapper.getNowBlock(); 42 | // 区块ID 43 | String blockId = BlockUtil.parseBlockId(nowBlock); 44 | log.info("区块ID:{}", blockId); 45 | log.info("区块高度:{}", nowBlock.getBlockHeader().getRawData().getNumber()); 46 | if (nowBlock.getTransactionsCount() <= 0) { 47 | log.debug("交易数量为0"); 48 | return; 49 | } 50 | // 区块中的所有交易 51 | List transactionsList = nowBlock.getTransactionsList(); 52 | // 遍历 53 | transactionsList.forEach( 54 | transaction -> { 55 | log.debug("--------------------------------"); 56 | // 交易ID 57 | String transactionId = TransactionUtil.getTransactionId(transaction); 58 | log.info("交易ID:{}", transactionId); 59 | // 交易状态 60 | boolean status = transaction.getRet(0).getContractRet().getNumber() == 1; 61 | log.debug("交易状态:{}", status ? "成功" : "失败"); 62 | if (!status) { 63 | return; 64 | } 65 | // 合约 66 | Chain.Transaction.Contract contract = transaction.getRawData().getContract(0); 67 | // 合约类型 68 | Chain.Transaction.Contract.ContractType contractType = contract.getType(); 69 | // parameter - 数据|入参 70 | Any parameter = contract.getParameter(); 71 | // 根据合约类型使用不同的工具进行解码 72 | // 如果是 触发智能合约 操作 73 | if (contractType == Chain.Transaction.Contract.ContractType.TriggerSmartContract) { 74 | try { 75 | log.debug("这是智能合约交易"); 76 | // 解码 77 | org.tron.trident.proto.Contract.TriggerSmartContract triggerSmartContract = 78 | parameter.unpack(org.tron.trident.proto.Contract.TriggerSmartContract.class); 79 | // 获取交易详情 80 | Transfer transfer = TransferUtil.getTransferInfo(triggerSmartContract); 81 | log.debug("类型:{}\t到账地址:{}\t金额:{}", transfer.getTransferType(), transfer.getTo(), transfer.getAmount()); 82 | // ...... 83 | } catch (InvalidProtocolBufferException e) { 84 | log.debug("unpack解包异常"); 85 | e.printStackTrace(); 86 | } catch (SmartParamDecodeException e) { 87 | log.debug("智能合约 转账参数 数据解析异常"); 88 | e.printStackTrace(); 89 | } catch (FunctionSelectorException e) { 90 | // 函数选择器错误 91 | } catch (Exception e) { 92 | log.error("兜底异常:{}", e.getMessage()); 93 | e.printStackTrace(); 94 | } 95 | } 96 | // 如果是trx 97 | else if (contractType == Chain.Transaction.Contract.ContractType.TransferContract) { 98 | try { 99 | log.debug("这是TRX交易"); 100 | Contract.TransferContract unpack = parameter.unpack(Contract.TransferContract.class); 101 | Transfer transferInfo = TransferUtil.getTransferInfo(unpack); 102 | } catch (InvalidProtocolBufferException e) { 103 | throw new RuntimeException(e); 104 | } 105 | } 106 | } 107 | ); 108 | } 109 | 110 | 111 | /** 112 | * 读取主网最新区块的转账交易内容 113 | */ 114 | @Test 115 | public void readBlockTest() throws IllegalException { 116 | // 此处使用主网 - 测试网读取区块看不出效果,交易数量太少 117 | wrapper = new ApiWrapper(Constant.TRONGRID_MAIN_NET, Constant.TRONGRID_MAIN_NET_SOLIDITY, key); 118 | // 获取最新块 119 | Chain.Block nowBlock = wrapper.getNowBlock(); 120 | 121 | // trx转账处理器 122 | TrxTransferHandler trxTransferHandler = new TrxTransferHandler(); 123 | // trc10转账处理器 124 | Trc10TransferHandler trc10TransferHandler = new Trc10TransferHandler(); 125 | // trc20转账处理器 126 | Trc20TransferHandler trc20TransferHandler = new Trc20TransferHandler(); 127 | 128 | // 转账处理器上下文 129 | TransferHandlerContext transferHandlerContext = new TransferHandlerContext(); 130 | transferHandlerContext.addHandler("trxTransferHandler", trxTransferHandler); 131 | transferHandlerContext.addHandler("trc10TransferHandler", trc10TransferHandler); 132 | transferHandlerContext.addHandler("trc20TransferHandler", trc20TransferHandler); 133 | 134 | // 遍历交易列表 135 | nowBlock.getTransactionsList().forEach(transaction -> { 136 | // 根据交易合约类型获取处理器 137 | TransferHandler handler = transferHandlerContext.getHandler(transaction.getRawData().getContract(0).getType()); 138 | if (null == handler) { 139 | return; 140 | } 141 | try { 142 | // 解析交易,不忽略失败的交易 143 | Transfer transfer = handler.parse(transaction); 144 | log.debug("状态:{}\t类型:{}\t到账地址:{}\t金额:{}", transfer.getStatus(), transfer.getTransferType(), transfer.getTo(), transfer.getAmount()); 145 | // 其他逻辑 ................ 146 | } catch (InvalidProtocolBufferException e) { 147 | log.error("解包错误:{}", e.getMessage()); 148 | } catch (SmartParamDecodeException e) { 149 | log.error("转账解析错误:{}", e.getMessage()); 150 | } catch (FunctionSelectorException e) { 151 | // 标准转账函数 - a9059cbb 152 | } catch (Exception e) { 153 | log.error("异常兜底:{}", e.getMessage()); 154 | e.printStackTrace(); 155 | } 156 | }); 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/handler/collection/FundCollection.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.handler.collection; 2 | 3 | import lombok.NonNull; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.tron.easywork.handler.transfer.Trc20TransferHandler; 6 | import org.tron.easywork.handler.transfer.TrxTransferHandler; 7 | import org.tron.easywork.model.AccountInfo; 8 | import org.tron.easywork.model.ReferenceBlock; 9 | import org.tron.easywork.model.Transfer; 10 | import org.tron.easywork.util.Trc20ContractUtil; 11 | import org.tron.trident.core.ApiWrapper; 12 | import org.tron.trident.core.exceptions.IllegalException; 13 | import org.tron.trident.core.key.KeyPair; 14 | import org.tron.trident.proto.Chain; 15 | import org.tron.trident.proto.Response; 16 | import org.tron.trident.utils.Convert; 17 | 18 | import java.math.BigDecimal; 19 | 20 | /** 21 | * 资金归集 22 | *

23 | * 归集指定 trc20合约余额、trx余额 到目标地址 24 | * 25 | * @author Admin 26 | * @version 1.0 27 | * @time 2022-11-08 15:57 28 | */ 29 | @Slf4j 30 | public class FundCollection { 31 | 32 | /** 33 | * 归集配置 34 | */ 35 | private final FundCollectionConfig fundCollectionConfig; 36 | 37 | /** 38 | * Trx转账处理器 39 | */ 40 | private final TrxTransferHandler trxTransferHandler; 41 | 42 | /** 43 | * Trc20转账处理器 44 | */ 45 | private final Trc20TransferHandler trc20TransferHandler; 46 | 47 | /** 48 | * ApiWrapper 49 | */ 50 | private final ApiWrapper wrapper; 51 | 52 | 53 | public FundCollection(FundCollectionConfig fundCollectionConfig, TrxTransferHandler trxTransferHandler, Trc20TransferHandler trc20TransferHandler, ApiWrapper wrapper) { 54 | this.fundCollectionConfig = fundCollectionConfig; 55 | this.trxTransferHandler = trxTransferHandler; 56 | this.trc20TransferHandler = trc20TransferHandler; 57 | this.wrapper = wrapper; 58 | } 59 | 60 | /** 61 | * 资金归集 - 归集 trc20 trx 62 | * 63 | * @param privateKey 小号私钥 - 转出账户 64 | * @param referenceBlock 引用区块 - 用于本地构造交易参数 65 | * @throws InterruptedException 延迟阻塞异常|Sleep睡眠异常 66 | */ 67 | public void collection(@NonNull String privateKey, @NonNull ReferenceBlock referenceBlock) throws InterruptedException { 68 | if (privateKey.trim().length() != 64) { 69 | throw new RuntimeException("错误私钥:" + privateKey); 70 | } 71 | KeyPair keyPair; 72 | try { 73 | keyPair = new KeyPair(privateKey.trim()); 74 | } catch (Exception e) { 75 | throw new RuntimeException("错误私钥:" + privateKey, e); 76 | } 77 | 78 | // 账户信息 79 | AccountInfo account = new AccountInfo(keyPair); 80 | 81 | // 过滤本地账户 - 不处理归集目标地址 - 不能自己给自己转账 82 | if (account.getBase58CheckAddress().equals(fundCollectionConfig.getTargetAddressOfTrc20()) || 83 | account.getBase58CheckAddress().equals(fundCollectionConfig.getTargetAddressOfTrx()) || 84 | account.getBase58CheckAddress().equals(fundCollectionConfig.getHandingFeeProviderAddress())) { 85 | log.debug("本地账户,不予处理"); 86 | return; 87 | } 88 | 89 | log.debug("开始处理账户:{}", account.getBase58CheckAddress()); 90 | 91 | // 查询Trc20余额 92 | BigDecimal balance = Trc20ContractUtil.trc20BalanceOf(fundCollectionConfig.getTrc20ContractInfo().getAddress(), account.getBase58CheckAddress(), wrapper); 93 | log.debug("Trc20余额:{}", fundCollectionConfig.getTrc20ContractInfo().getRealAmount(balance).stripTrailingZeros().toPlainString()); 94 | 95 | if (balance.compareTo(BigDecimal.ZERO) == 0) { 96 | log.debug("无需归集trc20"); 97 | } else { 98 | try { 99 | // 派发Trx矿工费 - 无需单独检查矿工费交易ID,如果失败会直接报错 100 | // trx转账信息 101 | Transfer handingFeeTransfer = Transfer.trxTransferBuilder( 102 | fundCollectionConfig.getHandingFeeProviderAddress(), 103 | account.getBase58CheckAddress(), 104 | Convert.toSun(fundCollectionConfig.getHandingFeeWithTrx(), Convert.Unit.TRX) 105 | ).build(); 106 | // 构建交易 107 | Chain.Transaction handingFeeTransaction = trxTransferHandler.buildLocalTransfer(handingFeeTransfer, referenceBlock); 108 | // 签名 109 | Chain.Transaction signTransaction = null; 110 | for (String key : fundCollectionConfig.getHandingFeeProviderKeys()) { 111 | signTransaction = wrapper.signTransaction(handingFeeTransaction, new KeyPair(key)); 112 | } 113 | // 广播 114 | String tid = wrapper.broadcastTransaction(signTransaction); 115 | log.debug("矿工费派发成功:{}", tid); 116 | } catch (Exception e) { 117 | log.error("矿工费派发失败:{},原因:{}", account.getBase58CheckAddress(), e.getMessage()); 118 | e.printStackTrace(); 119 | return; 120 | } 121 | 122 | // 防止节点反应不过来 - 必须 123 | Thread.sleep(1000); 124 | 125 | // trc20转账信息 126 | Transfer transfer = Transfer.trc20TransferBuilder(account.getBase58CheckAddress(), 127 | fundCollectionConfig.getTargetAddressOfTrc20(), 128 | balance, fundCollectionConfig.getTrc20ContractInfo().getAddress()) 129 | .build(); 130 | // 构建交易 131 | Chain.Transaction transaction = trc20TransferHandler.buildLocalTransfer(transfer, referenceBlock); 132 | // 签名 133 | Chain.Transaction signTransaction = wrapper.signTransaction(transaction, account.getKeyPair()); 134 | // 广播交易 135 | String tid = wrapper.broadcastTransaction(signTransaction); 136 | 137 | // 防止节点反应不过来 - 必须 138 | Thread.sleep(1000); 139 | 140 | // 检查交易 - 需要单独检查trc20交易 - 失败不会报错 141 | try { 142 | Chain.Transaction checkTransaction = wrapper.getTransactionById(tid); 143 | boolean status = checkTransaction.getRet(0).getContractRet().getNumber() == 1; 144 | if (!status) { 145 | log.error("trc20转目标地址失败:{},交易ID:{}", account.getBase58CheckAddress(), tid); 146 | return; 147 | } 148 | log.debug("trc20转目标地址成功:{}", tid); 149 | } catch (IllegalException e) { 150 | log.error("trc20转目标地址失败:{},交易ID:{}", account.getBase58CheckAddress(), tid); 151 | e.printStackTrace(); 152 | return; 153 | } 154 | log.debug("归集trc20成功"); 155 | 156 | 157 | // 防止节点反应不过来 - 必须 158 | Thread.sleep(1000); 159 | } 160 | 161 | 162 | // 查询账户 trx 余额、带宽数量 163 | Response.Account target = wrapper.getAccount(account.getBase58CheckAddress()); 164 | // trx 余额 165 | long trxBalance = target.getBalance(); 166 | log.debug("trx余额:{}", Convert.fromSun(BigDecimal.valueOf(trxBalance), Convert.Unit.TRX)); 167 | // 带宽费 168 | if (trxBalance > 1000000) { 169 | // 剩余带宽 170 | long net = 1500 - target.getFreeNetUsage(); 171 | log.debug("剩余带宽:{}", net); 172 | // 需要带宽费 173 | if (net < 300) { 174 | log.debug("带宽不足,需要支付带宽费用:0.3trx"); 175 | trxBalance = trxBalance - 300000; 176 | } 177 | 178 | // trx转账信息 179 | Transfer incomeTransferInfo = Transfer.trxTransferBuilder(account.getBase58CheckAddress(), 180 | fundCollectionConfig.getTargetAddressOfTrx(), BigDecimal.valueOf(trxBalance)).build(); 181 | // 构造trx转账交易 182 | Chain.Transaction incomeTransfer = trxTransferHandler.buildLocalTransfer(incomeTransferInfo, referenceBlock); 183 | // 签名 184 | Chain.Transaction signTransactionIncome = wrapper.signTransaction(incomeTransfer, account.getKeyPair()); 185 | // 广播 186 | String incomeTid = wrapper.broadcastTransaction(signTransactionIncome); 187 | log.debug("trx归集成功:{}", incomeTid); 188 | } else { 189 | log.debug("trx余额较低,无需归集"); 190 | } 191 | 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/GetInfoTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.ByteString; 5 | import com.google.protobuf.InvalidProtocolBufferException; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.Test; 8 | import org.tron.easywork.model.Trc20ContractInfo; 9 | import org.tron.easywork.util.TransactionUtil; 10 | import org.tron.easywork.util.Trc20ContractUtil; 11 | import org.tron.easywork.util.TronConverter; 12 | import org.tron.trident.core.contract.Contract; 13 | import org.tron.trident.core.contract.Trc20Contract; 14 | import org.tron.trident.core.exceptions.IllegalException; 15 | import org.tron.trident.proto.Chain; 16 | import org.tron.trident.proto.Common; 17 | import org.tron.trident.proto.Response; 18 | import org.tron.trident.utils.Base58Check; 19 | import org.tron.trident.utils.Convert; 20 | 21 | import java.math.BigDecimal; 22 | import java.math.BigInteger; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * 从链上获取信息 28 | * 29 | * @author Admin 30 | * @version 1.0 31 | * @time 2022-11-02 18:54 32 | */ 33 | @Slf4j 34 | public class GetInfoTest extends BaseTest { 35 | 36 | 37 | /** 38 | * # ApiWrapper原装 - 获取trc20余额 39 | */ 40 | @Test 41 | public void simple_balanceOfTrc20() { 42 | // 查询的地址 43 | String address = "TKjPqKq77777FPKUdLRMPNUWtU4jNEpUQF"; 44 | // 获取合约信息 45 | Contract contract = wrapper.getContract(contractAddress); 46 | // 构造trc20合约信息 47 | Trc20Contract trc20Contract = new Trc20Contract(contract, address, wrapper); 48 | // 合约精度 49 | BigInteger decimals = trc20Contract.decimals(); 50 | // 余额 51 | BigInteger balance = trc20Contract.balanceOf(address); 52 | // 真实余额 单位 个 53 | BigDecimal result = TronConverter.getRealAmount(new BigDecimal(balance), decimals.intValue()); 54 | log.debug("剩余数量:{}个", result); 55 | } 56 | 57 | /** 58 | * 获取trc20余额 59 | */ 60 | @Test 61 | public void balanceOfTrc20() { 62 | // 地址 63 | String address = "TP6QorvxAJ4bXg21LterCpGi5oZ2PxybCZ"; 64 | BigDecimal transferAmount = Trc20ContractUtil.trc20BalanceOf(contractAddress, address, wrapper); 65 | log.debug("Trc20余额:{}", transferAmount); 66 | 67 | // 合约信息 68 | Trc20ContractInfo trc20ContractInfo = Trc20ContractUtil.readTrc20ContractInfo(contractAddress, wrapper); 69 | BigDecimal realAmount = trc20ContractInfo.getRealAmount(transferAmount).stripTrailingZeros(); 70 | log.debug("Trc20真实余额:{}个", realAmount); 71 | } 72 | 73 | /** 74 | * # ApiWrapper原装 - 获取trx余额 75 | */ 76 | @Test 77 | public void simple_balanceOfTrx() { 78 | // 地址 79 | String address = from; 80 | // 获取账户信息 81 | Response.Account account = wrapper.getAccount(address); 82 | // 余额 83 | long balance = account.getBalance(); 84 | // 真实余额 85 | BigDecimal trx = Convert.fromSun(new BigDecimal(balance), Convert.Unit.TRX); 86 | log.debug("trx余额:{}", trx.toString()); 87 | 88 | long sum = account.getFrozenList().stream().mapToLong(Response.Account.Frozen::getFrozenBalance).sum(); 89 | long frozenBalance = account.getAccountResource().getFrozenBalanceForEnergy().getFrozenBalance(); 90 | long frozen = sum + frozenBalance; 91 | BigDecimal frozenTrx = Convert.fromSun(new BigDecimal(frozen), Convert.Unit.TRX); 92 | log.debug("trx质押:{}", frozenTrx); 93 | 94 | log.debug("trx 总余额:{}", trx.add(frozenTrx)); 95 | } 96 | 97 | 98 | /** 99 | * 检查账户是否具有权限 100 | */ 101 | @Test 102 | public void accountPermissionUpdateContract() throws IllegalException, InvalidProtocolBufferException { 103 | String checkAddress="TBjxJTNwZeaKrbHyDum5Rwj1xU99999n8Z"; 104 | Chain.Transaction transaction = wrapper.getTransactionById("3ccabcef02f4c0679f811a1600ad3c4ac1977859d7fed85a81371f48032df274"); 105 | // 检查交易是否成功 106 | boolean status = TransactionUtil.isTransactionSuccess(transaction); 107 | if (!status) { 108 | return; 109 | } 110 | // 合约 111 | Chain.Transaction.Contract contract = transaction.getRawData().getContract(0); 112 | // 合约类型 113 | Chain.Transaction.Contract.ContractType contractType = contract.getType(); 114 | // parameter 115 | Any parameter = contract.getParameter(); 116 | if (contractType == Chain.Transaction.Contract.ContractType.AccountPermissionUpdateContract) { 117 | 118 | log.warn("合约类型:{}", contractType.name()); 119 | // 解码 120 | 121 | org.tron.trident.proto.Contract.AccountPermissionUpdateContract accountPermissionUpdateContract = parameter.unpack(org.tron.trident.proto.Contract.AccountPermissionUpdateContract.class); 122 | 123 | // 发送人 124 | byte[] fromAddressBs = accountPermissionUpdateContract.getOwnerAddress().toByteArray(); 125 | String fromAddress = Base58Check.bytesToBase58(fromAddressBs); 126 | 127 | // 所有者权限 128 | Common.Permission owner = accountPermissionUpdateContract.getOwner(); 129 | // 拥有者权限所需权重 130 | long thresholdOfOwner = owner.getThreshold(); 131 | 132 | // 拥有者 133 | List keysList = owner.getKeysList(); 134 | 135 | log.debug("开始检查拥有者权限"); 136 | for (Common.Key key : keysList) { 137 | // 是否包含当前账户 138 | if (Base58Check.bytesToBase58(key.getAddress().toByteArray()).equals(checkAddress)) { 139 | long weight = key.getWeight(); 140 | if (weight >= thresholdOfOwner) { 141 | log.debug("权限到账,拥有者权限,权重:{},可完全支配", weight); 142 | } 143 | else { 144 | log.warn("收到拥有者权限指定,但权重不足,所需权重{},目前拥有:{}", thresholdOfOwner, weight); 145 | } 146 | return; 147 | } 148 | } 149 | log.debug("开始检查活跃权限"); 150 | List activesList = accountPermissionUpdateContract.getActivesList(); 151 | for (Common.Permission permission : activesList) { 152 | log.debug("--活跃权限指定,id:{}", permission.getId()); 153 | // 当前活跃权限所需权重 154 | long thresholdOfActive = permission.getThreshold(); 155 | // 当前活跃权限用户列表 156 | List keyList = permission.getKeysList(); 157 | 158 | // 权限可以操作的功能列表operations 159 | byte[] operations = permission.getOperations().toByteArray(); 160 | 161 | for (Common.Key key : keyList) { 162 | // 是否包含当前账户 163 | if (Base58Check.bytesToBase58(key.getAddress().toByteArray()).equals(checkAddress)) { 164 | // 权重 165 | long weight = key.getWeight(); 166 | 167 | 168 | if (weight >= thresholdOfActive) { 169 | log.debug("收到活跃权限指定,id:{},权重:{},可部分支配", permission.getId(), weight); 170 | log.debug("权限名:{},权限ID:{}", permission.getPermissionName(), permission.getId()); 171 | 172 | // 检查权限列表是否包含【触发智能合约】、【TRX转账】 173 | List contractIdList = new ArrayList<>(); 174 | 175 | for (int i = 0; i < operations.length; i++) { 176 | byte operation = operations[i]; 177 | for (int j = 0; j < 8; j++) { 178 | if ((operation & (1 << j)) != 0) { 179 | contractIdList.add(i * 8 + j); 180 | } 181 | } 182 | } 183 | log.debug(contractIdList.toString()); 184 | if (contractIdList.size() > 0) { 185 | for (Integer value : contractIdList) { 186 | if (value == 1) { 187 | log.debug("TRX转账"); 188 | } 189 | else if (value == 31) { 190 | log.debug("Smart Contract Trigger (TRC20/TRC721/TRC1155 Transfer)"); 191 | } 192 | } 193 | } 194 | } 195 | else { 196 | log.warn("收到活跃权限指定,id:{},但权重不足,所需权重{},目前拥有:{}", 197 | permission.getId(), thresholdOfActive, weight); 198 | // return; 199 | } 200 | } 201 | } 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/local/LocalTransferTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork.local; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.ByteString; 5 | import com.google.protobuf.GeneratedMessageV3; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.Test; 8 | import org.tron.easywork.BaseTest; 9 | import org.tron.easywork.handler.transfer.TransferHandler; 10 | import org.tron.easywork.handler.transfer.TransferHandlerContext; 11 | import org.tron.easywork.handler.transfer.Trc20TransferHandler; 12 | import org.tron.easywork.handler.transfer.TrxTransferHandler; 13 | import org.tron.easywork.model.ReferenceBlock; 14 | import org.tron.easywork.model.Transfer; 15 | import org.tron.easywork.model.Trc20ContractInfo; 16 | import org.tron.easywork.util.Trc20ContractUtil; 17 | import org.tron.trident.abi.FunctionEncoder; 18 | import org.tron.trident.abi.TypeReference; 19 | import org.tron.trident.abi.datatypes.Address; 20 | import org.tron.trident.abi.datatypes.Bool; 21 | import org.tron.trident.abi.datatypes.Function; 22 | import org.tron.trident.abi.datatypes.generated.Uint256; 23 | import org.tron.trident.core.ApiWrapper; 24 | import org.tron.trident.core.exceptions.IllegalException; 25 | import org.tron.trident.proto.Chain; 26 | import org.tron.trident.utils.Convert; 27 | 28 | import java.math.BigDecimal; 29 | import java.util.Arrays; 30 | import java.util.Calendar; 31 | import java.util.Date; 32 | import java.util.List; 33 | 34 | /** 35 | * @author Admin 36 | * @version 1.0 37 | * @time 2023-02-12 07:35 38 | */ 39 | @Slf4j 40 | public class LocalTransferTest extends BaseTest { 41 | 42 | /** 43 | * 构造任何类型的转账交易信息、转账 44 | * 引入 TransferHandlerContext ,可以不用在意传入的转账是什么类型,程序自动识别 45 | */ 46 | @Test 47 | public void TransferContextTest() throws IllegalException { 48 | // 转账处理器上下文 49 | TransferHandlerContext context = this.createTransferHandlerContext(); 50 | 51 | // 真实金额,单位:个 52 | BigDecimal realAmount = BigDecimal.valueOf(5); 53 | // TRC20合约信息 54 | Trc20ContractInfo trc20ContractInfo = Trc20ContractUtil.readTrc20ContractInfo(contractAddress, wrapper); 55 | // 实际金额,单位:合约最小单位 56 | BigDecimal amount = trc20ContractInfo.getTransferAmount(realAmount); 57 | // Trc20转账信息 58 | Transfer transfer = this.createTrc20Transfer(from, to, amount); 59 | // 转账处理器 60 | TransferHandler handler = context.getHandler(transfer.getTransferType().supportContractType); 61 | 62 | ApiWrapper apiWrapper = ApiWrapper.ofShasta(key); 63 | Chain.Block nowBlock = apiWrapper.getNowBlock(); 64 | ReferenceBlock referenceBlock = new ReferenceBlock(nowBlock.getBlockHeader()); 65 | 66 | Chain.Transaction transaction = handler.buildLocalTransfer(transfer, referenceBlock); 67 | this.sendTransaction(transaction); 68 | } 69 | 70 | /** 71 | * TRX转账 72 | */ 73 | @Test 74 | public void transferTrx() throws IllegalException { 75 | // 真实金额,单位:个 76 | BigDecimal realAmount = BigDecimal.valueOf(10); 77 | // 实际金额,单位:sum 78 | BigDecimal amount = Convert.toSun(realAmount, Convert.Unit.TRX); 79 | // TRX转账信息 80 | Transfer transfer = Transfer.trxTransferBuilder(from, to, amount).memo("备注:TRX转账").build(); 81 | // TRX转账处理器 82 | TrxTransferHandler handler = new TrxTransferHandler(); 83 | // 引用区块 84 | ReferenceBlock referenceBlock = this.getReferenceBlock(wrapper); 85 | // 交易 86 | Chain.Transaction transaction = handler.buildLocalTransfer(transfer, referenceBlock); 87 | this.sendTransaction(transaction); 88 | } 89 | 90 | /** 91 | * TRC20转账 92 | */ 93 | @Test 94 | public void transferTrc20() throws IllegalException { 95 | // 真实金额,单位:个 96 | BigDecimal realAmount = BigDecimal.valueOf(10); 97 | // TRC20合约信息 98 | Trc20ContractInfo trc20ContractInfo = Trc20ContractUtil.readTrc20ContractInfo(contractAddress, wrapper); 99 | // 实际金额,单位:合约最小单位 100 | BigDecimal amount = trc20ContractInfo.getTransferAmount(realAmount); 101 | // Trc20转账信息 102 | Transfer transfer = this.createTrc20Transfer(from, to, amount); 103 | // Trc20转账处理器 104 | Trc20TransferHandler handler = new Trc20TransferHandler(); 105 | // 引用区块 106 | ReferenceBlock referenceBlock = this.getReferenceBlock(wrapper); 107 | // 交易 108 | Chain.Transaction transaction = handler.buildLocalTransfer(transfer, referenceBlock); 109 | this.sendTransaction(transaction); 110 | 111 | } 112 | 113 | 114 | /** 115 | * # 333 - 完整的本地交易构造(参考) 116 | */ 117 | @Test 118 | public void localTransferTest() throws IllegalException { 119 | // 真实金额,单位:个 120 | BigDecimal realAmount = BigDecimal.valueOf(1.2); 121 | // TRC20合约信息 122 | Trc20ContractInfo trc20ContractInfo = Trc20ContractUtil.readTrc20ContractInfo(contractAddress, wrapper); 123 | // 实际金额,单位:合约最小单位 124 | BigDecimal amount = trc20ContractInfo.getTransferAmount(realAmount); 125 | // Trc20转账信息 126 | Transfer transfer = this.createTrc20Transfer(from, to, amount); 127 | // 本地转账 128 | this.localTransfer(transfer); 129 | } 130 | 131 | /** 132 | * # 333 -完整的本地交易构造(参考) 133 | * 134 | *

135 | * 原本流程一个交易是将信息通过 gRPC接口 在远程构建,现在使用代码在本地构建交易。 136 | * 好处是减少网络IO次数,更加灵活的配置交易变量 137 | * 需要注意的点,本地构造交易需要一个引用区块,这个区块距离最新区块高度不能超过65535,比如可以在系统中配置一个引用区块全局变量,每两个小时刷新一次,以达到复用效果。 138 | * 文档搜索:本地构建交易 139 | * 1dd048b5183e0d468a7891ad8db79cce6e1046957cd218b75e4e44aed5be27b3 140 | */ 141 | protected void localTransfer(Transfer transfer) throws IllegalException { 142 | // 当前时间 143 | Date now = new Date(); 144 | 145 | // 当前时间 +8 小时 - 用于过期时间 146 | Calendar calendar = Calendar.getInstance(); 147 | calendar.setTime(now); 148 | calendar.add(Calendar.HOUR, 8); 149 | 150 | // 合约类型 151 | Chain.Transaction.Contract.ContractType contractType = transfer.getTransferType().supportContractType; 152 | // 合约信息 153 | GeneratedMessageV3 message; 154 | 155 | if (contractType == Chain.Transaction.Contract.ContractType.TriggerSmartContract) { 156 | // 构造trc20转账函数 157 | Function function = new Function( 158 | "transfer", 159 | Arrays.asList( 160 | new Address(transfer.getTo()), 161 | new Uint256(transfer.getAmount().toBigInteger())), 162 | List.of(new TypeReference() { 163 | }) 164 | ); 165 | // 编码 166 | String encodedHex = FunctionEncoder.encode(function); 167 | 168 | // 构造trc20合约信息 169 | message = org.tron.trident.proto.Contract.TriggerSmartContract.newBuilder() 170 | .setOwnerAddress(ApiWrapper.parseAddress(transfer.getFrom())) 171 | .setContractAddress(ApiWrapper.parseAddress(transfer.getContractAddress())) 172 | .setData(ApiWrapper.parseHex(encodedHex)) 173 | .build(); 174 | } 175 | else if (contractType == Chain.Transaction.Contract.ContractType.TransferAssetContract) { 176 | // 构造trc10合约信息 177 | message = org.tron.trident.proto.Contract.TransferAssetContract.newBuilder() 178 | .setAmount(transfer.getAmount().longValue()) 179 | .setOwnerAddress(ApiWrapper.parseAddress(transfer.getFrom())) 180 | .setToAddress(ApiWrapper.parseAddress(transfer.getTo())) 181 | .setAssetName(ByteString.copyFrom(transfer.getAssetName().toByteArray())) 182 | .build(); 183 | } 184 | else if (contractType == Chain.Transaction.Contract.ContractType.TransferContract) { 185 | // 构造trx转账合约 186 | message = org.tron.trident.proto.Contract.TransferContract.newBuilder() 187 | .setAmount(transfer.getAmount().longValue()) 188 | .setOwnerAddress(ApiWrapper.parseAddress(transfer.getFrom())) 189 | .setToAddress(ApiWrapper.parseAddress(transfer.getTo())) 190 | .build(); 191 | } 192 | else { 193 | return; 194 | } 195 | 196 | // 获取参考区块 197 | Chain.Block nowBlock = wrapper.getNowBlock(); 198 | 199 | // 自定义引用区块信息类 200 | ReferenceBlock referenceBlock = new ReferenceBlock(nowBlock.getBlockHeader()); 201 | 202 | // 构造交易信息 203 | Chain.Transaction.Builder transactionBuilder = Chain.Transaction.newBuilder(); 204 | // 设置交易原数据 205 | transactionBuilder.setRawData( 206 | Chain.Transaction.raw.newBuilder() 207 | // 创建时间 208 | .setTimestamp(now.getTime()) 209 | // 过期时间 210 | .setExpiration(calendar.getTimeInMillis()) 211 | // 参考区块信息 212 | .setRefBlockHash(referenceBlock.getRefBlockHash()) 213 | // 参考区块信息 214 | .setRefBlockBytes(referenceBlock.getRefBlockBytes()) 215 | // 添加合约信息 216 | .addContract( 217 | Chain.Transaction.Contract.newBuilder() 218 | // 设置合约类型 219 | .setType(contractType) 220 | // 合约内容 221 | .setParameter(Any.pack(message)) 222 | // 权限ID 223 | .setPermissionId(0) 224 | ) 225 | // 备注 226 | .setData(ByteString.copyFromUtf8("备注一份")) 227 | // trc20 手续费限制 228 | .setFeeLimit(Convert.toSun(BigDecimal.valueOf(50), Convert.Unit.TRX).longValue()) 229 | ); 230 | Chain.Transaction transaction = transactionBuilder.build(); 231 | this.sendTransaction(transaction); 232 | } 233 | 234 | 235 | } 236 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/MultiSignatureTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork; 2 | 3 | import com.google.protobuf.ByteString; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.Test; 6 | import org.tron.easywork.factory.ApiWrapperFactory; 7 | import org.tron.easywork.handler.transfer.Trc20TransferHandler; 8 | import org.tron.easywork.model.AccountInfo; 9 | import org.tron.easywork.model.ReferenceBlock; 10 | import org.tron.easywork.model.Transfer; 11 | import org.tron.easywork.model.Trc20ContractInfo; 12 | import org.tron.easywork.util.Trc20ContractUtil; 13 | import org.tron.trident.core.ApiWrapper; 14 | import org.tron.trident.core.exceptions.IllegalException; 15 | import org.tron.trident.core.key.KeyPair; 16 | import org.tron.trident.proto.Chain; 17 | import org.tron.trident.proto.Common; 18 | import org.tron.trident.proto.Contract; 19 | import org.tron.trident.proto.Response; 20 | import org.tron.trident.utils.Base58Check; 21 | import org.tron.trident.utils.Convert; 22 | 23 | import java.math.BigDecimal; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.Date; 27 | import java.util.List; 28 | 29 | /** 30 | * 多签相关 31 | *

32 | * 创建活跃权限后,getAccount查询账户信息,可以获得permissionId。 33 | * 如果是拥有者权限,无需改permissionId,直接多签。 34 | * 35 | * @author Admin 36 | * @version 1.0 37 | * @time 2022-11-02 17:59 38 | */ 39 | @Slf4j 40 | public class MultiSignatureTest extends BaseTest { 41 | 42 | /** 43 | * 多签TRX转账 - trident原生 - 非本地构造交易 44 | */ 45 | @Test 46 | public void multiSignature_trident() throws IllegalException { 47 | // A 主账号 48 | AccountInfo account = new AccountInfo(""); 49 | 50 | // B 具有某些活动权限 51 | AccountInfo account1 = new AccountInfo(""); 52 | 53 | // C 具有某些活动权限 54 | AccountInfo account2 = new AccountInfo(""); 55 | 56 | ApiWrapper wrapper = ApiWrapperFactory.create(ApiWrapperFactory.NetType.Shasta, account.getHexPrivateKey(), null); 57 | 58 | 59 | BigDecimal amount = Convert.toSun("1", Convert.Unit.TRX); 60 | 61 | // 构造交易 62 | Response.TransactionExtention transfer = 63 | wrapper.transfer( 64 | account.getBase58CheckAddress() 65 | , to 66 | , amount.longValue() 67 | ); 68 | 69 | // 交易构造器 70 | Chain.Transaction.Builder transactionBuilder = transfer.getTransaction().toBuilder(); 71 | // 使用2号活跃权限 (B5 + C5 = 阈值10) 72 | transactionBuilder.getRawDataBuilder().getContractBuilder(0).setPermissionId(2); 73 | 74 | // 交易 75 | Chain.Transaction transaction = transactionBuilder.build(); 76 | 77 | // 签名 78 | Chain.Transaction sign1 = wrapper.signTransaction(transaction, account1.getKeyPair()); 79 | Chain.Transaction sign2 = wrapper.signTransaction(sign1, account2.getKeyPair()); 80 | 81 | /*byte[] rawData = transactionBuilder.getRawData().toByteArray(); 82 | byte[] tidByte = Hash.sha256(rawData); 83 | 84 | byte[] sign1 = KeyPair.signTransaction(tidByte, account1.getKeyPair()); 85 | byte[] sign2 = KeyPair.signTransaction(tidByte, account2.getKeyPair()); 86 | 87 | Chain.Transaction transaction = transactionBuilder 88 | .addSignature(ByteString.copyFrom(sign1)) 89 | .addSignature(ByteString.copyFrom(sign2)) 90 | .build();*/ 91 | 92 | // 广播并返回交易ID 93 | String id = wrapper.broadcastTransaction(sign2); 94 | log.debug(id); 95 | 96 | } 97 | 98 | /** 99 | * 活跃权限多签 100 | * 101 | * @throws IllegalException 102 | */ 103 | @Test 104 | public void multiSign_trident() throws IllegalException { 105 | // 拥有者 106 | String ownerAddress = "TKjPqKq77777FPKUdLRMPNUWtU4jNEpUQF"; 107 | // 拥有者 此单元测试没用到拥有者私钥 108 | // 仅用于构造ApiWrapper,随意使用任何私钥即可|此测试留空无碍 109 | KeyPair keyPair_owner = new KeyPair(""); 110 | 111 | // 活跃权限账户 112 | KeyPair keyPair_active = new KeyPair("..................3f567257e188335b0db6a0fb970f6db5e3f9c"); 113 | 114 | String toAddress = "TEczEK6uzD88888QhstH6QDwB167ZsXPrb"; 115 | String contractAddress = "TFd1piJ8iXmJQicTicq4zChDSNSMLPFR4w"; 116 | 117 | 118 | ApiWrapper apiWrapper = ApiWrapperFactory 119 | .create(ApiWrapperFactory.NetType.Shasta, keyPair_owner.toPrivateKey(), null); 120 | 121 | Trc20ContractInfo trc20ContractInfo = Trc20ContractUtil.readTrc20ContractInfo(contractAddress, apiWrapper); 122 | 123 | BigDecimal amount = Convert.toSun("1", Convert.Unit.TRX); 124 | 125 | // 构造交易 126 | Response.TransactionExtention transfer = 127 | wrapper.transfer( 128 | ownerAddress, toAddress, amount.longValue() 129 | ); 130 | 131 | // 交易构造器 132 | Chain.Transaction.Builder transactionBuilder = transfer.getTransaction().toBuilder(); 133 | // 使用3号活跃权限 | 活跃权限按顺序从2开始 | 保险可调 getAccount Api 查阅 134 | transactionBuilder.getRawDataBuilder().getContractBuilder(0).setPermissionId(3); 135 | 136 | // 交易 137 | Chain.Transaction transaction = transactionBuilder.build(); 138 | 139 | // 签名 140 | Chain.Transaction signedTransaction = wrapper.signTransaction(transaction, keyPair_active); 141 | 142 | // 广播并返回交易ID 143 | String id = wrapper.broadcastTransaction(signedTransaction); 144 | log.debug(id); 145 | 146 | } 147 | 148 | /** 149 | * 更新账户权限,目前需要花费100trx 150 | */ 151 | @Test 152 | public void permissionUpdate() throws IllegalException { 153 | // TKjPqKq77777FPKUdLRMPNUWtU4jNEpUQF 154 | String privateKey = "e9144533f0edf1a....................ec156c12e474886"; 155 | 156 | // 分配给 1 157 | String permissionAddress1 = "TBjxJTNwZeaKrbHyDum5Rwj1xU99999n8Z"; 158 | // 分配给 2 159 | String permissionAddress2 = "TEczEK6uzD88888QhstH6QDwB167ZsXPrb"; 160 | 161 | ApiWrapper wrapper = ApiWrapperFactory.create(ApiWrapperFactory.NetType.Shasta, privateKey, null); 162 | 163 | // 账户信息 164 | AccountInfo account = new AccountInfo(privateKey); 165 | // 我的地址 ByteString 格式 166 | ByteString ownerAddress = ByteString.copyFrom(Base58Check.base58ToBytes(account.getBase58CheckAddress())); 167 | 168 | // 指定权限列表,trident已经封装枚举类 169 | /*Integer[] contractId = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 31, 170 | 32, 33, 41, 42, 43, 44, 45};*/ 171 | Integer[] contractId = {1, Chain.Transaction.Contract.ContractType.TriggerSmartContract_VALUE}; 172 | 173 | List list = new ArrayList<>(Arrays.asList(contractId)); 174 | byte[] operations = new byte[32]; 175 | list.forEach(e -> operations[e / 8] |= (1 << e % 8)); 176 | 177 | // 构造权限分配信息 178 | Contract.AccountPermissionUpdateContract accountPermissionUpdateContract = 179 | Contract.AccountPermissionUpdateContract.newBuilder() 180 | .setOwnerAddress(ownerAddress) 181 | .setOwner( 182 | org.tron.trident.proto.Common.Permission.newBuilder() 183 | .setType(Common.Permission.PermissionType.Owner) 184 | .setPermissionName("owner-test") 185 | .setThreshold(1) 186 | /*.addKeys( 187 | Common.Key.newBuilder() 188 | .setAddress(ByteString.copyFrom(Base58Check.base58ToBytes("TBjxJTNwZeaKrbHyDum5Rwj1xU99999n8Z"))) 189 | .setWeight(5) 190 | )*/ 191 | .addKeys(Common.Key.newBuilder().setAddress(ownerAddress).setWeight(1)) 192 | ) 193 | .addActives( 194 | org.tron.trident.proto.Common.Permission.newBuilder() 195 | .setType(Common.Permission.PermissionType.Active) 196 | .setPermissionName("test") 197 | // 需要权重满足才执行操作 198 | .setThreshold(10) 199 | // 添加权限、指定权重 200 | .addKeys(Common.Key.newBuilder().setAddress(ByteString.copyFrom(Base58Check.base58ToBytes(permissionAddress1))).setWeight(5)) 201 | // 添加权限、指定权重 202 | .addKeys(Common.Key.newBuilder().setAddress(ByteString.copyFrom(Base58Check.base58ToBytes(permissionAddress2))).setWeight(5)) 203 | .setOperations(ByteString.copyFrom(operations)) 204 | ).addActives( 205 | org.tron.trident.proto.Common.Permission.newBuilder() 206 | .setType(Common.Permission.PermissionType.Active) 207 | .setPermissionName("active-demo") 208 | // 需要权重满足才执行操作 209 | .setThreshold(10) 210 | // 添加权限、指定权重 211 | .addKeys(Common.Key.newBuilder().setAddress(ByteString.copyFrom(Base58Check.base58ToBytes(permissionAddress1))).setWeight(10)) 212 | .setOperations(ByteString.copyFrom(operations)) 213 | ) 214 | .build(); 215 | 216 | // 发给节点,构造本次交易 217 | Response.TransactionExtention transactionExtention = wrapper.accountPermissionUpdate(accountPermissionUpdateContract); 218 | // 签名 219 | Chain.Transaction signTransaction = wrapper.signTransaction(transactionExtention); 220 | // 广播 221 | String id = wrapper.broadcastTransaction(signTransaction); 222 | log.debug(id); 223 | } 224 | 225 | /** 226 | * 本地多签转账 227 | * 228 | *

229 | * 296fc6ae7c8a61c0005b64d38b51c99623fb7475277ab2bbc0439b07f7a86afe 230 | */ 231 | @Test 232 | public void multiSignature() throws IllegalException { 233 | // A 主账号 234 | AccountInfo account = new AccountInfo(""); 235 | 236 | // B 具有某些活动权限 237 | AccountInfo account1 = new AccountInfo(""); 238 | 239 | // B 具有某些活动权限 240 | AccountInfo account2 = new AccountInfo(""); 241 | 242 | // 到账地址 243 | String fromAddress = account.getBase58CheckAddress(); 244 | // 实际转账金额 245 | BigDecimal realAmount = BigDecimal.valueOf(1); 246 | // 合约 247 | Trc20ContractInfo trc20ContractInfo = Trc20ContractUtil.readTrc20ContractInfo(contractAddress, wrapper); 248 | // 系统转账金额 249 | BigDecimal transferAmount = trc20ContractInfo.getTransferAmount(realAmount); 250 | // TRC20转账 251 | Transfer transfer = 252 | Transfer.trc20TransferBuilder(fromAddress, to, transferAmount, trc20ContractInfo.getAddress()) 253 | // 矿工费限制 254 | .feeLimit(Convert.toSun(BigDecimal.valueOf(20), Convert.Unit.TRX).longValue()) 255 | // 备注 256 | .memo("备注:" + new Date()) 257 | // 设置权限ID 258 | .permissionId(2) 259 | .build(); 260 | 261 | // 参考区块 262 | Chain.Block nowBlock = wrapper.getNowBlock(); 263 | ReferenceBlock referenceBlock = new ReferenceBlock(nowBlock.getBlockHeader()); 264 | // trc20 转账处理器 265 | Trc20TransferHandler trc20TransferHandler = new Trc20TransferHandler(); 266 | // 构造本地交易 267 | Chain.Transaction transaction = trc20TransferHandler.buildLocalTransfer(transfer, referenceBlock); 268 | 269 | // 账号1签名 270 | Chain.Transaction signTransaction = wrapper.signTransaction(transaction, account1.getKeyPair()); 271 | // 账号2签名 272 | signTransaction = wrapper.signTransaction(signTransaction, account2.getKeyPair()); 273 | // 广播并返回ID 274 | String tid = wrapper.broadcastTransaction(signTransaction); 275 | log.debug(tid); 276 | } 277 | 278 | 279 | /** 280 | * 多签质押演示 - v1 281 | */ 282 | @Test 283 | public void freezeBalance() throws IllegalException { 284 | // TKjPqKq77777FPKUdLRMPNUWtU4jNEpUQF 主账号(此处仅用到地址) 285 | AccountInfo account = new AccountInfo(""); 286 | // TBjxJTNwZeaKrbHyDum5Rwj1xU99999n8Z 具有某些活动权限 287 | AccountInfo account1 = new AccountInfo(""); 288 | // TEczEK6uzD88888QhstH6QDwB167ZsXPrb 具有某些活动权限 289 | AccountInfo account2 = new AccountInfo(""); 290 | 291 | BigDecimal amount = BigDecimal.valueOf(100); 292 | BigDecimal transferAmount = Convert.toSun(amount, Convert.Unit.TRX); 293 | 294 | // 请求远程构造交易 295 | Response.TransactionExtention transactionExtention = wrapper.freezeBalance( 296 | account.getBase58CheckAddress(), 297 | transferAmount.longValue(), 298 | 3, 299 | Common.ResourceCode.ENERGY_VALUE, 300 | "TBB3jfSew1ygkwhFf4Fjqq3LLSys77777P" 301 | ); 302 | // 获取交易构造器 303 | Chain.Transaction.Builder builder = transactionExtention.getTransaction().toBuilder(); 304 | // 设置权限ID 305 | builder.getRawDataBuilder().getContractBuilder(0).setPermissionId(2); 306 | // 构造交易 307 | Chain.Transaction transaction = builder.build(); 308 | 309 | // B 签名 310 | Chain.Transaction signTransaction = wrapper.signTransaction(transaction, account1.getKeyPair()); 311 | // C 签名 312 | signTransaction = wrapper.signTransaction(signTransaction, account2.getKeyPair()); 313 | // 广播 314 | String tid = wrapper.broadcastTransaction(signTransaction); 315 | log.debug(tid); 316 | } 317 | 318 | } 319 | -------------------------------------------------------------------------------- /src/test/java/org/tron/easywork/SomeTest.java: -------------------------------------------------------------------------------- 1 | package org.tron.easywork; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.ByteString; 5 | import com.google.protobuf.InvalidProtocolBufferException; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.bouncycastle.util.encoders.Hex; 8 | import org.junit.Test; 9 | import org.tron.easywork.exception.FunctionSelectorException; 10 | import org.tron.easywork.exception.SmartParamDecodeException; 11 | import org.tron.easywork.model.AccountInfo; 12 | import org.tron.easywork.model.Transfer; 13 | import org.tron.easywork.model.TransferFunctionParam; 14 | import org.tron.easywork.util.AccountUtils; 15 | import org.tron.easywork.util.TransferUtil; 16 | import org.tron.easywork.util.Trc20ContractUtil; 17 | import org.tron.easywork.util.TronConverter; 18 | import org.tron.trident.core.ApiWrapper; 19 | import org.tron.trident.core.contract.Contract; 20 | import org.tron.trident.core.contract.Trc20Contract; 21 | import org.tron.trident.core.exceptions.IllegalException; 22 | import org.tron.trident.core.key.KeyPair; 23 | import org.tron.trident.core.transaction.SignatureValidator; 24 | import org.tron.trident.crypto.Hash; 25 | import org.tron.trident.proto.Chain; 26 | import org.tron.trident.proto.Response; 27 | import org.tron.trident.utils.Base58Check; 28 | import org.tron.trident.utils.Convert; 29 | 30 | import java.math.BigDecimal; 31 | import java.nio.charset.StandardCharsets; 32 | 33 | /** 34 | * @author Admin 35 | * @version 1.0 36 | * @time 2022-04-13 21:34 37 | */ 38 | @Slf4j 39 | public class SomeTest extends BaseTest { 40 | 41 | @Test 42 | public void hexAddressToBase58() { 43 | String hexStr = "41b6cb08800483ad904d43df53cec7a8d866ed1d95"; 44 | byte[] decode = Hex.decode(hexStr); 45 | String base58 = Base58Check.bytesToBase58(decode); 46 | log.debug(base58); 47 | } 48 | 49 | /** 50 | * 地址格式转换 51 | */ 52 | @Test 53 | public void toHex() { 54 | String addr = "TYUFU6WtuwyMEGZg9c241z6NymnVTzg3WU"; 55 | byte[] bs = Base58Check.base58ToBytes(addr); 56 | String hex = Hex.toHexString(bs); 57 | log.debug(hex); 58 | } 59 | 60 | /** 61 | * 是否为Tron地址 62 | */ 63 | @Test 64 | public void isTronAddress() { 65 | String address = "TP6QorvxAJ4bXg21LterCpGi5oZ2PxybCZ"; 66 | 67 | try { 68 | // 推荐 69 | ApiWrapper.parseAddress(address); 70 | } catch (Exception e) { 71 | log.error("地址错误"); 72 | } 73 | 74 | // 非严谨 75 | boolean isAddress = AccountUtils.isTronAddress(address); 76 | log.debug("是否为正确的Tron地址:{}", isAddress); 77 | } 78 | 79 | /** 80 | * 创建新账户 81 | */ 82 | @Test 83 | public void createAccount() { 84 | AccountInfo newAccount = new AccountInfo(KeyPair.generate()); 85 | log.debug(newAccount.toString()); 86 | } 87 | 88 | /** 89 | * 私钥对应的账户详情 90 | */ 91 | @Test 92 | public void importAccount() { 93 | AccountInfo accountInfo = new AccountInfo(key); 94 | log.debug(accountInfo.toString()); 95 | } 96 | 97 | /** 98 | * 从区块ID中获取区块高度(未经大量测试) 99 | */ 100 | @Test 101 | public void getBlockHeightFromBlockId() { 102 | String blockId = "00000000032f44b1684f8a72488a3e74507b3efb918cf4ac9aeeadf0917b62d2"; 103 | String start = blockId.substring(0, blockId.length() - 48); 104 | long l = Long.parseLong(start, 16); 105 | log.debug(String.valueOf(l)); 106 | } 107 | 108 | /** 109 | * 解析trc10 资源名称 110 | */ 111 | @Test 112 | public void parseAsset_name() { 113 | String assetName = "31303030393835"; 114 | Integer n = TronConverter.hexToInt(assetName); 115 | log.debug(n.toString()); 116 | } 117 | 118 | 119 | // 原始授权 120 | // @Test 121 | public void approveTrc20() { 122 | Contract contract = wrapper.getContract(contractAddress); 123 | Trc20Contract trc20Contract = new Trc20Contract(contract, from, wrapper); 124 | // rc20Contract.approve() 125 | } 126 | 127 | /** 128 | * 交易签名过程演示 129 | */ 130 | @Test 131 | public void signApiTransaction() throws InvalidProtocolBufferException { 132 | // 手动从API构造的交易 https://api.shasta.trongrid.io/wallet/createtransaction 133 | // 这串hex格式字符串中包含了所有交易信息 134 | String raw_data_hex = "0a023e9d2208290ec6ce431394c44085bcb0fab4325225e5a487e6b3a8efbc9a546875204e6f762032312031353a31363a34382043535420323032345ab001081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a15416b147a03de98c2d2079753875344179dbcd49db81215413dff1aae5b4979a0067b001afdb34a9758ecaa832244a9059cbb0000000000000000000000004d8239a7557fc50472586547b555ecf529f0665400000000000000000000000000000000000000000000000000000000000f424028027085d4d2ecb432900180dac409"; 135 | // 解码 136 | byte[] raw_data = Hex.decode(raw_data_hex); 137 | // 交易id(即交易哈希,通过Transaction.rawData计算SHA256得到) 138 | byte[] txId = Hash.sha256(raw_data); 139 | String txIdHex = Hex.toHexString(txId); 140 | log.debug("txId:{}", txIdHex); 141 | 142 | // 构造raw对象 143 | Chain.Transaction.raw raw = Chain.Transaction.raw.parseFrom(raw_data); 144 | 145 | // ---↓ 如果改变原始交易数据,比如添加备注,此操作需要重新计算交易ID,否则会“签了个寂寞” 146 | raw = raw.toBuilder() 147 | // 添加备注 148 | .setData(ByteString.copyFromUtf8("hello")) 149 | .build(); 150 | // 重新计算交易id 151 | txId = Hash.sha256(raw.toByteArray()); 152 | txIdHex = Hex.toHexString(txId); 153 | log.debug("new txId:{}", txIdHex); 154 | // ---↑ 以上只是提醒你修改原始数据,交易ID也会改变,而签名签的就是交易ID 155 | 156 | // 对交易ID进行签名 157 | byte[] sign = KeyPair.signTransaction(txId, new KeyPair("your private key")); 158 | // 构造交易对象(设置 raw数据、签名数据 ) 159 | Chain.Transaction signedTransaction = 160 | Chain.Transaction.newBuilder() 161 | .setRawData(raw) 162 | // 添加签名 163 | .addSignature(ByteString.copyFrom(sign)) 164 | .build(); 165 | 166 | // 你也可以这样签名(在删掉 addSignature 的情况下,除非是多签) 167 | // 项目中一般都多用这种方式 168 | // Chain.Transaction signedTransaction = wrapper.signTransaction(unsignedTransaction, new KeyPair("your private key")); 169 | 170 | // 广播并返回 交易ID 171 | String id = wrapper.broadcastTransaction(signedTransaction); 172 | log.debug(id); 173 | } 174 | 175 | 176 | /** 177 | * 对前端发来的多签交易验证并签名 178 | * 前端创建交易,后端验证并签名,将签名发给前端组装广播 179 | */ 180 | @Test 181 | public void signRawDataHex() throws InvalidProtocolBufferException { 182 | // 将交易参数发给前端 183 | // …… 184 | 185 | // 前端根据参数构造交易,并将 raw_data_hex 发到此处 186 | String raw_data_hex = "0a02be69220876fa7aba2bcc89f54090ebfc9bb5325ab001081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a15416b147a03de98c2d2079753875344179dbcd49db81215413dff1aae5b4979a0067b001afdb34a9758ecaa832244a9059cbb0000000000000000000000004d8239a7557fc50472586547b555ecf529f0665400000000000000000000000000000000000000000000000000000000000f4240280270eaa1f99bb532900180ade204"; 187 | // hex解码 188 | byte[] raw_data = Hex.decode(raw_data_hex); 189 | 190 | // 构造raw对象 191 | Chain.Transaction.raw raw = Chain.Transaction.raw.parseFrom(raw_data); 192 | 193 | // 开始校验前端是否按照指定交易信息构造交易 194 | // 合约信息 195 | Chain.Transaction.Contract contract = raw.getContract(0); 196 | // 检查权限id 197 | boolean permissionIdCheck = contract.getPermissionId() == 2; 198 | // 检查交易类型 199 | Chain.Transaction.Contract.ContractType contractType = contract.getType(); 200 | boolean typeCheck = contract.getType() == Chain.Transaction.Contract.ContractType.TransferContract; 201 | 202 | // 解码并检查合约中的详细参数(纯演示,无具体逻辑。正常是比较复杂的,应采用策略模式或提取到不同的方法中去处理) 203 | Any parameter = contract.getParameter(); 204 | if (contractType == Chain.Transaction.Contract.ContractType.TriggerSmartContract) { 205 | try { 206 | log.debug("这是智能合约交易"); 207 | // 解码 208 | org.tron.trident.proto.Contract.TriggerSmartContract triggerSmartContract = 209 | parameter.unpack(org.tron.trident.proto.Contract.TriggerSmartContract.class); 210 | // 获取交易详情 211 | Transfer transfer = TransferUtil.getTransferInfo(triggerSmartContract); 212 | log.debug("类型:{}\t到账地址:{}\t金额:{}", transfer.getTransferType(), transfer.getTo(), transfer.getAmount()); 213 | // 检查...... 214 | } catch (InvalidProtocolBufferException e) { 215 | log.debug("unpack解包异常"); 216 | e.printStackTrace(); 217 | } catch (SmartParamDecodeException e) { 218 | log.debug("智能合约 转账参数 数据解析异常"); 219 | e.printStackTrace(); 220 | } catch (FunctionSelectorException e) { 221 | // 函数选择器错误 222 | } catch (Exception e) { 223 | log.error("兜底异常:{}", e.getMessage()); 224 | e.printStackTrace(); 225 | } 226 | } 227 | // 如果是trx 228 | else if (contractType == Chain.Transaction.Contract.ContractType.TransferContract) { 229 | try { 230 | log.debug("这是TRX交易"); 231 | org.tron.trident.proto.Contract.TransferContract unpack = parameter.unpack(org.tron.trident.proto.Contract.TransferContract.class); 232 | Transfer transferInfo = TransferUtil.getTransferInfo(unpack); 233 | // 检查...... 234 | } catch (InvalidProtocolBufferException e) { 235 | throw new RuntimeException(e); 236 | } 237 | } 238 | 239 | // 假设检查无误,继续后续步骤 240 | 241 | // 交易id(即交易哈希,通过Transaction.rawData计算SHA256得到) 242 | byte[] txId = Hash.sha256(raw_data); 243 | String txIdHex = Hex.toHexString(txId); 244 | log.debug("txIdHex:{}", txIdHex); 245 | 246 | // 对 txid 进行签名 247 | byte[] sign = KeyPair.signTransaction(txId, new KeyPair("your private key")); 248 | 249 | String signHex = Hex.toHexString(sign); 250 | log.debug("================================"); 251 | log.debug("签名发给前端:"); 252 | log.debug(signHex); 253 | log.debug("================================"); 254 | 255 | // 前端将签名组装后广播,得到链上答复后通知此处后端 256 | // 前端对我说说:“交易已经正确广播” 257 | // 开始监听交易id,如真实在链上发现此交易id,则代表订单已经完成 258 | } 259 | 260 | 261 | /** 262 | * 计算交易带宽 263 | */ 264 | @Test 265 | public void estimateBandwidth() throws IllegalException { 266 | // 转账金额 267 | BigDecimal amount = Convert.toSun("1", Convert.Unit.TRX); 268 | // 构造交易 269 | Response.TransactionExtention transfer = 270 | wrapper.transfer(from, to, amount.longValue()); 271 | // 签名交易 272 | Chain.Transaction transaction = wrapper.signTransaction(transfer); 273 | 274 | // 估计带宽 275 | long bandwidth = transaction.toBuilder().clearRet().build().getSerializedSize() + 64; 276 | log.info("带宽估计:{}", bandwidth); 277 | // 广播交易并返回ID 278 | String tid = wrapper.broadcastTransaction(transaction); 279 | log.info(tid); 280 | } 281 | 282 | /** 283 | * 字符串签名验证 284 | */ 285 | @Test 286 | public void verify_text() { 287 | String str = "hello world"; 288 | 289 | String hexString = Hex.toHexString(str.getBytes()); 290 | 291 | log.debug(hexString); 292 | 293 | // 交易id(即交易哈希,通过Transaction.rawData计算SHA256得到) 294 | byte[] tid = Hash.sha256(Hex.decode(hexString)); 295 | 296 | // 交易ID Hex 297 | log.debug("tid-hex:{}", Hex.toHexString(tid)); 298 | 299 | /*byte[] digest = new Keccak.Digest256().digest(Hex.decode(hexString)); 300 | log.debug("digest:{}", Hex.toHexString(digest));*/ 301 | 302 | // 对 tid 进行签名 303 | byte[] sign = KeyPair.signTransaction(tid, wrapper.keyPair); 304 | log.debug("sign-hex:{}", Hex.toHexString(sign)); 305 | 306 | // 所有者 307 | byte[] owner = Base58Check.base58ToBytes(from); 308 | 309 | // 签名 310 | boolean verify = SignatureValidator.verify(tid, sign, owner); 311 | log.info("签名结果:{}", verify); 312 | } 313 | 314 | /** 315 | * 验证交易签名(前端tronweb给的签名无法使用该方法验证,研究无果) 316 | */ 317 | @Test 318 | public void verifyTransaction() throws IllegalException { 319 | // trx 个数 320 | BigDecimal realAmount = BigDecimal.valueOf(1); 321 | // sun 个数 322 | BigDecimal sun = Convert.toSun(realAmount, Convert.Unit.TRX); 323 | 324 | // 远程构造交易 325 | Response.TransactionExtention transfer = 326 | wrapper.transfer(from, to, sun.longValue()); 327 | 328 | // 签名 329 | Chain.Transaction signTransaction = wrapper.signTransaction(transfer); 330 | 331 | boolean verify = SignatureValidator.verify( 332 | ApiWrapper.calculateTransactionHash(transfer.getTransaction()), 333 | signTransaction.getSignature(0).toByteArray(), 334 | ApiWrapper.parseAddress(wrapper.keyPair.toBase58CheckAddress()).toByteArray() 335 | ); 336 | 337 | if (verify) { 338 | // 广播 339 | String tid = wrapper.broadcastTransaction(signTransaction); 340 | log.debug(tid); 341 | return; 342 | } 343 | log.error("签名错误!"); 344 | } 345 | 346 | /** 347 | * 解码TRC20合约转账数据 348 | * 到账地址 + 转账金额 349 | */ 350 | @Test 351 | public void parseData() throws SmartParamDecodeException, FunctionSelectorException { 352 | String data = "a9059cbb0000000000000000000000001391667f4940d9f58d2779d92357e714f5bf3ea900000000000000000000000000000000000000000000000000000000005ffab4"; 353 | TransferFunctionParam transferFunctionParam = Trc20ContractUtil.getTransferFunctionParam(data); 354 | log.debug(transferFunctionParam.toString()); 355 | } 356 | 357 | /** 358 | * 验证trc20交易是否成功 359 | * 此处演示的txId均来自shasta测试网 360 | */ 361 | @Test 362 | public void isSuccess() throws IllegalException { 363 | String tid = "218414bb71d49037de6d49009fb6e4f49834aea8bda11037dc347130b6c88dbf"; 364 | Chain.Transaction transaction = wrapper.getTransactionById(tid); 365 | boolean status = transaction.getRet(0).getContractRet().getNumber() == 1; 366 | log.debug("{},{}", status ? "成功" : "失败", tid); 367 | 368 | tid = "17ac1d482f373752a094adf5632c61e55c807f91aa79cd794106c7d811dae8e8"; 369 | transaction = wrapper.getTransactionById(tid); 370 | status = transaction.getRet(0).getContractRet().getNumber() == 1; 371 | log.debug("{},{}", status ? "成功" : "失败", tid); 372 | } 373 | 374 | 375 | /** 376 | * trx 与 sun 转换 377 | */ 378 | @Test 379 | public void convert() { 380 | // 1 trx 381 | BigDecimal trx = BigDecimal.ONE; 382 | BigDecimal sunBalance = Convert.toSun(trx, Convert.Unit.TRX); 383 | log.debug("1trx={}sun", sunBalance); 384 | 385 | // 1000000 sun 386 | BigDecimal sun = BigDecimal.valueOf(1000000); 387 | BigDecimal trxBalance = Convert.fromSun(sun, Convert.Unit.TRX); 388 | log.debug("1000000sun={}trx", trxBalance); 389 | } 390 | 391 | 392 | @Test 393 | public void usdtName() { 394 | String res = "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045553445400000000000000000000000000000000000000000000000000000000"; 395 | String name_res = res.substring(res.length() - 32).replaceAll("0", ""); 396 | log.debug("name_res:{}", name_res); 397 | 398 | 399 | log.debug("name:{}", this.toStringHex("55534454")); 400 | 401 | // log.debug("{}", TronConverter.hexToInt("430e1b7")); 402 | 403 | } 404 | 405 | // 转化十六进制编码为字符串 406 | public String toStringHex(String s) { 407 | byte[] baKeyword = new byte[s.length() / 2]; 408 | for (int i = 0; i < baKeyword.length; i++) { 409 | try { 410 | baKeyword[i] = (byte) (0xff & Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16)); 411 | } catch (Exception e) { 412 | e.printStackTrace(); 413 | } 414 | } 415 | try { 416 | s = new String(baKeyword, StandardCharsets.UTF_8);// UTF-16le:Not 417 | } catch (Exception e1) { 418 | e1.printStackTrace(); 419 | } 420 | return s; 421 | } 422 | 423 | } 424 | --------------------------------------------------------------------------------