bifcore-sdk-nodejs-bop
Version:
bifcore sdk nodejs
1,013 lines (972 loc) • 39.8 kB
JavaScript
'use strict'
const rp = require('request-promise')
const signer = require('../signer')
const long = require('long')
const protoChain = require('../protobuf/chain_pb')
const operations = require('./operations')
const Operation = require('../validate')
const crypto = require('crypto')
const keypair = require('../keypair')
const Query = require('../query')
const BigNumber = require('bignumber.js')
const errors = require('../exception')
const is = require('is-type-of')
const config = require('../common/constant')
const util = require('../common/util')
const Operaction = require('../operation')
const keyPair = require('../keypair')
const OperactionType = new Operaction()
const Snowflake = require('../common/snowFlakeUtils')
const ContractInvokeOperation = require('../operation/contractInvokeOperation')
class Transaction {
constructor(options = {}) {
this.host = options.host
this.apiKey = options.apiKey
this.apiSecret = options.apiSecret
this.operations = []
this.blob = ''
this.poolType = 1
this.hash = ''
this.signatures = []
}
/**
* Add operation
*
* @param {String} type
* @param {Object} options
*/
addOperation (type, option = {}) {
let param = option
for (var i = 0; i < param.length; i++) {
const option = param[i]
switch (type) {
case 'payCoin':
this.operations.push(operations.payCoin(option))
break
case 'setMetadata':
this.operations.push(operations.setMetadata(option))
break
case 'activateAccount':
this.operations.push(operations.activateAccount(option))
break
case 'setPrivilegeOperation':
this.operations.push(operations.accountSetPrivilegeOperation(option))
break
case 'createContract':
this.operations.push(operations.createContract(option))
break
default:
throw new Error('unknown operation')
}
}
}
/**
* gasSend
* @param request
* @returns {Promise<{header: *}|{errorDesc: *, errorCode: *}>}
*/
async gasSend (request = {}) {
if (is.array(request) || !is.object(request) || arguments.length === 0) {
return util._responseError(errors.REQUEST_NULL_ERROR)
}
let { sourceAddress, privateKey, destAddress, remarks, amount, ceilLedgerSeq, gasPrice, feeLimit, 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 (amount == null || amount === '' || !util._isAvailableValue(amount)) {
return util._responseError(errors.INVALID_GAS_AMOUNT_ERROR)
}
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.SendOperation(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)
}
if (!keypair.isAddress(sourceAddress)) {
return util._responseError(errors.INVALID_ADDRESS_ERROR)
};
let gasSend = {
to: destAddress,
amount: amount
}
var transaction = new Transaction(options)
let operations = [gasSend]
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()
// console.log('transBlob:', this.blob)
if (result.results[0].error_code === 0) {
return util._responseData({
hash: result.results[0].hash
})
}
return util._response(result.results[0])
}
async getTransactionInfo (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
}
const regex = /^[A-Z0-9]{32}$/
if (!regex.test(this.apiKey)) {
return util._responseError(errors.INVALID_APIKEY_ERROR)
}
let options = {
host: this.host,
apiKey: this.apiKey,
apiSecret:this.apiSecret
}
let query = new Query(options)
let info = await query.getTransactionByHash(hash, domainId)
if (info.error_code !== 0) {
return info
}
return util._responseData({
total_count: info.result.total_count,
transactions: info.result.transactions || {}
})
}
async evaluateFee (request = {}) {
if (is.array(request) || !is.object(request) || arguments.length === 0) {
return util._responseError(errors.REQUEST_NULL_ERROR)
}
let { sourceAddress, privateKey, gasPrice, feeLimit, operations, domainId, nonceType } = request
if (!keyPair.isAddress(sourceAddress)) {
return util._responseError(errors.INVALID_ADDRESS_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 (domainId == null || domainId === '') {
domainId = config.INIT_ZERO
}
if(nonceType != null && nonceType !== '' && !(nonceType === '0' || nonceType === '1')) {
return util._responseError(errors.INVALID_NONCE_TYPE_ERROR)
}
if(nonceType == null || nonceType === '') {
nonceType = 0
}
if (!is.object(operations) || operations == null || operations === '') {
return util._responseError(errors.OPERATIONS_EMPTY_ERROR)
}
const regex = /^[A-Z0-9]{32}$/
if (!regex.test(this.apiKey)) {
return util._responseError(errors.INVALID_APIKEY_ERROR)
}
let options = {
host: this.host,
apiKey: this.apiKey,
apiSecret:this.apiSecret
}
let nonce
let maxLedgerSeq
let query = new Query(options)
if(nonceType == '1') {
let {result: blockNumber} = await query.getBlockNumber(domainId)
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)
}
let actionType = Object.getOwnPropertyDescriptor(request.operations, 'operationType').value
// 封装请求
let privateKeyArray = [privateKey]
let operation = [operations]
let transaction = new Transaction(options)
// 构建对象
let itemOperations = await transaction.buildOperation(actionType, operation)
let item = {
items: [
{
private_keys: privateKeyArray,
transaction_json: {
domain_id: domainId,
fee_limit: feeLimit,
gas_price: gasPrice,
source_address: sourceAddress,
nonce: nonce,
operations: [itemOperations],
nonce_type: nonceType == '1' ? 'RANDOM_NONCE' : 'INCREASE_NONCE',
max_ledger_seq: maxLedgerSeq
},
signature_number: 1
}
]
}
let info = await query.evaluateFee(item)
if (info.error_code !== 0) {
return info
}
var hop = info.result.txs[0].transaction_env.transaction
return util._responseData({
feeLimit: Object.prototype.hasOwnProperty.call(hop, 'fee_limit') ? hop.fee_limit : 0,
gasPrice: Object.prototype.hasOwnProperty.call(hop, 'gas_price') ? hop.gas_price : 0
})
}
async batchEvaluateFee (request = {}) {
if (is.array(request) || !is.object(request) || arguments.length === 0) {
return util._responseError(errors.REQUEST_NULL_ERROR)
}
let { sourceAddress, privateKey, gasPrice, feeLimit, operations, domainId, nonceType } = request
if (!keyPair.isAddress(sourceAddress)) {
return util._responseError(errors.INVALID_ADDRESS_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 (domainId == null || domainId === '') {
domainId = config.INIT_ZERO
}
if (operations == null || operations === '' || operations.length == 0) {
return util._responseError(errors.OPERATIONS_EMPTY_ERROR)
}
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 options = {
host: this.host,
apiKey: this.apiKey,
apiSecret:this.apiSecret
}
let transaction = new Transaction(options)
let nonce
let maxLedgerSeq
let query = new Query(options)
if(nonceType == '1') {
let {result: blockNumber} = await query.getBlockNumber(domainId)
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)
}
var operationNumber = []
for (var i = 0; i < request.operations.length; i++) {
let contractInvokeOperation = new ContractInvokeOperation()
contractInvokeOperation.setContractAddress(operations[i].contractAddress)
contractInvokeOperation.setAmount(operations[i].amount)
contractInvokeOperation.setInput(operations[i].input)
contractInvokeOperation.setOperationType(contractInvokeOperation.getOperationType())
contractInvokeOperation.setActionType(contractInvokeOperation.getActionType())
var actionType = Object.getOwnPropertyDescriptor(contractInvokeOperation, 'operationType').value
operationNumber.push(contractInvokeOperation)
}
// 封装请求
let privateKeyArray = [privateKey]
// 构建对象
let itemOperations = await transaction.buildOperation(actionType, operationNumber)
let item = {
items: [
{
private_keys: privateKeyArray,
transaction_json: {
fee_limit: feeLimit,
gas_price: gasPrice,
source_address: sourceAddress,
nonce: nonce,
operations: [itemOperations],
domain_id: domainId,
nonce_type: nonceType == '1' ? 'RANDOM_NONCE' : 'INCREASE_NONCE',
max_ledger_seq: maxLedgerSeq
},
signature_number: 1
}
]
}
let info = await query.evaluateFee(item)
console.log(info)
if (info.error_code !== 0) {
return info
}
// var hop = info.result.txs[0].transaction_env.transaction_env
var hop = info.result.hash
return util._responseData({
feeLimit: Object.prototype.hasOwnProperty.call(hop, 'fee_limit') ? hop.fee_limit : 0,
gasPrice: Object.prototype.hasOwnProperty.call(hop, 'gas_price') ? hop.gas_price : 0
})
}
async buildOperation (type, option = {}) {
let param = option
for (var i = 0; i < param.length; i++) {
let option = param[i]
let operation, item
switch (type) {
case 'ACCOUNT_ACTIVATE': {
operation = OperactionType.accountCreateOperation
operation = option
item = {
create_account: {
dest_address: operation.getDestAddress(),
init_balance: operation.getInitBalance(),
priv: {
master_weight: 1,
thresholds: {
tx_threshold: 1
}
}
},
type: operation.getActionType()
}
break
}
case 'ACCOUNT_SET_METADATA': {
operation = OperactionType.accountSetMetadataOperation
operation = option
item = {
set_metadata: {
key: operation.getKey(),
value: operation.getValue(),
version: operation.getVersion()
},
type: operation.getActionType()
}
break
}
case 'ACCOUNT_SET_PRIVILEGE': {
operation = OperactionType.accountSetPrivilegeOperation
operation = option
item = {
set_privilege: {
master_weight: operation.getMasterWeight(),
signers: operation.getSigners(),
tx_threshold: operation.getTxThreshold(),
type_thresholds: operation.getTypeThresholds()
},
type: operation.getActionType()
}
break
}
case 'GAS_SEND': {
operation = OperactionType.gasSendOperation
operation = option
item = {
pay_coin: {
dest_address: operation.getDestAddress(),
amount: operation.getAmount()
},
type: operation.getActionType()
}
break
}
case 'CONTRACT_CREATE': {
operation = OperactionType.contractCreateOperation
operation = option
item = {
create_account: {
contract: {
payload: operation.getPayload()
},
init_balance: operation.getInitBalance(),
init_input: operation.getInitInput(),
priv: {
master_weight: 0,
thresholds: {
tx_threshold: 1
}
}
},
type: operation.getActionType()
}
break
}
case 'CONTRACT_INVOKE': {
operation = OperactionType.contractInvokeOperation
operation = option
item = {
pay_coin: {
dest_address: operation.getContractAddress(),
amount: operation.getAmount(),
input: operation.getInput()
},
type: operation.getActionType()
}
break
}
default: {
throw new Error('unknown operation')
}
}
return item
}
}
/**
* Build transaction
*
* @param {Object} args
* @param {String} args.sourceAddress
* @param {String} args.nonce
* @param {String} [args.feeLimit]
* @param {String} [args.gasPrice]
* @param {String} [args.seq]
* @param {String} [args.metadata]
* @returns {string}
*/
buildTransaction (args = {}) {
if (this.operations.length === 0) {
throw new Error('must add operation first')
}
const {
sourceAddress,
nonce,
feeLimit,
gasPrice,
seqOffset,
metadata,
domainId,
nonceType,
maxLedgerSeq
} = args
const tx = new protoChain.Transaction()
tx.setSourceAddress(sourceAddress)
if (nonce !== 0 && nonce !== '0') {
tx.setNonce(long.fromValue(nonce))
}
if (feeLimit !== 0 && feeLimit !== '0') {
tx.setFeeLimit(long.fromValue(feeLimit))
}
if (gasPrice !== 0 && gasPrice !== '0') {
tx.setGasPrice(long.fromValue(gasPrice))
}
if (seqOffset) {
tx.setCeilLedgerSeq(long.fromValue(seqOffset))
}
if (metadata) {
tx.setMetadata(Uint8Array.from(Buffer.from(metadata, 'utf8')))
}
if (domainId) {
tx.setDomainId(Number(domainId))
}
if (nonceType && nonceType != '0') {
tx.setNonceType(nonceType)
}
if (maxLedgerSeq) {
tx.setMaxLedgerSeq(Number(maxLedgerSeq))
}
tx.setOperationsList(this.operations)
const blob = Buffer.from(tx.serializeBinary()).toString('hex')
this.blob = blob
this.hash = crypto.createHash('sha256')
.update(
new Buffer(blob, 'hex'))
.digest('hex')
}
/**
* Sign transaction
*
* @param {Array} privateKeys
*/
signTransaction (privateKeys) {
this.signatures = this.signTransSerialization(privateKeys, this.blob)
}
signTransSerialization (privateKeys, transBlob) {
if (!Array.isArray(privateKeys)) {
throw new Error('privateKeys must be an array')
}
if (transBlob == null || transBlob === '' || util._containsInvalidUnicodeCharacters(transBlob)) {
return util._responseError(errors.INVALID_SERIALIZATION_ERROR)
}
if (transBlob.length === 0) {
throw new Error('serialization is required')
}
const signatures = []
privateKeys.forEach(privateKey => {
if (!keypair.isPrivateKey(privateKey)) {
throw new Error(`'${privateKey}' is not privateKey`)
}
signatures.push({
sign_data: signer.sign(transBlob, privateKey),
public_key: keypair.getEncPublicKey(privateKey)
})
})
return signatures
}
/**
* Get signed transaction information
*
* @returns {Object}
* @public
*/
getSignedTransactionInfo () {
if (this.blob.length === 0) {
throw new Error('serialization is required')
}
if (this.signatures.length === 0) {
throw new Error('signatures is required')
}
const blob = this.blob
const signatures = this.signatures
let postData = {
items: [{
transaction_blob: blob,
signatures: signatures
}]
}
return postData
}
async submitTransaction () {
return this.submitTrans(this.blob, this.signatures)
}
async submitTrans (txBlob, signatures) {
if (txBlob == null || txBlob === '' || util._containsInvalidUnicodeCharacters(txBlob)) {
return util._responseError(errors.INVALID_SERIALIZATION_ERROR)
}
if (!Array.isArray(signatures) || signatures.length === 0) {
return util._responseError(errors.ERRCODE_SIGNATURE_ERROR)
}
const blob = txBlob
let postData = {
items: [{
transaction_blob: blob,
signatures: signatures
}]
}
const options = {
method: 'POST',
uri: `${this.host}/base/${this.apiKey}/submitTransaction`,
body: postData,
headers: {
'Content-Type': 'application/json', // header
'api-secret': `${this.apiSecret}`, // Another custom header
},
json: true
}
return rp(options)
}
async BIFSubmit (request = {}) {
if (is.array(request) || !is.object(request) || arguments.length === 0) {
return util._responseError(errors.REQUEST_NULL_ERROR)
}
const regex = /^[A-Z0-9]{32}$/
if (!regex.test(this.apiKey)) {
return util._responseError(errors.INVALID_APIKEY_ERROR)
}
let { serialization, signatures } = request
if (serialization == null || serialization === '' || util._containsInvalidUnicodeCharacters(serialization)) {
return util._responseError(errors.INVALID_SERIALIZATION_ERROR)
}
if (!Array.isArray(signatures) || signatures.length === 0) {
return util._responseError(errors.ERRCODE_SIGNATURE_ERROR)
}
const blob = serialization
let postData = {
items: [{
transaction_blob: blob,
signatures: signatures
}]
}
const options = {
method: 'POST',
uri: `${this.host}/base/${this.apiKey}/submitTransaction`,
body: postData,
headers: {
'Content-Type': 'application/json', // header
'api-secret': `${this.apiSecret}`, // Another custom header
},
json: true
}
return rp(options)
}
/**
* 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)
}
async getTxCacheSize (domainId) {
if (typeof (domainId) == 'undefined' || domainId == null || domainId === '') {
domainId = config.INIT_ZERO
}
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 options = {
host: this.host,
apiKey: this.apiKey,
apiSecret:this.apiSecret
}
let query = new Query(options)
return query.getTxCacheSize(domainId)
}
async getTransactionListByQuery (request) {
let { page, page_size } = request;
if (typeof (page) == 'undefined' || page == null || page === '') {
page = config.page
}
if (typeof (page_size) == 'undefined' || page_size == null || page_size === '') {
page_size = config.pageSize
}
const regex = /^[A-Z0-9]{32}$/
if (!regex.test(this.apiKey)) {
return util._responseError(errors.INVALID_APIKEY_ERROR)
}
let options = {
host: this.host,
apiKey: this.apiKey,
apiSecret:this.apiSecret
}
let query = new Query(options)
// 根据入参匹配路由
let params = util.getRoute({...request,...{page: page,page_size: page_size}})
return query.getTransactionListByQuery(params)
}
async getTxCacheData (request = {}) {
let { domainId, hash, poolType } = request
poolType = 1
if (typeof (domainId) == 'undefined' || domainId == null || domainId === '') {
domainId = config.INIT_ZERO
}
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)
}
if (typeof (hash) != 'undefined' && hash != null && hash !== '') {
if (!is.string(hash) || hash.trim().length != config.HASH_HEX_LENGTH) {
return util._responseError(errors.INVALID_HASH_ERROR)
}
} else {
hash = ''
}
let options = {
host: this.host,
apiKey: this.apiKey,
poolType: this.poolType,
apiSecret:this.apiSecret
}
let query = new Query(options)
return query.getTransactionCache(domainId, hash,poolType)
}
async parseBlob (blob) {
if (blob == null || blob === '' || blob.trim().length === 0) {
return util._responseError(errors.INVALID_SERIALIZATION_ERROR)
}
const regex = /^[A-Z0-9]{32}$/
if (!regex.test(this.apiKey)) {
return util._responseError(errors.INVALID_APIKEY_ERROR)
}
try {
const buffer = Buffer.from(blob, 'hex')
const tx = protoChain.Transaction.deserializeBinary(buffer)
const buf = Buffer.from(tx.getMetadata_asU8(), 'hex')
const meta = buf.toString('utf8')
var operationsListData = tx.toObject().operationsList
var newoperationsListData = []
operationsListData.map((item, index) => {
let operData
let newValue = ''
let keyValue = ''
switch (item.type) {
case 0:
newValue = 'UNKNOWN'
break
case 1:
newValue = 'CREATE_ACCOUNT'
keyValue = 'createAccount'
operData = item.createAccount
break
case 4:
newValue = 'SET_METADATA'
keyValue = 'setMetadata'
operData = item.setMetadata
break
case 7:
newValue = 'PAY_COIN'
keyValue = 'payCoin'
operData = {
destAddress: item.payCoin.destAddress,
amount: item.payCoin.amount,
input: item.payCoin.input
}
break
case 9:
newValue = 'SET_PRIVILEGE'
keyValue = 'setPrivilege'
operData = item.setPrivilege
break
default:
throw new Error('unknown type')
}
newoperationsListData.push({
type: newValue,
[keyValue]: operData
})
})
if (tx.toObject().domainId != 0) {
return util._response({
source_address: tx.toObject().sourceAddress,
fee_limit: tx.toObject().feeLimit,
gas_price: tx.toObject().gasPrice,
nonce: tx.toObject().nonce,
domain_id: tx.toObject().domainId,
remarks: meta,
operations: newoperationsListData
})
} else {
return util._response({
source_address: tx.toObject().sourceAddress,
fee_limit: tx.toObject().feeLimit,
gas_price: tx.toObject().gasPrice,
nonce: tx.toObject().nonce,
remarks: meta,
operations: newoperationsListData
})
}
} catch (e) {
return util._responseError(errors.INVALID_SERIALIZATION_ERROR)
}
}
/**
* batchGasSend
* @param request
* @returns {Promise<{header: *}|{errorDesc: *, errorCode: *}>}
*/
async batchGasSend (request = {}) {
if (is.array(request) || !is.object(request)) {
return util._responseError(errors.REQUEST_NULL_ERROR)
}
const regex = /^[A-Z0-9]{32}$/
if (!regex.test(this.apiKey)) {
return util._responseError(errors.INVALID_APIKEY_ERROR)
}
let { senderAddress, privateKey, feeLimit, gasPrice, remarks, ceilLedgerSeq, domainId, operations, nonceType } = request
if (operations == null || operations === '' || operations.length == 0) {
return util._responseError(errors.OPERATIONS_EMPTY_ERROR)
}
if(operations.length > 100) {
return util._responseError(errors.OPERATIONS_INVALID_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(nonceType != null && nonceType !== '' && !(nonceType === '0' || nonceType === '1')) {
return util._responseError(errors.INVALID_NONCE_TYPE_ERROR)
}
if(nonceType == null || nonceType === '') {
nonceType = 0
}
// 参数校验
let operation = new Operation()
let data = operation.GasSendRequestOperation(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 destAddress = operations[key].destAddress
if (!map.has(destAddress)) {
if (!keypair.isAddress(destAddress)) {
return util._responseError(errors.INVALID_ADDRESS_ERROR)
}
map.set(destAddress, 1)
}
let amount = operations[key].amount
if (amount == null || amount === '' || !util._isAvailableValue(amount)) {
return util._responseError(errors.INVALID_AMOUNT_ERROR)
}
let gasSendParam = {
to: destAddress,
amount: amount
}
operationsArr.push(gasSendParam)
}
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 = Transaction