microblog | 微博客
原创
访问
0
获赞
0
评论
相关推荐
暂无数据
最新文章
暂无数据
热门文章
暂无数据

Tron波场开发

写完bug就找女朋友 01月16日 17:47:29 0 2 0
分类专栏: 区块链 Java项目 SpringCloud 文章标签: Tron开发 区块链 USDT trx

一、前言

     近段时间公司接到了以为HK客户的一个项目,该项目主要涉及trx、usdt的相互转账,由于tron官方API都是英文且查阅起来不是很方便,加之相对于日常开发任务,tron涉及到的技术算是比较偏,因此下文记录一些Tron常用操作,方便自己积累、同行阅读。

二、具体实现

2.1 yaml配置

tron: transactionFee: 0.01 tronDomainOnline: false address: TC32xxx(系统账号) privateKey: ENC(1qjg178(钱包私钥,加密配置)) apiKey: 72fe3xxx(官网申请的apiKey) #usdt智能合约地址 usdtContract: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t # 查询钱包余额 trxTransferUrl: https://apilist.tronscanapi.com/api/transfer/trx ustdTransferUrl: https://api.trongrid.io/v1/accounts/{address}/transactions/trc2 # 转账详情查询地址 infoUrl: https://apilist.tronscanapi.com/api/transaction-info?hash= # 账户余额查询地址 balanceUrl: https://apilist.tronscanapi.com/api/account/tokens?address= # 冻结余额 freezeBalanceUrl: https://api.trongrid.io/wallet/freezebalancev2

对应的配置实体:

import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.tron.trident.core.ApiWrapper; import java.math.BigDecimal; /** * FileName: Tron * Author: wxz * Date: 2024/12/6 11:26 * Description: tron配置 */ @Component @Getter @Setter @ConfigurationProperties(prefix = "tron") public class TronConfig { private String address; private String trxTransferUrl; private String usdtTransferUrl; private String balanceUrl; private String infoUrl; private String apiKey; private String privateKey; private String usdtContract; private String freezeBalanceUrl; private boolean tronDomainOnline; private BigDecimal transactionFee; @Bean public ApiWrapper apiWrapper() { if (this.isTronDomainOnline()) { return ApiWrapper.ofMainnet(this.getPrivateKey(), this.getApiKey()); } return new ApiWrapper("grpc.trongrid.io:50051", "grpc.trongrid.io:50052", this.getPrivateKey()); } public ApiWrapper apiWrapper(String privateKey) { if (this.isTronDomainOnline()) { return ApiWrapper.ofMainnet(privateKey, this.getApiKey()); } return new ApiWrapper("grpc.trongrid.io:50051", "grpc.trongrid.io:50052", privateKey); } }

2.2 Tron操作工具类

