UNPKG

bifcore-sdk-nodejs-bop

Version:
649 lines (619 loc) 26.1 kB
'use strict' const config = require('../common/constant') const is = require('is-type-of') const errors = require('../exception') const Transaction = require('../transaction') // const Account = require('../account') const BigNumber = require('bignumber.js') const keyPair = require('../keypair') const Query = require('../query') const Operation = require('../validate') const Snowflake = require('../common/snowFlakeUtils') const util = require('../common/util') class Contract { constructor(options = {}) { this.host = options.host this.apiKey = options.apiKey this.apiSecret = options.apiSecret } /** * Get contract information * @param {String} address * @return {Object} */ async getInfo (address, domainId) { if (!keyPair.isAddress(address) || address.trim().length === 0) { return util._responseError(errors.INVALID_ADDRESS_ERROR) } if (domainId != null && domainId !== '' && !util._isAvailableValue(domainId)) { return util._responseError(errors.INVALID_DOMAINID_ERROR) } if (domainId == null || domainId === '') { domainId = config.INIT_ZERO } let options = { host: this.host, apiKey: this.apiKey, apiSecret: this.apiSecret } const regex = /^[A-Z0-9]{32}$/ if (!regex.test(this.apiKey)) { return util._responseError(errors.INVALID_APIKEY_ERROR) } let query = new Query(options) return query.getContractBase(address, domainId) }; /** * call Contract * @param address * @returns {Promise<*>} */ async callContract (paramsJson) { let options = { host: this.host, apiKey: this.apiKey, apiSecret:this.apiSecret } let query = new Query(options) return query.callContract(paramsJson) }; /** * check Contract Address * @param request * @returns {Promise<boolean|{errorDesc: *, errorCode: *}>} */ async checkContractAddress (request = {}) { if (is.array(request) || !is.object(request) || arguments.length === 0) { return util._responseError(errors.REQUEST_NULL_ERROR) } let { contractAddress, domainId } = request if (!keyPair.isAddress(contractAddress)) { return util._responseError(errors.INVALID_CONTRACTADDRESS_ERROR) } if (domainId != null && domainId !== '' && !util._isAvailableValue(domainId)) { return util._responseError(errors.INVALID_DOMAINID_ERROR) } if (domainId == null || domainId === '') { domainId = config.INIT_ZERO } const regex = /^[A-Z0-9]{32}$/ if (!regex.test(this.apiKey)) { return util._responseError(errors.INVALID_APIKEY_ERROR) } let isValid = { isValid: false } let info = await this.getInfo(contractAddress, domainId) if ((info.errorCode == 0 || info.error_code == 0) && (info.result.contract == null || info.result.contract === '')) { isValid = { isValid: false } } else if ((info.errorCode == 0 || info.error_code == 0) && (info.result.contract.payload == null || info.result.contract.payload === '')) { isValid = { isValid: false } } else if (info.errorCode == 0 || info.error_code == 0) { isValid = { isValid: true } } if (info.errorCode === config.CONNECTNETWORK_ERROR) { return info } if (info.errorCode != 0 && info.errorCode != null) { return info } return util._responseData(isValid) } /** * get Contract Info * @param request * @returns {Promise<{contract: *}|{errorDesc: *, errorCode: *}>} */ async getContractInfo (request = {}) { if (is.array(request) || !is.object(request) || arguments.length === 0) { return util._responseError(errors.REQUEST_NULL_ERROR) } let { contractAddress, domainId } = request if (!keyPair.isAddress(contractAddress)) { return util._responseError(errors.INVALID_CONTRACTADDRESS_ERROR) } let info = await this.getInfo(contractAddress, domainId) if (info.error_code !== 0 || info.errorCode === config.CONNECTNETWORK_ERROR) { return info } if (info.result.contract == null || info.result.contract === '') { return util._responseError(errors.CONTRACTADDRESS_NOT_CONTRACTACCOUNT_ERROR) } if (info.result.contract.payload == null || info.result.contract.payload === '') { return util._responseError(errors.CONTRACTADDRESS_NOT_CONTRACTACCOUNT_ERROR) } let data = {contract: info.result.contract } return util._response(data) } /** * get Contract Address * @param request * @returns {Promise<{contract_address_infos: *}|{errorDesc: *, errorCode: *}>} */ async getContractAddress (request = {}) { if (is.array(request) || !is.object(request) || arguments.length === 0) { return util._responseError(errors.REQUEST_NULL_ERROR) } let { hash, domainId } = request if (!is.string(hash) || hash.trim().length != config.HASH_HEX_LENGTH) { return util._responseError(errors.INVALID_HASH_ERROR) } if (domainId != null && domainId !== '' && !util._isAvailableValue(domainId)) { return util._responseError(errors.INVALID_DOMAINID_ERROR) } if (domainId == null || domainId === '') { domainId = config.INIT_ZERO } let options = { host: this.host, apiKey: this.apiKey, apiSecret: this.apiSecret } const regex = /^[A-Z0-9]{32}$/ if (!regex.test(this.apiKey)) { return util._responseError(errors.INVALID_APIKEY_ERROR) } let query = new Query(options) let info = await query.getContractAddress(hash, domainId) if (info.error_code !== 0 || info.errorCode === config.CONNECTNETWORK_ERROR) { return info } let contractData = { contract_address: info.contract_address_infos[0].contract_address, operation_index: info.contract_address_infos[0].operation_index } let data = { contract_address_infos: contractData } return data } /** * create Contract * @param request * @returns {Promise<{errorDesc: *, errorCode: *}>} */ async createContract (request = {}) { if (is.array(request) || !is.object(request) || arguments.length === 0) { return util._responseError(errors.REQUEST_NULL_ERROR) } let { sourceAddress, privateKey, payload, initBalance, remarks, type, feeLimit, gasPrice, ceilLedgerSeq, initInput, domainId, nonceType } = request if (!util._verifyValue2(initBalance)) { return util._responseError(errors.INVALID_INITBALANCE_ERROR) } if (ceilLedgerSeq != null && ceilLedgerSeq !== '' && !util._isAvailableValue(ceilLedgerSeq)) { return util._responseError(errors.INVALID_CEILLEDGERSEQ_ERROR) } if (feeLimit != null && feeLimit !== '' && !util._isAvailableValue(feeLimit)) { return util._responseError(errors.INVALID_FEELIMIT_ERROR) } if (gasPrice != null && gasPrice !== '' && !util._isAvailableValue(gasPrice)) { return util._responseError(errors.INVALID_GASPRICE_ERROR) } if (domainId != null && domainId !== '' && !util._isAvailableValue(domainId)) { return util._responseError(errors.INVALID_DOMAINID_ERROR) } if(type != null && type !== '' && !(type === 0 || type === 1)) { return util._responseError(errors.INVALID_CONTRACT_TYPE_ERROR) } if(type == null || type === '') { type = 0 } if(nonceType != null && nonceType !== '' && !(nonceType === '0' || nonceType === '1')) { return util._responseError(errors.INVALID_NONCE_TYPE_ERROR) } if(nonceType == null || nonceType === '') { nonceType = 0 } const regex = /^[A-Z0-9]{32}$/ if (!regex.test(this.apiKey)) { return util._responseError(errors.INVALID_APIKEY_ERROR) } // 参数校验 let operation = new Operation() let data = operation.contractCreateOperation(request) if (data.errorCode !== 0) { return data } let options = { host: this.host, apiKey: this.apiKey, apiSecret: this.apiSecret } let transaction = new Transaction(options) if (!keyPair.isAddress(sourceAddress)) { return util._responseError(errors.INVALID_ADDRESS_ERROR) } let createContractOperation = { initBalance: initBalance, payload: payload, initInput: initInput, type: type } let operations = [createContractOperation] transaction.addOperation('createContract', operations) let nonce let maxLedgerSeq let blockNumberFind let query = new Query(options) if(nonceType == '1') { let {result: blockNumber} = await query.getBlockNumber(domainId) console.log('blockNumber',blockNumber) blockNumberFind = blockNumber maxLedgerSeq = Number(blockNumber.header.blockNumber) + Number(Snowflake.getDefaultRangeRandom()) nonce = Snowflake.randomLenNum().toString() } else { let nonceResult = await query.getNonce(sourceAddress, domainId) if (nonceResult.errorCode !== 0) { return nonceResult } nonce = nonceResult.result.nonce nonce = new BigNumber(nonce).plus(1).toString(10) } if (feeLimit == null || feeLimit === '') { feeLimit = config.feeLimit } if (gasPrice == null || gasPrice === '') { gasPrice = config.gasPrice } if (domainId == null || domainId === '') { domainId = config.INIT_ZERO } // 获取blockNumber if (ceilLedgerSeq != null && ceilLedgerSeq !== '') { let blockNumber = await this.getBlockNumber(domainId) ceilLedgerSeq = Number(ceilLedgerSeq) + Number(blockNumber.result.header.blockNumber) } let tranactionParameter = { sourceAddress: sourceAddress, nonce: nonce, feeLimit: feeLimit, gasPrice: gasPrice, seqOffset: ceilLedgerSeq, metadata: remarks, domainId: domainId, nonceType: nonceType, maxLedgerSeq: maxLedgerSeq } transaction.buildTransaction(tranactionParameter) let privateKeyArray = [privateKey] transaction.signTransaction(privateKeyArray) const result = await transaction.submitTransaction() if (result.results[0].error_code === 0) { return util._responseData({ hash: result.results[0].hash }) } return util._response(result.results[0]) } /** * contract Query * @param request * @returns {Promise<{header: *}|{errorDesc: *, errorCode: *}>} */ async contractQuery (request = {}) { if (is.array(request) || !is.object(request) || arguments.length === 0) { return util._responseError(errors.REQUEST_NULL_ERROR) } let { sourceAddress, contractAddress, input, feeLimit, gasPrice, domainId } = request if (sourceAddress != null && sourceAddress !== '' && !keyPair.isAddress(sourceAddress)) { return util._responseError(errors.INVALID_SOURCEADDRESS_ERROR) } if (!keyPair.isAddress(contractAddress)) { return util._responseError(errors.INVALID_CONTRACTADDRESS_ERROR) } if (sourceAddress === contractAddress) { return util._responseError(errors.SOURCEADDRESS_EQUAL_CONTRACTADDRESS_ERROR) } if (feeLimit != null && feeLimit !== '' && !util._isAvailableValue(feeLimit)) { return util._responseError(errors.INVALID_FEELIMIT_ERROR) } if (gasPrice != null && gasPrice !== '' && !util._isAvailableValue(gasPrice)) { return util._responseError(errors.INVALID_GASPRICE_ERROR) } if (domainId != null && domainId !== '' && !util._isAvailableValue(domainId)) { return util._responseError(errors.INVALID_DOMAINID_ERROR) } const regex = /^[A-Z0-9]{32}$/ if (!regex.test(this.apiKey)) { return util._responseError(errors.INVALID_APIKEY_ERROR) } // 参数校验 let operation = new Operation() let data = operation.contractQueryOperation(request) if (data.errorCode !== 0) { return data } if (feeLimit == null || feeLimit === '') { feeLimit = config.feeLimit } if (gasPrice == null || gasPrice === '') { gasPrice = config.gasPrice } if (domainId == null || domainId === '') { domainId = config.INIT_ZERO } let contractQueryOperation = { source_address: sourceAddress, contract_address: contractAddress, opt_type: config.contract_query_type, input: input, gas_price: gasPrice, fee_limit: feeLimit, domain_id: domainId } let info = await this.callContract(contractQueryOperation) if (info.error_code !== 0 || info.errorCode === config.CONNECTNETWORK_ERROR) { return info } let result = { query_rets: info.result.query_rets } return result } /** * contract Invoke * @param request * @returns {Promise<{header: *}|{errorDesc: *, errorCode: *}>} */ async contractInvoke (request = {}) { if (is.array(request) || !is.object(request) || arguments.length === 0) { return util._responseError(errors.REQUEST_NULL_ERROR) } let { sourceAddress, privateKey, feeLimit, gasPrice, contractAddress, remarks, amount, ceilLedgerSeq, input, domainId , nonceType } = request if (ceilLedgerSeq != null && ceilLedgerSeq !== '' && !util._isAvailableValue(ceilLedgerSeq)) { return util._responseError(errors.INVALID_CEILLEDGERSEQ_ERROR) } if (feeLimit != null && feeLimit !== '' && !util._isAvailableValue(feeLimit)) { return util._responseError(errors.INVALID_FEELIMIT_ERROR) } if (gasPrice != null && gasPrice !== '' && !util._isAvailableValue(gasPrice)) { return util._responseError(errors.INVALID_GASPRICE_ERROR) } if (domainId != null && domainId !== '' && !util._isAvailableValue(domainId)) { return util._responseError(errors.INVALID_DOMAINID_ERROR) } if (nonceType != null && nonceType !== '' && !util._isAvailableValue(nonceType)) { nonceType = config.INIT_ZERO } if (nonceType != '0' && nonceType != '1') { return util._responseError(errors.INVALID_NONCE_TYPE_ERROR) } const regex = /^[A-Z0-9]{32}$/ if (!regex.test(this.apiKey)) { return util._responseError(errors.INVALID_APIKEY_ERROR) } // 参数校验 let operation = new Operation() let data = operation.contractInvokeOperation(request) if (data.errorCode !== 0) { return data } let options = { host: this.host, apiKey: this.apiKey, apiSecret:this.apiSecret } if (this.apiSecret === null) { return util._responseError(errors.ITEM_HEADER_MUST_API_SECRET) } let transaction = new Transaction(options) if (!keyPair.isAddress(request.sourceAddress)) { return util._responseError(errors.INVALID_ADDRESS_ERROR) } // 调用合约账户接口判断是否为合约账户 let contract = new Contract(options) let paramContractObject = { contractAddress, domainId } let isContractAddress = await contract.checkContractAddress(paramContractObject) if (isContractAddress.result == false) { return util._responseError(errors.CONTRACTADDRESS_NOT_CONTRACTACCOUNT_ERROR) } let contractInvoke = { to: contractAddress, amount: amount, input: input } let operations = [contractInvoke] transaction.addOperation('payCoin', operations) let nonce let maxLedgerSeq let blockNumberFind let query = new Query(options) if(nonceType == '1') { let {result: blockNumber} = await query.getBlockNumber(domainId) blockNumberFind = blockNumber maxLedgerSeq = Number(blockNumber.header.blockNumber) + Number(Snowflake.getDefaultRangeRandom()) nonce = Snowflake.randomLenNum().toString() } else { let nonceResult = await query.getNonce(sourceAddress, domainId) if (nonceResult.errorCode !== 0) { return nonceResult } nonce = nonceResult.result.nonce nonce = new BigNumber(nonce).plus(1).toString(10) } if (feeLimit == null || feeLimit === '') { feeLimit = config.feeLimit } if (gasPrice == null || gasPrice === '') { gasPrice = config.gasPrice } if (domainId == null || domainId === '') { domainId = config.INIT_ZERO } // 获取blockNumber if (ceilLedgerSeq != null && ceilLedgerSeq !== '') { let blockNumber = await this.getBlockNumber(domainId) ceilLedgerSeq = Number(ceilLedgerSeq) + Number(blockNumber.result.header.blockNumber) } let tranactionParameter = { sourceAddress: sourceAddress, nonce: nonce, feeLimit: feeLimit, gasPrice: gasPrice, metadata: remarks, seqOffset: ceilLedgerSeq, domainId: domainId, nonceType: nonceType, maxLedgerSeq: maxLedgerSeq } transaction.buildTransaction(tranactionParameter) let privateKeyArray = [privateKey] transaction.signTransaction(privateKeyArray) const result = await transaction.submitTransaction() if (result.results[0].error_code === 0) { return util._responseData({ hash: result.results[0].hash }) } return util._response(result.results[0]) } /** * getBlockNumber * @param request * @returns {Promise<{errorDesc: *, errorCode: *}>} */ async getBlockNumber (domainId) { let options = { host: this.host, apiKey: this.apiKey, apiSecret:this.apiSecret } let query = new Query(options) return query.getBlockNumber(domainId) } /** * contract Invoke * @param request * @returns {Promise<{header: *}|{errorDesc: *, errorCode: *}>} */ async batchContractInvoke (request = {}) { if (is.array(request) || !is.object(request) || arguments.length === 0) { return util._responseError(errors.REQUEST_NULL_ERROR) } let { senderAddress, privateKey, feeLimit, gasPrice, remarks, ceilLedgerSeq, domainId, operations, nonceType } = request if (ceilLedgerSeq != null && ceilLedgerSeq !== '' && !util._isAvailableValue(ceilLedgerSeq)) { return util._responseError(errors.INVALID_CEILLEDGERSEQ_ERROR) } if (feeLimit != null && feeLimit !== '' && !util._isAvailableValue(feeLimit)) { return util._responseError(errors.INVALID_FEELIMIT_ERROR) } if (gasPrice != null && gasPrice !== '' && !util._isAvailableValue(gasPrice)) { return util._responseError(errors.INVALID_GASPRICE_ERROR) } if (domainId != null && domainId !== '' && !util._isAvailableValue(domainId)) { return util._responseError(errors.INVALID_DOMAINID_ERROR) } if (operations == null || operations === '' || operations.length == 0) { return util._responseError(errors.OPERATIONS_EMPTY_ERROR) } if (nonceType != null && nonceType !== '' && !util._isAvailableValue(nonceType)) { nonceType = config.INIT_ZERO } if (nonceType != '0' && nonceType != '1') { return util._responseError(errors.INVALID_NONCE_TYPE_ERROR) } const regex = /^[A-Z0-9]{32}$/ if (!regex.test(this.apiKey)) { return util._responseError(errors.INVALID_APIKEY_ERROR) } // 参数校验 let operation = new Operation() let data = operation.contractInvokeRequestOperation(request) if (data.errorCode !== 0) { return data } let options = { host: this.host, apiKey: this.apiKey, apiSecret:this.apiSecret } if (this.apiSecret === null) { return util._responseError(errors.ITEM_HEADER_MUST_API_SECRET) } let transaction = new Transaction(options) if (!keyPair.isAddress(request.senderAddress)) { return util._responseError(errors.INVALID_ADDRESS_ERROR) } // 参数验证及合约账号校验 let operationsArr = [] let map = new Map() for (var key in operations) { var contractAddress = operations[key].contractAddress if (map.has(contractAddress)) { map.set(contractAddress, operations[key]) } else { let tmpList = [] tmpList.push(operations[key]) if (!keyPair.isAddress(contractAddress)) { return util._responseError(errors.INVALID_ADDRESS_ERROR) } // 调用合约账户接口判断是否为合约账户 let contract = new Contract(options) let paramContractObject = { contractAddress, domainId } let isContractAddress = await contract.checkContractAddress(paramContractObject) if (isContractAddress.result == false) { return util._responseError(errors.CONTRACTADDRESS_NOT_CONTRACTACCOUNT_ERROR) } map.set(contractAddress, tmpList) } let amount = operations[key].amount if (amount == null || amount === '' || !util._isAvailableValue(amount)) { return util._responseError(errors.INVALID_GAS_AMOUNT_ERROR) } let contractInvokeParam = { to: contractAddress, amount: amount, input: operations[key].input } operationsArr.push(contractInvokeParam) } transaction.addOperation('payCoin', operationsArr) let nonce let maxLedgerSeq let blockNumberFind let query = new Query(options) if(nonceType == '1') { let {result: blockNumber} = await query.getBlockNumber(domainId) blockNumberFind = blockNumber maxLedgerSeq = Number(blockNumber.header.blockNumber) + Number(Snowflake.getDefaultRangeRandom()) nonce = Snowflake.randomLenNum().toString() } else { let nonceResult = await query.getNonce(senderAddress, domainId) if (nonceResult.errorCode !== 0) { return nonceResult } nonce = nonceResult.result.nonce nonce = new BigNumber(nonce).plus(1).toString(10) } if (feeLimit == null || feeLimit === '') { feeLimit = config.feeLimit } if (gasPrice == null || gasPrice === '') { gasPrice = config.gasPrice } if (domainId == null || domainId === '') { domainId = config.INIT_ZERO } // 获取blockNumber if (ceilLedgerSeq != null && ceilLedgerSeq !== '') { let blockNumber = await this.getBlockNumber(domainId) ceilLedgerSeq = Number(ceilLedgerSeq) + Number(blockNumber.result.header.blockNumber) } let tranactionParameter = { sourceAddress: senderAddress, nonce: nonce, feeLimit: feeLimit, gasPrice: gasPrice, metadata: remarks, seqOffset: ceilLedgerSeq, domainId: domainId, nonceType: nonceType, maxLedgerSeq: maxLedgerSeq } transaction.buildTransaction(tranactionParameter) let privateKeyArray = [privateKey] transaction.signTransaction(privateKeyArray) const result = await transaction.submitTransaction() if (result.results[0].error_code === 0) { return util._responseData({ hash: result.results[0].hash }) } return util._response(result.results[0]) } } module.exports = Contract