diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 01347884a..94332b560 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -1,16 +1,17 @@ -import { MempoolInfo, Transaction, Block, MempoolEntries, MempoolEntry } from '../../interfaces'; +import { MempoolInfo, Transaction, Block, MempoolEntries, MempoolEntry, Address } from '../../interfaces'; export interface AbstractBitcoinApi { - getMempoolInfo(): Promise; - getRawMempool(): Promise; - getRawTransaction(txId: string): Promise; - getBlockHeightTip(): Promise; - getTxIdsForBlock(hash: string): Promise; - getBlockHash(height: number): Promise; - getBlock(hash: string): Promise; - getMempoolEntry(txid: string): Promise; + $getMempoolInfo(): Promise; + $getRawMempool(): Promise; + $getRawTransaction(txId: string): Promise; + $getBlockHeightTip(): Promise; + $getTxIdsForBlock(hash: string): Promise; + $getBlockHash(height: number): Promise; + $getBlock(hash: string): Promise; + $getMempoolEntry(txid: string): Promise; + $getAddress(address: string): Promise
; // Custom - getRawMempoolVerbose(): Promise; - getRawTransactionBitcond(txId: string): Promise; + $getRawMempoolVerbose(): Promise; + $getRawTransactionBitcond(txId: string): Promise; } diff --git a/backend/src/api/bitcoin/bitcoind-api.ts b/backend/src/api/bitcoin/bitcoind-api.ts index d9c7cb4a3..7f01f5f55 100644 --- a/backend/src/api/bitcoin/bitcoind-api.ts +++ b/backend/src/api/bitcoin/bitcoind-api.ts @@ -1,5 +1,5 @@ import config from '../../config'; -import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry } from '../../interfaces'; +import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry, Address } from '../../interfaces'; import * as bitcoin from '@mempool/bitcoin'; class BitcoindApi { @@ -15,23 +15,23 @@ class BitcoindApi { }); } - getMempoolInfo(): Promise { + $getMempoolInfo(): Promise { return this.bitcoindClient.getMempoolInfo(); } - getRawMempool(): Promise { + $getRawMempool(): Promise { return this.bitcoindClient.getRawMemPool(); } - getRawMempoolVerbose(): Promise { + $getRawMempoolVerbose(): Promise { return this.bitcoindClient.getRawMemPool(true); } - getMempoolEntry(txid: string): Promise { - return this.bitcoindClient.getMempoolEntry(txid,); + $getMempoolEntry(txid: string): Promise { + return this.bitcoindClient.getMempoolEntry(txid); } - getRawTransaction(txId: string): Promise { + $getRawTransaction(txId: string): Promise { return this.bitcoindClient.getRawTransaction(txId, true) .then((transaction: Transaction) => { transaction.vout.forEach((vout) => vout.value = vout.value * 100000000); @@ -39,23 +39,23 @@ class BitcoindApi { }); } - getBlockHeightTip(): Promise { + $getBlockHeightTip(): Promise { return this.bitcoindClient.getChainTips() .then((result) => result[0].height); } - getTxIdsForBlock(hash: string): Promise { + $getTxIdsForBlock(hash: string): Promise { return this.bitcoindClient.getBlock(hash, 1) .then((rpcBlock: RpcBlock) => { return rpcBlock.tx; }); } - getBlockHash(height: number): Promise { - return this.bitcoindClient.getBlockHash(height) + $getBlockHash(height: number): Promise { + return this.bitcoindClient.getBlockHash(height); } - getBlock(hash: string): Promise { + $getBlock(hash: string): Promise { return this.bitcoindClient.getBlock(hash) .then((rpcBlock: RpcBlock) => { return { @@ -75,7 +75,11 @@ class BitcoindApi { }); } - getRawTransactionBitcond(txId: string): Promise { + $getRawTransactionBitcond(txId: string): Promise { + throw new Error('Method not implemented.'); + } + + $getAddress(address: string): Promise
{ throw new Error('Method not implemented.'); } } diff --git a/backend/src/api/bitcoin/bitcoind-electrs-api.ts b/backend/src/api/bitcoin/bitcoind-electrs-api.ts index b99321a78..f80baa0c2 100644 --- a/backend/src/api/bitcoin/bitcoind-electrs-api.ts +++ b/backend/src/api/bitcoin/bitcoind-electrs-api.ts @@ -1,9 +1,10 @@ import config from '../../config'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; -import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry } from '../../interfaces'; +import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry, Address } from '../../interfaces'; import * as bitcoin from '@mempool/bitcoin'; import * as ElectrumClient from '@codewarriorr/electrum-client-js'; import logger from '../../logger'; +import transactionUtils from '../transaction-utils'; class BitcoindElectrsApi implements AbstractBitcoinApi { bitcoindClient: any; @@ -27,64 +28,64 @@ class BitcoindElectrsApi implements AbstractBitcoinApi { this.electrumClient.connect( 'electrum-client-js', '1.4' - ) + ); } - getMempoolInfo(): Promise { + $getMempoolInfo(): Promise { return this.bitcoindClient.getMempoolInfo(); } - getRawMempool(): Promise { + $getRawMempool(): Promise { return this.bitcoindClient.getRawMemPool(); } - getRawMempoolVerbose(): Promise { + $getRawMempoolVerbose(): Promise { return this.bitcoindClient.getRawMemPool(true); } - getMempoolEntry(txid: string): Promise { - return this.bitcoindClient.getMempoolEntry(txid,); + $getMempoolEntry(txid: string): Promise { + return this.bitcoindClient.getMempoolEntry(txid); } - async getRawTransaction(txId: string): Promise { + async $getRawTransaction(txId: string): Promise { try { const transaction: Transaction = await this.electrumClient.blockchain_transaction_get(txId, true); if (!transaction) { - throw new Error('not found'); + throw new Error(txId + ' not found!'); } - transaction.vout.forEach((vout) => vout.value = vout.value * 100000000); + transactionUtils.bitcoindToElectrsTransaction(transaction); return transaction; } catch (e) { - logger.debug('getRawTransaction error: ' + (e.message || e)); + logger.debug('getRawTransaction error: ' + (e.message || e)); throw new Error(e); } } - getRawTransactionBitcond(txId: string): Promise { + $getRawTransactionBitcond(txId: string): Promise { return this.bitcoindClient.getRawTransaction(txId, true) .then((transaction: Transaction) => { - transaction.vout.forEach((vout) => vout.value = vout.value * 100000000); + transactionUtils.bitcoindToElectrsTransaction(transaction); return transaction; }); } - getBlockHeightTip(): Promise { + $getBlockHeightTip(): Promise { return this.bitcoindClient.getChainTips() .then((result) => result[0].height); } - getTxIdsForBlock(hash: string): Promise { + $getTxIdsForBlock(hash: string): Promise { return this.bitcoindClient.getBlock(hash, 1) .then((rpcBlock: RpcBlock) => { return rpcBlock.tx; }); } - getBlockHash(height: number): Promise { - return this.bitcoindClient.getBlockHash(height) + $getBlockHash(height: number): Promise { + return this.bitcoindClient.getBlockHash(height); } - getBlock(hash: string): Promise { + $getBlock(hash: string): Promise { return this.bitcoindClient.getBlock(hash) .then((rpcBlock: RpcBlock) => { return { @@ -103,6 +104,19 @@ class BitcoindElectrsApi implements AbstractBitcoinApi { }; }); } + + async $getAddress(address: string): Promise
{ + try { + const addressInfo: Address = await this.electrumClient.blockchain_scripthash_getBalance(address); + if (!address) { + throw new Error('not found'); + } + return addressInfo; + } catch (e) { + logger.debug('getRawTransaction error: ' + (e.message || e)); + throw new Error(e); + } + } } export default BitcoindElectrsApi; diff --git a/backend/src/api/bitcoin/electrs-api.ts b/backend/src/api/bitcoin/electrs-api.ts index a7028ab59..a2dc86ad4 100644 --- a/backend/src/api/bitcoin/electrs-api.ts +++ b/backend/src/api/bitcoin/electrs-api.ts @@ -1,6 +1,6 @@ import config from '../../config'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; -import { Transaction, Block, MempoolInfo, MempoolEntry, MempoolEntries } from '../../interfaces'; +import { Transaction, Block, MempoolInfo, MempoolEntry, MempoolEntries, Address } from '../../interfaces'; import axios from 'axios'; class ElectrsApi implements AbstractBitcoinApi { @@ -8,7 +8,7 @@ class ElectrsApi implements AbstractBitcoinApi { constructor() { } - getMempoolInfo(): Promise { + $getMempoolInfo(): Promise { return axios.get(config.ELECTRS.REST_API_URL + '/mempool', { timeout: 10000 }) .then((response) => { return { @@ -18,45 +18,49 @@ class ElectrsApi implements AbstractBitcoinApi { }); } - getRawMempool(): Promise { + $getRawMempool(): Promise { return axios.get(config.ELECTRS.REST_API_URL + '/mempool/txids') .then((response) => response.data); } - getRawTransaction(txId: string): Promise { + $getRawTransaction(txId: string): Promise { return axios.get(config.ELECTRS.REST_API_URL + '/tx/' + txId) .then((response) => response.data); } - getBlockHeightTip(): Promise { + $getBlockHeightTip(): Promise { return axios.get(config.ELECTRS.REST_API_URL + '/blocks/tip/height') .then((response) => response.data); } - getTxIdsForBlock(hash: string): Promise { + $getTxIdsForBlock(hash: string): Promise { return axios.get(config.ELECTRS.REST_API_URL + '/block/' + hash + '/txids') .then((response) => response.data); } - getBlockHash(height: number): Promise { + $getBlockHash(height: number): Promise { return axios.get(config.ELECTRS.REST_API_URL + '/block-height/' + height) .then((response) => response.data); } - getBlock(hash: string): Promise { + $getBlock(hash: string): Promise { return axios.get(config.ELECTRS.REST_API_URL + '/block/' + hash) .then((response) => response.data); } - getRawMempoolVerbose(): Promise { + $getRawMempoolVerbose(): Promise { throw new Error('Method not implemented.'); } - getMempoolEntry(): Promise { + $getMempoolEntry(): Promise { throw new Error('Method not implemented.'); } - getRawTransactionBitcond(txId: string): Promise { + $getRawTransactionBitcond(txId: string): Promise { + throw new Error('Method not implemented.'); + } + + $getAddress(address: string): Promise
{ throw new Error('Method not implemented.'); } } diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 31e2525f6..a941f8fa2 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -2,9 +2,10 @@ import config from '../config'; import bitcoinApi from './bitcoin/bitcoin-api-factory'; import logger from '../logger'; import memPool from './mempool'; -import { Block, Transaction, TransactionExtended, TransactionMinerInfo } from '../interfaces'; +import { Block, TransactionExtended, TransactionMinerInfo } from '../interfaces'; import { Common } from './common'; import diskCache from './disk-cache'; +import transactionUtils from './transaction-utils'; class Blocks { private static KEEP_BLOCK_AMOUNT = 8; @@ -28,7 +29,7 @@ class Blocks { } public async $updateBlocks() { - const blockHeightTip = await bitcoinApi.getBlockHeightTip(); + const blockHeightTip = await bitcoinApi.$getBlockHeightTip(); if (this.blocks.length === 0) { this.currentBlockHeight = blockHeightTip - Blocks.KEEP_BLOCK_AMOUNT; @@ -43,8 +44,8 @@ class Blocks { if (!this.lastDifficultyAdjustmentTime) { const heightDiff = blockHeightTip % 2016; - const blockHash = await bitcoinApi.getBlockHash(blockHeightTip - heightDiff); - const block = await bitcoinApi.getBlock(blockHash); + const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff); + const block = await bitcoinApi.$getBlock(blockHash); this.lastDifficultyAdjustmentTime = block.timestamp; } @@ -56,11 +57,11 @@ class Blocks { logger.debug(`New block found (#${this.currentBlockHeight})!`); } - let transactions: TransactionExtended[] = []; + const transactions: TransactionExtended[] = []; - const blockHash = await bitcoinApi.getBlockHash(this.currentBlockHeight); - const block = await bitcoinApi.getBlock(blockHash); - let txIds: string[] = await bitcoinApi.getTxIdsForBlock(blockHash); + const blockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight); + const block = await bitcoinApi.$getBlock(blockHash); + const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash); const mempool = memPool.getMempool(); let found = 0; @@ -70,19 +71,13 @@ class Blocks { transactions.push(mempool[txIds[i]]); found++; } else { - if (config.MEMPOOL.BACKEND === 'electrs') { + // When using bitcoind, just skip parsing past block tx's for now except for coinbase + if (config.MEMPOOL.BACKEND === 'electrs' || i === 0) { // logger.debug(`Fetching block tx ${i} of ${txIds.length}`); - const tx = await memPool.getTransactionExtended(txIds[i]); + const tx = await transactionUtils.getTransactionExtended(txIds[i]); if (tx) { transactions.push(tx); } - } else { // When using bitcoind, just skip parsing past block tx's for now - if (i === 0) { - const tx = await memPool.getTransactionExtended(txIds[i], true); - if (tx) { - transactions.push(tx); - } - } } } } @@ -115,6 +110,10 @@ class Blocks { return this.lastDifficultyAdjustmentTime; } + public getCurrentBlockHeight(): number { + return this.currentBlockHeight; + } + private stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo { return { vin: [{ @@ -122,7 +121,7 @@ class Blocks { }], vout: tx.vout .map((vout) => ({ - scriptpubkey_address: vout.scriptpubkey_address || (vout['scriptPubKey']['addresses'] && vout['scriptPubKey']['addresses'][0]) || null, + scriptpubkey_address: vout.scriptpubkey_address, value: vout.value })) .filter((vout) => vout.value) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 6e87c9896..fdd50a11a 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -53,7 +53,7 @@ export class Common { txid: tx.txid, fee: tx.fee, weight: tx.weight, - value: tx.vout ? tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0) : 0, + value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0), }; } } diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 6a477d5f7..cb13fb874 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -3,6 +3,7 @@ import bitcoinApi from './bitcoin/bitcoin-api-factory'; import { MempoolInfo, TransactionExtended, Transaction, VbytesPerSecond, MempoolEntry, MempoolEntries } from '../interfaces'; import logger from '../logger'; import { Common } from './common'; +import transactionUtils from './transaction-utils'; class Mempool { private inSync: boolean = false; @@ -18,7 +19,6 @@ class Mempool { private vBytesPerSecond: number = 0; private mempoolProtection = 0; private latestTransactions: any[] = []; - private mempoolEntriesCache: MempoolEntries | null = null; constructor() { setInterval(this.updateTxPerSecond.bind(this), 1000); @@ -49,7 +49,7 @@ class Mempool { } public async $updateMemPoolInfo() { - this.mempoolInfo = await bitcoinApi.getMempoolInfo(); + this.mempoolInfo = await bitcoinApi.$getMempoolInfo(); } public getMempoolInfo(): MempoolInfo | undefined { @@ -76,60 +76,19 @@ class Mempool { return txTimes; } - public async getTransactionExtended(txId: string, isCoinbase = false): Promise { - try { - let transaction: Transaction; - if (!isCoinbase && config.MEMPOOL.BACKEND === 'bitcoind-electrs') { - transaction = await bitcoinApi.getRawTransactionBitcond(txId); - } else { - transaction = await bitcoinApi.getRawTransaction(txId); - } - if (config.MEMPOOL.BACKEND !== 'electrs' && !isCoinbase) { - transaction = await this.$appendFeeData(transaction); - } - return this.extendTransaction(transaction); - } catch (e) { - logger.debug(txId + ' not found'); - return false; - } - } - - private async $appendFeeData(transaction: Transaction): Promise { - let mempoolEntry: MempoolEntry; - if (!this.inSync && !this.mempoolEntriesCache) { - this.mempoolEntriesCache = await bitcoinApi.getRawMempoolVerbose(); - } - if (this.mempoolEntriesCache && this.mempoolEntriesCache[transaction.txid]) { - mempoolEntry = this.mempoolEntriesCache[transaction.txid]; - } else { - mempoolEntry = await bitcoinApi.getMempoolEntry(transaction.txid); - } - transaction.fee = mempoolEntry.fees.base * 100000000; - return transaction; - } - - private extendTransaction(transaction: Transaction | MempoolEntry): TransactionExtended { - // @ts-ignore - return Object.assign({ - vsize: Math.round(transaction.weight / 4), - feePerVsize: Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)), - firstSeen: Math.round((new Date().getTime() / 1000)), - }, transaction); - } - public async $updateMempool() { logger.debug('Updating mempool'); const start = new Date().getTime(); let hasChange: boolean = false; const currentMempoolSize = Object.keys(this.mempoolCache).length; let txCount = 0; - const transactions = await bitcoinApi.getRawMempool(); + const transactions = await bitcoinApi.$getRawMempool(); const diff = transactions.length - currentMempoolSize; const newTransactions: TransactionExtended[] = []; for (const txid of transactions) { if (!this.mempoolCache[txid]) { - const transaction = await this.getTransactionExtended(txid); + const transaction = await transactionUtils.getTransactionExtended(txid, false, true); if (transaction) { this.mempoolCache[txid] = transaction; txCount++; @@ -197,7 +156,6 @@ class Mempool { if (!this.inSync && transactions.length === Object.keys(newMempool).length) { this.inSync = true; - this.mempoolEntriesCache = null; logger.info('The mempool is now in sync!'); } diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts new file mode 100644 index 000000000..c94a903be --- /dev/null +++ b/backend/src/api/transaction-utils.ts @@ -0,0 +1,124 @@ +import bitcoinApi from './bitcoin/bitcoin-api-factory'; +import { MempoolEntries, MempoolEntry, Transaction, TransactionExtended, TransactionMinerInfo } from '../interfaces'; +import config from '../config'; +import logger from '../logger'; +import mempool from './mempool'; +import blocks from './blocks'; + +class TransactionUtils { + private mempoolEntriesCache: MempoolEntries | null = null; + + constructor() { } + + public async $addPrevoutsToTransaction(transaction: TransactionExtended): Promise { + for (const vin of transaction.vin) { + const innerTx = await bitcoinApi.$getRawTransaction(vin.txid); + vin.prevout = innerTx.vout[vin.vout]; + } + return transaction; + } + + public async $calculateFeeFromInputs(transaction: Transaction): Promise { + if (transaction.vin[0]['coinbase']) { + transaction.fee = 0; + // @ts-ignore + return transaction; + } + let totalIn = 0; + for (const vin of transaction.vin) { + const innerTx = await bitcoinApi.$getRawTransaction(vin.txid); + totalIn += innerTx.vout[vin.vout].value; + } + const totalOut = transaction.vout.reduce((prev, output) => prev + output.value, 0); + transaction.fee = parseFloat((totalIn - totalOut).toFixed(8)); + return this.extendTransaction(transaction); + } + + public extendTransaction(transaction: Transaction | MempoolEntry): TransactionExtended { + // @ts-ignore + return Object.assign({ + vsize: Math.round(transaction.weight / 4), + feePerVsize: Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)), + firstSeen: Math.round((new Date().getTime() / 1000)), + }, transaction); + } + + public stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo { + return { + vin: [{ + scriptsig: tx.vin[0].scriptsig || tx.vin[0]['coinbase'] + }], + vout: tx.vout + .map((vout) => ({ + scriptpubkey_address: vout.scriptpubkey_address, + value: vout.value + })) + .filter((vout) => vout.value) + }; + } + + public async getTransactionExtended(txId: string, isCoinbase = false, inMempool = false): Promise { + try { + let transaction: Transaction; + if (inMempool) { + transaction = await bitcoinApi.$getRawTransactionBitcond(txId); + } else { + transaction = await bitcoinApi.$getRawTransaction(txId); + } + if (config.MEMPOOL.BACKEND !== 'electrs' && !isCoinbase) { + if (inMempool) { + transaction = await this.$appendFeeData(transaction); + } else { + transaction = await this.$calculateFeeFromInputs(transaction); + } + } + return this.extendTransaction(transaction); + } catch (e) { + logger.debug('getTransactionExtended error: ' + (e.message || e)); + console.log(e); + return null; + } + } + + public bitcoindToElectrsTransaction(transaction: any): void { + try { + transaction.vout = transaction.vout.map((vout) => { + return { + value: vout.value * 100000000, + scriptpubkey: vout.scriptPubKey.hex, + scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : null, + scriptpubkey_asm: vout.scriptPubKey.asm, + scriptpubkey_type: vout.scriptPubKey.type, + }; + }); + if (transaction.confirmations) { + transaction['status'] = { + confirmed: true, + block_height: blocks.getCurrentBlockHeight() - transaction.confirmations, + block_hash: transaction.blockhash, + block_time: transaction.blocktime, + }; + } else { + transaction['status'] = { confirmed: false }; + } + } catch (e) { + console.log('augment failed: ' + (e.message || e)); + } + } + + private async $appendFeeData(transaction: Transaction): Promise { + let mempoolEntry: MempoolEntry; + if (!mempool.isInSync() && !this.mempoolEntriesCache) { + this.mempoolEntriesCache = await bitcoinApi.$getRawMempoolVerbose(); + } + if (this.mempoolEntriesCache && this.mempoolEntriesCache[transaction.txid]) { + mempoolEntry = this.mempoolEntriesCache[transaction.txid]; + } else { + mempoolEntry = await bitcoinApi.$getMempoolEntry(transaction.txid); + } + transaction.fee = mempoolEntry.fees.base * 100000000; + return transaction; + } +} + +export default new TransactionUtils(); diff --git a/backend/src/index.ts b/backend/src/index.ts index ab50afc36..11878460f 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -208,6 +208,21 @@ class Server { } }); } + + if (config.MEMPOOL.BACKEND === 'bitcoind' || config.MEMPOOL.BACKEND === 'bitcoind-electrs') { + this.app + .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', routes.getTransaction) + .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', routes.getTransactionOutspends) + .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', routes.getBlock) + .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', routes.getBlocks) + .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs/:index', routes.getBlockTransactions) + .get(config.MEMPOOL.API_URL_PREFIX + 'block-height/:height', routes.getBlockHeight) + .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address', routes.getAddress) + .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs', routes.getAddressTransactions) + .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs/chain/:txId', routes.getAdressTxChain) + .get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', routes.getAddressPrefix) + ; + } } } diff --git a/backend/src/interfaces.ts b/backend/src/interfaces.ts index e4fab5181..302d43e12 100644 --- a/backend/src/interfaces.ts +++ b/backend/src/interfaces.ts @@ -142,7 +142,7 @@ export interface RpcBlock { strippedsize: number; weight: number; height: number; - version: number, + version: number; versionHex: string; merkleroot: string; tx: Transaction[]; @@ -152,38 +152,38 @@ export interface RpcBlock { bits: number; difficulty: number; chainwork: string; - nTx: number, + nTx: number; previousblockhash: string; nextblockhash: string; } -export interface MempoolEntries { [txId: string]: MempoolEntry }; +export interface MempoolEntries { [txId: string]: MempoolEntry; } export interface MempoolEntry { - fees: Fees - vsize: number - weight: number - fee: number - modifiedfee: number - time: number - height: number - descendantcount: number - descendantsize: number - descendantfees: number - ancestorcount: number - ancestorsize: number - ancestorfees: number - wtxid: string - depends: any[] - spentby: any[] - 'bip125-replaceable': boolean + fees: Fees; + vsize: number; + weight: number; + fee: number; + modifiedfee: number; + time: number; + height: number; + descendantcount: number; + descendantsize: number; + descendantfees: number; + ancestorcount: number; + ancestorsize: number; + ancestorfees: number; + wtxid: string; + depends: any[]; + spentby: any[]; + 'bip125-replaceable': boolean; } export interface Fees { - base: number - modified: number - ancestor: number - descendant: number + base: number; + modified: number; + ancestor: number; + descendant: number; } export interface Address { diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 3d60f7df2..bc24c7b6b 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -8,10 +8,12 @@ import mempool from './api/mempool'; import bisq from './api/bisq/bisq'; import websocketHandler from './api/websocket-handler'; import bisqMarket from './api/bisq/markets-api'; -import { OptimizedStatistic, RequiredSpec } from './interfaces'; +import { OptimizedStatistic, RequiredSpec, Transaction, TransactionExtended } from './interfaces'; import { MarketsApiError } from './api/bisq/interfaces'; import donations from './api/donations'; import logger from './logger'; +import bitcoinApi from './api/bitcoin/bitcoin-api-factory'; +import transactionUtils from './api/transaction-utils'; class Routes { private cache: { [date: string]: OptimizedStatistic[] } = { @@ -524,6 +526,71 @@ class Routes { }; } + public async getTransaction(req: Request, res: Response) { + try { + let transaction: TransactionExtended | null; + const txInMempool = mempool.getMempool()[req.params.txId]; + if (txInMempool) { + transaction = txInMempool; + } else { + transaction = await transactionUtils.getTransactionExtended(req.params.txId); + } + if (transaction) { + transaction = await transactionUtils.$addPrevoutsToTransaction(transaction); + res.json(transaction); + } else { + res.status(500).send('Error fetching transaction.'); + } + } catch (e) { + res.status(500).send(e.message); + } + } + + public async getBlock(req: Request, res: Response) { + try { + const result = await bitcoinApi.$getBlock(req.params.hash); + res.json(result); + } catch (e) { + res.status(500).send(e.message); + } + } + + public async getBlocks(req: Request, res: Response) { + res.status(404).send('Not implemented'); + } + + public async getBlockTransactions(req: Request, res: Response) { + res.status(404).send('Not implemented'); + } + + public async getBlockHeight(req: Request, res: Response) { + res.status(404).send('Not implemented'); + } + + public async getAddress(req: Request, res: Response) { + try { + const result = await bitcoinApi.$getAddress(req.params.hash); + res.json(result); + } catch (e) { + res.status(500).send(e.message); + } + } + + public async getAddressTransactions(req: Request, res: Response) { + res.status(404).send('Not implemented'); + } + + public async getAdressTxChain(req: Request, res: Response) { + res.status(404).send('Not implemented'); + } + + public async getAddressPrefix(req: Request, res: Response) { + res.json([]); + } + + public getTransactionOutspends(req: Request, res: Response) { + res.json([]); + } } export default new Routes(); diff --git a/frontend/proxy.conf.json b/frontend/proxy.conf.json index c02eb5e1e..5886ff104 100644 --- a/frontend/proxy.conf.json +++ b/frontend/proxy.conf.json @@ -9,10 +9,10 @@ "ws": true }, "/api/": { - "target": "http://localhost:50001/", + "target": "http://localhost:8999/", "secure": false, "pathRewrite": { - "^/api/": "" + "^/api/": "/api/v1/" } }, "/testnet/api/v1": {