Merge branch 'master' into regtest-1

This commit is contained in:
Antoni Spaanderman
2022-02-09 10:42:23 +01:00
committed by GitHub
66 changed files with 1397 additions and 450 deletions

View File

@@ -2,7 +2,7 @@
<div class="title-asset">
<h1 i18n="asset|Liquid Asset page title">Asset</h1>
<div class="tx-link">
<a [routerLink]="['/asset/' | relativeUrl, assetString]">
<a [routerLink]="['/assets/asset/' | relativeUrl, assetString]">
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ assetString }}</span>
</a>
@@ -20,7 +20,7 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td i18n="asset.name|Liquid Asset name">Name</td>
<td i18n="Asset name header">Name</td>
<td class="assetName">{{ assetContract[2] }} ({{ assetContract[1] }})</td>
</tr>
<tr>

View File

@@ -63,6 +63,7 @@ export class AssetComponent implements OnInit, OnDestroy {
.pipe(
switchMap((params: ParamMap) => {
this.error = undefined;
this.imageError = false;
this.isLoadingAsset = true;
this.loadedConfirmedTxCount = 0;
this.asset = null;

View File

@@ -0,0 +1,35 @@
<div *ngIf="group$ | async as group; else loading">
<div class="main-title">
<h2>{{ group.group.name }}</h2>
<div class="sub-title" i18n>Group of {{ group.group.assets.length | number }} assets</div>
</div>
<div class="clearfix"></div>
<br>
<div class="featuredBox">
<div *ngFor="let asset of group.assets">
<div class="card">
<a [routerLink]="['/assets/asset' | relativeUrl, asset.asset_id]">
<img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + asset.asset_id + '/icon'">
</a>
<div class="title">
<a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a>
</div>
<div class="ticker">{{ asset.ticker }}</div>
</div>
</div>
</div>
</div>
<ng-template #loading>
<br>
<div class="text-center loadingGraphs">
<div class="spinner-border text-light"></div>
</div>
</ng-template>

View File

@@ -0,0 +1,60 @@
.image {
width: 150px;
float: left;
}
.main-title {
float: left
}
.sub-title {
color: grey;
}
.featuredBox {
display: flex;
flex-flow: row wrap;
justify-content: center;
gap: 27px;
}
.card {
background-color: #1d1f31;
width: 200px;
height: 200px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
@media (max-width: 767.98px) {
width: 150px;
height: 150px;
}
}
.title {
font-size: 14px;
font-weight: bold;
margin-top: 10px;
text-align: center;
}
.sub-title {
color: grey;
}
.assetIcon {
width: 100px;
height: 100px;
@media (max-width: 767.98px) {
width: 50px;
height: 50px;
}
}
.view-link {
margin-top: 30px;
}
.ticker {
color: grey;
}

View File

@@ -0,0 +1,44 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { combineLatest, Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ApiService } from 'src/app/services/api.service';
import { AssetsService } from 'src/app/services/assets.service';
@Component({
selector: 'app-asset-group',
templateUrl: './asset-group.component.html',
styleUrls: ['./asset-group.component.scss']
})
export class AssetGroupComponent implements OnInit {
group$: Observable<any>;
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
private assetsService: AssetsService,
) { }
ngOnInit(): void {
this.group$ = this.route.paramMap
.pipe(
switchMap((params: ParamMap) => {
return combineLatest([
this.assetsService.getAssetsJson$,
this.apiService.getAssetGroup$(params.get('id')),
]);
}),
map(([assets, group]) => {
const items = [];
// @ts-ignore
for (const item of group.assets) {
items.push(assets.objects[item]);
}
return {
group: group,
assets: items
};
})
);
}
}

View File

@@ -0,0 +1,29 @@
<div *ngIf="featuredAssets$ | async as featured; else loading" class="featuredBox">
<div class="card" *ngFor="let group of featured">
<ng-template [ngIf]="group.assets" [ngIfElse]="singleAsset">
<a [routerLink]="['/assets/group' | relativeUrl, group.id]">
<img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + group.assets[0] + '/icon'">
</a>
<div class="title"><a [routerLink]="['/assets/group' | relativeUrl, group.id]">{{ group.name }}</a></div>
<div class="sub-title" i18n>Group of {{ group.assets.length | number }} assets</div>
</ng-template>
<ng-template #singleAsset>
<a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">
<img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + group.asset + '/icon'">
</a>
<div class="title">
<a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">{{ group.name }}</a>
</div>
<div class="ticker">{{ group.ticker }}</div>
</ng-template>
</div>
</div>
<ng-template #loading>
<br>
<div class="text-center loadingGraphs">
<div class="spinner-border text-light"></div>
</div>
</ng-template>

View File

@@ -0,0 +1,49 @@
.featuredBox {
display: flex;
flex-flow: row wrap;
justify-content: center;
gap: 27px;
}
.card {
background-color: #1d1f31;
width: 200px;
height: 200px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
@media (max-width: 767.98px) {
width: 150px;
height: 150px;
}
}
.title {
font-size: 14px;
font-weight: bold;
margin-top: 10px;
text-align: center;
}
.sub-title {
color: grey;
font-size: 12px;
}
.assetIcon {
width: 100px;
height: 100px;
@media (max-width: 767.98px) {
width: 50px;
height: 50px;
}
}
.view-link {
margin-top: 30px;
}
.ticker {
color: grey;
}

View File

@@ -0,0 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from 'src/app/services/api.service';
@Component({
selector: 'app-assets-featured',
templateUrl: './assets-featured.component.html',
styleUrls: ['./assets-featured.component.scss']
})
export class AssetsFeaturedComponent implements OnInit {
featuredAssets$: Observable<any>;
constructor(
private apiService: ApiService,
) { }
ngOnInit(): void {
this.featuredAssets$ = this.apiService.listFeaturedAssets$();
}
}

View File

@@ -0,0 +1,33 @@
<div class="container-xl">
<div class="title-asset">
<h1 i18n="Assets page header">Assets</h1>
</div>
<div class="nav-container">
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" [routerLink]="['/assets/featured' | relativeUrl]" routerLinkActive="active" i18n>Featured</a>
</li>
<li class="nav-item">
<a class="nav-link" [routerLink]="['/assets/all' | relativeUrl]" routerLinkActive="active" i18n>All</a>
</li>
</ul>
<form [formGroup]="searchForm">
<div class="input-group mb-2">
<input #instance="ngbTypeahead" [ngbTypeahead]="typeaheadSearchFn" [resultFormatter]="formatterFn" (selectItem)="itemSelected()" (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="Search Assets Placeholder Text" placeholder="Search asset">
<div class="input-group-append">
<button [disabled]="!searchForm.get('searchText')?.value.length" class="btn btn-secondary" type="button" (click)="searchForm.get('searchText')?.setValue('');" autocomplete="off" i18n="Search Clear Button">Clear</button>
</div>
</div>
</form>
</div>
<div class="clearfix"></div>
<router-outlet></router-outlet>
</div>
<br>

View File

@@ -0,0 +1,24 @@
ul {
margin-bottom: 20px;
float: left;
}
form {
float: right;
width: 300px;
@media (max-width: 767.98px) {
width: 90%;
margin-bottom: 15px;
}
}
@media (max-width: 767.98px) {
.nav-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: auto;
}
}

View File

@@ -0,0 +1,95 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
import { merge, Observable, of, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
import { AssetExtended } from 'src/app/interfaces/electrs.interface';
import { AssetsService } from 'src/app/services/assets.service';
import { SeoService } from 'src/app/services/seo.service';
import { StateService } from 'src/app/services/state.service';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-assets-nav',
templateUrl: './assets-nav.component.html',
styleUrls: ['./assets-nav.component.scss']
})
export class AssetsNavComponent implements OnInit {
@ViewChild('instance', {static: true}) instance: NgbTypeahead;
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
searchForm: FormGroup;
assetsCache: AssetExtended[];
typeaheadSearchFn: ((text: Observable<string>) => Observable<readonly any[]>);
formatterFn = (asset: AssetExtended) => asset.name + ' (' + asset.ticker + ')';
focus$ = new Subject<string>();
click$ = new Subject<string>();
itemsPerPage = 15;
constructor(
private formBuilder: FormBuilder,
private seoService: SeoService,
private router: Router,
private assetsService: AssetsService,
private stateService: StateService,
private relativeUrlPipe: RelativeUrlPipe,
) { }
ngOnInit(): void {
this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`);
this.typeaheadSearchFn = this.typeaheadSearch;
this.searchForm = this.formBuilder.group({
searchText: [{ value: '', disabled: false }, Validators.required]
});
}
typeaheadSearch = (text$: Observable<string>) => {
const debouncedText$ = text$.pipe(
distinctUntilChanged()
);
const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
const inputFocus$ = this.focus$;
return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$)
.pipe(
switchMap((searchText) => {
if (!searchText.length) {
return of([]);
}
return this.assetsService.getAssetsJson$.pipe(
map((assets) => {
if (searchText.length ) {
const filteredAssets = assets.array.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
|| (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1
|| (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1);
return filteredAssets.slice(0, this.itemsPerPage);
} else {
return assets.array.slice(0, this.itemsPerPage);
}
})
)
}),
);
}
itemSelected() {
setTimeout(() => this.search());
}
search() {
const searchText = this.searchForm.value.searchText;
this.navigate('/assets/asset/', searchText.asset_id);
}
navigate(url: string, searchText: string, extras?: any) {
this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras);
this.searchForm.setValue({
searchText: '',
});
}
}

View File

@@ -0,0 +1,52 @@
<ng-container *ngIf="(assets$ | async) as filteredAssets; else isLoading">
<table class="table table-borderless table-striped">
<thead>
<th class="td-name" i18n="Asset name header">Name</th>
<th i18n="Asset ticker header">Ticker</th>
<th class="d-none d-md-table-cell" i18n="Asset Issuer Domain header">Issuer domain</th>
<th class="d-none d-lg-table-cell" i18n="Asset ID header">Asset ID</th>
</thead>
<tbody>
<tr *ngFor="let asset of filteredAssets; trackBy: trackByAsset">
<td class="td-name"><a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a></td>
<td>{{ asset.ticker }}</td>
<td class="d-none d-md-table-cell">{{ asset.entity && asset.entity.domain }}</td>
<td class="d-none d-lg-table-cell"><a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
</tr>
</tbody>
</table>
<br>
<ngb-pagination [collectionSize]="assets.length" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="ellipses"></ngb-pagination>
</ng-container>
<ng-template #isLoading>
<table class="table table-borderless table-striped">
<thead>
<th i18n="Asset name header">Name</th>
<th i18n="Asset ticker header">Ticker</th>
<th class="d-none d-md-table-cell" i18n="Asset Issuer Domain header">Issuer domain</th>
<th class="d-none d-lg-table-cell" i18n="Asset ID header">Asset ID</th>
</thead>
<tbody>
<tr *ngFor="let dummy of [0,0,0,0,0,0,0,0,0,0]">
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
<td class="d-none d-md-table-cell"><span class="skeleton-loader"></span></td>
<td class="d-none d-lg-table-cell"><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</ng-template>
<ng-template [ngIf]="error">
<div class="text-center">
<ng-container i18n="Asset data load error">Error loading assets data.</ng-container>
<br>
<i>{{ error.error }}</i>
</div>
</ng-template>

View File

@@ -0,0 +1,13 @@
.td-name {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.title-asset {
h1 {
line-height: 1;
margin: 0px;
padding-bottom: 10px;
}
}

View File

@@ -0,0 +1,99 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { AssetsService } from 'src/app/services/assets.service';
import { environment } from 'src/environments/environment';
import { FormGroup } from '@angular/forms';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { combineLatest, Observable } from 'rxjs';
import { AssetExtended } from 'src/app/interfaces/electrs.interface';
import { SeoService } from 'src/app/services/seo.service';
import { StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-assets',
templateUrl: './assets.component.html',
styleUrls: ['./assets.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetsComponent implements OnInit {
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 4 : 6;
ellipses = window.matchMedia('(max-width: 670px)').matches ? false : true;
assets: AssetExtended[];
assetsCache: AssetExtended[];
searchForm: FormGroup;
assets$: Observable<AssetExtended[]>;
page = 1;
error: any;
itemsPerPage: number;
contentSpace = window.innerHeight - (250 + 200);
fiveItemsPxSize = 250;
constructor(
private assetsService: AssetsService,
private route: ActivatedRoute,
private router: Router,
private seoService: SeoService,
private stateService: StateService,
) { }
ngOnInit() {
this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`);
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
this.assets$ = combineLatest([
this.assetsService.getAssetsJson$,
this.route.queryParams,
])
.pipe(
take(1),
switchMap(([assets, qp]) => {
this.assets = assets.array;
return this.route.queryParams
.pipe(
filter((queryParams) => {
const newPage = parseInt(queryParams.page, 10);
if (newPage !== this.page) {
return true;
}
return false;
}),
map((queryParams) => {
if (queryParams.page) {
const newPage = parseInt(queryParams.page, 10);
this.page = newPage;
} else {
this.page = 1;
}
return '';
})
);
}),
map(() => {
const start = (this.page - 1) * this.itemsPerPage;
return this.assets.slice(start, this.itemsPerPage + start);
})
);
}
pageChange(page: number) {
const queryParams = { page: page };
if (queryParams.page === 1) {
queryParams.page = null;
}
this.page = -1;
this.router.navigate([], {
relativeTo: this.route,
queryParams: queryParams,
queryParamsHandling: 'merge',
});
}
trackByAsset(index: number, asset: any) {
return asset.asset_id;
}
}

