Asset tracking.

Asset caching.
refs #37
This commit is contained in:
softsimon 2020-05-05 15:26:23 +07:00
parent d662292afb
commit 20c7ee98e7
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
14 changed files with 127 additions and 53 deletions

View File

@ -72,6 +72,14 @@ class WebsocketHandler {
}
}
if (parsedMessage && parsedMessage['track-asset']) {
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-asset'])) {
client['track-asset'] = parsedMessage['track-asset'];
} else {
client['track-asset'] = null;
}
}
if (parsedMessage.action === 'init') {
const _blocks = blocks.getBlocks();
if (!_blocks) {
@ -155,24 +163,24 @@ class WebsocketHandler {
}
}
// Send all new incoming transactions related to tracked address
if (client['track-address']) {
// Send all new incoming transactions related to tracked asset
if (client['track-asset']) {
const foundTransactions: TransactionExtended[] = [];
newTransactions.forEach((tx) => {
const someVin = tx.vin.some((vin) => !!vin.prevout && vin.prevout.scriptpubkey_address === client['track-address']);
const someVin = tx.vin.some((vin) => !!vin.issuance && vin.issuance.asset_id === client['track-asset']);
if (someVin) {
foundTransactions.push(tx);
return;
}
const someVout = tx.vout.some((vout) => vout.scriptpubkey_address === client['track-address']);
const someVout = tx.vout.some((vout) => !!vout.asset && vout.asset === client['track-asset']);
if (someVout) {
foundTransactions.push(tx);
}
});
if (foundTransactions.length) {
response['address-transactions'] = foundTransactions;
response['asset-transactions'] = foundTransactions;
}
}
@ -213,7 +221,34 @@ class WebsocketHandler {
foundTransactions.push(tx);
return;
}
if (tx.vout && tx.vout.some((vout) => vout.scriptpubkey_address === client['track-address'])) {
if (tx.vout && tx.vout.some((vout) => !!vout.asset && vout.asset === client['track-asset'])) {
foundTransactions.push(tx);
}
});
if (foundTransactions.length) {
foundTransactions.forEach((tx) => {
tx.status = {
confirmed: true,
block_height: block.height,
block_hash: block.id,
block_time: block.timestamp,
};
});
response['asset-block-transactions'] = foundTransactions;
}
}
if (client['track-asset']) {
const foundTransactions: TransactionExtended[] = [];
transactions.forEach((tx) => {
if (tx.vin && tx.vin.some((vin) => !!vin.issuance && vin.issuance.asset_id === client['track-asset'])) {
foundTransactions.push(tx);
return;
}
if (tx.vout && tx.vout.some((vout) => !!vout.asset && vout.asset === client['track-asset'])) {
foundTransactions.push(tx);
}
});

View File

@ -56,6 +56,20 @@ export interface Vin {
sequence: any;
witness?: string[];
inner_witnessscript_asm?: string;
issuance?: Issuance;
}
interface Issuance {
asset_id: string;
is_reissuance: string;
asset_blinding_nonce: string;
asset_entropy: string;
contract_hash: string;
assetamount?: number;
assetamountcommitment?: string;
tokenamount?: number;
tokenamountcommitment?: string;
}
export interface Vout {
@ -64,6 +78,8 @@ export interface Vout {
scriptpubkey_type: string;
scriptpubkey_address: string;
value: number;
asset?: string;
}
export interface Status {

View File

@ -7,7 +7,7 @@
<ng-template [ngIf]="!isLoading && !error">
<table class="table table-borderless table-striped">
<thead>
<th style="td-name">Name</th>
<th class="td-name">Name</th>
<th>Ticker</th>
<th class="d-none d-md-block">Issuer domain</th>
<th>Asset ID</th>

View File

@ -20,7 +20,11 @@ export class AssetsComponent implements OnInit {
) { }
ngOnInit() {
this.assetsService.getAssetsJson$()
setTimeout(() => this.getAssets());
}
getAssets() {
this.assetsService.getAssetsJson$
.subscribe((assets) => {
this.assets = Object.values(assets);
this.assets.push({
@ -36,6 +40,6 @@ export class AssetsComponent implements OnInit {
this.error = error;
this.isLoading = false;
});
}
}
}

View File

@ -15,7 +15,7 @@
<div class="row">
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tbody *ngIf="network !== 'liquid'">
<tr>
<td>Total received</td>
<td>{{ receieved / 100000000 | number: '1.8-8' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC</td>
@ -29,6 +29,20 @@
<td>{{ (receieved - sent) / 100000000 | number: '1.8-8' }} <ng-template [ngIf]="network === 'liquid'">L-</ng-template>BTC (<app-fiat [value]="receieved - sent"></app-fiat>)</td>
</tr>
</tbody>
<tbody *ngIf="network === 'liquid'">
<tr>
<td>Total received</td>
<td>Confidential</td>
</tr>
<tr>
<td>Total sent</td>
<td>Confidential</td>
</tr>
<tr>
<td>Balance</td>
<td>Confidential</td>
</tr>
</tbody>
</table>
</div>
<div class="col qrcode-col">

View File

@ -84,7 +84,7 @@ export class AssetComponent implements OnInit, OnDestroy {
console.log(err);
return of(null);
})
), this.assetsService.assetsMinimal$])
), this.assetsService.getAssetsMinimalJson$])
.pipe(
take(1)
);
@ -141,7 +141,7 @@ export class AssetComponent implements OnInit, OnDestroy {
this.isLoadingAsset = false;
});
this.stateService.mempoolTransactions$
this.stateService.assetTransactions$
.subscribe((transaction) => {
if (this.transactions.some((t) => t.txid === transaction.txid)) {
return;
@ -151,22 +151,7 @@ export class AssetComponent implements OnInit, OnDestroy {
this.transactions = this.transactions.slice();
this.txCount++;
// if (transaction.vout.some((vout) => vout.scriptpubkey_asset === this.asset.asset)) {
// this.audioService.playSound('cha-ching');
// } else {
// this.audioService.playSound('chime');
// }
// transaction.vin.forEach((vin) => {
// if (vin.prevout.scriptpubkey_asset === this.asset.asset) {
// this.sent += vin.prevout.value;
// }
// });
// transaction.vout.forEach((vout) => {
// if (vout.scriptpubkey_asset === this.asset.asset) {
// this.receieved += vout.value;
// }
// });
this.audioService.playSound('chime');
});
this.stateService.blockTransactions$

View File

@ -1,5 +1,5 @@
<span #buttonWrapper [attr.data-tlite]="'Copied!'" style="position: relative;">
<button #btn class="btn btn-sm btn-link pt-0" style="line-height: 1;" [attr.data-clipboard-text]="text">
<button #btn class="btn btn-sm btn-link pt-0" style="line-height: 0.9;" [attr.data-clipboard-text]="text">
<img src="./resources/clippy.svg" width="13">
</button>
</span>

View File

@ -31,10 +31,12 @@ export class SearchFormComponent implements OnInit {
this.searchForm = this.formBuilder.group({
searchText: ['', Validators.required],
});
this.assetsService.assetsMinimal$
.subscribe((assets) => {
this.assets = assets;
});
if (this.network === 'liquid') {
this.assetsService.getAssetsMinimalJson$
.subscribe((assets) => {
this.assets = assets;
});
}
}
search() {

View File

@ -29,7 +29,7 @@
</td>
<td>
<div [ngSwitch]="true">
<ng-container *ngSwitchCase="vin.is_coinbase">Coinbase (Newly Generated Coins)</ng-container>
<ng-container *ngSwitchCase="vin.is_coinbase">Coinbase<ng-template [ngIf]="network !== 'liquid'"> (Newly Generated Coins)</ng-template></ng-container>
<ng-container *ngSwitchCase="vin.is_pegin">PEG IN</ng-container>
<ng-container *ngSwitchDefault>
<a [routerLink]="['/address/', vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">

View File

@ -35,9 +35,11 @@ export class TransactionsListComponent implements OnInit, OnChanges {
ngOnInit() {
this.latestBlock$ = this.stateService.blocks$;
this.assetsService.assetsMinimal$.subscribe((assets) => {
this.assetsMinimal = assets;
});
if (this.network === 'liquid') {
this.assetsService.getAssetsMinimalJson$.subscribe((assets) => {
this.assetsMinimal = assets;
});
}
}
ngOnChanges() {

View File

@ -38,6 +38,19 @@ export interface Vin {
witness?: string[];
inner_witnessscript_asm?: string;
is_pegin?: boolean;
issuance?: Issuance;
}
interface Issuance {
asset_id: string;
is_reissuance: string;
asset_blinding_nonce: string;
asset_entropy: string;
contract_hash: string;
assetamount?: number;
assetamountcommitment?: string;
tokenamount?: number;
tokenamountcommitment?: string;
}
export interface Vout {

View File

@ -1,7 +1,8 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ReplaySubject } from 'rxjs';
import { ReplaySubject, Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
import { shareReplay } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
@ -9,24 +10,13 @@ import { environment } from 'src/environments/environment';
export class AssetsService {
network = environment.network;
assetsMinimal$ = new ReplaySubject<any>(1);
getAssetsJson$: Observable<any>;
getAssetsMinimalJson$: Observable<any>;
constructor(
private httpClient: HttpClient,
) {
if (this.network === 'liquid') {
this.getAssetsMinimalJson$();
}
}
getAssetsMinimalJson$() {
this.httpClient.get('/resources/assets.minimal.json')
.subscribe((data) => {
this.assetsMinimal$.next(data);
});
}
getAssetsJson$() {
return this.httpClient.get('/resources/assets.json');
this.getAssetsJson$ = this.httpClient.get('/resources/assets.json').pipe(shareReplay());
this.getAssetsMinimalJson$ = this.httpClient.get('/resources/assets.minimal.json').pipe(shareReplay());
}
}

View File

@ -21,6 +21,7 @@ export class StateService {
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
txConfirmed$ = new Subject<Block>();
mempoolTransactions$ = new Subject<Transaction>();
assetTransactions$ = new Subject<Transaction>();
blockTransactions$ = new Subject<Transaction>();
live2Chart$ = new Subject<OptimizedMempoolStats>();

View File

@ -96,6 +96,18 @@ export class WebsocketService {
});
}
if (response['asset-transactions']) {
response['asset-transactions'].forEach((assetTransaction: Transaction) => {
this.stateService.assetTransactions$.next(assetTransaction);
});
}
if (response['asset-block-transactions']) {
response['asset-block-transactions'].forEach((addressTransaction: Transaction) => {
this.stateService.blockTransactions$.next(addressTransaction);
});
}
if (response['live-2h-chart']) {
this.stateService.live2Chart$.next(response['live-2h-chart']);
}