Liquid: Display unconfidential address and fix tracking

fixes #761
This commit is contained in:
softsimon 2021-09-06 10:20:31 +04:00
parent 844b640c8c
commit 2d007b9100
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
10 changed files with 60 additions and 12 deletions

View File

@ -98,12 +98,15 @@ export namespace IBitcoinApi {
export interface AddressInformation { export interface AddressInformation {
isvalid: boolean; // (boolean) If the address is valid or not. If not, this is the only property returned. isvalid: boolean; // (boolean) If the address is valid or not. If not, this is the only property returned.
isvalid_parent?: boolean; // (boolean) Elements only
address: string; // (string) The bitcoin address validated address: string; // (string) The bitcoin address validated
scriptPubKey: string; // (string) The hex-encoded scriptPubKey generated by the address scriptPubKey: string; // (string) The hex-encoded scriptPubKey generated by the address
isscript: boolean; // (boolean) If the key is a script isscript: boolean; // (boolean) If the key is a script
iswitness: boolean; // (boolean) If the address is a witness iswitness: boolean; // (boolean) If the address is a witness
witness_version?: boolean; // (numeric, optional) The version number of the witness program witness_version?: boolean; // (numeric, optional) The version number of the witness program
witness_program: string; // (string, optional) The hex value of the witness program witness_program: string; // (string, optional) The hex value of the witness program
confidential_key?: string; // (string) Elements only
unconfidential?: string; // (string) Elements only
} }
export interface ChainTips { export interface ChainTips {

View File

@ -60,7 +60,6 @@ class BitcoinApi implements AbstractBitcoinApi {
return this.bitcoindClient.getBlock(hash, 0); return this.bitcoindClient.getBlock(hash, 0);
} }
$getBlockHash(height: number): Promise<string> { $getBlockHash(height: number): Promise<string> {
return this.bitcoindClient.getBlockHash(height); return this.bitcoindClient.getBlockHash(height);
} }
@ -238,10 +237,6 @@ class BitcoinApi implements AbstractBitcoinApi {
}); });
} }
protected $validateAddress(address: string): Promise<IBitcoinApi.AddressInformation> {
return this.bitcoindClient.validateAddress(address);
}
private $getMempoolEntry(txid: string): Promise<IBitcoinApi.MempoolEntry> { private $getMempoolEntry(txid: string): Promise<IBitcoinApi.MempoolEntry> {
return this.bitcoindClient.getMempoolEntry(txid); return this.bitcoindClient.getMempoolEntry(txid);
} }

View File

@ -44,6 +44,10 @@ class BitcoinBaseApi {
$getBlockchainInfo(): Promise<IBitcoinApi.BlockchainInfo> { $getBlockchainInfo(): Promise<IBitcoinApi.BlockchainInfo> {
return this.bitcoindClient.getBlockchainInfo(); return this.bitcoindClient.getBlockchainInfo();
} }
$validateAddress(address: string): Promise<IBitcoinApi.AddressInformation> {
return this.bitcoindClient.validateAddress(address);
}
} }
export default new BitcoinBaseApi(); export default new BitcoinBaseApi();

View File