import cn.hutool.core.util.RandomUtil; import com.tron.config.TronConfig; import com.tron.dto.AccountBalance; import com.tron.dto.AccountBalance.Balance; import com.tron.dto.FreezeDto; import com.tron.dto.TransferDto; import com.tron.entity.AjaxResult; import com.tron.excetion.UtilException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.context.annotation.Configuration; import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.Base58; import org.tron.keystore.StringUtils; import org.tron.keystore.WalletFile; import org.tron.trident.core.ApiWrapper; import org.tron.trident.core.contract.Contract; import org.tron.trident.core.contract.Trc20Contract; import org.tron.trident.core.exceptions.IllegalException; import org.tron.trident.core.key.KeyPair; import org.tron.trident.proto.Chain; import org.tron.trident.proto.Chain.Transaction; import org.tron.trident.proto.Response.Account; import org.tron.trident.proto.Response.AccountResourceMessage; import org.tron.trident.proto.Response.TransactionExtention; import org.tron.trident.utils.Convert; import org.tron.trident.utils.Numeric; import org.tron.walletserver.WalletApi; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @Configuration @Log4j2 @RequiredArgsConstructor public class TronUtil { private final TronConfig config; /** * 激活账户: √ * * @param hexAddress * @return */ public AjaxResult activate(String hexAddress) { TransactionExtention transaction = null; ApiWrapper wrapper = config.apiWrapper(); try { transaction = wrapper.createAccount(wrapper.keyPair.toBase58CheckAddress(), hexAddress); Transaction signedTxn = wrapper.signTransaction(transaction); log.info("账号激活结果:{}", signedTxn.toString()); String s = wrapper.broadcastTransaction(signedTxn); wrapper.close(); return AjaxResult.success(s); } catch (IllegalException e) { e.printStackTrace(); log.error("账号激活失败,原因: {}", e.getMessage()); return AjaxResult.error(e.getMessage()); } finally { wrapper.close(); } } /** * 创建账户 √ * * @return */ public static KeyPair generate() { KeyPair keyPair = ApiWrapper.generateAddress(); log.info("生成账号信息: {}", keyPair.toString()); return keyPair; } /** * 查询账户 √ * * @param hexAddress */ public Account getAccount(String hexAddress) { ApiWrapper wrapper = config.apiWrapper(); Account account = wrapper.getAccount(hexAddress); wrapper.close(); return account; } /** * 转账; 如果付款账号没有交易所需带宽/能量,会冻结以获取; 如果转账金额就是账户可用余额,会在转账金额里面扣除冻结金额, */ public void transferTrx(TransferDto dto) { ApiWrapper wrapper = config.apiWrapper(dto.getPrivateKey()); try { long finalTransferAmount = getFinalTransferAmount(wrapper, dto.getFromAddress(), dto.getAmount()); TransactionExtention transaction = wrapper.transfer(dto.getFromAddress(), dto.getToAddress(), finalTransferAmount); Transaction signedTxn = wrapper.signTransaction(transaction, wrapper.keyPair); String ret = wrapper.broadcastTransaction(signedTxn); log.info("正在进行转账交易,from :{} ,to :{} ,balance:{} , 广播结果:{}", dto.getFromAddress(), dto.getToAddress(), dto.getAmount(), ret); } catch (Exception e) { e.printStackTrace(); log.error("转账发生错误,原因:{}", e.getMessage()); } finally { wrapper.close(); } } /** * 获取本次实际能交易的金额 * * @param fromAddress 付款账户 * @param amount 交易金额 * @return 实际能交易的金额 */ private long getFinalTransferAmount(ApiWrapper wrapper, String fromAddress, long amount) { AccountResourceMessage accountResource = wrapper.getAccountResource(fromAddress); long netLimit = accountResource.getNetLimit(); long netUsed = accountResource.getNetUsed(); long energyLimit = accountResource.getEnergyLimit(); long energyUsed = accountResource.getEnergyUsed(); log.info("账号带宽:{}", (netLimit - netUsed)); log.info("账号能量:{}", (energyLimit - energyUsed)); if ((netUsed < netLimit) && (energyUsed < energyLimit)) { return amount; } Account account = this.getAccount(fromAddress); if (amount > account.getBalance()) { throw new UtilException("本次交易金额( " + amount + " )大于账户可用余额 " + account.getBalance() + ",交易失败."); } //本次交易手续费 long freezeAmount = calculateDynamicFee(wrapper, fromAddress); log.error("账户能量不足以支持本次转账,尝试冻结部分余额....."); FreezeDto dto = new FreezeDto(); dto.setAddress(fromAddress).setBalance(freezeAmount); long freeze = 0; if ((netUsed - netLimit) <= 0) { dto.setResourceCode(0); if (!delegateResource(dto)) { //资源委派失败,冻结付款账号余额以获取交易资格 freeze += freezeAmount; freezeTrxForResources(wrapper, dto); } } if ((energyUsed - energyLimit) <= 0) { dto.setResourceCode(1); if (!delegateResource(dto)) { freeze += freezeAmount; freezeTrxForResources(wrapper, dto); } } long totalConsume = freeze + amount; //账户余额小于本次交易总费用时,返回实际可交易金额 if (totalConsume > account.getBalance()) { return account.getBalance() - freeze; } return amount; } /** * 资源委派 */ private boolean delegateResource(FreezeDto dto) { try { WalletFile walletFile = WalletApi.CreateWalletFile(Numeric.hexStringToByteArray(RandomUtil.randomString(18)), Numeric.hexStringToByteArray(config.getPrivateKey())); WalletApi api = new WalletApi(walletFile); return api.delegateResource(api.getAddress(), dto.getBalance(), dto.getResourceCode(), decode58Check(dto.getAddress()), false, 0); } catch (Exception e) { log.error("资源委派失败,原因:{}", e.getMessage()); return false; } } /** * 动态调整矿工费率 */ private long calculateDynamicFee(ApiWrapper wrapper, String fromAddress) { // 获取账户资源信息 AccountResourceMessage accountResource = wrapper.getAccountResource(fromAddress); long netLimit = accountResource.getNetLimit(); long netUsed = accountResource.getNetUsed(); long energyLimit = accountResource.getEnergyLimit(); long energyUsed = accountResource.getEnergyUsed(); // 根据资源使用情况动态调整费用 // 网络较拥堵时增加费用 if ((netUsed >= netLimit * 0.8) || (energyUsed >= energyLimit * 0.8)) { return Convert.toSun("2", Convert.Unit.TRX).longValue(); } return Convert.toSun("1", Convert.Unit.TRX).longValue(); } /** * 冻结trx以获取带宽和能量 * * @return */ public void freezeTrxForResources(ApiWrapper wrapper, FreezeDto dto) { String pass = RandomUtil.randomString(18); byte[] passwd = StringUtils.char2Byte(pass.toCharArray()); try { WalletFile walletFile = WalletApi.CreateWalletFile(passwd, Numeric.hexStringToByteArray(wrapper.keyPair.toPrivateKey())); WalletApi api = new WalletApi(walletFile); FreezeUtil freezeUtil = new FreezeUtil(); boolean b = freezeUtil.freezeBalanceV2(api.getAddress(), dto.getBalance(), dto.getResourceCode(), walletFile, pass); if (!b) { throw new com.tron.util.UtilException("冻结资金失败,无法操作"); } } catch (Exception e) { log.error("冻结资金出现问题,原因: {}, 金额:{}", e.getMessage(), dto.getBalance()); throw new com.tron.util.UtilException("冻结资金失败,无法操作"); } } public static byte[] decode58Check(String input) { byte[] decodeCheck = Base58.decode(input); if (decodeCheck.length <= 4) { return null; } byte[] decodeData = new byte[decodeCheck.length - 4]; System.arraycopy(decodeCheck, 0, decodeData, 0, decodeData.length); byte[] hash0 = Sha256Sm3Hash.hash(decodeData); byte[] hash1 = Sha256Sm3Hash.hash(hash0); return hash1[0] == decodeCheck[decodeData.length] && hash1[1] == decodeCheck[decodeData.length + 1] && hash1[2] == decodeCheck[decodeData.length + 2] && hash1[3] == decodeCheck[decodeData.length + 3] ? decodeData : null; } /** * 转USDT * * @param dto */ public void transferUSDT(TransferDto dto) { ApiWrapper wrapper = config.apiWrapper(dto.getPrivateKey()); Contract contract = this.getUsdtContract(); Trc20Contract token = new Trc20Contract(contract, wrapper.keyPair.toBase58CheckAddress(), wrapper); log.error(token); long finalTransferAmount = getFinalTransferAmount(wrapper, dto.getFromAddress(), dto.getAmount()); String transferStr = token.transfer(dto.getToAddress(), finalTransferAmount, 1, "memo", 1); log.info(transferStr); wrapper.close(); } /** * 获取ustd转账合约 */ public Contract getUsdtContract() { ApiWrapper wrapper = config.apiWrapper(); Contract contract = wrapper.getContract(config.getUsdtContract()); wrapper.close(); return contract; } /** * 查询交易状态 * * @param txid */ public String getTransactionStatusById(String txid) throws IllegalException { ApiWrapper client = config.apiWrapper(); Chain.Transaction getTransaction = client.getTransactionById(txid); client.close(); return getTransaction.getRet(0).getContractRet().name(); } /** * 获取USTD余额 * * @return 账户余额 */ public AccountBalance getBalance(String address) { AccountBalance accountBalance = new AccountBalance(address); List<Balance> list = new ArrayList<>(); Account account = this.getAccount(address); list.add(new Balance("trx", account.getBalance())); ApiWrapper wrapper = config.apiWrapper(); Trc20Contract token = new Trc20Contract(this.getUsdtContract(), address, wrapper); wrapper.close(); BigInteger bigInteger = token.balanceOf(address); list.add(new Balance("usdt", bigInteger.longValue())); accountBalance.setBalance(list); return accountBalance; } }

2.3 Tron操作工具类

import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.log4j.Log4j2; import org.tron.api.GrpcAPI.Return; import org.tron.api.GrpcAPI.TransactionExtention; import org.tron.api.GrpcAPI.TransactionSignWeight; import org.tron.api.GrpcAPI.TransactionSignWeight.Result.response_code; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.TransactionUtils; import org.tron.common.utils.Utils; import org.tron.core.exception.CancelException; import org.tron.core.exception.CipherException; import org.tron.keystore.StringUtils; import org.tron.keystore.Wallet; import org.tron.keystore.WalletFile; import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.BalanceContract.FreezeBalanceV2Contract; import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; import org.tron.walletserver.GrpcClient; import java.io.IOException; import java.util.Objects; import static com.tron.util.TronUtil.decode58Check; import static org.tron.walletserver.WalletApi.*; /** * FileName: FreezeUtil * Author: wxz * Date: 2024/12/18 17:28 * Description: */ @Log4j2 public class FreezeUtil { private static final GrpcClient rpcCli = init(); private static FreezeBalanceV2Contract createFreezeBalanceContractV2(byte[] address, long frozen_balance, int resourceCode) { org.tron.protos.contract.BalanceContract.FreezeBalanceV2Contract.Builder builder = FreezeBalanceV2Contract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setFrozenBalance(frozen_balance).setResourceValue(resourceCode); return builder.build(); } /** * 冻结 * * @param ownerAddress * @param frozen_balance * @param resourceCode * @param walletFile * @param password * @return * @throws IOException * @throws CancelException */ public boolean freezeBalanceV2(byte[] ownerAddress, long frozen_balance, int resourceCode, WalletFile walletFile, String password) throws IOException, CancelException { FreezeBalanceV2Contract contract = createFreezeBalanceContractV2(ownerAddress, frozen_balance, resourceCode); TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return this.processTransactionExtention(transactionExtention, walletFile, password.toCharArray()); } private boolean processTransactionExtention(TransactionExtention transactionExtention, WalletFile walletFile, char[] password) throws IOException, CancelException { if (transactionExtention == null) { return false; } Return ret = transactionExtention.getResult(); if (!ret.getResult()) { log.error("Code = " + ret.getCode()); log.error("Message = " + ret.getMessage().toStringUtf8()); return false; } Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContractCount() == 0) { log.error("Transaction is empty"); return false; } if (transaction.getRawData().getContract(0).getType() == ContractType.ShieldedTransferContract) { return false; } transaction = this.signTransaction(transaction, walletFile, password); this.showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); } private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { CreateSmartContract createSmartContract = (CreateSmartContract) transaction.getRawData() .getContract(0) .getParameter() .unpack(CreateSmartContract.class); } } private byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { byte[] txRawDataHash = Sha256Sm3Hash.of(trx.getRawData().toByteArray()).getBytes(); byte[] combined = new byte[txRawDataHash.length + ownerAddress.length]; System.arraycopy(txRawDataHash, 0, combined, 0, txRawDataHash.length); System.arraycopy(ownerAddress, 0, combined, txRawDataHash.length, ownerAddress.length); return Hash.sha3omit12(combined); } private Transaction signTransaction(Transaction transaction, WalletFile walletFile, char[] password) throws IOException, CancelException { if (transaction.getRawData().getTimestamp() == 0L) { transaction = TransactionUtils.setTimestamp(transaction); } transaction = TransactionUtils.setExpirationTime(transaction); try { byte[] passwd = StringUtils.char2Byte(password); transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); TransactionSignWeight weight = getTransactionSignWeight(transaction); if (weight.getResult().getCode() == response_code.ENOUGH_PERMISSION) { return transaction; } if (weight.getResult().getCode() != response_code.NOT_ENOUGH_PERMISSION) { throw new CancelException(weight.getResult().getMessage()); } System.out.println("Current signWeight is:"); System.out.println(Utils.printTransactionSignWeight(weight)); System.out.println("Please confirm if continue add signature enter y or Y, else any other"); } catch (Exception e) { } this.showTransactionAfterSign(transaction); throw new CancelException("User cancelled"); } private ECKey getEcKey(WalletFile walletFile, byte[] password) throws CipherException { return Wallet.decrypt(password, walletFile); } /** * 转账 * * @param ownerAddress * @param to * @param amount * @param walletFile * @param password * @return * @throws IOException * @throws CancelException */ public boolean sendCoin(byte[] ownerAddress, String to, long amount, WalletFile walletFile, String password) throws IOException, CancelException { TransferContract contract = createTransferContract(Objects.requireNonNull(decode58Check(to)), ownerAddress, amount); TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return this.processTransactionExtention(transactionExtention, walletFile, password.toCharArray()); } }

