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 |
--------------------------------------------------------------------------------