diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 7ff1e770b..5b3680f3d 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -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, diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 60d4f1954..79128f8ea 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -51,7 +51,12 @@ After - + + Features + + + + @@ -68,9 +73,7 @@ {{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB   - Optimal - Overpaid {{ overpaidTimes }}x - Overpaid {{ overpaidTimes }}x + @@ -124,7 +127,12 @@ - + + Features + + + + @@ -260,15 +268,3 @@
- - - - Features - - SegWit - SegWit - SegWit - RBF - - - \ No newline at end of file diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 495685831..d71f87031 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -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(); diff --git a/frontend/src/app/components/tx-features/tx-features.component.html b/frontend/src/app/components/tx-features/tx-features.component.html new file mode 100644 index 000000000..20513614d --- /dev/null +++ b/frontend/src/app/components/tx-features/tx-features.component.html @@ -0,0 +1,4 @@ +SegWit +SegWit +SegWit +RBF diff --git a/frontend/src/app/components/tx-features/tx-features.component.scss b/frontend/src/app/components/tx-features/tx-features.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/components/tx-features/tx-features.component.ts b/frontend/src/app/components/tx-features/tx-features.component.ts new file mode 100644 index 000000000..0c3711499 --- /dev/null +++ b/frontend/src/app/components/tx-features/tx-features.component.ts @@ -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); + } +} diff --git a/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.html b/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.html new file mode 100644 index 000000000..bd2a4221f --- /dev/null +++ b/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.html @@ -0,0 +1,3 @@ +Optimal +Overpaid {{ overpaidTimes }}x +Overpaid {{ overpaidTimes }}x \ No newline at end of file diff --git a/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.scss b/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.ts b/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.ts new file mode 100644 index 000000000..32ca9b258 --- /dev/null +++ b/frontend/src/app/components/tx-fee-rating/tx-fee-rating.component.ts @@ -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; + } + } + } +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 274f898c7..9c27e80dd 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -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 {}