Merge pull request #2824 from mempool/mononaut/more-rbf-info
cache, serve & display more comprehensive RBF info
This commit is contained in:
commit
59eb271782
@ -18,6 +18,7 @@ import blocks from '../blocks';
|
|||||||
import bitcoinClient from './bitcoin-client';
|
import bitcoinClient from './bitcoin-client';
|
||||||
import difficultyAdjustment from '../difficulty-adjustment';
|
import difficultyAdjustment from '../difficulty-adjustment';
|
||||||
import transactionRepository from '../../repositories/TransactionRepository';
|
import transactionRepository from '../../repositories/TransactionRepository';
|
||||||
|
import rbfCache from '../rbf-cache';
|
||||||
|
|
||||||
class BitcoinRoutes {
|
class BitcoinRoutes {
|
||||||
public initRoutes(app: Application) {
|
public initRoutes(app: Application) {
|
||||||
@ -31,6 +32,8 @@ class BitcoinRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/replaces', this.getRbfHistory)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/cached', this.getCachedTx)
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -589,6 +592,28 @@ class BitcoinRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getRbfHistory(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const result = rbfCache.getReplaces(req.params.txId);
|
||||||
|
res.json(result || []);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getCachedTx(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const result = rbfCache.getTx(req.params.txId);
|
||||||
|
if (result) {
|
||||||
|
res.json(result);
|
||||||
|
} else {
|
||||||
|
res.status(404).send('not found');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async getTransactionOutspends(req: Request, res: Response) {
|
private async getTransactionOutspends(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const result = await bitcoinApi.$getOutspends(req.params.txId);
|
const result = await bitcoinApi.$getOutspends(req.params.txId);
|
||||||
|
@ -60,8 +60,6 @@ export class Common {
|
|||||||
static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } {
|
static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } {
|
||||||
const matches: { [txid: string]: TransactionExtended } = {};
|
const matches: { [txid: string]: TransactionExtended } = {};
|
||||||
deleted
|
deleted
|
||||||
// The replaced tx must have at least one input with nSequence < maxint-1 (That’s the opt-in)
|
|
||||||
.filter((tx) => tx.vin.some((vin) => vin.sequence < 0xfffffffe))
|
|
||||||
.forEach((deletedTx) => {
|
.forEach((deletedTx) => {
|
||||||
const foundMatches = added.find((addedTx) => {
|
const foundMatches = added.find((addedTx) => {
|
||||||
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
||||||
@ -70,7 +68,7 @@ export class Common {
|
|||||||
&& addedTx.feePerVsize > deletedTx.feePerVsize
|
&& addedTx.feePerVsize > deletedTx.feePerVsize
|
||||||
// Spends one or more of the same inputs
|
// Spends one or more of the same inputs
|
||||||
&& deletedTx.vin.some((deletedVin) =>
|
&& deletedTx.vin.some((deletedVin) =>
|
||||||
addedTx.vin.some((vin) => vin.txid === deletedVin.txid));
|
addedTx.vin.some((vin) => vin.txid === deletedVin.txid && vin.vout === deletedVin.vout));
|
||||||
});
|
});
|
||||||
if (foundMatches) {
|
if (foundMatches) {
|
||||||
matches[deletedTx.txid] = foundMatches;
|
matches[deletedTx.txid] = foundMatches;
|
||||||
|
@ -210,7 +210,7 @@ class Mempool {
|
|||||||
for (const rbfTransaction in rbfTransactions) {
|
for (const rbfTransaction in rbfTransactions) {
|
||||||
if (this.mempoolCache[rbfTransaction]) {
|
if (this.mempoolCache[rbfTransaction]) {
|
||||||
// Store replaced transactions
|
// Store replaced transactions
|
||||||
rbfCache.add(rbfTransaction, rbfTransactions[rbfTransaction].txid);
|
rbfCache.add(this.mempoolCache[rbfTransaction], rbfTransactions[rbfTransaction].txid);
|
||||||
// Erase the replaced transactions from the local mempool
|
// Erase the replaced transactions from the local mempool
|
||||||
delete this.mempoolCache[rbfTransaction];
|
delete this.mempoolCache[rbfTransaction];
|
||||||
}
|
}
|
||||||
@ -236,6 +236,7 @@ class Mempool {
|
|||||||
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
|
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
|
||||||
if (lazyDeleteAt && lazyDeleteAt < now) {
|
if (lazyDeleteAt && lazyDeleteAt < now) {
|
||||||
delete this.mempoolCache[tx];
|
delete this.mempoolCache[tx];
|
||||||
|
rbfCache.evict(tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,62 @@
|
|||||||
export interface CachedRbf {
|
import { TransactionExtended } from "../mempool.interfaces";
|
||||||
txid: string;
|
|
||||||
expires: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RbfCache {
|
class RbfCache {
|
||||||
private cache: { [txid: string]: CachedRbf; } = {};
|
private replacedBy: { [txid: string]: string; } = {};
|
||||||
|
private replaces: { [txid: string]: string[] } = {};
|
||||||
|
private txs: { [txid: string]: TransactionExtended } = {};
|
||||||
|
private expiring: { [txid: string]: Date } = {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
setInterval(this.cleanup.bind(this), 1000 * 60 * 60);
|
setInterval(this.cleanup.bind(this), 1000 * 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(replacedTxId: string, newTxId: string): void {
|
public add(replacedTx: TransactionExtended, newTxId: string): void {
|
||||||
this.cache[replacedTxId] = {
|
this.replacedBy[replacedTx.txid] = newTxId;
|
||||||
expires: new Date(Date.now() + 1000 * 604800), // 1 week
|
this.txs[replacedTx.txid] = replacedTx;
|
||||||
txid: newTxId,
|
if (!this.replaces[newTxId]) {
|
||||||
};
|
this.replaces[newTxId] = [];
|
||||||
|
}
|
||||||
|
this.replaces[newTxId].push(replacedTx.txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(txId: string): CachedRbf | undefined {
|
public getReplacedBy(txId: string): string | undefined {
|
||||||
return this.cache[txId];
|
return this.replacedBy[txId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getReplaces(txId: string): string[] | undefined {
|
||||||
|
return this.replaces[txId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTx(txId: string): TransactionExtended | undefined {
|
||||||
|
return this.txs[txId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// flag a transaction as removed from the mempool
|
||||||
|
public evict(txid): void {
|
||||||
|
this.expiring[txid] = new Date(Date.now() + 1000 * 86400); // 24 hours
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanup(): void {
|
private cleanup(): void {
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
for (const c in this.cache) {
|
for (const txid in this.expiring) {
|
||||||
if (this.cache[c].expires < currentDate) {
|
if (this.expiring[txid] < currentDate) {
|
||||||
delete this.cache[c];
|
delete this.expiring[txid];
|
||||||
|
this.remove(txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove a transaction & all previous versions from the cache
|
||||||
|
private remove(txid): void {
|
||||||
|
// don't remove a transaction while a newer version remains in the mempool
|
||||||
|
if (this.replaces[txid] && !this.replacedBy[txid]) {
|
||||||
|
const replaces = this.replaces[txid];
|
||||||
|
delete this.replaces[txid];
|
||||||
|
for (const tx of replaces) {
|
||||||
|
// recursively remove prior versions from the cache
|
||||||
|
delete this.replacedBy[tx];
|
||||||
|
delete this.txs[tx];
|
||||||
|
this.remove(tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,10 @@ class WebsocketHandler {
|
|||||||
client['track-tx'] = parsedMessage['track-tx'];
|
client['track-tx'] = parsedMessage['track-tx'];
|
||||||
// Client is telling the transaction wasn't found
|
// Client is telling the transaction wasn't found
|
||||||
if (parsedMessage['watch-mempool']) {
|
if (parsedMessage['watch-mempool']) {
|
||||||
const rbfCacheTx = rbfCache.get(client['track-tx']);
|
const rbfCacheTxid = rbfCache.getReplacedBy(client['track-tx']);
|
||||||
if (rbfCacheTx) {
|
if (rbfCacheTxid) {
|
||||||
response['txReplaced'] = {
|
response['txReplaced'] = {
|
||||||
txid: rbfCacheTx.txid,
|
txid: rbfCacheTxid,
|
||||||
};
|
};
|
||||||
client['track-tx'] = null;
|
client['track-tx'] = null;
|
||||||
} else {
|
} else {
|
||||||
@ -467,6 +467,7 @@ class WebsocketHandler {
|
|||||||
for (const txId of txIds) {
|
for (const txId of txIds) {
|
||||||
delete _memPool[txId];
|
delete _memPool[txId];
|
||||||
removed.push(txId);
|
removed.push(txId);
|
||||||
|
rbfCache.evict(txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||||
|
@ -6,7 +6,14 @@
|
|||||||
<app-truncate [text]="rbfTransaction.txid" [lastChars]="12" [link]="['/tx/' | relativeUrl, rbfTransaction.txid]"></app-truncate>
|
<app-truncate [text]="rbfTransaction.txid" [lastChars]="12" [link]="['/tx/' | relativeUrl, rbfTransaction.txid]"></app-truncate>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="!rbfTransaction || rbfTransaction?.size">
|
<div *ngIf="rbfReplaces?.length" class="alert alert-mempool" role="alert">
|
||||||
|
<span i18n="transaction.rbf.replaced|RBF replaced">This transaction replaced:</span>
|
||||||
|
<div class="tx-list">
|
||||||
|
<app-truncate [text]="replaced" [lastChars]="12" *ngFor="let replaced of rbfReplaces" [link]="['/tx/' | relativeUrl, replaced]"></app-truncate>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!rbfTransaction || rbfTransaction?.size || tx">
|
||||||
<h1 i18n="shared.transaction">Transaction</h1>
|
<h1 i18n="shared.transaction">Transaction</h1>
|
||||||
|
|
||||||
<span class="tx-link">
|
<span class="tx-link">
|
||||||
@ -25,7 +32,10 @@
|
|||||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="tx && !tx?.status.confirmed">
|
<ng-template [ngIf]="tx && !tx?.status?.confirmed && replaced">
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Replaced</button>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template [ngIf]="tx && !tx?.status?.confirmed && !replaced">
|
||||||
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
@ -88,7 +98,7 @@
|
|||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template [ngIf]="transactionTime !== 0">
|
<ng-template [ngIf]="transactionTime !== 0 && !replaced">
|
||||||
<tr *ngIf="transactionTime === -1; else firstSeenTmpl">
|
<tr *ngIf="transactionTime === -1; else firstSeenTmpl">
|
||||||
<td><span class="skeleton-loader"></span></td>
|
<td><span class="skeleton-loader"></span></td>
|
||||||
<td><span class="skeleton-loader"></span></td>
|
<td><span class="skeleton-loader"></span></td>
|
||||||
@ -100,7 +110,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<tr>
|
<tr *ngIf="!replaced">
|
||||||
<td class="td-width" i18n="transaction.eta|Transaction ETA">ETA</td>
|
<td class="td-width" i18n="transaction.eta|Transaction ETA">ETA</td>
|
||||||
<td>
|
<td>
|
||||||
<ng-template [ngIf]="txInBlockIndex === undefined" [ngIfElse]="estimationTmpl">
|
<ng-template [ngIf]="txInBlockIndex === undefined" [ngIfElse]="estimationTmpl">
|
||||||
|
@ -204,4 +204,10 @@
|
|||||||
.txids {
|
.txids {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-list {
|
||||||
|
.alert-link {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
@ -40,15 +40,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
transactionTime = -1;
|
transactionTime = -1;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
fetchCpfpSubscription: Subscription;
|
fetchCpfpSubscription: Subscription;
|
||||||
|
fetchRbfSubscription: Subscription;
|
||||||
|
fetchCachedTxSubscription: Subscription;
|
||||||
txReplacedSubscription: Subscription;
|
txReplacedSubscription: Subscription;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
queryParamsSubscription: Subscription;
|
queryParamsSubscription: Subscription;
|
||||||
urlFragmentSubscription: Subscription;
|
urlFragmentSubscription: Subscription;
|
||||||
fragmentParams: URLSearchParams;
|
fragmentParams: URLSearchParams;
|
||||||
rbfTransaction: undefined | Transaction;
|
rbfTransaction: undefined | Transaction;
|
||||||
|
replaced: boolean = false;
|
||||||
|
rbfReplaces: string[];
|
||||||
cpfpInfo: CpfpInfo | null;
|
cpfpInfo: CpfpInfo | null;
|
||||||
showCpfpDetails = false;
|
showCpfpDetails = false;
|
||||||
fetchCpfp$ = new Subject<string>();
|
fetchCpfp$ = new Subject<string>();
|
||||||
|
fetchRbfHistory$ = new Subject<string>();
|
||||||
|
fetchCachedTx$ = new Subject<string>();
|
||||||
now = new Date().getTime();
|
now = new Date().getTime();
|
||||||
timeAvg$: Observable<number>;
|
timeAvg$: Observable<number>;
|
||||||
liquidUnblinding = new LiquidUnblinding();
|
liquidUnblinding = new LiquidUnblinding();
|
||||||
@ -159,6 +165,49 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.cpfpInfo = cpfpInfo;
|
this.cpfpInfo = cpfpInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.fetchRbfSubscription = this.fetchRbfHistory$
|
||||||
|
.pipe(
|
||||||
|
switchMap((txId) =>
|
||||||
|
this.apiService
|
||||||
|
.getRbfHistory$(txId)
|
||||||
|
),
|
||||||
|
catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
})
|
||||||
|
).subscribe((replaces) => {
|
||||||
|
this.rbfReplaces = replaces;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetchCachedTxSubscription = this.fetchCachedTx$
|
||||||
|
.pipe(
|
||||||
|
switchMap((txId) =>
|
||||||
|
this.apiService
|
||||||
|
.getRbfCachedTx$(txId)
|
||||||
|
),
|
||||||
|
catchError(() => {
|
||||||
|
return of(null);
|
||||||
|
})
|
||||||
|
).subscribe((tx) => {
|
||||||
|
if (!tx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tx = tx;
|
||||||
|
if (tx.fee === undefined) {
|
||||||
|
this.tx.fee = 0;
|
||||||
|
}
|
||||||
|
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
|
||||||
|
this.isLoadingTx = false;
|
||||||
|
this.error = undefined;
|
||||||
|
this.waitingForTransaction = false;
|
||||||
|
this.graphExpanded = false;
|
||||||
|
this.setupGraph();
|
||||||
|
|
||||||
|
if (!this.tx?.status?.confirmed) {
|
||||||
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.subscription = this.route.paramMap
|
this.subscription = this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
@ -272,6 +321,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.fetchCpfp$.next(this.tx.txid);
|
this.fetchCpfp$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
setTimeout(() => { this.applyFragment(); }, 0);
|
setTimeout(() => { this.applyFragment(); }, 0);
|
||||||
},
|
},
|
||||||
@ -303,6 +353,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.rbfTransaction = rbfTransaction;
|
this.rbfTransaction = rbfTransaction;
|
||||||
this.cacheService.setTxCache([this.rbfTransaction]);
|
this.cacheService.setTxCache([this.rbfTransaction]);
|
||||||
|
this.replaced = true;
|
||||||
|
if (rbfTransaction && !this.tx) {
|
||||||
|
this.fetchCachedTx$.next(this.txId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||||
@ -368,8 +422,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.waitingForTransaction = false;
|
this.waitingForTransaction = false;
|
||||||
this.isLoadingTx = true;
|
this.isLoadingTx = true;
|
||||||
this.rbfTransaction = undefined;
|
this.rbfTransaction = undefined;
|
||||||
|
this.replaced = false;
|
||||||
this.transactionTime = -1;
|
this.transactionTime = -1;
|
||||||
this.cpfpInfo = null;
|
this.cpfpInfo = null;
|
||||||
|
this.rbfReplaces = [];
|
||||||
this.showCpfpDetails = false;
|
this.showCpfpDetails = false;
|
||||||
document.body.scrollTo(0, 0);
|
document.body.scrollTo(0, 0);
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
@ -435,6 +491,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
this.fetchCpfpSubscription.unsubscribe();
|
this.fetchCpfpSubscription.unsubscribe();
|
||||||
|
this.fetchRbfSubscription.unsubscribe();
|
||||||
|
this.fetchCachedTxSubscription.unsubscribe();
|
||||||
this.txReplacedSubscription.unsubscribe();
|
this.txReplacedSubscription.unsubscribe();
|
||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
this.queryParamsSubscription.unsubscribe();
|
this.queryParamsSubscription.unsubscribe();
|
||||||
|
@ -5,7 +5,7 @@ import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITrans
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||||
import { Outspend } from '../interfaces/electrs.interface';
|
import { Outspend, Transaction } from '../interfaces/electrs.interface';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -119,6 +119,14 @@ export class ApiService {
|
|||||||
return this.httpClient.get<AddressInformation>(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address);
|
return this.httpClient.get<AddressInformation>(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRbfHistory$(txid: string): Observable<string[]> {
|
||||||
|
return this.httpClient.get<string[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/replaces');
|
||||||
|
}
|
||||||
|
|
||||||
|
getRbfCachedTx$(txid: string): Observable<Transaction> {
|
||||||
|
return this.httpClient.get<Transaction>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/cached');
|
||||||
|
}
|
||||||
|
|
||||||
listLiquidPegsMonth$(): Observable<LiquidPegs[]> {
|
listLiquidPegsMonth$(): Observable<LiquidPegs[]> {
|
||||||
return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
|
return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user