Advanced SDK Concepts

Creating a prepared transaction

The FIO SDK offers some helper functions for integrators who wish to separate the creation of the transaction from the sending of the transaction. This is helpful if you want to have the ability to inspect the transaction prior to submitting it or if you want to have the ability to re-submit the same transaction in cases where the initial transaction send fails.

In the Typescript SDK, when preparing a transaction, setting setSignedTrxReturnOption to true will cause calls to the genericAction action method to return a prepared transaction without submitting it to the chain. Once the prepared transaction has been captured, it can then be sent to the chain using executePreparedTrx.

The following is an example using setSignedTrxReturnOption and executePreparedTrx to create and send a prepared transaction. The full working example can be found in the fiosdk_typescript-examples repo.

const transferFioPreparedTxn = async () => {

  const user = new FIOSDK(
    privateKey,
    publicKey,
    baseUrl,
    fetchJson
  );

  let preparedTrx;

  user.setSignedTrxReturnOption(true);
  preparedTrx = await user.genericAction('pushTransaction', {
    action: 'trnsfiopubky',
    account: 'fio.token',
    data: {
      payee_public_key: payeeKey,
      amount: amount,
      max_fee: max_fee,
      tpid: ''
    }
  });

  const result = await user.executePreparedTrx('transfer_tokens_pub_key', preparedTrx);
  user.setSignedTrxReturnOption(false);
};

Offline signing of transactions

For integrators who want to limit access to FIO private keys, the FIO SDK offers methods that separate the serialization and signing of transactions.

This fiosdk_typescript example demonstrates how to use the FIO Javascript SDK to enable offline signing of transactions. First, it creates a serialized transaction without requiring any FIO keys. It then passes the serialized transaction to the sign method to generate the signature.

const {FIOSDK } = require('@fioprotocol/fiosdk')
fetch = require('node-fetch')
const createHash = require('create-hash');
const properties = require('./properties.js')

const fetchJson = async (uri, opts = {}) => {
  return fetch(uri, opts)
}

const baseUrl = properties.server + '/v1/'

const privateKey = properties.privateKey,
  publicKey = properties.publicKey,
  payeeKey = '',  // FIO Public Key of the payee
  amount = 1000000000,
  max_fee = 100000000000


const main = async () => {

  user = new FIOSDK(
      '',
      '',
      baseUrl,
      fetchJson
  )

  const chainData = await user.transactions.getChainDataForTx();

  const transaction = await user.transactions.createRawTransaction({
      action: 'trnsfiopubky',
      account: 'fio.token',
      data: {
          payee_public_key: payeeKey,
          amount: amount,
          max_fee: max_fee,
          tpid: ''
      },
      publicKey,
      chainData,
  });

  const { serializedContextFreeData, serializedTransaction } = await user.transactions.serialize({
      chainId: chainData.chain_id,
      transaction,
  });
  
  // Pre-compute transaction ID
  const txnId = createHash('sha256').update(serializedTransaction).digest().toString('hex');

  const signedTransaction = await user.transactions.sign({
      chainId: chainData.chain_id,
      privateKeys: [privateKey],
      transaction,
      serializedTransaction,
      serializedContextFreeData,
  });

  const result = await user.executePreparedTrx('transfer_tokens_pub_key', signedTransaction);
}

main();

Pre-compute transaction ID

Pre-computing the transaction ID for a FIO transaction is useful for integrators wanting to confirm transactions on the FIO chain. To calculate the transaction_id for a FIO transaction prior to sending it to the blockchain, you perform a SHA-256 hash of the packed_trx.

For example, here is a typical transaction you might pass to push_transaction:

txn:  { signatures:
   [ 'SIG_K1_Km62xn9thv3LYQv356PJMj9bP5ZwHRWZ2CgGan75sbcMfeZ7gtLrD1yukDiLgmdPVLZV3tpH4FW4A96ZKs5U42uAsnuyDb' ],
  compression: 0,
  packed_context_free_data: '',
  packed_trx:
   '1958cb60285764a002ba0000000001003056372503a85b0000c6eaa6645232013059393021cea2d800000000a8ed32326812656274657374314066696f746573746e657402034243480342434818626974636f696e636173683a617364666173646661736466044441534804444153481764617368616464726573736173646661736466617364660046c323000000003059393021cea2d80000' }

The packed_trx field contains the serialized transaction. The "Offline signing of transactions" code above provides an example of how to pre-compute the transaction ID from this serialized transaction.

const txnId = createHash('sha256').update(serializedTransaction).digest().toString('hex');

You can also manually view the pre-computed transaction ID by plugging the packed_trx hex into the Binary hash field of a calculator and checking the SHA-256 result.

Pre-compute transaction ID lookup example

Using fiojs to package and send transactions

The FIO SDK wraps the fiosjs library to provide convenience methods for creating and submitting transactions to the FIO chain. Integrators who want to limit the number of third party libraries they embed or who want lower level access to FIO functionality in their applications can access the fiojs library directly in their applications.

This fiojs example demonstrates how to use the fiojs library to create a trnsfiopubky transaction on the FIO chain:

const { Fio } = require('@fioprotocol/fiojs');
const { TextEncoder, TextDecoder } = require('text-encoding');
const fetch = require('node-fetch');
const properties = require('./properties.js')

const httpEndpoint = properties.server

const privateKey = properties.privateKey,
  publicKey = properties.publicKey,
  account = properties.account,
  payeeKey = '',  // FIO Public Key of the payee
  amount = 1000000000,
  maxFee = 100000000000

const fiojsTrnsfiopubky = async () => {
  info = await (await fetch(httpEndpoint + '/v1/chain/get_info')).json();
  blockInfo = await (await fetch(httpEndpoint + '/v1/chain/get_block', {body: `{"block_num_or_id": ${info.last_irreversible_block_num}}`, method: 'POST'})).json()
  chainId = info.chain_id;
  currentDate = new Date();
  timePlusTen = currentDate.getTime() + 10000;
  timeInISOString = (new Date(timePlusTen)).toISOString();
  expiration = timeInISOString.substr(0, timeInISOString.length - 1);

  transaction = {
    expiration,
    ref_block_num: blockInfo.block_num & 0xffff,
    ref_block_prefix: blockInfo.ref_block_prefix,
    actions: [{
      account: 'fio.token',
      name: 'trnsfiopubky',
      authorization: [{
        actor: account,
        permission: 'active',
      }],
      data: {
        payee_public_key: payeeKey,
        amount: amount,
        max_fee: maxFee,
        tpid: '',
        actor: account
      }
    }]
  };

  abiMap = new Map()
  tokenRawAbi = await (await fetch(httpEndpoint + '/v1/chain/get_raw_abi', {body: `{"account_name": "fio.token"}`, method: 'POST'})).json()
  abiMap.set('fio.token', tokenRawAbi)
 
  var privateKeys = [privateKey];
  
  const tx = await Fio.prepareTransaction({
    transaction,
    chainId,
    privateKeys,
    abiMap,
    textDecoder: new TextDecoder(),
    textEncoder: new TextEncoder()
  });

  pushResult = await fetch(httpEndpoint + '/v1/chain/push_transaction', {
      body: JSON.stringify(tx),
      method: 'POST',
  });
  
  json = await pushResult.json();

  if (json.type) {
    console.log('Error: ', json.fields[0].error);
  } else {
    console.log('Success. Transaction ID: ', json.transaction_id)
  }
   
};

fiojsTrnsfiopubky();