Add basic transaction link previews
This commit is contained in:
		
							parent
							
								
									a6b1d4059f
								
							
						
					
					
						commit
						e0ea47b8ee
					
				@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
 | 
			
		||||
import { StartComponent } from './components/start/start.component';
 | 
			
		||||
import { TransactionComponent } from './components/transaction/transaction.component';
 | 
			
		||||
import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
 | 
			
		||||
import { BlockComponent } from './components/block/block.component';
 | 
			
		||||
import { BlockAuditComponent } from './components/block-audit/block-audit.component';
 | 
			
		||||
import { BlockPreviewComponent } from './components/block/block-preview.component';
 | 
			
		||||
@ -366,6 +367,21 @@ let routes: Routes = [
 | 
			
		||||
        children: [],
 | 
			
		||||
        component: AddressPreviewComponent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'tx/:id',
 | 
			
		||||
        children: [],
 | 
			
		||||
        component: TransactionPreviewComponent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'testnet/tx/:id',
 | 
			
		||||
        children: [],
 | 
			
		||||
        component: TransactionPreviewComponent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'signet/tx/:id',
 | 
			
		||||
        children: [],
 | 
			
		||||
        component: TransactionPreviewComponent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'lightning',
 | 
			
		||||
        loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
 | 
			
		||||
@ -643,6 +659,16 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
 | 
			
		||||
          children: [],
 | 
			
		||||
          component: AddressPreviewComponent
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          path: 'tx/:id',
 | 
			
		||||
          children: [],
 | 
			
		||||
          component: TransactionPreviewComponent
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          path: 'testnet/tx/:id',
 | 
			
		||||
          children: [],
 | 
			
		||||
          component: TransactionPreviewComponent
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,116 @@
 | 
			
		||||
<div class="box preview-box" *ngIf="tx && !error">
 | 
			
		||||
 | 
			
		||||
  <div class="page-title">
 | 
			
		||||
    <h1 i18n="shared.transaction">Transaction</h1>
 | 
			
		||||
    <div *ngIf="network !== 'liquid' && network !== 'liquidtestnet'" class="features">
 | 
			
		||||
      <app-tx-features [tx]="tx"></app-tx-features>
 | 
			
		||||
      <span *ngIf="cpfpInfo && cpfpInfo.bestDescendant" class="badge badge-primary mr-1">
 | 
			
		||||
        CPFP
 | 
			
		||||
      </span>
 | 
			
		||||
      <span *ngIf="cpfpInfo && !cpfpInfo.bestDescendant && cpfpInfo.ancestors.length" class="badge badge-info mr-1">
 | 
			
		||||
        CPFP
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <a [routerLink]="['/tx/' | relativeUrl, txId]" class="tx-link">
 | 
			
		||||
    {{ txId }}
 | 
			
		||||
  </a>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col-sm">
 | 
			
		||||
      <table class="table table-borderless table-striped">
 | 
			
		||||
        <tbody>
 | 
			
		||||
          <tr *ngIf="tx.status.confirmed; else firstSeen">
 | 
			
		||||
            <td i18n="block.timestamp">Timestamp</td>
 | 
			
		||||
            <td>
 | 
			
		||||
              ‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <ng-template #firstSeen>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="transaction.first-seen|Transaction first seen">First seen</td>
 | 
			
		||||
              <td *ngIf="transactionTime > 0; else notSeen">
 | 
			
		||||
                ‎{{ transactionTime * 1000 | date:'yyyy-MM-dd HH:mm' }}
 | 
			
		||||
              </td>
 | 
			
		||||
              <ng-template #notSeen>
 | 
			
		||||
                <td>?</td>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
            </tr>
 | 
			
		||||
          </ng-template>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td>
 | 
			
		||||
            <td>
 | 
			
		||||
              <ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
 | 
			
		||||
              <ng-template #defaultAmount>
 | 
			
		||||
                <app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="block.size">Size</td>
 | 
			
		||||
            <td [innerHTML]="'‎' + (tx.size | bytes: 2)"></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="block.weight">Weight</td>
 | 
			
		||||
            <td [innerHTML]="'‎' + (tx.weight | wuBytes: 2)"></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="transaction.inputs">Inputs</td>
 | 
			
		||||
            <td *ngIf="!isCoinbase(tx); else coinbaseInputs">{{ tx.vin.length }}</td>
 | 
			
		||||
            <ng-template #coinbaseInputs>
 | 
			
		||||
              <td i18n="transactions-list.coinbase">Coinbase</td>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="col-sm">
 | 
			
		||||
      <table class="table table-borderless table-striped">
 | 
			
		||||
        <tbody>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
 | 
			
		||||
            <td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr *ngIf="!cpfpInfo || (!cpfpInfo.bestDescendant && !cpfpInfo.ancestors.length); else cpfpFee">
 | 
			
		||||
            <td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
 | 
			
		||||
            <td>
 | 
			
		||||
              {{ tx.feePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
 | 
			
		||||
              <ng-template [ngIf]="tx.status.confirmed">
 | 
			
		||||
                 
 | 
			
		||||
                <app-tx-fee-rating *ngIf="tx.fee && ((cpfpInfo && !cpfpInfo.bestDescendant && !cpfpInfo.ancestors.length) || !cpfpInfo)" [tx]="tx"></app-tx-fee-rating>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <ng-template #cpfpFee>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <div class="effective-fee-container">
 | 
			
		||||
                  {{ tx.effectiveFeePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
 | 
			
		||||
                  <ng-template [ngIf]="tx.status.confirmed">
 | 
			
		||||
                    <app-tx-fee-rating class="d-none d-lg-inline ml-2" *ngIf="tx.fee" [tx]="tx"></app-tx-fee-rating>
 | 
			
		||||
                  </ng-template>
 | 
			
		||||
                </div>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
          </ng-template>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
 | 
			
		||||
            <td [innerHTML]="'‎' + (tx.weight / 4 | vbytes: 2)"></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="transaction.locktime">Locktime</td>
 | 
			
		||||
            <td [innerHTML]="'‎' + (tx.locktime | number)"></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="transaction.outputs">Outputs</td>
 | 
			
		||||
            <td>{{ tx.vout.length }}</td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
.adjust-btn-padding {
 | 
			
		||||
  padding: 0.55rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.td-width {
 | 
			
		||||
	width: 150px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::ng-deep .badge {
 | 
			
		||||
	font-size: 28px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-small-height {
 | 
			
		||||
	line-height: 1.1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.arrow-green {
 | 
			
		||||
	color: #1a9436;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.arrow-red {
 | 
			
		||||
	color: #dc3545;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row {
 | 
			
		||||
	flex-direction: row;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.effective-fee-container {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title {
 | 
			
		||||
  h2 {
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding-bottom: 10px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-outline-info {
 | 
			
		||||
	margin-top: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page-title {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
 | 
			
		||||
  h1 {
 | 
			
		||||
    font-size: 52px;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .features {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
  font-size: 32px;
 | 
			
		||||
 | 
			
		||||
  ::ng-deep .symbol {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tx-link {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  font-size: 28px;
 | 
			
		||||
  margin-bottom: 6px;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,224 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import {
 | 
			
		||||
  switchMap,
 | 
			
		||||
  filter,
 | 
			
		||||
  catchError,
 | 
			
		||||
  retryWhen,
 | 
			
		||||
  delay,
 | 
			
		||||
  map
 | 
			
		||||
} from 'rxjs/operators';
 | 
			
		||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { CpfpInfo } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { LiquidUnblinding } from './liquid-ublinding';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-transaction-preview',
 | 
			
		||||
  templateUrl: './transaction-preview.component.html',
 | 
			
		||||
  styleUrls: ['./transaction-preview.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TransactionPreviewComponent implements OnInit, OnDestroy {
 | 
			
		||||
  network = '';
 | 
			
		||||
  tx: Transaction;
 | 
			
		||||
  txId: string;
 | 
			
		||||
  isLoadingTx = true;
 | 
			
		||||
  error: any = undefined;
 | 
			
		||||
  errorUnblinded: any = undefined;
 | 
			
		||||
  transactionTime = -1;
 | 
			
		||||
  subscription: Subscription;
 | 
			
		||||
  fetchCpfpSubscription: Subscription;
 | 
			
		||||
  cpfpInfo: CpfpInfo | null;
 | 
			
		||||
  showCpfpDetails = false;
 | 
			
		||||
  fetchCpfp$ = new Subject<string>();
 | 
			
		||||
  liquidUnblinding = new LiquidUnblinding();
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private electrsApiService: ElectrsApiService,
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    private openGraphService: OpenGraphService,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.stateService.networkChanged$.subscribe(
 | 
			
		||||
      (network) => (this.network = network)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this.fetchCpfpSubscription = this.fetchCpfp$
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((txId) =>
 | 
			
		||||
          this.apiService
 | 
			
		||||
            .getCpfpinfo$(txId)
 | 
			
		||||
            .pipe(retryWhen((errors) => errors.pipe(delay(2000))))
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe((cpfpInfo) => {
 | 
			
		||||
        if (!this.tx) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        const lowerFeeParents = cpfpInfo.ancestors.filter(
 | 
			
		||||
          (parent) => parent.fee / (parent.weight / 4) < this.tx.feePerVsize
 | 
			
		||||
        );
 | 
			
		||||
        let totalWeight =
 | 
			
		||||
          this.tx.weight +
 | 
			
		||||
          lowerFeeParents.reduce((prev, val) => prev + val.weight, 0);
 | 
			
		||||
        let totalFees =
 | 
			
		||||
          this.tx.fee +
 | 
			
		||||
          lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
 | 
			
		||||
 | 
			
		||||
        if (cpfpInfo.bestDescendant) {
 | 
			
		||||
          totalWeight += cpfpInfo.bestDescendant.weight;
 | 
			
		||||
          totalFees += cpfpInfo.bestDescendant.fee;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
 | 
			
		||||
        this.stateService.markBlock$.next({
 | 
			
		||||
          txFeePerVSize: this.tx.effectiveFeePerVsize,
 | 
			
		||||
        });
 | 
			
		||||
        this.cpfpInfo = cpfpInfo;
 | 
			
		||||
        this.openGraphService.waitOver('cpfp-data');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    this.subscription = this.route.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((params: ParamMap) => {
 | 
			
		||||
          this.openGraphService.waitFor('tx-data');
 | 
			
		||||
          const urlMatch = (params.get('id') || '').split(':');
 | 
			
		||||
          this.txId = urlMatch[0];
 | 
			
		||||
          this.seoService.setTitle(
 | 
			
		||||
            $localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
 | 
			
		||||
          );
 | 
			
		||||
          this.resetTransaction();
 | 
			
		||||
          return merge(
 | 
			
		||||
            of(true),
 | 
			
		||||
            this.stateService.connectionState$.pipe(
 | 
			
		||||
              filter(
 | 
			
		||||
                (state) => state === 2 && this.tx && !this.tx.status.confirmed
 | 
			
		||||
              )
 | 
			
		||||
            )
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        switchMap(() => {
 | 
			
		||||
          let transactionObservable$: Observable<Transaction>;
 | 
			
		||||
          if (history.state.data && history.state.data.fee !== -1) {
 | 
			
		||||
            transactionObservable$ = of(history.state.data);
 | 
			
		||||
          } else {
 | 
			
		||||
            transactionObservable$ = this.electrsApiService
 | 
			
		||||
              .getTransaction$(this.txId)
 | 
			
		||||
              .pipe(
 | 
			
		||||
                catchError(error => {
 | 
			
		||||
                  this.error = error;
 | 
			
		||||
                  this.isLoadingTx = false;
 | 
			
		||||
                  return of(null);
 | 
			
		||||
                })
 | 
			
		||||
              );
 | 
			
		||||
          }
 | 
			
		||||
          return merge(
 | 
			
		||||
            transactionObservable$,
 | 
			
		||||
            this.stateService.mempoolTransactions$
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        switchMap((tx) => {
 | 
			
		||||
          if (this.network === 'liquid' || this.network === 'liquidtestnet') {
 | 
			
		||||
            return from(this.liquidUnblinding.checkUnblindedTx(tx))
 | 
			
		||||
              .pipe(
 | 
			
		||||
                catchError((error) => {
 | 
			
		||||
                  this.errorUnblinded = error;
 | 
			
		||||
                  return of(tx);
 | 
			
		||||
                })
 | 
			
		||||
              );
 | 
			
		||||
          }
 | 
			
		||||
          return of(tx);
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe((tx: Transaction) => {
 | 
			
		||||
          if (!tx) {
 | 
			
		||||
            this.openGraphService.fail('tx-data');
 | 
			
		||||
            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;
 | 
			
		||||
 | 
			
		||||
          if (!tx.status.confirmed && tx.firstSeen) {
 | 
			
		||||
            this.transactionTime = tx.firstSeen;
 | 
			
		||||
          } else {
 | 
			
		||||
            this.getTransactionTime();
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (!this.tx.status.confirmed) {
 | 
			
		||||
            if (tx.cpfpChecked) {
 | 
			
		||||
              this.cpfpInfo = {
 | 
			
		||||
                ancestors: tx.ancestors,
 | 
			
		||||
                bestDescendant: tx.bestDescendant,
 | 
			
		||||
              };
 | 
			
		||||
            } else {
 | 
			
		||||
              this.openGraphService.waitFor('cpfp-data');
 | 
			
		||||
              this.fetchCpfp$.next(this.tx.txid);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.openGraphService.waitOver('tx-data');
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.openGraphService.fail('tx-data');
 | 
			
		||||
          this.error = error;
 | 
			
		||||
          this.isLoadingTx = false;
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTransactionTime() {
 | 
			
		||||
    this.openGraphService.waitFor('tx-time');
 | 
			
		||||
    this.apiService
 | 
			
		||||
      .getTransactionTimes$([this.tx.txid])
 | 
			
		||||
      .pipe(
 | 
			
		||||
        catchError((err) => {
 | 
			
		||||
          return of(0);
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe((transactionTimes) => {
 | 
			
		||||
        this.transactionTime = transactionTimes[0];
 | 
			
		||||
        this.openGraphService.waitOver('tx-time');
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  resetTransaction() {
 | 
			
		||||
    this.error = undefined;
 | 
			
		||||
    this.tx = null;
 | 
			
		||||
    this.isLoadingTx = true;
 | 
			
		||||
    this.transactionTime = -1;
 | 
			
		||||
    this.cpfpInfo = null;
 | 
			
		||||
    this.showCpfpDetails = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isCoinbase(tx: Transaction): boolean {
 | 
			
		||||
    return tx.vin.some((v: any) => v.is_coinbase === true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  haveBlindedOutputValues(tx: Transaction): boolean {
 | 
			
		||||
    return tx.vout.some((v: any) => v.value === undefined);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTotalTxOutput(tx: Transaction) {
 | 
			
		||||
    return tx.vout.map((v: Vout) => v.value || 0).reduce((a: number, b: number) => a + b);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    this.subscription.unsubscribe();
 | 
			
		||||
    this.fetchCpfpSubscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -43,6 +43,7 @@ import { RouterModule } from '@angular/router';
 | 
			
		||||
import { CapAddressPipe } from './pipes/cap-address-pipe/cap-address-pipe';
 | 
			
		||||
import { StartComponent } from '../components/start/start.component';
 | 
			
		||||
import { TransactionComponent } from '../components/transaction/transaction.component';
 | 
			
		||||
import { TransactionPreviewComponent } from '../components/transaction/transaction-preview.component';
 | 
			
		||||
import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component';
 | 
			
		||||
import { BlockComponent } from '../components/block/block.component';
 | 
			
		||||
import { BlockPreviewComponent } from '../components/block/block-preview.component';
 | 
			
		||||
@ -118,6 +119,7 @@ import { ToggleComponent } from './components/toggle/toggle.component';
 | 
			
		||||
    LiquidMasterPageComponent,
 | 
			
		||||
    StartComponent,
 | 
			
		||||
    TransactionComponent,
 | 
			
		||||
    TransactionPreviewComponent,
 | 
			
		||||
    BlockComponent,
 | 
			
		||||
    BlockPreviewComponent,
 | 
			
		||||
    BlockAuditComponent,
 | 
			
		||||
@ -220,6 +222,7 @@ import { ToggleComponent } from './components/toggle/toggle.component';
 | 
			
		||||
    AmountComponent,
 | 
			
		||||
    StartComponent,
 | 
			
		||||
    TransactionComponent,
 | 
			
		||||
    TransactionPreviewComponent,
 | 
			
		||||
    BlockComponent,
 | 
			
		||||
    BlockPreviewComponent,
 | 
			
		||||
    BlockAuditComponent,
 | 
			
		||||
 | 
			
		||||
@ -157,6 +157,9 @@ class Server {
 | 
			
		||||
      case 'address':
 | 
			
		||||
        ogTitle = `Address: ${parts[1]}`;
 | 
			
		||||
      break;
 | 
			
		||||
      case 'tx':
 | 
			
		||||
        ogTitle = `Transaction: ${parts[1]}`;
 | 
			
		||||
      break;
 | 
			
		||||
      case 'lightning':
 | 
			
		||||
        switch (parts[1]) {
 | 
			
		||||
          case 'node':
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user