Merge branch 'master' into simon/angular-universal

* master:
  Update list of supported locales
  Add one more fix to nginx.conf for i18n
  Remove unused i18n tags in frontend/src/index.html
  Update translations from Transifex
  Enable 'fr' locale for French
  Corrected some missing spaces on transactions page and a blank render bug when confirmation time is below 1 minute.
  Implement i18n support in frontend using Angular + Transifex + NGINX

# Conflicts:
#	frontend/src/app/app.constants.ts
#	frontend/sync-assets.js
This commit is contained in:
softsimon 2020-12-02 20:05:49 +07:00
commit 2bba51e6c1
No known key found for this signature in database
GPG key ID: 488D7DCFB5A430D7
60 changed files with 41992 additions and 451 deletions

7
frontend/.tx/config Normal file
View file

@ -0,0 +1,7 @@
[main]
host = https://www.transifex.com
[mempool.frontend-src-locale-messages-xlf--wiz-i18n]
file_filter = frontend/src/locale/messages.<lang>.xlf
source_lang = en-US
type = XLIFF

View file

@ -13,6 +13,70 @@
"root": "",
"sourceRoot": "src",
"prefix": "app",
"i18n": {
"sourceLocale": {
"code":"en-US",
"baseHref":"/"
},
"locales": {
"cs": {
"translation": "src/locale/messages.cs.xlf",
"baseHref": "/cs/"
},
"de": {
"translation": "src/locale/messages.de.xlf",
"baseHref": "/de/"
},
"es": {
"translation": "src/locale/messages.es.xlf",
"baseHref": "/es/"
},
"fa": {
"translation": "src/locale/messages.fa.xlf",
"baseHref": "/fa/"
},
"fr": {
"translation": "src/locale/messages.fr.xlf",
"baseHref": "/fr/"
},
"ja": {
"translation": "src/locale/messages.ja.xlf",
"baseHref": "/ja/"
},
"nl": {
"translation": "src/locale/messages.nl.xlf",
"baseHref": "/nl/"
},
"nn": {
"translation": "src/locale/messages.nn.xlf",
"baseHref": "/nn/"
},
"pt": {
"translation": "src/locale/messages.pt.xlf",
"baseHref": "/pt/"
},
"sl": {
"translation": "src/locale/messages.sl.xlf",
"baseHref": "/sl/"
},
"sv": {
"translation": "src/locale/messages.sv.xlf",
"baseHref": "/sv/"
},
"tr": {
"translation": "src/locale/messages.tr.xlf",
"baseHref": "/tr/"
},
"uk": {
"translation": "src/locale/messages.uk.xlf",
"baseHref": "/uk/"
},
"zh": {
"translation": "src/locale/messages.zh.xlf",
"baseHref": "/zh/"
}
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
@ -176,4 +240,4 @@
}
}},
"defaultProject": "mempool"
}
}

1
frontend/frontend Symbolic link
View file

@ -0,0 +1 @@
.

View file

@ -22,9 +22,11 @@
"scripts": {
"ng": "./node_modules/@angular/cli/bin/ng",
"tsc": "./node_modules/typescript/bin/tsc",
"i18n-extract-from-source": "./node_modules/@angular/cli/bin/ng xi18n --ivy --out-file ./src/locale/messages.xlf",
"i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1",
"serve": "ng serve --proxy-config proxy.conf.json",
"start": "npm run generate-config && npm run sync-assets-dev && ng serve --proxy-config proxy.conf.json",
"build": "npm run generate-config && ng build --prod && npm run sync-assets",
"build": "npm run generate-config && ng build --prod --localize && npm run sync-assets",
"sync-assets": "node sync-assets.js",
"sync-assets-dev": "node sync-assets.js dev",
"generate-config": "node generate-config.js",

View file

@ -33,3 +33,83 @@ export const mempoolFeeColors = [
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
interface Env {
TESTNET_ENABLED: boolean;
LIQUID_ENABLED: boolean;
BISQ_ENABLED: boolean;
BISQ_SEPARATE_BACKEND: boolean;
SPONSORS_ENABLED: boolean;
ELCTRS_ITEMS_PER_PAGE: number;
KEEP_BLOCKS_AMOUNT: number;
}
const defaultEnv: Env = {
'TESTNET_ENABLED': false,
'LIQUID_ENABLED': false,
'BISQ_ENABLED': false,
'BISQ_SEPARATE_BACKEND': false,
'SPONSORS_ENABLED': false,
'ELCTRS_ITEMS_PER_PAGE': 25,
'KEEP_BLOCKS_AMOUNT': 8
};
const browserWindow = window || {};
// @ts-ignore
const browserWindowEnv = browserWindow.__env || {};
export const env: Env = Object.assign(defaultEnv, browserWindowEnv);
export interface Language {
code: string;
name: string;
}
export const languages: Language[] = [
// { code: 'ar', name: 'العربية' }, // Arabic
// { code: 'bg', name: 'Български' }, // Bulgarian
// { code: 'bs', name: 'Bosanski' }, // Bosnian
// { code: 'ca', name: 'Català' }, // Catalan
{ code: 'cs', name: 'Čeština' }, // Czech
// { code: 'da', name: 'Dansk' }, // Danish
{ code: 'de', name: 'Deutsch' }, // German
// { code: 'et', name: 'Eesti' }, // Estonian
// { code: 'el', name: 'Ελληνικά' }, // Greek
{ code: 'en', name: 'English' }, // English
{ code: 'es', name: 'Español' }, // Spanish
// { code: 'eo', name: 'Esperanto' }, // Esperanto
// { code: 'eu', name: 'Euskara' }, // Basque
{ code: 'fa', name: 'فارسی' }, // Persian
{ code: 'fr', name: 'Français' }, // French
// { code: 'gl', name: 'Galego' }, // Galician
// { code: 'ko', name: '한국어' }, // Korean
// { code: 'hr', name: 'Hrvatski' }, // Croatian
// { code: 'id', name: 'Bahasa Indonesia' },// Indonesian
// { code: 'it', name: 'Italiano' }, // Italian
// { code: 'he', name: 'עברית' }, // Hebrew
// { code: 'ka', name: 'ქართული' }, // Georgian
// { code: 'lv', name: 'Latviešu' }, // Latvian
// { code: 'lt', name: 'Lietuvių' }, // Lithuanian
// { code: 'hu', name: 'Magyar' }, // Hungarian
// { code: 'mk', name: 'Македонски' }, // Macedonian
// { code: 'ms', name: 'Bahasa Melayu' }, // Malay
{ code: 'nl', name: 'Nederlands' }, // Dutch
{ code: 'ja', name: '日本語' }, // Japanese
// { code: 'nb', name: 'Norsk bokmål' }, // Norwegian Bokmål
{ code: 'nn', name: 'Norsk' }, // Norwegian Nynorsk
// { code: 'pl', name: 'Polski' }, // Polish
{ code: 'pt', name: 'Português' }, // Portuguese
// { code: 'pt-BR', name: 'Português (Brazil)' }, // Portuguese (Brazil)
// { code: 'ro', name: 'Română' }, // Romanian
// { code: 'ru', name: 'Русский' }, // Russian
// { code: 'sk', name: 'Slovenčina' }, // Slovak
{ code: 'sl', name: 'Slovenščina' }, // Slovenian
// { code: 'sr', name: 'Српски / srpski' }, // Serbian
// { code: 'sh', name: 'Srpskohrvatski / српскохрватски' },// Serbo-Croatian
// { code: 'fi', name: 'Suomi' }, // Finnish
{ code: 'sv', name: 'Svenska' }, // Swedish
// { code: 'th', name: 'ไทย' }, // Thai
{ code: 'tr', name: 'Türkçe' }, // Turkish
{ code: 'uk', name: 'Українська' }, // Ukrainian
// { code: 'vi', name: 'Tiếng Việt' }, // Vietnamese
{ code: 'zh', name: '中文' }, // Chinese
];

View file

@ -47,6 +47,7 @@ import { faAngleDoubleDown, faAngleDoubleUp, faAngleDown, faAngleUp, faBolt, faC
faLink, faList, faSearch, faTachometerAlt, faThList, faTint, faTv } from '@fortawesome/free-solid-svg-icons';
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { TranslationStringsComponent } from './components/translation-strings/translation-strings.component';
import { StorageService } from './services/storage.service';
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
@ -83,6 +84,7 @@ import { HttpCacheInterceptor } from './services/http-cache.interceptor';
DashboardComponent,
ApiDocsComponent,
TermsOfServiceComponent,
TranslationStringsComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),

View file

@ -10,16 +10,16 @@
<br>
<h2>About the project</h2>
<h2 i18n="about.about-the-project">About the project</h2>
<div class="row row-cols-1">
<div class="col col-md-6 offset-md-3">
<p>The mempool open-source project aims to implement a high quality explorer and visualization website for the entire Bitcoin ecosystem, without distractions like altcoins, advertising, or third-party trackers.</p>
<p i18n>The mempool open-source project aims to implement a high quality explorer and visualization website for the entire Bitcoin ecosystem, without distractions like altcoins, advertising, or third-party trackers.</p>
</div>
</div>
<br>
<h2>Maintainers</h2>
<h2 i18n="about.maintainers">Maintainers</h2>
<div class="container text-center">
<div class="row row-cols-2">
@ -29,7 +29,7 @@
@softsimon_
</a>
<br>
Development
<span i18n="about.development">Development</span>
</div>
<div class="col col-md-2">
<a href="https://twitter.com/wiz">
@ -37,14 +37,14 @@
@wiz
</a>
<br>
Operations
<span i18n="about.operations">Operations</span>
</div>
</div>
</div>
<br><br>
<h2>Sponsors ❤️</h2>
<h2 i18n="about.sponsors.withHeart">Sponsors ❤️</h2>
<div *ngIf="sponsors === null">
<br>
@ -60,9 +60,9 @@
</ng-template>
<br><br>
<button type="button" class="btn btn-primary" (click)="donationStatus = 2" [hidden]="donationStatus !== 1">Become a sponsor ❤️</button>
<button type="button" class="btn btn-primary" (click)="donationStatus = 2" [hidden]="donationStatus !== 1" i18n="about.become-a-sponsor">Become a sponsor ❤️</button>
<p *ngIf="donationStatus === 2 && !sponsorsEnabled">
Navigate to <a href="https://mempool.space/about" target="_blank">https://mempool.space/about</a> to sponsor
<span i18n="about.navigate-to">Navigate to</span> <a href="https://mempool.space/about" target="_blank">https://mempool.space/about</a> <span i18n="about.to-sponsor">to sponsor</span>
</p>
<div style="max-width: 300px;" class="mx-auto" [hidden]="donationStatus !== 2 || !sponsorsEnabled">
@ -79,17 +79,17 @@
</div>
<input formControlName="handle" class="form-control" type="text" placeholder="Twitter handle (Optional)">
</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('required')">Amount required</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('min')">Minimum amount is 0.001 BTC</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('required')" i18n="about.sponsor.amount-required">Amount required</div>
<div class="required" *ngIf="donationForm.get('amount').hasError('min')" i18n="about.sponsor.minimum-amount">Minimum amount is 0.001 BTC</div>
<div class="input-group mt-4">
<button class="btn btn-primary mx-auto" type="submit" [disabled]="donationForm.invalid">Request invoice</button>
<button class="btn btn-primary mx-auto" type="submit" [disabled]="donationForm.invalid" i18n="about.sponsor.request-invoice">Request invoice</button>
</div>
</form>
</div>
<ng-template #lowAmount>
<div class="input-group mb-4 text-small">
If you donate 0.01 BTC or more, your profile photo will be added to the list of sponsors above :)
<span i18n="about.sponsor.description">If you donate 0.01 BTC or more, your profile photo will be added to the list of sponsors above :)</span>
</div>
</ng-template>
@ -167,13 +167,13 @@
<p style="font-size: 12px;">{{ donationObj.amount }} BTC</p>
</ng-template>
<p>Waiting for transaction... </p>
<p i18n="about.sponsor.waiting-for-transaction">Waiting for transaction... </p>
<div class="spinner-border text-light"></div>
</div>
<div *ngIf="donationStatus === 4" class="text-center">
<h2>Donation confirmed!<br>Thank you!</h2>
<p>If you specified a Twitter handle, the profile photo should now be visible on this page when you reload.</p>
<h2><span i18n="about.sponsor.donation-confirmed">Donation confirmed!</span><br><span i18n="about.sponsor.thank-you">Thank you!</span></h2>
<p i18n="about.sponsor.sponsor-completed">If you specified a Twitter handle, the profile photo should now be visible on this page when you reload.</p>
</div>
<br><br><br><br>
@ -203,7 +203,7 @@
<br><br>
<div class="text-center">
<a [routerLink]="['/terms-of-service']">Terms of Service</a>
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
</div>
</div>

View file

