Software SDK

Software SDK

To interact with HEAT blockchain natives we use https://github.com/heatcrypto/heat-sdk-v2, heat-sdk-v2 offers all native operations available on HEAT blockchain.

To name a few:

  • building, signing and parsing binary transactions
  • crypto native operations (signing, validating, encryption/decryption)
  • supports all available transaction types

To use heat-sdk-v2 in your code use it directly from github like this.

/* package.json */

{
  "name": "heat_app_js",
  "version": "0.1.0",
  "description": "Js crypto routines for Heat Wallet app",
  "main": "index.js",
  "engines": {
    "node": ">=0.12"
  },
  "scripts": {
    "build": "npm run test && node build.js",
    "test": "mocha --recursive"
  },
  "author": "me",
  "license": "UNLICENSED",
  "browser": {
    "stream": "stream-browserify",
    "crypto": "crypto-browserify",
    "big.js": "./node_modules/big.js/big.js",
    "curve25519": "./node_modules/curve25519/dist/curve25519.js",
    "cryptojs": "./node_modules/cryptojs/dist/CryptoJs.js"
  },
  "dependencies": {
    "big.js": "dmdeklerk/big.js#master",
    "buffer": "6.0.3",
    "crypto-browserify": "3.12.0",
    "cryptojs": "heatcrypto/CryptoJs#master",
    "curve25519": "heatcrypto/curve25519#master",
    "heat-sdk-v2": "heatcrypto/heat-sdk-v2#master",  // <-- Use from github.com
    "stream-browserify": "3.0.0"
  }
}

Get Public Key From Private Key

This example shows how to obtain a public key from a private key.

const { getPublicKeyFromPrivateKey } = require('heat-sdk-v2/dist/crypto')

/**
 * @param {{
 *   privateKeyAsHex: string,
 * }} params
 * @returns string
 */
function HEAT_GET_PUBLICKEY_FROM_PRIVATEKEY(params) {
  const { privateKeyAsHex } = params;
  return getPublicKeyFromPrivateKey(privateKeyAsHex)
}

module.exports = { HEAT_GET_PUBLICKEY_FROM_PRIVATEKEY }

Get Address From Public Key

This example shows how to obtain an address from a public key

const { getAccountIdFromPublicKey } = require('heat-sdk-v2/dist/crypto')

/**
 * @param {{
 *   publicKeyAsHex: string,
 * }} params
 * @returns string
 */
function HEAT_GET_ADDRESS_FROM_PUBLICKEY(params) {
  const { publicKeyAsHex } = params;
  return getAccountIdFromPublicKey(publicKeyAsHex)
}

module.exports = { HEAT_GET_ADDRESS_FROM_PUBLICKEY }

Transfer HEAT Transaction

This example shows how to build and sign a HEAT transfer

const { HeatSDK, Configuration, Builder, attachment, Transaction } = require("heat-sdk-v2")
const isString = require("lodash/isString")
const isBoolean = require('lodash/isBoolean')

/**
 * @typedef {"prod" | "test"} NetworkType
 */

/**
 * @param {{
 *  key: string, 
 *  recipientAddress: string, 
 *  recipientPublicKey: string, 
 *  amount: string, 
 *  fee: string, 
 *  networkType: ('prod' | 'test' | null), 
 *  message: string, 
 *  messageIsPrivate: boolean, 
 *  messageIsBinary: boolean
 * }} params
 * @returns hex string
 */
function HEAT_TRANSFER_HEAT(params) {
  const { key, recipientAddress, recipientPublicKey, amount, fee, networkType, message, messageIsPrivate, messageIsBinary, } = params
  return transferHeat(key, recipientAddress, recipientPublicKey, amount, fee, networkType, message, messageIsPrivate, messageIsBinary,)
}

