Refactored "features" and "fee rating" from transaction into components.
This commit is contained in:
		
							parent
							
								
									c893608a41
								
							
						
					
					
						commit
						cca69556d0
					
				@ -3,7 +3,7 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
import { HttpClientModule } from '@angular/common/http';
 | 
			
		||||
import { ReactiveFormsModule } from '@angular/forms';
 | 
			
		||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 | 
			
		||||
import { NgbButtonsModule, NgbTooltipModule, NgbPaginationModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { NgbButtonsModule, NgbPaginationModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
 | 
			
		||||
 | 
			
		||||
import { AppRoutingModule } from './app-routing.module';
 | 
			
		||||
@ -79,7 +79,6 @@ import { SharedModule } from './shared/shared.module';
 | 
			
		||||
    ReactiveFormsModule,
 | 
			
		||||
    BrowserAnimationsModule,
 | 
			
		||||
    NgbButtonsModule,
 | 
			
		||||
    NgbTooltipModule,
 | 
			
		||||
    NgbPaginationModule,
 | 
			
		||||
    NgbDropdownModule,
 | 
			
		||||
    InfiniteScrollModule,
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,12 @@
 | 
			
		||||
                    <td>After <app-timespan [time]="tx.status.block_time - transactionTime"></app-timespan></td>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
                <ng-container *ngTemplateOutlet="features"></ng-container>
 | 
			
		||||
                <tr *ngIf="network !== 'liquid'">
 | 
			
		||||
                  <td class="td-width">Features</td>
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <app-tx-features [tx]="tx"></app-tx-features>
 | 
			
		||||
                  </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
@ -68,9 +73,7 @@
 | 
			
		||||
                    <td>
 | 
			
		||||
                      {{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB
 | 
			
		||||
                       
 | 
			
		||||
                      <span *ngIf="feeRating === 1" class="badge badge-success">Optimal</span>
 | 
			
		||||
                      <span *ngIf="feeRating === 2" class="badge badge-warning" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
                      <span *ngIf="feeRating === 3" class="badge badge-danger" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
                      <app-tx-fee-rating [tx]="tx"></app-tx-fee-rating>
 | 
			
		||||
                    </td>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
@ -124,7 +127,12 @@
 | 
			
		||||
                    </ng-template>
 | 
			
		||||
                  </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <ng-container *ngTemplateOutlet="features"></ng-container>
 | 
			
		||||
                <tr *ngIf="network !== 'liquid'">
 | 
			
		||||
                  <td class="td-width">Features</td>
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <app-tx-features [tx]="tx"></app-tx-features>
 | 
			
		||||
                  </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
@ -260,15 +268,3 @@
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<br>
 | 
			
		||||
 | 
			
		||||
<ng-template #features>
 | 
			
		||||
  <tr *ngIf="network !== 'liquid'">
 | 
			
		||||
    <td class="td-width">Features</td>
 | 
			
		||||
    <td>
 | 
			
		||||
      <span *ngIf="segwitGains.realizedGains && !segwitGains.potentialBech32Gains" class="badge badge-success mr-1" ngbTooltip="This transaction saved {{ segwitGains.realizedGains * 100 | number:  '1.0-0' }}% on fees by using native SegWit-Bech32" placement="bottom">SegWit</span>
 | 
			
		||||
      <span *ngIf="segwitGains.realizedGains && segwitGains.potentialBech32Gains" class="badge badge-warning mr-1" ngbTooltip="This transaction saved {{ segwitGains.realizedGains * 100 | number:  '1.0-0' }}% on fees by using SegWit and could save {{ segwitGains.potentialBech32Gains * 100 | number : '1.0-0' }}% more by fully upgrading to native SegWit-Bech32" placement="bottom">SegWit</span>
 | 
			
		||||
      <span *ngIf="segwitGains.potentialP2shGains" class="badge badge-danger mr-1" ngbTooltip="This transaction could save {{ segwitGains.potentialBech32Gains * 100 | number : '1.0-0' }}% on fees by upgrading to native SegWit-Bech32 or {{ segwitGains.potentialP2shGains * 100 | number:  '1.0-0' }}% by upgrading to SegWit-P2SH" placement="bottom"><del>SegWit</del></span>
 | 
			
		||||
      <span *ngIf="isRbfTransaction" class="badge badge-success" ngbTooltip="This transaction support Replace-By-Fee (RBF) allowing fee bumping" placement="bottom">RBF</span>
 | 
			
		||||
    </td>
 | 
			
		||||
  </tr>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -9,7 +9,6 @@ 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 { calcSegwitFeeGains } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { BisqTransaction } from 'src/app/bisq/bisq.interfaces';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
@ -21,9 +20,6 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
  network = '';
 | 
			
		||||
  tx: Transaction;
 | 
			
		||||
  txId: string;
 | 
			
		||||
  feeRating: number;
 | 
			
		||||
  overpaidTimes: number;
 | 
			
		||||
  medianFeeNeeded: number;
 | 
			
		||||
  txInBlockIndex: number;
 | 
			
		||||
  isLoadingTx = true;
 | 
			
		||||
  error: any = undefined;
 | 
			
		||||
@ -31,14 +27,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
  latestBlock: Block;
 | 
			
		||||
  transactionTime = -1;
 | 
			
		||||
  subscription: Subscription;
 | 
			
		||||
  segwitGains = {
 | 
			
		||||
    realizedGains: 0,
 | 
			
		||||
    potentialBech32Gains: 0,
 | 
			
		||||
    potentialP2shGains: 0,
 | 
			
		||||
  };
 | 
			
		||||
  isRbfTransaction: boolean;
 | 
			
		||||
  rbfTransaction: undefined | Transaction;
 | 
			
		||||
  bisqTx: BisqTransaction;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
@ -89,8 +78,6 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
      this.error = undefined;
 | 
			
		||||
      this.waitingForTransaction = false;
 | 
			
		||||
      this.setMempoolBlocksSubscription();
 | 
			
		||||
      this.segwitGains = calcSegwitFeeGains(tx);
 | 
			
		||||
      this.isRbfTransaction = tx.vin.some((v) => v.sequence < 0xfffffffe);
 | 
			
		||||
 | 
			
		||||
      if (!tx.status.confirmed) {
 | 
			
		||||
        this.websocketService.startTrackTransaction(tx.txid);
 | 
			
		||||
@ -100,8 +87,6 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
        } else {
 | 
			
		||||
          this.getTransactionTime();
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        this.findBlockAndSetFeeRating();
 | 
			
		||||
      }
 | 
			
		||||
      if (this.tx.status.confirmed) {
 | 
			
		||||
        this.stateService.markBlock$.next({ blockHeight: tx.status.block_height });
 | 
			
		||||
@ -127,7 +112,6 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
          };
 | 
			
		||||
          this.stateService.markBlock$.next({ blockHeight: block.height });
 | 
			
		||||
          this.audioService.playSound('magic');
 | 
			
		||||
          this.findBlockAndSetFeeRating();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
@ -171,42 +155,12 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  findBlockAndSetFeeRating() {
 | 
			
		||||
    this.stateService.blocks$
 | 
			
		||||
      .pipe(
 | 
			
		||||
        filter(([block]) => block.height === this.tx.status.block_height),
 | 
			
		||||
        take(1)
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe(([block]) => {
 | 
			
		||||
        const feePervByte = this.tx.fee / (this.tx.weight / 4);
 | 
			
		||||
        this.medianFeeNeeded = Math.round(block.feeRange[Math.round(block.feeRange.length * 0.5)]);
 | 
			
		||||
 | 
			
		||||
        // Block not filled
 | 
			
		||||
        if (block.weight < 4000000 * 0.95) {
 | 
			
		||||
          this.medianFeeNeeded = 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.overpaidTimes = Math.round(feePervByte / this.medianFeeNeeded);
 | 
			
		||||
 | 
			
		||||
        if (feePervByte <= this.medianFeeNeeded || this.overpaidTimes < 2) {
 | 
			
		||||
          this.feeRating = 1;
 | 
			
		||||
        } else {
 | 
			
		||||
          this.feeRating = 2;
 | 
			
		||||
          if (this.overpaidTimes > 10) {
 | 
			
		||||
            this.feeRating = 3;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  resetTransaction() {
 | 
			
		||||
    this.error = undefined;
 | 
			
		||||
    this.tx = null;
 | 
			
		||||
    this.feeRating = undefined;
 | 
			
		||||
    this.waitingForTransaction = false;
 | 
			
		||||
    this.isLoadingTx = true;
 | 
			
		||||
    this.rbfTransaction = undefined;
 | 
			
		||||
    this.bisqTx = undefined;
 | 
			
		||||
    this.transactionTime = -1;
 | 
			
		||||
    document.body.scrollTo(0, 0);
 | 
			
		||||
    this.leaveTransaction();
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
<span *ngIf="segwitGains.realizedGains && !segwitGains.potentialBech32Gains" class="badge badge-success mr-1" ngbTooltip="This transaction saved {{ segwitGains.realizedGains * 100 | number:  '1.0-0' }}% on fees by using native SegWit-Bech32" placement="bottom">SegWit</span>
 | 
			
		||||
<span *ngIf="segwitGains.realizedGains && segwitGains.potentialBech32Gains" class="badge badge-warning mr-1" ngbTooltip="This transaction saved {{ segwitGains.realizedGains * 100 | number:  '1.0-0' }}% on fees by using SegWit and could save {{ segwitGains.potentialBech32Gains * 100 | number : '1.0-0' }}% more by fully upgrading to native SegWit-Bech32" placement="bottom">SegWit</span>
 | 
			
		||||
<span *ngIf="segwitGains.potentialP2shGains" class="badge badge-danger mr-1" ngbTooltip="This transaction could save {{ segwitGains.potentialBech32Gains * 100 | number : '1.0-0' }}% on fees by upgrading to native SegWit-Bech32 or {{ segwitGains.potentialP2shGains * 100 | number:  '1.0-0' }}% by upgrading to SegWit-P2SH" placement="bottom"><del>SegWit</del></span>
 | 
			
		||||
<span *ngIf="isRbfTransaction" class="badge badge-success" ngbTooltip="This transaction support Replace-By-Fee (RBF) allowing fee bumping" placement="bottom">RBF</span>
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
import { Component, ChangeDetectionStrategy, OnChanges, Input } from '@angular/core';
 | 
			
		||||
import { calcSegwitFeeGains } from 'src/app/bitcoin.utils';
 | 
			
		||||
import { Transaction } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tx-features',
 | 
			
		||||
  templateUrl: './tx-features.component.html',
 | 
			
		||||
  styleUrls: ['./tx-features.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class TxFeaturesComponent implements OnChanges {
 | 
			
		||||
  @Input() tx: Transaction;
 | 
			
		||||
 | 
			
		||||
  segwitGains = {
 | 
			
		||||
    realizedGains: 0,
 | 
			
		||||
    potentialBech32Gains: 0,
 | 
			
		||||
    potentialP2shGains: 0,
 | 
			
		||||
  };
 | 
			
		||||
  isRbfTransaction: boolean;
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
    this.segwitGains = calcSegwitFeeGains(this.tx);
 | 
			
		||||
    this.isRbfTransaction = this.tx.vin.some((v) => v.sequence < 0xfffffffe);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
<span *ngIf="feeRating === 1" class="badge badge-success">Optimal</span>
 | 
			
		||||
<span *ngIf="feeRating === 2" class="badge badge-warning" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
<span *ngIf="feeRating === 3" class="badge badge-danger" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block">Overpaid {{ overpaidTimes }}x</span>
 | 
			
		||||
@ -0,0 +1,64 @@
 | 
			
		||||
import { Component, ChangeDetectionStrategy, OnChanges, Input, OnInit, ChangeDetectorRef } from '@angular/core';
 | 
			
		||||
import { Transaction, Block } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tx-fee-rating',
 | 
			
		||||
  templateUrl: './tx-fee-rating.component.html',
 | 
			
		||||
  styleUrls: ['./tx-fee-rating.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TxFeeRatingComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() tx: Transaction;
 | 
			
		||||
 | 
			
		||||
  medianFeeNeeded: number;
 | 
			
		||||
  overpaidTimes: number;
 | 
			
		||||
  feeRating: number;
 | 
			
		||||
 | 
			
		||||
  blocks: Block[] = [];
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.stateService.blocks$.subscribe(([block]) => {
 | 
			
		||||
      this.blocks.push(block);
 | 
			
		||||
      if (this.tx.status.confirmed && this.tx.status.block_height === block.height) {
 | 
			
		||||
        this.calculateRatings(block);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
    this.feeRating = undefined;
 | 
			
		||||
    if (!this.tx.status.confirmed) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const foundBlock = this.blocks.find((b) => b.height === this.tx.status.block_height);
 | 
			
		||||
    if (foundBlock) {
 | 
			
		||||
      this.calculateRatings(foundBlock);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  calculateRatings(block: Block) {
 | 
			
		||||
    const feePervByte = this.tx.fee / (this.tx.weight / 4);
 | 
			
		||||
    this.medianFeeNeeded = Math.round(block.feeRange[Math.round(block.feeRange.length * 0.5)]);
 | 
			
		||||
 | 
			
		||||
    // Block not filled
 | 
			
		||||
    if (block.weight < 4000000 * 0.95) {
 | 
			
		||||
      this.medianFeeNeeded = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.overpaidTimes = Math.round(feePervByte / this.medianFeeNeeded);
 | 
			
		||||
 | 
			
		||||
    if (feePervByte <= this.medianFeeNeeded || this.overpaidTimes < 2) {
 | 
			
		||||
      this.feeRating = 1;
 | 
			
		||||
    } else {
 | 
			
		||||
      this.feeRating = 2;
 | 
			
		||||
      if (this.overpaidTimes > 10) {
 | 
			
		||||
        this.feeRating = 3;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -12,10 +12,18 @@ import { TimeSinceComponent } from '../components/time-since/time-since.componen
 | 
			
		||||
import { ClipboardComponent } from '../components/clipboard/clipboard.component';
 | 
			
		||||
import { QrcodeComponent } from '../components/qrcode/qrcode.component';
 | 
			
		||||
import { FiatComponent } from '../fiat/fiat.component';
 | 
			
		||||
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { NgbNavModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { TxFeaturesComponent } from '../components/tx-features/tx-features.component';
 | 
			
		||||
import { TxFeeRatingComponent } from '../components/tx-fee-rating/tx-fee-rating.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
    ClipboardComponent,
 | 
			
		||||
    TimeSinceComponent,
 | 
			
		||||
    QrcodeComponent,
 | 
			
		||||
    FiatComponent,
 | 
			
		||||
    TxFeaturesComponent,
 | 
			
		||||
    TxFeeRatingComponent,
 | 
			
		||||
    ScriptpubkeyTypePipe,
 | 
			
		||||
    RelativeUrlPipe,
 | 
			
		||||
    Hex2asciiPipe,
 | 
			
		||||
@ -24,14 +32,11 @@ import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
    WuBytesPipe,
 | 
			
		||||
    CeilPipe,
 | 
			
		||||
    ShortenStringPipe,
 | 
			
		||||
    ClipboardComponent,
 | 
			
		||||
    TimeSinceComponent,
 | 
			
		||||
    QrcodeComponent,
 | 
			
		||||
    FiatComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
    NgbNavModule,
 | 
			
		||||
    NgbTooltipModule,
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    VbytesPipe,
 | 
			
		||||
@ -39,6 +44,13 @@ import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
  exports: [
 | 
			
		||||
    NgbNavModule,
 | 
			
		||||
    CommonModule,
 | 
			
		||||
    NgbTooltipModule,
 | 
			
		||||
    TimeSinceComponent,
 | 
			
		||||
    ClipboardComponent,
 | 
			
		||||
    QrcodeComponent,
 | 
			
		||||
    FiatComponent,
 | 
			
		||||
    TxFeaturesComponent,
 | 
			
		||||
    TxFeeRatingComponent,
 | 
			
		||||
    ScriptpubkeyTypePipe,
 | 
			
		||||
    RelativeUrlPipe,
 | 
			
		||||
    Hex2asciiPipe,
 | 
			
		||||
@ -47,10 +59,6 @@ import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
    WuBytesPipe,
 | 
			
		||||
    CeilPipe,
 | 
			
		||||
    ShortenStringPipe,
 | 
			
		||||
    TimeSinceComponent,
 | 
			
		||||
    ClipboardComponent,
 | 
			
		||||
    QrcodeComponent,
 | 
			
		||||
    FiatComponent,
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class SharedModule {}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user