Source: api/contracts/contractMethod.js

/**
 * @file Manages web3.eth.contract wrapper
 * @author Ivan Violentov <ivan.violentov@jibrel.network>
 */

import Promise from 'bluebird'

import config from '../../config'
import validate from '../../validation'
import checkWeb3 from '../../utils/checkWeb3'
import getContractInstance from './getContractInstance'
import getAddressFromPrivateKey from '../getAddressFromPrivateKey'

import {
  getEvents,
  subscribe,
} from '../../utils/eventUtils'

import * as txUtils from '../../utils/txUtils'

/**
 * @async
 * @function call
 *
 * @description Wrapper for callContractMethod function (@see callContractMethod)
 */
function call(payload) {
  return prepareContractInstanceMethod(payload).then(callContractMethod)
}

/**
 * @async
 * @function sendTransaction
 *
 * @description Wrapper for sendContractTransaction function (@see sendContractTransaction)
 */
function sendTransaction(payload) {
  return prepareContractInstanceMethod(payload).then(sendContractTransaction)
}

/**
 * @async
 * @function subscribeToEvent
 *
 * @description Wrapper for subscribeToContractEvent function (@see subscribeToContractEvent)
 */
function subscribeToEvent(payload) {
  return prepareContractInstanceMethod(payload).then(subscribeToContractEvent)
}

/**
 * @async
 * @function getPastEvents
 *
 * @description Wrapper for getPastContractEvents function (@see getPastContractEvents)
 */
function getPastEvents(payload) {
  return prepareContractInstanceMethod(payload).then(getPastContractEvents)
}

/**
 * @async
 * @function estimateGas
 *
 * @description Wrapper for estimateContractGas function (@see estimateContractGas)
 */
function estimateGas(payload) {
  return prepareContractInstanceMethod(payload).then(estimateContractGas)
}

function prepareContractInstanceMethod(payload) {
  return Promise
    .bind(this, payload)
    .then(validate)
    .then(checkWeb3)
    .then(getContractInstance)
}

/**
 * @async
 * @function callContractMethod
 *
 * @description Calls specific contract method with provided arguments
 *
 * @param {object} payload - Payload object
 * @param {object} payload.contractInstance - Contract instance
 * @param {string} payload.interfaceName - Interface name
 * @param {string} payload.method - Method name
 * @param {array} payload.args - Method arguments
 *
 * @returns Promise that will be resolved with the result of contract method execution
 */
function callContractMethod(payload) {
  const {
    args,
    method,
    interfaceName,
    contractInstance,
  } = payload

  return Promise
    .promisify(contractInstance[method].call)(...args)
    .timeout(
      config.promiseTimeout,
      new Error(`Can not call ${interfaceName}.${method} within ${config.promiseTimeout}ms`)
    )
}

/**
 * @async
 * @function sendContractTransaction
 *
 * @description Sends contract transaction
 *
 * @param {object} payload - Payload object
 * @param {object} payload.props - API function properties
 * @param {string} payload.props.privateKey - Private key (64 hex symbols, without '0x' prefix)
 * @param {object} payload.contractInstance - Contract instance
 * @param {string} payload.interfaceName - Interface name
 * @param {string} payload.method - Method name
 * @param {array} payload.args - Method arguments
 *
 * @returns Promise that will be resolved with the hash of the contract transaction
 */
async function sendContractTransaction(payload) {
  const {
    args,
    props,
    method,
    interfaceName,
    contractInstance,
  } = payload

  const { privateKey } = props
  const contractMethod = contractInstance[method]

  const address = getAddressFromPrivateKey(privateKey)

  // Extend contract method args. Add transaction object as last argument
  const transactionObject = { from: address }
  args.push(transactionObject)

  const rawTx = await txUtils.getContractRawTx({ props, address, contractMethod, args })
  const signedTx = txUtils.signTx(rawTx, privateKey)

  return Promise
    .promisify(jWeb3.eth.sendRawTransaction)(signedTx)
    .timeout(
      config.promiseTimeout,
      new Error(`Can not submit ${interfaceName}.${method} within ${config.promiseTimeout}ms`)
    )
}

/**
 * @function subscribeToContractEvent
 *
 * @description Subscribes to specific contract event
 *
 * @param {object} payload - Payload object
 * @param {object} payload.props - API function properties
 * @param {object} [payload.props.options] - Event options (@see subscribe)
 * @param {eventCallback} [payload.props.callback] - Event callback (@see subscribe)
 * @param {object} payload.contractInstance - Contract instance
 * @param {string} payload.method - Event name
 *
 * @returns {object} The event emitter (@see subscribe)
 */
function subscribeToContractEvent(payload) {
  const {
    props,
    method,
    contractInstance,
  } = payload

  const {
    options,
    callback,
  } = props

  return subscribe(contractInstance[method], options, callback)
}

/**
 * @async
 * @function getPastContractEvents
 *
 * @description Gets past contract events
 *
 * @param {object} payload - Payload object
 * @param {object} payload.props - API function properties
 * @param {object} [payload.props.options] - Event options (@see getEvents)
 *
 * @returns Promise that will be resolved with past events (@see getEvents)
 */
function getPastContractEvents(payload) {
  return getEvents(payload.props.options)
}

/**
 * @async
 * @function estimateContractGas
 *
 * @description Gets estimate gas for the contract transaction
 * Only one of the two params ({privateKey} or {from}) is required
 * {privateKey} has higher priority
 *
 * @param {object} payload - Payload object
 * @param {object} payload.props - API function properties
 * @param {string} payload.props.method - Contract method name
 * @param {array} payload.props.args - Contract method arguments
 * @param {string} payload.props.privateKey - Private key (64 hex symbols, without '0x' prefix)
 * @param {string} payload.props.from - Address from (42 hex symbols, with '0x' prefix)
 * @param {object} payload.contractInstance - Contract instance
 *
 * @returns Promise (@see getContractGasLimit)
 */
function estimateContractGas(payload) {
  const {
    props,
    contractInstance,
  } = payload

  const {
    args,
    from,
    method,
  } = props

  // Extend contract method args. Add transaction object as last argument
  const transactionObject = { from }
  args.push(transactionObject)

  return txUtils.estimateContractGas(contractInstance[method], args)
}

export default {
  call,
  estimateGas,
  getPastEvents,
  sendTransaction,
  subscribeToEvent,
}