/**
 * Create a transaction to transfer HEAT.
 * 
 * @param {String} key 
 * @param {String | null} recipientAddress 
 * @param {String | null} recipientPublicKey 
 * @param {String} amount 
 * @param {String | null} fee 
 * @param {'prod' | 'test' | null} networkType 
 * @param {String | null} message 
 * @param {Boolean | null} messageIsPrivate 
 * @param {Boolean | null} messageIsBinary 
 * 
 * @returns bytes HEX string
 */
 function transferHeat(key, recipientAddress, recipientPublicKey, amount, fee, networkType, message, messageIsPrivate, messageIsBinary) {
  if (!isString(key)) throw new Error(`Key arg should be "String"`)
  if (!isValidAddress(recipientAddress)) throw new Error(`recipientAddress arg should be "String"`)
  if (recipientPublicKey && !isString(recipientPublicKey)) throw new Error(`recipientPublicKey arg should be "String"`)
  if (!isString(amount) && !isNaN(Number(amount)) && Number(amount) > 0) throw new Error(`amount arg should be "String"`)
  if (!isString(fee) && !isNaN(Number(fee)) && Number(fee) > 0) throw new Error(`fee arg should be "String"`)
  if (!isString(networkType)) throw new Error(`networkType arg should be "String"`)
  if (message && !isString(message)) throw new Error(`message arg should be "String"`)
  if (!isBoolean(messageIsPrivate)) throw new Error(`messageIsPrivate arg should be "Boolean"`)
  if (!isBoolean(messageIsBinary)) throw new Error(`messageIsBinary arg should be "Boolean"`)

  const isTestnet = networkType == 'test' ? true : false
  const sdk = new HeatSDK(new Configuration({ isTestnet: isTestnet }))
  const recipientAddressOrPublicKey = (isString(recipientPublicKey) ? recipientPublicKey : recipientAddress)
  let builder = new Builder()
    .isTestnet(sdk.config.isTestnet)
    .genesisKey(sdk.config.genesisKey)
    .attachment(attachment.ORDINARY_PAYMENT)
    .amountHQT(amount)
    .feeHQT(fee)
  let txn = new Transaction(sdk, recipientAddressOrPublicKey, builder)
  if (message) {
    txn = messageIsPrivate ? txn.privateMessage(message, messageIsBinary) : txn.publicMessage(message, messageIsBinary)
  }
  return txn.sign(key).then(t => {
    let transaction = t.getTransaction()
    let bytes = transaction.getBytesAsHex()
    return bytes
  })
}

function isValidAddress(value) {
  return isString(value) && !isNaN(Number(value)) && Number(value) != 0
}

module.exports = { HEAT_TRANSFER_HEAT }

Transfer Asset Transaction

This example shows how to build and sign an Asset Transfer

const { HeatSDK, Configuration, Builder, attachment, Transaction } = require("heat-sdk-v2")
const isString = require("lodash/isString")
const isBoolean = require('lodash/isBoolean')

/**
 * @param {{
 *  key: string, 
 *  recipientAddress: string, 
 *  recipientPublicKey: string, 
 *  amount: string, 
 *  fee: string, 
 *  networkType: "prod" | "test", 
 *  asset: string, 
 *  message: string, 
 *  messageIsPrivate: boolean, 
 *  messageIsBinary: boolean
 * }} params
 * @returns hex string
 */
function HEAT_TRANSFER_ASSET(params) {
  const { key, recipientAddress, recipientPublicKey, amount, fee, networkType, asset, message, messageIsPrivate, messageIsBinary } = params
  return transferAsset(key, recipientAddress, recipientPublicKey, amount, fee, networkType, asset, message, messageIsPrivate, messageIsBinary)
}

/**
 * Create a transaction to transfer HEAT.
 * 
 * @param {String} key 
 * @param {String | null} recipientAddress 
 * @param {String | null} recipientPublicKey 
 * @param {String} amount 
 * @param {String | null} fee 
 * @param {'prod' | 'test' | null} networkType 
 * @param {String} asset
 * @param {String | null} message 
 * @param {Boolean | null} messageIsPrivate 
 * @param {Boolean | null} messageIsBinary 
 * 
 * @returns bytes HEX string
 */
 function transferAsset(key, recipientAddress, recipientPublicKey, amount, fee, networkType, asset, message, messageIsPrivate, messageIsBinary) {
  if (!isString(key)) throw new Error(`Key arg should be "String"`)
  if (!isValidAddress(recipientAddress)) throw new Error(`recipientAddress arg should be "String"`)
  if (recipientPublicKey && !isString(recipientPublicKey)) throw new Error(`recipientPublicKey arg should be "String"`)
  if (!isString(amount) && !isNaN(Number(amount)) && Number(amount) > 0) throw new Error(`amount arg should be "String"`)
  if (!isString(fee) && !isNaN(Number(fee)) && Number(fee) > 0) throw new Error(`fee arg should be "String"`)
  if (!isString(networkType)) throw new Error(`networkType arg should be "String"`)
  if (!isValidAddress(asset)) throw new Error(`asset arg not valid should be "String"`)
  if (message && !isString(message)) throw new Error(`message arg should be "String"`)
  if (!isBoolean(messageIsPrivate)) throw new Error(`messageIsPrivate arg should be "Boolean"`)
  if (!isBoolean(messageIsBinary)) throw new Error(`messageIsBinary arg should be "Boolean"`)

  const isTestnet = networkType == 'test' ? true : false
  const sdk = new HeatSDK(new Configuration({isTestnet:isTestnet}))
  const recipientAddressOrPublicKey = (isString(recipientPublicKey) ? recipientPublicKey : recipientAddress)
  let builder = new Builder()
    .isTestnet(sdk.config.isTestnet)
    .genesisKey(sdk.config.genesisKey)
    .attachment(new attachment.AssetTransfer().init(asset, amount))
    .amountHQT("0")
    .feeHQT(fee)
  let txn = new Transaction(sdk, recipientAddressOrPublicKey, builder)
  if (message) {
    txn = messageIsPrivate ? txn.privateMessage(message, messageIsBinary) : txn.publicMessage(message, messageIsBinary)
  }
  return txn.sign(key).then(t => {
    let transaction = t.getTransaction()
    let bytes = transaction.getBytesAsHex()
    return bytes
  }) 
}

