io.nuls.v2.service.TransactionService Maven / Gradle / Ivy
package io.nuls.v2.service;
import io.nuls.base.basic.AddressTool;
import io.nuls.base.data.*;
import io.nuls.base.signture.MultiSignTxSignature;
import io.nuls.base.signture.P2PHKSignature;
import io.nuls.core.basic.Result;
import io.nuls.core.constant.ErrorCode;
import io.nuls.core.constant.TxType;
import io.nuls.core.crypto.HexUtil;
import io.nuls.core.exception.NulsException;
import io.nuls.core.model.StringUtils;
import io.nuls.v2.SDKContext;
import io.nuls.v2.constant.AccountConstant;
import io.nuls.v2.error.AccountErrorCode;
import io.nuls.v2.model.dto.*;
import io.nuls.v2.txdata.*;
import io.nuls.v2.util.*;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
import static io.nuls.v2.SDKContext.NULS_DEFAULT_OTHER_TX_FEE_PRICE;
import static io.nuls.v2.constant.AccountConstant.ALIAS_FEE;
import static io.nuls.v2.constant.Constant.NULS_CHAIN_ID;
import static io.nuls.v2.util.ValidateUtil.validateChainId;
public class TransactionService {
private TransactionService() {
}
private static TransactionService instance = new TransactionService();
public static TransactionService getInstance() {
return instance;
}
public Result getTx(String txHash) {
validateChainId();
RestFulResult restFulResult = RestFulUtil.get("api/tx/" + txHash);
Result result;
if (restFulResult.isSuccess()) {
result = Result.getSuccess(null);
Map map = (Map) restFulResult.getData();
TransactionDto tx = TransactionDto.mapToPojo(map);
result.setData(tx);
} else {
ErrorCode errorCode = ErrorCode.init(restFulResult.getError().getCode());
result = Result.getFailed(errorCode).setMsg(restFulResult.getError().getMessage());
}
return result;
}
public Result getTransaction(String txHash) {
validateChainId();
RestFulResult restFulResult = RestFulUtil.get("api/tx/" + txHash);
Result result;
if (restFulResult.isSuccess()) {
result = Result.getSuccess(null);
Map map = (Map) restFulResult.getData();
TransactionDto tx = TransactionDto.mapToPojo(map);
restFulResult = RestFulUtil.get("api/block/header/height/" + tx.getBlockHeight());
if (restFulResult.isSuccess()) {
map = (Map) restFulResult.getData();
tx.setBlockHash((String) map.get("hash"));
}
result.setData(tx);
} else {
ErrorCode errorCode = ErrorCode.init(restFulResult.getError().getCode());
result = Result.getFailed(errorCode).setMsg(restFulResult.getError().getMessage());
}
return result;
}
public Result transfer(TransferForm transferForm) {
validateChainId();
Map params = new HashMap<>();
params.put("address", transferForm.getAddress());
params.put("toAddress", transferForm.getToAddress());
params.put("password", transferForm.getPassword());
params.put("amount", transferForm.getAmount());
params.put("remark", transferForm.getRemark());
RestFulResult restFulResult = RestFulUtil.post("api/accountledger/transfer", params);
Result result;
if (restFulResult.isSuccess()) {
result = Result.getSuccess(restFulResult.getData());
} else {
ErrorCode errorCode = ErrorCode.init(restFulResult.getError().getCode());
result = Result.getFailed(errorCode).setMsg(restFulResult.getError().getMessage());
}
return result;
}
public Result crossTransfer(CrossTransferForm form) {
validateChainId();
Map params = new HashMap<>();
params.put("address", form.getAddress());
params.put("toAddress", form.getToAddress());
params.put("password", form.getPassword());
params.put("assetChainId", form.getAssetChainId());
params.put("assetId", form.getAssetId());
params.put("amount", form.getAmount());
params.put("remark", form.getRemark());
RestFulResult restFulResult = RestFulUtil.post("api/accountledger/crossTransfer", params);
Result result;
if (restFulResult.isSuccess()) {
result = Result.getSuccess(restFulResult.getData());
} else {
ErrorCode errorCode = ErrorCode.init(restFulResult.getError().getCode());
result = Result.getFailed(errorCode).setMsg(restFulResult.getError().getMessage());
}
return result;
}
/**
* 计算转账交易手续费
*
* @param dto 请求参数
* @return result
*/
public BigInteger calcTransferTxFee(TransferTxFeeDto dto) {
if (dto.getPrice() == null) {
dto.setPrice(SDKContext.NULS_DEFAULT_NORMAL_TX_FEE_PRICE);
}
return TxUtils.calcTransferTxFee(dto.getAddressCount(), dto.getFromLength(), dto.getToLength(), dto.getRemark(), dto.getPrice());
}
@Deprecated
public Map calcCrossTransferTxFee(CrossTransferTxFeeDto dto) {
boolean isMainNet = false;
if (SDKContext.main_chain_id == NULS_CHAIN_ID || SDKContext.main_chain_id == 2) {
isMainNet = true;
}
return TxUtils.calcCrossTxFee(dto.getAddressCount(), dto.getFromLength(), dto.getToLength(), dto.getRemark(), isMainNet);
}
/**
* 计算NULS跨链交易,所需收取的NULS手续费
*
* @param dto
* @return
*/
public BigInteger calcCrossTransferNulsTxFee(CrossTransferTxFeeDto dto) {
return TxUtils.calcCrossTxFee(dto.getAddressCount(), dto.getFromLength(), dto.getToLength(), dto.getRemark());
}
/**
* 便捷版 组装在NULS链内,转账非NULS资产的单账户对单账户普通转账(不能用于转NULS)。
* 该方法会主动用fromAddress组装(NULS资产)打包手续费,
* 如果from地址中没有足够的手续费,该交易不会成功。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress 转出地址(NULS地址)
* @param toAddress 转入地址(NULS地址)
* @param assetId
* @param amount
* @return
*/
public Result createTxSimpleTransferOfNonNuls(String fromAddress, String toAddress, int assetChainId, int assetId, BigInteger amount) {
return createTxSimpleTransferOfNonNuls(fromAddress, toAddress, assetChainId, assetId, amount, 0, null);
}
/**
* 便捷版 组装在NULS链内,转账非NULS资产的单账户对单账户普通转账(不能用于转NULS)。
* 该方法会主动用fromAddress组装(NULS资产)打包手续费,
* 如果from地址中没有足够的手续费,该交易不会成功。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress 转出地址(NULS地址)
* @param toAddress 转入地址(NULS地址)
* @param assetChainId
* @param assetId
* @param amount
* @param time
* @param remark
* @return
*/
public Result createTxSimpleTransferOfNonNuls(String fromAddress, String toAddress, int assetChainId, int assetId, BigInteger amount, long time, String remark) {
Result accountBalanceR = NulsSDKTool.getAccountBalance(fromAddress, assetChainId, assetId);
if (!accountBalanceR.isSuccess()) {
return Result.getFailed(accountBalanceR.getErrorCode()).setMsg(accountBalanceR.getMsg());
}
Map balance = (Map) accountBalanceR.getData();
BigInteger senderBalance = new BigInteger(balance.get("available").toString());
if (senderBalance.compareTo(amount) < 0) {
return Result.getFailed(AccountErrorCode.INSUFFICIENT_BALANCE);
}
String nonce = balance.get("nonce").toString();
TransferDto transferDto = new TransferDto();
List inputs = new ArrayList<>();
//转账资产
CoinFromDto from = new CoinFromDto();
from.setAddress(fromAddress);
from.setAmount(amount);
from.setAssetChainId(assetChainId);
from.setAssetId(assetId);
from.setNonce(nonce);
inputs.add(from);
Result accountBalanceFeeR = NulsSDKTool.getAccountBalance(fromAddress, SDKContext.main_chain_id, SDKContext.main_asset_id);
if (!accountBalanceFeeR.isSuccess()) {
return Result.getFailed(accountBalanceFeeR.getErrorCode()).setMsg(accountBalanceFeeR.getMsg());
}
Map balanceFee = (Map) accountBalanceFeeR.getData();
BigInteger senderBalanceFee = new BigInteger(balanceFee.get("available").toString());
TransferTxFeeDto feeDto = new TransferTxFeeDto();
feeDto.setAddressCount(1);
feeDto.setFromLength(2);
feeDto.setToLength(1);
feeDto.setRemark(remark);
BigInteger feeNeed = NulsSDKTool.calcTransferTxFee(feeDto);
if (senderBalanceFee.compareTo(feeNeed) < 0) {
return Result.getFailed(AccountErrorCode.INSUFFICIENT_FEE);
}
String nonceFee = balanceFee.get("nonce").toString();
//手续费资产
CoinFromDto fromFee = new CoinFromDto();
fromFee.setAddress(fromAddress);
fromFee.setAmount(feeNeed);
fromFee.setAssetChainId(SDKContext.main_chain_id);
fromFee.setAssetId(SDKContext.main_asset_id);
fromFee.setNonce(nonceFee);
inputs.add(fromFee);
List outputs = new ArrayList<>();
CoinToDto to = new CoinToDto();
to.setAddress(toAddress);
to.setAmount(amount);
to.setAssetChainId(assetChainId);
to.setAssetId(assetId);
outputs.add(to);
transferDto.setInputs(inputs);
transferDto.setOutputs(outputs);
transferDto.setTime(time);
transferDto.setRemark(remark);
return createTransferTx(transferDto);
}
/**
* 便捷版 组装在NULS链内,转账NULS资产的单账户对单账户普通转账。
* !! 打包手续费不包含在amount中, 本函数将从fromAddress中额外获取手续费追加到coinfrom中,
* 请不要将手续费事先加入到amount参数中, amount参数作为实际到账的数量。。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress
* @param toAddress
* @param amount
* @return
*/
public Result createTxSimpleTransferOfNuls(String fromAddress, String toAddress, BigInteger amount) {
return createTxSimpleTransferOfNuls(fromAddress, toAddress, amount, 0, null);
}
/**
* 便捷版 组装在NULS链内,转账NULS资产的单账户对单账户普通转账。
* !! 打包手续费不包含在amount中, 本函数将从fromAddress中额外获取手续费追加到coinfrom中,
* 请不要将手续费事先加入到amount参数中, amount参数作为实际到账的数量。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress
* @param toAddress
* @param amount
* @param time
* @param remark
* @return
*/
public Result createTxSimpleTransferOfNuls(String fromAddress, String toAddress, BigInteger amount, long time, String remark) {
Result accountBalanceR = NulsSDKTool.getAccountBalance(fromAddress, SDKContext.main_chain_id, SDKContext.main_asset_id);
if (!accountBalanceR.isSuccess()) {
return Result.getFailed(accountBalanceR.getErrorCode()).setMsg(accountBalanceR.getMsg());
}
Map balance = (Map) accountBalanceR.getData();
BigInteger senderBalance = new BigInteger(balance.get("available").toString());
TransferTxFeeDto feeDto = new TransferTxFeeDto();
feeDto.setAddressCount(1);
feeDto.setFromLength(2);
feeDto.setToLength(1);
feeDto.setRemark(remark);
BigInteger feeNeed = NulsSDKTool.calcTransferTxFee(feeDto);
BigInteger amountTotal = amount.add(feeNeed);
if (senderBalance.compareTo(amountTotal) < 0) {
return Result.getFailed(AccountErrorCode.INSUFFICIENT_BALANCE);
}
String nonce = balance.get("nonce").toString();
TransferDto transferDto = new TransferDto();
List inputs = new ArrayList<>();
//转账资产
CoinFromDto from = new CoinFromDto();
from.setAddress(fromAddress);
from.setAmount(amountTotal);
from.setAssetChainId(SDKContext.main_chain_id);
from.setAssetId(SDKContext.main_asset_id);
from.setNonce(nonce);
inputs.add(from);
List outputs = new ArrayList<>();
CoinToDto to = new CoinToDto();
to.setAddress(toAddress);
to.setAmount(amount);
to.setAssetChainId(SDKContext.main_chain_id);
to.setAssetId(SDKContext.main_asset_id);
outputs.add(to);
transferDto.setInputs(inputs);
transferDto.setOutputs(outputs);
transferDto.setTime(time);
transferDto.setRemark(remark);
return createTransferTx(transferDto);
}
/**
* 创建转账交易(离线)
* create transfer transaction(off-line)
*
* @param transferDto 转账请求参数
* @return
*/
public Result createTransferTx(TransferDto transferDto) {
validateChainId();
try {
CommonValidator.checkTransferDto(transferDto);
for (CoinFromDto fromDto : transferDto.getInputs()) {
if (fromDto.getAssetChainId() == 0) {
fromDto.setAssetChainId(SDKContext.main_chain_id);
}
if (fromDto.getAssetId() == 0) {
fromDto.setAssetId(SDKContext.main_asset_id);
}
}
for (CoinToDto toDto : transferDto.getOutputs()) {
if (toDto.getAssetChainId() == 0) {
toDto.setAssetChainId(SDKContext.main_chain_id);
}
if (toDto.getAssetId() == 0) {
toDto.setAssetId(SDKContext.main_asset_id);
}
}
Transaction tx = new Transaction(TxType.TRANSFER);
if (transferDto.getTime() != 0) {
tx.setTime(transferDto.getTime());
} else {
tx.setTime(getCurrentTimeSeconds());
}
tx.setRemark(StringUtils.bytes(transferDto.getRemark()));
CoinData coinData = assemblyCoinData(transferDto.getInputs(), transferDto.getOutputs(), tx.getSize());
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
/**
* 组装转账交易的coinData数据
* Assemble the coinData for the transfer transaction
*
* @return coinData
* @throws NulsException
*/
private CoinData assemblyCoinData(List inputs, List outputs, int txSize) throws NulsException {
List coinFroms = new ArrayList<>();
for (CoinFromDto from : inputs) {
byte[] address = AddressTool.getAddress(from.getAddress());
byte[] nonce = HexUtil.decode(from.getNonce());
CoinFrom coinFrom = new CoinFrom(address, from.getAssetChainId(), from.getAssetId(), from.getAmount(), nonce, AccountConstant.NORMAL_TX_LOCKED);
coinFroms.add(coinFrom);
}
List coinTos = new ArrayList<>();
for (CoinToDto to : outputs) {
byte[] addressByte = AddressTool.getAddress(to.getAddress());
CoinTo coinTo = new CoinTo(addressByte, to.getAssetChainId(), to.getAssetId(), to.getAmount(), to.getLockTime());
coinTos.add(coinTo);
}
txSize = txSize + getSignatureSize(coinFroms);
TxUtils.calcTxFee(coinFroms, coinTos, txSize);
CoinData coinData = new CoinData();
coinData.setFrom(coinFroms);
coinData.setTo(coinTos);
return coinData;
}
/**
* 便捷版 组装跨链转账非NULS资产的单账户对单账户普通跨链转账(不能用于转NULS)。
* 该方法会主动用fromAddress组装(NULS资产)打包手续费。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress 转出地址(NULS地址)
* @param toAddress 转入地址(非NULS地址)
* @param assetChainId 转账资产链id
* @param assetId 转账资产id
* @param amount 转账token数量
* @return
*/
public Result createCrossTxSimpleTransferOfNonNuls(String fromAddress, String toAddress, int assetChainId, int assetId, BigInteger amount) {
return createCrossTxSimpleTransferOfNonNuls(fromAddress, toAddress, assetChainId, assetId, amount, 0, null);
}
/**
* 便捷版 组装跨链转账非NULS资产的单账户对单账户普通跨链转账(不能用于转NULS)。
* 该方法会主动用fromAddress组装(NULS资产)打包手续费。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress 转出地址(NULS地址)
* @param toAddress 转入地址(非NULS地址)
* @param assetChainId 转账资产链id
* @param assetId 转账资产id
* @param amount 转账token数量
* @param time 交易时间
* @param remark 备注
* @return
*/
public Result createCrossTxSimpleTransferOfNonNuls(String fromAddress, String toAddress, int assetChainId, int assetId, BigInteger amount, long time, String remark) {
Result accountBalanceR = NulsSDKTool.getAccountBalance(fromAddress, assetChainId, assetId);
if (!accountBalanceR.isSuccess()) {
return Result.getFailed(accountBalanceR.getErrorCode()).setMsg(accountBalanceR.getMsg());
}
Map balance = (Map) accountBalanceR.getData();
BigInteger senderBalance = new BigInteger(balance.get("available").toString());
if (senderBalance.compareTo(amount) < 0) {
return Result.getFailed(AccountErrorCode.INSUFFICIENT_BALANCE);
}
String nonce = balance.get("nonce").toString();
TransferDto transferDto = new TransferDto();
List inputs = new ArrayList<>();
//转账资产
CoinFromDto from = new CoinFromDto();
from.setAddress(fromAddress);
from.setAmount(amount);
from.setAssetChainId(assetChainId);
from.setAssetId(assetId);
from.setNonce(nonce);
inputs.add(from);
Result accountBalanceFeeR = NulsSDKTool.getAccountBalance(fromAddress, SDKContext.main_chain_id, SDKContext.main_asset_id);
if (!accountBalanceFeeR.isSuccess()) {
return Result.getFailed(accountBalanceFeeR.getErrorCode()).setMsg(accountBalanceFeeR.getMsg());
}
Map balanceFee = (Map) accountBalanceFeeR.getData();
BigInteger senderBalanceFee = new BigInteger(balanceFee.get("available").toString());
CrossTransferTxFeeDto crossFeeDto = new CrossTransferTxFeeDto();
crossFeeDto.setAddressCount(1);
crossFeeDto.setFromLength(2);
crossFeeDto.setToLength(1);
crossFeeDto.setRemark(remark);
BigInteger feeNeed = calcCrossTransferNulsTxFee(crossFeeDto);
if (senderBalanceFee.compareTo(feeNeed) < 0) {
return Result.getFailed(AccountErrorCode.INSUFFICIENT_FEE);
}
String nonceFee = balanceFee.get("nonce").toString();
//手续费资产
CoinFromDto fromFee = new CoinFromDto();
fromFee.setAddress(fromAddress);
fromFee.setAmount(feeNeed);
fromFee.setAssetChainId(SDKContext.main_chain_id);
fromFee.setAssetId(SDKContext.main_asset_id);
fromFee.setNonce(nonceFee);
inputs.add(fromFee);
List outputs = new ArrayList<>();
CoinToDto to = new CoinToDto();
to.setAddress(toAddress);
to.setAmount(amount);
to.setAssetChainId(assetChainId);
to.setAssetId(assetId);
outputs.add(to);
transferDto.setInputs(inputs);
transferDto.setOutputs(outputs);
transferDto.setTime(time);
transferDto.setRemark(remark);
return createCrossTransferTx(transferDto);
}
/**
* 便捷版 组装跨链转账NULS资产的单账户对单账户普通跨链转账。
* !! 打包手续费不包含在amount中, 本函数将从fromAddress中额外获取手续费追加到coinfrom中,
* 请不要将手续费事先加入到amount参数中, amount参数作为实际到账的数量。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress
* @param toAddress (非NULS地址)
* @param amount
* @return
*/
public Result createCrossTxSimpleTransferOfNuls(String fromAddress, String toAddress, BigInteger amount) {
return createCrossTxSimpleTransferOfNuls(fromAddress, toAddress, amount, 0, null);
}
/**
* 便捷版 组装跨链转账NULS资产的单账户对单账户普通跨链转账。
* !! 打包手续费不包含在amount中, 本函数将从fromAddress中额外获取手续费追加到coinfrom中,
* 请不要将手续费事先加入到amount参数中, amount参数作为实际到账的数量。
*
* 如果需要完整信息或结构更复杂的转账(比如多账户),请使用完全版的离线交易组装
*
* @param fromAddress
* @param toAddress (非NULS地址)
* @param amount
* @param time
* @param remark
* @return
*/
public Result createCrossTxSimpleTransferOfNuls(String fromAddress, String toAddress, BigInteger amount, long time, String remark) {
Result accountBalanceR = NulsSDKTool.getAccountBalance(fromAddress, SDKContext.main_chain_id, SDKContext.main_asset_id);
if (!accountBalanceR.isSuccess()) {
return Result.getFailed(accountBalanceR.getErrorCode()).setMsg(accountBalanceR.getMsg());
}
Map balance = (Map) accountBalanceR.getData();
BigInteger senderBalance = new BigInteger(balance.get("available").toString());
CrossTransferTxFeeDto crossFeeDto = new CrossTransferTxFeeDto();
crossFeeDto.setAddressCount(1);
crossFeeDto.setFromLength(2);
crossFeeDto.setToLength(1);
crossFeeDto.setRemark(remark);
BigInteger feeNeed = NulsSDKTool.calcCrossTransferNulsTxFee(crossFeeDto);
BigInteger amountTotal = amount.add(feeNeed);
if (senderBalance.compareTo(amountTotal) < 0) {
return Result.getFailed(AccountErrorCode.INSUFFICIENT_BALANCE);
}
String nonce = balance.get("nonce").toString();
TransferDto transferDto = new TransferDto();
List inputs = new ArrayList<>();
//转账资产
CoinFromDto from = new CoinFromDto();
from.setAddress(fromAddress);
from.setAmount(amountTotal);
from.setAssetChainId(SDKContext.main_chain_id);
from.setAssetId(SDKContext.main_asset_id);
from.setNonce(nonce);
inputs.add(from);
List outputs = new ArrayList<>();
CoinToDto to = new CoinToDto();
to.setAddress(toAddress);
to.setAmount(amount);
to.setAssetChainId(SDKContext.main_chain_id);
to.setAssetId(SDKContext.main_asset_id);
outputs.add(to);
transferDto.setInputs(inputs);
transferDto.setOutputs(outputs);
transferDto.setTime(time);
transferDto.setRemark(remark);
return createCrossTransferTx(transferDto);
}
/**
* 创建跨链转账交易
*
* @param transferDto
* @return
*/
public Result createCrossTransferTx(TransferDto transferDto) {
validateChainId();
try {
CommonValidator.checkCrossTransferDto(transferDto);
Transaction tx = new Transaction(TxType.CROSS_CHAIN);
if (transferDto.getTime() != 0) {
tx.setTime(transferDto.getTime());
} else {
tx.setTime(getCurrentTimeSeconds());
}
tx.setRemark(StringUtils.bytes(transferDto.getRemark()));
CoinData coinData = createCrossTxCoinData(transferDto.getInputs(), transferDto.getOutputs());
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
public CoinData createCrossTxCoinData(List inputs, List outputs) {
List coinFroms = new ArrayList<>();
for (CoinFromDto from : inputs) {
byte[] address = AddressTool.getAddress(from.getAddress());
byte[] nonce = HexUtil.decode(from.getNonce());
CoinFrom coinFrom = new CoinFrom(address, from.getAssetChainId(), from.getAssetId(), from.getAmount(), nonce, AccountConstant.NORMAL_TX_LOCKED);
coinFroms.add(coinFrom);
}
List coinTos = new ArrayList<>();
for (CoinToDto to : outputs) {
byte[] addressByte = AddressTool.getAddress(to.getAddress());
CoinTo coinTo = new CoinTo(addressByte, to.getAssetChainId(), to.getAssetId(), to.getAmount(), to.getLockTime());
coinTos.add(coinTo);
}
CoinData coinData = new CoinData();
coinData.setFrom(coinFroms);
coinData.setTo(coinTos);
return coinData;
}
public Result createMultiSignTransferTx(MultiSignTransferDto transferDto) {
validateChainId();
try {
CommonValidator.checkMultiSignTransferDto(transferDto);
for (CoinFromDto fromDto : transferDto.getInputs()) {
if (fromDto.getAssetChainId() == 0) {
fromDto.setAssetChainId(SDKContext.main_chain_id);
}
if (fromDto.getAssetId() == 0) {
fromDto.setAssetId(SDKContext.main_asset_id);
}
}
for (CoinToDto toDto : transferDto.getOutputs()) {
if (toDto.getAssetChainId() == 0) {
toDto.setAssetChainId(SDKContext.main_chain_id);
}
if (toDto.getAssetId() == 0) {
toDto.setAssetId(SDKContext.main_asset_id);
}
}
Transaction tx = new Transaction(TxType.TRANSFER);
tx.setTime(getCurrentTimeSeconds());
tx.setRemark(StringUtils.bytes(transferDto.getRemark()));
CoinData coinData = assemblyCoinData(transferDto, tx.getSize());
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
MultiSignTxSignature signature = new MultiSignTxSignature();
signature.setM((byte) transferDto.getMinSigns());
List list = new ArrayList<>();
for (String pubKey : transferDto.getPubKeys()) {
list.add(HexUtil.decode(pubKey));
}
signature.setPubKeyList(list);
tx.setTransactionSignature(signature.serialize());
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
private CoinData assemblyCoinData(MultiSignTransferDto transferDto, int txSize) throws NulsException {
List coinFroms = new ArrayList<>();
for (CoinFromDto from : transferDto.getInputs()) {
byte[] address = AddressTool.getAddress(from.getAddress());
byte[] nonce = HexUtil.decode(from.getNonce());
CoinFrom coinFrom = new CoinFrom(address, from.getAssetChainId(), from.getAssetId(), from.getAmount(), nonce, AccountConstant.NORMAL_TX_LOCKED);
coinFroms.add(coinFrom);
}
List coinTos = new ArrayList<>();
for (CoinToDto to : transferDto.getOutputs()) {
byte[] addressByte = AddressTool.getAddress(to.getAddress());
CoinTo coinTo = new CoinTo(addressByte, to.getAssetChainId(), to.getAssetId(), to.getAmount(), to.getLockTime());
coinTos.add(coinTo);
}
txSize = txSize + getMultiSignSignatureSize(transferDto.getPubKeys().size());
TxUtils.calcTxFee(coinFroms, coinTos, txSize);
CoinData coinData = new CoinData();
coinData.setFrom(coinFroms);
coinData.setTo(coinTos);
return coinData;
}
/**
* 计算转账交易手续费
*
* @param dto 请求参数
* @return result
*/
public BigInteger calcMultiSignTransferTxFee(MultiSignTransferTxFeeDto dto) {
if (dto.getPrice() == null) {
dto.setPrice(SDKContext.NULS_DEFAULT_NORMAL_TX_FEE_PRICE);
}
return TxUtils.calcTransferTxFee(dto.getPubKeyCount(), dto.getFromLength(), dto.getToLength(), dto.getRemark(), dto.getPrice());
}
/**
* 通过coinFroms计算签名数据的size
* 如果coinFroms有重复地址则只计算一次
* Calculate the size of the signature data by coinFroms
* if coinFroms has duplicate addresses, it will only be evaluated once
*
* @param coinFroms 交易输入
* @return int size
*/
private int getSignatureSize(List coinFroms) {
int size = 0;
Set commonAddress = new HashSet<>();
for (CoinFrom coinFrom : coinFroms) {
String address = AddressTool.getStringAddressByBytes(coinFrom.getAddress());
commonAddress.add(address);
}
size += commonAddress.size() * P2PHKSignature.SERIALIZE_LENGTH;
return size;
}
private int getMultiSignSignatureSize(int signNumber) {
int size = signNumber * P2PHKSignature.SERIALIZE_LENGTH;
return size;
}
public Result createAliasTx(AliasDto aliasDto) {
validateChainId();
try {
CommonValidator.checkAliasDto(aliasDto);
Transaction tx = new Transaction(TxType.ACCOUNT_ALIAS);
tx.setTime(getCurrentTimeSeconds());
tx.setRemark(StringUtils.bytes(aliasDto.getRemark()));
Alias alias = new Alias(AddressTool.getAddress(aliasDto.getAddress()), aliasDto.getAlias());
tx.setTxData(alias.serialize());
CoinData coinData = assemblyCoinData(aliasDto);
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
private CoinData assemblyCoinData(AliasDto dto) {
byte[] address = AddressTool.getAddress(dto.getAddress());
byte[] nonce = HexUtil.decode(dto.getNonce());
List coinFroms = new ArrayList<>();
CoinFrom coinFrom = new CoinFrom();
coinFrom.setAddress(address);
coinFrom.setNonce(nonce);
coinFrom.setAmount(ALIAS_FEE.add(NULS_DEFAULT_OTHER_TX_FEE_PRICE));
coinFrom.setAssetsChainId(SDKContext.main_chain_id);
coinFrom.setAssetsId(SDKContext.main_asset_id);
coinFroms.add(coinFrom);
String prefix = AccountTool.getPrefix(dto.getAddress());
List coinTos = new ArrayList<>();
CoinTo coinTo = new CoinTo();
coinTo.setAddress(AddressTool.getAddress(AccountConstant.DESTORY_PUBKEY, SDKContext.main_chain_id, prefix));
coinTo.setAmount(ALIAS_FEE);
coinTo.setAssetsChainId(SDKContext.main_chain_id);
coinTo.setAssetsId(SDKContext.main_asset_id);
coinTos.add(coinTo);
CoinData coinData = new CoinData();
coinData.setFrom(coinFroms);
coinData.setTo(coinTos);
return coinData;
}
/**
* 组装创建共识节点交易
* Assemble to create consensus node transactions
*
* @param consensusDto 创建共识节点请求参数
* @return result
*/
public Result createConsensusTx(ConsensusDto consensusDto) {
validateChainId();
try {
if (StringUtils.isBlank(consensusDto.getRewardAddress())) {
consensusDto.setRewardAddress(consensusDto.getAgentAddress());
}
CommonValidator.validateConsensusDto(consensusDto);
if (consensusDto.getInput().getAssetChainId() == 0) {
consensusDto.getInput().setAssetChainId(SDKContext.main_chain_id);
}
if (consensusDto.getInput().getAssetId() == 0) {
consensusDto.getInput().setAssetId(SDKContext.main_asset_id);
}
Transaction tx = new Transaction(TxType.REGISTER_AGENT);
tx.setTime(getCurrentTimeSeconds());
Agent agent = new Agent();
agent.setAgentAddress(AddressTool.getAddress(consensusDto.getAgentAddress()));
agent.setPackingAddress(AddressTool.getAddress(consensusDto.getPackingAddress()));
agent.setRewardAddress(AddressTool.getAddress(consensusDto.getRewardAddress()));
agent.setDeposit((consensusDto.getDeposit()));
agent.setCommissionRate((byte) consensusDto.getCommissionRate());
tx.setTxData(agent.serialize());
CoinData coinData = assemblyCoinData(consensusDto.getInput(), agent.getDeposit(), tx.size());
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
/**
* Create a proxy consensus transaction
* 创建委托共识交易
*
* @param dto 委托共识请求参数
* @return result
*/
public Result createDepositTx(DepositDto dto) {
validateChainId();
try {
CommonValidator.validateDepositDto(dto);
if (dto.getInput().getAssetChainId() == 0) {
dto.getInput().setAssetChainId(SDKContext.main_chain_id);
}
if (dto.getInput().getAssetId() == 0) {
dto.getInput().setAssetId(SDKContext.main_asset_id);
}
Transaction tx = new Transaction(TxType.DEPOSIT);
tx.setTime(getCurrentTimeSeconds());
Deposit deposit = new Deposit();
deposit.setAddress(AddressTool.getAddress(dto.getAddress()));
deposit.setAgentHash(NulsHash.fromHex(dto.getAgentHash()));
deposit.setDeposit(dto.getDeposit());
tx.setTxData(deposit.serialize());
CoinData coinData = assemblyCoinData(dto.getInput(), dto.getDeposit(), tx.size());
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
private CoinData assemblyCoinData(CoinFromDto from, BigInteger amount, int txSize) throws NulsException {
List coinFroms = new ArrayList<>();
byte[] address = AddressTool.getAddress(from.getAddress());
byte[] nonce = HexUtil.decode(from.getNonce());
CoinFrom coinFrom = new CoinFrom(address, from.getAssetChainId(), from.getAssetId(), from.getAmount(), nonce, AccountConstant.NORMAL_TX_LOCKED);
coinFroms.add(coinFrom);
List coinTos = new ArrayList<>();
CoinTo coinTo = new CoinTo(address, from.getAssetChainId(), from.getAssetId(), amount, -1);
coinTos.add(coinTo);
txSize = txSize + getSignatureSize(coinFroms);
TxUtils.calcTxFee(coinFroms, coinTos, txSize);
CoinData coinData = new CoinData();
coinData.setFrom(coinFroms);
coinData.setTo(coinTos);
return coinData;
}
/**
* 创建取消委托交易
*
* @param dto 取消委托交易参数
* @return result
*/
public Result createWithdrawDepositTx(WithDrawDto dto) {
validateChainId();
try {
if (dto.getPrice() == null) {
dto.setPrice(NULS_DEFAULT_OTHER_TX_FEE_PRICE);
}
CommonValidator.validateWithDrawDto(dto);
if (dto.getInput().getAssetChainId() == 0) {
dto.getInput().setAssetChainId(SDKContext.main_chain_id);
}
if (dto.getInput().getAssetId() == 0) {
dto.getInput().setAssetId(SDKContext.main_asset_id);
}
Transaction tx = new Transaction(TxType.CANCEL_DEPOSIT);
tx.setTime(getCurrentTimeSeconds());
CancelDeposit cancelDeposit = new CancelDeposit();
cancelDeposit.setAddress(AddressTool.getAddress(dto.getAddress()));
cancelDeposit.setJoinTxHash(NulsHash.fromHex(dto.getDepositHash()));
tx.setTxData(cancelDeposit.serialize());
CoinData coinData = assemblyCoinData(dto, tx.size());
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
/**
* 组装退出共识交易coinData
*
* @param dto 请求参数
* @param txSize 交易大小
* @return coinData
* @throws NulsException
*/
private CoinData assemblyCoinData(WithDrawDto dto, int txSize) throws NulsException {
List coinFroms = new ArrayList<>();
CoinFromDto from = dto.getInput();
byte[] address = AddressTool.getAddress(from.getAddress());
CoinFrom coinFrom = new CoinFrom(address, from.getAssetChainId(), from.getAssetId(), from.getAmount(), (byte) -1);
NulsHash nulsHash = NulsHash.fromHex(dto.getDepositHash());
coinFrom.setNonce(TxUtils.getNonce(nulsHash.getBytes()));
coinFroms.add(coinFrom);
List coinTos = new ArrayList<>();
CoinTo coinTo = new CoinTo(address, from.getAssetChainId(), from.getAssetId(), from.getAmount().subtract(dto.getPrice()), 0);
coinTos.add(coinTo);
txSize = txSize + getSignatureSize(coinFroms);
TxUtils.calcTxFee(coinFroms, coinTos, txSize);
CoinData coinData = new CoinData();
coinData.setFrom(coinFroms);
coinData.setTo(coinTos);
return coinData;
}
/**
* 创建注销共识节点交易
*
* @param dto 注销节点参数请求
* @return result
*/
public Result createStopConsensusTx(StopConsensusDto dto) {
validateChainId();
try {
if (dto.getPrice() == null) {
dto.setPrice(NULS_DEFAULT_OTHER_TX_FEE_PRICE);
}
CommonValidator.validateStopConsensusDto(dto);
for (StopDepositDto depositDto : dto.getDepositList()) {
if (depositDto.getInput().getAssetChainId() == 0) {
depositDto.getInput().setAssetChainId(SDKContext.main_chain_id);
}
if (depositDto.getInput().getAssetId() == 0) {
depositDto.getInput().setAssetId(SDKContext.main_asset_id);
}
}
Transaction tx = new Transaction(TxType.STOP_AGENT);
tx.setTime(getCurrentTimeSeconds());
StopAgent stopAgent = new StopAgent();
NulsHash nulsHash = NulsHash.fromHex(dto.getAgentHash());
stopAgent.setCreateTxHash(nulsHash);
tx.setTxData(stopAgent.serialize());
CoinData coinData = assemblyCoinData(dto, tx.size());
tx.setCoinData(coinData.serialize());
tx.setHash(NulsHash.calcHash(tx.serializeForHash()));
Map map = new HashMap<>();
map.put("hash", tx.getHash().toHex());
map.put("txHex", HexUtil.encode(tx.serialize()));
return Result.getSuccess(map);
} catch (NulsException e) {
return Result.getFailed(e.getErrorCode()).setMsg(e.format());
} catch (IOException e) {
return Result.getFailed(AccountErrorCode.DATA_PARSE_ERROR).setMsg(AccountErrorCode.DATA_PARSE_ERROR.getMsg());
}
}
/**
* 组装注销节点交易coinData
*
* @param dto 参数
* @param txSize 交易大小
* @return
* @throws NulsException
*/
private CoinData assemblyCoinData(StopConsensusDto dto, int txSize) throws NulsException {
//获取当前链注册共识资产的chainId和assetId
int chainId = SDKContext.main_chain_id;
int assetId = SDKContext.main_asset_id;
List coinFromList = new ArrayList<>();
//组装创建节点交易的coinFrom
byte[] addressBytes = AddressTool.getAddress(dto.getAgentAddress());
CoinFrom coinFrom = new CoinFrom(addressBytes, chainId, assetId, dto.getDeposit(), (byte) -1);
NulsHash nulsHash = NulsHash.fromHex(dto.getAgentHash());
coinFrom.setNonce(TxUtils.getNonce(nulsHash.getBytes()));
coinFromList.add(coinFrom);
Map dtoMap = new HashMap<>();
CoinFromDto fromDto;
//组装所有委托的coinFrom
for (StopDepositDto depositDto : dto.getDepositList()) {
CoinFromDto input = depositDto.getInput();
byte[] address = AddressTool.getAddress(input.getAddress());
CoinFrom coinFrom1 = new CoinFrom(address, input.getAssetChainId(), input.getAssetId(), input.getAmount(), (byte) -1);
NulsHash nulsHash1 = NulsHash.fromHex(depositDto.getDepositHash());
coinFrom1.setNonce(TxUtils.getNonce(nulsHash1.getBytes()));
coinFromList.add(coinFrom1);
//将相同账户的多次委托的金额存放在一起
String key = input.getAddress() + input.getAssetChainId() + input.getAssetId();
fromDto = dtoMap.get(key);
if (fromDto == null) {
dtoMap.put(key, input);
} else {
fromDto.setAmount(fromDto.getAmount().add(input.getAmount()));
}
}
//通过dtoMap组装交易输出
List coinToList = new ArrayList<>();
for (CoinFromDto input : dtoMap.values()) {
byte[] address = AddressTool.getAddress(input.getAddress());
CoinTo coinTo = new CoinTo(address, input.getAssetChainId(), input.getAssetId(), input.getAmount(), 0L);
coinToList.add(coinTo);
}
//计算手续费
BigInteger fee = TxUtils.calcStopConsensusTxFee(coinFromList.size(), coinToList.size() + 1, dto.getPrice());
//组装退回保证金的coinTo
CoinTo coinTo = new CoinTo(addressBytes, coinFrom.getAssetsChainId(), coinFrom.getAssetsId(), coinFrom.getAmount().subtract(fee), getCurrentTimeSeconds() + SDKContext.STOP_AGENT_LOCK_TIME);
coinToList.add(0, coinTo);
txSize = txSize + P2PHKSignature.SERIALIZE_LENGTH;
TxUtils.calcTxFee(coinFromList, coinToList, txSize);
CoinData coinData = new CoinData();
coinData.setFrom(coinFromList);
coinData.setTo(coinToList);
return coinData;
}
/**
* 密文私钥签名交易(单签)
*
* @param address
* @param txHex
* @return
*/
public Result signTx(String txHex, String address, String encryptedPrivateKey, String password) {
List signDtoList = new ArrayList<>();
SignDto signDto = new SignDto();
signDto.setAddress(address);
signDto.setEncryptedPrivateKey(encryptedPrivateKey);
signDto.setPassword(password);
signDtoList.add(signDto);
return NulsSDKTool.sign(signDtoList, txHex);
}
/**
* 明文私钥签名交易(单签)
*
* @param address
* @param txHex
* @return
*/
public Result signTx(String txHex, String address, String privateKey) {
List signDtoList = new ArrayList<>();
SignDto signDto = new SignDto();
signDto.setAddress(address);
signDto.setPriKey(privateKey);
signDtoList.add(signDto);
return NulsSDKTool.sign(signDtoList, txHex);
}
/**
* 广播交易
*
* @param txHex
* @return
*/
public Result broadcastTx(String txHex) {
RpcResult