# How to send a transaction

# Step1: Construct

{
  "ref_block_num": 54701, // an exist block num
  "ref_block_prefix": 2861949695, // The exist block id
  "expiration": "2019-01-18T03:35:54", // transaction expiration date, max expiration = current + 24hours
  "operations": [
   [
      0, // operation id
      { // operation
        "fee": {
          "amount": 1179,
          "asset_id": "1.3.1"
        },
        "from": "1.2.937396",
        "to": "1.2.19645",
        "amount": {
          "amount": 173600000,
          "asset_id": "1.3.1"
        },
        "memo": {
          "from": "GXC6cUP6LvdpcfC9G4TMre4yxB3PxttUjVK1ybybgt63ZtEKCXamC", // memo_key of from account
          "to": "GXC7o71VExYFoFJKtduFXEF15jgPdbmC1tdyT8BPCpnzCTeFiXEog", // memo_key of to account
          "nonce": 1, // nonce of encryption key
          "message": "15e06e19346d8f9c2d5355abcf5fffaa" // ECIES encrypted message
        },
        "extensions": []
      }
    ]
  ],
  "extensions": [],
  "signatures": []
}

From the above transaction structure, we can see that the whole transaction is mainly divided into three parts: block Header, operations and signatures.

The block header contains 3 fields:

  • ref_block_num = head_block_num & 0xFFFF (head_block_num mod 65535)
  • ref_block_prefix = hex2Num(head_block_id.substring(12,14)+head_block_id.substring(10,12)+head_block_id.substring(8,10))