function isValidAddress(value) {
  return isString(value) && !isNaN(Number(value)) && Number(value) != 0
}

module.exports = { HEAT_TRANSFER_ASSET }

Parse Raw Transaction Bytes

This example shows how to parse raw transaction bytes

const { byteArrayToHexString } = require("heat-sdk-v2/dist/converters");
const { calculateFullHash, calculateTransactionId } = require("heat-sdk-v2/dist/crypto");
const { TransactionImpl } = require("heat-sdk-v2/dist/builder");
const { isObject, find } = require("lodash");

/**
 * @param {{
 *   hex: string,
 *   isTestnet: boolean
 * }} params
 * @returns {{
 *   id?: string,
 *   amount: string,
 *   fee: string,
 *   recipient: string,
 *   deadline: number
 *   isTestnet: boolean,
 *   assetId?: string,
 *   message?: string,
 * }}
 */
function HEAT_PARSE_TRANSACTION(params) {
  const { hex, isTestnet } = params;
  return parseTransaction(hex, isTestnet);
}

/**
 * @param {string} hex
 * @param {boolean} _isTestnet
 * @returns {{
 *   id?: string,
 *   amount: string,
 *   fee: string,
 *   recipient: string,
 *   deadline: number
 *   isTestnet: boolean,
 *   assetId?: string,
 *   message?: string,
 *   senderPublicKey?: string,
 * }}
 */
function parseTransaction(hex, _isTestnet) {
  const transaction = TransactionImpl.parse(hex, _isTestnet);
  const amount = transaction.amountHQT;
  const fee = transaction.feeHQT;
  const recipient = transaction.recipientId;
  const deadline = transaction.deadline;
  const isTestnet = transaction.isTestnet;
  const message = transaction.message;
  const transactionId = getTransactionId(transaction.getUnsignedBytes(), transaction.signature);
  const senderPublicKey = byteArrayToHexString(transaction.senderPublicKey);

  // @ts-ignore
  const assetTransfer = getAssetTransfer(transaction);
  return {
    id: transactionId,
    amount: assetTransfer ? assetTransfer.quantity : amount,
    fee,
    recipient,
    deadline,
    isTestnet,
    assetId: assetTransfer ? assetTransfer.assetId : null,
    message,
    senderPublicKey,
  };
}

/**
 * @param {{
 *  appendages: [{
 *    assetId:any,
 *    quantity: any
 *  }]
 * }} transaction
 * @returns {{
 *   assetId:string,
 *   quantity: string
 * }}
 */
function getAssetTransfer(transaction) {
  const appendage = find(
    transaction.appendages,
    (attachment) =>
      isObject(attachment.assetId) && isObject(attachment.quantity)
  );
  if (appendage) {
    return {
      assetId: appendage.assetId.toUnsigned().toString(),
      quantity: appendage.quantity.toString(),
    };
  }
}

/**
 * @param {Buffer} unsignedBytes 
 * @param {Buffer} signture 
 * @returns {String}
 */
function getTransactionId(unsignedBytes, signture) {
  const unsignedBytesHex = byteArrayToHexString(unsignedBytes)
  const signatureHex = byteArrayToHexString(signture)
  const fullHash = calculateFullHash(unsignedBytesHex, signatureHex)
  return calculateTransactionId(fullHash)
}

module.exports = { HEAT_PARSE_TRANSACTION };