@ -1,2 +1,2 @@
<span *ngIf="multisig" class="badge badge-pill badge-warning">multisig {{ multisigM }} of {{ multisigN }}</span>
<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning">Layer{{ network === 'liquid' ? '3' : '2' }} Peg-out</span>
<span *ngIf="multisig" class="badge badge-pill badge-warning" i18n="address-labels.multisig">multisig {{ multisigM }} of {{ multisigN }}</span>
<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning" i18n="address-labels.upper-layer-peg-out">Layer{{ network === 'liquid' ? '3' : '2' }} Peg-out</span>

View file

@ -1,5 +1,5 @@
<div class="container-xl">
<h1 style="float: left;">Address</h1>
<h1 style="float: left;" i18n="shared.address">Address</h1>
<a [routerLink]="['/address/' | relativeUrl, addressString]" style="line-height: 56px; margin-left: 10px;">
<span class="d-inline d-lg-none">{{ addressString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ addressString }}</span>
@ -17,15 +17,15 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Total received</td>
<td i18n="address.total-received">Total received</td>
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="receieved" [noFiat]="true"></app-amount></td>
</tr>
<tr>
<td>Total sent</td>
<td i18n="address.total-sent">Total sent</td>
<td *ngIf="address.chain_stats.spent_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="sent" [noFiat]="true"></app-amount></td>
</tr>
<tr>
<td>Balance</td>
<td i18n="address.balance">Balance</td>
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="receieved - sent" [noFiat]="true"></app-amount> (<app-fiat [value]="receieved - sent"></app-fiat>)</td>
</tr>
</tbody>
@ -43,7 +43,7 @@
<br>
<h2><ng-template [ngIf]="transactions?.length">{{ (transactions?.length | number) || '?' }} of </ng-template>{{ txCount | number }} transactions</h2>
<h2><ng-template [ngIf]="transactions?.length">{{ (transactions?.length | number) || '?' }} <span i18n="shared.of">of</span>&nbsp;</ng-template>{{ txCount | number }} <span i18n="shared.transactions">transactions</span></h2>
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
@ -98,7 +98,7 @@
<ng-template [ngIf]="error">
<div class="text-center">
Error loading address data.
<span i18n="address.error.loading-address-data">Error loading address data.</span>
<br>
<i>{{ error.error }}</i>
</div>
@ -109,5 +109,5 @@
<br>
<ng-template #confidentialTd>
<td>Confidential</td>
<td i18n="shared.confidential">Confidential</td>
</ng-template>

View file

@ -3,7 +3,7 @@
</ng-container>
<ng-template #viewFiatVin>
<ng-template [ngIf]="network === 'liquid' && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
Confidential
<span i18n="shared.confidential">Confidential</span>
</ng-template>
<ng-template #default>
{{ satoshis / 100000000 | number : digitsInfo }}

View file

@ -6,17 +6,17 @@
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="0">
<a ngbNavLink>Websocket</a>
<a ngbNavLink i18n="api-docs.tab.websocket|API Docs tab for Websocket">Websocket</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap">wss://{{ hostname }}{{ network.val === '' ? '' : '/' + network.val }}/api/v1/ws</td>
<td>Default push: <code>{{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }}</code> to express what you want pushed. Available: <code>blocks</code>, <code>mempool-block</code>, <code>live-2h-chart</code>, and <code>stats</code>.<br><br>Push transactions related to address: <code>{{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }}</code> to receive all new transactions containing that address as input or output. Returns an array of transactions. <code>address-transactions</code> for new mempool transactions, and <code>block-transactions</code> for new block confirmed transactions.</td>
<td i18n="api-docs.websocket.websocket">Default push: <code>{{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }}</code> to express what you want pushed. Available: <code>blocks</code>, <code>mempool-block</code>, <code>live-2h-chart</code>, and <code>stats</code>.<br><br>Push transactions related to address: <code>{{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }}</code> to receive all new transactions containing that address as input or output. Returns an array of transactions. <code>address-transactions</code> for new mempool transactions, and <code>block-transactions</code> for new block confirmed transactions.</td>
</tr>
</table>
@ -24,21 +24,21 @@
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="1">
<a ngbNavLink>Fee Estimates</a>
<a ngbNavLink i18n="api-docs.tab.fees|API Docs tab for Fees">Fees</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/v1/fees/recommended" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/v1/fees/recommended</a></td>
<td>Returns our currently suggested fees for new transactions.</td>
<td i18n="api-docs.fees.recommended|API Docs for /api/v1/fees/recommended">Returns our currently suggested fees for new transactions.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/v1/fees/mempool-blocks" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/v1/fees/mempool-blocks</a></td>
<td>Returns current mempool as projected blocks.</td>
<td i18n="api-docs.fees.mempool-blocks|API Docs for /api/v1/fees/mempool-blocks">Returns current mempool as projected blocks.</td>
</tr>
</table>
@ -46,25 +46,25 @@
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="2">
<a ngbNavLink>Mempool</a>
<a ngbNavLink i18n="api-docs.tab.mempool|API Docs tab for Mempool">Mempool</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/mempool" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/mempool</a></td>
<td>Returns current mempool backlog statistics.</td>
<td i18n="api-docs.mempool.mempool|API Docs for /api/mempool">Returns current mempool backlog statistics.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/mempool/txids" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/mempool/txids</a></td>
<td>Get the full list of txids in the mempool as an array. The order of the txids is arbitrary and does not match bitcoind.</td>
<td i18n="api-docs.mempool.txids|API Docs for /api/mempool/txids">Get the full list of txids in the mempool as an array. The order of the txids is arbitrary and does not match bitcoind.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/mempool/recent" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/mempool/recent</a></td>
<td>Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: <code>txid</code>, <code>fee</code>, <code>vsize</code>, and <code>value</code>.</td>
<td i18n="api-docs.mempool.recent|API Docs for /api/mempool/recent">Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: <code>txid</code>, <code>fee</code>, <code>vsize</code>, and <code>value</code>.</td>
</tr>
</table>
@ -72,53 +72,53 @@
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="3">
<a ngbNavLink>Blocks</a>
<a ngbNavLink i18n="api-docs.tab.blocks|API Docs tab for Blocks">Blocks</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/block/000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/block/:hash</a></td>
<td>Returns details about a block. Available fields: <code>id</code>, <code>height</code>, <code>version</code>, <code>timestamp</code>, <code>bits</code>, <code>nonce</code>, <code>merkle_root</code>, <code>tx_count</code>, <code>size</code>, <code>weight</code>,<ng-container *ngIf="network.val === 'liquid'"> <code>proof</code>,</ng-container> and <code>previousblockhash</code>.</td>
<td i18n>Returns details about a block. Available fields: <code>id</code>, <code>height</code>, <code>version</code>, <code>timestamp</code>, <code>bits</code>, <code>nonce</code>, <code>merkle_root</code>, <code>tx_count</code>, <code>size</code>, <code>weight</code>,<ng-container *ngIf="network.val === 'liquid'"> <code>proof</code>,</ng-container> and <code>previousblockhash</code>.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/block/000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce/status" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/block/:hash/status</a></td>
<td>Returns the confirmation status of a block. Available fields: <code>in_best_chain</code> (boolean, false for orphaned blocks), <code>next_best</code> (the hash of the next block, only available for blocks in the best chain).</td>
<td i18n>Returns the confirmation status of a block. Available fields: <code>in_best_chain</code> (boolean, false for orphaned blocks), <code>next_best</code> (the hash of the next block, only available for blocks in the best chain).</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/block/000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce/txs" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/block/:hash/txs[/:start_index]</a></td>
<td>Returns a list of transactions in the block (up to 25 transactions beginning at <code>start_index</code>). Transactions returned here do not have the <code>status</code> field, since all the transactions share the same block and confirmation status.</td>
<td i18n>Returns a list of transactions in the block (up to 25 transactions beginning at <code>start_index</code>). Transactions returned here do not have the <code>status</code> field, since all the transactions share the same block and confirmation status.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/block/000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce/txids" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/block/:hash/txids</a></td>
<td>Returns a list of all txids in the block.</td>
<td i18n>Returns a list of all txids in the block.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/block/000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce/txid/218" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/block/:hash/txid/:index</a></td>
<td>Returns the transaction at index <code>:index</code> within the specified block.</td>
<td i18n>Returns the transaction at index <code>:index</code> within the specified block.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/block/000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce/raw" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/block/:hash/txid/raw</a></td>
<td>Returns the raw block representation in binary.</td>
<td i18n>Returns the raw block representation in binary.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/block-height/0" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/block-height/:height</a></td>
<td>Returns the hash of the block currently at <code>:height</code>.</td>
<td i18n>Returns the hash of the block currently at <code>:height</code>.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/blocks" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/blocks[/:start_height]</a></td>
<td>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</td>
<td i18n>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/blocks/tip/height" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/blocks/tip/height</a></td>
<td>Returns the height of the last block.</td>
<td i18n>Returns the height of the last block.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/blocks/tip/hash" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/blocks/tip/hash</a></td>
<td>Returns the hash of the last block.</td>
<td i18n>Returns the hash of the last block.</td>
</tr>
</table>
@ -126,49 +126,49 @@
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="4">
<a ngbNavLink>Transactions</a>
<a ngbNavLink i18n="api-docs.tab.transactions|API Docs tab for Transactions">Transactions</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid</a></td>
<td>Returns details about a transaction. Available fields: <code>txid</code>, <code>version</code>, <code>locktime</code>, <code>size</code>, <code>weight</code>, <code>fee</code>, <code>vin</code>, <code>vout</code>, and <code>status</code>.</td>
<td i18n>Returns details about a transaction. Available fields: <code>txid</code>, <code>version</code>, <code>locktime</code>, <code>size</code>, <code>weight</code>, <code>fee</code>, <code>vin</code>, <code>vout</code>, and <code>status</code>.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521/status" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid/status</a></td>
<td>Returns the confirmation status of a transaction. Available fields: <code>confirmed</code> (boolean), <code>block_height</code> (optional), and <code>block_hash</code> (optional).</td>
<td i18n>Returns the confirmation status of a transaction. Available fields: <code>confirmed</code> (boolean), <code>block_height</code> (optional), and <code>block_hash</code> (optional).</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521/hex" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid/hex</a></td>
<td>Returns a transaction serialized as hex.</td>
<td i18n>Returns a transaction serialized as hex.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521/raw" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid/raw</a></td>
<td>Returns a transaction as binary data.</td>
<td i18n>Returns a transaction as binary data.</td>
</tr>
<tr *ngIf="network.val !== 'liquid'">
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521/merkleblock-proof" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid/merkleblock-proof</a></td>
<td>Returns a merkle inclusion proof for the transaction using <a href="https://bitcoin.org/en/glossary/merkle-block">bitcoind's merkleblock</a> format.</td>
<td i18n>Returns a merkle inclusion proof for the transaction using <a href="https://bitcoin.org/en/glossary/merkle-block">bitcoind's merkleblock</a> format.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521/merkle-proof" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid/merkle-proof</a></td>
<td>Returns a merkle inclusion proof for the transaction using <a href="https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle">Electrum's blockchain.transaction.get_merkle format.</a></td>
<td i18n>Returns a merkle inclusion proof for the transaction using <a href="https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle">Electrum's blockchain.transaction.get_merkle format.</a></td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521/outspend/3" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid/outspend/:vout</a></td>
<td>Returns the spending status of a transaction output. Available fields: <code>spent</code> (boolean), <code>txid</code> (optional), <code>vin</code> (optional), and <code>status</code> (optional, the status of the spending tx).</td>
<td i18n>Returns the spending status of a transaction output. Available fields: <code>spent</code> (boolean), <code>txid</code> (optional), <code>vin</code> (optional), and <code>status</code> (optional, the status of the spending tx).</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/tx/15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521/outspends" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/tx/:txid/outspends</a></td>
<td>Returns the spending status of all transaction outputs.</td>
<td i18n>Returns the spending status of all transaction outputs.</td>
</tr>
<tr>
<td class="nowrap">POST {{ network.val === '' ? '' : '/' + network.val }}/api/tx</td>
<td>Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The <code>txid</code> will be returned on success.</td>
<td i18n>Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The <code>txid</code> will be returned on success.</td>
</tr>
</table>
@ -176,33 +176,33 @@
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="5">
<a ngbNavLink>Addresses</a>
<a ngbNavLink i18n="api-docs.tab.addresses|API Docs tab for Addresses">Addresses</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/address/:address</a></td>
<td>Returns details about an address. Available fields: <code>address</code>, <code>chain_stats</code>, and <code>mempool_stats</code>. {{ '{' }}chain,mempool{{ '}' }}_stats each contain an object with <code>tx_count</code>, <code>funded_txo_count</code>, <code>funded_txo_sum</code>, <code>spent_txo_count</code>, and <code>spent_txo_sum</code>.</td>
<td i18n>Returns details about an address. Available fields: <code>address</code>, <code>chain_stats</code>, and <code>mempool_stats</code>. {{ '{' }}chain,mempool{{ '}' }}_stats each contain an object with <code>tx_count</code>, <code>funded_txo_count</code>, <code>funded_txo_sum</code>, <code>spent_txo_count</code>, and <code>spent_txo_sum</code>.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC/txs" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/address/:address/txs</a></td>
<td>Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using <code>:last_seen_txid</code> (see below).
<td i18n>Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using <code>:last_seen_txid</code> (see below).
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC/txs/chain" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/address/:address/txs/chain</a></td>
<td>Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query.</td>
<td i18n>Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC/txs/mempool" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/address/:address/txs/mempool</a></td>
<td>Get unconfirmed transaction history for the specified address/scripthash. Returns up to 50 transactions (no paging).</td>
<td i18n>Get unconfirmed transaction history for the specified address/scripthash. Returns up to 50 transactions (no paging).</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC/utxo" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/address/:address/utxo</a></td>
<td>Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: <code>txid</code>, <code>vout</code>, <code>value</code>, and <code>status</code> (with the status of the funding tx).<ng-container *ngIf="network.val === 'liquid'">There is also a <code>valuecommitment</code> field that may appear in place of <code>value</code>, plus the following additional fields: <code>asset</code>/<code>assetcommitment</code>, <code>nonce</code>/<code>noncecommitment</code>, <code>surjection_proof</code>, and <code>range_proof</code>.</ng-container></td>
<td i18n>Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: <code>txid</code>, <code>vout</code>, <code>value</code>, and <code>status</code> (with the status of the funding tx).<ng-container *ngIf="network.val === 'liquid'">There is also a <code>valuecommitment</code> field that may appear in place of <code>value</code>, plus the following additional fields: <code>asset</code>/<code>assetcommitment</code>, <code>nonce</code>/<code>noncecommitment</code>, <code>surjection_proof</code>, and <code>range_proof</code>.</ng-container></td>
</tr>
</table>
@ -210,66 +210,66 @@
</li>
<li *ngIf="network.val === 'liquid'" [ngbNavItem]="6">
<a ngbNavLink>Assets</a>
<a ngbNavLink i18n="api-docs.tab.assets|API Docs tab for Assets">Assets</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap"><a href="/liquid/api/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" target="_blank">GET /liquid/api/asset/:asset_id</a></td>
<td>Returns information about a Liquid asset.</td>
<td i18n>Returns information about a Liquid asset.</td>
</tr>
<tr>
<td class="nowrap"><a href="/liquid/api/asset/4b5417ec5ab6112bedf539c3b4f5a806ed539542d8b717e1c4470aa3180edce5/txs" target="_blank">GET /liquid/api/asset/:asset_id/txs[/mempool|/chain]</a></td>
<td>Returns transactions associated with the specified Liquid asset. For the network's native asset, returns a list of peg in, peg out, and burn transactions. For user-issued assets, returns a list of issuance, reissuance, and burn transactions. Does not include regular transactions transferring this asset.</td>
<td i18n>Returns transactions associated with the specified Liquid asset. For the network's native asset, returns a list of peg in, peg out, and burn transactions. For user-issued assets, returns a list of issuance, reissuance, and burn transactions. Does not include regular transactions transferring this asset.</td>
</tr>
<tr>
<td class="nowrap"><a href="/liquid/api/asset/4b5417ec5ab6112bedf539c3b4f5a806ed539542d8b717e1c4470aa3180edce5/supply" target="_blank">GET /liquid/api/asset/:asset_id/supply[/decimal]</a></td>
<td>Get the current total supply of the specified asset. For the native asset (L-BTC), this is calculated as [chain,mempool]_stats.peg_in_amount - [chain,mempool]_stats.peg_out_amount - [chain,mempool]_stats.burned_amount. For issued assets, this is calculated as [chain,mempool]_stats.issued_amount - [chain,mempool]_stats.burned_amount. Not available for assets with blinded issuances. If /decimal is specified, returns the supply as a decimal according to the asset's divisibility. Otherwise, returned in base units.</td>
<td i18n>Get the current total supply of the specified asset. For the native asset (L-BTC), this is calculated as [chain,mempool]_stats.peg_in_amount - [chain,mempool]_stats.peg_out_amount - [chain,mempool]_stats.burned_amount. For issued assets, this is calculated as [chain,mempool]_stats.issued_amount - [chain,mempool]_stats.burned_amount. Not available for assets with blinded issuances. If /decimal is specified, returns the supply as a decimal according to the asset's divisibility. Otherwise, returned in base units.</td>
</tr>
</table>
</ng-template>
</li>
<li *ngIf="network.val === 'bisq'" [ngbNavItem]="1">
<a ngbNavLink>BSQ</a>
<a ngbNavLink i18n="api-docs.tab.bsq|API Docs tab for BSQ">BSQ</a>
<ng-template ngbNavContent>
<table class="table">
<tr>
<th style="border-top: 0;">Endpoint</th>
<th style="border-top: 0;">Description</th>
<th style="border-top: 0;" i18n="api-docs.shared.endpoint|API Docs Endpoint">Endpoint</th>
<th style="border-top: 0;" i18n="api-docs.shared.description|API Docs Description">Description</th>
</tr>
<tr>
<td class="nowrap"><a href="/bisq/api/stats" target="_blank">GET /bisq/api/stats</a></td>
<td>Returns statistics about all Bisq transactions.</td>
<td i18n>Returns statistics about all Bisq transactions.</td>
</tr>
<tr>
<td class="nowrap"><a href="/bisq/api/tx/4b5417ec5ab6112bedf539c3b4f5a806ed539542d8b717e1c4470aa3180edce5" target="_blank">GET /bisq/api/tx/:txid</a></td>
<td>Returns details about a Bisq transaction.</td>
<td i18n>Returns details about a Bisq transaction.</td>
</tr>
<tr>
<td class="nowrap"><a href="/bisq/api/txs/0/25" target="_blank">GET /bisq/api/txs/:index/:length</a></td>
<td>Returns :length of latest Bisq transactions, starting from :index.</td>
<td i18n>Returns :length of latest Bisq transactions, starting from :index.</td>
</tr>
<tr>
<td class="nowrap"><a href="/bisq/api/block/000000000000000000079aa6bfa46eb8fc20474e8673d6e8a123b211236bf82d" target="_blank">GET /bisq/api/block/:hash</a></td>
<td>Returns all Bisq transactions that exist in a Bitcoin block.</td>
<td i18n>Returns all Bisq transactions that exist in a Bitcoin block.</td>
</tr>
<tr>
<td class="nowrap"><a href="/bisq/api/blocks/0/25" target="_blank">GET /bisq/api/blocks/:index/:length</a></td>
<td>Returns :length Bitcoin blocks that contain Bisq transactions, starting from :index.</td>
<td i18n>Returns :length Bitcoin blocks that contain Bisq transactions, starting from :index.</td>
</tr>
<tr>
<td class="nowrap"><a href="/bisq/api/blocks/tip/height" target="_blank">GET /bisq/api/blocks/tip/height</a></td>
<td>Returns the most recently processed Bitcoin block height processed by Bisq.</td>
<td i18n>Returns the most recently processed Bitcoin block height processed by Bisq.</td>
</tr>
<tr>
<td class="nowrap"><a href="/bisq/api/address/B1DgwRN92rdQ9xpEVCdXRfgeqGw9X4YtrZz" target="_blank">GET /bisq/api/address/:address</a></td>
<td>Returns all Bisq transactions belonging to a Bitcoin address, with 'B' prefixed in front of the address.</td>
<td i18n>Returns all Bisq transactions belonging to a Bitcoin address, with 'B' prefixed in front of the address.</td>
</tr>
</table>
</ng-template>
@ -281,7 +281,7 @@
<br>
<div class="text-center">
<a [routerLink]="['/terms-of-service']">Terms of Service</a>
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
</div>
</div>

