btc-rpc-explorer
Version:
Open-source, self-hosted Bitcoin explorer
1,100 lines (766 loc) • 27.9 kB
JavaScript
"use strict";
const debug = require("debug");
const debugLog = debug("btcexp:router");
const express = require('express');
const router = express.Router();
const util = require('util');
const moment = require('moment');
const qrcode = require('qrcode');
const bitcoinjs = require('bitcoinjs-lib');
const sha256 = require("crypto-js/sha256");
const hexEnc = require("crypto-js/enc-hex");
const { bech32, bech32m } = require("bech32");
const Decimal = require("decimal.js");
const asyncHandler = require("express-async-handler");
const markdown = require("markdown-it")();
const coins = require("./../app/coins.js");
const config = require("./../app/config.js");
const utils = require('./../app/utils.js');
const coreApi = require("./../app/api/coreApi.js");
const addressApi = require("./../app/api/addressApi.js");
const xyzpubApi = require("./../app/api/xyzpubApi.js");
const rpcApi = require("./../app/api/rpcApi.js");
const apiDocs = require("./../docs/api.js");
const btcQuotes = require("./../app/coins/btcQuotes.js");
router.get("/docs", function(req, res, next) {
res.locals.apiDocs = apiDocs;
res.locals.apiBaseUrl = apiDocs.baseUrl;
res.locals.route = req.query.route;
res.locals.categories = [];
apiDocs.routes.forEach(x => {
let category = x.category;
if (!res.locals.categories.find(y => (y.name == category))) {
res.locals.categories.push({name:category, items:[]});
}
res.locals.categories.find(x => (x.name == category)).items.push(x);
});
res.render("api-docs");
next();
});
router.get("/changelog", function(req, res, next) {
res.locals.changelogHtml = markdown.render(global.apiChangelogMarkdown);
res.render("api-changelog");
next();
});
router.get("/version", function(req, res, next) {
res.send(apiDocs.version);
next();
});
/// BLOCKS
router.get("/blocks/tip", asyncHandler(async (req, res, next) => {
try {
const getblockchaininfo = await coreApi.getBlockchainInfo();
res.send({
height: getblockchaininfo.blocks,
hash: getblockchaininfo.bestblockhash
});
} catch (e) {
utils.logError("a39gfoeuew", e);
res.json({success: false});
}
next();
}));
router.get("/block/:hashOrHeight", asyncHandler(async (req, res, next) => {
const hashOrHeight = req.params.hashOrHeight;
let hash = (hashOrHeight.length == 64 ? hashOrHeight : null);
try {
if (hash == null) {
hash = await coreApi.getBlockHashByHeight(parseInt(hashOrHeight));
}
const block = await coreApi.getBlockByHash(hash);
res.json(block);
} catch (e) {
utils.logError("w9fgeddsuos", e);
res.json({success: false});
}
next();
}));
router.get("/block/header/:hashOrHeight", asyncHandler(async (req, res, next) => {
const hashOrHeight = req.params.hashOrHeight;
let hash = (hashOrHeight.length == 64 ? hashOrHeight : null);
try {
if (hash == null) {
hash = await coreApi.getBlockHashByHeight(parseInt(hashOrHeight));
}
const block = await coreApi.getBlockHeaderByHash(hash);
res.json(block);
} catch (e) {
utils.logError("w8kwqpoauns", e);
res.json({success: false});
}
next();
}));
/// TRANSACTIONS
router.get("/tx/:txid", asyncHandler(async (req, res, next) => {
let txid = utils.asHash(req.params.txid);
let promises = [];
let txInputLimit = (res.locals.crawlerBot) ? 3 : -1;
try {
let results = await coreApi.getRawTransactionsWithInputs([txid], txInputLimit);
let outJson = results.transactions[0];
let txInputs = results.txInputsByTransaction[txid] || {};
let inputBtc = 0;
if (txInputs[0]) {
for (let key in txInputs) {
let item = txInputs[key];
inputBtc += item["value"] * global.coinConfig.baseCurrencyUnit.multiplier;
outJson.vin[key].scriptSig.address = item.scriptPubKey.address;
outJson.vin[key].scriptSig.type = item.scriptPubKey.type;
outJson.vin[key].value = item.value;
}
}
let outputBtc = 0;
for (let key in outJson.vout) {
let item = outJson.vout[key];
outputBtc += item.value * global.coinConfig.baseCurrencyUnit.multiplier;
}
outJson.fee = {
"amount": (inputBtc - outputBtc) / global.coinConfig.baseCurrencyUnit.multiplier,
"unit": "BTC"
};
if (outJson.confirmations == null) {
outJson.mempool = await coreApi.getMempoolTxDetails(txid, false);
}
if (global.specialTransactions && global.specialTransactions[txid]) {
let funInfo = global.specialTransactions[txid];
outJson.fun = funInfo;
}
res.json(outJson);
} catch(err) {
utils.logError("10328fwgdaqw", err);
res.json({success:false, error:err});
}
next();
}));
router.get("/tx/volume/24h", function(req, res, next) {
try {
if (networkVolume && networkVolume.d1 && networkVolume.d1.amt) {
let currencyValue = parseInt(networkVolume.d1.amt);
res.json({"24h": currencyValue});
} else {
res.json({success:false, error: "Volume data not yet loaded."});
}
next();
} catch (err) {
utils.logError("39024y484", err);
res.json({success:false, error:err});
next();
}
});
/// BLOCKCHAIN
router.get("/blockchain/coins", asyncHandler(async (req, res, next) => {
if (global.utxoSetSummary) {
let supply = parseFloat(global.utxoSetSummary.total_amount).toString();
res.send({
supply: supply.toString(),
type: "calculated"
});
next();
} else {
// estimated supply
let getblockchaininfo = await coreApi.getBlockchainInfo();
let estimatedSupply = utils.estimatedSupply(getblockchaininfo.blocks);
let lastCheckpoint = coinConfig.utxoSetCheckpointsByNetwork[global.activeBlockchain];
res.send({
supply: estimatedSupply.toString(),
type: "estimated",
lastCheckpointHeight: lastCheckpoint.height
})
next();
}
}));
router.get("/blockchain/utxo-set", asyncHandler(async (req, res, next) => {
const utxoSetSummary = await coreApi.getUtxoSetSummary(true, true);
res.json(utxoSetSummary);
next();
}));
router.get("/blockchain/next-halving", asyncHandler(async (req, res, next) => {
try {
const getblockchaininfo = await coreApi.getBlockchainInfo();
let promises = [];
res.locals.getblockchaininfo = getblockchaininfo;
res.locals.difficultyPeriod = parseInt(Math.floor(getblockchaininfo.blocks / coinConfig.difficultyAdjustmentBlockCount));
let blockHeights = [];
if (getblockchaininfo.blocks) {
for (let i = 0; i < 1; i++) {
blockHeights.push(getblockchaininfo.blocks - i);
}
} else if (global.activeBlockchain == "regtest") {
// hack: default regtest node returns getblockchaininfo.blocks=0, despite
// having a genesis block; hack this to display the genesis block
blockHeights.push(0);
}
promises.push(utils.timePromise("homepage.getBlockHeaderByHeight", async () => {
let h = coinConfig.difficultyAdjustmentBlockCount * res.locals.difficultyPeriod;
res.locals.difficultyPeriodFirstBlockHeader = await coreApi.getBlockHeaderByHeight(h);
}));
promises.push(utils.timePromise("homepage.getBlocksByHeight", async () => {
const latestBlocks = await coreApi.getBlocksByHeight(blockHeights);
res.locals.latestBlocks = latestBlocks;
}));
await utils.awaitPromises(promises);
let nextHalvingData = utils.nextHalvingEstimates(res.locals.difficultyPeriodFirstBlockHeader, res.locals.latestBlocks[0]);
// timeAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date())));
let timeAgo = moment.duration(moment.utc(nextHalvingData.nextHalvingDate).diff(moment.utc(new Date())));
let format = timeAgo.format();
let formatParts = format.split(",").map(x => x.trim());
formatParts = formatParts.map(x => { return x.startsWith("0 ") ? "" : x; }).filter(x => x.length > 0);
res.json({
nextHalvingIndex: nextHalvingData.nextHalvingIndex,
nextHalvingBlock: nextHalvingData.nextHalvingBlock,
nextHalvingSubsidy: coinConfig.blockRewardFunction(nextHalvingData.nextHalvingBlock, global.activeBlockchain),
blocksUntilNextHalving: nextHalvingData.blocksUntilNextHalving,
timeUntilNextHalving: formatParts.join(", "),
nextHalvingEstimatedDate: nextHalvingData.nextHalvingDate,
});
next();
} catch (e) {
utils.logError("013923hege3", e)
res.json({success:false});
next();
}
}));
/// ADDRESSES
router.get("/address/:address", asyncHandler(async (req, res, next) => {
try {
const { perfId, perfResults } = utils.perfLogNewItem({action:"api.address"});
res.locals.perfId = perfId;
let limit = config.site.addressTxPageSize;
let offset = 0;
let sort = "desc";
res.locals.maxTxOutputDisplayCount = config.site.addressPage.txOutputMaxDefaultDisplay;
if (req.query.limit) {
limit = parseInt(req.query.limit);
}
if (req.query.offset) {
offset = parseInt(req.query.offset);
}
if (req.query.sort) {
sort = req.query.sort;
}
const address = utils.asAddress(req.params.address);
const transactions = [];
const addressApiSupport = addressApi.getCurrentAddressApiFeatureSupport();
const result = {};
let addressEncoding = "unknown";
let base58Error = null;
let bech32Error = null;
let bech32mError = null;
if (address.match(/^[132mn].*$/)) {
try {
let base58Data = bitcoinjs.address.fromBase58Check(address);
result.base58 = {hash:base58Data.hash.toString("hex"), version:base58Data.version};
addressEncoding = "base58";
} catch (err) {
utils.logError("api.AddressParseError-001", err);
}
}
if (addressEncoding == "unknown") {
try {
let bech32Data = bitcoinjs.address.fromBech32(address);
result.bech32 = {data:bech32Data.data.toString("hex"), version:bech32Data.version};
addressEncoding = "bech32";
} catch (err) {
utils.logError("api.AddressParseError-002", err);
}
}
if (addressEncoding == "unknown") {
try {
let bech32mData = bech32m.decode(address);
result.bech32m = {words:Buffer.from(bech32mData.words).toString("hex"), version:bech32mData.version};
addressEncoding = "bech32m";
} catch (err) {
utils.logError("api.AddressParseError-003", err);
}
}
if (addressEncoding == "unknown") {
res.json({success:false, error:"Invalid address"});
next();
return;
}
result.encoding = addressEncoding;
result.notes = [];
if (global.specialAddresses[address] && global.specialAddresses[address].type == "fun") {
let funInfo = global.specialAddresses[address].addressInfo;
notes.push(funInfo);
}
if (global.miningPoolsConfigs) {
for (let i = 0; i < global.miningPoolsConfigs.length; i++) {
if (global.miningPoolsConfigs[i].payout_addresses[address]) {
let note = global.miningPoolsConfigs[i].payout_addresses[address];
note.type = "payout address for miner";
result.notes.push(note);
break;
}
}
}
if (result.notes.length == 0) {
delete result.notes;
}
const validateaddressResult = await coreApi.getAddress(address);
result.validateaddress = validateaddressResult;
const promises = [];
let addrScripthash = hexEnc.stringify(sha256(hexEnc.parse(validateaddressResult.scriptPubKey)));
addrScripthash = addrScripthash.match(/.{2}/g).reverse().join("");
result.electrumScripthash = addrScripthash;
promises.push(utils.timePromise("address.getAddressDetails", async () => {
const addressDetailsResult = await addressApi.getAddressDetails(address, validateaddressResult.scriptPubKey, sort, limit, offset);
let addressDetails = addressDetailsResult.addressDetails;
result.txHistory = addressDetails;
result.txHistory.request = {};
result.txHistory.request.limit = limit;
result.txHistory.request.offset = offset;
result.txHistory.request.sort = sort;
if (addressDetailsResult.errors && addressDetailsResult.errors.length > 0) {
result.txHistory.errors = addressDetailsResult.errors;
}
}, perfResults));
await utils.awaitPromises(promises);
res.json(result);
next();
} catch (e) {
res.json({success:false});
next();
}
}));
/// XYZ PUBS
// redirect for an old path
router.get("/util/xyzpub/:extendedPubkey", asyncHandler(async (req, res, next) => {
const extendedPubkey = req.params.extendedPubkey;
res.redirect(`${req.baseUrl}/xyzpub/${extendedPubkey}`);
}));
router.get("/xyzpub/:extendedPubkey", asyncHandler(async (req, res, next) => {
try {
const extendedPubkey = req.params.extendedPubkey;
res.locals.extendedPubkey = extendedPubkey;
let limit = 20;
if (req.query.limit) {
limit = parseInt(req.query.limit);
}
let offset = 0;
if (req.query.offset) {
offset = parseInt(req.query.offset);
}
let relatedKeys = [];
let outputType = "Unknown";
let outputTypeDesc = null;
let bip32Path = "Unknown";
const keyDetails = xyzpubApi.getKeyDetails(extendedPubkey);
keyDetails.receiveAddresses = xyzpubApi.getXpubAddresses(extendedPubkey, 0, limit, offset);
keyDetails.changeAddresses = xyzpubApi.getXpubAddresses(extendedPubkey, 1, limit, offset);
res.json(keyDetails);
next();
} catch (err) {
res.locals.pageErrors.push(utils.logError("0923tygdusde", err));
next();
}
}));
router.get("/xyzpub/txids/:extendedPubkey", asyncHandler(async (req, res, next) => {
try {
const extendedPubkey = req.params.extendedPubkey;
let gapLimit = 20;
if (req.query.gapLimit) {
gapLimit = parseInt(req.query.gapLimit);
}
let limit = -1;
if (req.query.limit) {
limit = parseInt(req.query.limit);
}
const searchResult = await xyzpubApi.searchXpubTxids(extendedPubkey, gapLimit, limit);
let result = {
txids: [],
txCount: 0
};
searchResult.usedAddresses.forEach(addrResult => {
addrResult.txids.forEach(txid => {
if (!result.txids.includes(txid)) {
result.txids.push(txid);
result.txCount++;
}
});
})
if (searchResult) {
res.json(result);
} else {
res.json({success:false});
}
next();
} catch (e) {
utils.logError("382rdere", e);
res.json({success:false, error: e.toString()});
next();
}
}));
router.get("/xyzpub/addresses/:extendedPubkey", asyncHandler(async (req, res, next) => {
try {
const extendedPubkey = req.params.extendedPubkey;
let receiveOrChange = 0;
if (req.query.receiveOrChange) {
receiveOrChange = parseInt(req.query.receiveOrChange);
}
let limit = 10;
if (req.query.limit) {
limit = parseInt(req.query.limit);
}
let offset = 0;
if (req.query.offset) {
offset = parseInt(req.query.offset);
}
const xyzpubResult = await xyzpubApi.getXpubAddresses(extendedPubkey, receiveOrChange, limit, offset);
if (xyzpubResult){
res.json(xyzpubResult);
} else {
res.json({success:false});
}
next();
} catch (e) {
utils.logError("3297rwegee", e);
res.json({success:false, error: e.toString()});
next();
}
}));
/// MINING
router.get("/mining/hashrate", asyncHandler(async (req, res, next) => {
try {
let decimals = 3;
if (req.query.decimals) {
decimals = parseInt(req.query.decimals);
}
let blocksPerDay = 24 * 60 * 60 / coinConfig.targetBlockTimeSeconds;
let rates = [];
let timePeriods = [
1 * blocksPerDay,
7 * blocksPerDay,
30 * blocksPerDay,
90 * blocksPerDay,
365 * blocksPerDay,
];
let promises = [];
for (let i = 0; i < timePeriods.length; i++) {
const index = i;
const x = timePeriods[i];
promises.push(new Promise(async (resolve, reject) => {
try {
const hashrate = await coreApi.getNetworkHashrate(x);
let summary = utils.formatLargeNumber(hashrate, decimals);
rates[index] = {
val: parseFloat(summary[0]),
unit: `${summary[1].name}hash`,
unitAbbreviation: `${summary[1].abbreviation}H`,
unitExponent: summary[1].exponent,
unitMultiplier: summary[1].val,
raw: summary[0] * summary[1].val,
string1: `${summary[0]}x10^${summary[1].exponent}`,
string2: `${summary[0]}e${summary[1].exponent}`,
string3: `${(summary[0] * summary[1].val).toLocaleString()}`
};
resolve();
} catch (ex) {
utils.logError("8ehfwe8ehe", ex);
resolve();
}
}));
}
await Promise.all(promises);
res.json({
"1Day": rates[0],
"7Day": rates[1],
"30Day": rates[2],
"90day": rates[3],
"365Day": rates[4]
});
} catch (e) {
utils.logError("23reuhd8uw92D", e);
res.json({
error: typeof(e) == "string" ? e : utils.stringifySimple(e)
});
}
}));
router.get("/mining/diff-adj-estimate", asyncHandler(async (req, res, next) => {
const { perfId, perfResults } = utils.perfLogNewItem({action:"api.diff-adj-estimate"});
res.locals.perfId = perfId;
let promises = [];
const getblockchaininfo = await utils.timePromise("api_diffAdjEst_getBlockchainInfo", coreApi.getBlockchainInfo);
let currentBlock;
let difficultyPeriod = parseInt(Math.floor(getblockchaininfo.blocks / coinConfig.difficultyAdjustmentBlockCount));
let difficultyPeriodFirstBlockHeader;
promises.push(utils.timePromise("api.diff-adj-est.getBlockHeaderByHeight", async () => {
currentBlock = await coreApi.getBlockHeaderByHeight(getblockchaininfo.blocks);
}, perfResults));
promises.push(utils.timePromise("api.diff-adj-est.getBlockHeaderByHeight2", async () => {
let h = coinConfig.difficultyAdjustmentBlockCount * difficultyPeriod;
difficultyPeriodFirstBlockHeader = await coreApi.getBlockHeaderByHeight(h);
}, perfResults));
await utils.awaitPromises(promises);
let firstBlockHeader = difficultyPeriodFirstBlockHeader;
let heightDiff = currentBlock.height - firstBlockHeader.height;
let blockCount = heightDiff + 1;
let timeDiff = currentBlock.mediantime - firstBlockHeader.mediantime;
let timePerBlock = timeDiff / heightDiff;
let dt = new Date().getTime() / 1000 - firstBlockHeader.time;
let predictedBlockCount = dt / coinConfig.targetBlockTimeSeconds;
let timePerBlock2 = dt / heightDiff;
let blockRatioPercent = new Decimal(blockCount / predictedBlockCount).times(100);
if (blockRatioPercent > 400) {
blockRatioPercent = new Decimal(400);
}
if (blockRatioPercent < 25) {
blockRatioPercent = new Decimal(25);
}
let diffAdjPercent = 0;
if (predictedBlockCount > blockCount) {
diffAdjPercent = new Decimal(100).minus(blockRatioPercent).times(-1);
//diffAdjPercent = diffAdjPercent * -1;
} else {
diffAdjPercent = blockRatioPercent.minus(new Decimal(100));
}
res.send(diffAdjPercent.toFixed(2).toString());
}));
router.get("/mining/next-block", asyncHandler(async (req, res, next) => {
const promises = [];
const result = {};
promises.push(utils.timePromise("api/next-block/getblocktemplate", async () => {
let nextBlockEstimate = await utils.timePromise("api/next-block/getNextBlockEstimate", async () => {
return await coreApi.getNextBlockEstimate();
});
//result.blockTemplate = nextBlockEstimate.blockTemplate;
//result.feeRateGroups = nextBlockEstimate.feeRateGroups;
result.txCount = nextBlockEstimate.blockTemplate.transactions.length;
result.minFeeRate = nextBlockEstimate.minFeeRate;
result.maxFeeRate = nextBlockEstimate.maxFeeRate;
result.minFeeTxid = nextBlockEstimate.minFeeTxid;
result.maxFeeTxid = nextBlockEstimate.maxFeeTxid;
result.totalFees = nextBlockEstimate.totalFees.toNumber();
}));
await utils.awaitPromises(promises);
res.json(result);
}));
router.get("/mining/next-block/txids", asyncHandler(async (req, res, next) => {
const promises = [];
const txids = [];
promises.push(utils.timePromise("api/next-block/getblocktemplate", async () => {
let nextBlockEstimate = await utils.timePromise("api/next-block/getNextBlockEstimate", async () => {
return await coreApi.getNextBlockEstimate();
});
nextBlockEstimate.blockTemplate.transactions.forEach(x => {
txids.push(x.txid);
});
}));
await utils.awaitPromises(promises);
res.json(txids);
}));
router.get("/mining/next-block/includes/:txid", asyncHandler(async (req, res, next) => {
const txid = req.params.txid;
const promises = [];
let txidIndex = -1;
let txCount = -1;
promises.push(utils.timePromise("api/next-block/getblocktemplate", async () => {
let nextBlockEstimate = await utils.timePromise("api/next-block/getNextBlockEstimate", async () => {
return await coreApi.getNextBlockEstimate();
});
txCount = nextBlockEstimate.blockTemplate.transactions.length;
for (let i = 0; i < nextBlockEstimate.blockTemplate.transactions.length; i++) {
if (nextBlockEstimate.blockTemplate.transactions[i].txid == txid) {
txidIndex = i;
return;
}
}
}));
await utils.awaitPromises(promises);
let response = {included:(txidIndex >= 0)};
if (txidIndex >= 0) {
response.index = txidIndex;
response.txCount = txCount;
}
res.json(response);
}));
router.get("/mining/miner-summary", asyncHandler(async (req, res, next) => {
let startHeight = -1;
let endHeight = -1;
if (req.query.since) {
const regex = /^([0-9]+)d$/;
const match = req.query.since.match(regex);
if (match) {
let days = parseInt(match[1]);
let getblockchaininfo = await coreApi.getBlockchainInfo();
startHeight = getblockchaininfo.blocks - 144 * days;
endHeight = getblockchaininfo.blocks;
}
} else if (req.query.startHeight && req.query.endHeight) {
startHeight = parseInt(req.query.startHeight);
endHeight = parseInt(req.query.endHeight);
}
if (startHeight == -1 || endHeight == -1) {
res.json({success:false, error:"Unknown start or end height - use either 'since' (e.g. 'since=7d') or 'startHeight'+'endHeight' parameters to specify the blocks to analyze."});
return;
}
const summary = await coreApi.buildMiningSummary(null, startHeight, endHeight, null);
res.json(summary);
}));
/// MEMPOOL
router.get("/mempool/summary", function(req, res, next) {
coreApi.getMempoolInfo().then(function(info){
res.json(info);
}).catch(next);
});
router.get("/mempool/fees", function(req, res, next) {
let feeConfTargets = [1, 3, 6, 144];
coreApi.getSmartFeeEstimates("CONSERVATIVE", feeConfTargets).then(function(rawSmartFeeEstimates){
let smartFeeEstimates = {};
for (let i = 0; i < feeConfTargets.length; i++) {
let rawSmartFeeEstimate = rawSmartFeeEstimates[i];
if (rawSmartFeeEstimate.errors) {
smartFeeEstimates[feeConfTargets[i]] = "?";
} else {
smartFeeEstimates[feeConfTargets[i]] = parseInt(new Decimal(rawSmartFeeEstimate.feerate).times(coinConfig.baseCurrencyUnit.multiplier).dividedBy(1000));
}
}
let results = {
"nextBlock":smartFeeEstimates[1],
"30min":smartFeeEstimates[3],
"60min":smartFeeEstimates[6],
"1day":smartFeeEstimates[144]
};
res.json(results);
}).catch(next);
});
/// PRICE
const supportedCurrencies = ["usd", "eur", "gbp", "xau"];
router.get("/price/sats", function(req, res, next) {
let result = {};
let amount = 1.0;
if (!global.exchangeRates) {
result.success = false;
result.error = "You have exchange-rate requests disabled (this is the default state; in your server configuration, you must set BTCEXP_NO_RATES to 'false', and ensure that BTCEXP_PRIVACY_MODE is also still its default value of 'false')"
}
supportedCurrencies.forEach(currency => {
if (global.exchangeRates != null && global.exchangeRates[currency] != null) {
let satsRateData = utils.satoshisPerUnitOfLocalCurrency(currency);
result[currency] = satsRateData.amtRaw;
} else if (currency == "xau" && global.exchangeRates != null && global.goldExchangeRates != null) {
let dec = new Decimal(amount);
dec = dec.times(global.exchangeRates.usd).dividedBy(global.goldExchangeRates.usd);
let satCurrencyType = global.currencyTypes["sat"];
let one = new Decimal(1);
dec = one.dividedBy(dec);
dec = dec.times(satCurrencyType.multiplier);
result[currency] = dec.toFixed(0);
}
});
res.json(result);
next();
});
router.get("/price/marketcap", function(req, res, next) {
let result = 0;
if (!global.exchangeRates) {
result.success = false;
result.error = "You have exchange-rate requests disabled (this is the default state; in your server configuration, you must set BTCEXP_NO_RATES to 'false', and ensure that BTCEXP_PRIVACY_MODE is also still its default value of 'false')"
}
coreApi.getBlockchainInfo().then(function(getblockchaininfo){
let estimatedSupply = utils.estimatedSupply(getblockchaininfo.blocks);
let price = 0;
let amount = 1.0;
let result = {};
supportedCurrencies.forEach(currency => {
if (global.exchangeRates != null && global.exchangeRates[currency] != null) {
let formatData = utils.formatExchangedCurrency(amount, currency);
price = parseFloat(formatData.valRaw).toFixed(2);
} else if (currency == "xau" && global.exchangeRates != null && global.goldExchangeRates != null) {
let dec = new Decimal(amount);
dec = dec.times(global.exchangeRates.usd).dividedBy(global.goldExchangeRates.usd);
let exchangedAmt = parseFloat(Math.round(dec * 100) / 100).toFixed(2);
price = exchangedAmt;
}
result[currency] = estimatedSupply * price;
});
res.json(result);
next();
}).catch(next);
});
router.get("/price", function(req, res, next) {
let amount = 1.0;
let result = {};
let format = (req.query.format == "true");
if (!global.exchangeRates) {
result.success = false;
result.error = "You have exchange-rate requests disabled (this is the default state; in your server configuration, you must set BTCEXP_NO_RATES to 'false', and ensure that BTCEXP_PRIVACY_MODE is also still its default value of 'false')"
}
supportedCurrencies.forEach(currency => {
if (global.exchangeRates != null && global.exchangeRates[currency] != null) {
let formatData = utils.formatExchangedCurrency(amount, currency);
if (format) {
result[currency] = formatData.val;
} else {
result[currency] = formatData.valRaw;
}
} else if (currency == "xau" && global.exchangeRates != null && global.goldExchangeRates != null) {
let dec = new Decimal(amount);
dec = dec.times(global.exchangeRates.usd).dividedBy(global.goldExchangeRates.usd);
let exchangedAmt = parseFloat(Math.round(dec * 100) / 100).toFixed(2);
result[currency] = utils.addThousandsSeparators(exchangedAmt);
}
});
res.json(result);
next();
});
/// FUN
router.get("/quotes/random", function(req, res, next) {
let index = utils.randomInt(0, btcQuotes.items.length);
let quote = null;
let done = false;
while (!done) {
let quoteIndex = utils.randomInt(0, btcQuotes.items.length);
quote = btcQuotes.items[quoteIndex];
done = !utils.objHasProperty(quote, "duplicateIndex");
}
res.json(quote);
next();
});
router.get("/quotes/all", function(req, res, next) {
res.json(btcQuotes.items);
next();
});
router.get("/quotes/:quoteIndex", function(req, res, next) {
if (!req.params.quoteIndex.match(/\d+/)) {
return;
}
let index = parseInt(req.params.quoteIndex);
res.json(btcQuotes.items[index]);
next();
});
router.get("/holidays/all", function(req, res, next) {
res.json(global.btcHolidays.sortedItems);
next();
});
router.get("/holidays/today", function(req, res, next) {
let momentObj = moment.utc(new Date());
if (req.query.tzOffset) {
momentObj = momentObj.add(parseInt(req.query.tzOffset), "hours")
}
let day = momentObj.format("MM-DD");
if (global.btcHolidays.byDay[day]) {
res.json({day: day, holidays: global.btcHolidays.byDay[day]});
} else {
res.json({day: day, holidays: []});
}
next();
});
router.get("/holidays/:day", function(req, res, next) {
if (!req.params.day.match(/^(\d{4}-)?\d{2}-\d{2}$/)) {
return;
}
let day = req.params.day;
if (req.params.day.match(/^\d{4}-\d{2}-\d{2}$/)) {
// strip off year
day = day.substring(5);
} else if (req.params.day.match(/^\d{2}-\d{2}$/)) {
// already correct format
}
if (global.btcHolidays.byDay[day]) {
res.json({day: day, holidays: global.btcHolidays.byDay[day]});
} else {
res.json({day: day, holidays: []});
}
next();
});
module.exports = router;