@ -11,6 +11,7 @@ import * as sha256 from 'crypto-js/sha256';
import * as hexEnc from 'crypto-js/enc-hex'; import * as hexEnc from 'crypto-js/enc-hex';
import loadingIndicators from '../loading-indicators'; import loadingIndicators from '../loading-indicators';
import memoryCache from '../memory-cache'; import memoryCache from '../memory-cache';
import bitcoinBaseApi from './bitcoin-base.api';
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
private electrumClient: any; private electrumClient: any;
@ -44,7 +45,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
} }
async $getAddress(address: string): Promise<IEsploraApi.Address> { async $getAddress(address: string): Promise<IEsploraApi.Address> {
const addressInfo = await this.$validateAddress(address); const addressInfo = await bitcoinBaseApi.$validateAddress(address);
if (!addressInfo || !addressInfo.isvalid) { if (!addressInfo || !addressInfo.isvalid) {
return ({ return ({
'address': address, 'address': address,
@ -98,7 +99,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
} }
async $getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]> { async $getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]> {
const addressInfo = await this.$validateAddress(address); const addressInfo = await bitcoinBaseApi.$validateAddress(address);
if (!addressInfo || !addressInfo.isvalid) { if (!addressInfo || !addressInfo.isvalid) {
return []; return [];
} }

View File

@ -251,6 +251,7 @@ class Server {
.get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs', routes.getAddressTransactions) .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs', routes.getAddressTransactions)
.get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs/chain/:txId', routes.getAddressTransactions) .get(config.MEMPOOL.API_URL_PREFIX + 'address/:address/txs/chain/:txId', routes.getAddressTransactions)
.get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', routes.getAddressPrefix) .get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', routes.getAddressPrefix)
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', routes.validateAddress)
; ;
} }
} }

View File

@ -17,6 +17,7 @@ import transactionUtils from './api/transaction-utils';
import blocks from './api/blocks'; import blocks from './api/blocks';
import loadingIndicators from './api/loading-indicators'; import loadingIndicators from './api/loading-indicators';
import { Common } from './api/common'; import { Common } from './api/common';
import bitcoinBaseApi from './api/bitcoin/bitcoin-base.api';
class Routes { class Routes {
constructor() {} constructor() {}
@ -687,6 +688,15 @@ class Routes {
} }
} }
public async validateAddress(req: Request, res: Response) {
try {
const result = await bitcoinBaseApi.$validateAddress(req.params.address);
res.json(result);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
public getTransactionOutspends(req: Request, res: Response) { public getTransactionOutspends(req: Request, res: Response) {
res.status(501).send('Not implemented'); res.status(501).send('Not implemented');
} }
@ -737,7 +747,7 @@ class Routes {
previousRetarget, previousRetarget,
nextRetargetHeight, nextRetargetHeight,
timeAvg, timeAvg,
} };
res.json(result); res.json(result);
} catch (e) { } catch (e) {

View File

@ -18,6 +18,10 @@
<div class="col-md"> <div class="col-md">
<table class="table table-borderless table-striped"> <table class="table table-borderless table-striped">
<tbody> <tbody>
<tr *ngIf="addressInfo && addressInfo.unconfidential">
<td i18n="address.unconfidential">Unconfidential</td>
<td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">{{ addressInfo.unconfidential }}</a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
</tr>
<ng-template [ngIf]="!address.electrum"> <ng-template [ngIf]="!address.electrum">
<tr> <tr>
<td i18n="address.total-received">Total received</td> <td i18n="address.total-received">Total received</td>

View File

@ -9,6 +9,7 @@ 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, Observable } from 'rxjs'; import { of, merge, Subscription, Observable } from 'rxjs';
import { SeoService } from 'src/app/services/seo.service'; import { SeoService } from 'src/app/services/seo.service';
import { AddressInformation } from 'src/app/interfaces/node-api.interface';
@Component({ @Component({
selector: 'app-address', selector: 'app-address',
@ -27,6 +28,7 @@ export class AddressComponent implements OnInit, OnDestroy {
error: any; error: any;
mainSubscription: Subscription; mainSubscription: Subscription;
addressLoadingStatus$: Observable<number>; addressLoadingStatus$: Observable<number>;
addressInfo: null | AddressInformation = null;
totalConfirmedTxCount = 0; totalConfirmedTxCount = 0;
loadedConfirmedTxCount = 0; loadedConfirmedTxCount = 0;
@ -67,6 +69,7 @@ export class AddressComponent implements OnInit, OnDestroy {
this.address = null; this.address = null;
this.isLoadingTransactions = true; this.isLoadingTransactions = true;
this.transactions = null; this.transactions = null;
this.addressInfo = null;
document.body.scrollTo(0, 0); document.body.scrollTo(0, 0);
this.addressString = params.get('id') || ''; this.addressString = params.get('id') || '';
this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
@ -92,10 +95,20 @@ export class AddressComponent implements OnInit, OnDestroy {
) )
.pipe( .pipe(
filter((address) => !!address), filter((address) => !!address),
tap((address: Address) => {
if (this.stateService.network === 'liquid' && /^([m-zA-HJ-NP-Z1-9]{26,35}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[a-km-zA-HJ-NP-Z1-9]{80})$/.test(address.address)) {
this.apiService.validateAddress$(address.address)
.subscribe((addressInfo) => {
this.addressInfo = addressInfo;
this.websocketService.startTrackAddress(addressInfo.unconfidential);
});
} else {
this.websocketService.startTrackAddress(address.address);
}
}),
switchMap((address) => { switchMap((address) => {
this.address = address; this.address = address;
this.updateChainStats(); this.updateChainStats();
this.websocketService.startTrackAddress(address.address);
this.isLoadingAddress = false; this.isLoadingAddress = false;
this.isLoadingTransactions = true; this.isLoadingTransactions = true;
return this.electrsApiService.getAddressTransactions$(address.address); return this.electrsApiService.getAddressTransactions$(address.address);

View File

@ -34,3 +34,16 @@ export interface DifficultyAdjustment {
remainingBlocks: number; remainingBlocks: number;
remainingTime: number; remainingTime: number;
} }
export interface AddressInformation {
isvalid: boolean; // (boolean) If the address is valid or not. If not, this is the only property returned.
isvalid_parent?: boolean; // (boolean) Elements only
address: string; // (string) The bitcoin address validated
scriptPubKey: string; // (string) The hex-encoded scriptPubKey generated by the address
isscript: boolean; // (boolean) If the key is a script
iswitness: boolean; // (boolean) If the address is a witness
witness_version?: boolean; // (numeric, optional) The version number of the witness program
witness_program: string; // (string, optional) The hex value of the witness program
confidential_key?: string; // (string) Elements only
unconfidential?: string; // (string) Elements only
}

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment } from '../interfaces/node-api.interface'; import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation } from '../interfaces/node-api.interface';
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';
@ -96,4 +96,8 @@ export class ApiService {
getDifficultyAdjustment$(): Observable<DifficultyAdjustment> { getDifficultyAdjustment$(): Observable<DifficultyAdjustment> {
return this.httpClient.get<DifficultyAdjustment>(this.apiBaseUrl + this.apiBasePath + '/api/v1/difficulty-adjustment'); return this.httpClient.get<DifficultyAdjustment>(this.apiBaseUrl + this.apiBasePath + '/api/v1/difficulty-adjustment');
} }
validateAddress$(address: string): Observable<AddressInformation> {
return this.httpClient.get<AddressInformation>(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address);
}
} }