View file

@ -17,19 +17,19 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Name</td>
<td i18n="asset.name|Liquid Asset name">Name</td>
<td>{{ assetContract[2] }} ({{ assetContract[1] }})</td>
</tr>
<tr>
<td>Precision</td>
<td i18n="asset.precision|Liquid Asset precision">Precision</td>
<td>{{ assetContract[3] }}</td>
</tr>
<tr *ngIf="!isNativeAsset && assetContract[0]">
<td>Issuer</td>
<td i18n="asset.issuer|Liquid Asset issuer">Issuer</td>
<td><a target="_blank" href="{{ 'http://' + assetContract[0] }}">{{ assetContract[0] }}</a></td>
</tr>
<tr *ngIf="!isNativeAsset">
<td>Issuance tx</td>
<td i18n="asset.issuance-tx|Liquid Asset issuance TX">Issuance TX</td>
<td><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></td>
</tr>
</tbody>
@ -40,27 +40,27 @@
<table class="table table-borderless table-striped">
<tbody>
<tr *ngIf="isNativeAsset">
<td>Pegged in</td>
<td i18n="asset.pegged-in|Liquid Asset pegged-in amount">Pegged in</td>
<td>{{ formatAmount(asset.chain_stats.peg_in_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="isNativeAsset">
<td>Pegged out</td>
<td i18n="asset.pegged-out|Liquid Asset pegged-out amount">Pegged out</td>
<td>{{ formatAmount(asset.chain_stats.peg_out_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="!isNativeAsset">
<td>Issued amount</td>
<td i18n="asset.issued-amount|Liquid Asset issued amount">Issued amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.issued_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr>
<td>Burned amount</td>
<td i18n="asset.burned-amount|Liquid Asset burned amount">Burned amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.burned_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="!isNativeAsset">
<td>Circulating amount</td>
<td i18n="asset.circulating-amount|Liquid Asset circulating amount">Circulating amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.issued_amount - asset.chain_stats.burned_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="isNativeAsset">
<td>Circulating amount</td>
<td i18n="asset.circulating-amount|Liquid Asset circulating amount">Circulating amount</td>
<td>{{ formatAmount(asset.chain_stats.peg_in_amount - asset.chain_stats.burned_amount - asset.chain_stats.peg_out_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
</tbody>
@ -129,7 +129,7 @@
<ng-template [ngIf]="error">
<div class="text-center">
Error loading asset data.
<span i18n="asset.error.loading-asset-data">Error loading asset data.</span>
<br>
<i>{{ error.error }}</i>
</div>
@ -140,5 +140,5 @@
<br>
<ng-template #confidentialTd>
<td>Confidential</td>
<td i18n="shared.confidential">Confidential</td>
</ng-template>

View file

@ -1,7 +1,7 @@
<div class="container-xl">
<div class="title-block">
<h1 class="float-left"><ng-template [ngIf]="blockHeight === 0">Genesis </ng-template>Block <ng-template [ngIf]="blockHeight"><a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
<h1 class="float-left"><ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis </ng-template><ng-template [ngIf]="blockHeight" i18n="block.block">Block <a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
<button [routerLink]="['/' | relativeUrl]" class="btn btn-sm float-right mr-2 mt-2">&#10005;</button>
</div>
@ -15,24 +15,24 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width">Hash</td>
<td class="td-width" i18n="block.hash">Hash</td>
<td><a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
</tr>
<tr>
<td>Timestamp</td>
<td i18n="block.timestamp">Timestamp</td>
<td>
{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
<div class="lg-inline">
<i>(<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago)</i>
<i>(<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>)</i>
</div>
</td>
</tr>
<tr>
<td>Size</td>
<td i18n="block.size">Size</td>
<td>{{ block.size | bytes: 2 }}</td>
</tr>
<tr>
<td>Weight</td>
<td i18n="block.weight">Weight</td>
<td>{{ block.weight | wuBytes: 2 }}</td>
</tr>
</tbody>
@ -42,19 +42,19 @@
<table class="table table-borderless table-striped">
<tbody>
<tr *ngIf="block.medianFee !== undefined">
<td class="td-width">Median fee</td>
<td>~{{ block.medianFee | number:'1.0-0' }} sat/vB (<app-fiat [value]="block.medianFee * 140" digitsInfo="1.2-2" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)</td>
<td class="td-width" i18n="block.median-fee">Median fee</td>
<td>~{{ block.medianFee | number:'1.0-0' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span> (<app-fiat [value]="block.medianFee * 140" digitsInfo="1.2-2" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)</td>
</tr>
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
<tr>
<td>Total fees</td>
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
<td *ngIf="network !== 'liquid'; else liquidTotalFees"><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> (<app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat>)</td>
<ng-template #liquidTotalFees>
<td>{{ fees * 100000000 | number }} L-sat (<app-fiat [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>)</td>
</ng-template>
</tr>
<tr *ngIf="network !== 'liquid'">
<td>Subsidy + fees:</td>
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td>
<app-amount [satoshis]="(blockSubsidy + fees) * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> (<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>)
</td>
@ -62,16 +62,16 @@
</ng-template>
<ng-template #loadingFees>
<tr>
<td>Total fees</td>
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
</tr>
<tr *ngIf="network !== 'liquid'">
<td>Subsidy + fees:</td>
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td><span class="skeleton-loader"></span></td>
</tr>
</ng-template>
<tr>
<td>Miner</td>
<td i18n="block.miner">Miner</td>
<td><app-miner [coinbaseTransaction]="coinbaseTx"></app-miner></td>
</tr>
</tbody>
@ -82,7 +82,7 @@
<br>
<h2 class="float-left">{{ block.tx_count | number }} transaction<ng-template [ngIf]="block.tx_count !== 1">s</ng-template></h2>
<h2 class="float-left">{{ block.tx_count | number }} <ng-template [ngIf]="block.tx_count === 1" i18n="shared.transaction-count.singular">transaction</ng-template><ng-template [ngIf]="block.tx_count !== 1" i18n="shared.transaction-count.plural">transactions</ng-template></h2>
<ngb-pagination class="float-right" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true"></ngb-pagination>
@ -162,7 +162,7 @@
<ng-template [ngIf]="error">
<div class="text-center">
Error loading block data.
<span i18n="block.error.loading-block-data">Error loading block data.</span>
<br><br>
<i>{{ error.error }}</i>
</div>

View file

@ -7,14 +7,14 @@
</div>
<div class="block-body">
<div class="fees">
~{{ block.medianFee | number:'1.0-0' }} sat/vB
~{{ block.medianFee | number:'1.0-0' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</div>
<div class="fee-span">
{{ block.feeRange[1] | number:'1.0-0' }} - {{ block.feeRange[block.feeRange.length - 1] | number:'1.0-0' }} sat/vB
{{ block.feeRange[1] | number:'1.0-0' }} - {{ block.feeRange[block.feeRange.length - 1] | number:'1.0-0' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</div>
<div class="block-size">{{ block.size | bytes: 2 }}</div>
<div class="transaction-count">{{ block.tx_count | number }} transaction<ng-template [ngIf]="block.tx_count !== 1">s</ng-template></div>
<div class="time-difference"><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</div>
<div class="transaction-count">{{ block.tx_count | number }} <ng-template [ngIf]="block.tx_count === 1" i18n="shared.transaction">transaction</ng-template><ng-template [ngIf]="block.tx_count !== 1" i18n="shared.transactions">transactions</ng-template></div>
<div class="time-difference"><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
</div>
</div>
</div>

View file

@ -8,7 +8,7 @@
</div>
<div *ngIf="(isLoading$ | async) === true" class="position-container loading">
<div class="loading-block">
<h3>Waiting for blocks...</h3>
<h3 i18n="blockchain.waiting-for-blocks|Loading text">Waiting for blocks...</h3>
<div class="spinner-border text-light mt-2"></div>
</div>
</div>

View file

@ -2,4 +2,4 @@
<button #btn class="btn btn-sm btn-link pt-0" style="line-height: 0.9;" [attr.data-clipboard-text]="text">
<img src="./resources/clippy.svg" width="13">
</button>
</span>
</span>

View file

@ -1,21 +1,21 @@
<table style="width: 100%;">
<tr *ngIf="(isLoadingWebSocket$ | async) === false && (feeEstimations$ | async) as feeEstimations; else loadingFees">
<td class="d-none d-md-block">
<h5 class="card-title">Low priority</h5>
<h5 class="card-title" i18n="fees-box.low-priority">Low priority</h5>
<p class="card-text">
{{ feeEstimations.hourFee }} sat/vB (<app-fiat [value]="feeEstimations.hourFee * 140" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)
{{ feeEstimations.hourFee }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span> (<app-fiat [value]="feeEstimations.hourFee * 140" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)
</p>
</td>
<td>
<h5 class="card-title">Medium priority</h5>
<h5 class="card-title" i18n="fees-box.medium-priority">Medium priority</h5>
<p class="card-text">
{{ feeEstimations.halfHourFee }} sat/vB (<app-fiat [value]="feeEstimations.halfHourFee * 140" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)
{{ feeEstimations.halfHourFee }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span> (<app-fiat [value]="feeEstimations.halfHourFee * 140" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)
</p>
</td>
<td>
<h5 class="card-title">High priority</h5>
<h5 class="card-title" i18n="fees-box.high-priority">High priority</h5>
<p class="card-text">
{{ feeEstimations.fastestFee }} sat/vB (<app-fiat [value]="feeEstimations.fastestFee * 140" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)
{{ feeEstimations.fastestFee }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span> (<app-fiat [value]="feeEstimations.fastestFee * 140" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)
</p>
</td>
</tr>
@ -24,15 +24,15 @@
<ng-template #loadingFees>
<tr>
<td class="d-none d-md-block">
<h5 class="card-title">Low priority</h5>
<h5 class="card-title" i18n="fees-box.low-priority">Low priority</h5>
<p class="card-text"><span class="skeleton-loader"></span></p>
</td>
<td>
<h5 class="card-title">Medium priority</h5>
<h5 class="card-title" i18n="fees-box.medium-priority">Medium priority</h5>
<p class="card-text"><span class="skeleton-loader" style="width: 80%;"></span></p>
</td>
<td>
<h5 class="card-title">High priority</h5>
<h5 class="card-title" i18n="fees-box.high-priority">High priority</h5>
<p class="card-text"><span class="skeleton-loader"></span></p>
</td>
</tr>

View file

@ -2,22 +2,22 @@
<div class="container-xl">
<div class="row text-center" *ngIf="mempoolInfoData$ | async as mempoolInfoData">
<div class="col d-none d-sm-block">
<span class="txPerSecond">Tx vBytes per second:</span>
<span class="txPerSecond" i18n="footer.tx-vbytes-per-second">Tx vBytes per second:</span>
<span *ngIf="mempoolInfoData.vBytesPerSecond === 0; else inSync">
&nbsp;<span class="badge badge-pill badge-warning">Backend is synchronizing</span>
&nbsp;<span class="badge badge-pill badge-warning" i18n="footer.backend-is-synchronizing">Backend is synchronizing</span>
</span>
<ng-template #inSync>
<div class="progress sub-text">
<div class="progress-bar {{ mempoolInfoData.progressClass }}" role="progressbar" [ngStyle]="{'width': mempoolInfoData.progressWidth}">{{ mempoolInfoData.vBytesPerSecond | ceil | number }} vBytes/s</div>
<div class="progress-bar {{ mempoolInfoData.progressClass }}" role="progressbar" [ngStyle]="{'width': mempoolInfoData.progressWidth}">{{ mempoolInfoData.vBytesPerSecond | ceil | number }} <span i18n="shared.vbytes-per-second">vBytes/s</span></div>
</div>
</ng-template>
</div>
<div class="col">
<span class="unconfirmedTx">Unconfirmed<span class="extra-text"> transactions</span>:</span>
<span class="unconfirmedTx"><span i18n="shared.unconfirmed">Unconfirmed</span> <span class="extra-text" i18n="shared.transactions">transactions</span>:</span>
<div class="sub-text">{{ mempoolInfoData.memPoolInfo.size | number }}</div>
</div>
<div class="col">
<span class="mempoolSize">Mempool size:</span>
<span class="mempoolSize" i18n="footer.mempool-size">Mempool size:</span>
<div class="sub-text" *ngIf="(mempoolBlocksData$ | async) as mempoolBlocksData">{{ mempoolBlocksData.size | bytes }} ({{ mempoolBlocksData.blocks }} block<span [hidden]="mempoolBlocksData.blocks <= 1">s</span>)</div>
</div>
</div>

View file

@ -6,17 +6,17 @@
<table class="table table-borderless" [alwaysCallback]="true" [fromRoot]="true" [infiniteScrollContainer]="'body'" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="loadMore()">
<thead>
<th style="width: 15%;">Height</th>
<th class="d-none d-md-block" style="width: 20%;">Timestamp</th>
<th style="width: 20%;">Mined</th>
<th class="d-none d-lg-block" style="width: 15%;">Transactions</th>
<th style="width: 20%;">Filled</th>
<th style="width: 15%;" i18n="latest-blocks.height">Height</th>
<th class="d-none d-md-block" style="width: 20%;" i18n="latest-blocks.timestamp">Timestamp</th>
<th style="width: 20%;" i18n="latest-blocks.mined">Mined</th>
<th class="d-none d-lg-block" style="width: 15%;" i18n="latest-blocks.transactions">Transactions</th>
<th style="width: 20%;" i18n="latest-blocks.filled">Filled</th>
</thead>
<tbody>
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
<td><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
<td class="d-none d-md-block">{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</td>
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></td>
<td class="d-none d-lg-block">{{ block.tx_count | number }}</td>
<td>
<div class="progress position-relative">

View file

@ -4,8 +4,8 @@
<a class="navbar-brand" [routerLink]="['/' | relativeUrl]" style="position: relative;">
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
<img src="./resources/mempool-logo.png" height="35" width="140" class="logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }">
<div class="badge badge-warning connection-badge" *ngIf="connectionState.val === 0">Offline</div>
<div class="badge badge-warning connection-badge" style="left: 0px;" *ngIf="connectionState.val === 1">Reconnecting...</div>
<div class="badge badge-warning connection-badge" *ngIf="connectionState.val === 0" i18n="master-page.offline">Offline</div>
<div class="badge badge-warning connection-badge" style="left: 0px;" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
</ng-container>
</a>
@ -16,7 +16,7 @@
<div ngbDropdownMenu>
<button ngbDropdownItem class="mainnet" routerLink="/"><img src="./resources/bitcoin-logo.png" style="width: 30px;" class="mr-1"> Mainnet</button>
<button ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" routerLink="/testnet"><img src="./resources/testnet-logo.png" style="width: 30px;" class="mr-1"> Testnet</button>
<h6 *ngIf="env.LIQUID_ENABLED || env.BISQ_ENABLED" class="dropdown-header">Layer 2 Networks</h6>
<h6 *ngIf="env.LIQUID_ENABLED || env.BISQ_ENABLED" class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<button ngbDropdownItem *ngIf="env.BISQ_ENABLED" class="mainnet" [class.active]="network.val === 'bisq'" routerLink="/bisq"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</button>
<button ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid" [class.active]="network.val === 'liquid'" routerLink="/liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</button>
</div>

View file

@ -13,23 +13,23 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Median fee</td>
<td i18n="mempool-block.median-fee">Median fee</td>
<td>~{{ mempoolBlock.medianFee | number:'1.0-0' }} sat/vB (<app-fiat [value]="mempoolBlock.medianFee * 140" digitsInfo="1.2-2" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat>)</td>
</tr>
<tr>
<td>Fee span</td>
<td i18n="mempool-block.fee-span">Fee span</td>
<td><span class="yellow-color">{{ mempoolBlock.feeRange[0] | number:'1.0-0' }} - {{ mempoolBlock.feeRange[mempoolBlock.feeRange.length - 1] | number:'1.0-0' }} sat/vB</span></td>
</tr>
<tr>
<td>Total fees</td>
<td i18n="mempool-block.total-fees">Total fees</td>
<td><app-amount [satoshis]="mempoolBlock.totalFees" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount> (<app-fiat [value]="mempoolBlock.totalFees" digitsInfo="1.0-0"></app-fiat>)</td>
</tr>
<tr>
<td>Transactions</td>
<td i18n="mempool-block.transactions">Transactions</td>
<td>{{ mempoolBlock.nTx }}</td>
</tr>
<tr>
<td>Filled</td>
<td i18n="mempool-block.filled">Filled</td>
<td>
<div class="progress position-relative">
<div class="progress-bar progress-mempool {{ (network$ | async) }}" role="progressbar" [ngStyle]="{'width': (mempoolBlock.blockVSize / 1000000) * 100 + '%' }"></div>
@ -48,4 +48,4 @@
<br>
</div>
</div>

View file

@ -5,23 +5,23 @@
<a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink">&nbsp;</a>
<div class="block-body">
<div class="fees">
~{{ projectedBlock.medianFee | number:'1.0-0' }} sat/vB
~{{ projectedBlock.medianFee | number:'1.0-0' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</div>
<div class="fee-span">
{{ projectedBlock.feeRange[0] | number:'1.0-0' }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:'1.0-0' }} sat/vB
{{ projectedBlock.feeRange[0] | number:'1.0-0' }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:'1.0-0' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</div>
<div class="block-size">{{ projectedBlock.blockSize | bytes: 2 }}</div>
<div class="transaction-count">{{ projectedBlock.nTx | number }} transaction<ng-template [ngIf]="projectedBlock.nTx !== 1">s</ng-template></div>
<div class="transaction-count">{{ projectedBlock.nTx | number }} <ng-template [ngIf]="projectedBlock.nTx === 1" i18n="shared.transaction-count.singular">transaction</ng-template><ng-template [ngIf]="projectedBlock.nTx !== 1" i18n="shared.transaction-count.plural">transactions</ng-template></div>
<div class="time-difference" *ngIf="projectedBlock.blockVSize <= 1000000; else mergedBlock">
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="timeDiffMainnet">
In &lt; {{ 1 * i + 1 }} minute
<span i18n="mempool-blocks.eta-of-next-block|Block Frequency">In</span> &lt; {{ 1 * i + 1 }} <span i18n="shared.minute">minute</span>
</ng-template>
<ng-template #timeDiffMainnet>
In ~{{ 10 * i + 10 }} minutes
<span i18n="mempool-blocks.eta-of-next-block|Block Frequency">In</span> ~{{ 10 * i + 10 }} <span i18n="shared.minutes">minutes</span>
</ng-template>
</div>
<ng-template #mergedBlock>
<div class="time-difference"><b>({{ projectedBlock.blockVSize / 1000000 | ceil }} blocks)</b></div>
<div class="time-difference"><b>({{ projectedBlock.blockVSize / 1000000 | ceil }} <span i18n="shared.blocks">blocks</span>)</b></div>
</ng-template>
</div>
<span class="animated-border"></span>

View file

@ -7,6 +7,6 @@
<a placement="bottom" [ngbTooltip]="title" [href]="url" target="_blank" class="badge badge-primary">{{ miner }}</a>
</ng-template>
<ng-template #unknownMiner>
<span class="badge badge-secondary">Unknown</span>
<span class="badge badge-secondary" i18n="miner.tag.unknown-miner">Unknown</span>
</ng-template>
</ng-template>

View file

@ -1,10 +1,10 @@
<form [formGroup]="searchForm" (submit)="searchForm.valid && search()" novalidate>
<div class="d-flex">
<div class="search-box-container mr-2">
<input #instance="ngbTypeahead" [ngbTypeahead]="typeaheadSearch" (selectItem)="itemSelected()" (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" placeholder="TXID, block height, hash or address">
<input #instance="ngbTypeahead" [ngbTypeahead]="typeaheadSearch" (selectItem)="itemSelected()" (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="TXID, block height, hash or address">
</div>
<div>
<button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary"><fa-icon [icon]="['fas', 'search']" [fixedWidth]="true" title="Search"></fa-icon></button>
</div>
</div>
</form>
</form>

View file

@ -3,7 +3,7 @@
<div class="row">
<div class="col-lg-12" *ngIf="loading">
<div class="text-center">
<h3>Loading graphs...</h3>
<h3 i18n="statistics.loading-graphs">Loading graphs...</h3>
<br>
<div class="spinner-border text-light"></div>
</div>
@ -13,7 +13,7 @@
<div class="card mb-3" *ngIf="mempoolStats.length">
<div class="card-header">
<i class="fa fa-area-chart"></i> Mempool by vBytes (sat/vByte)
<i class="fa fa-area-chart"></i> <span i18n="statistics.memory-by-vBytes">Mempool by vBytes (sat/vByte)</span>
<form [formGroup]="radioGroupForm" style="float: right;">
<div class="spinner-border text-light bootstrap-spinner" *ngIf="spinnerLoading"></div>
@ -54,7 +54,8 @@
<div class="col-lg-12">
<div class="card mb-3" *ngIf="mempoolTransactionsWeightPerSecondData">
<div class="card-header">
<i class="fa fa-area-chart"></i> Transaction vBytes per second (vB/s)</div>
<i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
</div>
<div class="card-body">
<div style="height: 600px;">
<app-chartist

View file

@ -18,4 +18,4 @@
</div>
</div>
</div>

View file

@ -18,17 +18,6 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy {
private ref: ChangeDetectorRef,
private stateService: StateService,
) {
if (document.body.clientWidth < 768) {
this.intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
min: 60,
sec: 1
};
} else {
this.intervals = {
year: 31536000,
month: 2592000,
@ -38,7 +27,6 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy {
minute: 60,
second: 1
};
}
}
ngOnInit() {
@ -65,7 +53,7 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy {
calculate() {
const seconds = Math.floor((+new Date() - +new Date(this.time * 1000)) / 1000);
if (seconds < 60) {
return '< 1 minute';
return $localize`:@@time-since.just-now:Just now`;
}
let counter;
for (const i in this.intervals) {
@ -73,9 +61,41 @@ export class TimeSinceComponent implements OnInit, OnChanges, OnDestroy {
counter = Math.floor(seconds / this.intervals[i]);
if (counter > 0) {
if (counter === 1) {
return counter + ' ' + i; // singular (1 day ago)
switch (i) { // singular (1 day ago)
case 'year': return $localize`:@@time-since.year.ago:${counter}:INTERPOLATION: year ago`; break;
case 'month': return $localize`:@@time-since.month.ago:${counter}:INTERPOLATION: month ago`; break;
case 'week': return $localize`:@@time-since.week.ago:${counter}:INTERPOLATION: week ago`; break;
case 'day': return $localize`:@@time-since.day.ago:${counter}:INTERPOLATION: day ago`; break;
case 'hour': return $localize`:@@time-since.hour.ago:${counter}:INTERPOLATION: hour ago`; break;
case 'minute':
if (document.body.clientWidth < 768) {
return $localize`:@@time-since.min.ago:${counter}:INTERPOLATION: min ago`;
}
return $localize`:@@time-since.minute.ago:${counter}:INTERPOLATION: minute ago`;
case 'second':
if (document.body.clientWidth < 768) {
return $localize`:@@time-since.sec.ago:${counter}:INTERPOLATION: sec ago`;
}
return $localize`:@@time-since.second.ago:${counter}:INTERPOLATION: second ago`;
}
} else {
return counter + ' ' + i + 's'; // plural (2 days ago)
switch (i) { // plural (2 days ago)
case 'year': return $localize`:@@time-since.years.ago:${counter}:INTERPOLATION: years ago`; break;
case 'month': return $localize`:@@time-since.months.ago:${counter}:INTERPOLATION: months ago`; break;
case 'week': return $localize`:@@time-since.weeks.ago:${counter}:INTERPOLATION: weeks ago`; break;
case 'day': return $localize`:@@time-since.days.ago:${counter}:INTERPOLATION: days ago`; break;
case 'hour': return $localize`:@@time-since.hours.ago:${counter}:INTERPOLATION: hours ago`; break;
case 'minute':
if (document.body.clientWidth < 768) {
return $localize`:@@time-since.mins.ago:${counter}:INTERPOLATION: mins ago`;
}
return $localize`:@@time-since.minutes.ago:${counter}:INTERPOLATION: minutes ago`;
case 'second':
if (document.body.clientWidth < 768) {
return $localize`:@@time-since.secs.ago:${counter}:INTERPOLATION: secs ago`;
}
return $localize`:@@time-since.seconds.ago:${counter}:INTERPOLATION: seconds ago`;
}
}
}
}

View file

@ -14,7 +14,8 @@ export class TimespanComponent implements OnChanges {
ngOnChanges() {
const seconds = this.time;
if (seconds < 60) {
return '< 1 minute';
this.text = '< 1 minute';
return;
}
const intervals = {
year: 31536000,

View file

@ -2,20 +2,20 @@
<div class="title-block">
<div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert">
This transaction has been replaced by:
<span i18n="transaction.rbf.replacement|RBF replacement">This transaction has been replaced by:</span>
<a class="alert-link" [routerLink]="['/tx/' | relativeUrl, rbfTransaction.txid]" [state]="{ data: rbfTransaction }">
<span class="d-inline d-lg-none">{{ rbfTransaction.txid | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ rbfTransaction.txid }}</span>
</a>
</div>
<h1 class="float-left mr-3 mb-md-3">Transaction</h1>
<h1 class="float-left mr-3 mb-md-3" i18n="shared.transaction">Transaction</h1>
<ng-template [ngIf]="tx?.status?.confirmed">
<button *ngIf="latestBlock" type="button" class="btn btn-sm btn-success float-right mr-2 mt-1 mt-md-3">{{ latestBlock.height - tx.status.block_height + 1 }} confirmation<ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1">s</ng-container></button>
<button *ngIf="latestBlock" type="button" class="btn btn-sm btn-success float-right mr-2 mt-1 mt-md-3">{{ latestBlock.height - tx.status.block_height + 1 }} <ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 == 1" i18n="shared.confirmation-count.singular|Transaction singular confirmation count">confirmation</ng-container><ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1" i18n="shared.confirmation-count.plural|Transaction plural confirmation count">confirmations</ng-container></button>
</ng-template>
<ng-template [ngIf]="tx && !tx?.status.confirmed">
<button type="button" class="btn btn-sm btn-danger float-right mr-2 mt-1 mt-md-3">Unconfirmed</button>
<button type="button" class="btn btn-sm btn-danger float-right mr-2 mt-1 mt-md-3" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
</ng-template>
<div>
@ -39,28 +39,28 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Timestamp</td>
<td i18n="transaction.timestamp|Transaction Timestamp">Timestamp</td>
<td>
{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}
<div class="lg-inline">
<i>(<app-time-since [time]="tx.status.block_time" [fastRender]="true"></app-time-since> ago)</i>
<i>(<app-time-since [time]="tx.status.block_time" [fastRender]="true"></app-time-since>)</i>
</div>
</td>
</tr>
<tr *ngIf="latestBlock && tx.status.block_height <= latestBlock.height - 8">
<td class="td-width">Included in block</td>
<td class="td-width" i18n="transaction.included-in-block|Transaction included in block">Included in block</td>
<td>
<a [routerLink]="['/block/' | relativeUrl, tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
</td>
</tr>
<ng-template [ngIf]="transactionTime > 0">
<tr>
<td>Confirmed</td>
<td>After <app-timespan [time]="tx.status.block_time - transactionTime"></app-timespan></td>
<td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
<td><span i18n="transaction.confirmed.after|Transaction confirmed after">After</span>&nbsp;<app-timespan [time]="tx.status.block_time - transactionTime"></app-timespan></td>
</tr>
</ng-template>
<tr *ngIf="network !== 'liquid'">
<td class="td-width">Features</td>
<td class="td-width" i18n="transaction.features|Transaction features">Features</td>
<td>
<app-tx-features [tx]="tx"></app-tx-features>
</td>
@ -72,13 +72,13 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width">Fee</td>
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
<td>{{ tx.fee | number }} sat (<app-fiat [value]="tx.fee"></app-fiat>)</td>
</tr>
<tr>
<td>Fee per vByte</td>
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
<td>
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
&nbsp;
<app-tx-fee-rating *ngIf="tx.fee" [tx]="tx"></app-tx-fee-rating>
</td>
@ -106,35 +106,34 @@
</tr>
<ng-template #firstSeenTmpl>
<tr>
<td>First seen</td>
<td><i><app-time-since [time]="transactionTime" [fastRender]="true"></app-time-since> ago</i></td>
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
<td><i><app-time-since [time]="transactionTime" [fastRender]="true"></app-time-since></i></td>
</tr>
</ng-template>
</ng-template>
<tr>
<td class="td-width">ETA</td>
<td class="td-width" i18n="transaction.eta|Transaction ETA">ETA</td>
<td>
<ng-template [ngIf]="txInBlockIndex === undefined" [ngIfElse]="estimationTmpl">
<span class="skeleton-loader"></span>
</ng-template>
<ng-template #estimationTmpl>
<ng-template [ngIf]="txInBlockIndex >= 7" [ngIfElse]="belowBlockLimit">
In several hours (or more)
<span i18n="transaction.eta.in-several-hours|Transaction ETA in several hours or more">In several hours (or more)</span>
</ng-template>
<ng-template #belowBlockLimit>
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="timeEstimateDefault">
&lt; {{ 1 * txInBlockIndex + 1 }} minutes <i>({{ txInBlockIndex + 1 }} block{{ txInBlockIndex > 0 ? 's' : '' }})</i>
&lt; {{ 1 * txInBlockIndex + 1 }}&nbsp;<span i18n="transaction.minutes|Transaction Minutes">minutes</span>&nbsp;<i>({{ txInBlockIndex + 1 }} <span i18n="transaction.eta.block|Transaction ETA (X blocks)">block</span>{{ txInBlockIndex > 0 ? 's' : '' }})</i>
</ng-template>
<ng-template #timeEstimateDefault>
~{{ 10 * txInBlockIndex + 10 }} minutes <i>({{ txInBlockIndex + 1 }} block{{ txInBlockIndex > 0 ? 's' : '' }})</i>
~{{ 10 * txInBlockIndex + 10 }}&nbsp;<span i18n="transaction.minutes|Transaction Minutes">minutes</span>&nbsp;<i>({{ txInBlockIndex + 1 }} <span i18n="transaction.eta.block|Transaction ETA (X blocks)">block</span>{{ txInBlockIndex > 0 ? 's' : '' }})</i>
</ng-template>
</ng-template>
</ng-template>
</td>
</tr>
<tr *ngIf="network !== 'liquid'">
<td class="td-width">Features</td>
<td class="td-width" i18n="transaction.features|Transaction Features">Features</td>
<td>
<app-tx-features [tx]="tx"></app-tx-features>
</td>
@ -146,12 +145,12 @@
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width">Fee</td>
<td>{{ tx.fee | number }} sat (<app-fiat [value]="tx.fee"></app-fiat>)</td>
<td class="td-width" i18n="transaction.fee|Transaction Fee">Fee</td>
<td>{{ tx.fee | number }} <span i18n="transaction.fee.sat|Transaction Fee sat">sat</span> (<app-fiat [value]="tx.fee"></app-fiat>)</td>
</tr>
<tr>
<td>Fee per vByte</td>
<td>{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB</td>
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
<td>{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
</tr>
</tbody>
</table>
@ -162,24 +161,24 @@
<br>
<h2 class="float-left">Inputs & Outputs</h2>
<h2 class="float-left" i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
<button type="button" class="btn btn-outline-info btn-sm float-right mr-1 mt-0 mt-md-2" (click)="txList.toggleDetails()">Details</button>
<button type="button" class="btn btn-outline-info btn-sm float-right mr-1 mt-0 mt-md-2" (click)="txList.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
<div class="clearfix"></div>
<app-transactions-list #txList [transactions]="[tx]" [transactionPage]="true"></app-transactions-list>
<h2>Details</h2>
<h2 i18n="transaction.details">Details</h2>
<div class="box">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Size</td>
<td i18n="transaction.size|Transaction Size">Size</td>
<td>{{ tx.size | bytes: 2 }}</td>
</tr>
<tr>
<td>Weight</td>
<td i18n="transaction.weight|Transaction Weight">Weight</td>
<td>{{ tx.weight | wuBytes: 2 }}</td>
</tr>
</tbody>
@ -263,8 +262,8 @@
<ng-template [ngIf]="error">
<div class="text-center" *ngIf="waitingForTransaction; else errorTemplate">
<h3>Transaction not found.</h3>
<h5>Waiting for it to appear in the mempool...</h5>
<h3 i18n="transaction.error.transaction-not-found">Transaction not found.</h3>
<h5 i18n="transaction.error.waiting-for-it-to-appear">Waiting for it to appear in the mempool...</h5>
<div class="spinner-border text-light mt-2"></div>
</div>

View file

@ -7,7 +7,7 @@
<div class="float-right">
<ng-template [ngIf]="tx.status.confirmed">{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}</ng-template>
<ng-template [ngIf]="!tx.status.confirmed && tx.firstSeen">
<i><app-time-since [time]="tx.firstSeen" [fastRender]="true"></app-time-since> ago</i>
<i><app-time-since [time]="tx.firstSeen" [fastRender]="true"></app-time-since></i>
</ng-template>
</div>
<div class="clearfix"></div>
@ -36,9 +36,9 @@
</td>
<td>
<div [ngSwitch]="true">
<ng-container *ngSwitchCase="vin.is_coinbase"><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii">Coinbase<ng-template [ngIf]="network !== 'liquid'"> (Newly Generated Coins)</ng-template></a><br><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></ng-container>
<ng-container *ngSwitchCase="vin.is_coinbase"><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii"><span i18n="transactions-list.coinbase">Coinbase</span><ng-template [ngIf]="network !== 'liquid'">&nbsp;<span i18n="transactions-list.newly-generated-coins">(Newly Generated Coins)</span></ng-template></a><br><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></ng-container>
<ng-container *ngSwitchCase="vin.is_pegin">
Peg-in
<span i18n="transactions-list.peg-in">Peg-in</span>
</ng-container>
<ng-container *ngSwitchDefault>
<a [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
@ -68,32 +68,32 @@
<tbody>
<ng-template [ngIf]="vin.scriptsig">
<tr>
<td>ScriptSig (ASM)</td>
<td i18n="transactions-list.scriptsig.asm|ScriptSig (ASM)">ScriptSig (ASM)</td>
<td [innerHTML]="vin.scriptsig_asm | asmStyler"></td>
</tr>
<tr>
<td>ScriptSig (HEX)</td>
<td i18n="transactions-list.scriptsig.hex|ScriptSig (HEX)">ScriptSig (HEX)</td>
<td>{{ vin.scriptsig }}</td>
</tr>
</ng-template>
<tr *ngIf="vin.witness">
<td>Witness</td>
<td i18n="transactions-list.witness">Witness</td>
<td>{{ vin.witness.join(' ') }}</td>
</tr>
<tr *ngIf="vin.inner_redeemscript_asm">
<td>P2SH redeem script</td>
<td i18n="transactions-list.p2sh-redeem-script">P2SH redeem script</td>
<td [innerHTML]="vin.inner_redeemscript_asm | asmStyler"></td>
</tr>
<tr *ngIf="vin.inner_witnessscript_asm">
<td>P2WSH witness script</td>
<td i18n="transactions-list.p2wsh-witness-script">P2WSH witness script</td>
<td [innerHTML]="vin.inner_witnessscript_asm | asmStyler"></td>
</tr>
<tr>
<td>nSequence</td>
<td i18n="transactions-list.nsequence">nSequence</td>
<td>{{ formatHex(vin.sequence) }}</td>
</tr>
<tr *ngIf="vin.prevout">
<td>Previous output script</td>
<td i18n="transactions-list.previous-output-script">Previous output script</td>
<td [innerHTML]="vin.prevout.scriptpubkey_asm | asmStyler">{{ vin.prevout.scriptpubkey_type ? ('(' + vin.prevout.scriptpubkey_type + ')') : '' }}"</td>
</tr>
</tbody>
@ -103,7 +103,7 @@
</ng-template>
<tr *ngIf="tx.vin.length > 10 && tx['@vinLimit']">
<td colspan="3" class="text-center">
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@vinLimit'] = false;">Load all ({{ tx.vin.length - 10 }})</button>
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@vinLimit'] = false;"><span i18n="transactions-list.load-all">Load all</span> ({{ tx.vin.length - 10 }})</button>
</td>
</tr>
</tbody>
@ -122,7 +122,8 @@
</a>
<ng-template #scriptpubkey_type>
<ng-template [ngIf]="vout.pegout" [ngIfElse]="defaultscriptpubkey_type">
Peg-out to <a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
<span i18n="transactions-list.peg-out-to">Peg-out to</span>
<a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.pegout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vout.pegout.scriptpubkey_address | shortenString : 35 }}</span>
</a>
@ -160,19 +161,19 @@
<table class="table table-striped table-borderless details-table mb-3">
<tbody>
<tr *ngIf="vout.scriptpubkey_type">
<td>Type</td>
<td i18n="transactions-list.vout.scriptpubkey-type">Type</td>
<td>{{ vout.scriptpubkey_type.toUpperCase() }}</td>
</tr>
<tr>
<td>scriptPubKey (ASM)</td>
<td i18n="transactions-list.scriptpubkey.asm|ScriptPubKey (ASM)">ScriptPubKey (ASM)</td>
<td [innerHTML]="vout.scriptpubkey_asm | asmStyler"></td>
</tr>
<tr>
<td>scriptPubKey (HEX)</td>
<td i18n="transactions-list.scriptpubkey.hex|ScriptPubKey (HEX)">ScriptPubKey (HEX)</td>
<td>{{ vout.scriptpubkey }}</td>
</tr>
<tr *ngIf="vout.scriptpubkey_type == 'op_return'">
<td>OP_RETURN data</td>
<td>OP_RETURN <span i18n="transactions-list.vout.scriptpubkey-type.data">data</span></td>
<td>{{ vout.scriptpubkey_asm | hex2ascii }}</td>
</tr>
</tbody>
@ -182,7 +183,7 @@
</ng-template>
<tr *ngIf="tx.vout.length > 10 && tx['@voutLimit']">
<td colspan="3" class="text-center">
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@voutLimit'] = false;">Load all ({{ tx.vout.length - 10 }})</button>
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@voutLimit'] = false;"><span i18n="transactions-list.load-all">Load all</span> ({{ tx.vout.length - 10 }})</button>
</td>
</tr>
</tbody>
@ -192,19 +193,19 @@
<div>
<div class="float-left mt-2-5" *ngIf="!transactionPage && tx.fee">
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB <span class="d-none d-sm-inline-block"> &ndash; {{ tx.fee | number }} sat (<app-fiat [value]="tx.fee"></app-fiat>)</span>
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block">&nbsp;&ndash; {{ tx.fee | number }} <span i18n="shared.sat|sat">sat</span> (<app-fiat [value]="tx.fee"></app-fiat>)</span>
</div>
<div class="float-right">
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">{{ latestBlock.height - tx.status.block_height + 1 }} confirmation<ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1">s</ng-container></button>
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">{{ latestBlock.height - tx.status.block_height + 1 }} <ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 == 1" i18n="shared.confirmation-count.singular">confirmation</ng-container><ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1" i18n="shared.confirmation-count.plural">confirmations</ng-container></button>
<ng-template #unconfirmedButton>
<button type="button" class="btn btn-sm btn-danger mt-2">Unconfirmed</button>
<button type="button" class="btn btn-sm btn-danger mt-2" i18n="transactions-list.unconfirmed">Unconfirmed</button>
</ng-template>
&nbsp;
</span>
<button type="button" class="btn btn-sm btn-primary mt-2" (click)="switchCurrency()">
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="defaultAmount">Confidential</ng-template>
<ng-template [ngIf]="network === 'liquid'" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
<ng-template #defaultAmount>
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
</ng-template>

View file

@ -0,0 +1,22 @@
<!-- this component is only used for the angular string extractor -->{{counter}}
<div>
<span i18n="@@time-since.just-now">Just now</span>
<span i18n="@@time-since.sec.ago">{{counter}} sec ago</span>
<span i18n="@@time-since.secs.ago">{{counter}} secs ago</span>
<span i18n="@@time-since.second.ago">{{counter}} second ago</span>
<span i18n="@@time-since.seconds.ago">{{counter}} seconds ago</span>
<span i18n="@@time-since.min.ago">{{counter}} min ago</span>
<span i18n="@@time-since.mins.ago">{{counter}} mins ago</span>
<span i18n="@@time-since.minute.ago">{{counter}} minute ago</span>
<span i18n="@@time-since.minutes.ago">{{counter}} minutes ago</span>
<span i18n="@@time-since.hour.ago">{{counter}} hour ago</span>
<span i18n="@@time-since.hours.ago">{{counter}} hours ago</span>
<span i18n="@@time-since.day.ago">{{counter}} day ago</span>
<span i18n="@@time-since.days.ago">{{counter}} days ago</span>
<span i18n="@@time-since.week.ago">{{counter}} week ago</span>
<span i18n="@@time-since.weeks.ago">{{counter}} weeks ago</span>
<span i18n="@@time-since.month.ago">{{counter}} month ago</span>
<span i18n="@@time-since.months.ago">{{counter}} months ago</span>
<span i18n="@@time-since.year.ago">{{counter}} year ago</span>
<span i18n="@@time-since.years.ago">{{counter}} years ago</span>
</div>

View file

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-translation-strings',
templateUrl: './translation-strings.component.html'
})
export class TranslationStringsComponent {
counter: string;
constructor() { }
}

View file

@ -1,8 +1,8 @@
<span *ngIf="segwitGains.realizedGains && !segwitGains.potentialBech32Gains; else segwitTwo" 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; else segwitTwo" 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" i18n="tx-features.tag.segwit|SegWit">SegWit</span>
<ng-template #segwitTwo>
<span *ngIf="segwitGains.realizedGains && segwitGains.potentialBech32Gains else potentialP2shGains" 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.realizedGains && segwitGains.potentialBech32Gains else potentialP2shGains" 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" i18n="tx-features.tag.segwit|SegWit">SegWit</span>
<ng-template #potentialP2shGains>
<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="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 i18n="tx-features.tag.segwit|SegWit">SegWit</del></span>
</ng-template>
</ng-template>
<span *ngIf="isRbfTransaction" class="badge badge-success" ngbTooltip="This transaction support Replace-By-Fee (RBF) allowing fee bumping" placement="bottom">RBF</span>
<span *ngIf="isRbfTransaction" class="badge badge-success" ngbTooltip="This transaction support Replace-By-Fee (RBF) allowing fee bumping" placement="bottom" i18n="tx-features.tag.rbf|RBF">RBF</span>

View file

@ -1,3 +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>
<span *ngIf="feeRating === 1" class="badge badge-success" i18n="tx-fee-rating.optimal|TX Fee Rating is Optimal">Optimal</span>
<span *ngIf="feeRating === 2" class="badge badge-warning" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.warning|TX Fee Rating is Warning">Overpaid {{ overpaidTimes }}x</span>
<span *ngIf="feeRating === 3" class="badge badge-danger" title="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.danger|TX Fee Rating is Danger">Overpaid {{ overpaidTimes }}x</span>

View file

@ -72,18 +72,18 @@
<div class="col mb-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">Latest blocks</h5>
<h5 class="card-title" i18n="dashboard.latest-blocks">Latest blocks</h5>
<table class="table table-borderless text-left">
<thead>
<th style="width: 15%;">Height</th>
<th style="width: 35%;">Mined</th>
<th style="width: 20%;" class="d-none d-lg-table-cell">Txs</th>
<th style="width: 30%;">Filled</th>
<th style="width: 15%;" i18n="dashboard.latest-blocks.height">Height</th>
<th style="width: 35%;" i18n="dashboard.latest-blocks.mined">Mined</th>
<th style="width: 20%;" class="d-none d-lg-table-cell" i18n="dashboard.latest-blocks.transaction-count">TXs</th>
<th style="width: 30%;" i18n="dashboard.latest-blocks.size">Size</th>
</thead>
<tbody>
<tr *ngFor="let block of blocks$ | async; let i = index; trackBy: trackByBlock">
<td><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since> ago</td>
<td><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></td>
<td class="d-none d-lg-table-cell">{{ block.tx_count | number }}</td>
<td>
<div class="progress position-relative">
@ -94,27 +94,27 @@
</tr>
</tbody>
</table>
<div class="text-center"><a href="" [routerLink]="['/blocks' | relativeUrl]">View all &raquo;</a></div>
<div class="text-center"><a href="" [routerLink]="['/blocks' | relativeUrl]" i18n="dashboard.view-all">View all &raquo;</a></div>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">Latest transactions</h5>
<h5 class="card-title" i18n="dashboard.latest-transactions">Latest transactions</h5>
<table class="table table-borderless text-left">
<thead>
<th style="width: 20%;">TXID</th>
<th style="width: 35%;" class="text-right d-none d-lg-table-cell">Amount</th>
<th *ngIf="(network$ | async) === ''" style="width: 20%;" class="text-right d-none d-lg-table-cell">USD</th>
<th style="width: 25%;" class="text-right">Fee</th>
<th style="width: 20%;" i18n="dashboard.latest-transactions.txid">TXID</th>
<th style="width: 35%;" class="text-right d-none d-lg-table-cell" i18n="dashboard.latest-transactions.amount">Amount</th>
<th *ngIf="(network$ | async) === ''" style="width: 20%;" class="text-right d-none d-lg-table-cell" i18n="dashboard.latest-transactions.USD">USD</th>
<th style="width: 25%;" class="text-right" i18n="dashboard.latest-transactions.fee">Fee</th>
</thead>
<tbody>
<tr *ngFor="let transaction of transactions$ | async; let i = index;">
<td><a [routerLink]="['/tx' | relativeUrl, transaction.txid]">{{ transaction.txid | shortenString : 10 }}</a></td>
<td class="text-right d-none d-lg-table-cell"><app-amount [satoshis]="transaction.value" digitsInfo="1.8-8" [noFiat]="true"></app-amount></td>
<td *ngIf="(network$ | async) === ''" class="text-right d-none d-lg-table-cell"><app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat></td>
<td class="text-right">{{ transaction.fee / (transaction.weight / 4) | number : '1.1-1' }} sat/vB</td>
<td class="text-right">{{ transaction.fee / (transaction.weight / 4) | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
</tr>
</tbody>
</table>
@ -133,8 +133,14 @@
</div>
</button>
<div [formGroup]="languageForm" class="text-small text-center mt-4">
<select formControlName="language" class="form-control form-control-secondary form-control-sm mx-auto" style="width: 130px;" (change)="changeLanguage()">
<option *ngFor="let lang of languages" [value]="lang.code">{{ lang.name }}</option>
</select>
</div>
<div class="text-small text-center mt-3">
<a [routerLink]="['/terms-of-service']">Terms of Service</a>
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
</div>
</div>
@ -153,15 +159,15 @@
<table style="width: 100%;">
<tr>
<td>
<h5 class="card-title">Mempool size</h5>
<h5 class="card-title" i18n="dashboard.mempool-size|Mempool size">Mempool size</h5>
<p class="card-text" *ngIf="(mempoolBlocksData$ | async) as mempoolBlocksData; else loading">
{{ mempoolBlocksData.size | bytes }} ({{ mempoolBlocksData.blocks }} block<span [hidden]="mempoolBlocksData.blocks <= 1">s</span>)
{{ mempoolBlocksData.size | bytes }} ({{ mempoolBlocksData.blocks }} <span [hidden]="mempoolBlocksData.blocks == 1" i18n="dashboard.block">block</span><span [hidden]="mempoolBlocksData.blocks != 1">blocks</span>)
</p>
</td>
<td>
<h5 class="card-title">Unconfirmed</h5>
<h5 class="card-title" i18n="dashboard.unconfirmed|Unconfirmed count">Unconfirmed</h5>
<p class="card-text" *ngIf="mempoolInfoData.value; else loading">
{{ mempoolInfoData.value.memPoolInfo.size | number }} TXs
{{ mempoolInfoData.value.memPoolInfo.size | number }} <span i18n="dashboard.txs">TXs</span>
</p>
</td>
</tr>
@ -169,14 +175,14 @@
</ng-template>
<ng-template #txPerSecond let-mempoolInfoData>
<h5 class="card-title">Incoming transactions</h5>
<h5 class="card-title" i18n="dashboard.incoming-transactions">Incoming transactions</h5>
<ng-template [ngIf]="mempoolInfoData.value" [ngIfElse]="loading">
<span *ngIf="mempoolInfoData.value.vBytesPerSecond === 0; else inSync">
&nbsp;<span class="badge badge-pill badge-warning">Backend is synchronizing</span>
&nbsp;<span class="badge badge-pill badge-warning" i18n="dashboard.backend-is-synchronizing">Backend is synchronizing</span>
</span>
<ng-template #inSync>
<div class="progress sub-text" style="max-width: 250px;">
<div class="progress-bar {{ mempoolInfoData.value.progressClass }}" style="padding: 4px;" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth}">{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} vB/s</div>
<div class="progress-bar {{ mempoolInfoData.value.progressClass }}" style="padding: 4px;" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth}">{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} <ng-template i18n="shared.vbytes-per-second|vB/s">vB/s</ng-template></div>
</div>
</ng-template>
</ng-template>
@ -185,7 +191,7 @@
<ng-template #difficultyEpoch>
<div class="card text-center">
<div class="card-body more-padding">
<h5 class="card-title">Difficulty adjustment</h5>
<h5 class="card-title" i18n="dashboard.difficulty-adjustment">Difficulty adjustment</h5>
<div class="progress" *ngIf="(difficultyEpoch$ | async) as epochData; else loading">
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}"><ng-template [ngIf]="epochData.change > 0">+</ng-template>{{ epochData.change | number: '1.0-2' }}%</div>
<div class="progress-bar bg-success" role="progressbar" style="width: 0%" [ngStyle]="{'width': epochData.green}"></div>
@ -193,4 +199,4 @@
</div>
</div>
</div>
</ng-template>
</ng-template>

View file

@ -7,10 +7,12 @@ import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interf
import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service';
import * as Chartist from '@mempool/chartist';
import { formatDate } from '@angular/common';
import { DOCUMENT, formatDate } from '@angular/common';
import { WebsocketService } from '../services/websocket.service';
import { SeoService } from '../services/seo.service';
import { StorageService } from '../services/storage.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { languages, Language } from '../app.constants';
interface MempoolBlocksData {
blocks: number;
@ -55,6 +57,8 @@ export class DashboardComponent implements OnInit {
mempoolTransactionsWeightPerSecondData: any;
mempoolStats$: Observable<MempoolStatsData>;
transactionsWeightPerSecondOptions: any;
languageForm: FormGroup;
languages: Language[];
constructor(
@Inject(LOCALE_ID) private locale: string,
@ -63,14 +67,22 @@ export class DashboardComponent implements OnInit {
private websocketService: WebsocketService,
private seoService: SeoService,
private storageService: StorageService,
private formBuilder: FormBuilder,
@Inject(DOCUMENT) private document: Document
) { }
ngOnInit(): void {
this.languages = languages;
this.seoService.resetTitle();
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
this.network$ = merge(of(''), this.stateService.networkChanged$);
this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one';
this.languageForm = this.formBuilder.group({
language: ['']
});
this.setLanguageFromUrl();
this.mempoolInfoData$ = combineLatest([
this.stateService.mempoolInfo$,
this.stateService.vbytesPerSecond$
@ -232,4 +244,21 @@ export class DashboardComponent implements OnInit {
}
this.storageService.setValue('dashboard-collapsed', this.collapseLevel);
}
setLanguageFromUrl() {
const urlLanguage = this.document.location.pathname.split('/')[1];
if (urlLanguage === '') {
this.languageForm.get('language').setValue('en');
} else if (this.languages.map((lang) => lang.code).indexOf(urlLanguage) > -1) {
this.languageForm.get('language').setValue(urlLanguage);
}
}
changeLanguage() {
const language = this.languageForm.get('language').value;
this.document.location.href = (language === 'en' ? '/' : '/' + language);
try {
document.cookie = `lang=${language}; expires=Thu, 18 Dec 2050 12:00:00 UTC; path=/`;
} catch (e) { }
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -97,6 +97,15 @@ body {
color: #000;
}
.form-control.form-control-secondary {
color: #fff;
background-color: #2d3348;
border: 1px solid #2d3348;
}
.form-control.form-control-secondary:focus {
color: #fff;
}
.h2-match-table {
padding-left: .65rem;
}

View file

@ -1,7 +1,7 @@
var https = require('https');
var fs = require('fs');
var PATH = 'dist/mempool/browser/resources/';
var PATH = 'dist/mempool/en-US/resources/';
if (process.argv[2] && process.argv[2] === 'dev') {
PATH = 'src/resources/';
}

View file

@ -0,0 +1,200 @@
root /mempool/public_html/mainnet/;
index index.html;
add_header Onion-Location http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion$request_uri;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
set $frameOptions "DENY";
set $contentSecurityPolicy "frame-ancestors 'none'";
if ($http_referer ~ ^https://mempool.space/)
{
set $frameOptions "ALLOW-FROM https://mempool.space";
set $contentSecurityPolicy "frame-ancestors https://mempool.space";
}
if ($http_referer ~ ^https://mempool.ninja/)
{
set $frameOptions "ALLOW-FROM https://mempool.ninja";
set $contentSecurityPolicy "frame-ancestors https://mempool.ninja";
}
if ($http_referer ~ ^https://node100.bitcoin.wiz.biz/)
{
set $frameOptions "ALLOW-FROM https://node100.bitcoin.wiz.biz";
set $contentSecurityPolicy "frame-ancestors https://node100.bitcoin.wiz.biz";
}
if ($http_referer ~ ^https://wiz.biz/)
{
set $frameOptions "ALLOW-FROM https://wiz.biz";
set $contentSecurityPolicy "frame-ancestors https://wiz.biz";
}
add_header X-Frame-Options $frameOptions;
add_header Content-Security-Policy $contentSecurityPolicy;
# fallback for all URLs i.e. /address/foo /tx/foo /block/000
location / {
#return 302 https://mempool.space/$request_uri;
try_files /$lang/$uri /$lang/$uri/ $uri $uri/ /en-US/$uri @index;
# /$lang/index.html /en-US/index.html =404;
}
location @index {
add_header Cache-Control must-revalidate;
try_files /$lang/index.html /en-US/index.html =404;
}
# location block using regex are matched in order
# used to rewrite resources from /<lang>/ to /en-US/
location ~ ^/(ar|bg|bs|ca|cs|da|de|et|el|es|eo|eu|fa|fr|gl|ko|hr|id|it|he|ka|lv|lt|hu|mk|ms|nl|ja|no|nb|nn|pl|pt|pt-BR|ro|ru|sk|sl|sr|sh|fi|sv|th|tr|uk|vi|zh)/resources/ {
rewrite ^/[a-zA-Z-]*/resources/(.*) /en-US/resources/$1;
}
# used for cookie override
location ~ ^/(ar|bg|bs|ca|cs|da|de|et|el|es|eo|eu|fa|fr|gl|ko|hr|id|it|he|ka|lv|lt|hu|mk|ms|nl|ja|no|nb|nn|pl|pt|pt-BR|ro|ru|sk|sl|sr|sh|fi|sv|th|tr|uk|vi|zh)/ {
try_files $uri $uri/ /$1/index.html =404;
}
# add /sitemap for production SEO
location /sitemap {
try_files $uri =410;
}
# old /explorer redirect from v1 days
location /explorer {
rewrite /explorer/(.*) https://$host/$1 permanent;
}
# static API docs
location = /api {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
location = /api/ {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
location = /liquid/api {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
location = /liquid/api/ {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
location = /testnet/api {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
location = /testnet/api/ {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
location = /bisq/api {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
location = /bisq/api/ {
#return 302 https://mempool.space/$request_uri;
try_files $uri $uri/ /en-US/index.html =404;
}
# mainnet API
location /api/v1/donations {
proxy_pass http://127.0.0.1:8999;
# don't rate limit this API prefix
}
location /api/v1/donations/images {
proxy_pass http://127.0.0.1:8999;
proxy_cache cache;
proxy_cache_valid 200 1d;
}
location /api/v1/ws {
proxy_pass http://127.0.0.1:8999/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /api/v1 {
proxy_pass http://127.0.0.1:8999/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /api/ {
proxy_pass http://[::1]:3000/;
limit_req burst=50 nodelay zone=electrs;
}
# liquid API
location /liquid/api/v1/ws {
proxy_pass http://127.0.0.1:8998/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /liquid/api/v1 {
proxy_pass http://127.0.0.1:8998/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /liquid/api/ {
proxy_pass http://[::1]:3001/;
limit_req burst=50 nodelay zone=electrs;
}
# testnet API
location /testnet/api/v1/ws {
proxy_pass http://127.0.0.1:8997/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /testnet/api/v1 {
proxy_pass http://127.0.0.1:8997/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /testnet/api/ {
proxy_pass http://[::1]:3002/;
limit_req burst=50 nodelay zone=electrs;
}
# bisq API
location /bisq/api/v1/ws {
proxy_pass http://127.0.0.1:8996/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /bisq/api/v1/markets {
proxy_pass http://127.0.0.1:8996/api/v1/bisq/markets;
#limit_req burst=50 nodelay zone=api;
}
location /bisq/api/v1 {
proxy_pass http://127.0.0.1:8996/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /bisq/api {
proxy_pass http://127.0.0.1:8996/api/v1/bisq;
limit_req burst=50 nodelay zone=api;
}
# mainnet API
location /ws {
proxy_pass http://127.0.0.1:8999/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /ws/mainnet {
proxy_pass http://127.0.0.1:8999/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /ws/liquid {
proxy_pass http://127.0.0.1:8998/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /ws/testnet {
proxy_pass http://127.0.0.1:8997/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}

View file

@ -1,5 +1,4 @@
user nobody;
pid /var/run/nginx.pid;
worker_processes auto;
@ -38,10 +37,6 @@ http {
# number of requests per connection, does not affect SPDY
keepalive_requests 100;
types_hash_max_size 2048;
proxy_cache off;
# enable gzip compression
gzip on;
gzip_vary on;
@ -55,203 +50,117 @@ http {
client_max_body_size 10m;
# proxy cache
proxy_cache off;
proxy_cache_path /var/cache/nginx keys_zone=cache:20m levels=1:2 inactive=600s max_size=500m;
types_hash_max_size 2048;
# rate limit requests
limit_req_zone $binary_remote_addr zone=api:5m rate=50r/m;
limit_req_zone $binary_remote_addr zone=electrs:5m rate=1000r/m;
limit_req_zone $binary_remote_addr zone=api:5m rate=200r/m;
limit_req_zone $binary_remote_addr zone=electrs:5m rate=2000r/m;
limit_req_status 429;
# rate limit connections
limit_conn_zone $binary_remote_addr zone=websocket:10m;
limit_conn_status 429;
server {
listen 80 backlog=1024;
listen [::]:80 backlog=1024;
map $http_accept_language $header_lang {
default en-US;
~*^en-US en-US;
~*^en en-US;
~*^cs cs;
~*^de de;
~*^es es;
~*^fa fa;
~*^fr fr;
~*^ja ja;
~*^nl nl;
~*^nn nn;
~*^pt pt;
~*^sl sl;
~*^sv sv;
~*^tr tr;
~*^uk uk;
~*^zh zh;
}
server_name mempool.space;
map $cookie_lang $lang {
default $header_lang;
~*^en-US en-US;
~*^en en-US;
~*^cs cs;
~*^de de;
~*^es es;
~*^fa fa;
~*^fr fr;
~*^ja ja;
~*^nl nl;
~*^nn nn;
~*^pt pt;
~*^sl sl;
~*^sv sv;
~*^tr tr;
~*^uk uk;
~*^zh zh;
}
server {
listen 80;
server_name mempool.space mempool.ninja bsq.ninja node100.bitcoin.wiz.biz;
return 301 https://$host$request_uri;
}
server {
listen 127.0.0.1:81 backlog=1024;
listen [::]:443 ssl default http2 backlog=1024;
listen 443 ssl http2;
server_name bsq.ninja;
ssl_certificate /usr/local/etc/letsencrypt/live/bsq.ninja/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/bsq.ninja/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
set $redirect_uri https://mempool.space/bisq;
if ($uri = /tx.html) {
set $redirect_uri https://mempool.space/bisq/tx/$arg_tx;
}
if ($uri = /txo.html) {
set $redirect_uri https://mempool.space/bisq/tx/$arg_txo;
}
if ($uri = /Address.html) {
set $redirect_uri https://mempool.space/bisq/address/$arg_addr;
}
return 301 $redirect_uri;
}
server {
listen 443 ssl http2;
server_name node100.bitcoin.wiz.biz;
ssl_certificate /usr/local/etc/letsencrypt/live/node100.bitcoin.wiz.biz/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/node100.bitcoin.wiz.biz/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
include /usr/local/etc/nginx/nginx-mempool.conf;
}
server {
listen 443 ssl http2;
server_name mempool.ninja;
ssl_certificate /usr/local/etc/letsencrypt/live/mempool.ninja/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/mempool.ninja/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
include /usr/local/etc/nginx/nginx-mempool.conf;
}
server {
listen 127.0.0.1:81;
listen 443 ssl default http2 backlog=1024;
server_name mempool.space;
ssl_certificate /usr/local/etc/letsencrypt/live/mempool.space/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/mempool.space/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
root /mempool/public_html/mainnet/;
index index.html;
# security headers
set $frameOptions "DENY";
set $contentSecurityPolicy "frame-ancestors 'none'";
if ($http_referer ~ ^https://mempool.space/)
{
set $frameOptions "ALLOW-FROM https://mempool.space";
set $contentSecurityPolicy "frame-ancestors https://mempool.space";
}
if ($http_referer ~ ^https://wiz.biz/)
{
set $frameOptions "ALLOW-FROM https://wiz.biz";
set $contentSecurityPolicy "frame-ancestors https://wiz.biz";
}
add_header X-Frame-Options $frameOptions;
add_header Content-Security-Policy $contentSecurityPolicy;
add_header Link "<https://mempool.space$request_uri>; rel=\"canonical\"";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
#add_header Onion-Location http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion$request_uri;
# /
location / {
try_files $uri $uri/ /index.html =404;
}
# # /sitemap
# location /sitemap {
# try_files $uri =410;
# }
#
# # /explorer
# location /explorer {
# rewrite /explorer/(.*) https://$host/$1 permanent;
# }
# /api
location = /api {
try_files $uri $uri/ /index.html =404;
}
location = /api/ {
try_files $uri $uri/ /index.html =404;
}
location /api/v1/donations/images {
# don't rate limit this URL prefix
proxy_pass http://127.0.0.1:8999;
proxy_cache cache;
proxy_cache_valid 200 1d;
}
location /api/v1/ws {
proxy_pass http://127.0.0.1:8999/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
limit_conn websocket 10;
}
location /api/v1 {
proxy_pass http://127.0.0.1:8999/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /api/ {
proxy_pass http://[::1]:3000/;
limit_req burst=100 nodelay zone=electrs;
}
# /mainnet/api
location = /mainnet/api {
try_files $uri $uri/ /index.html =404;
}
location = /mainnet/api/ {
try_files $uri $uri/ /index.html =404;
}
location /mainnet/api/v1/ws {
proxy_pass http://127.0.0.1:8999/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
limit_conn websocket 10;
}
location /mainnet/api/v1 {
proxy_pass http://127.0.0.1:8999/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /mainnet/api/ {
proxy_pass http://[::1]:3000/;
limit_req burst=100 nodelay zone=electrs;
}
# /liquid/api
location = /liquid/api {
try_files $uri $uri/ /index.html =404;
}
location = /liquid/api/ {
try_files $uri $uri/ /index.html =404;
}
location /liquid/api/v1/ws {
proxy_pass http://127.0.0.1:8998/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
limit_conn websocket 10;
}
location /liquid/api/v1 {
proxy_pass http://127.0.0.1:8998/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /liquid/api/ {
proxy_pass http://[::1]:3001/;
limit_req burst=100 nodelay zone=electrs;
}
# /testnet/api
location = /testnet/api {
try_files $uri $uri/ /index.html =404;
}
location = /testnet/api/ {
try_files $uri $uri/ /index.html =404;
}
location /testnet/api/v1/ws {
proxy_pass http://127.0.0.1:8997/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
limit_conn websocket 10;
}
location /testnet/api/v1 {
proxy_pass http://127.0.0.1:8997/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /testnet/api/ {
proxy_pass http://[::1]:3002/;
limit_req burst=100 nodelay zone=electrs;
}
# /bisq
location = /bisq/api {
try_files $uri $uri/ /index.html =404;
}
location = /bisq/api/ {
try_files $uri $uri/ /index.html =404;
}
location /bisq/api/v1/ws {
proxy_pass http://127.0.0.1:8996/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /bisq/api/v1/markets {
proxy_pass http://127.0.0.1:8996/api/v1/bisq/markets;
#limit_req burst=50 nodelay zone=api;
}
location /bisq/api/v1 {
proxy_pass http://127.0.0.1:8996/api/v1;
limit_req burst=50 nodelay zone=api;
}
location /bisq/api {
proxy_pass http://127.0.0.1:8996/api/v1/bisq;
limit_req burst=50 nodelay zone=api;
}
include /usr/local/etc/nginx/nginx-mempool.conf;
}
}

49
production/test-nginx Executable file
View file

@ -0,0 +1,49 @@
#!/usr/bin/env zsh
PROTO=https
HOSTNAME=mempool.ninja
URL_BASE=${PROTO}://${HOSTNAME}
curltest()
{
read output
if [ "${output}" = "$1" ];then
echo "PASS: |${output}|"
else
echo "FAIL: |${output}|"
echo "WANT: |$1|"
exit 1
fi
}
echo "Starting tests to ${URL_BASE}"
echo "Test locale for / with no header or cookie"
curl -s ${URL_BASE}/ | grep '<html lang' | tr -d '\r\n' | curltest '<html lang="en-US">'
echo "Test locale for / with 'ja' lang header and no cookie"
curl -s -H 'Accept-Language: ja' ${URL_BASE}/ | grep '<html lang' | tr -d '\r\n' | curltest '<html lang="ja">'
echo "Test locale for / with 'ja' lang header and 'en' lang cookie"
curl -s -H 'Accept-Language: ja' --cookie 'lang=en' ${URL_BASE}/ | grep '<html lang' | tr -d '\r\n' | curltest '<html lang="en-US">'
echo "Test locale for / with 'ja' lang header and 'sv' lang cookie"
curl -s -H 'Accept-Language: ja' --cookie 'lang=sv' ${URL_BASE}/ | grep '<html lang' | tr -d '\r\n' | curltest '<html lang="sv">'
echo "Test locale for / with 'ja' lang header and 'foo' lang cookie"
curl -s -H 'Accept-Language: ja' --cookie 'lang=foo' ${URL_BASE}/ | grep '<html lang' | tr -d '\r\n' | curltest '<html lang="ja">'
echo "Test rewrite for /resources/pools.json with no header and no cookie"
curl -s -i ${URL_BASE}/resources/pools.json | grep -i content-type | tr -d '\r\n' | curltest 'content-type: application/json'
echo "Test rewrite for /sv/resources/pools.json with no header and no cookie"
curl -s -i ${URL_BASE}/sv/resources/pools.json | grep -i content-type | tr -d '\r\n' | curltest 'content-type: application/json'
echo "Test rewrite for /resources/pools.json with 'ja' lang header and no cookie"
curl -s -i -H 'Accept-Language: ja' ${URL_BASE}/resources/pools.json | grep -i content-type | tr -d '\r\n' | curltest 'content-type: application/json'
echo "Test rewrite for /ja/resources/pools.json with 'ja' lang header and no cookie"
curl -s -i -H 'Accept-Language: ja' ${URL_BASE}/ja/resources/pools.json | grep -i content-type | tr -d '\r\n' | curltest 'content-type: application/json'
#curl -s -i -H 'Accept-Language: sv' ${URL_BASE}/ja/resources/pools.json | grep -i content-type
#curl -s -i -H 'Accept-Language: foo' --cookie 'lang=sv' ${URL_BASE}/ja/resources/pools.json | grep -i content-type
#curl -s -i -H 'Accept-Language: foo' --cookie 'lang=sv' ${URL_BASE}/sv/resources/pools.json | grep -i content-type