Source: api/contracts/contractMethod.js

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

import Promise from 'bluebird'

import getContractInstance from './getContractInstance'

import getAddressFromPrivateKey from '../getAddressFromPrivateKey'

import validate from '../../validation'

import checkWeb3 from '../../utils/checkWeb3'
import { getEvents, subscribe } from '../../utils/eventUtils'
import { signTx, getContractRawTx, getContractGasLimit } from '../../utils/txUtils'

import config from '../../config'

/**
 * @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 { contractInstance, interfaceName, method, args } = 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 { contractInstance, interfaceName, method, props, args } = 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 getContractRawTx({ props, address, contractMethod, args })
  const signedTx = 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, contractInstance, method } = 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 {string} payload.props.event - Event name
 * @param {object} [payload.props.options] - Event options (@see getEvents)
 * @param {object} payload.contractInstance - Contract instance
 *
 * @returns Promise that will be resolved with past events (@see getEvents)
 */
function getPastContractEvents(payload) {
  const { props, contractInstance } = payload
  const { event, options } = props

  return getEvents(contractInstance[event], options)
}

/**
 * @async
 * @function estimateContractGas
 *
 * @description Gets estimate gas for the contract transaction
 *
 * @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 {object} payload.contractInstance - Contract instance
 *
 * @returns Promise (@see getContractGasLimit)
 */
function estimateContractGas(payload) {
  const { contractInstance, props } = payload
  const { method, args, privateKey } = props

  const address = getAddressFromPrivateKey(privateKey)

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

  return getContractGasLimit(contractInstance[method], args)
}

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