'use strict';
/**
* @module lib/operations/blockchains
* @summary Whiteflag API blockchains endpoints handler module
* @description Module with api blockchains endpoint handlers
* @tutorial modules
* @tutorial openapi
*/
module.exports = {
getBlockchains,
getBlockchain,
scanBlocks,
transferFunds,
getAccounts,
getAccount,
createAccount,
updateAccount,
deleteAccount,
createSignature
};
/* Common internal functions and classes */
const arr = require('../_common/arrays');
const { ProcessingError } = require('../_common/errors');
const { createBody,
sendImperative,
sendIndicative } = require('./_common/response');
/* Whiteflag modules */
const wfBlockchains = require('../blockchains');
const wfAuthenticate = require('../protocol/authenticate');
const wfState = require('../protocol/state');
/* MAIN MODULE FUNCTIONS */
/**
* Provides current state of all blockchains from state module
* @function getBlockchains
* @alias module:lib/operations/blockchains.getBlockchains
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function getBlockchains(req, res, operationId, callback) {
wfState.getBlockchains(function opsGetBlockchainsCb(err, blockchainsState = null) {
// Create response body and preserve information before responding
let resBody = createBody(req, operationId);
let resData = [];
// Send response using common endpoint response function
if (!err && !blockchainsState) err = new Error('Could not retrieve blockchains');
if (blockchainsState) {
resBody.meta.info = arr.addItem(resBody.meta.info, 'Current blockchains in state');
resData = Object.keys(blockchainsState);
}
return sendIndicative(res, err, resBody, resData, callback);
});
}
/**
* Provides current blockchain state from state module
* @function getBlockchain
* @alias module:lib/operations/blockchains.getBlockchain
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function getBlockchain(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
wfState.getBlockchainData(blockchain, function opsGetBlockchainCb(err, bcState = null) {
// Create response body and preserve information before responding
let resBody = createBody(req, operationId);
// Send response using common endpoint response function
if (!err && !bcState) err = new ProcessingError(`Blockchain ${blockchain} does not exist`, null, 'WF_API_NO_RESOURCE');
if (!err) {
resBody.meta.blockchain = blockchain;
resBody.meta.info = arr.addItem(resBody.meta.info, `Current ${blockchain} state`);
}
return sendIndicative(res, err, resBody, bcState, callback);
});
}
/**
* Scans a number of blocks for Whiteflag messages
* @function getBlockchain
* @alias module:lib/operations/blockchains.scanBlocks
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function scanBlocks(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
let resBody = createBody(req, operationId);
// Check query
let queryErrors = []
if (!req.query?.from) queryErrors.push('Query parameter \'from\' is missing');
if (!req.query?.to) queryErrors.push('Query parameter \'to\' is missing');
if (queryErrors.length > 0) {
const err = new ProcessingError('Request is not a valid query', queryErrors, 'WF_API_BAD_REQUEST');
return sendImperative(res, err, resBody, null, callback)
}
// Scan blocks
wfBlockchains.scanBlocks(Number(req.query.from), Number(req.query.to), blockchain, function opsScanBlocksCb(err, wfMessages = []) {
// Prepare response
let resData = [];
if (!err) {
resBody.meta.blockchain = blockchain;
resBody.meta.query = req.query;
resBody.meta.info = arr.addItem(resBody.meta.info, `Found ${wfMessages.length} Whiteflag messages on the blockchain between blocks ${req.query.from} and ${req.query.to}`);
resData = wfMessages;
}
// Send response using common endpoint response function
return sendIndicative(res, err, resBody, resData, callback);
});
}
/**
* Provides all blockchain accounts from state module
* @function getAccounts
* @alias module:lib/operations/blockchains.getAccounts
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function getAccounts(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
wfState.getBlockchainData(blockchain, function opsGetAccountsCb(err, bcState = null) {
// Create response body and preserve information before responding
let resData = [];
let resBody = createBody(req, operationId);
// Extract account data from blockchain state
if (!err && !bcState) err = new ProcessingError(`Blockchain ${blockchain} does not exist`, null, 'WF_API_NO_RESOURCE');
if (!err) resBody.meta.blockchain = blockchain;
if (!err && !bcState.accounts) err = new Error(`Could not retrieve accounts for ${blockchain}`);
if (!err) {
resData = arr.pluck(bcState.accounts, 'address') || [];
resBody.meta.info = arr.addItem(resBody.meta.info, `Blockchain accounts on ${blockchain}`);
}
// Send response using common endpoint response function
return sendIndicative(res, err, resBody, resData, callback);
});
}
/**
* Provides specific blockchain account from state module
* @function getAccount
* @alias module:lib/operations/blockchains.getAccount
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function getAccount(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
const address = req.params.account;
wfState.getBlockchainData(blockchain, function opsGetAccountCb(err, bcState = null) {
// Create response body and preserve information before responding
let resData = {};
let resBody = createBody(req, operationId);
// Get the requested account object
if (!err && !bcState) err = new ProcessingError(`Blockchain ${blockchain} does not exist`, null, 'WF_API_NO_RESOURCE');
if (!err) resBody.meta.blockchain = blockchain;
if (!err && !bcState.accounts) err = new Error(`Could not retrieve accounts for ${blockchain}`);
if (!err) {
const index = bcState.accounts.findIndex(
item => item.address.toLowerCase() === address.toLowerCase()
);
if (index < 0) {
err = new ProcessingError(`Blockchain account ${address} does not exist`, null, 'WF_API_NO_RESOURCE');
} else {
resBody.meta.account = address;
resBody.meta.info = arr.addItem(resBody.meta.info, 'Blockchain account details');
resData = bcState.accounts[index];
}
}
// Send response using common endpoint response function
return sendIndicative(res, err, resBody, resData, callback);
});
}
/**
* Creates account on specified blockchain
* @function createAccount
* @alias module:lib/operations/blockchains.createAccount
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function createAccount(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
let secret = req.body.secret || null;
wfBlockchains.createAccount(blockchain, secret, function opsCreateAccountCb(err, result) {
// Create response body and preserve information before responding
let resData = {};
let resBody = createBody(req, operationId);
// Check results
if (result?.address) {
resBody.meta.resource = `/blockchains/${blockchain}/accounts/${result.address}`;
} else if (!err) {
err = new Error('Could not determine address of created blockchain account');
}
if (!err) {
resBody.meta.blockchain = blockchain;
resBody.meta.account = result.address;
resBody.meta.info = arr.addItem(resBody.meta.info, `Created blockchain account on ${blockchain}`);
resData = result;
}
// Send response using common endpoint response function
return sendImperative(res, err, resBody, resData, callback);
});
secret = null;
}
/**
* Updates account on specified blockchain
* @function updateAccount
* @alias module:lib/operations/blockchains.updateAccount
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function updateAccount(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
const address = req.params.account;
const account = req.body;
wfBlockchains.updateAccount(account, address, blockchain, function opsUpdateAccountCb(err, result) {
// Create response body and preserve information before responding
let resData = {};
let resBody = createBody(req, operationId);
// Send response using common endpoint response function
if (!err) {
resBody.meta.blockchain = blockchain;
resBody.meta.account = address;
if (result) {
resBody.meta.info = arr.addItem(resBody.meta.info, `Updated blockchain account on ${blockchain}`);
resData = result;
} else {
resBody.meta.warnings = arr.addItem(resBody.meta.warnings, 'Did not receive result of account update');
}
}
return sendImperative(res, err, resBody, resData, callback);
});
}
/**
* Deletes account on specified blockchain
* @function deleteAccount
* @alias module:lib/operations/blockchains.deleteAccount
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function deleteAccount(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
const address = req.params.account;
wfBlockchains.deleteAccount(address, blockchain, function opsDeleteAccountCb(err, result) {
// Create response body and preserve information before responding
let resData = {};
let resBody = createBody(req, operationId);
// Send response using common endpoint response function
if (!err) {
resBody.meta.blockchain = blockchain;
resBody.meta.account = address;
if (result) {
resBody.meta.info = arr.addItem(resBody.meta.info, `Deleted lockchain account on ${blockchain}`);
resData = result;
} else {
resBody.meta.warnings = arr.addItem(resBody.meta.warnings, 'Did not receive result of account deletion');
}
}
return sendImperative(res, err, resBody, resData, callback);
});
}
/**
* Requests a Whiteflag authentication signature
* @function createSignature
* @alias module:lib/operations/blockchains.createSignature
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function createSignature(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
const address = req.params.account;
const signPayload = req.body;
wfAuthenticate.sign(signPayload, address, blockchain, function opsCreateSignatureCb(err, wfSignature, wfSignDecoded) {
// Create response body and preserve information before responding
let resBody = createBody(req, operationId);
// Send response using common endpoint response function
if (!err) {
resBody.meta.blockchain = blockchain;
resBody.meta.account = address;
if (!wfSignature || !wfSignDecoded) {
err = new Error('Did not receive valid signature for blockchain account');
}
}
if (wfSignDecoded) {
resBody.meta.info = arr.addItem(resBody.meta.info, `Signature for blockchain account on ${blockchain}`);
resBody.meta.decoded = wfSignDecoded;
}
return sendImperative(res, err, resBody, wfSignature, callback);
});
}
/**
* Transfers value from onse blockchain address to an other address
* @function transferFunds
* @alias module:lib/operations/blockchains.transferFunds
* @param {Object} req the http request
* @param {Object} res the http response
* @param {string} operationId the operation id as defined in the openapi definition
* @param {logEndpointEventCb} callback
*/
function transferFunds(req, res, operationId, callback) {
const blockchain = req.params.blockchain;
const address = req.params.account;
const transfer = req.body;
wfBlockchains.transferFunds(transfer, address, blockchain, function opsTransferFundsCb(err, txHash, blockNumber) {
// Create response body and preserve information before responding
let resData = {};
let resBody = createBody(req, operationId);
// Check results and send response using common endpoint response function
if (!err) {
resBody.meta.blockchain = blockchain;
resBody.meta.account = address;
resData = transfer;
if (txHash) {
resData.transactionHash = txHash;
resBody.meta.info = arr.addItem(resBody.meta.info, `Submitted transaction for a transfer on ${blockchain}`);
} else {
resBody.meta.warnings = arr.addItem(resBody.meta.warnings, 'Could not retrieve transaction hash');
}
if (blockNumber) resData.blockNumber = blockNumber;
}
return sendImperative(res, err, resBody, resData, callback);
});
}