涉及到的DTO:

import lombok.Data; import lombok.experimental.Accessors; /** * FileName: TransferDto * Author: wxz * Date: 2024/12/13 17:28 * Description: */ @Data @Accessors(chain = true) public class TransferDto { private String fromAddress; private String toAddress; /** * 转账金额,单位为sun(1/1000000trx) */ private long amount; private String privateKey; }
@Data @Accessors(chain = true) public class FreezeDto { /** * 账户地址 */ @JsonProperty("owner_address") private String address; /** * 冻结余额 */ @JsonProperty("frozen_balance") private long balance; private final boolean visible = true; public void setResourceCode(int resourceCode) { if (resourceCode>1 || resourceCode<0){ throw new UtilException("换取的资源类型不正确"); } this.resourceCode = resourceCode; } /** * 冻结资源换取的资源类型: BANDWIDTH(0)、ENERGY(1) ; 默认为:BANDWIDTH */ private int resourceCode; }
import cn.hutool.core.util.StrUtil; import lombok.Data; import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; /** * FileName: AccountBalance * Author: wxz * Date: 2024/12/16 14:38 * Description: 账户余额 */ @Data public class AccountBalance { private String address; private List<Balance> balance = new ArrayList<>(); public AccountBalance(String address) { this.address = address; } @Data @NoArgsConstructor public static class Balance { /** * 余额类型" trx/ustd */ private String tokenAbbr; private long balance; public Balance(String tokenAbbr, long balance) { this.tokenAbbr = tokenAbbr; this.balance = balance; } } public long getBalanceByAbbr(String abbr) { if (StrUtil.isEmpty(abbr) || this.balance == null || this.balance.size() <= 0) return 0; return this.balance.stream() .filter(b -> abbr.equalsIgnoreCase(b.getTokenAbbr())) .findFirst() .map(Balance::getBalance) .orElse(0L); } }

三、总结

     上述util中,FreezeUtil是经过我自己拆包、封装的tron提供的控制台工具而来,该工具主要提供账户资金冻结功能,专门拆包、封装这个工具的原因是tron官方提供的API已经升级了,不再支持资金冻结操作,可是又没有找到官方提供的新API,索性就自己拆包了一个控制台操作的接口,重新封装成一个util。

三、涉及到的资源

     上文中涉及到的资源已提供,连接如下:点击下载

官方API:
https://developers.tron.network/reference/background



评论区

登录后参与交流、获取后续更新提醒

目录
暂无数据