parent
							
								
									1a756c5fa9
								
							
						
					
					
						commit
						40dc476460
					
				@ -70,7 +70,7 @@ class ChannelsRoutes {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getChannelsByTransactionIds(req: Request, res: Response) {
 | 
			
		||||
  private async $getChannelsByTransactionIds(req: Request, res: Response): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      if (!Array.isArray(req.query.txId)) {
 | 
			
		||||
        res.status(400).send('Not an array');
 | 
			
		||||
@ -83,27 +83,26 @@ class ChannelsRoutes {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      const channels = await channelsApi.$getChannelsByTransactionId(txIds);
 | 
			
		||||
      const inputs: any[] = [];
 | 
			
		||||
      const outputs: any[] = [];
 | 
			
		||||
      const result: any[] = [];
 | 
			
		||||
      for (const txid of txIds) {
 | 
			
		||||
        const foundChannelInputs = channels.find((channel) => channel.closing_transaction_id === txid);
 | 
			
		||||
        if (foundChannelInputs) {
 | 
			
		||||
          inputs.push(foundChannelInputs);
 | 
			
		||||
        } else {
 | 
			
		||||
          inputs.push(null);
 | 
			
		||||
        const inputs: any = {};
 | 
			
		||||
        const outputs: any = {};
 | 
			
		||||
        // Assuming that we only have one lightning close input in each transaction. This may not be true in the future
 | 
			
		||||
        const foundChannelsFromInput = channels.find((channel) => channel.closing_transaction_id === txid);
 | 
			
		||||
        if (foundChannelsFromInput) {
 | 
			
		||||
          inputs[0] = foundChannelsFromInput;
 | 
			
		||||
        }
 | 
			
		||||
        const foundChannelOutputs = channels.find((channel) => channel.transaction_id === txid);
 | 
			
		||||
        if (foundChannelOutputs) {
 | 
			
		||||
          outputs.push(foundChannelOutputs);
 | 
			
		||||
        } else {
 | 
			
		||||
          outputs.push(null);
 | 
			
		||||
        const foundChannelsFromOutputs = channels.filter((channel) => channel.transaction_id === txid);
 | 
			
		||||
        for (const output of foundChannelsFromOutputs) {
 | 
			
		||||
          outputs[output.transaction_vout] = output;
 | 
			
		||||
        }
 | 
			
		||||
        result.push({
 | 
			
		||||
          inputs,
 | 
			
		||||
          outputs,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      res.json({
 | 
			
		||||
        inputs: inputs,
 | 
			
		||||
        outputs: outputs,
 | 
			
		||||
      });
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@
 | 
			
		||||
      <div class="col">
 | 
			
		||||
        <table class="table table-borderless smaller-text table-sm table-tx-vin">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <ng-template ngFor let-vin [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length > rowLimit) ? tx.vin.slice(0, rowLimit - 2) : tx.vin.slice(0, rowLimit)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
 | 
			
		||||
            <ng-template ngFor let-vin let-vindex="index" [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length > rowLimit) ? tx.vin.slice(0, rowLimit - 2) : tx.vin.slice(0, rowLimit)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
 | 
			
		||||
              <tr [ngClass]="{
 | 
			
		||||
                'assetBox': assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded,
 | 
			
		||||
                'highlight': vin.prevout?.scriptpubkey_address === this.address && this.address !== ''
 | 
			
		||||
@ -77,7 +77,7 @@
 | 
			
		||||
                          {{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
 | 
			
		||||
                        </ng-template>
 | 
			
		||||
                        <div>
 | 
			
		||||
                          <app-address-labels [vin]="vin" [channel]="channels && channels.inputs[i] || null"></app-address-labels>
 | 
			
		||||
                          <app-address-labels [vin]="vin" [channel]="tx._channels && tx._channels.inputs[vin.vout] || null"></app-address-labels>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </ng-template>
 | 
			
		||||
                    </ng-container>
 | 
			
		||||
@ -172,7 +172,7 @@
 | 
			
		||||
                    </span>
 | 
			
		||||
                  </a>
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <app-address-labels [vout]="vout" [channel]="channels && channels.outputs[i] && channels.outputs[i].transaction_vout === vindex ? channels.outputs[i] : null"></app-address-labels>
 | 
			
		||||
                    <app-address-labels [vout]="vout" [channel]="tx._channels && tx._channels.outputs[vindex] ? tx._channels.outputs[vindex] : null"></app-address-labels>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <ng-template #scriptpubkey_type>
 | 
			
		||||
                    <ng-template [ngIf]="vout.pegout" [ngIfElse]="defaultscriptpubkey_type">
 | 
			
		||||
@ -212,15 +212,15 @@
 | 
			
		||||
                  </ng-template>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="arrow-td">
 | 
			
		||||
                  <span *ngIf="!outspends[i] || vout.scriptpubkey_type === 'op_return' || vout.scriptpubkey_type === 'fee' ; else outspend" class="grey">
 | 
			
		||||
                  <span *ngIf="!tx._outspends || vout.scriptpubkey_type === 'op_return' || vout.scriptpubkey_type === 'fee' ; else outspend" class="grey">
 | 
			
		||||
                    <fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
                  </span>
 | 
			
		||||
                  <ng-template #outspend>
 | 
			
		||||
                    <span *ngIf="!outspends[i][vindex] || !outspends[i][vindex].spent; else spent" class="green">
 | 
			
		||||
                    <span *ngIf="!tx._outspends[vindex] || !tx._outspends[vindex].spent; else spent" class="green">
 | 
			
		||||
                      <fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <ng-template #spent>
 | 
			
		||||
                      <a *ngIf="outspends[i][vindex].txid else outputNoTxId" [routerLink]="['/tx/' | relativeUrl, outspends[i][vindex].txid]" class="red">
 | 
			
		||||
                      <a *ngIf="tx._outspends[vindex].txid else outputNoTxId" [routerLink]="['/tx/' | relativeUrl, tx._outspends[vindex].txid]" class="red">
 | 
			
		||||
                        <fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
                      </a>
 | 
			
		||||
                      <ng-template #outputNoTxId>
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,6 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() outputIndex: number;
 | 
			
		||||
  @Input() address: string = '';
 | 
			
		||||
  @Input() rowLimit = 12;
 | 
			
		||||
  @Input() channels: { inputs: any[], outputs: any[] };
 | 
			
		||||
 | 
			
		||||
  @Output() loadMore = new EventEmitter();
 | 
			
		||||
 | 
			
		||||
@ -36,8 +35,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
  refreshOutspends$: ReplaySubject<string[]> = new ReplaySubject();
 | 
			
		||||
  refreshChannels$: ReplaySubject<string[]> = new ReplaySubject();
 | 
			
		||||
  showDetails$ = new BehaviorSubject<boolean>(false);
 | 
			
		||||
  outspends: Outspend[][] = [];
 | 
			
		||||
  assetsMinimal: any;
 | 
			
		||||
  transactionsLength: number = 0;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
@ -47,7 +46,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
    private ref: ChangeDetectorRef,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.latestBlock$ = this.stateService.blocks$.pipe(map(([block]) => block));
 | 
			
		||||
    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
			
		||||
 | 
			
		||||
@ -62,14 +61,17 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
        .pipe(
 | 
			
		||||
          switchMap((txIds) => this.apiService.getOutspendsBatched$(txIds)),
 | 
			
		||||
          tap((outspends: Outspend[][]) => {
 | 
			
		||||
            this.outspends = this.outspends.concat(outspends);
 | 
			
		||||
            const transactions = this.transactions.filter((tx) => !tx._outspends);
 | 
			
		||||
            outspends.forEach((outspend, i) => {
 | 
			
		||||
              transactions[i]._outspends = outspend;
 | 
			
		||||
            });
 | 
			
		||||
          }),
 | 
			
		||||
        ),
 | 
			
		||||
      this.stateService.utxoSpent$
 | 
			
		||||
        .pipe(
 | 
			
		||||
          tap((utxoSpent) => {
 | 
			
		||||
            for (const i in utxoSpent) {
 | 
			
		||||
              this.outspends[0][i] = {
 | 
			
		||||
              this.transactions[0]._outspends[i] = {
 | 
			
		||||
                spent: true,
 | 
			
		||||
                txid: utxoSpent[i].txid,
 | 
			
		||||
                vin: utxoSpent[i].vin,
 | 
			
		||||
@ -81,21 +83,23 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
          .pipe(
 | 
			
		||||
            filter(() => this.stateService.env.LIGHTNING),
 | 
			
		||||
            switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
 | 
			
		||||
            map((channels) => {
 | 
			
		||||
              this.channels = channels;
 | 
			
		||||
            tap((channels) => {
 | 
			
		||||
              const transactions = this.transactions.filter((tx) => !tx._channels);
 | 
			
		||||
              channels.forEach((channel, i) => {
 | 
			
		||||
                transactions[i]._channels = channel;
 | 
			
		||||
              });
 | 
			
		||||
            }),
 | 
			
		||||
          )
 | 
			
		||||
        ,
 | 
			
		||||
    ).subscribe(() => this.ref.markForCheck());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
  ngOnChanges(): void {
 | 
			
		||||
    if (!this.transactions || !this.transactions.length) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.paginated) {
 | 
			
		||||
      this.outspends = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.transactionsLength = this.transactions.length;
 | 
			
		||||
    if (this.outputIndex) {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        const assetBoxElements = document.getElementsByClassName('assetBox');
 | 
			
		||||
@ -126,14 +130,19 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
        tx['addressValue'] = addressIn - addressOut;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    const txIds = this.transactions.map((tx) => tx.txid);
 | 
			
		||||
    const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
 | 
			
		||||
    if (txIds.length) {
 | 
			
		||||
      this.refreshOutspends$.next(txIds);
 | 
			
		||||
    if (!this.channels) {
 | 
			
		||||
    }
 | 
			
		||||
    if (this.stateService.env.LIGHTNING) {
 | 
			
		||||
      const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid);
 | 
			
		||||
      if (txIds.length) {
 | 
			
		||||
        this.refreshChannels$.next(txIds);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onScroll() {
 | 
			
		||||
  onScroll(): void {
 | 
			
		||||
    const scrollHeight = document.body.scrollHeight;
 | 
			
		||||
    const scrollTop = document.documentElement.scrollTop;
 | 
			
		||||
    if (scrollHeight > 0){
 | 
			
		||||
@ -148,11 +157,11 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
    return tx.vout.some((v: any) => v.value === undefined);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTotalTxOutput(tx: Transaction) {
 | 
			
		||||
  getTotalTxOutput(tx: Transaction): number {
 | 
			
		||||
    return tx.vout.map((v: Vout) => v.value || 0).reduce((a: number, b: number) => a + b);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switchCurrency() {
 | 
			
		||||
  switchCurrency(): void {
 | 
			
		||||
    if (this.network === 'liquid' || this.network === 'liquidtestnet') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -164,7 +173,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
    return tx.txid + tx.status.confirmed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  trackByIndexFn(index: number) {
 | 
			
		||||
  trackByIndexFn(index: number): number {
 | 
			
		||||
    return index;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -177,7 +186,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
    return Math.pow(base, exponent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleDetails() {
 | 
			
		||||
  toggleDetails(): void {
 | 
			
		||||
    if (this.showDetails$.value === true) {
 | 
			
		||||
      this.showDetails$.next(false);
 | 
			
		||||
    } else {
 | 
			
		||||
@ -185,7 +194,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadMoreInputs(tx: Transaction) {
 | 
			
		||||
  loadMoreInputs(tx: Transaction): void {
 | 
			
		||||
    tx['@vinLimit'] = false;
 | 
			
		||||
 | 
			
		||||
    this.electrsApiService.getTransaction$(tx.txid)
 | 
			
		||||
@ -196,7 +205,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
    this.outspendsSubscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
import { IChannel } from './node-api.interface';
 | 
			
		||||
 | 
			
		||||
export interface Transaction {
 | 
			
		||||
  txid: string;
 | 
			
		||||
  version: number;
 | 
			
		||||
@ -19,6 +21,13 @@ export interface Transaction {
 | 
			
		||||
  deleteAfter?: number;
 | 
			
		||||
  _unblinded?: any;
 | 
			
		||||
  _deduced?: boolean;
 | 
			
		||||
  _outspends?: Outspend[];
 | 
			
		||||
  _channels?: TransactionChannels;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TransactionChannels {
 | 
			
		||||
  inputs: { [vin: number]: IChannel };
 | 
			
		||||
  outputs: { [vout: number]: IChannel };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Ancestor {
 | 
			
		||||
 | 
			
		||||
@ -189,3 +189,35 @@ export interface IOldestNodes {
 | 
			
		||||
  city?: any,
 | 
			
		||||
  country?: any,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IChannel {
 | 
			
		||||
  id: number;
 | 
			
		||||
  short_id: string;
 | 
			
		||||
  capacity: number;
 | 
			
		||||
  transaction_id: string;
 | 
			
		||||
  transaction_vout: number;
 | 
			
		||||
  closing_transaction_id: string;
 | 
			
		||||
  closing_reason: string;
 | 
			
		||||
  updated_at: string;
 | 
			
		||||
  created: string;
 | 
			
		||||
  status: number;
 | 
			
		||||
  node_left: Node,
 | 
			
		||||
  node_right: Node,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export interface INode {
 | 
			
		||||
  alias: string;
 | 
			
		||||
  public_key: string;
 | 
			
		||||
  channels: number;
 | 
			
		||||
  capacity: number;
 | 
			
		||||
  base_fee_mtokens: number;
 | 
			
		||||
  cltv_delta: number;
 | 
			
		||||
  fee_rate: number;
 | 
			
		||||
  is_disabled: boolean;
 | 
			
		||||
  max_htlc_mtokens: number;
 | 
			
		||||
  min_htlc_mtokens: number;
 | 
			
		||||
  updated_at: string;
 | 
			
		||||
  longitude: number;
 | 
			
		||||
  latitude: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -65,13 +65,13 @@
 | 
			
		||||
    <ng-container *ngIf="transactions$ | async as transactions">
 | 
			
		||||
      <ng-template [ngIf]="transactions[0]">
 | 
			
		||||
        <h3>Opening transaction</h3>
 | 
			
		||||
        <app-transactions-list [transactions]="[transactions[0]]" [showConfirmations]="true" [rowLimit]="5" [channels]="{ inputs: [], outputs: [channel] }"></app-transactions-list>
 | 
			
		||||
        <app-transactions-list [transactions]="[transactions[0]]" [showConfirmations]="true" [rowLimit]="5"></app-transactions-list>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
      <ng-template [ngIf]="transactions[1]">
 | 
			
		||||
        <div class="closing-header">
 | 
			
		||||
          <h3 style="margin: 0;">Closing transaction</h3>  <app-closing-type [type]="channel.closing_reason"></app-closing-type>
 | 
			
		||||
        </div>
 | 
			
		||||
        <app-transactions-list [transactions]="[transactions[1]]" [showConfirmations]="true" [rowLimit]="5" [channels]="{ inputs: [channel], outputs: [] }"></app-transactions-list>
 | 
			
		||||
        <app-transactions-list [transactions]="[transactions[1]]" [showConfirmations]="true" [rowLimit]="5"></app-transactions-list>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { forkJoin, Observable, of, share, zip } from 'rxjs';
 | 
			
		||||
import { catchError, map, shareReplay, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { IChannel } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
@ -62,10 +63,15 @@ export class ChannelComponent implements OnInit {
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this.transactions$ = this.channel$.pipe(
 | 
			
		||||
      switchMap((data) => {
 | 
			
		||||
      switchMap((channel: IChannel) => {
 | 
			
		||||
        return zip([
 | 
			
		||||
          data.transaction_id ? this.electrsApiService.getTransaction$(data.transaction_id) : of(null),
 | 
			
		||||
          data.closing_transaction_id ? this.electrsApiService.getTransaction$(data.closing_transaction_id) : of(null),
 | 
			
		||||
          channel.transaction_id ? this.electrsApiService.getTransaction$(channel.transaction_id) : of(null),
 | 
			
		||||
          channel.closing_transaction_id ? this.electrsApiService.getTransaction$(channel.closing_transaction_id).pipe(
 | 
			
		||||
            map((tx) => {
 | 
			
		||||
              tx._channels = { inputs: {0: channel}, outputs: {}};
 | 
			
		||||
              return tx;
 | 
			
		||||
            })
 | 
			
		||||
          ) : of(null),
 | 
			
		||||
        ]);
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -242,12 +242,12 @@ export class ApiService {
 | 
			
		||||
    return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + `/api/v1/enterprise/info/` + name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getChannelByTxIds$(txIds: string[]): Observable<{ inputs: any[], outputs: any[] }> {
 | 
			
		||||
  getChannelByTxIds$(txIds: string[]): Observable<any[]> {
 | 
			
		||||
    let params = new HttpParams();
 | 
			
		||||
    txIds.forEach((txId: string) => {
 | 
			
		||||
      params = params.append('txId[]', txId);
 | 
			
		||||
    });
 | 
			
		||||
    return this.httpClient.get<{ inputs: any[], outputs: any[] }>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels/txids/', { params });
 | 
			
		||||
    return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels/txids/', { params });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  lightningSearch$(searchText: string): Observable<any[]> {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user