head_block_num and ref_block_prefix can be obtained by `get_dynamic_global_properties

curl -XPOST --data '{
    "jsonrpc": "2.0",
    "method": "call",
    "params": [0, "get_dynamic_global_properties", []],
    "id": 1
}' https://node1.gxb.io/rpc

Reference:

The above transaction is not signed, such a transaction cannot be broadcast on the blockchain before we do Serialize and Signature

# Step2: Serialize a transaction

There are a total of 76 different transaction structures on the GXChain. The transaction serialization uses a custom Protocol Buffer protocol to implement serialization (toByteBuffer) and deserialization (fromByteBuffer) of the following Basic Types:

type description
uint8 Single-byte unsigned integer
uint16 2-byte unsigned integer
uint32 4-byte unsigned integer
uint64 8-byte unsigned integer
int64 8-byte integer
bool Boolean
string String
bytes byte array
array Array type for custom types
protocol_id_type protocol object id type on gxchain(eg. 1.3.1 and 1.2.1)
vote_id vote type id on gxchain (eg. 0:11 and 1:13)
map a map of key-value
set collection type (the difference with array is that only number and string types are supported in a set)
public_key public key string type
time_point_second timestamp(eg. "2019-01-18T03:35:54")
name_type base32 encoded string
optional use to describe optional and nullable field
future_extensions extension features(usually an empty array)

Let's take a look at the transfer operation serializer, which is defined like this:

export const transfer = new Serializer (
    "transfer",
    {
        fee: asset,
        from: protocol_id_type ("account"),
        to: protocol_id_type ("account"),
        amount: asset,
        memo: optional (memo_data),
        extensions: set (future_extensions)
    }
);

The Serializer instance provides fromBuffer and toBuffer methods, which can serialize and deserialize a JSON according to the defined structure. Serializers can be nested with each other. During the serialization process, recursive serialization is performed when a non-basic type is encountered. For example, the memo field in the transfer message body is defined as follows:

export const memo_data = new Serializer (
    "memo_data",
    {
        from: public_key,
        to: public_key,
        nonce: uint64,
        message: bytes ()
    }
);

For details on serialization and deserialization, you can refer to the following source code:

The process of serialization is complicated in the process of transaction encapsulation. Don't worry, we have prepared ready-made lib for you.tx_serializer (opens new window)

tx_serializer.js Provides 2 methods:

  1. Smart contract parameter serialization
var serializeCallData = function serializeCallData(action, params, abi)
  1. Transaction serialization
var serializeTransaction = function serializeTransaction(transaction)

So the problem returns to how to call tx_serializer.js in the program to serialize the transaction body. You can refer to the implementation of gxclient-ios:

First we construct a JS execution environment (jsContext), load tx_serializer.js

+(JSContext*)jsContext{
    static dispatch_once_t onceToken;
    static JSContext* instance;
    dispatch_once(&onceToken, ^{
        NSBundle* bundle = [NSBundle bundleForClass:[GXUtil class]];
        NSString * path = [bundle pathForResource:@"gxclient.bundle/tx_serializer.min" ofType:@"js"];
        NSData * jsData = [[NSData alloc]initWithContentsOfFile:path];
        NSString * jsCode = [[NSString alloc]initWithData:jsData encoding:NSUTF8StringEncoding];
        instance=[[JSContext alloc] init];
        [instance evaluateScript:jsCode];
    });

    return instance;
}

Then we can call the two methods of jsContext to serialize the contract call parameters and transactions.

+(NSString*) serialize_action_data:(NSString*)action params:(NSDictionary*)params abi:(NSDictionary*)abi{
    NSString* jsCode = [NSString stringWithFormat:@"serializer.serializeCallData('%@',%@,%@).toString('hex')",action, [params json],[abi json]];
    NSString* result = [[[GXUtil jsContext] evaluateScript:jsCode] toString];
    return result;
}

+(NSString*) serialize_transaction:(NSDictionary*)transaction{
    NSString* jsCode = [NSString stringWithFormat:@"serializer.serializeTransaction(%@).toString('hex')", [transaction json]];
    NSString* result = [[[GXUtil jsContext] evaluateScript:jsCode] toString];
    return result;
}

Since now we have compress a json formatted transaction into a byte array, the next step is to sign the byte array with your private key.

# Step3: Sign a transaction

let tx_buffer = serializer.serializeTransaction(tx);
let privateKey = ecc.PrivateKey.fromWif(private_key);
let signature = ecc.Signature.signBuffer(tx_buffer, privateKey).toHex();
tx.signatures=[signature];

So we got the signed transaction

{
  "ref_block_num": 54701, // an exist block num
  "ref_block_prefix": 2861949695, // The exist block id
  "expiration": "2019-01-18T03:35:54", // transaction expiration date, max expiration = current + 24hours
  "operations": [
   [
      0, // operation id
      { // operation
        "fee": {
          "amount": 1179,
          "asset_id": "1.3.1"
        },
        "from": "1.2.937396",
        "to": "1.2.19645",
        "amount": {
          "amount": 173600000,
          "asset_id": "1.3.1"
        },
        "memo": {
          "from": "GXC6cUP6LvdpcfC9G4TMre4yxB3PxttUjVK1ybybgt63ZtEKCXamC", // memo_key of from account
          "to": "GXC7o71VExYFoFJKtduFXEF15jgPdbmC1tdyT8BPCpnzCTeFiXEog", // memo_key of to account
          "nonce": 1, // nonce of encryption key
          "message": "15e06e19346d8f9c2d5355abcf5fffaa" // ECIES encrypted message
        },
        "extensions": []
      }
    ]
  ],
  "extensions": [],
  "signatures": [
    "1b4a899ab4831ee6c0e7bb3825cfbbf47947d5861671cd8f8e6c61436c7f71b0336622e98b2e29b1fa1f00ca697baa1a797e280c619cc04bab1645db19c87f78aa"
  ]
}

# Step4: Broadcast a transaction

By calling broadcast_transaction_synchronous, we can push the transaction to the entry point and broadcast it to GXChain network.

curl --data '{
    "jsonrpc": "2.0",
    "method": "call",
    "params": [2,"broadcast_transaction_synchronous",[tx]],
    "id": 1
}' https://node23.gxb.io/rpc

# Code Reference

bepalcore-java (opens new window) bepalcore-oc (opens new window)