How to Build a Transaction Locally
This article will show developers how to build a transaction locally through an example. It is used to build data inside the transaction. If developers have offline-signature, remark transaction and other requirements, you can refer to this article. These data include:
message raw {
bytes ref_block_bytes = 1;//The bytes from 6 to 8(not included) of the height of the latest block
int64 ref_block_num = 3;//The height of the latest block.Optional.
bytes ref_block_hash = 4;//The bytes from the 8 to 16(not included) of the hash of the latest block
int64 expiration = 8;// Use the latest block time plus N minutes as the value of expiration time('N' can be modified in the configuration file of the local node, if using a public node, the default 'N' is one minute).The network judgment condition is if (expiration > latest block time and expiration < latest block time + 24 hours) means the transaction is in period of validity. Otherwise, it will be an overdue transaction, will not be accepted by the Mainnet.
repeated authority auths = 9;//Permission setting
// data not used
bytes data = 10;//Unused field, can be used for remark information, optional
//only support size = 1, repeated list here for extension
repeated Contract contract = 11;//The contract data
// scripts not used
bytes scripts = 12;//Unused field, can be used for remark information, optional
int64 timestamp = 14;//Create transaction time
int64 fee_limit = 18;//Threshold for trx consumption due to insufficient resources
}
Note:
Only supports RPC
Java
The calling environment of the RPC interface can be obtained through wallet-cli
package org.tron.demo;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.tron.api.GrpcAPI.Return;
import org.tron.api.GrpcAPI.TransactionExtention;
import org.tron.common.crypto.ECKey;
import org.tron.common.crypto.Sha256Sm3Hash;
import org.tron.common.utils.ByteArray;
import org.tron.core.exception.CancelException;
import org.tron.protos.Contract;
import org.tron.protos.Protocol.Block;
import org.tron.protos.Protocol.Transaction;
import org.tron.walletserver.WalletApi;
import java.util.Arrays;
public class TransactionSignDemo {
/* set reference data*/
public static Transaction setReference(Transaction transaction, Block newestBlock) {
long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber();
byte[] blockHash = getBlockHash(newestBlock).getBytes();
byte[] refBlockNum = ByteArray.fromLong(blockHeight);
Transaction.raw rawData = transaction.getRawData().toBuilder()
.setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16)))
.setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8)))
.setRefBlockNum(blockHeight)
.build();
return transaction.toBuilder().setRawData(rawData).build();
}
public static Sha256Sm3Hash getBlockHash(Block block) {
return Sha256Sm3Hash.of(block.getBlockHeader().getRawData().toByteArray());
}
public static String getTransactionHash(Transaction transaction) {
String txid = ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()));
return txid;
}
public static Transaction createTransaction(byte[] from, byte[] to, long amount) {
Transaction.Builder transactionBuilder = Transaction.newBuilder();
Block newestBlock = WalletApi.getBlock(-1);
/*set the contract data*/
Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder();
Contract.TransferContract.Builder transferContractBuilder =
Contract.TransferContract.newBuilder();
transferContractBuilder.setAmount(amount);
ByteString bsTo = ByteString.copyFrom(to);
ByteString bsOwner = ByteString.copyFrom(from);
transferContractBuilder.setToAddress(bsTo);
transferContractBuilder.setOwnerAddress(bsOwner);
try {
Any any = Any.pack(transferContractBuilder.build());
contractBuilder.setParameter(any);
} catch (Exception e) {
return null;
}
/*set memo,etc*/
contractBuilder.setType(Transaction.Contract.ContractType.TransferContract);
transactionBuilder.getRawDataBuilder().addContract(contractBuilder)
.setTimestamp(System.currentTimeMillis())
.setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000)
.setData(ByteString.copyFromUtf8("memo"))
.setScripts(ByteString.copyFromUtf8("scripts"));
Transaction transaction = transactionBuilder.build();
Transaction refTransaction = setReference(transaction, newestBlock);
return refTransaction;
}
/*signature*/
private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey)
throws InvalidProtocolBufferException {
ECKey ecKey = ECKey.fromPrivate(privateKey);
Transaction transaction1 = Transaction.parseFrom(transaction);
byte[] rawdata = transaction1.getRawData().toByteArray();
byte[] hash = Sha256Sm3Hash.hash(rawdata);
byte[] sign = ecKey.sign(hash).toByteArray();
return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray();
}
private static boolean broadcast(byte[] transactionBytes) throws InvalidProtocolBufferException {
return WalletApi.broadcastTransaction(transactionBytes);
}
public static void main(String[] args) throws InvalidProtocolBufferException, CancelException {
String privateStr = "da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0";
byte[] privateBytes = ByteArray.fromHexString(privateStr);
ECKey ecKey = ECKey.fromPrivate(privateBytes);
byte[] from = ecKey.getAddress();
byte[] to = WalletApi.decodeFromBase58Check("TN9RRaXkCFtTXRso2GdTZxSxxwufzxLQPP");
long amount = 100_000_000L; // 100 TRX, api only receive trx in Sun, and 1 trx = 1000000 Sun
Transaction transaction = createTransaction(from, to, amount);
byte[] transactionBytes = transaction.toByteArray();
byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes);
boolean result = broadcast(transaction4);
System.out.println(result);
}
}
After executing the above code, you can get the following information:
{
"ret": [
{
"contractRet": "SUCCESS"
}
],
"signature": [
"ced3929af13ca455fca59088c1a98908897640f6dc7c5746f1a21eb850fb266e6d7996e1984464b0d8a262f991342a1c9e4197311ea3562631f411ffde9abcda00"
],
"txID": "a1185aad04cfa78cb66d5eb3780b8801dde1d49d14aee9e21841194b81a64bd6",
"raw_data": {
"data": "6d656d6f",
"contract": [
{
"parameter": {
"value": {
"amount": 100000000,
"owner_address": "41bf97a54f4b829c4e9253b26024b1829e1a3b1120",
"to_address": "41859009fd225692b11237a6ffd8fdba2eb7140cca"
},
"type_url": "type.googleapis.com/protocol.TransferContract"
},
"type": "TransferContract"
}
],
"ref_block_bytes": "c75b",
"ref_block_hash": "7cfc890c6e4d7a15",
"expiration": 1583304414000,
"ref_block_num": 2541403,
"scripts": "73637269707473",
"timestamp": 1583268416080
},
"raw_data_hex": "0a02c75b18db8e9b0122087cfc890c6e4d7a1540b0a6b0a28a2e52046d656d6f5a68080112640a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412330a1541bf97a54f4b829c4e9253b26024b1829e1a3b1120121541859009fd225692b11237a6ffd8fdba2eb7140cca1880c2d72f62077363726970747370d0949b918a2e"
}
Note:
The information in the
data
andscripts
fields can be used as remark information (data
is displayed in TronScan), and the content can be obtained by converting Hex to String.
Updated almost 5 years ago