mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Merge branch 'master' into dependabot/npm_and_yarn/frontend/cypress-15.7.0
This commit is contained in:
commit
7c23c8427d
36 changed files with 4915 additions and 10192 deletions
8587
backend/package-lock.json
generated
8587
backend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -59,15 +59,19 @@
|
|||
"@types/compression": "^1.7.2",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/ws": "~8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||
"@typescript-eslint/parser": "^5.55.0",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest": "^30.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-jest": "^29.4.5",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"overrides": {
|
||||
"js-yaml": "^4.1.1",
|
||||
"glob": "^11.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,55 @@
|
|||
[
|
||||
{
|
||||
"txid": "50136231cb7eeeffb17fc41d1cca213426abe5bf3760e3d6421cad0c0edad367",
|
||||
"version": 1,
|
||||
"locktime": 0,
|
||||
"vin": [
|
||||
{
|
||||
"txid": "c7f86fb7b830124057475b282809f3474ef3565daa3de0b599980fb9e84ab019",
|
||||
"vout": 4217,
|
||||
"prevout": {
|
||||
"scriptpubkey": "001466197b5eadd8067ec194a457e1044b6d1fbdd3b3",
|
||||
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 66197b5eadd8067ec194a457e1044b6d1fbdd3b3",
|
||||
"scriptpubkey_type": "v0_p2wpkh",
|
||||
"scriptpubkey_address": "bc1qvcvhkh4dmqr8asv553t7zpztd50mm5ang4na33",
|
||||
"value": 106
|
||||
},
|
||||
"scriptsig": "",
|
||||
"scriptsig_asm": "",
|
||||
"witness": [
|
||||
"3043021f2af6060a142c6cfd7428adad6a50745d2424813d7ced5c0bbcca85e70de1be022021440ca1c8c3ed49ecd1b64dca6911adcd430c5d3dd60d77ffe0072953999f5b01",
|
||||
"02ead5c34e3d2c506574b562f857576e11380b6ba15d9f0ad7b7303fdaa9c1513d"
|
||||
],
|
||||
"is_coinbase": false,
|
||||
"sequence": 4294967295
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"scriptpubkey": "6a023a29",
|
||||
"scriptpubkey_asm": "OP_RETURN OP_PUSHBYTES_2 3a29",
|
||||
"scriptpubkey_type": "op_return",
|
||||
"value": 0
|
||||
{
|
||||
"txid": "f859a4e6692c3f15a279449eac191ecb9d17d2d6c003bbae9a55e5da07147eab",
|
||||
"version": 1,
|
||||
"locktime": 0,
|
||||
"size": 170,
|
||||
"weight": 371,
|
||||
"fee": 11586,
|
||||
"vin": [
|
||||
{
|
||||
"is_coinbase": false,
|
||||
"prevout": {
|
||||
"value": 11586,
|
||||
"scriptpubkey": "512088939b42f2ed113dd4756055b179cd784c2cfa52cad29046411c28335aa2055c",
|
||||
"scriptpubkey_address": "bc1p3zfekshja5gnm4r4vp2mz7wd0pxze7jjetffq3jprs5rxk4zq4wqesykl2",
|
||||
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 88939b42f2ed113dd4756055b179cd784c2cfa52cad29046411c28335aa2055c",
|
||||
"scriptpubkey_type": "v1_p2tr"
|
||||
},
|
||||
"scriptsig": "",
|
||||
"scriptsig_asm": "",
|
||||
"sequence": 4294967295,
|
||||
"txid": "9b93dab94a3334b6119f75d107da6bcef9435fa52ec6c0de14aa8a094794551a",
|
||||
"vout": 0,
|
||||
"witness": [
|
||||
"6840b6fa27a00ba001cc92797ce4f3ab7b7a32c21d1fce49e893b42e506bd92e8db187966a84ef799915cf671334cc59779915b192bfb66b2afcf384bb61d0f4",
|
||||
"500049276d20616e20616e6e6578212041726520796f7520616e20616e6e65783f00"
|
||||
],
|
||||
"inner_redeemscript_asm": "",
|
||||
"inner_witnessscript_asm": ""
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"value": 0,
|
||||
"scriptpubkey": "6a05616e6e6578",
|
||||
"scriptpubkey_address": "",
|
||||
"scriptpubkey_asm": "OP_RETURN OP_PUSHBYTES_5 616e6e6578",
|
||||
"scriptpubkey_type": "op_return"
|
||||
}
|
||||
],
|
||||
"status": {
|
||||
"confirmed": true,
|
||||
"block_height": 896088,
|
||||
"block_hash": "0000000000000000000148370c6ad0eceb23d0de54ea86a362679cee7fcd3f4a",
|
||||
"block_time": 1746868490
|
||||
},
|
||||
{
|
||||
"scriptpubkey": "6a036d7648",
|
||||
"scriptpubkey_asm": "OP_RETURN OP_PUSHBYTES_3 6d7648",
|
||||
"scriptpubkey_type": "op_return",
|
||||
"value": 0
|
||||
}
|
||||
],
|
||||
"size": 186,
|
||||
"weight": 420,
|
||||
"sigops": 1,
|
||||
"fee": 106,
|
||||
"status": {
|
||||
"confirmed": true,
|
||||
"block_height": 836361,
|
||||
"block_hash": "0000000000000000000341cc26cda4af82cd25f7063c448772228cbf2836915b",
|
||||
"block_time": 1711448028
|
||||
"order": 2877166599,
|
||||
"vsize": 93,
|
||||
"adjustedVsize": 92.75,
|
||||
"sigops": 0,
|
||||
"feePerVsize": 124.91644204851752,
|
||||
"adjustedFeePerVsize": 124.91644204851752,
|
||||
"effectiveFeePerVsize": 124.91644204851752
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -24,6 +24,7 @@ const MAX_STANDARD_SCRIPTSIG_SIZE = 1650;
|
|||
const DUST_RELAY_TX_FEE = 3;
|
||||
const MAX_OP_RETURN_RELAY = 83;
|
||||
const DEFAULT_PERMIT_BAREMULTISIG = true;
|
||||
const MAX_TX_LEGACY_SIGOPS = 2_500 * 4; // witness-adjusted sigops
|
||||
|
||||
export class Common {
|
||||
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
|
||||
|
|
@ -224,6 +225,11 @@ export class Common {
|
|||
return true;
|
||||
}
|
||||
|
||||
// legacy sigops
|
||||
if (this.isNonStandardLegacySigops(tx, height)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// input validation
|
||||
for (const vin of tx.vin) {
|
||||
if (vin.is_coinbase) {
|
||||
|
|
@ -285,6 +291,7 @@ export class Common {
|
|||
|
||||
// output validation
|
||||
let opreturnCount = 0;
|
||||
let opreturnBytes = 0;
|
||||
for (const vout of tx.vout) {
|
||||
// scriptpubkey
|
||||
if (['nonstandard', 'provably_unspendable', 'empty'].includes(vout.scriptpubkey_type)) {
|
||||
|
|
@ -308,10 +315,7 @@ export class Common {
|
|||
}
|
||||
} else if (vout.scriptpubkey_type === 'op_return') {
|
||||
opreturnCount++;
|
||||
if ((vout.scriptpubkey.length / 2) > MAX_OP_RETURN_RELAY) {
|
||||
// over default datacarrier limit
|
||||
return true;
|
||||
}
|
||||
opreturnBytes += vout.scriptpubkey.length / 2;
|
||||
}
|
||||
// dust
|
||||
// (we could probably hardcode this for the different output types...)
|
||||
|
|
@ -333,9 +337,11 @@ export class Common {
|
|||
}
|
||||
}
|
||||
|
||||
// multi-op-return
|
||||
if (opreturnCount > 1) {
|
||||
return true;
|
||||
// op_return
|
||||
if (opreturnCount > 0) {
|
||||
if (!this.isStandardOpReturn(opreturnBytes, opreturnCount, height)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: non-mandatory-script-verify-flag
|
||||
|
|
@ -428,6 +434,49 @@ export class Common {
|
|||
return false;
|
||||
}
|
||||
|
||||
// OP_RETURN size & count limits were lifted in v28.3/v29.2/v30.0
|
||||
static OP_RETURN_STANDARDNESS_ACTIVATION_HEIGHT = {
|
||||
'testnet4': 108_000,
|
||||
'testnet': 4_750_000,
|
||||
'signet': 276_500,
|
||||
'': 921_000,
|
||||
};
|
||||
static MAX_DATACARRIER_BYTES = 83;
|
||||
static isStandardOpReturn(bytes: number, outputs: number,height?: number): boolean {
|
||||
if (
|
||||
(height == null || (
|
||||
this.OP_RETURN_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
|
||||
&& height >= this.OP_RETURN_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
|
||||
)) // limits lifted
|
||||
|| // OR
|
||||
(bytes <= this.MAX_DATACARRIER_BYTES && outputs <= 1) // below old limits
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// New legacy sigops limit started to be enforced in v30.0
|
||||
static LEGACY_SIGOPS_STANDARDNESS_ACTIVATION_HEIGHT = {
|
||||
'testnet4': 108_000,
|
||||
'testnet': 4_750_000,
|
||||
'signet': 276_500,
|
||||
'': 921_000,
|
||||
};
|
||||
static isNonStandardLegacySigops(tx: TransactionExtended, height?: number): boolean {
|
||||
if (
|
||||
height == null || (
|
||||
this.LEGACY_SIGOPS_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
|
||||
&& height >= this.LEGACY_SIGOPS_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
|
||||
)
|
||||
) {
|
||||
if (!transactionUtils.checkSigopsBIP54(tx, MAX_TX_LEGACY_SIGOPS)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static getNonWitnessSize(tx: TransactionExtended): number {
|
||||
let weight = tx.weight;
|
||||
let hasWitness = false;
|
||||
|
|
|
|||
|
|
@ -145,6 +145,9 @@ class TransactionUtils {
|
|||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the witness-adjusted sigops cost of an asm script
|
||||
*/
|
||||
public countScriptSigops(script: string, isRawScript: boolean = false, witness: boolean = false): number {
|
||||
if (!script?.length) {
|
||||
return 0;
|
||||
|
|
@ -213,6 +216,41 @@ class TransactionUtils {
|
|||
return sigops;
|
||||
}
|
||||
|
||||
/**
|
||||
* see https://github.com/bitcoin/bitcoin/blob/25c45bb0d0bd6618ec9296a1a43605657124e5de/src/policy/policy.cpp#L166-L193
|
||||
* returns true if the transactions is permitted under bip54 sigops rules
|
||||
*
|
||||
* "Unlike the existing block wide sigop limit which counts sigops present in the block
|
||||
* itself (including the scriptPubKey which is not executed until spending later), BIP54
|
||||
* counts sigops in the block where they are potentially executed (only).
|
||||
* This means sigops in the spent scriptPubKey count toward the limit.
|
||||
* `fAccurate` means correctly accounting sigops for CHECKMULTISIGs(VERIFY) with 16 pubkeys
|
||||
* or fewer. This method of accounting was introduced by BIP16, and BIP54 reuses it.
|
||||
* The GetSigOpCount call on the previous scriptPubKey counts both bare and P2SH sigops."
|
||||
*/
|
||||
public checkSigopsBIP54(tx: TransactionExtended, limit): boolean {
|
||||
let sigops = 0;
|
||||
for (const input of tx.vin) {
|
||||
if (input.scriptsig_asm) {
|
||||
sigops += this.countScriptSigops(input.scriptsig_asm);
|
||||
}
|
||||
if (input.prevout) {
|
||||
// P2SH redeem script
|
||||
if (input.prevout.scriptpubkey_type === 'p2sh' && input.inner_redeemscript_asm) {
|
||||
sigops += this.countScriptSigops(input.inner_redeemscript_asm);
|
||||
} else {
|
||||
// prevout scriptpubkey
|
||||
sigops += this.countScriptSigops(input.prevout.scriptpubkey_asm);
|
||||
}
|
||||
}
|
||||
|
||||
if (sigops > limit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns the most significant 4 bytes of the txid as an integer
|
||||
public txidToOrdering(txid: string): number {
|
||||
return parseInt(
|
||||
|
|
|
|||
|
|
@ -181,8 +181,8 @@
|
|||
"inject": false
|
||||
},
|
||||
{
|
||||
"input": "src/theme-wiz.scss",
|
||||
"bundleName": "wiz",
|
||||
"input": "src/theme-softsimon.scss",
|
||||
"bundleName": "softsimon",
|
||||
"inject": false
|
||||
},
|
||||
{
|
||||
|
|
|
|||
5641
frontend/package-lock.json
generated
5641
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -32,13 +32,10 @@
|
|||
"start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora",
|
||||
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod",
|
||||
"start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed",
|
||||
"build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets && npm run build-mempool.js",
|
||||
"build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets",
|
||||
"sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'",
|
||||
"sync-assets-dev": "node sync-assets.js 'src/resources/'",
|
||||
"generate-config": "node generate-config.js",
|
||||
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js",
|
||||
"build-mempool-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
|
||||
"build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
|
||||
"test": "npm run ng -- test",
|
||||
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
||||
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
|
||||
|
|
@ -60,28 +57,26 @@
|
|||
"cypress:run:ci:parameterized": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:parameterized 4200 cypress:run:record"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular-devkit/build-angular": "^20.3.7",
|
||||
"@angular/animations": "^20.3.9",
|
||||
"@angular/cli": "^20.3.7",
|
||||
"@angular/common": "^20.3.9",
|
||||
"@angular/compiler": "^20.3.9",
|
||||
"@angular/core": "^20.3.9",
|
||||
"@angular/forms": "^20.3.9",
|
||||
"@angular/localize": "^20.3.9",
|
||||
"@angular/platform-browser": "^20.3.9",
|
||||
"@angular/platform-browser-dynamic": "^20.3.9",
|
||||
"@angular/platform-server": "^20.3.9",
|
||||
"@angular/router": "^20.3.9",
|
||||
"@angular/ssr": "^20.3.7",
|
||||
"@angular-devkit/build-angular": "^20.3.12",
|
||||
"@angular/animations": "^20.3.14",
|
||||
"@angular/cli": "^20.3.12",
|
||||
"@angular/common": "^20.3.14",
|
||||
"@angular/compiler": "^20.3.14",
|
||||
"@angular/core": "^20.3.14",
|
||||
"@angular/forms": "^20.3.14",
|
||||
"@angular/localize": "^20.3.14",
|
||||
"@angular/platform-browser": "^20.3.14",
|
||||
"@angular/platform-browser-dynamic": "^20.3.14",
|
||||
"@angular/platform-server": "^20.3.14",
|
||||
"@angular/router": "^20.3.14",
|
||||
"@angular/ssr": "^20.3.12",
|
||||
"@fortawesome/angular-fontawesome": "^3.0.0",
|
||||
"@fortawesome/fontawesome-common-types": "~6.7.2",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.7.2",
|
||||
"@mempool/mempool.js": "2.3.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^19.0.0",
|
||||
"@types/qrcode": "~1.5.0",
|
||||
"bootstrap": "~4.6.2",
|
||||
"browserify": "^17.0.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"domino": "^2.1.6",
|
||||
"echarts": "~5.4.0",
|
||||
|
|
@ -90,14 +85,13 @@
|
|||
"qrcode": "1.5.1",
|
||||
"rxjs": "~7.8.1",
|
||||
"esbuild": "^0.25.8",
|
||||
"tinyify": "^4.0.0",
|
||||
"tlite": "^0.1.9",
|
||||
"tslib": "~2.8.0",
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "^20.3.9",
|
||||
"@angular/language-service": "^20.3.9",
|
||||
"@angular/compiler-cli": "^20.3.14",
|
||||
"@angular/language-service": "^20.3.14",
|
||||
"@types/node": "^24.9.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||
"@typescript-eslint/parser": "^8.46.2",
|
||||
|
|
|
|||
|
|
@ -427,16 +427,11 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="maintainers" id="project-maintainers">
|
||||
<h3 i18n="about.maintainers">Project Maintainers</h3>
|
||||
<div class="managers" id="project-managers">
|
||||
<h3 i18n="about.project-manager">Powered By</h3>
|
||||
<div class="wrapper">
|
||||
<a href="https://x.com/softsimon_" target="_blank" title="softsimon">
|
||||
<img class="image" src="/resources/profile/softsimon.jpg" />
|
||||
<span>softsimon</span>
|
||||
</a>
|
||||
<a href="https://x.com/wiz" target="_blank" title="wiz">
|
||||
<img class="image" src="/resources/profile/wiz.png" />
|
||||
<span>wiz</span>
|
||||
<a href="https://wiz.biz" target="_blank" title="wiz & associates">
|
||||
<img class="wiz-logo" src="/resources/wiz.png" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -444,7 +439,7 @@
|
|||
<div class="copyright">
|
||||
<div class="title">
|
||||
Copyright © 2019-2025<br>
|
||||
Mempool Space K.K.<br>
|
||||
Mempool Holdings S.A. de C.V.<br>
|
||||
and other shadowy super-coders
|
||||
</div>
|
||||
<p>
|
||||
|
|
@ -460,7 +455,7 @@
|
|||
Trademark Notice<br>
|
||||
</div>
|
||||
<p>
|
||||
The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Wallet™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
|
||||
The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Wallet™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are either registered trademarks or trademarks of Mempool Holdings S.A. de C.V. in Japan, the United States, and/or other countries.
|
||||
</p>
|
||||
<p>
|
||||
While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our <a href="https://mempool.space/trademark-policy">Trademark Policy and Guidelines</a> for more details, published on <https://mempool.space/trademark-policy>.
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@
|
|||
.alliances,
|
||||
.enterprise-sponsor,
|
||||
.community-integrations-sponsor,
|
||||
.maintainers {
|
||||
.maintainers,
|
||||
.managers {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 68px;
|
||||
scroll-margin: 30px;
|
||||
|
|
@ -67,7 +68,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.maintainers {
|
||||
.maintainers,
|
||||
.managers {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
|
|
@ -259,6 +261,12 @@
|
|||
height: 64px;
|
||||
}
|
||||
|
||||
.wiz-logo {
|
||||
width: 260px;
|
||||
max-width: 90vw;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.enterprise-sponsor {
|
||||
.wrapper {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -285,8 +285,8 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
this.isLoadingTransactions = false;
|
||||
|
||||
let addressVin: Vin[] = [];
|
||||
let vinIds: string[] = [];
|
||||
const addressVin: Vin[] = [];
|
||||
const vinIds: string[] = [];
|
||||
for (const tx of this.transactions) {
|
||||
tx.vin.forEach((v, index) => {
|
||||
if (v.prevout?.scriptpubkey_address === this.address.address) {
|
||||
|
|
@ -296,7 +296,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
this.addressTypeInfo.processInputs(addressVin, vinIds);
|
||||
this.hasTapTree = this.addressTypeInfo.tapscript && this.addressTypeInfo.scripts.values().next().value.scriptPath.length / 2 > 33;
|
||||
this.hasTapTree = this.addressTypeInfo.tapscript && this.addressTypeInfo.scripts.values().next().value.taprootInfo.scriptPath.merkleBranches.length > 0;
|
||||
// hack to trigger change detection
|
||||
this.addressTypeInfo = this.addressTypeInfo.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
<br>
|
||||
|
||||
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, the <a href="https://bitcoin.gob.sv/">bitcoin.gob.sv</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
||||
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, the <a href="https://bitcoin.gob.sv/">bitcoin.gob.sv</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Holdings S.A. de C.V. in El Salvador ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
||||
|
||||
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
||||
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Holdings S.A. de C.V. in El Salvador ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
||||
|
||||
<br>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<ng-container *ngIf="chainTips$ | async as chainTips">
|
||||
<div *ngFor="let chainTip of chainTips" class="chain-tip">
|
||||
<p class="info">
|
||||
<span class="height"><a [routerLink]="['/block', chainTip.canonical?.id || chainTip.height]">{{ chainTip.height }}</a></span>
|
||||
<span class="height"><a [routerLink]="[('/block/' + (chainTip.canonical?.id || chainTip.height)) | relativeUrl]">{{ chainTip.height }}</a></span>
|
||||
<span class="badges">
|
||||
<span class="type" [ngSwitch]="chainTip.status">
|
||||
<span *ngSwitchCase="'headers-only'" class="badge badge-info" i18n="chain-tips.headers-only">Headers Only</span>
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
<div class="block-column stale">
|
||||
<div class="block-label" i18n="chain-tips.stale-block"><span class="badge badge-secondary">Stale</span></div>
|
||||
<div class="block-wrapper">
|
||||
<div class="bitcoin-block mined-block stale-block" [style.background]="getBlockGradient(chainTip.stale)" [routerLink]="['/block/' + chainTip.stale.id]"></div>
|
||||
<div class="bitcoin-block mined-block stale-block" [style.background]="getBlockGradient(chainTip.stale)" [routerLink]="[('/block/' + chainTip.stale.id) | relativeUrl]"></div>
|
||||
<div class="block-body">
|
||||
<div *ngIf="chainTip.stale?.extras; else emptyStaleBlockFees" class="fees">
|
||||
~<app-fee-rate [fee]="chainTip.stale.extras.medianFee" unitClass="" rounding="1.0-0"></app-fee-rate>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
|
||||
<div class="block-column canonical">
|
||||
<div class="block-label" i18n="chain-tips.winning-block"><span class="badge badge-success">Winning</span></div>
|
||||
<div class="bitcoin-block mined-block canonical-block" [style.background]="getBlockGradient(chainTip.canonical)" [routerLink]="['/block/' + chainTip.canonical.id]">
|
||||
<div class="bitcoin-block mined-block canonical-block" [style.background]="getBlockGradient(chainTip.canonical)" [routerLink]="[('/block/' + chainTip.canonical.id) | relativeUrl]">
|
||||
<div class="block-body">
|
||||
<div *ngIf="chainTip.canonical?.extras; else emptyCanonicalBlockFees" class="fees">
|
||||
~<app-fee-rate [fee]="chainTip.canonical.extras.medianFee" unitClass="" rounding="1.0-0"></app-fee-rate>
|
||||
|
|
|
|||
|
|
@ -78,16 +78,12 @@ export class TaprootAddressScriptsComponent implements OnChanges {
|
|||
const merklePaths: { leafVersion: number, merklePath: string[] }[] = [];
|
||||
this.depth = 0;
|
||||
for (const script of scripts) {
|
||||
const controlBlock = script.scriptPath;
|
||||
const m = ((controlBlock.length / 2) - 33) / 32;
|
||||
if (!Number.isInteger(m) || m <= 0) {
|
||||
const scriptInfo = script.taprootInfo;
|
||||
if (!scriptInfo.scriptPath?.merkleBranches?.length) {
|
||||
throw new Error("Merkle path length must be >= 1");
|
||||
}
|
||||
const leafVersion = parseInt(controlBlock.slice(0, 2), 16) & 0xfe;
|
||||
const merklePath = [];
|
||||
for (let i = 0; i < m; i++) {
|
||||
merklePath.push(controlBlock.slice(66 + i * 64, 66 + (i + 1) * 64));
|
||||
}
|
||||
const leafVersion = scriptInfo.scriptPath.leafVersion;
|
||||
const merklePath = scriptInfo.scriptPath.merkleBranches.slice();
|
||||
if (merklePath.length > this.depth) {
|
||||
this.depth = merklePath.length;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
<div class="text-left">
|
||||
|
||||
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, the <a href="https://bitcoin.gob.sv/">bitcoin.gob.sv</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
||||
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, the <a href="https://bitcoin.gob.sv/">bitcoin.gob.sv</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Holdings S.A. de C.V. in El Salvador ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
||||
|
||||
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
||||
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Holdings S.A. de C.V. in El Salvador ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
||||
|
||||
<h5>By accessing this Website, you agree to the following Terms of Service:</h5>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div [formGroup]="themeForm" class="text-small text-center">
|
||||
<select formControlName="theme" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" (change)="changeTheme()">
|
||||
<option value="default" i18n="mempool-goggles.classic">Classic</option>
|
||||
<option value="default">wiz</option>
|
||||
<option value="contrast">BlueMatt</option>
|
||||
<option value="wiz">wiz</option>
|
||||
<option value="softsimon">softsimon</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { Subscription } from 'rxjs';
|
|||
})
|
||||
export class ThemeSelectorComponent implements OnInit {
|
||||
themeForm: UntypedFormGroup;
|
||||
themes = ['default', 'contrast', 'wiz', 'bukele'];
|
||||
themes = ['default', 'contrast', 'softsimon', 'bukele'];
|
||||
themeSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="container-xl trademark-policy">
|
||||
|
||||
<div *ngIf="!officialMempoolSpace">
|
||||
<p>This website is operated by an individual member of the Bitcoin community, and is not affiliated with Mempool Space K.K.</p>
|
||||
<p>This website is operated by an individual member of the Bitcoin community, and is not affiliated with Mempool Holdings S.A. de C.V.</p>
|
||||
<p>Please view the Trademark Policy and Guidelines for The Mempool Open Source Project at <a href="https://mempool.space/trademark-policy">https://mempool.space/trademark-policy</a>.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<br>
|
||||
|
||||
<p>Thank you for your interest in The Mempool Open Source Project (“Project”). While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. This Policy (the “Policy”) outlines the allowable use of all trademarks and service marks, whether word marks, logos or trade dress (collectively, the "Marks"), owned by Mempool Space K.K. (“Mempool”).</p>
|
||||
<p>Thank you for your interest in The Mempool Open Source Project (“Project”). While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. This Policy (the “Policy”) outlines the allowable use of all trademarks and service marks, whether word marks, logos or trade dress (collectively, the "Marks"), owned by Mempool Holdings S.A. de C.V. (“Mempool”).</p>
|
||||
|
||||
<p>The role of trademarks is to provide assurance about the quality of the products or services with which the trademark is associated. Therefore, because an open source license allows your unrestricted modification of the copyrighted software, we cannot be sure that your modifications to the software are ones that will not be misleading if distributed under the same name. Instead, this Policy describes the circumstances under which you may use our Marks.</p>
|
||||
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr><td>Mempool Space K.K.</td></tr>
|
||||
<tr><td>Mempool Holdings S.A. de C.V.</td></tr>
|
||||
<tr><td>The Mempool Open Source Project</td></tr>
|
||||
<tr><td>Mempool Accelerator</td></tr>
|
||||
<tr><td>Mempool Enterprise</td></tr>
|
||||
|
|
@ -341,7 +342,7 @@
|
|||
|
||||
<p>Also, if you are using our Marks in a way described in the sections "Uses for Which We Are Granting a License," you must include the following trademark attribution at the foot of the webpage where you have used the Mark (or, if in a book, on the credits page), on any packaging or labeling, and on advertising or marketing materials:</p>
|
||||
|
||||
<p>"The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Wallet™, Mempool®, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, the Mempool Accelerator logo;, the Mempool Goggles logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."</p>
|
||||
<p>"The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Wallet™, Mempool®, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, the Mempool Accelerator logo;, the Mempool Goggles logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Holdings S.A. de C.V. in Japan, the United States, and/or other countries, and are used with permission. Mempool Holdings S.A. de C.V.. has no affiliation with and does not sponsor or endorse the information provided herein."</p>
|
||||
|
||||
<li>What to Do When You See Abuse</li>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export class TrademarkPolicyComponent {
|
|||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle('Trademark Policy');
|
||||
this.seoService.setDescription('An overview of the trademarks registered by Mempool Space K.K. and The Mempool Open Source Project® and what we consider to be lawful usage of those trademarks.');
|
||||
this.seoService.setDescription('An overview of the trademarks registered by Mempool Holdings S.A. de C.V. and The Mempool Open Source Project® and what we consider to be lawful usage of those trademarks.');
|
||||
this.ogService.setManualOgImage('trademark-policy.jpg');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@
|
|||
<td style="text-align: left;">
|
||||
<app-asm
|
||||
[asm]="vin.scriptsig_asm"
|
||||
[annotations]="{ signatures: tx['_sigmap'], selectedSig: selectedSig?.txIndex === i && selectedSig?.vindex === vindex ? selectedSig.sig : null }"
|
||||
[annotations]="{ signatures: tx['_sigmap'], selectedSig: selectedSig?.txIndex === i && selectedSig?.vindex === vindex ? selectedSig.sig : null, p2sh: vin.prevout?.scriptpubkey_type === 'p2sh' && !vin.witness }"
|
||||
(showSigInfo)="showSigInfo(i, vindex, $event)"
|
||||
(hideSigInfo)="hideSigInfo()"
|
||||
></app-asm>
|
||||
|
|
@ -183,32 +183,43 @@
|
|||
<fa-icon [icon]="['fas', 'key']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</ng-container>
|
||||
@if (vin.witness.length > 1 && windex === vin.witness.length - 1 && witness.slice(0, 2) === '50') {
|
||||
@if (windex === vin.taprootInfo?.controlBlockIndex) {
|
||||
<span class="control-block" ngbTooltip="taproot control block" (click)="toggleTaprootControlBlock(vindex)">
|
||||
<fa-icon [icon]="['fas', 'code-fork']" [fixedWidth]="true" [rotate]="180"></fa-icon>
|
||||
</span>
|
||||
<ng-container *ngTemplateOutlet="taprootControlBlock; context: { $implicit: vin.taprootInfo?.scriptPath, controlBlock: witness, truncate: !showFullWitness[vindex][windex] }"></ng-container>
|
||||
} @else if (windex === vin.taprootInfo?.scriptIndex || (windex === vin.witness.length - 1 && ['p2sh', 'v0_p2wsh'].includes(vin.prevout?.scriptpubkey_type) && vin.scriptsig.length !== 46)) {
|
||||
<span class="script" ngbTooltip="script">
|
||||
<fa-icon [icon]="['fas', 'code']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
} @else if (windex === vin.taprootInfo?.annexIndex) {
|
||||
<span class="annex" ngbTooltip="annex">
|
||||
<fa-icon [icon]="['fas', 'paperclip']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
}
|
||||
@if (witness.length > 1000) {
|
||||
@if (!showFullWitness[vindex][windex]) {
|
||||
{{ witness | slice:0:1000 }}
|
||||
@if (windex !== vin.taprootInfo?.controlBlockIndex) {
|
||||
@if (witness.length > 1000) {
|
||||
@if (!showFullWitness[vindex][windex]) {
|
||||
{{ witness | slice:0:1000 }}
|
||||
} @else {
|
||||
{{ witness }}
|
||||
}
|
||||
} @else if (witness) {
|
||||
<ng-container *ngIf="tx['_sigmap']?.[witness]?.sig.sighash !== 0 && tx['_sigmap']?.[witness] as sigInfo; else plainSig">
|
||||
<span class="witness">
|
||||
{{witness.slice(0, -2)}}<span class="sig sighash-{{sigInfo.sig.sighash}}"
|
||||
[class.hovered]="selectedSig && selectedSig.txIndex === i && selectedSig.vindex === vindex && selectedSig.sig === sigInfo.sig"
|
||||
(mouseenter)="showSigInfo(i, vindex, sigInfo.sig)"
|
||||
(mouseleave)="hideSigInfo()"
|
||||
[ngbTooltip]="sighashLabels[sigInfo.sig.sighash]">{{witness.slice(-2)}}</span>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-template #plainSig>
|
||||
{{ witness }}
|
||||
</ng-template>
|
||||
} @else {
|
||||
{{ witness }}
|
||||
<empty>
|
||||
}
|
||||
} @else if (witness) {
|
||||
<ng-container *ngIf="tx['_sigmap']?.[witness]?.sig.sighash !== 0 && tx['_sigmap']?.[witness] as sigInfo; else plainSig">
|
||||
<span class="witness">
|
||||
{{witness.slice(0, -2)}}<span class="sig sighash-{{sigInfo.sig.sighash}}"
|
||||
[class.hovered]="selectedSig && selectedSig.txIndex === i && selectedSig.vindex === vindex && selectedSig.sig === sigInfo.sig"
|
||||
(mouseenter)="showSigInfo(i, vindex, sigInfo.sig)"
|
||||
(mouseleave)="hideSigInfo()"
|
||||
[ngbTooltip]="sighashLabels[sigInfo.sig.sighash]">{{witness.slice(-2)}}</span>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-template #plainSig>
|
||||
{{ witness }}
|
||||
</ng-template>
|
||||
} @else {
|
||||
<empty>
|
||||
}
|
||||
</p>
|
||||
@if (witness.length > 1000) {
|
||||
|
|
@ -232,6 +243,14 @@
|
|||
<td i18n="transactions-list.p2sh-redeem-script">P2SH redeem script</td>
|
||||
<td style="text-align: left;" [innerHTML]="vin.inner_redeemscript_asm | asmStyler"></td>
|
||||
</tr>
|
||||
@if (vin.taprootInfo?.scriptPath && showTaprootControlBlock[vindex]) {
|
||||
<tr>
|
||||
<td i18n="transactions-list.p2tr-control-block">P2TR control block</td>
|
||||
<td style="text-align: left; padding: 0px;">
|
||||
<ng-container *ngTemplateOutlet="controlBlockInfo; context: { $implicit: vin.taprootInfo?.scriptPath }"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr *ngIf="vin.inner_witnessscript_asm">
|
||||
@if (isLiquid && vin.inner_simplicityscript) {
|
||||
<td i18n="transactions-list.p2tr-simplicity-script">P2TR Simplicity script</td>
|
||||
|
|
@ -536,3 +555,37 @@
|
|||
</ng-template>
|
||||
|
||||
<ng-template #xRemaining let-x i18n="x-remaining">{{ x }} remaining</ng-template>
|
||||
|
||||
<ng-template #taprootControlBlock let-scriptPath let-controlBlock="controlBlock" let-truncate="truncate">
|
||||
<span class="leaf-version" ngbTooltip="leaf version and parity"> {{ controlBlock.slice(0, 2) }}</span>
|
||||
@if (scriptPath.isNUMS) {
|
||||
<span class="internal-key nums" ngbTooltip="internal key (NUMS point)">{{ scriptPath.internalKey }}</span>
|
||||
} @else {
|
||||
<span class="internal-key" ngbTooltip="internal key">{{ scriptPath.internalKey }}</span>
|
||||
}
|
||||
<span *ngFor="let branch of scriptPath.merkleBranches.slice(0, truncate ? 14 : scriptPath.merkleBranches.length); index as i" class="merkle-branch" ngbTooltip="taptree merkle branch {{ i + 1 }}">{{ branch }}</span>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #controlBlockInfo let-scriptPath>
|
||||
<table class="table table-borderless control-block-info">
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td i18n="transactions-list.taproot.leaf-version">leaf version</td>
|
||||
<td>{{ scriptPath.leafVersion | decimal2hex:1 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="transactions-list.taproot.parity">parity</td>
|
||||
<td>{{ scriptPath.parity ? '0x01' : '0x00' }}</td>
|
||||
</tr>
|
||||
@if (scriptPath.isNUMS) {
|
||||
<tr>
|
||||
<td i18n="transactions-list.taproot.keypath">key path</td>
|
||||
<td>disabled</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td i18n="transactions-list.taproot.script-depth">script depth</td>
|
||||
<td>{{ scriptPath.merkleBranches.length }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-template>
|
||||
|
|
@ -156,11 +156,12 @@
|
|||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.annex {
|
||||
.annex, .control-block, .script {
|
||||
margin-right: -5px;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mobile-bottomcol {
|
||||
|
|
@ -351,4 +352,42 @@ h2 {
|
|||
@media (min-width: 1200px) {
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.merkle-branch:nth-child(2n + 1), .internal-key {
|
||||
color: #cccccc;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.merkle-branch, .internal-key, .leaf-version {
|
||||
&:hover {
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.internal-key.nums {
|
||||
color: #ffdddd;
|
||||
&:hover {
|
||||
color: #ffdddd;
|
||||
}
|
||||
}
|
||||
|
||||
.control-block-info {
|
||||
padding: 0;
|
||||
|
||||
tr td {
|
||||
width: 50%;
|
||||
white-space: nowrap;
|
||||
|
||||
&:first-child {
|
||||
color: white;
|
||||
width: 50%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import { OrdApiService } from '@app/services/ord-api.service';
|
|||
import { Inscription } from '@app/shared/ord/inscription.utils';
|
||||
import { Etching, Runestone } from '@app/shared/ord/rune.utils';
|
||||
import { ADDRESS_SIMILARITY_THRESHOLD, AddressMatch, AddressSimilarity, AddressType, AddressTypeInfo, checkedCompareAddressStrings, detectAddressType } from '@app/shared/address-utils';
|
||||
import { processInputSignatures, Sighash, SigInfo, SighashLabels } from '@app/shared/transaction.utils';
|
||||
import { processInputSignatures, Sighash, SigInfo, SighashLabels, parseTaproot } from '@app/shared/transaction.utils';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SighashFlag } from '@app/shared/transaction.utils';
|
||||
|
||||
|
|
@ -68,6 +68,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
showFullScriptPubkeyHex: { [voutIndex: number]: boolean } = {};
|
||||
showFullOpReturnData: { [voutIndex: number]: boolean } = {};
|
||||
showFullOpReturnPreview: { [voutIndex: number]: boolean } = {};
|
||||
showTaprootControlBlock: { [vinIndex: number]: boolean } = {};
|
||||
showOrdData: { [key: string]: { show: boolean; inscriptions?: Inscription[]; runestone?: Runestone, runeInfo?: { [id: string]: { etching: Etching; txid: string; } }; } } = {};
|
||||
similarityMatches: Map<string, Map<string, { score: number, match: AddressMatch, group: number }>> = new Map();
|
||||
|
||||
|
|
@ -305,15 +306,6 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
// Check for ord data fingerprints in inputs and outputs
|
||||
if (this.stateService.network !== 'liquid' && this.stateService.network !== 'liquidtestnet') {
|
||||
for (let i = 0; i < tx.vin.length; i++) {
|
||||
if (tx.vin[i].prevout?.scriptpubkey_type === 'v1_p2tr' && tx.vin[i].witness?.length) {
|
||||
const hasAnnex = tx.vin[i].witness?.[tx.vin[i].witness.length - 1].startsWith('50');
|
||||
if (tx.vin[i].witness.length > (hasAnnex ? 2 : 1) && tx.vin[i].witness[tx.vin[i].witness.length - (hasAnnex ? 3 : 2)].includes('0063036f7264')) {
|
||||
tx.vin[i].isInscription = true;
|
||||
tx.largeInput = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < tx.vout.length; i++) {
|
||||
if (tx.vout[i]?.scriptpubkey?.startsWith('6a5d')) {
|
||||
tx.vout[i].isRunestone = true;
|
||||
|
|
@ -337,19 +329,19 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
}
|
||||
tx['_showSignatures'] = this.shouldShowSignatures(tx);
|
||||
} else { // check for simplicity script spends
|
||||
for (const vin of tx.vin) {
|
||||
if (vin.prevout?.scriptpubkey_type === 'v1_p2tr' && vin.inner_witnessscript_asm) {
|
||||
const hasAnnex = vin.witness[vin.witness.length - 1].startsWith('50');
|
||||
const isScriptSpend = vin.witness.length > (hasAnnex ? 2 : 1);
|
||||
if (isScriptSpend) {
|
||||
const controlBlock = hasAnnex ? vin.witness[vin.witness.length - 2] : vin.witness[vin.witness.length - 1];
|
||||
const scriptHex = hasAnnex ? vin.witness[vin.witness.length - 3] : vin.witness[vin.witness.length - 2]; // bip341 script element
|
||||
const tapleafVersion = parseInt(controlBlock.slice(0, 2), 16) & 0xfe;
|
||||
// simplicity script spend
|
||||
if (tapleafVersion === 0xbe) {
|
||||
vin.inner_simplicityscript = vin.witness[1]; // simplicity program is the second witness element
|
||||
}
|
||||
}
|
||||
// parse taproot spends
|
||||
for (const vin of tx.vin) {
|
||||
if (vin.prevout?.scriptpubkey_type === 'v1_p2tr') {
|
||||
vin.taprootInfo = parseTaproot(vin.witness);
|
||||
if (this.stateService.network !== 'liquid' && this.stateService.network !== 'liquidtestnet') {
|
||||
if (vin.taprootInfo?.scriptPath?.script.includes('0063036f7264')) {
|
||||
vin.isInscription = true;
|
||||
tx.largeInput = true;
|
||||
}
|
||||
} else {
|
||||
if (vin.taprootInfo?.scriptPath?.leafVersion === 0xbe) {
|
||||
vin.inner_simplicityscript = vin.witness[1]; // simplicity program is the second witness element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -614,6 +606,10 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.showFullOpReturnPreview[voutIndex] = !this.showFullOpReturnPreview[voutIndex];
|
||||
}
|
||||
|
||||
toggleTaprootControlBlock(vinIndex: number): void {
|
||||
this.showTaprootControlBlock[vinIndex] = !this.showTaprootControlBlock[vinIndex];
|
||||
}
|
||||
|
||||
toggleOrdData(txid: string, type: 'vin' | 'vout', index: number) {
|
||||
const tx = this.transactions.find((tx) => tx.txid === txid);
|
||||
if (!tx) {
|
||||
|
|
@ -624,10 +620,9 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.showOrdData[key] = this.showOrdData[key] || { show: false };
|
||||
|
||||
if (type === 'vin') {
|
||||
|
||||
if (!this.showOrdData[key].inscriptions) {
|
||||
const hasAnnex = tx.vin[index].witness?.[tx.vin[index].witness.length - 1].startsWith('50');
|
||||
this.showOrdData[key].inscriptions = this.ordApiService.decodeInscriptions(tx.vin[index].witness[tx.vin[index].witness.length - (hasAnnex ? 3 : 2)]);
|
||||
const tapscript = tx.vin[index].taprootInfo?.scriptPath?.script;
|
||||
this.showOrdData[key].inscriptions = this.ordApiService.decodeInscriptions(tapscript);
|
||||
}
|
||||
this.showOrdData[key].show = !this.showOrdData[key].show;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Price } from '@app/services/price.service';
|
||||
import { IChannel } from '@interfaces/node-api.interface';
|
||||
import { ParsedTaproot } from '../shared/transaction.utils';
|
||||
|
||||
export interface Transaction {
|
||||
txid: string;
|
||||
|
|
@ -80,6 +81,8 @@ export interface Vin {
|
|||
isInscription?: boolean;
|
||||
// temporary field for extracted raw simplicity scripts
|
||||
inner_simplicityscript?: string;
|
||||
// parsed taproot info
|
||||
taprootInfo?: ParsedTaproot;
|
||||
}
|
||||
|
||||
interface Issuance {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import '@angular/localize/init';
|
|||
import { ScriptInfo } from '@app/shared/script.utils';
|
||||
import { Vin, Vout } from '@interfaces/electrs.interface';
|
||||
import { BECH32_CHARS_LW, BASE58_CHARS, HEX_CHARS } from '@app/shared/regex.utils';
|
||||
import { parseTaproot } from './transaction.utils';
|
||||
|
||||
export type AddressType = 'fee'
|
||||
| 'empty'
|
||||
|
|
@ -158,20 +159,18 @@ export class AddressTypeInfo {
|
|||
if (this.type === 'v1_p2tr') {
|
||||
for (let i = 0; i < vin.length; i++) {
|
||||
const v = vin[i];
|
||||
const hasAnnex = v.witness[v.witness.length - 1].startsWith('50');
|
||||
const isScriptSpend = v.witness.length > (hasAnnex ? 2 : 1);
|
||||
if (isScriptSpend) {
|
||||
const controlBlock = hasAnnex ? v.witness[v.witness.length - 2] : v.witness[v.witness.length - 1];
|
||||
const scriptHex = hasAnnex ? v.witness[v.witness.length - 3] : v.witness[v.witness.length - 2];
|
||||
const tapleafVersion = parseInt(controlBlock.slice(0, 2), 16) & 0xfe;
|
||||
|
||||
if (tapleafVersion === 0xc0 && v.inner_witnessscript_asm) {
|
||||
if (!v.taprootInfo) {
|
||||
v.taprootInfo = parseTaproot(v.witness);
|
||||
}
|
||||
const taprootInfo = v.taprootInfo;
|
||||
if (taprootInfo.scriptPath) {
|
||||
if (taprootInfo.scriptPath.leafVersion === 0xc0 && v.inner_witnessscript_asm) {
|
||||
this.tapscript = true;
|
||||
this.processScript(new ScriptInfo('inner_witnessscript', scriptHex, v.inner_witnessscript_asm, v.witness, controlBlock, vinIds?.[i]));
|
||||
} else if (this.network === 'liquid' || this.network === 'liquidtestnet' && tapleafVersion === 0xbe) {
|
||||
this.processScript(new ScriptInfo('inner_witnessscript', taprootInfo.scriptPath.script, v.inner_witnessscript_asm, v.witness, taprootInfo, vinIds?.[i]));
|
||||
} else if (this.network === 'liquid' || this.network === 'liquidtestnet' && taprootInfo.scriptPath.leafVersion === 0xbe) {
|
||||
this.simplicity = true;
|
||||
v.inner_simplicityscript = v.witness[1];
|
||||
this.processScript(new ScriptInfo('inner_simplicityscript', scriptHex, null, v.witness, controlBlock, vinIds?.[i]));
|
||||
this.processScript(new ScriptInfo('inner_simplicityscript', taprootInfo.scriptPath.simplicityScript, null, v.witness, taprootInfo, vinIds?.[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="asm">
|
||||
@for (instruction of instructions; track instruction.instruction) {
|
||||
@for (instruction of instructions; track instruction.instruction; let i = $index) {
|
||||
<span [class]='opcodeStyles.get(instruction.instruction)'>OP_{{instruction.instruction}}</span>
|
||||
@for (arg of instruction.args; track arg) {
|
||||
@for (arg of instruction.args; track arg; let j = $index) {
|
||||
<ng-container *ngIf="annotations.signatures?.[arg] as sigInfo; else plainArg">
|
||||
<span class="sig sig-key sig-inline sighash-{{sigInfo.sig.sighash}}"
|
||||
[class.hovered]="annotations.selectedSig && annotations.selectedSig === sigInfo.sig"
|
||||
|
|
@ -22,6 +22,11 @@
|
|||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #plainArg>
|
||||
@if (annotations.p2sh && i === instructions.length - 1) {
|
||||
<span class="script" ngbTooltip="script">
|
||||
<fa-icon [icon]="['fas', 'code']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
}
|
||||
{{ arg }}
|
||||
</ng-template>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,9 @@
|
|||
margin-left: 4px;
|
||||
margin-right: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.script {
|
||||
margin-left: 4px;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
|
@ -13,10 +13,12 @@ export class AsmComponent {
|
|||
@Input() crop: number = 0;
|
||||
@Input() annotations: {
|
||||
signatures: Record<string, { sig: SigInfo, vindex: number }>,
|
||||
selectedSig: SigInfo | null
|
||||
selectedSig: SigInfo | null,
|
||||
p2sh: boolean
|
||||
} = {
|
||||
signatures: {},
|
||||
selectedSig: null
|
||||
selectedSig: null,
|
||||
p2sh: false
|
||||
};
|
||||
@Output() showSigInfo = new EventEmitter<SigInfo>();
|
||||
@Output() hideSigInfo = new EventEmitter<void>();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||
standalone: false,
|
||||
})
|
||||
export class Decimal2HexPipe implements PipeTransform {
|
||||
transform(decimal: number): string {
|
||||
return `0x` + ( decimal.toString(16) ).padStart(8, '0');
|
||||
transform(decimal: number, bytes: number = 4): string {
|
||||
return `0x` + ( decimal.toString(16) ).padStart(bytes * 2, '0');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Vin } from "../interfaces/electrs.interface";
|
||||
import { AddressType, detectAddressType } from "./address-utils";
|
||||
import { ParsedTaproot } from "./transaction.utils";
|
||||
|
||||
const opcodes = {
|
||||
OP_FALSE: 0,
|
||||
|
|
@ -175,18 +176,18 @@ export const ScriptTemplates: { [type: string]: (...args: any) => ScriptTemplate
|
|||
|
||||
export class ScriptInfo {
|
||||
type: ScriptType;
|
||||
scriptPath?: string;
|
||||
taprootInfo?: ParsedTaproot;
|
||||
hex?: string;
|
||||
asm?: string;
|
||||
vinId?: string;
|
||||
template: ScriptTemplate;
|
||||
|
||||
constructor(type: ScriptType, hex?: string, asm?: string, witness?: string[], scriptPath?: string, vinId?: string) {
|
||||
constructor(type: ScriptType, hex?: string, asm?: string, witness?: string[], taprootInfo?: ParsedTaproot, vinId?: string) {
|
||||
this.type = type;
|
||||
this.hex = hex;
|
||||
this.asm = asm;
|
||||
if (scriptPath) {
|
||||
this.scriptPath = scriptPath;
|
||||
if (taprootInfo) {
|
||||
this.taprootInfo = taprootInfo;
|
||||
}
|
||||
if (vinId) {
|
||||
this.vinId = vinId;
|
||||
|
|
@ -201,7 +202,7 @@ export class ScriptInfo {
|
|||
}
|
||||
|
||||
get key(): string {
|
||||
return this.type + (this.scriptPath || '');
|
||||
return this.type + (this.taprootInfo?.controlBlock || '');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa
|
|||
faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck,
|
||||
faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faTimeline,
|
||||
faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes, faCreditCard, faMicroscope, faExclamationTriangle, faLockOpen, faPaperclip, faAddressCard,
|
||||
faMedal, faBug, faFilePdf, faPiggyBank, faLayerGroup, faHeart, faCashRegister } from '@fortawesome/free-solid-svg-icons';
|
||||
faMedal, faBug, faFilePdf, faPiggyBank, faLayerGroup, faHeart, faCashRegister, faCodeFork, faCode } from '@fortawesome/free-solid-svg-icons';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { MenuComponent } from '@components/menu/menu.component';
|
||||
import { PreviewTitleComponent } from '@components/master-page-preview/preview-title.component';
|
||||
|
|
@ -484,5 +484,7 @@ export class SharedModule {
|
|||
library.addIcons(faLayerGroup);
|
||||
library.addIcons(faHeart);
|
||||
library.addIcons(faCashRegister);
|
||||
library.addIcons(faCodeFork);
|
||||
library.addIcons(faCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ const MAX_STANDARD_SCRIPTSIG_SIZE = 1650;
|
|||
const DUST_RELAY_TX_FEE = 3;
|
||||
const MAX_OP_RETURN_RELAY = 83;
|
||||
const DEFAULT_PERMIT_BAREMULTISIG = true;
|
||||
const MAX_TX_LEGACY_SIGOPS = 2_500 * 4; // witness-adjusted sigops
|
||||
|
||||
/**
|
||||
* Calculate the witness-adjusted sigops cost of an asm script
|
||||
*/
|
||||
export function countScriptSigops(script: string, isRawScript: boolean = false, witness: boolean = false): number {
|
||||
if (!script?.length) {
|
||||
return 0;
|
||||
|
|
@ -55,8 +59,8 @@ export function setSchnorrSighashFlags(flags: bigint, witness: string[]): bigint
|
|||
if (!witness?.length) {
|
||||
return flags;
|
||||
}
|
||||
const hasAnnex = witness.length > 1 && witness[witness.length - 1].startsWith('50');
|
||||
if (witness?.length === (hasAnnex ? 2 : 1)) {
|
||||
const taprootInfo = parseTaproot(witness);
|
||||
if (taprootInfo.keyPath) {
|
||||
// keypath spend, signature is the only witness item
|
||||
if (witness[0].length === 130) {
|
||||
flags |= setSighashFlags(flags, witness[0]);
|
||||
|
|
@ -64,12 +68,13 @@ export function setSchnorrSighashFlags(flags: bigint, witness: string[]): bigint
|
|||
flags |= TransactionFlags.sighash_default;
|
||||
}
|
||||
} else {
|
||||
// scriptpath spend, all items except for the script, control block and annex could be signatures
|
||||
for (let i = 0; i < witness.length - (hasAnnex ? 3 : 2); i++) {
|
||||
// scriptpath spend, all initial stack items could be signatures
|
||||
const stack = taprootInfo.stack;
|
||||
for (const w of stack) {
|
||||
// handle probable signatures
|
||||
if (witness[i].length === 130) {
|
||||
flags |= setSighashFlags(flags, witness[i]);
|
||||
} else if (witness[i].length === 128) {
|
||||
if (w.length === 130) {
|
||||
flags |= setSighashFlags(flags, w);
|
||||
} else if (w.length === 128) {
|
||||
flags |= TransactionFlags.sighash_default;
|
||||
}
|
||||
}
|
||||
|
|
@ -287,14 +292,8 @@ export function processInputSignatures(vin: Vin): SigInfo[] {
|
|||
signatures = extractDERSignaturesWitness(vin.witness || []);
|
||||
break;
|
||||
case 'v1_p2tr': {
|
||||
const hasAnnex = vin.witness?.length > 1 && vin.witness[vin.witness.length - 1].startsWith('50');
|
||||
const isKeyspend = vin.witness?.length === (hasAnnex ? 2 : 1);
|
||||
if (isKeyspend) {
|
||||
signatures = extractSchnorrSignatures(vin.witness);
|
||||
} else {
|
||||
const stackItems = vin.witness?.slice(0, hasAnnex ? -3 : -2);
|
||||
signatures = extractSchnorrSignatures(stackItems);
|
||||
}
|
||||
const taprootInfo = parseTaproot(vin.witness);
|
||||
signatures = extractSchnorrSignatures(taprootInfo.stack);
|
||||
} break;
|
||||
default:
|
||||
// non-signed input types?
|
||||
|
|
@ -406,12 +405,11 @@ export function fillUnsignedInput(vin: Vin): { missingSigs: number, bytes: numbe
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'v1_p2tr':
|
||||
const hasAnnex = vin.witness?.length > 1 && vin.witness[vin.witness.length - 1].startsWith('50');
|
||||
if (vin.inner_witnessscript_asm) {
|
||||
const stackItems = vin.witness.slice(0, hasAnnex ? -3 : -2);
|
||||
case 'v1_p2tr': {
|
||||
const taprootInfo = parseTaproot(vin.witness);
|
||||
signatures = extractSchnorrSignatures(taprootInfo.stack);
|
||||
if (taprootInfo.scriptPath) {
|
||||
if (/^OP_PUSHBYTES_32 [a-fA-F0-9]{64} OP_CHECKSIG$/.test(vin.inner_witnessscript_asm)) {
|
||||
signatures = extractSchnorrSignatures(stackItems);
|
||||
if (!signatures.length) {
|
||||
missingSigs = 1;
|
||||
bytes = 65;
|
||||
|
|
@ -421,7 +419,6 @@ export function fillUnsignedInput(vin: Vin): { missingSigs: number, bytes: numbe
|
|||
|
||||
multisig = parseTapscriptMultisig(vin.inner_witnessscript_asm);
|
||||
if (multisig) {
|
||||
signatures = extractSchnorrSignatures(stackItems);
|
||||
if (multisig.m - signatures.length > 0) {
|
||||
missingSigs = multisig.m - signatures.length;
|
||||
bytes = 65 * missingSigs + (multisig.n - multisig.m); // empty witness items for each non-signing keys
|
||||
|
|
@ -429,9 +426,8 @@ export function fillUnsignedInput(vin: Vin): { missingSigs: number, bytes: numbe
|
|||
}
|
||||
}
|
||||
|
||||
let unanimousMultisig = parseTapscriptUnanimousMultisig(vin.inner_witnessscript_asm);
|
||||
const unanimousMultisig = parseTapscriptUnanimousMultisig(vin.inner_witnessscript_asm);
|
||||
if (unanimousMultisig) {
|
||||
signatures = extractSchnorrSignatures(stackItems);
|
||||
if (unanimousMultisig - signatures.length > 0) {
|
||||
missingSigs = unanimousMultisig - signatures.length;
|
||||
bytes = 65 * missingSigs;
|
||||
|
|
@ -439,14 +435,13 @@ export function fillUnsignedInput(vin: Vin): { missingSigs: number, bytes: numbe
|
|||
}
|
||||
}
|
||||
} else { // Assume keyspend
|
||||
signatures = extractSchnorrSignatures(vin.witness?.slice(0, hasAnnex ? -1 : undefined) || []);
|
||||
if (!signatures.length) {
|
||||
missingSigs = 1;
|
||||
bytes = 65;
|
||||
addToWitness = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -483,6 +478,11 @@ export function isNonStandard(tx: Transaction, height?: number, network?: string
|
|||
return true;
|
||||
}
|
||||
|
||||
// legacy sigops
|
||||
if (isNonStandardLegacySigops(tx, height, network)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// input validation
|
||||
for (const vin of tx.vin) {
|
||||
if (vin.is_coinbase) {
|
||||
|
|
@ -516,25 +516,24 @@ export function isNonStandard(tx: Transaction, height?: number, network?: string
|
|||
}
|
||||
// bad-witness-nonstandard
|
||||
if (vin.prevout?.scriptpubkey_type === 'v1_p2tr' && vin.witness?.length) {
|
||||
const hasAnnex = vin.witness.length > 1 && vin.witness[vin.witness.length - 1].startsWith('50');
|
||||
const taprootInfo = parseTaproot(vin.witness);
|
||||
// annex is non-standard
|
||||
if (hasAnnex) {
|
||||
if (taprootInfo.annex) {
|
||||
return true;
|
||||
}
|
||||
if (vin.witness.length > (hasAnnex ? 2 : 1)) {
|
||||
if (taprootInfo.scriptPath) {
|
||||
// script path spend
|
||||
const controlBlock = vin.witness[vin.witness.length - (hasAnnex ? 2 : 1)];
|
||||
// control block is required
|
||||
if (!controlBlock.length) {
|
||||
if (!taprootInfo.controlBlock.length) {
|
||||
return false;
|
||||
} else {
|
||||
// Leaf version must be 0xc0 (aka Tapscript, see BIP 342)
|
||||
if ((parseInt(controlBlock.slice(0, 2), 16) & 0xfe) !== 0xc0) {
|
||||
if (taprootInfo.scriptPath.leafVersion !== 0xc0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// remaining witness items (except for the script) must be within MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit
|
||||
if (vin.witness.slice(0, vin.witness.length - (hasAnnex ? 3 : 2)).some(v => v.length > 160)) {
|
||||
if (taprootInfo.stack.some(v => v.length > 160)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -544,6 +543,7 @@ export function isNonStandard(tx: Transaction, height?: number, network?: string
|
|||
|
||||
// output validation
|
||||
let opreturnCount = 0;
|
||||
let opreturnBytes = 0;
|
||||
for (const vout of tx.vout) {
|
||||
// scriptpubkey
|
||||
if (['nonstandard', 'provably_unspendable', 'empty'].includes(vout.scriptpubkey_type)) {
|
||||
|
|
@ -567,10 +567,7 @@ export function isNonStandard(tx: Transaction, height?: number, network?: string
|
|||
}
|
||||
} else if (vout.scriptpubkey_type === 'op_return') {
|
||||
opreturnCount++;
|
||||
if ((vout.scriptpubkey.length / 2) > MAX_OP_RETURN_RELAY) {
|
||||
// over default datacarrier limit
|
||||
return true;
|
||||
}
|
||||
opreturnBytes += vout.scriptpubkey.length / 2;
|
||||
}
|
||||
// dust
|
||||
// (we could probably hardcode this for the different output types...)
|
||||
|
|
@ -592,9 +589,11 @@ export function isNonStandard(tx: Transaction, height?: number, network?: string
|
|||
}
|
||||
}
|
||||
|
||||
// multi-op-return
|
||||
if (opreturnCount > 1) {
|
||||
return true;
|
||||
// op_return
|
||||
if (opreturnCount > 0) {
|
||||
if (!isStandardOpReturn(opreturnBytes, opreturnCount, height, network)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: non-mandatory-script-verify-flag
|
||||
|
|
@ -668,6 +667,49 @@ function isStandardEphemeralDust(tx: Transaction, height?: number, network?: str
|
|||
return false;
|
||||
}
|
||||
|
||||
// OP_RETURN size & count limits were lifted in v28.3/v29.2/v30.0
|
||||
const OP_RETURN_STANDARDNESS_ACTIVATION_HEIGHT = {
|
||||
'testnet4': 108_000,
|
||||
'testnet': 4_750_000,
|
||||
'signet': 276_500,
|
||||
'': 921_000,
|
||||
};
|
||||
const MAX_DATACARRIER_BYTES = 83;
|
||||
function isStandardOpReturn(bytes: number, outputs: number,height?: number, network?: string): boolean {
|
||||
if (
|
||||
(height == null || (
|
||||
OP_RETURN_STANDARDNESS_ACTIVATION_HEIGHT[network]
|
||||
&& height >= OP_RETURN_STANDARDNESS_ACTIVATION_HEIGHT[network]
|
||||
)) // limits lifted
|
||||
|| // OR
|
||||
(bytes <= MAX_DATACARRIER_BYTES && outputs <= 1) // below old limits
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// New legacy sigops limit started to be enforced in v30.0
|
||||
const LEGACY_SIGOPS_STANDARDNESS_ACTIVATION_HEIGHT = {
|
||||
'testnet4': 108_000,
|
||||
'testnet': 4_750_000,
|
||||
'signet': 276_500,
|
||||
'': 921_000,
|
||||
};
|
||||
function isNonStandardLegacySigops(tx: Transaction, height?: number, network?: string): boolean {
|
||||
if (
|
||||
height == null || (
|
||||
LEGACY_SIGOPS_STANDARDNESS_ACTIVATION_HEIGHT[network]
|
||||
&& height >= LEGACY_SIGOPS_STANDARDNESS_ACTIVATION_HEIGHT[network]
|
||||
)
|
||||
) {
|
||||
if (!checkSigopsBIP54(tx, MAX_TX_LEGACY_SIGOPS)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// A witness program is any valid scriptpubkey that consists of a 1-byte push opcode
|
||||
// followed by a data push between 2 and 40 bytes.
|
||||
// https://github.com/bitcoin/bitcoin/blob/2c79abc7ad4850e9e3ba32a04c530155cda7f980/src/script/script.cpp#L224-L240
|
||||
|
|
@ -805,10 +847,8 @@ export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replac
|
|||
// created before taproot activation don't need to have any witness data
|
||||
// (see https://mempool.space/tx/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41)
|
||||
if (vin.witness?.length) {
|
||||
// in taproot, if there are at least two witness elements, and the first byte of the last element is 0x50, that last element is an annex
|
||||
const hasAnnex = vin.witness?.length > 1 && 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)) {
|
||||
const taprootInfo = parseTaproot(vin.witness);
|
||||
if (taprootInfo.scriptPath) {
|
||||
// the script itself is the second-to-last witness item, not counting the annex
|
||||
const asm = vin.inner_witnessscript_asm;
|
||||
// inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope
|
||||
|
|
@ -816,7 +856,7 @@ export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replac
|
|||
flags |= TransactionFlags.inscription;
|
||||
}
|
||||
}
|
||||
if (hasAnnex) {
|
||||
if (taprootInfo.annex) {
|
||||
flags |= TransactionFlags.annex;
|
||||
}
|
||||
}
|
||||
|
|
@ -1653,6 +1693,41 @@ export function countSigops(transaction: Transaction): number {
|
|||
return sigops;
|
||||
}
|
||||
|
||||
/**
|
||||
* see https://github.com/bitcoin/bitcoin/blob/25c45bb0d0bd6618ec9296a1a43605657124e5de/src/policy/policy.cpp#L166-L193
|
||||
* returns true if the transactions is permitted under bip54 sigops rules
|
||||
*
|
||||
* "Unlike the existing block wide sigop limit which counts sigops present in the block
|
||||
* itself (including the scriptPubKey which is not executed until spending later), BIP54
|
||||
* counts sigops in the block where they are potentially executed (only).
|
||||
* This means sigops in the spent scriptPubKey count toward the limit.
|
||||
* `fAccurate` means correctly accounting sigops for CHECKMULTISIGs(VERIFY) with 16 pubkeys
|
||||
* or fewer. This method of accounting was introduced by BIP16, and BIP54 reuses it.
|
||||
* The GetSigOpCount call on the previous scriptPubKey counts both bare and P2SH sigops."
|
||||
*/
|
||||
function checkSigopsBIP54(tx: Transaction, limit: number = MAX_TX_LEGACY_SIGOPS): boolean {
|
||||
let sigops = 0;
|
||||
for (const input of tx.vin) {
|
||||
if (input.scriptsig_asm) {
|
||||
sigops += countScriptSigops(input.scriptsig_asm);
|
||||
}
|
||||
if (input.prevout) {
|
||||
// P2SH redeem script
|
||||
if (input.prevout.scriptpubkey_type === 'p2sh' && input.inner_redeemscript_asm) {
|
||||
sigops += countScriptSigops(input.inner_redeemscript_asm);
|
||||
} else {
|
||||
// prevout scriptpubkey
|
||||
sigops += countScriptSigops(input.prevout.scriptpubkey_asm);
|
||||
}
|
||||
}
|
||||
|
||||
if (sigops > limit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function scriptPubKeyToAddress(scriptPubKey: string, network: string): { address: string, type: string } {
|
||||
// P2PKH
|
||||
if (/^76a914[0-9a-f]{40}88ac$/.test(scriptPubKey)) {
|
||||
|
|
@ -2157,3 +2232,73 @@ const opcodes = {
|
|||
254: 'OP_PUBKEY',
|
||||
255: 'OP_INVALIDOPCODE',
|
||||
};
|
||||
|
||||
export interface ParsedTaproot {
|
||||
keyPath: boolean;
|
||||
scriptPath?: {
|
||||
leafVersion: number;
|
||||
parity: number;
|
||||
script: string;
|
||||
simplicityScript?: string; // liquid only
|
||||
merkleBranches: string[];
|
||||
internalKey: string;
|
||||
isNUMS?: boolean;
|
||||
}
|
||||
stack: string[]; // witness items excluding annex, script, control block
|
||||
annex?: string;
|
||||
controlBlock?: string;
|
||||
annexIndex?: number;
|
||||
controlBlockIndex?: number;
|
||||
scriptIndex?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out the different parts of a taproot spend from a p2tr witness
|
||||
* if present, `witness` MUST be a valid p2tr witness stack, otherwise the result will be garbage
|
||||
* otherwise assume this is an unsigned input from a non finalized PSBT
|
||||
*/
|
||||
export function parseTaproot(witness: string[]): ParsedTaproot | null {
|
||||
if (!witness?.length) {
|
||||
return { keyPath: true, stack: [] };
|
||||
}
|
||||
const parsed: ParsedTaproot = {
|
||||
keyPath: true,
|
||||
stack: witness.slice(0, 1), // assume keyspend for now
|
||||
};
|
||||
const hasAnnex = witness.length > 1 && witness[witness.length - 1].startsWith('50');
|
||||
if (hasAnnex) {
|
||||
parsed.annexIndex = witness.length - 1;
|
||||
parsed.annex = witness[parsed.annexIndex];
|
||||
}
|
||||
if (witness.length > (hasAnnex ? 2 : 1)) {
|
||||
parsed.keyPath = false;
|
||||
parsed.controlBlockIndex = witness.length - (hasAnnex ? 2 : 1);
|
||||
parsed.scriptIndex = witness.length - (hasAnnex ? 3 : 2);
|
||||
// control block is the last non-annex element
|
||||
parsed.controlBlock = witness[parsed.controlBlockIndex];
|
||||
const leafVersionParity = parseInt(parsed.controlBlock.slice(0, 2), 16);
|
||||
const internalKey = parsed.controlBlock.slice(2, 66);
|
||||
parsed.scriptPath = {
|
||||
// script is the second last non-annex element
|
||||
script: witness[parsed.scriptIndex],
|
||||
// first two bytes are the leaf version & parity bit
|
||||
leafVersion: leafVersionParity & 0xfe,
|
||||
parity: leafVersionParity & 0x01,
|
||||
internalKey: internalKey,
|
||||
isNUMS: internalKey === '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0',
|
||||
merkleBranches: [],
|
||||
};
|
||||
for (let i = 66; (i + 64) <= parsed.controlBlock.length; i += 64) {
|
||||
parsed.scriptPath.merkleBranches.push(parsed.controlBlock.slice(i, i + 64));
|
||||
}
|
||||
// remaining items are the initial stack
|
||||
parsed.stack = witness.slice(0, (hasAnnex ? -3 : -2));
|
||||
|
||||
if (parsed.scriptPath.leafVersion === 0xbe) {
|
||||
// override script stuff for simplicity
|
||||
parsed.scriptIndex = 1;
|
||||
parsed.scriptPath.simplicityScript = witness[parsed.scriptIndex];
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny-ps" viewBox="0 0 241 241" xml:space="preserve"><title>Mempool Space K.K.</title><g><g><path fill="#2E3349" d="M241.37,211.23c0,16.56-13.43,29.99-29.99,29.99H30.36c-16.56,0-29.99-13.43-29.99-29.99V30.21 c0-16.56,13.43-29.99,29.99-29.99h181.02c16.56,0,29.99,13.43,29.99,29.99V211.23z"></path><linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="120.8689" y1="68.6556" x2="120.8689" y2="301.1491"><stop offset="0" stop-color="#AE61FF"></stop><stop offset="1" stop-color="#13EFD8"></stop></linearGradient><path fill="url(#SVGID_1_)" d="M0.32,120.99v90.24c0,16.56,13.49,29.99,30.14,29.99h180.82c16.64,0,30.13-13.43,30.13-29.99 v-90.24H0.32z"></path></g><g><path fill="#FFFFFF" fill-opacity="0.3" d="M212.72,209c0,3.7-2.53,6.7-5.65,6.7h-31.24c-3.12,0-5.65-3-5.65-6.7V32.44c0-3.7,2.53-6.7,5.65-6.7h31.24 c3.12,0,5.65,3,5.65,6.7V209z"></path></g></g></svg>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny-ps" viewBox="0 0 241 241" xml:space="preserve"><title>Mempool Holdings S.A. de C.V.</title><g><g><path fill="#2E3349" d="M241.37,211.23c0,16.56-13.43,29.99-29.99,29.99H30.36c-16.56,0-29.99-13.43-29.99-29.99V30.21 c0-16.56,13.43-29.99,29.99-29.99h181.02c16.56,0,29.99,13.43,29.99,29.99V211.23z"></path><linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="120.8689" y1="68.6556" x2="120.8689" y2="301.1491"><stop offset="0" stop-color="#AE61FF"></stop><stop offset="1" stop-color="#13EFD8"></stop></linearGradient><path fill="url(#SVGID_1_)" d="M0.32,120.99v90.24c0,16.56,13.49,29.99,30.14,29.99h180.82c16.64,0,30.13-13.43,30.13-29.99 v-90.24H0.32z"></path></g><g><path fill="#FFFFFF" fill-opacity="0.3" d="M212.72,209c0,3.7-2.53,6.7-5.65,6.7h-31.24c-3.12,0-5.65-3-5.65-6.7V32.44c0-3.7,2.53-6.7,5.65-6.7h31.24 c3.12,0,5.65,3,5.65,6.7V209z"></path></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
BIN
frontend/src/resources/wiz.png
Normal file
BIN
frontend/src/resources/wiz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
|
|
@ -1,10 +1,10 @@
|
|||
/* Theme */
|
||||
$bg: #1d1f31;
|
||||
$active-bg: #11131f;
|
||||
$bg: #11131f;
|
||||
$active-bg: #000000;
|
||||
$hover-bg: #12131e;
|
||||
$fg: #fff;
|
||||
$nav-bg: $bg;
|
||||
$title-fg: #4a68b9;
|
||||
$title-fg: #2055e3;
|
||||
|
||||
$taproot: #eba814;
|
||||
$taproot-light: #d5a90a;
|
||||
|
|
@ -18,11 +18,11 @@ $gray-700: $fg;
|
|||
|
||||
$nav-tabs-link-active-bg: $active-bg;
|
||||
|
||||
$primary: #105fb0;
|
||||
$secondary: #2d3348;
|
||||
$tertiary: #653b9c;
|
||||
$success: #1a9436;
|
||||
$info: #1bd8f4;
|
||||
$primary: #007cfa;
|
||||
$secondary: #272f4e;
|
||||
$tertiary: #6225b2;
|
||||
$success: #0aab2f;
|
||||
$info: #00ddff;
|
||||
|
||||
$h5-font-size: 1.15rem !default;
|
||||
|
||||
|
|
@ -79,13 +79,13 @@ $dropdown-link-active-bg: $active-bg;
|
|||
--skeleton-bg: #2e324e;
|
||||
--skeleton-bg-light: #5d6182;
|
||||
|
||||
--box-bg: #24273e;
|
||||
--stat-box-bg: #181b2d;
|
||||
--box-bg: #171c2a;
|
||||
--stat-box-bg: #0b1018;
|
||||
--alert-bg: #3a1c61;
|
||||
--navbar-bg: #212121;
|
||||
--transparent-fg: #ffffff66;
|
||||
--fade-out-box-bg-start: rgba(36, 39, 62, 0);
|
||||
--fade-out-box-bg-end: rgba(36, 39, 62, 1);
|
||||
--transparent-fg: #ffffffbb;
|
||||
--fade-out-box-bg-start: rgba(23, 28, 42, 0);
|
||||
--fade-out-box-bg-end: rgba(23, 28, 42, 1);
|
||||
--opacity: 0.57;
|
||||
|
||||
--testnet: #1d486f;
|
||||
|
|
@ -102,12 +102,12 @@ $dropdown-link-active-bg: $active-bg;
|
|||
--taproot-light: #d5a90a;
|
||||
--taproot-dark: #9d7c05;
|
||||
|
||||
--green: #3bcc49;
|
||||
--red: #dc3545;
|
||||
--green: #83fd00;
|
||||
--red: #ff3d00;
|
||||
--yellow: #fff000;
|
||||
--grey: #6c757d;
|
||||
--grey: #7e7e7e;
|
||||
--tooltip-grey: #b1b1b1;
|
||||
--orange: #b86d12;
|
||||
--orange: #ff9f00;
|
||||
|
||||
--search-button: #4d2d77;
|
||||
--search-button-border: #472a6e;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/* Theme */
|
||||
$bg: #11131f;
|
||||
$active-bg: #000000;
|
||||
$bg: #1d1f31;
|
||||
$active-bg: #11131f;
|
||||
$hover-bg: #12131e;
|
||||
$fg: #fff;
|
||||
$nav-bg: $bg;
|
||||
$title-fg: #2055e3;
|
||||
$title-fg: #4a68b9;
|
||||
|
||||
$taproot: #eba814;
|
||||
$taproot-light: #d5a90a;
|
||||
|
|
@ -18,11 +18,11 @@ $gray-700: $fg;
|
|||
|
||||
$nav-tabs-link-active-bg: $active-bg;
|
||||
|
||||
$primary: #007cfa;
|
||||
$secondary: #272f4e;
|
||||
$tertiary: #6225b2;
|
||||
$success: #0aab2f;
|
||||
$info: #00ddff;
|
||||
$primary: #105fb0;
|
||||
$secondary: #2d3348;
|
||||
$tertiary: #653b9c;
|
||||
$success: #1a9436;
|
||||
$info: #1bd8f4;
|
||||
|
||||
$h5-font-size: 1.15rem !default;
|
||||
|
||||
|
|
@ -76,12 +76,12 @@ $dropdown-link-active-bg: $active-bg;
|
|||
--skeleton-bg: #2e324e;
|
||||
--skeleton-bg-light: #5d6182;
|
||||
|
||||
--box-bg: #171c2a;
|
||||
--stat-box-bg: #0b1018;
|
||||
--box-bg: #24273e;
|
||||
--stat-box-bg: #181b2d;
|
||||
--alert-bg: #3a1c61;
|
||||
--transparent-fg: #ffffffbb;
|
||||
--fade-out-box-bg-start: rgba(23, 28, 42, 0);
|
||||
--fade-out-box-bg-end: rgba(23, 28, 42, 1);
|
||||
--transparent-fg: #ffffff66;
|
||||
--fade-out-box-bg-start: rgba(36, 39, 62, 0);
|
||||
--fade-out-box-bg-end: rgba(36, 39, 62, 1);
|
||||
--opacity: 0.57;
|
||||
|
||||
--testnet: #1d486f;
|
||||
|
|
@ -98,12 +98,12 @@ $dropdown-link-active-bg: $active-bg;
|
|||
--taproot-light: #d5a90a;
|
||||
--taproot-dark: #9d7c05;
|
||||
|
||||
--green: #83fd00;
|
||||
--red: #ff3d00;
|
||||
--green: #3bcc49;
|
||||
--red: #dc3545;
|
||||
--yellow: #fff000;
|
||||
--grey: #7e7e7e;
|
||||
--grey: #6c757d;
|
||||
--tooltip-grey: #b1b1b1;
|
||||
--orange: #ff9f00;
|
||||
--orange: #b86d12;
|
||||
|
||||
--search-button: #4d2d77;
|
||||
--search-button-border: #472a6e;
|
||||
|
|
@ -64,6 +64,8 @@ bitcoin_checkconfig()
|
|||
echo "Missing data directory: ${bitcoin_data_dir}"
|
||||
exit 1
|
||||
fi
|
||||
touch "${bitcoin_data_dir}/debug.log"
|
||||
chmod 644 "${bitcoin_data_dir}/debug.log"
|
||||
chown -R "${bitcoin_user}:${bitcoin_group}" "${bitcoin_data_dir}"
|
||||
|
||||
if [ ! -f "${bitcoin_config_file}" ]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue