mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 06:47:52 +01:00
Merge pull request #5605 from mempool/mononaut/tx-extras-module
Refactor transaction page component
This commit is contained in:
commit
c8ce4631e2
8 changed files with 614 additions and 341 deletions
|
@ -0,0 +1,324 @@
|
|||
<div class="box">
|
||||
<div class="row">
|
||||
@if (isMobile) {
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngTemplateOutlet="detailsLeft"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="detailsRight"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngTemplateOutlet="detailsLeft"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngTemplateOutlet="detailsRight"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #detailsLeft>
|
||||
@if (tx?.status?.confirmed) {
|
||||
<ng-container *ngTemplateOutlet="timestampRow"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="confirmedAfterRow"></ng-container>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="firstSeenRow"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="etaRow"></ng-container>
|
||||
}
|
||||
<ng-container *ngTemplateOutlet="featuresRow"></ng-container>
|
||||
@if (tx?.status?.confirmed) {
|
||||
<ng-container *ngTemplateOutlet="auditRow"></ng-container>
|
||||
}
|
||||
<ng-container *ngTemplateOutlet="gogglesRow"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #detailsRight>
|
||||
<ng-container *ngTemplateOutlet="feeRow"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="feeRateRow"></ng-container>
|
||||
@if (!isLoadingTx && !tx?.status?.confirmed && isAcceleration && ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo)) {
|
||||
<ng-container *ngTemplateOutlet="acceleratingRow"></ng-container>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="effectiveRateRow"></ng-container>
|
||||
}
|
||||
@if (tx?.status?.confirmed) {
|
||||
<ng-container *ngTemplateOutlet="minerRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #timestampRow>
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td i18n="block.timestamp">Timestamp</td>
|
||||
<td>
|
||||
‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time kind="since" [time]="tx.status.block_time" [fastRender]="true"></app-time>)</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #confirmedAfterRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if (transactionTime > 0) {
|
||||
<tr>
|
||||
<td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
|
||||
<td><app-time kind="span" [time]="tx.status.block_time - transactionTime" [fastRender]="true" [showTooltip]="true"></app-time></td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #firstSeenRow>
|
||||
@if (isLoadingTx) {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
} @else if (transactionTime > 0) {
|
||||
<tr>
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td><i><app-time kind="since" [time]="transactionTime" [fastRender]="true" [showTooltip]="true"></app-time></i></td>
|
||||
</tr>
|
||||
} @else if (isLoadingFirstSeen) {
|
||||
<tr>
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #featuresRow>
|
||||
@if (network !== 'liquid' && network !== 'liquidtestnet') {
|
||||
@if (!isLoadingTx) {
|
||||
@if (featuresEnabled) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="transaction.features|Transaction features" id="acceleratePreviewAnchor">Features</td>
|
||||
<td>
|
||||
<app-tx-features [tx]="tx"></app-tx-features>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #auditRow>
|
||||
@if (network === '') {
|
||||
@if (!isLoadingTx) {
|
||||
@if (auditStatus) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="block.toggle-audit|Toggle Audit">Audit</td>
|
||||
<td class="wrap-cell">
|
||||
<ng-container>
|
||||
@if (auditStatus.coinbase) {
|
||||
<span class="badge badge-primary mr-1" i18n="transactions-list.coinbase">Coinbase</span>
|
||||
} @else if (auditStatus.expected) {
|
||||
<span class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span>
|
||||
} @else if (auditStatus.seen) {
|
||||
<span class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span>
|
||||
} @else if (!auditStatus.conflict) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span>
|
||||
}
|
||||
@if (auditStatus.added) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Added transaction tooltip" ngbTooltip="This transaction may have been added out-of-band" placement="bottom" i18n="tx-features.tag.added|Added">Added</span>
|
||||
}
|
||||
@if (auditStatus.prioritized) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Prioritized transaction tooltip" ngbTooltip="This transaction may have been prioritized out-of-band" placement="bottom" i18n="tx-features.tag.prioritized|Prioritized">Prioritized</span>
|
||||
}
|
||||
@if (auditStatus.conflict) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Conflict in mempool tooltip" ngbTooltip="This transaction conflicted with another version in our mempool" placement="bottom" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
|
||||
}
|
||||
</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #etaRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if (!replaced && !isCached) {
|
||||
<tr>
|
||||
<td class="td-width align-items-center align-middle" i18n="transaction.eta|Transaction ETA">ETA</td>
|
||||
<td>
|
||||
<ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton">
|
||||
@if (network === 'liquid' || network === 'liquidtestnet') {
|
||||
<app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
} @else {
|
||||
<span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) ? 'etaDeepMempool d-flex justify-content-between' : ''">
|
||||
@if (eta.blocks >= 7) {
|
||||
<span i18n="transaction.eta.not-any-time-soon|Transaction ETA mot any time soon">Not any time soon</span>
|
||||
} @else {
|
||||
<app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
}
|
||||
@if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) {
|
||||
<div class="d-flex accelerate">
|
||||
<a class="btn btn-sm accelerateDeepMempool btn-small-height" [class.disabled]="!eligibleForAcceleration" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
|
||||
<a *ngIf="!eligibleForAcceleration" href="https://mempool.space/accelerator#why-cant-accelerate" target="_blank" class="info-badges ml-1" i18n-ngbTooltip="Mempool Accelerator™ tooltip" ngbTooltip="This transaction cannot be accelerated">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-template #etaSkeleton>
|
||||
<span class="skeleton-loader"></span>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #gogglesRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if (isAcceleration || filters.length) {
|
||||
<tr>
|
||||
<td class="td-width">
|
||||
<span class="goggles-icon"><app-svg-images name="goggles" width="100%" height="100%"></app-svg-images></span>
|
||||
</td>
|
||||
<td class="wrap-cell">
|
||||
@if (isAcceleration) {
|
||||
<span class="badge badge-accelerated mr-1" i18n="transaction.audit.accelerated">Accelerated</span>
|
||||
}
|
||||
<ng-container *ngFor="let filter of filters;">
|
||||
<span class="badge badge-primary filter-tag mr-1">{{ filter.label }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #feeRow>
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
||||
<td class="text-wrap">{{ tx.fee | number }} <span class="symbol" i18n="shared.sats">sats</span>
|
||||
@if (accelerationInfo?.bidBoost ?? tx.feeDelta > 0) {
|
||||
<span class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band"> +{{ accelerationInfo?.bidBoost ?? tx.feeDelta | number }} </span><span class="symbol" i18n="shared.sats">sats</span>
|
||||
}
|
||||
<span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee + ((accelerationInfo?.bidBoost ?? tx.feeDelta) || 0)"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #feeRateRow>
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
||||
<td>
|
||||
<app-fee-rate [fee]="tx.feePerVsize"></app-fee-rate>
|
||||
@if (tx?.status?.confirmed && tx.fee && !hasEffectiveFeeRate && !accelerationInfo) {
|
||||
|
||||
<app-tx-fee-rating [tx]="tx"></app-tx-fee-rating>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #effectiveRateRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo) {
|
||||
<tr>
|
||||
@if (isAcceleration) {
|
||||
<td i18n="transaction.accelerated-fee-rate|Accelerated transaction fee rate">Accelerated fee rate</td>
|
||||
} @else {
|
||||
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
}
|
||||
<td>
|
||||
<div class="effective-fee-container">
|
||||
@if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize || tx.acceleration)) {
|
||||
<app-fee-rate [class.oobFees]="isAcceleration" [fee]="accelerationInfo.acceleratedFeeRate"></app-fee-rate>
|
||||
} @else {
|
||||
<app-fee-rate [class.oobFees]="isAcceleration" [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
|
||||
}
|
||||
|
||||
@if (tx?.status?.confirmed && !tx.acceleration && !accelerationInfo && tx.fee && tx.effectiveFeePerVsize) {
|
||||
<app-tx-fee-rating class="ml-2 mr-2 effective-fee-rating" [tx]="tx"></app-tx-fee-rating>
|
||||
}
|
||||
</div>
|
||||
@if (hasCpfp) {
|
||||
<button type="button" class="btn btn-outline-info btn-sm btn-small-height float-right" (click)="toggleCpfp()">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #acceleratingRow>
|
||||
<tr>
|
||||
<td rowspan="2" colspan="2" style="padding: 0;">
|
||||
<app-active-acceleration-box [acceleratedBy]="tx.acceleratedBy" [effectiveFeeRate]="tx.effectiveFeePerVsize" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats" [hasCpfp]="hasCpfp" (toggleCpfp)="showCpfpDetails = !showCpfpDetails" [chartPositionLeft]="isMobile"></app-active-acceleration-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #minerRow>
|
||||
@if (network === '') {
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="block.miner">Miner</td>
|
||||
@if (pool) {
|
||||
<td class="wrap-cell">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: #FFF;padding:0;">
|
||||
<span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
|
||||
@if (pool.minerNames[1].length > 16) {
|
||||
{{ pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pool.name + ' mining pool'">
|
||||
{{ pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
} @else {
|
||||
<td>
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #skeletonDetailsRow>
|
||||
<tr>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
|
@ -0,0 +1,183 @@
|
|||
.title-block {
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
@media (min-width: 650px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
h1 {
|
||||
margin: 0rem;
|
||||
margin-right: 15px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.td-width {
|
||||
width: 150px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 175px;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.miner-name {
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.pool-logo {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.badge.badge-accelerated {
|
||||
background-color: var(--tertiary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-small-height {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.row{
|
||||
flex-direction: column;
|
||||
@media (min-width: 850px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.box.hidden {
|
||||
visibility: hidden;
|
||||
height: 0px;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.mobile-bottomcol {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.details-table td:first-child {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.fiat {
|
||||
display: block;
|
||||
@media (min-width: 768px){
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
tr td {
|
||||
padding: 0.75rem 0.5rem;
|
||||
@media (min-width: 576px) {
|
||||
padding: 0.75rem 0.75rem;
|
||||
}
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
@media (min-width: 850px) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.wrap-cell {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.effective-fee-container {
|
||||
display: block;
|
||||
@media (min-width: 768px){
|
||||
display: inline-block;
|
||||
}
|
||||
@media (max-width: 425px){
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px){
|
||||
.hide-on-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.effective-fee-rating {
|
||||
@media (max-width: 767px){
|
||||
margin-right: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline-info {
|
||||
margin-top: 5px;
|
||||
@media (min-width: 768px){
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.eta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
@media (min-width: 850px) {
|
||||
justify-content: left !important;
|
||||
}
|
||||
}
|
||||
|
||||
.accelerate {
|
||||
@media (min-width: 850px) {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.etaDeepMempool {
|
||||
flex-wrap: wrap;
|
||||
@media (max-width: 849px) {
|
||||
justify-content: right !important;
|
||||
}
|
||||
}
|
||||
|
||||
.accelerateDeepMempool {
|
||||
background-color: var(--tertiary);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.goggles-icon {
|
||||
display: block;
|
||||
width: 2.7em;
|
||||
}
|
||||
|
||||
.pool-logo {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.oobFees {
|
||||
color: #905cf4;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { Component, OnInit, Input, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core';
|
||||
import { Transaction } from '@interfaces/electrs.interface';
|
||||
import { Acceleration, CpfpInfo } from '@interfaces/node-api.interface';
|
||||
import { Pool, TxAuditStatus } from '@components/transaction/transaction.component';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ETA } from '@app/services/eta.service';
|
||||
import { MiningStats } from '@app/services/mining.service';
|
||||
import { Filter } from '@app/shared/filters.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transaction-details',
|
||||
templateUrl: './transaction-details.component.html',
|
||||
styleUrls: ['./transaction-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TransactionDetailsComponent implements OnInit {
|
||||
@Input() network: string;
|
||||
@Input() tx: Transaction;
|
||||
@Input() isLoadingTx: boolean;
|
||||
@Input() isMobile: boolean;
|
||||
@Input() transactionTime: number;
|
||||
@Input() isLoadingFirstSeen: boolean;
|
||||
@Input() featuresEnabled: boolean;
|
||||
@Input() auditStatus: TxAuditStatus;
|
||||
@Input() filters: Filter[];
|
||||
@Input() miningStats: MiningStats;
|
||||
@Input() pool: Pool | null;
|
||||
@Input() isAcceleration: boolean;
|
||||
@Input() hasEffectiveFeeRate: boolean;
|
||||
@Input() cpfpInfo: CpfpInfo;
|
||||
@Input() hasCpfp: boolean;
|
||||
@Input() showCpfpDetails: boolean;
|
||||
@Input() accelerationInfo: Acceleration;
|
||||
@Input() acceleratorAvailable: boolean;
|
||||
@Input() accelerateCtaType: string;
|
||||
@Input() notAcceleratedOnLoad: boolean;
|
||||
@Input() showAccelerationSummary: boolean;
|
||||
@Input() eligibleForAcceleration: boolean;
|
||||
@Input() replaced: boolean;
|
||||
@Input() isCached: boolean;
|
||||
@Input() ETA$: Observable<ETA>;
|
||||
|
||||
@Output() accelerateClicked = new EventEmitter<boolean>();
|
||||
@Output() toggleCpfp$ = new EventEmitter<void>();
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
onAccelerateClicked(): void {
|
||||
this.accelerateClicked.emit(true);
|
||||
}
|
||||
|
||||
toggleCpfp(): void {
|
||||
this.toggleCpfp$.emit();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
],
|
||||
exports: [
|
||||
]
|
||||
})
|
||||
export class TransactionExtrasModule { }
|
|
@ -31,35 +31,35 @@
|
|||
<div class="clearfix"></div>
|
||||
|
||||
@if (!error) {
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
@if (isMobile) {
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngTemplateOutlet="detailsLeft"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="detailsRight"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngTemplateOutlet="detailsLeft"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngTemplateOutlet="detailsRight"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<app-transaction-details
|
||||
[network]="network"
|
||||
[tx]="tx"
|
||||
[isLoadingTx]="isLoadingTx"
|
||||
[isMobile]="isMobile"
|
||||
[transactionTime]="transactionTime"
|
||||
[isLoadingFirstSeen]="isLoadingFirstSeen"
|
||||
[featuresEnabled]="featuresEnabled"
|
||||
[auditStatus]="auditStatus"
|
||||
[filters]="filters"
|
||||
[miningStats]="miningStats"
|
||||
[pool]="pool"
|
||||
[isAcceleration]="isAcceleration"
|
||||
[acceleratorAvailable]="acceleratorAvailable"
|
||||
[accelerateCtaType]="accelerateCtaType"
|
||||
[notAcceleratedOnLoad]="notAcceleratedOnLoad"
|
||||
[showAccelerationSummary]="showAccelerationSummary"
|
||||
[eligibleForAcceleration]="eligibleForAcceleration"
|
||||
[hasEffectiveFeeRate]="hasEffectiveFeeRate"
|
||||
[cpfpInfo]="cpfpInfo"
|
||||
[hasCpfp]="hasCpfp"
|
||||
[showCpfpDetails]="showCpfpDetails"
|
||||
[accelerationInfo]="accelerationInfo"
|
||||
[replaced]="replaced"
|
||||
[isCached]="isCached"
|
||||
[ETA$]="ETA$"
|
||||
(accelerateClicked)="onAccelerateClicked()"
|
||||
(toggleCpfp$)="this.showCpfpDetails = !this.showCpfpDetails"
|
||||
></app-transaction-details>
|
||||
}
|
||||
|
||||
<span id="accelerate"></span>
|
||||
|
@ -416,299 +416,4 @@
|
|||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
|
||||
<ng-template #detailsLeft>
|
||||
@if (tx?.status?.confirmed) {
|
||||
<ng-container *ngTemplateOutlet="timestampRow"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="confirmedAfterRow"></ng-container>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="firstSeenRow"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="etaRow"></ng-container>
|
||||
}
|
||||
<ng-container *ngTemplateOutlet="featuresRow"></ng-container>
|
||||
@if (tx?.status?.confirmed) {
|
||||
<ng-container *ngTemplateOutlet="auditRow"></ng-container>
|
||||
}
|
||||
<ng-container *ngTemplateOutlet="gogglesRow"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #detailsRight>
|
||||
<ng-container *ngTemplateOutlet="feeRow"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="feeRateRow"></ng-container>
|
||||
@if (!isLoadingTx && !tx?.status?.confirmed && isAcceleration && ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo)) {
|
||||
<ng-container *ngTemplateOutlet="acceleratingRow"></ng-container>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="effectiveRateRow"></ng-container>
|
||||
}
|
||||
@if (tx?.status?.confirmed) {
|
||||
<ng-container *ngTemplateOutlet="minerRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #timestampRow>
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td i18n="block.timestamp">Timestamp</td>
|
||||
<td>
|
||||
‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time kind="since" [time]="tx.status.block_time" [fastRender]="true"></app-time>)</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #confirmedAfterRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if (transactionTime > 0) {
|
||||
<tr>
|
||||
<td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
|
||||
<td><app-time kind="span" [time]="tx.status.block_time - transactionTime" [fastRender]="true" [showTooltip]="true"></app-time></td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #firstSeenRow>
|
||||
@if (isLoadingTx) {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
} @else if (transactionTime > 0) {
|
||||
<tr>
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td><i><app-time kind="since" [time]="transactionTime" [fastRender]="true" [showTooltip]="true"></app-time></i></td>
|
||||
</tr>
|
||||
} @else if (isLoadingFirstSeen) {
|
||||
<tr>
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #featuresRow>
|
||||
@if (network !== 'liquid' && network !== 'liquidtestnet') {
|
||||
@if (!isLoadingTx) {
|
||||
@if (featuresEnabled) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="transaction.features|Transaction features" id="acceleratePreviewAnchor">Features</td>
|
||||
<td>
|
||||
<app-tx-features [tx]="tx"></app-tx-features>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #auditRow>
|
||||
@if (network === '') {
|
||||
@if (!isLoadingTx) {
|
||||
@if (auditStatus) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="block.toggle-audit|Toggle Audit">Audit</td>
|
||||
<td class="wrap-cell">
|
||||
<ng-container>
|
||||
@if (auditStatus.coinbase) {
|
||||
<span class="badge badge-primary mr-1" i18n="transactions-list.coinbase">Coinbase</span>
|
||||
} @else if (auditStatus.expected) {
|
||||
<span class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span>
|
||||
} @else if (auditStatus.seen) {
|
||||
<span class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span>
|
||||
} @else if (!auditStatus.conflict) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span>
|
||||
}
|
||||
@if (auditStatus.added) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Added transaction tooltip" ngbTooltip="This transaction may have been added out-of-band" placement="bottom" i18n="tx-features.tag.added|Added">Added</span>
|
||||
}
|
||||
@if (auditStatus.prioritized) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Prioritized transaction tooltip" ngbTooltip="This transaction may have been prioritized out-of-band" placement="bottom" i18n="tx-features.tag.prioritized|Prioritized">Prioritized</span>
|
||||
}
|
||||
@if (auditStatus.conflict) {
|
||||
<span class="badge badge-warning mr-1" i18n-ngbTooltip="Conflict in mempool tooltip" ngbTooltip="This transaction conflicted with another version in our mempool" placement="bottom" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
|
||||
}
|
||||
</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #etaRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if (!replaced && !isCached) {
|
||||
<tr>
|
||||
<td class="td-width align-items-center align-middle" i18n="transaction.eta|Transaction ETA">ETA</td>
|
||||
<td>
|
||||
<ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton">
|
||||
@if (network === 'liquid' || network === 'liquidtestnet') {
|
||||
<app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
} @else {
|
||||
<span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) ? 'etaDeepMempool d-flex justify-content-between' : ''">
|
||||
@if (eta.blocks >= 7) {
|
||||
<span i18n="transaction.eta.not-any-time-soon|Transaction ETA mot any time soon">Not any time soon</span>
|
||||
} @else {
|
||||
<app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
}
|
||||
@if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) {
|
||||
<div class="d-flex accelerate">
|
||||
<a class="btn btn-sm accelerateDeepMempool btn-small-height" [class.disabled]="!eligibleForAcceleration" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
|
||||
<a *ngIf="!eligibleForAcceleration" href="https://mempool.space/accelerator#why-cant-accelerate" target="_blank" class="info-badges ml-1" i18n-ngbTooltip="Mempool Accelerator™ tooltip" ngbTooltip="This transaction cannot be accelerated">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-template #etaSkeleton>
|
||||
<span class="skeleton-loader"></span>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #gogglesRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if (isAcceleration || filters.length) {
|
||||
<tr>
|
||||
<td class="td-width">
|
||||
<span class="goggles-icon"><app-svg-images name="goggles" width="100%" height="100%"></app-svg-images></span>
|
||||
</td>
|
||||
<td class="wrap-cell">
|
||||
@if (isAcceleration) {
|
||||
<span class="badge badge-accelerated mr-1" i18n="transaction.audit.accelerated">Accelerated</span>
|
||||
}
|
||||
<ng-container *ngFor="let filter of filters;">
|
||||
<span class="badge badge-primary filter-tag mr-1">{{ filter.label }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #feeRow>
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
||||
<td class="text-wrap">{{ tx.fee | number }} <span class="symbol" i18n="shared.sats">sats</span>
|
||||
@if (accelerationInfo?.bidBoost ?? tx.feeDelta > 0) {
|
||||
<span class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band"> +{{ accelerationInfo?.bidBoost ?? tx.feeDelta | number }} </span><span class="symbol" i18n="shared.sats">sats</span>
|
||||
}
|
||||
<span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee + ((accelerationInfo?.bidBoost ?? tx.feeDelta) || 0)"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #feeRateRow>
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
||||
<td>
|
||||
<app-fee-rate [fee]="tx.feePerVsize"></app-fee-rate>
|
||||
@if (tx?.status?.confirmed && tx.fee && !hasEffectiveFeeRate && !accelerationInfo) {
|
||||
|
||||
<app-tx-fee-rating [tx]="tx"></app-tx-fee-rating>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #effectiveRateRow>
|
||||
@if (!isLoadingTx) {
|
||||
@if ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo) {
|
||||
<tr>
|
||||
@if (isAcceleration) {
|
||||
<td i18n="transaction.accelerated-fee-rate|Accelerated transaction fee rate">Accelerated fee rate</td>
|
||||
} @else {
|
||||
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
}
|
||||
<td>
|
||||
<div class="effective-fee-container">
|
||||
@if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize || tx.acceleration)) {
|
||||
<app-fee-rate [class.oobFees]="isAcceleration" [fee]="accelerationInfo.acceleratedFeeRate"></app-fee-rate>
|
||||
} @else {
|
||||
<app-fee-rate [class.oobFees]="isAcceleration" [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
|
||||
}
|
||||
|
||||
@if (tx?.status?.confirmed && !tx.acceleration && !accelerationInfo && tx.fee && tx.effectiveFeePerVsize) {
|
||||
<app-tx-fee-rating class="ml-2 mr-2 effective-fee-rating" [tx]="tx"></app-tx-fee-rating>
|
||||
}
|
||||
</div>
|
||||
@if (hasCpfp) {
|
||||
<button type="button" class="btn btn-outline-info btn-sm btn-small-height float-right" (click)="showCpfpDetails = !showCpfpDetails">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #acceleratingRow>
|
||||
<tr>
|
||||
<td rowspan="2" colspan="2" style="padding: 0;">
|
||||
<app-active-acceleration-box [acceleratedBy]="tx.acceleratedBy" [effectiveFeeRate]="tx.effectiveFeePerVsize" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats" [hasCpfp]="hasCpfp" (toggleCpfp)="showCpfpDetails = !showCpfpDetails" [chartPositionLeft]="isMobile"></app-active-acceleration-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #minerRow>
|
||||
@if (network === '') {
|
||||
@if (!isLoadingTx) {
|
||||
<tr>
|
||||
<td class="td-width" i18n="block.miner">Miner</td>
|
||||
@if (pool) {
|
||||
<td class="wrap-cell">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: #FFF;padding:0;">
|
||||
<span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
|
||||
@if (pool.minerNames[1].length > 16) {
|
||||
{{ pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pool.name + ' mining pool'">
|
||||
{{ pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
} @else {
|
||||
<td>
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #skeletonDetailsRow>
|
||||
<tr>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</div>
|
|
@ -18,6 +18,7 @@
|
|||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -60,19 +61,6 @@
|
|||
top: -1px;
|
||||
}
|
||||
|
||||
.miner-name {
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.pool-logo {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.badge.badge-accelerated {
|
||||
background-color: var(--tertiary);
|
||||
color: white;
|
||||
|
@ -94,7 +82,7 @@
|
|||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.row{
|
||||
.row {
|
||||
flex-direction: column;
|
||||
@media (min-width: 850px) {
|
||||
flex-direction: row;
|
||||
|
@ -337,4 +325,4 @@
|
|||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import { ZONE_SERVICE } from '@app/injection-tokens';
|
|||
import { MiningService, MiningStats } from '@app/services/mining.service';
|
||||
import { ETA, EtaService } from '@app/services/eta.service';
|
||||
|
||||
interface Pool {
|
||||
export interface Pool {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
|
|
|
@ -2,8 +2,10 @@ import { NgModule } from '@angular/core';
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { TransactionComponent } from '@components/transaction/transaction.component';
|
||||
import { TransactionDetailsComponent } from '@components/transaction/transaction-details/transaction-details.component';
|
||||
import { SharedModule } from '@app/shared/shared.module';
|
||||
import { TxBowtieModule } from '@components/tx-bowtie-graph/tx-bowtie.module';
|
||||
import { TransactionExtrasModule } from '@components/transaction/transaction-extras.module';
|
||||
import { GraphsModule } from '@app/graphs/graphs.module';
|
||||
import { AccelerateCheckout } from '@components/accelerate-checkout/accelerate-checkout.component';
|
||||
import { AccelerateFeeGraphComponent } from '@components/accelerate-checkout/accelerate-fee-graph.component';
|
||||
|
@ -40,14 +42,17 @@ export class TransactionRoutingModule { }
|
|||
SharedModule,
|
||||
GraphsModule,
|
||||
TxBowtieModule,
|
||||
TransactionExtrasModule,
|
||||
],
|
||||
declarations: [
|
||||
TransactionComponent,
|
||||
TransactionDetailsComponent,
|
||||
AccelerateCheckout,
|
||||
AccelerateFeeGraphComponent,
|
||||
],
|
||||
exports: [
|
||||
TransactionComponent,
|
||||
TransactionDetailsComponent,
|
||||
AccelerateCheckout,
|
||||
AccelerateFeeGraphComponent,
|
||||
]
|
||||
|
|
Loading…
Add table
Reference in a new issue