parent
b2d2fd225c
commit
11b1d9bbd3
@ -2,7 +2,7 @@
|
|||||||
<span>{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
<span>{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #viewFiatVin>
|
<ng-template #viewFiatVin>
|
||||||
<ng-template [ngIf]="network === 'liquid' && !satoshis" [ngIfElse]="default">
|
<ng-template [ngIf]="network === 'liquid' && satoshis === undefined" [ngIfElse]="default">
|
||||||
Confidential
|
Confidential
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #default>
|
<ng-template #default>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
<ng-template [ngIf]="!isLoadingAsset && !error">
|
<ng-template [ngIf]="!isLoadingAsset && !error && assetContract">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -18,15 +18,15 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ asset.name }} ({{ asset.ticker }})</td>
|
<td>{{ assetContract[2] }} ({{ assetContract[1] }})</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Precision</td>
|
<td>Precision</td>
|
||||||
<td>{{ asset.precision }}</td>
|
<td>{{ assetContract[3] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Issuer</td>
|
<td>Issuer</td>
|
||||||
<td><a target="_blank" href="{{ 'http://' + asset.contract.entity.domain }}">{{ asset.contract.entity.domain }}</a></td>
|
<td><a target="_blank" href="{{ 'http://' + assetContract[0] }}">{{ assetContract[0] }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Issuance tx</td>
|
<td>Issuance tx</td>
|
||||||
@ -40,15 +40,15 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Circulating amount</td>
|
<td>Circulating amount</td>
|
||||||
<td>{{ (asset.chain_stats.issued_amount - asset.chain_stats.burned_amount) / 100000000 | number: '1.0-' + asset.precision }}</td>
|
<td>{{ (asset.chain_stats.issued_amount - asset.chain_stats.burned_amount) / 100000000 | number: '1.0-' + assetContract[3] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Issued amount</td>
|
<td>Issued amount</td>
|
||||||
<td>{{ asset.chain_stats.issued_amount / 100000000 | number: '1.0-' + asset.precision }}</td>
|
<td>{{ asset.chain_stats.issued_amount / 100000000 | number: '1.0-' + assetContract[3] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Burned amount</td>
|
<td>Burned amount</td>
|
||||||
<td>{{ asset.chain_stats.burned_amount / 100000000 | number: '1.0-' + asset.precision }}</td>
|
<td>{{ asset.chain_stats.burned_amount / 100000000 | number: '1.0-' + assetContract[3] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { switchMap, filter, catchError } from 'rxjs/operators';
|
import { switchMap, filter, catchError, take } from 'rxjs/operators';
|
||||||
import { Asset, Transaction } from '../../interfaces/electrs.interface';
|
import { Asset, Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
import { AudioService } from 'src/app/services/audio.service';
|
import { AudioService } from 'src/app/services/audio.service';
|
||||||
import { ApiService } from 'src/app/services/api.service';
|
import { ApiService } from 'src/app/services/api.service';
|
||||||
import { of, merge, Subscription } from 'rxjs';
|
import { of, merge, Subscription, combineLatest } from 'rxjs';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
|
import { AssetsService } from 'src/app/services/assets.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-asset',
|
selector: 'app-asset',
|
||||||
@ -20,6 +21,7 @@ export class AssetComponent implements OnInit, OnDestroy {
|
|||||||
network = environment.network;
|
network = environment.network;
|
||||||
|
|
||||||
asset: Asset;
|
asset: Asset;
|
||||||
|
assetContract: any;
|
||||||
assetString: string;
|
assetString: string;
|
||||||
isLoadingAsset = true;
|
isLoadingAsset = true;
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
@ -45,6 +47,7 @@ export class AssetComponent implements OnInit, OnDestroy {
|
|||||||
private audioService: AudioService,
|
private audioService: AudioService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
|
private assetsService: AssetsService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -57,6 +60,7 @@ export class AssetComponent implements OnInit, OnDestroy {
|
|||||||
this.isLoadingAsset = true;
|
this.isLoadingAsset = true;
|
||||||
this.loadedConfirmedTxCount = 0;
|
this.loadedConfirmedTxCount = 0;
|
||||||
this.asset = null;
|
this.asset = null;
|
||||||
|
this.assetContract = null;
|
||||||
this.isLoadingTransactions = true;
|
this.isLoadingTransactions = true;
|
||||||
this.transactions = null;
|
this.transactions = null;
|
||||||
document.body.scrollTo(0, 0);
|
document.body.scrollTo(0, 0);
|
||||||
@ -69,22 +73,27 @@ export class AssetComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(filter((state) => state === 2 && this.transactions && this.transactions.length > 0))
|
.pipe(filter((state) => state === 2 && this.transactions && this.transactions.length > 0))
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => this.electrsApiService.getAsset$(this.assetString)
|
switchMap(() => {
|
||||||
|
return combineLatest([this.electrsApiService.getAsset$(this.assetString)
|
||||||
|
.pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.isLoadingAsset = false;
|
||||||
|
this.error = err;
|
||||||
|
console.log(err);
|
||||||
|
return of(null);
|
||||||
|
})
|
||||||
|
), this.assetsService.assetsMinimal$])
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError((err) => {
|
take(1)
|
||||||
this.isLoadingAsset = false;
|
);
|
||||||
this.error = err;
|
})
|
||||||
console.log(err);
|
|
||||||
return of(null);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((asset: Asset) => {
|
switchMap(([asset, assetsData]) => {
|
||||||
this.asset = asset;
|
this.asset = asset;
|
||||||
|
this.assetContract = assetsData[this.asset.asset_id];
|
||||||
this.updateChainStats();
|
this.updateChainStats();
|
||||||
this.websocketService.startTrackAsset(asset.asset_id);
|
this.websocketService.startTrackAsset(asset.asset_id);
|
||||||
this.isLoadingAsset = false;
|
this.isLoadingAsset = false;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
import { AssetsService } from 'src/app/services/assets.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-search-form',
|
selector: 'app-search-form',
|
||||||
@ -9,6 +11,9 @@ import { Router } from '@angular/router';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class SearchFormComponent implements OnInit {
|
export class SearchFormComponent implements OnInit {
|
||||||
|
network = environment.network;
|
||||||
|
assets: object;
|
||||||
|
|
||||||
searchForm: FormGroup;
|
searchForm: FormGroup;
|
||||||
@Output() searchTriggered = new EventEmitter();
|
@Output() searchTriggered = new EventEmitter();
|
||||||
|
|
||||||
@ -19,12 +24,17 @@ export class SearchFormComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private assetsService: AssetsService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.searchForm = this.formBuilder.group({
|
this.searchForm = this.formBuilder.group({
|
||||||
searchText: ['', Validators.required],
|
searchText: ['', Validators.required],
|
||||||
});
|
});
|
||||||
|
this.assetsService.assetsMinimal$
|
||||||
|
.subscribe((assets) => {
|
||||||
|
this.assets = assets;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
search() {
|
search() {
|
||||||
@ -37,7 +47,11 @@ export class SearchFormComponent implements OnInit {
|
|||||||
this.router.navigate(['/block/', searchText]);
|
this.router.navigate(['/block/', searchText]);
|
||||||
this.searchTriggered.emit();
|
this.searchTriggered.emit();
|
||||||
} else if (this.regexTransaction.test(searchText)) {
|
} else if (this.regexTransaction.test(searchText)) {
|
||||||
this.router.navigate(['/tx/', searchText]);
|
if (this.network === 'liquid' && this.assets[searchText]) {
|
||||||
|
this.router.navigate(['/asset/', searchText]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/tx/', searchText]);
|
||||||
|
}
|
||||||
this.searchTriggered.emit();
|
this.searchTriggered.emit();
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
.position-container {
|
.position-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 150px;
|
bottom: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-holder {
|
.chart-holder {
|
||||||
@ -37,7 +37,7 @@
|
|||||||
@media (min-width: 1920px) {
|
@media (min-width: 1920px) {
|
||||||
.position-container {
|
.position-container {
|
||||||
transform: scale(1.3);
|
transform: scale(1.3);
|
||||||
bottom: 190px;
|
bottom: 210px;
|
||||||
}
|
}
|
||||||
.chart-holder {
|
.chart-holder {
|
||||||
height: calc(100% - 280px);
|
height: calc(100% - 280px);
|
||||||
|
@ -70,10 +70,22 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right nowrap">
|
<td class="text-right nowrap">
|
||||||
<app-amount [satoshis]="vout.value"></app-amount>
|
<ng-template [ngIf]="vout.asset && vout.scriptpubkey_type === 'op_return' && vout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
|
||||||
|
<div *ngIf="assetsMinimal && assetsMinimal[vout.asset]">
|
||||||
|
{{ vout.value / 100000000 | number: '1.0-' + assetsMinimal[vout.asset][3] }} {{ assetsMinimal[vout.asset][1] }}
|
||||||
|
<br>
|
||||||
|
{{ assetsMinimal[vout.asset][0] }}
|
||||||
|
<br>
|
||||||
|
<a [routerLink]="['/asset/', vout.asset]">{{ vout.asset | shortenString : 13 }}</a>
|
||||||
|
<br><br>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #defaultOutput>
|
||||||
|
<app-amount [satoshis]="vout.value"></app-amount>
|
||||||
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td class="pl-1 arrow-td">
|
<td class="pl-1 arrow-td">
|
||||||
<i *ngIf="!outspends[i]; else outspend" class="arrow grey"></i>
|
<i *ngIf="!outspends[i] || vout.scriptpubkey_type === 'op_return' || vout.scriptpubkey_type === 'fee' ; else outspend" class="arrow grey"></i>
|
||||||
<ng-template #outspend>
|
<ng-template #outspend>
|
||||||
<i *ngIf="!outspends[i][vindex] || !outspends[i][vindex].spent; else spent" class="arrow green"></i>
|
<i *ngIf="!outspends[i][vindex] || !outspends[i][vindex].spent; else spent" class="arrow green"></i>
|
||||||
<ng-template #spent>
|
<ng-template #spent>
|
||||||
@ -97,7 +109,10 @@
|
|||||||
|
|
||||||
</span>
|
</span>
|
||||||
<button type="button" class="btn btn-sm btn-primary mt-3" (click)="switchCurrency()">
|
<button type="button" class="btn btn-sm btn-primary mt-3" (click)="switchCurrency()">
|
||||||
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="defaultAmount">Confidential</ng-template>
|
||||||
|
<ng-template #defaultAmount>
|
||||||
|
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
||||||
|
</ng-template>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -3,6 +3,8 @@ import { StateService } from '../../services/state.service';
|
|||||||
import { Observable, forkJoin } from 'rxjs';
|
import { Observable, forkJoin } from 'rxjs';
|
||||||
import { Block, Outspend, Transaction } from '../../interfaces/electrs.interface';
|
import { Block, Outspend, Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
import { AssetsService } from 'src/app/services/assets.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transactions-list',
|
selector: 'app-transactions-list',
|
||||||
@ -11,6 +13,9 @@ import { ElectrsApiService } from '../../services/electrs-api.service';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class TransactionsListComponent implements OnInit, OnChanges {
|
export class TransactionsListComponent implements OnInit, OnChanges {
|
||||||
|
network = environment.network;
|
||||||
|
nativeAssetId = environment.nativeAssetId;
|
||||||
|
|
||||||
@Input() transactions: Transaction[];
|
@Input() transactions: Transaction[];
|
||||||
@Input() showConfirmations = false;
|
@Input() showConfirmations = false;
|
||||||
@Input() transactionPage = false;
|
@Input() transactionPage = false;
|
||||||
@ -19,15 +24,20 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
latestBlock$: Observable<Block>;
|
latestBlock$: Observable<Block>;
|
||||||
outspends: Outspend[] = [];
|
outspends: Outspend[] = [];
|
||||||
|
assetsMinimal: any;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
|
private assetsService: AssetsService,
|
||||||
private ref: ChangeDetectorRef,
|
private ref: ChangeDetectorRef,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.latestBlock$ = this.stateService.blocks$;
|
this.latestBlock$ = this.stateService.blocks$;
|
||||||
|
this.assetsService.assetsMinimal$.subscribe((assets) => {
|
||||||
|
this.assetsMinimal = assets;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
@ -66,6 +76,9 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switchCurrency() {
|
switchCurrency() {
|
||||||
|
if (this.network === 'liquid') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const oldvalue = !this.stateService.viewFiat$.value;
|
const oldvalue = !this.stateService.viewFiat$.value;
|
||||||
this.stateService.viewFiat$.next(oldvalue);
|
this.stateService.viewFiat$.next(oldvalue);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ export interface Vout {
|
|||||||
scriptpubkey_type: string;
|
scriptpubkey_type: string;
|
||||||
scriptpubkey_address: string;
|
scriptpubkey_address: string;
|
||||||
value: number;
|
value: number;
|
||||||
|
asset?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Status {
|
export interface Status {
|
||||||
@ -108,11 +109,6 @@ export interface Asset {
|
|||||||
status: Status;
|
status: Status;
|
||||||
chain_stats: AssetChainStats;
|
chain_stats: AssetChainStats;
|
||||||
mempool_stats: AssetMempoolStats;
|
mempool_stats: AssetMempoolStats;
|
||||||
contract: Contract;
|
|
||||||
entity: Entity;
|
|
||||||
precision: number;
|
|
||||||
name: string;
|
|
||||||
ticker: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IssuanceTxin {
|
interface IssuanceTxin {
|
||||||
@ -157,15 +153,6 @@ interface AssetMempoolStats {
|
|||||||
burn_count: number;
|
burn_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Contract {
|
|
||||||
entity: Entity;
|
|
||||||
issuer_pubkey: string;
|
|
||||||
name: string;
|
|
||||||
precision: number;
|
|
||||||
ticker: string;
|
|
||||||
version: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Entity {
|
interface Entity {
|
||||||
domain: string;
|
domain: string;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ export class ScriptpubkeyTypePipe implements PipeTransform {
|
|||||||
return 'Transaction fee';
|
return 'Transaction fee';
|
||||||
case 'op_return':
|
case 'op_return':
|
||||||
default:
|
default:
|
||||||
return 'Script';
|
return 'OP_RETURN';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
frontend/src/app/services/assets.service.ts
Normal file
29
frontend/src/app/services/assets.service.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AssetsService {
|
||||||
|
network = environment.network;
|
||||||
|
|
||||||
|
assetsMinimal$ = new ReplaySubject<any>(1);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private httpClient: HttpClient,
|
||||||
|
) {
|
||||||
|
if (this.network === 'liquid') {
|
||||||
|
this.getAssetsMinimalJson$();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssetsMinimalJson$() {
|
||||||
|
this.httpClient.get('/assets/assets.minimal.json')
|
||||||
|
.subscribe((data) => {
|
||||||
|
console.log(data);
|
||||||
|
this.assetsMinimal$.next(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -5,4 +5,5 @@ const sub = parts[0];
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
network: sub,
|
network: sub,
|
||||||
|
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
network: 'mainnet',
|
network: 'mainnet',
|
||||||
|
nativeAssetId: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d',
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user