View File

@@ -74,9 +74,9 @@
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr *ngIf="block.medianFee !== undefined">
<tr *ngIf="block?.extras?.medianFee != undefined">
<td class="td-width" i18n="block.median-fee">Median fee</td>
<td>~{{ block.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
</tr>
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
<tr>

View File

@@ -3,12 +3,13 @@ import { Location } from '@angular/common';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { switchMap, tap, debounceTime, catchError, map } from 'rxjs/operators';
import { Block, Transaction, Vout } from '../../interfaces/electrs.interface';
import { Transaction, Vout } from '../../interfaces/electrs.interface';
import { Observable, of, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { SeoService } from 'src/app/services/seo.service';
import { WebsocketService } from 'src/app/services/websocket.service';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({
selector: 'app-block',
@@ -17,13 +18,13 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
})
export class BlockComponent implements OnInit, OnDestroy {
network = '';
block: Block;
block: BlockExtended;
blockHeight: number;
nextBlockHeight: number;
blockHash: string;
isLoadingBlock = true;
latestBlock: Block;
latestBlocks: Block[] = [];
latestBlock: BlockExtended;
latestBlocks: BlockExtended[] = [];
transactions: Transaction[];
isLoadingTransactions = true;
error: any;
@@ -76,7 +77,9 @@ export class BlockComponent implements OnInit, OnDestroy {
if (block.id === this.blockHash) {
this.block = block;
this.fees = block.reward / 100000000 - this.blockSubsidy;
if (block?.extras?.reward != undefined) {
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
}
}
});
@@ -108,7 +111,7 @@ export class BlockComponent implements OnInit, OnDestroy {
} else {
this.isLoadingBlock = true;
let blockInCache: Block;
let blockInCache: BlockExtended;
if (isBlockHeight) {
blockInCache = this.latestBlocks.find((block) => block.height === parseInt(blockHash, 10));
if (blockInCache) {
@@ -134,7 +137,7 @@ export class BlockComponent implements OnInit, OnDestroy {
return this.electrsApiService.getBlock$(blockHash);
}
}),
tap((block: Block) => {
tap((block: BlockExtended) => {
this.block = block;
this.blockHeight = block.height;
this.nextBlockHeight = block.height + 1;
@@ -142,12 +145,10 @@ export class BlockComponent implements OnInit, OnDestroy {
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
this.isLoadingBlock = false;
if (block.coinbaseTx) {
this.coinbaseTx = block.coinbaseTx;
}
this.coinbaseTx = block?.extras?.coinbaseTx;
this.setBlockSubsidy();
if (block.reward !== undefined) {
this.fees = block.reward / 100000000 - this.blockSubsidy;
if (block?.extras?.reward !== undefined) {
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
}
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
this.isLoadingTransactions = true;

View File

@@ -8,10 +8,10 @@
</div>
<div class="block-body">
<div class="fees">
~{{ block.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
</div>
<div class="fee-span">
{{ block.feeRange[1] | number:feeRounding }} - {{ block.feeRange[block.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
{{ block?.extras?.feeRange[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
</div>
<div class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div>
<div class="transaction-count">

View File

@@ -1,9 +1,9 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { Block } from 'src/app/interfaces/electrs.interface';
import { StateService } from 'src/app/services/state.service';
import { Router } from '@angular/router';
import { specialBlocks } from 'src/app/app.constants';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({
selector: 'app-blockchain-blocks',
@@ -14,8 +14,8 @@ import { specialBlocks } from 'src/app/app.constants';
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
specialBlocks = specialBlocks;
network = '';
blocks: Block[] = [];
emptyBlocks: Block[] = this.mountEmptyBlocks();
blocks: BlockExtended[] = [];
emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
markHeight: number;
blocksSubscription: Subscription;
networkSubscription: Subscription;
@@ -70,8 +70,8 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
this.blocks.unshift(block);
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
if (this.blocksFilled && !this.tabHidden) {
block.stage = block.matchRate >= 66 ? 1 : 2;
if (this.blocksFilled && !this.tabHidden && block.extras) {
block.extras.stage = block.extras.matchRate >= 66 ? 1 : 2;
}
if (txConfirmed) {
@@ -144,16 +144,16 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
}
}
trackByBlocksFn(index: number, item: Block) {
trackByBlocksFn(index: number, item: BlockExtended) {
return item.height;
}
getStyleForBlock(block: Block) {
getStyleForBlock(block: BlockExtended) {
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
let addLeft = 0;
if (block.stage === 1) {
block.stage = 2;
if (block?.extras?.stage === 1) {
block.extras.stage = 2;
addLeft = -205;
}
@@ -168,11 +168,11 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
};
}
getStyleForEmptyBlock(block: Block) {
getStyleForEmptyBlock(block: BlockExtended) {
let addLeft = 0;
if (block.stage === 1) {
block.stage = 2;
if (block?.extras?.stage === 1) {
block.extras.stage = 2;
addLeft = -205;
}

View File

@@ -153,7 +153,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
this.blockSubscription = this.stateService.blocks$
.subscribe(([block]) => {
if (block.matchRate >= 66 && !this.tabHidden) {
if (block?.extras?.matchRate >= 66 && !this.tabHidden) {
this.blockIndex++;
}
});

View File

@@ -105,11 +105,11 @@ export class SearchFormComponent implements OnInit {
const matches = this.regexTransaction.exec(searchText);
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.assets[matches[1]]) {
this.navigate('/asset/', matches[1]);
this.navigate('/assets/asset/', matches[1]);
}
this.electrsApiService.getAsset$(matches[1])
.subscribe(
() => { this.navigate('/asset/', matches[1]); },
() => { this.navigate('/assets/asset/', matches[1]); },
() => {
this.electrsApiService.getBlock$(matches[1])
.subscribe(

View File

@@ -9,14 +9,14 @@ import {
delay,
map
} from 'rxjs/operators';
import { Transaction, Block } from '../../interfaces/electrs.interface';
import { Transaction } from '../../interfaces/electrs.interface';
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
import { StateService } from '../../services/state.service';
import { WebsocketService } from '../../services/websocket.service';
import { AudioService } from 'src/app/services/audio.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 { BlockExtended, CpfpInfo } from 'src/app/interfaces/node-api.interface';
import { LiquidUnblinding } from './liquid-ublinding';
@Component({
@@ -33,7 +33,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
error: any = undefined;
errorUnblinded: any = undefined;
waitingForTransaction = false;
latestBlock: Block;
latestBlock: BlockExtended;
transactionTime = -1;
subscription: Subscription;
fetchCpfpSubscription: Subscription;

View File

@@ -274,5 +274,5 @@
<br />
{{ assetsMinimal[item.asset][0] }}
<br />
<a [routerLink]="['/asset/' | relativeUrl, item.asset]">{{ item.asset | shortenString : 13 }}</a>
<a [routerLink]="['/assets/asset/' | relativeUrl, item.asset]">{{ item.asset | shortenString : 13 }}</a>
</ng-template>

View File

@@ -1,11 +1,12 @@
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, Output, EventEmitter } from '@angular/core';
import { StateService } from '../../services/state.service';
import { Observable, forkJoin } from 'rxjs';
import { Block, Outspend, Transaction } from '../../interfaces/electrs.interface';
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { environment } from 'src/environments/environment';
import { AssetsService } from 'src/app/services/assets.service';
import { map } from 'rxjs/operators';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({
selector: 'app-transactions-list',
@@ -26,7 +27,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
@Output() loadMore = new EventEmitter();
latestBlock$: Observable<Block>;
latestBlock$: Observable<BlockExtended>;
outspends: Outspend[] = [];
assetsMinimal: any;

View File

@@ -1,7 +1,8 @@
import { Component, ChangeDetectionStrategy, OnChanges, Input, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { Transaction, Block } from 'src/app/interfaces/electrs.interface';
import { Transaction } from 'src/app/interfaces/electrs.interface';
import { StateService } from 'src/app/services/state.service';
import { Subscription } from 'rxjs';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({
selector: 'app-tx-fee-rating',
@@ -18,7 +19,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
overpaidTimes: number;
feeRating: number;
blocks: Block[] = [];
blocks: BlockExtended[] = [];
constructor(
private stateService: StateService,
@@ -28,7 +29,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
ngOnInit() {
this.blocksSubscription = this.stateService.blocks$.subscribe(([block]) => {
this.blocks.push(block);
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block.medianFee > 0) {
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block?.extras?.medianFee > 0) {
this.calculateRatings(block);
this.cd.markForCheck();
}
@@ -42,7 +43,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
}
const foundBlock = this.blocks.find((b) => b.height === this.tx.status.block_height);
if (foundBlock && foundBlock.medianFee > 0) {
if (foundBlock && foundBlock?.extras?.medianFee > 0) {
this.calculateRatings(foundBlock);
}
}
@@ -51,9 +52,9 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
this.blocksSubscription.unsubscribe();
}
calculateRatings(block: Block) {
calculateRatings(block: BlockExtended) {
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
this.medianFeeNeeded = block.medianFee;
this.medianFeeNeeded = block?.extras?.medianFee;
// Block not filled
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {