diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts
index 358a98c98..af93b9622 100644
--- a/backend/src/api/common.ts
+++ b/backend/src/api/common.ts
@@ -263,8 +263,13 @@ export class Common {
case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break;
case 'v1_p2tr': {
flags |= TransactionFlags.p2tr;
- if (vin.witness.length > 2) {
- const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - 2]);
+ // in taproot, if the last witness item begins with 0x50, it's an annex
+ const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50');
+ // script spends have more than one witness item, not counting the annex (if present)
+ if (vin.witness.length > (hasAnnex ? 2 : 1)) {
+ // the script itself is the second-to-last witness item, not counting the annex
+ const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - (hasAnnex ? 3 : 2)]);
+ // inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope
if (asm?.includes('OP_0 OP_IF')) {
flags |= TransactionFlags.inscription;
}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 59bdac54a..f49fd614a 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -58,7 +58,7 @@
"optionalDependencies": {
"@cypress/schematic": "^2.5.0",
"@types/cypress": "^1.1.3",
- "cypress": "^13.6.0",
+ "cypress": "^13.6.2",
"cypress-fail-on-console-error": "~5.1.0",
"cypress-wait-until": "^2.0.1",
"mock-socket": "~9.3.1",
@@ -7083,9 +7083,9 @@
"peer": true
},
"node_modules/cypress": {
- "version": "13.6.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.0.tgz",
- "integrity": "sha512-quIsnFmtj4dBUEJYU4OH0H12bABJpSujvWexC24Ju1gTlKMJbeT6tTO0vh7WNfiBPPjoIXLN+OUqVtiKFs6SGw==",
+ "version": "13.6.2",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.2.tgz",
+ "integrity": "sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
@@ -22272,9 +22272,9 @@
"peer": true
},
"cypress": {
- "version": "13.6.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.0.tgz",
- "integrity": "sha512-quIsnFmtj4dBUEJYU4OH0H12bABJpSujvWexC24Ju1gTlKMJbeT6tTO0vh7WNfiBPPjoIXLN+OUqVtiKFs6SGw==",
+ "version": "13.6.2",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.2.tgz",
+ "integrity": "sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==",
"optional": true,
"requires": {
"@cypress/request": "^3.0.0",
diff --git a/frontend/package.json b/frontend/package.json
index 8dbcbcf3e..330250871 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -110,7 +110,7 @@
"optionalDependencies": {
"@cypress/schematic": "^2.5.0",
"@types/cypress": "^1.1.3",
- "cypress": "^13.6.0",
+ "cypress": "^13.6.2",
"cypress-fail-on-console-error": "~5.1.0",
"cypress-wait-until": "^2.0.1",
"mock-socket": "~9.3.1",
diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html
index b34b39c8c..1e6edd2c8 100644
--- a/frontend/src/app/components/block/block.component.html
+++ b/frontend/src/app/components/block/block.component.html
@@ -47,7 +47,7 @@
@@ -233,7 +233,9 @@
-
+
+
+
@@ -245,7 +247,9 @@
-
+
+
+
@@ -452,5 +456,24 @@
+
+
+
+
+ Total fees |
+ |
+
+
+ Weight |
+ |
+
+
+ Transactions |
+ |
+
+
+
+
+
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html
index 85e2ea17f..838c7cb4e 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.html
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.html
@@ -46,7 +46,7 @@
- {{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
+ {{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
|
{{ block.height }}
|
- {{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
+ {{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
|
diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index 446d8be5a..2a9983a24 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -2,13 +2,14 @@ import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewC
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { EventType, NavigationStart, Router } from '@angular/router';
import { AssetsService } from '../../services/assets.service';
-import { StateService } from '../../services/state.service';
+import { Env, StateService } from '../../services/state.service';
import { Observable, of, Subject, zip, BehaviorSubject, combineLatest } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap, catchError, map, startWith, tap } from 'rxjs/operators';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { ApiService } from '../../services/api.service';
import { SearchResultsComponent } from './search-results/search-results.component';
+import { Network, findOtherNetworks, getRegex, getTargetUrl, needBaseModuleChange } from '../../shared/regex.utils';
@Component({
selector: 'app-search-form',
@@ -18,7 +19,7 @@ import { SearchResultsComponent } from './search-results/search-results.componen
})
export class SearchFormComponent implements OnInit {
@Input() hamburgerOpen = false;
-
+ env: Env;
network = '';
assets: object = {};
isSearching = false;
@@ -36,12 +37,13 @@ export class SearchFormComponent implements OnInit {
}
}
- regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[A-z]{2,5}1[a-zA-HJ-NP-Z0-9]{39,59}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64})$/;
- regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
- regexTransaction = /^([a-fA-F0-9]{64})(:\d+)?$/;
- regexBlockheight = /^[0-9]{1,9}$/;
- regexDate = /^(?:\d{4}[-/]\d{1,2}[-/]\d{1,2}(?: \d{1,2}:\d{2})?)$/;
- regexUnixTimestamp = /^\d{10}$/;
+ regexAddress = getRegex('address', 'mainnet'); // Default to mainnet
+ regexBlockhash = getRegex('blockhash', 'mainnet');
+ regexTransaction = getRegex('transaction');
+ regexBlockheight = getRegex('blockheight');
+ regexDate = getRegex('date');
+ regexUnixTimestamp = getRegex('timestamp');
+
focus$ = new Subject();
click$ = new Subject();
@@ -66,8 +68,14 @@ export class SearchFormComponent implements OnInit {
}
ngOnInit(): void {
- this.stateService.networkChanged$.subscribe((network) => this.network = network);
-
+ this.env = this.stateService.env;
+ this.stateService.networkChanged$.subscribe((network) => {
+ this.network = network;
+ // TODO: Eventually change network type here from string to enum of consts
+ this.regexAddress = getRegex('address', network as any || 'mainnet');
+ this.regexBlockhash = getRegex('blockhash', network as any || 'mainnet');
+ });
+
this.router.events.subscribe((e: NavigationStart) => { // Reset search focus when changing page
if (this.searchInput && e.type === EventType.NavigationStart) {
this.searchInput.nativeElement.blur();
@@ -96,9 +104,6 @@ export class SearchFormComponent implements OnInit {
const searchText$ = this.searchForm.get('searchText').valueChanges
.pipe(
map((text) => {
- if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) {
- return text.substr(1);
- }
return text.trim();
}),
tap((text) => {
@@ -132,9 +137,6 @@ export class SearchFormComponent implements OnInit {
);
}),
map((result: any[]) => {
- if (this.network === 'bisq') {
- result[0] = result[0].map((address: string) => 'B' + address);
- }
return result;
}),
tap(() => {
@@ -164,6 +166,7 @@ export class SearchFormComponent implements OnInit {
blockHeight: false,
txId: false,
address: false,
+ otherNetworks: [],
addresses: [],
nodes: [],
channels: [],
@@ -174,15 +177,21 @@ export class SearchFormComponent implements OnInit {
const addressPrefixSearchResults = result[0];
const lightningResults = result[1];
+ // Do not show date and timestamp results for liquid and bisq
+ const isNetworkBitcoin = this.network === '' || this.network === 'testnet' || this.network === 'signet';
+
const matchesBlockHeight = this.regexBlockheight.test(searchText) && parseInt(searchText) <= this.stateService.latestBlockHeight;
- const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date';
- const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText);
+ const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date' && new Date(searchText).getTime() <= Date.now() && isNetworkBitcoin;
+ const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText) && parseInt(searchText) <= Math.floor(Date.now() / 1000) && isNetworkBitcoin;
const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText);
const matchesBlockHash = this.regexBlockhash.test(searchText);
- const matchesAddress = !matchesTxId && this.regexAddress.test(searchText);
+ let matchesAddress = !matchesTxId && this.regexAddress.test(searchText);
+ const otherNetworks = findOtherNetworks(searchText, this.network as any || 'mainnet', this.env);
- if (matchesAddress && this.network === 'bisq') {
- searchText = 'B' + searchText;
+ // Add B prefix to addresses in Bisq network
+ if (!matchesAddress && this.network === 'bisq' && getRegex('address', 'mainnet').test(searchText)) {
+ searchText = 'B' + searchText;
+ matchesAddress = !matchesTxId && this.regexAddress.test(searchText);
}
if (matchesDateTime && searchText.indexOf('/') !== -1) {
@@ -198,7 +207,8 @@ export class SearchFormComponent implements OnInit {
txId: matchesTxId,
blockHash: matchesBlockHash,
address: matchesAddress,
- addresses: addressPrefixSearchResults,
+ addresses: matchesAddress && addressPrefixSearchResults.length === 1 && searchText === addressPrefixSearchResults[0] ? [] : addressPrefixSearchResults, // If there is only one address and it matches the search text, don't show it in the dropdown
+ otherNetworks: otherNetworks,
nodes: lightningResults.nodes,
channels: lightningResults.channels,
};
@@ -223,6 +233,15 @@ export class SearchFormComponent implements OnInit {
this.navigate('/lightning/node/', result.public_key);
} else if (result.short_id) {
this.navigate('/lightning/channel/', result.id);
+ } else if (result.network) {
+ if (result.isNetworkAvailable) {
+ this.navigate('/address/', result.address, undefined, result.network);
+ } else {
+ this.searchForm.setValue({
+ searchText: '',
+ });
+ this.isSearching = false;
+ }
}
}
@@ -230,6 +249,7 @@ export class SearchFormComponent implements OnInit {
const searchText = result || this.searchForm.value.searchText.trim();
if (searchText) {
this.isSearching = true;
+
if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) {
this.navigate('/address/', searchText);
} else if (this.regexBlockhash.test(searchText)) {
@@ -258,6 +278,11 @@ export class SearchFormComponent implements OnInit {
} else if (this.regexDate.test(searchText) || this.regexUnixTimestamp.test(searchText)) {
let timestamp: number;
this.regexDate.test(searchText) ? timestamp = Math.floor(new Date(searchText).getTime() / 1000) : timestamp = searchText;
+ // Check if timestamp is too far in the future or before the genesis block
+ if (timestamp > Math.floor(Date.now() / 1000)) {
+ this.isSearching = false;
+ return;
+ }
this.apiService.getBlockDataFromTimestamp$(timestamp).subscribe(
(data) => { this.navigate('/block/', data.hash); },
(error) => { console.log(error); this.isSearching = false; }
@@ -269,12 +294,17 @@ export class SearchFormComponent implements OnInit {
}
}
- navigate(url: string, searchText: string, extras?: any): void {
- this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras);
- this.searchTriggered.emit();
- this.searchForm.setValue({
- searchText: '',
- });
- this.isSearching = false;
+
+ navigate(url: string, searchText: string, extras?: any, swapNetwork?: string) {
+ if (needBaseModuleChange(this.env.BASE_MODULE as 'liquid' | 'bisq' | 'mempool', swapNetwork as Network)) {
+ window.location.href = getTargetUrl(swapNetwork as Network, searchText, this.env);
+ } else {
+ this.router.navigate([this.relativeUrlPipe.transform(url, swapNetwork), searchText], extras);
+ this.searchTriggered.emit();
+ this.searchForm.setValue({
+ searchText: '',
+ });
+ this.isSearching = false;
+ }
}
}
diff --git a/frontend/src/app/components/search-form/search-results/search-results.component.html b/frontend/src/app/components/search-form/search-results/search-results.component.html
index 6c096fc4e..5d64b7495 100644
--- a/frontend/src/app/components/search-form/search-results/search-results.component.html
+++ b/frontend/src/app/components/search-form/search-results/search-results.component.html
@@ -1,4 +1,4 @@
- |