diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 79505d5c3..b0a04116f 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -21,11 +21,6 @@ class BitcoinApi implements AbstractBitcoinApi { return this.$addPrevouts(txInMempool); } - // Special case to fetch the Coinbase transaction - if (txId === '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b') { - return this.$returnCoinbaseTransaction(); - } - return this.bitcoindClient.getRawTransaction(txId, true) .then((transaction: IBitcoinApi.Transaction) => { if (skipConversion) { @@ -35,6 +30,12 @@ class BitcoinApi implements AbstractBitcoinApi { return transaction; } return this.$convertTransaction(transaction, addPrevout); + }) + .catch((e: Error) => { + if (e.message.startsWith('The genesis block coinbase')) { + return this.$returnCoinbaseTransaction(); + } + throw e; }); } @@ -238,12 +239,14 @@ class BitcoinApi implements AbstractBitcoinApi { } protected $returnCoinbaseTransaction(): Promise { - return this.bitcoindClient.getBlock('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 2) - .then((block: IBitcoinApi.Block) => { - return this.$convertTransaction(Object.assign(block.tx[0], { - confirmations: blocks.getCurrentBlockHeight() + 1, - blocktime: 1231006505 }), false); - }); + return this.bitcoindClient.getBlockHash(0).then((hash: string) => + this.bitcoindClient.getBlock(hash, 2) + .then((block: IBitcoinApi.Block) => { + return this.$convertTransaction(Object.assign(block.tx[0], { + confirmations: blocks.getCurrentBlockHeight() + 1, + blocktime: block.time }), false); + }) + ); } private $getMempoolEntry(txid: string): Promise { diff --git a/frontend/proxy.conf.js b/frontend/proxy.conf.js index faae04499..4a0489c77 100644 --- a/frontend/proxy.conf.js +++ b/frontend/proxy.conf.js @@ -61,10 +61,7 @@ PROXY_CONFIG = [ }, { context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'], - target: "https://liquid.network/testnet", - pathRewrite: { - "^/api/liquidtestnet/": "/liquidtestnet/api" - }, + target: "https://liquid.network", ws: true, secure: false, changeOrigin: true @@ -73,7 +70,9 @@ PROXY_CONFIG = [ if (configContent && configContent.BASE_MODULE == "liquid") { PROXY_CONFIG.push({ - context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'], + context: ['/resources/pools.json', + '/resources/assets.json', '/resources/assets.minimal.json', + '/resources/assets-testnet.json', '/resources/assets-testnet.minimal.json'], target: "https://liquid.network", secure: false, changeOrigin: true, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index fd7ab3a3d..3e2c40b25 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -46,6 +46,7 @@ import { SharedModule } from './shared/shared.module'; import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; import { FeesBoxComponent } from './components/fees-box/fees-box.component'; import { DashboardComponent } from './dashboard/dashboard.component'; +import { DifficultyComponent } from './components/difficulty/difficulty.component'; import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons'; @@ -97,6 +98,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; StatusViewComponent, FeesBoxComponent, DashboardComponent, + DifficultyComponent, ApiDocsComponent, CodeTemplateComponent, TermsOfServiceComponent, diff --git a/frontend/src/app/assets/assets.component.html b/frontend/src/app/assets/assets.component.html index 5d5118af0..c8962cd15 100644 --- a/frontend/src/app/assets/assets.component.html +++ b/frontend/src/app/assets/assets.component.html @@ -43,7 +43,7 @@ Name Ticker - Issuer domain + Issuer domain Asset ID diff --git a/frontend/src/app/components/asset/asset.component.html b/frontend/src/app/components/asset/asset.component.html index c79e260b6..c0a2bfbf5 100644 --- a/frontend/src/app/components/asset/asset.component.html +++ b/frontend/src/app/components/asset/asset.component.html @@ -35,13 +35,6 @@ Issuance TX {{ asset.issuance_txin.txid | shortenString : 13 }} - - - -
-
- - @@ -69,6 +62,13 @@
Pegged in {{ formatAmount(asset.chain_stats.peg_in_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}
+
+
+ + + + +
@@ -109,28 +109,39 @@ - -
- - - - - - - - - - - - -
-
-
-
- - +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
diff --git a/frontend/src/app/components/asset/asset.component.scss b/frontend/src/app/components/asset/asset.component.scss index 270ad97e3..bb2b7f783 100644 --- a/frontend/src/app/components/asset/asset.component.scss +++ b/frontend/src/app/components/asset/asset.component.scss @@ -50,3 +50,26 @@ h1 { } } +.assetIcon { + height: 150px; + margin: 25px; + @media (min-width: 768px) { + height: 250px; + margin: 0; + } +} + +.icon-holder { + display: flex; + justify-content: center; + align-items: center; +} + +.defaultIcon { + margin: 25px; + height: 150px; +} + +.defaultIcon.skeleton { + opacity: 0.5; +} diff --git a/frontend/src/app/components/asset/asset.component.ts b/frontend/src/app/components/asset/asset.component.ts index 74b074d97..ecb216052 100644 --- a/frontend/src/app/components/asset/asset.component.ts +++ b/frontend/src/app/components/asset/asset.component.ts @@ -32,6 +32,7 @@ export class AssetComponent implements OnInit, OnDestroy { isNativeAsset = false; error: any; mainSubscription: Subscription; + imageError = false; totalConfirmedTxCount = 0; loadedConfirmedTxCount = 0; diff --git a/frontend/src/app/components/difficulty/difficulty.component.html b/frontend/src/app/components/difficulty/difficulty.component.html new file mode 100644 index 000000000..5064c1c08 --- /dev/null +++ b/frontend/src/app/components/difficulty/difficulty.component.html @@ -0,0 +1,78 @@ +
Difficulty Adjustment
+
+
+
+
+
+
Remaining
+
+ + {{ i }} blocks + {{ i }} block +
+
+
+
+
Estimate
+
+ + + + + + + {{ epochData.change | absolute | number: '1.2-2' }} + % +
+ +
+
+
+ Previous: + + + + + + + + {{ epochData.previousRetarget | absolute | number: '1.2-2' }} % +
+
+
+
Current Period
+
{{ epochData.progress | number: '1.2-2' }} %
+
+
 
+
+
+
+
+
+
+ + +
+
+
Remaining
+
+
+
+
+
+
+
Estimate
+
+
+
+
+
+
+
Current Period
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/difficulty/difficulty.component.scss b/frontend/src/app/components/difficulty/difficulty.component.scss new file mode 100644 index 000000000..f66e2c8e5 --- /dev/null +++ b/frontend/src/app/components/difficulty/difficulty.component.scss @@ -0,0 +1,150 @@ +.difficulty-adjustment-container { + display: flex; + flex-direction: row; + justify-content: space-around; + height: 76px; + .shared-block { + color: #ffffff66; + font-size: 12px; + } + .item { + padding: 0 5px; + width: 100%; + &:nth-child(1) { + display: none; + @media (min-width: 485px) { + display: table-cell; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: table-cell; + } + } + } + .card-text { + font-size: 22px; + margin-top: -9px; + position: relative; + } +} + + +.difficulty-skeleton { + display: flex; + justify-content: space-between; + @media (min-width: 376px) { + flex-direction: row; + } + .item { + max-width: 150px; + margin: 0; + width: -webkit-fill-available; + @media (min-width: 376px) { + margin: 0 auto 0px; + } + &:first-child{ + display: none; + @media (min-width: 485px) { + display: block; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: block; + } + } + &:last-child { + margin-bottom: 0; + } + } + .card-text { + .skeleton-loader { + width: 100%; + display: block; + &:first-child { + margin: 14px auto 0; + max-width: 80px; + } + &:last-child { + margin: 10px auto 0; + max-width: 120px; + } + } + } +} + +.card { + background-color: #1d1f31; + height: 100%; +} + +.card-title { + color: #4a68b9; + font-size: 1rem; +} + +.progress { + display: inline-flex; + width: 100%; + background-color: #2d3348; + height: 1.1rem; + max-width: 180px; +} + +.skeleton-loader { + max-width: 100%; +} + +.more-padding { + padding: 18px; +} + +.small-bar { + height: 8px; + top: -4px; + max-width: 120px; +} + +.loading-container { + min-height: 76px; +} + +.main-title { + position: relative; + color: #ffffff91; + margin-top: -13px; + font-size: 10px; + text-transform: uppercase; + font-weight: 500; + text-align: center; + padding-bottom: 3px; +} + +.card-wrapper { + .card { + height: auto !important; + } + .card-body { + display: flex; + flex: inherit; + text-align: center; + flex-direction: column; + justify-content: space-around; + padding: 22px 20px; + } +} + +.retarget-sign { + margin-right: -3px; + font-size: 14px; + top: -2px; + position: relative; +} + +.previous-retarget-sign { + margin-right: -2px; + font-size: 10px; +} diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts new file mode 100644 index 000000000..312c1b2d0 --- /dev/null +++ b/frontend/src/app/components/difficulty/difficulty.component.ts @@ -0,0 +1,111 @@ +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { combineLatest, Observable, timer } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { StateService } from '../..//services/state.service'; + +interface EpochProgress { + base: string; + change: number; + progress: string; + remainingBlocks: number; + newDifficultyHeight: number; + colorAdjustments: string; + colorPreviousAdjustments: string; + timeAvg: string; + remainingTime: number; + previousRetarget: number; +} + +@Component({ + selector: 'app-difficulty', + templateUrl: './difficulty.component.html', + styleUrls: ['./difficulty.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DifficultyComponent implements OnInit { + isLoadingWebSocket$: Observable; + difficultyEpoch$: Observable; + + constructor( + public stateService: StateService, + ) { } + + ngOnInit(): void { + this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; + this.difficultyEpoch$ = timer(0, 1000) + .pipe( + switchMap(() => combineLatest([ + this.stateService.blocks$.pipe(map(([block]) => block)), + this.stateService.lastDifficultyAdjustment$, + this.stateService.previousRetarget$ + ])), + map(([block, DATime, previousRetarget]) => { + const now = new Date().getTime() / 1000; + const diff = now - DATime; + const blocksInEpoch = block.height % 2016; + const progress = (blocksInEpoch >= 0) ? (blocksInEpoch / 2016 * 100).toFixed(2) : `100`; + const remainingBlocks = 2016 - blocksInEpoch; + const newDifficultyHeight = block.height + remainingBlocks; + + let change = 0; + if (remainingBlocks < 1870) { + if (blocksInEpoch > 0) { + change = (600 / (diff / blocksInEpoch ) - 1) * 100; + } + if (change > 300) { + change = 300; + } + if (change < -75) { + change = -75; + } + } + + const timeAvgDiff = change * 0.1; + + let timeAvgMins = 10; + if (timeAvgDiff > 0) { + timeAvgMins -= Math.abs(timeAvgDiff); + } else { + timeAvgMins += Math.abs(timeAvgDiff); + } + + const timeAvg = timeAvgMins.toFixed(0); + const remainingTime = (remainingBlocks * timeAvgMins * 60 * 1000) + (now * 1000); + + let colorAdjustments = '#ffffff66'; + if (change > 0) { + colorAdjustments = '#3bcc49'; + } + if (change < 0) { + colorAdjustments = '#dc3545'; + } + + let colorPreviousAdjustments = '#dc3545'; + if (previousRetarget) { + if (previousRetarget >= 0) { + colorPreviousAdjustments = '#3bcc49'; + } + if (previousRetarget === 0) { + colorPreviousAdjustments = '#ffffff66'; + } + } else { + colorPreviousAdjustments = '#ffffff66'; + } + + return { + base: `${progress}%`, + change, + progress, + remainingBlocks, + timeAvg, + colorAdjustments, + colorPreviousAdjustments, + blocksInEpoch, + newDifficultyHeight, + remainingTime, + previousRetarget, + }; + }) + ); + } +} diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index 25cacb428..b3cbd5fa1 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -11,7 +11,7 @@
- +
@@ -38,7 +38,7 @@
- +
@@ -228,84 +228,3 @@ - - -
Difficulty Adjustment
-
-
-
-
-
-
Remaining
-
- - {{ i }} blocks - {{ i }} block -
-
-
-
-
Estimate
-
- - - - - - - {{ epochData.change | absolute | number: '1.2-2' }} - % -
- -
-
-
- Previous: - - - - - - - - {{ epochData.previousRetarget | absolute | number: '1.2-2' }} % -
-
-
-
Current Period
-
{{ epochData.progress | number: '1.2-2' }} %
-
-
 
-
-
-
-
-
-
-
- - -
-
-
Remaining
-
-
-
-
-
-
-
Estimate
-
-
-
-
-
-
-
Current Period
-
-
-
-
-
-
-
diff --git a/frontend/src/app/dashboard/dashboard.component.scss b/frontend/src/app/dashboard/dashboard.component.scss index 541bd2129..39ca2101a 100644 --- a/frontend/src/app/dashboard/dashboard.component.scss +++ b/frontend/src/app/dashboard/dashboard.component.scss @@ -243,84 +243,6 @@ max-width: 120px; } -.difficulty-adjustment-container { - display: flex; - flex-direction: row; - justify-content: space-around; - height: 76px; - .shared-block { - color: #ffffff66; - font-size: 12px; - } - .item { - padding: 0 5px; - width: 100%; - &:nth-child(1) { - display: none; - @media (min-width: 485px) { - display: table-cell; - } - @media (min-width: 768px) { - display: none; - } - @media (min-width: 992px) { - display: table-cell; - } - } - } - .card-text { - font-size: 22px; - margin-top: -9px; - position: relative; - } -} - - -.difficulty-skeleton { - display: flex; - justify-content: space-between; - @media (min-width: 376px) { - flex-direction: row; - } - .item { - max-width: 150px; - margin: 0; - width: -webkit-fill-available; - @media (min-width: 376px) { - margin: 0 auto 0px; - } - &:first-child{ - display: none; - @media (min-width: 485px) { - display: block; - } - @media (min-width: 768px) { - display: none; - } - @media (min-width: 992px) { - display: block; - } - } - &:last-child { - margin-bottom: 0; - } - } - .card-text { - .skeleton-loader { - width: 100%; - display: block; - &:first-child { - margin: 14px auto 0; - max-width: 80px; - } - &:last-child { - margin: 10px auto 0; - max-width: 120px; - } - } - } -} - .loading-container { min-height: 76px; } diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index 3dbec5ce3..a582baba3 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -15,19 +15,6 @@ interface MempoolBlocksData { size: number; } -interface EpochProgress { - base: string; - change: number; - progress: string; - remainingBlocks: number; - newDifficultyHeight: number; - colorAdjustments: string; - colorPreviousAdjustments: string; - timeAvg: string; - remainingTime: number; - previousRetarget: number; -} - interface MempoolInfoData { memPoolInfo: MempoolInfo; vBytesPerSecond: number; @@ -51,7 +38,6 @@ export class DashboardComponent implements OnInit { network$: Observable; mempoolBlocksData$: Observable; mempoolInfoData$: Observable; - difficultyEpoch$: Observable; mempoolLoadingStatus$: Observable; vBytesPerSecondLimit = 1667; blocks$: Observable; @@ -126,82 +112,6 @@ export class DashboardComponent implements OnInit { }) ); - this.difficultyEpoch$ = timer(0, 1000) - .pipe( - switchMap(() => combineLatest([ - this.stateService.blocks$.pipe(map(([block]) => block)), - this.stateService.lastDifficultyAdjustment$, - this.stateService.previousRetarget$ - ])), - map(([block, DATime, previousRetarget]) => { - const now = new Date().getTime() / 1000; - const diff = now - DATime; - const blocksInEpoch = block.height % 2016; - const progress = (blocksInEpoch >= 0) ? (blocksInEpoch / 2016 * 100).toFixed(2) : `100`; - const remainingBlocks = 2016 - blocksInEpoch; - const newDifficultyHeight = block.height + remainingBlocks; - - let change = 0; - if (remainingBlocks < 1870) { - if (blocksInEpoch > 0) { - change = (600 / (diff / blocksInEpoch ) - 1) * 100; - } - if (change > 300) { - change = 300; - } - if (change < -75) { - change = -75; - } - } - - const timeAvgDiff = change * 0.1; - - let timeAvgMins = 10; - if (timeAvgDiff > 0) { - timeAvgMins -= Math.abs(timeAvgDiff); - } else { - timeAvgMins += Math.abs(timeAvgDiff); - } - - const timeAvg = timeAvgMins.toFixed(0); - const remainingTime = (remainingBlocks * timeAvgMins * 60 * 1000) + (now * 1000); - - let colorAdjustments = '#ffffff66'; - if (change > 0) { - colorAdjustments = '#3bcc49'; - } - if (change < 0) { - colorAdjustments = '#dc3545'; - } - - let colorPreviousAdjustments = '#dc3545'; - if (previousRetarget) { - if (previousRetarget >= 0) { - colorPreviousAdjustments = '#3bcc49'; - } - if (previousRetarget === 0) { - colorPreviousAdjustments = '#ffffff66'; - } - } else { - colorPreviousAdjustments = '#ffffff66'; - } - - return { - base: `${progress}%`, - change, - progress, - remainingBlocks, - timeAvg, - colorAdjustments, - colorPreviousAdjustments, - blocksInEpoch, - newDifficultyHeight, - remainingTime, - previousRetarget, - }; - }) - ); - this.mempoolBlocksData$ = this.stateService.mempoolBlocks$ .pipe( map((mempoolBlocks) => {