From cdc4a430cdef5881d01e7429a979c6bb9f23cc18 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 31 Oct 2024 09:52:34 +0100 Subject: [PATCH 01/60] [refactor] cleaning users.full_name --- frontend/src/app/services/services-api.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index 2b0f884ff..1f51c4afa 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -18,7 +18,6 @@ export interface IUser { subscription_tag: string; status: 'pending' | 'verified' | 'disabled'; features: string | null; - fullName: string | null; countryCode: string | null; imageMd5: string; ogRank: number | null; From 72ddb8c6a48ac274964846c99055e51a6ead1c63 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Thu, 14 Nov 2024 16:46:18 +0100 Subject: [PATCH 02/60] [accelerator] display payment errors, auto reload after 10 secs instead of 3 secs --- .../accelerate-checkout.component.html | 16 ++++++++++++---- .../accelerate-checkout.component.ts | 6 +++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html index df67de65c..644d3e9d5 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -1,10 +1,18 @@
@if (accelerateError) { -
-
-

Sorry, something went wrong!

+ @if (accelerateError.includes('Payment declined')) { +
+
+

{{ accelerateError }}

+
-
+ } @else { +
+
+

Sorry, something went wrong!

+
+
+ }
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 9d2d2ad46..763332ceb 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -543,7 +543,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { // Reset everything by reloading the page :D, can be improved const urlParams = new URLSearchParams(window.location.search); window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 3000); + }, 10000); } } }); @@ -643,7 +643,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { // Reset everything by reloading the page :D, can be improved const urlParams = new URLSearchParams(window.location.search); window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 3000); + }, 10000); } } }); @@ -738,7 +738,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { // Reset everything by reloading the page :D, can be improved const urlParams = new URLSearchParams(window.location.search); window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 3000); + }, 10000); } } }); From ba1ee15286c1bb79401b56eaee2fbd6ca2b58adb Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 22 Dec 2024 12:27:29 +0000 Subject: [PATCH 03/60] [accelerator] improve SCA UX --- .../accelerate-checkout.component.html | 9 +- .../accelerate-checkout.component.scss | 7 + .../accelerate-checkout.component.ts | 221 ++++++++++-------- 3 files changed, 135 insertions(+), 102 deletions(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html index df67de65c..cfe2beec0 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -1,4 +1,4 @@ -
+
@if (accelerateError) {
@@ -361,7 +361,7 @@

Payment to mempool.space for acceleration of txid {{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}

- @if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) { + @if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay)) {

Your account will be debited no more than {{ cost | number }} sats

@@ -484,6 +484,11 @@
}
+ @if (tokenizing) { +
+
+
+ }
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss index ad085ed20..75c6a397d 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss @@ -8,6 +8,13 @@ color: var(--green) } +.accelerate-checkout-inner { + &.input-disabled { + pointer-events: none; + opacity: 0.75; + } +} + .paymentMethod { padding: 10px; background-color: var(--secondary); diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index d6ac7f54f..236326e0d 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -76,6 +76,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { calculating = true; processing = false; + checkoutLocked = false; + tokenizing = false; selectedOption: 'wait' | 'accel'; cantPayReason = ''; quoteError = ''; // error fetching estimate or initial data @@ -504,55 +506,64 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.loadingApplePay = false; applePayButton.addEventListener('click', async event => { event.preventDefault(); - const tokenResult = await this.applePay.tokenize(); - if (tokenResult?.status === 'OK') { - const card = tokenResult.details?.card; - if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { - console.error(`Cannot retreive payment card details`); - this.accelerateError = 'apple_pay_no_card_details'; - this.processing = false; - return; - } - const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); - this.servicesApiService.accelerateWithApplePay$( - this.tx.txid, - tokenResult.token, - cardTag, - `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, - costUSD - ).subscribe({ - next: () => { + try { + // lock the checkout UI and show a loading spinner until the square modals are finished + this.checkoutLocked = true; + this.tokenizing = true; + const tokenResult = await this.applePay.tokenize(); + if (tokenResult?.status === 'OK') { + const card = tokenResult.details?.card; + if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { + console.error(`Cannot retreive payment card details`); + this.accelerateError = 'apple_pay_no_card_details'; this.processing = false; - this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); - this.audioService.playSound('ascend-chime-cartoon'); - if (this.applePay) { - this.applePay.destroy(); - } - setTimeout(() => { - this.moveToStep('paid'); - }, 1000); - }, - error: (response) => { - this.processing = false; - this.accelerateError = response.error; - if (!(response.status === 403 && response.error === 'not_available')) { - setTimeout(() => { - // Reset everything by reloading the page :D, can be improved - const urlParams = new URLSearchParams(window.location.search); - window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 3000); - } + return; } - }); - } else { - this.processing = false; - let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; - if (tokenResult.errors) { - errorMessage += ` and errors: ${JSON.stringify( - tokenResult.errors, - )}`; + const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); + this.servicesApiService.accelerateWithApplePay$( + this.tx.txid, + tokenResult.token, + cardTag, + `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, + costUSD + ).subscribe({ + next: () => { + this.processing = false; + this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); + this.audioService.playSound('ascend-chime-cartoon'); + if (this.applePay) { + this.applePay.destroy(); + } + setTimeout(() => { + this.moveToStep('paid'); + }, 1000); + }, + error: (response) => { + this.processing = false; + this.accelerateError = response.error; + if (!(response.status === 403 && response.error === 'not_available')) { + setTimeout(() => { + // Reset everything by reloading the page :D, can be improved + const urlParams = new URLSearchParams(window.location.search); + window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); + }, 3000); + } + } + }); + } else { + this.processing = false; + let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; + if (tokenResult.errors) { + errorMessage += ` and errors: ${JSON.stringify( + tokenResult.errors, + )}`; + } + throw new Error(errorMessage); } - throw new Error(errorMessage); + } finally { + // always unlock the checkout once we're finished + this.tokenizing = false; + this.checkoutLocked = false; } }); } catch (e) { @@ -603,63 +614,73 @@ export class AccelerateCheckout implements OnInit, OnDestroy { document.getElementById('google-pay-button').addEventListener('click', async event => { event.preventDefault(); - const tokenResult = await this.googlePay.tokenize(); - if (tokenResult?.status === 'OK') { - const card = tokenResult.details?.card; - if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { - console.error(`Cannot retreive payment card details`); - this.accelerateError = 'apple_pay_no_card_details'; - this.processing = false; - return; - } - const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2)); - if (!verificationToken) { - console.error(`SCA verification failed`); - this.accelerateError = 'SCA Verification Failed. Payment Declined.'; - this.processing = false; - return; - } - const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); - this.servicesApiService.accelerateWithGooglePay$( - this.tx.txid, - tokenResult.token, - verificationToken, - cardTag, - `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, - costUSD - ).subscribe({ - next: () => { + try { + // lock the checkout UI and show a loading spinner until the square modals are finished + this.checkoutLocked = true; + this.tokenizing = true; + const tokenResult = await this.googlePay.tokenize(); + tokenResult.token = 'ccof:customer-card-id-requires-verification'; + if (tokenResult?.status === 'OK') { + const card = tokenResult.details?.card; + if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { + console.error(`Cannot retreive payment card details`); + this.accelerateError = 'apple_pay_no_card_details'; this.processing = false; - this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); - this.audioService.playSound('ascend-chime-cartoon'); - if (this.googlePay) { - this.googlePay.destroy(); - } - setTimeout(() => { - this.moveToStep('paid'); - }, 1000); - }, - error: (response) => { - this.processing = false; - this.accelerateError = response.error; - if (!(response.status === 403 && response.error === 'not_available')) { - setTimeout(() => { - // Reset everything by reloading the page :D, can be improved - const urlParams = new URLSearchParams(window.location.search); - window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 3000); - } + return; } - }); - } else { - this.processing = false; - let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; - if (tokenResult.errors) { - errorMessage += ` and errors: ${JSON.stringify( - tokenResult.errors, - )}`; + const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2)); + if (!verificationToken) { + console.error(`SCA verification failed`); + this.accelerateError = 'SCA Verification Failed. Payment Declined.'; + this.processing = false; + return; + } + const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); + this.servicesApiService.accelerateWithGooglePay$( + this.tx.txid, + tokenResult.token, + verificationToken, + cardTag, + `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, + costUSD + ).subscribe({ + next: () => { + this.processing = false; + this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); + this.audioService.playSound('ascend-chime-cartoon'); + if (this.googlePay) { + this.googlePay.destroy(); + } + setTimeout(() => { + this.moveToStep('paid'); + }, 1000); + }, + error: (response) => { + this.processing = false; + this.accelerateError = response.error; + if (!(response.status === 403 && response.error === 'not_available')) { + setTimeout(() => { + // Reset everything by reloading the page :D, can be improved + const urlParams = new URLSearchParams(window.location.search); + window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); + }, 3000); + } + } + }); + } else { + this.processing = false; + let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; + if (tokenResult.errors) { + errorMessage += ` and errors: ${JSON.stringify( + tokenResult.errors, + )}`; + } + throw new Error(errorMessage); } - throw new Error(errorMessage); + } finally { + // always unlock the checkout once we're finished + this.tokenizing = false; + this.checkoutLocked = false; } }); } From 464fabf137a1deb6021b8c3bd9cc472d12c05c45 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 22 Dec 2024 12:27:29 +0000 Subject: [PATCH 04/60] [accelerator] reference counting for checkout lock --- .../accelerate-checkout.component.html | 4 +-- .../accelerate-checkout.component.ts | 35 +++++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html index cfe2beec0..150da04da 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -1,4 +1,4 @@ -
+
@if (accelerateError) {
@@ -484,7 +484,7 @@
}
- @if (tokenizing) { + @if (isTokenizing > 0) {
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 236326e0d..be40b92b1 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -76,8 +76,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { calculating = true; processing = false; - checkoutLocked = false; - tokenizing = false; + isCheckoutLocked = 0; // reference counter, 0 = unlocked, >0 = locked + isTokenizing = 0; // reference counter, 0 = false, >0 = true selectedOption: 'wait' | 'accel'; cantPayReason = ''; quoteError = ''; // error fetching estimate or initial data @@ -508,8 +508,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { event.preventDefault(); try { // lock the checkout UI and show a loading spinner until the square modals are finished - this.checkoutLocked = true; - this.tokenizing = true; + this.isCheckoutLocked++; + this.isTokenizing++; const tokenResult = await this.applePay.tokenize(); if (tokenResult?.status === 'OK') { const card = tokenResult.details?.card; @@ -520,6 +520,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return; } const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); + // keep checkout in loading state until the acceleration request completes + this.isTokenizing++; + this.isCheckoutLocked++; this.servicesApiService.accelerateWithApplePay$( this.tx.txid, tokenResult.token, @@ -535,12 +538,16 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.applePay.destroy(); } setTimeout(() => { + this.isTokenizing--; + this.isCheckoutLocked--; this.moveToStep('paid'); }, 1000); }, error: (response) => { this.processing = false; this.accelerateError = response.error; + this.isTokenizing--; + this.isCheckoutLocked--; if (!(response.status === 403 && response.error === 'not_available')) { setTimeout(() => { // Reset everything by reloading the page :D, can be improved @@ -562,8 +569,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } } finally { // always unlock the checkout once we're finished - this.tokenizing = false; - this.checkoutLocked = false; + this.isTokenizing--; + this.isCheckoutLocked--; } }); } catch (e) { @@ -616,10 +623,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy { event.preventDefault(); try { // lock the checkout UI and show a loading spinner until the square modals are finished - this.checkoutLocked = true; - this.tokenizing = true; + this.isCheckoutLocked++; + this.isTokenizing++; const tokenResult = await this.googlePay.tokenize(); - tokenResult.token = 'ccof:customer-card-id-requires-verification'; if (tokenResult?.status === 'OK') { const card = tokenResult.details?.card; if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { @@ -636,6 +642,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return; } const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); + // keep checkout in loading state until the acceleration request completes + this.isCheckoutLocked++; + this.isTokenizing++; this.servicesApiService.accelerateWithGooglePay$( this.tx.txid, tokenResult.token, @@ -646,6 +655,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { ).subscribe({ next: () => { this.processing = false; + this.isTokenizing--; + this.isCheckoutLocked--; this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); this.audioService.playSound('ascend-chime-cartoon'); if (this.googlePay) { @@ -658,6 +669,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { error: (response) => { this.processing = false; this.accelerateError = response.error; + this.isTokenizing--; + this.isCheckoutLocked--; if (!(response.status === 403 && response.error === 'not_available')) { setTimeout(() => { // Reset everything by reloading the page :D, can be improved @@ -679,8 +692,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } } finally { // always unlock the checkout once we're finished - this.tokenizing = false; - this.checkoutLocked = false; + this.isTokenizing--; + this.isCheckoutLocked--; } }); } From f49152d09dc46a05d7bf122356bdbe2ed58dad66 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 22 Dec 2024 15:17:39 +0000 Subject: [PATCH 05/60] [accelerator] keep checkout locked until request completes --- .../accelerate-checkout.component.ts | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index be40b92b1..a69b7b107 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -156,7 +156,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.accelerateError = null; this.timePaid = 0; this.btcpayInvoiceFailed = false; - this.moveToStep('summary'); + this.moveToStep('summary', true); } else { this.auth = auth; } @@ -165,11 +165,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy { const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('cash_request_id')) { // Redirected from cashapp - this.moveToStep('processing'); + this.moveToStep('processing', true); this.insertSquare(); this.setupSquare(); } else { - this.moveToStep('summary'); + this.moveToStep('summary', true); } this.conversionsSubscription = this.stateService.conversions$.subscribe( @@ -194,14 +194,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } if (changes.accelerating && this.accelerating) { if (this.step === 'processing' || this.step === 'paid') { - this.moveToStep('success'); + this.moveToStep('success', true); } else { // Edge case where the transaction gets accelerated by someone else or on another session this.closeModal(); } } } - moveToStep(step: CheckoutStep): void { + moveToStep(step: CheckoutStep, force: boolean = false): void { + if (this.isCheckoutLocked > 0 && !force) { + return; + } this.processing = false; this._step = step; if (this.timeoutTimer) { @@ -244,7 +247,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { closeModal(): void { this.completed.emit(true); - this.moveToStep('summary'); + this.moveToStep('summary', true); } /** @@ -395,7 +398,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.audioService.playSound('ascend-chime-cartoon'); this.showSuccess = true; this.estimateSubscription.unsubscribe(); - this.moveToStep('paid'); + this.moveToStep('paid', true); }, error: (response) => { this.processing = false; @@ -505,6 +508,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } this.loadingApplePay = false; applePayButton.addEventListener('click', async event => { + if (this.isCheckoutLocked > 0 || this.isTokenizing > 0) { + return; + } event.preventDefault(); try { // lock the checkout UI and show a loading spinner until the square modals are finished @@ -540,16 +546,16 @@ export class AccelerateCheckout implements OnInit, OnDestroy { setTimeout(() => { this.isTokenizing--; this.isCheckoutLocked--; - this.moveToStep('paid'); + this.moveToStep('paid', true); }, 1000); }, error: (response) => { this.processing = false; this.accelerateError = response.error; - this.isTokenizing--; - this.isCheckoutLocked--; if (!(response.status === 403 && response.error === 'not_available')) { setTimeout(() => { + this.isTokenizing--; + this.isCheckoutLocked--; // Reset everything by reloading the page :D, can be improved const urlParams = new URLSearchParams(window.location.search); window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); @@ -620,6 +626,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.loadingGooglePay = false; document.getElementById('google-pay-button').addEventListener('click', async event => { + if (this.isCheckoutLocked > 0 || this.isTokenizing > 0) { + return; + } event.preventDefault(); try { // lock the checkout UI and show a loading spinner until the square modals are finished @@ -655,15 +664,15 @@ export class AccelerateCheckout implements OnInit, OnDestroy { ).subscribe({ next: () => { this.processing = false; - this.isTokenizing--; - this.isCheckoutLocked--; this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); this.audioService.playSound('ascend-chime-cartoon'); if (this.googlePay) { this.googlePay.destroy(); } setTimeout(() => { - this.moveToStep('paid'); + this.isTokenizing--; + this.isCheckoutLocked--; + this.moveToStep('paid', true); }, 1000); }, error: (response) => { @@ -760,7 +769,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.cashAppPay.destroy(); } setTimeout(() => { - this.moveToStep('paid'); + this.moveToStep('paid', true); if (window.history.replaceState) { const urlParams = new URLSearchParams(window.location.search); window.history.replaceState(null, null, window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, '')); @@ -834,7 +843,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); this.audioService.playSound('ascend-chime-cartoon'); this.estimateSubscription.unsubscribe(); - this.moveToStep('paid'); + this.moveToStep('paid', true); } isLoggedIn(): boolean { From a5c67b5ca1bb13084e2e6e205a3a76f3cde9566b Mon Sep 17 00:00:00 2001 From: natsoni Date: Sun, 22 Dec 2024 20:16:07 +0100 Subject: [PATCH 06/60] Add websocket commands doc --- .../src/app/docs/api-docs/api-docs-data.ts | 2524 ++++++++++++++++- .../docs/api-docs/api-docs-nav.component.ts | 4 +- .../app/docs/api-docs/api-docs.component.html | 45 +- .../app/docs/api-docs/api-docs.component.scss | 18 + .../app/docs/api-docs/api-docs.component.ts | 28 +- 5 files changed, 2513 insertions(+), 106 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 1f83cabc9..c32baa3f7 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -13,96 +13,2442 @@ const emptyCodeSample = { const showJsExamplesDefault = { "": true, "testnet": true, "signet": true, "liquid": true, "liquidtestnet": false }; const showJsExamplesDefaultFalse = { "": false, "testnet": false, "signet": false, "liquid": false, "liquidtestnet": false }; -export const wsApiDocsData = { - showJsExamples: showJsExamplesDefault, - codeTemplate: { - curl: `/api/v1/ws`, - commonJS: ` - const { %{0}: { websocket } } = mempoolJS(); - - const ws = websocket.initClient({ - options: ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart'], - }); - - ws.addEventListener('message', function incoming({data}) { - const res = JSON.parse(data.toString()); - if (res.block) { - document.getElementById("result-blocks").textContent = JSON.stringify(res.block, undefined, 2); - } - if (res.mempoolInfo) { - document.getElementById("result-mempool-info").textContent = JSON.stringify(res.mempoolInfo, undefined, 2); - } - if (res.transactions) { - document.getElementById("result-transactions").textContent = JSON.stringify(res.transactions, undefined, 2); - } - if (res["mempool-blocks"]) { - document.getElementById("result-mempool-blocks").textContent = JSON.stringify(res["mempool-blocks"], undefined, 2); - } - }); - `, - esModule: ` - const { %{0}: { websocket } } = mempoolJS(); - - const ws = websocket.initServer({ - options: ["blocks", "stats", "mempool-blocks", "live-2h-chart"], - }); - - ws.on("message", function incoming(data) { - const res = JSON.parse(data.toString()); - if (res.block) { - console.log(res.block); - } - if (res.mempoolInfo) { - console.log(res.mempoolInfo); - } - if (res.transactions) { - console.log(res.transactions); - } - if (res["mempool-blocks"]) { - console.log(res["mempool-blocks"]); - } - }); - `, - python: `import websocket -import _thread -import time -import rel -import json - -rel.safe_read() - -def on_message(ws, message): - print(json.loads(message)) - -def on_error(ws, error): - print(error) - -def on_close(ws, close_status_code, close_msg): - print("### closed ###") - -def on_open(ws): - message = { "action": "init" } - ws.send(json.dumps(message)) - message = { "action": "want", "data": ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart', 'watch-mempool'] } - ws.send(json.dumps(message)) - -if __name__ == "__main__": - ws = websocket.WebSocketApp("wss://mempool.space/api/v1/ws", - on_open=on_open, - on_message=on_message, - on_error=on_error, - on_close=on_close) - - ws.run_forever(dispatcher=rel) # Set dispatcher to automatic reconnection - rel.signal(2, rel.abort) # Keyboard Interrupt - rel.dispatch() - `, +export const wsApiDocsData = [ + { + type: "category", + category: "general", + fragment: "general", + title: "General", + showConditions: bitcoinNetworks.concat(liquidNetworks) }, - codeSampleMainnet: emptyCodeSample, - codeSampleTestnet: emptyCodeSample, - codeSampleSignet: emptyCodeSample, - codeSampleLiquid: emptyCodeSample, -}; + { + type: "endpoint", + category: "general", + fragment: "live-data", + title: "Live Data", + description: { + default: "Subscribe to live data. Available: blocks, mempool-block, live-2h-chart, and stats." + }, + payload: '{ "action": "want", "data": ["mempool-blocks", "stats"] }', + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-blocks": [ + { + "blockSize": 1801614, + "blockVSize": 997936.5, + "nTx": 3391, + "totalFees": 8170664, + "medianFee": 6.011217160720601, + "feeRange": [ + 4.584615384615384, + 5, + 5.100456621004566, + 6.002319288751449, + 7.235398230088496, + 10.377668308702791, + 200 + ] + }, + ... + { + "blockSize": 198543075, + "blockVSize": 101691348, + "nTx": 249402, + "totalFees": 135312667, + "medianFee": 1.2559438783834156, + "feeRange": [ + 1.000685629033809, + 1.0020213063577312, + 1.0019080827758888, + 1.0227913345013278, + 1.1188648002395873, + 1.2559438783834156, + 1.4077952614964329, + 1.4079805737077244, + 1.5106880342499638, + 2.003440424869914, + 2.2713888268854894 + ] + } + ], + "mempoolInfo": { + "loaded": true, + "size": 264505, + "bytes": 108875402, + "usage": 649908688, + "total_fee": 1.61036575, + "maxmempool": 300000000, + "mempoolminfee": 0.00001858, + "minrelaytxfee": 0.00001, + "incrementalrelayfee": 0.00001, + "unbroadcastcount": 0, + "fullrbf": true + }, + "vBytesPerSecond": 1651, + "fees": { + "fastestFee": 7, + "halfHourFee": 6, + "hourFee": 5, + "economyFee": 4, + "minimumFee": 2 + }, + "da": { + "progressPercent": 32.49007936507937, + "difficultyChange": 0.7843046881601534, + "estimatedRetargetDate": 1735514828279, + "remainingBlocks": 1361, + "remainingTime": 811481279, + "previousRetarget": 4.429396745461176, + "previousTime": 1734312810, + "nextRetargetHeight": 876960, + "timeAvg": 596239, + "adjustedTimeAvg": 596239, + "timeOffset": 0, + "expectedBlocks": 650.895 + } +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-blocks": [ + { + "blockSize": 1009960, + "blockVSize": 997827.25, + "nTx": 3545, + "totalFees": 2844117938, + "medianFee": 2524.178404298769, + "feeRange": [ + 2010.9044259140476, + 2011.0887096774193, + 2011.2914608327453, + 2441.5893066980025, + 3541.35960591133, + 3936.6254416961133, + 6031.746031746032 + ] + }, + ... + ], + "mempoolInfo": { + "loaded": true, + "size": 517666, + "bytes": 168219654, + "usage": 855583264, + "total_fee": 133.53837564, + "maxmempool": 4096000000, + "mempoolminfee": 0.00001, + "minrelaytxfee": 0.00001, + "incrementalrelayfee": 0.00001, + "unbroadcastcount": 0, + "fullrbf": true + }, + "vBytesPerSecond": 358, + "fees": { + "fastestFee": 2525, + "halfHourFee": 2268, + "hourFee": 2082, + "economyFee": 2, + "minimumFee": 1 + }, + "da": { + "progressPercent": 45.882936507936506, + "difficultyChange": -51.21445794134847, + "estimatedRetargetDate": 1736046916382, + "remainingBlocks": 1091, + "remainingTime": 1343241382, + "previousRetarget": 255.61790932023905, + "previousTime": 1733564813, + "nextRetargetHeight": 3538080, + "timeAvg": 1200000, + "adjustedTimeAvg": 1231202, + "timeOffset": 0, + "expectedBlocks": 1898.1033333333332 + } +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{"mempool-blocks": [ + { + "blockSize": 1009960, + "blockVSize": 997827.25, + "nTx": 3545, + "totalFees": 2844117938, + "medianFee": 2524.178404298769, + "feeRange": [ + 2010.9044259140476, + 2011.0887096774193, + 2011.2914608327453, + 2441.5893066980025, + 3541.35960591133, + 3936.6254416961133, + 6031.746031746032 + ] + }, + ... + ], + "mempoolInfo": { + "loaded": true, + "size": 59, + "bytes": 9834, + "usage": 68832, + "total_fee": 0.00013935, + "maxmempool": 4096000000, + "mempoolminfee": 0.00001, + "minrelaytxfee": 0.00001, + "incrementalrelayfee": 0.00001, + "unbroadcastcount": 0, + "fullrbf": true + }, + "vBytesPerSecond": 28, + "da": { + "progressPercent": 68.60119047619048, + "difficultyChange": -2.913529439274176, + "estimatedRetargetDate": 1735095294116, + "remainingBlocks": 633, + "remainingTime": 391480116, + "previousRetarget": 2.0685719720386118, + "previousTime": 1733848494, + "nextRetargetHeight": 227808, + "timeAvg": 618452, + "adjustedTimeAvg": 618452, + "timeOffset": 0, + "expectedBlocks": 1425.5333333333333 + }, + "fees": { + "fastestFee": 1, + "halfHourFee": 1, + "hourFee": 1, + "economyFee": 1, + "minimumFee": 1 + }, +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-blocks": [ + { + "blockSize": 27409, + "blockVSize": 7675, + "nTx": 2, + "totalFees": 769, + "medianFee": 0, + "feeRange": [ + 0.10012450036039577, + 0.10012450036039577, + 0.10012450036039577, + 0.10012450036039577, + 0.10012450036039577, + 0.10012450036039577, + 0.10012450036039577 + ] + } + ], + "mempoolInfo": { + "loaded": true, + "size": 2, + "bytes": 7676, + "usage": 3568, + "total_fee": 0.00000769, + "maxmempool": 300000000, + "mempoolminfee": 0.000001, + "minrelaytxfee": 0.000001, + "unbroadcastcount": 0 + }, + "vBytesPerSecond": 60, + "fees": { + "fastestFee": 0.1, + "halfHourFee": 0.1, + "hourFee": 0.1, + "economyFee": 0.1, + "minimumFee": 0.1 + }, + "da": { + "progressPercent": 4.315476190476191, + "difficultyChange": null, + "estimatedRetargetDate": null, + "remainingBlocks": 1929, + "remainingTime": null, + "previousRetarget": null, + "previousTime": 1734698648, + "nextRetargetHeight": 3173184, + "timeAvg": 60448, + "adjustedTimeAvg": null, + "timeOffset": 0, + "expectedBlocks": 8.765 + } +}` + } + } + } + }, + { + type: "category", + category: "addresses", + fragment: "addresses", + title: "Addresses", + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: "endpoint", + category: "addresses", + fragment: "track-address", + title: "Track Address", + description: { + default: "Subscribe to a single address to receive live updates on new transactions having that address in input or output. address-transactions field contains new mempool transactions, and block-transactions contains new confirmed transactions." + }, + payload: '{ "track-address": "bc1qeldw4mqns26wew8swgpkt3fs364w3ehs046w2f" }', + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "block-transactions": [ + { + "txid": "9d3ea0d131c45450c135d549b62032019bc47a80368e14edc72caf38f5a88033", + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": "69da555a9c69788a3a081958457894e56b1ee6766bc72cecf881b1b4f327f78b", + "vout": 0, + "prevout": { + "scriptpubkey": "a914c9848245ae4f5d5934b5cbdfb79e04cdd337470b87", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 c9848245ae4f5d5934b5cbdfb79e04cdd337470b OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3L4YUynB4X44rJBY9CmiLMN8Wjti49JCYB", + "value": 24962957 + }, + "scriptsig": "0048304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f0147304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e014c695221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_72 304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f01 OP_PUSHBYTES_71 304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e01 OP_PUSHDATA1 5221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c0 OP_PUSHBYTES_33 03556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb695 OP_PUSHBYTES_33 031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb OP_PUSHNUM_3 OP_CHECKMULTISIG" + }, + ... + { + "txid": "43852d32c7ae6d362d446d090daa4d389f78ec77e6693f9248cd924dc0b1ecc3", + "vout": 1, + "prevout": { + "scriptpubkey": "a914a3aff5f5765f167c1582fd85517ddde83174118187", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 a3aff5f5765f167c1582fd85517ddde831741181 OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3GcWrnGFoNzbn1KaiP5czS5xPELdWcgDX2", + "value": 1719827 + }, + "scriptsig": "0047304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201483045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff144014c69522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_71 304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201 OP_PUSHBYTES_72 3045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff14401 OP_PUSHDATA1 522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 03650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b28 OP_PUSHBYTES_33 02510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a OP_PUSHBYTES_33 02985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f8 OP_PUSHNUM_3 OP_CHECKMULTISIG" + } + ], + "vout": [ + { + "scriptpubkey": "0014292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1q9yhuu4yty2xd8hmrxnw4yhavvtn7khm62uw38p", + "value": 57000 + }, + ... + { + "scriptpubkey": "0020e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_32 e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_type": "v0_p2wsh", + "scriptpubkey_address": "bc1quhruqrghgcca950rvhtrg7cpd7u8k6svpzgzmrjy8xyukacl5lkq0r8l2d", + "value": 17343523 + } + ], + "size": 5514, + "weight": 22056, + "sigops": 208, + "fee": 44000, + "status": { + "confirmed": true, + "block_height": 875602, + "block_hash": "000000000000000000016c0639b6c1a34d6659c231aa2de5849ab3377ed75020", + "block_time": 1734704791 + }, + "order": 864069877, + "vsize": 5514, + "adjustedVsize": 5514, + "feePerVsize": 7.979688066739209, + "adjustedFeePerVsize": 7.979688066739209, + "effectiveFeePerVsize": 7.979688066739209, + "firstSeen": 1734704590, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 191567 + }, + "flags": 1099511659526 + } + ] +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "block-transactions": [ + { + "txid": "9d3ea0d131c45450c135d549b62032019bc47a80368e14edc72caf38f5a88033", + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": "69da555a9c69788a3a081958457894e56b1ee6766bc72cecf881b1b4f327f78b", + "vout": 0, + "prevout": { + "scriptpubkey": "a914c9848245ae4f5d5934b5cbdfb79e04cdd337470b87", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 c9848245ae4f5d5934b5cbdfb79e04cdd337470b OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3L4YUynB4X44rJBY9CmiLMN8Wjti49JCYB", + "value": 24962957 + }, + "scriptsig": "0048304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f0147304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e014c695221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_72 304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f01 OP_PUSHBYTES_71 304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e01 OP_PUSHDATA1 5221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c0 OP_PUSHBYTES_33 03556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb695 OP_PUSHBYTES_33 031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb OP_PUSHNUM_3 OP_CHECKMULTISIG" + }, + ... + { + "txid": "43852d32c7ae6d362d446d090daa4d389f78ec77e6693f9248cd924dc0b1ecc3", + "vout": 1, + "prevout": { + "scriptpubkey": "a914a3aff5f5765f167c1582fd85517ddde83174118187", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 a3aff5f5765f167c1582fd85517ddde831741181 OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3GcWrnGFoNzbn1KaiP5czS5xPELdWcgDX2", + "value": 1719827 + }, + "scriptsig": "0047304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201483045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff144014c69522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_71 304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201 OP_PUSHBYTES_72 3045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff14401 OP_PUSHDATA1 522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 03650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b28 OP_PUSHBYTES_33 02510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a OP_PUSHBYTES_33 02985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f8 OP_PUSHNUM_3 OP_CHECKMULTISIG" + } + ], + "vout": [ + { + "scriptpubkey": "0014292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1q9yhuu4yty2xd8hmrxnw4yhavvtn7khm62uw38p", + "value": 57000 + }, + ... + { + "scriptpubkey": "0020e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_32 e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_type": "v0_p2wsh", + "scriptpubkey_address": "bc1quhruqrghgcca950rvhtrg7cpd7u8k6svpzgzmrjy8xyukacl5lkq0r8l2d", + "value": 17343523 + } + ], + "size": 5514, + "weight": 22056, + "sigops": 208, + "fee": 44000, + "status": { + "confirmed": true, + "block_height": 875602, + "block_hash": "000000000000000000016c0639b6c1a34d6659c231aa2de5849ab3377ed75020", + "block_time": 1734704791 + }, + "order": 864069877, + "vsize": 5514, + "adjustedVsize": 5514, + "feePerVsize": 7.979688066739209, + "adjustedFeePerVsize": 7.979688066739209, + "effectiveFeePerVsize": 7.979688066739209, + "firstSeen": 1734704590, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 191567 + }, + "flags": 1099511659526 + } + ] +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "block-transactions": [ + { + "txid": "9d3ea0d131c45450c135d549b62032019bc47a80368e14edc72caf38f5a88033", + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": "69da555a9c69788a3a081958457894e56b1ee6766bc72cecf881b1b4f327f78b", + "vout": 0, + "prevout": { + "scriptpubkey": "a914c9848245ae4f5d5934b5cbdfb79e04cdd337470b87", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 c9848245ae4f5d5934b5cbdfb79e04cdd337470b OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3L4YUynB4X44rJBY9CmiLMN8Wjti49JCYB", + "value": 24962957 + }, + "scriptsig": "0048304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f0147304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e014c695221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_72 304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f01 OP_PUSHBYTES_71 304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e01 OP_PUSHDATA1 5221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c0 OP_PUSHBYTES_33 03556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb695 OP_PUSHBYTES_33 031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb OP_PUSHNUM_3 OP_CHECKMULTISIG" + }, + ... + { + "txid": "43852d32c7ae6d362d446d090daa4d389f78ec77e6693f9248cd924dc0b1ecc3", + "vout": 1, + "prevout": { + "scriptpubkey": "a914a3aff5f5765f167c1582fd85517ddde83174118187", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 a3aff5f5765f167c1582fd85517ddde831741181 OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3GcWrnGFoNzbn1KaiP5czS5xPELdWcgDX2", + "value": 1719827 + }, + "scriptsig": "0047304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201483045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff144014c69522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_71 304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201 OP_PUSHBYTES_72 3045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff14401 OP_PUSHDATA1 522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 03650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b28 OP_PUSHBYTES_33 02510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a OP_PUSHBYTES_33 02985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f8 OP_PUSHNUM_3 OP_CHECKMULTISIG" + } + ], + "vout": [ + { + "scriptpubkey": "0014292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1q9yhuu4yty2xd8hmrxnw4yhavvtn7khm62uw38p", + "value": 57000 + }, + ... + { + "scriptpubkey": "0020e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_32 e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_type": "v0_p2wsh", + "scriptpubkey_address": "bc1quhruqrghgcca950rvhtrg7cpd7u8k6svpzgzmrjy8xyukacl5lkq0r8l2d", + "value": 17343523 + } + ], + "size": 5514, + "weight": 22056, + "sigops": 208, + "fee": 44000, + "status": { + "confirmed": true, + "block_height": 875602, + "block_hash": "000000000000000000016c0639b6c1a34d6659c231aa2de5849ab3377ed75020", + "block_time": 1734704791 + }, + "order": 864069877, + "vsize": 5514, + "adjustedVsize": 5514, + "feePerVsize": 7.979688066739209, + "adjustedFeePerVsize": 7.979688066739209, + "effectiveFeePerVsize": 7.979688066739209, + "firstSeen": 1734704590, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 191567 + }, + "flags": 1099511659526 + } + ] +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "block-transactions": [ + { + "txid": "9d3ea0d131c45450c135d549b62032019bc47a80368e14edc72caf38f5a88033", + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": "69da555a9c69788a3a081958457894e56b1ee6766bc72cecf881b1b4f327f78b", + "vout": 0, + "prevout": { + "scriptpubkey": "a914c9848245ae4f5d5934b5cbdfb79e04cdd337470b87", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 c9848245ae4f5d5934b5cbdfb79e04cdd337470b OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3L4YUynB4X44rJBY9CmiLMN8Wjti49JCYB", + "value": 24962957 + }, + "scriptsig": "0048304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f0147304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e014c695221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_72 304502210099219ee0cd5da341650078e3c63885b3cc2211069f2551cf436e0100f421e1760220349b4ec284255b458d6da539fa17314e8330459e0a653c254f775d4ec8f32b3f01 OP_PUSHBYTES_71 304402203a8353c5ee76a2e266432e5f993f882e05725297e64c0833cf44719f7dda8d3b022058a2f72e7739efd21657b4943cac60a0a3c749e712787f0e85726da4c3adcf8e01 OP_PUSHDATA1 5221027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c02103556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb69521031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb53ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 027f2a0df8e86535d08ca3e766a178f90c813d2dd1d55b0166e82518efbffb18c0 OP_PUSHBYTES_33 03556a35844b517e2fc8216701e2e0a64dbcbe62ad420ac6dd73dc79e69efeb695 OP_PUSHBYTES_33 031ef21bd55171032b7aec21ec82932735fb986f1d4d8611feee62ab38acf4a6bb OP_PUSHNUM_3 OP_CHECKMULTISIG" + }, + ... + { + "txid": "43852d32c7ae6d362d446d090daa4d389f78ec77e6693f9248cd924dc0b1ecc3", + "vout": 1, + "prevout": { + "scriptpubkey": "a914a3aff5f5765f167c1582fd85517ddde83174118187", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 a3aff5f5765f167c1582fd85517ddde831741181 OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "3GcWrnGFoNzbn1KaiP5czS5xPELdWcgDX2", + "value": 1719827 + }, + "scriptsig": "0047304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201483045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff144014c69522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "scriptsig_asm": "OP_0 OP_PUSHBYTES_71 304402205f83d22a0476158aa0986682c96ce2b2dab26c814968dba62905cdfeef1b3ac7022059438a3439bb18bd49242010c8a276ea6f1810d523042e679fa6679d60e89e0201 OP_PUSHBYTES_72 3045022100eb085df09e0fb4894090a5f39b9f2188392f7ac2847ed8255629baffc7371f170220120463b91d6c4bb8968fb3eda9012b88d13d8ca71de28e7a64b1dd88282ff14401 OP_PUSHDATA1 522103650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b282102510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a2102985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f853ae", + "is_coinbase": false, + "sequence": 4294967295, + "inner_redeemscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 03650083cbc9cd1da1224e0780bce1ee8abd5150c5252defd0edeccd3521610b28 OP_PUSHBYTES_33 02510ab30a6a97464ef0d61f71ec8b1d2325f12934ff15ba73579bfd0ac5f4fc1a OP_PUSHBYTES_33 02985b3be77f56a9a29c5f68d3c893d6c4d76ec8c07792f0291d375c29b71ee2f8 OP_PUSHNUM_3 OP_CHECKMULTISIG" + } + ], + "vout": [ + { + "scriptpubkey": "0014292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 292fce548b228cd3df6334dd525fac62e7eb5f7a", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1q9yhuu4yty2xd8hmrxnw4yhavvtn7khm62uw38p", + "value": 57000 + }, + ... + { + "scriptpubkey": "0020e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_32 e5c7c00d174631d2d1e365d6347b016fb87b6a0c08902d8e443989cb771fa7ec", + "scriptpubkey_type": "v0_p2wsh", + "scriptpubkey_address": "bc1quhruqrghgcca950rvhtrg7cpd7u8k6svpzgzmrjy8xyukacl5lkq0r8l2d", + "value": 17343523 + } + ], + "size": 5514, + "weight": 22056, + "sigops": 208, + "fee": 44000, + "status": { + "confirmed": true, + "block_height": 875602, + "block_hash": "000000000000000000016c0639b6c1a34d6659c231aa2de5849ab3377ed75020", + "block_time": 1734704791 + }, + "order": 864069877, + "vsize": 5514, + "adjustedVsize": 5514, + "feePerVsize": 7.979688066739209, + "adjustedFeePerVsize": 7.979688066739209, + "effectiveFeePerVsize": 7.979688066739209, + "firstSeen": 1734704590, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 191567 + }, + "flags": 1099511659526 + } + ] +}` + } + } + } + }, + { + type: "endpoint", + category: "addresses", + fragment: "track-addresses", + title: "Track Addresses", + description: { + default: "Subscribe to multiple addresses to receive live updates on new transactions having these addresses in input or output. Limits on the maximum number of tracked addresses apply. For higher tracking limits, consider upgrading to an enterprise sponsorship." + }, + payload: `{ + "track-addresses": [ + "bc1qeldw4mqns26wew8swgpkt3fs364w3ehs046w2f", + "bc1qjj09853tfpztjgrk4jeyzj4ml59fv9cmslv3c4gxxf57u0k3kxmqllx29y" + ] +}`, + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "multi-address-transactions": { + "bc1qjj09853tfpztjgrk4jeyzj4ml59fv9cmslv3c4gxxf57u0k3kxmqllx29y": { + "mempool": [], + "confirmed": [ + { + "txid": "1e4764f908f19b74284a889478b95d013c1bd36dc832dcb7eb36fe1801fed404", + "version": 2, + "locktime": 875625, + "vin": [ + { + "txid": "ce361fed5996aec6d440556383164e9e4e5b8be8c2a213c4b36ae711efda3b3f", + "vout": 1, + "prevout": { + "scriptpubkey": "0014257ba1ebc987831dbe8ee560419282483bf68588", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 257ba1ebc987831dbe8ee560419282483bf68588", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1qy4a6r67fs7p3m05wu4syry5zfqaldpvg8vsqzz", + "value": 1831200 + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "3044022028363f66fe74bdddf46d204cbf9844d4ef99d6fcb801f93f3ea1666ff51514340220058eb99790dd002323bd12afa0b62903cf72465d48c40cb11366dfa4eebbd87a01", + "020e625e13a81995f29ee828e31500b8454bd0b115f84dfa07d994eecd733efffa" + ], + "is_coinbase": false, + "sequence": 4294967294 + }, + ... + ], + "vout": [ + { + "scriptpubkey": "0020949e53d22b4844b92076acb2414abbfd0a96171b87d91c55063269ee3ed1b1b6", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_32 949e53d22b4844b92076acb2414abbfd0a96171b87d91c55063269ee3ed1b1b6", + "scriptpubkey_type": "v0_p2wsh", + "scriptpubkey_address": "bc1qjj09853tfpztjgrk4jeyzj4ml59fv9cmslv3c4gxxf57u0k3kxmqllx29y", + "value": 2546637 + } + ], + "size": 351, + "weight": 756, + "sigops": 2, + "fee": 4206, + "status": { + "confirmed": true, + "block_height": 875626, + "block_hash": "0000000000000000000086de1f4815ff0f7f0411d846301c5efa1e437130dc22", + "block_time": 1734720142 + }, + "order": 81067521, + "vsize": 189, + "adjustedVsize": 189, + "feePerVsize": 22.253968253968253, + "adjustedFeePerVsize": 22.253968253968253, + "effectiveFeePerVsize": 22.253968253968253, + "firstSeen": 1734719830, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 134866.5 + }, + "flags": 1099511640074 + } + ], + "removed": [] + } + } +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "multi-address-transactions": { + "bc1qjj09853tfpztjgrk4jeyzj4ml59fv9cmslv3c4gxxf57u0k3kxmqllx29y": { + "mempool": [], + "confirmed": [ + { + "txid": "1e4764f908f19b74284a889478b95d013c1bd36dc832dcb7eb36fe1801fed404", + "version": 2, + "locktime": 875625, + "vin": [ + { + "txid": "ce361fed5996aec6d440556383164e9e4e5b8be8c2a213c4b36ae711efda3b3f", + "vout": 1, + "prevout": { + "scriptpubkey": "0014257ba1ebc987831dbe8ee560419282483bf68588", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 257ba1ebc987831dbe8ee560419282483bf68588", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1qy4a6r67fs7p3m05wu4syry5zfqaldpvg8vsqzz", + "value": 1831200 + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "3044022028363f66fe74bdddf46d204cbf9844d4ef99d6fcb801f93f3ea1666ff51514340220058eb99790dd002323bd12afa0b62903cf72465d48c40cb11366dfa4eebbd87a01", + "020e625e13a81995f29ee828e31500b8454bd0b115f84dfa07d994eecd733efffa" + ], + "is_coinbase": false, + "sequence": 4294967294 + }, + ... + ], + "vout": [ + { + "scriptpubkey": "0020949e53d22b4844b92076acb2414abbfd0a96171b87d91c55063269ee3ed1b1b6", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_32 949e53d22b4844b92076acb2414abbfd0a96171b87d91c55063269ee3ed1b1b6", + "scriptpubkey_type": "v0_p2wsh", + "scriptpubkey_address": "bc1qjj09853tfpztjgrk4jeyzj4ml59fv9cmslv3c4gxxf57u0k3kxmqllx29y", + "value": 2546637 + } + ], + "size": 351, + "weight": 756, + "sigops": 2, + "fee": 4206, + "status": { + "confirmed": true, + "block_height": 875626, + "block_hash": "0000000000000000000086de1f4815ff0f7f0411d846301c5efa1e437130dc22", + "block_time": 1734720142 + }, + "order": 81067521, + "vsize": 189, + "adjustedVsize": 189, + "feePerVsize": 22.253968253968253, + "adjustedFeePerVsize": 22.253968253968253, + "effectiveFeePerVsize": 22.253968253968253, + "firstSeen": 1734719830, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 134866.5 + }, + "flags": 1099511640074 + } + ], + "removed": [] + } + } +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "multi-address-transactions": { + "bc1qjj09853tfpztjgrk4jeyzj4ml59fv9cmslv3c4gxxf57u0k3kxmqllx29y": { + "mempool": [], + "confirmed": [ + { + "txid": "1e4764f908f19b74284a889478b95d013c1bd36dc832dcb7eb36fe1801fed404", + "version": 2, + "locktime": 875625, + "vin": [ + { + "txid": "ce361fed5996aec6d440556383164e9e4e5b8be8c2a213c4b36ae711efda3b3f", + "vout": 1, + "prevout": { + "scriptpubkey": "0014257ba1ebc987831dbe8ee560419282483bf68588", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 257ba1ebc987831dbe8ee560419282483bf68588", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1qy4a6r67fs7p3m05wu4syry5zfqaldpvg8vsqzz", + "value": 1831200 + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "3044022028363f66fe74bdddf46d204cbf9844d4ef99d6fcb801f93f3ea1666ff51514340220058eb99790dd002323bd12afa0b62903cf72465d48c40cb11366dfa4eebbd87a01", + "020e625e13a81995f29ee828e31500b8454bd0b115f84dfa07d994eecd733efffa" + ], + "is_coinbase": false, + "sequence": 4294967294 + }, + ... + ], + "vout": [ + { + "scriptpubkey": "0020949e53d22b4844b92076acb2414abbfd0a96171b87d91c55063269ee3ed1b1b6", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_32 949e53d22b4844b92076acb2414abbfd0a96171b87d91c55063269ee3ed1b1b6", + "scriptpubkey_type": "v0_p2wsh", + "scriptpubkey_address": "bc1qjj09853tfpztjgrk4jeyzj4ml59fv9cmslv3c4gxxf57u0k3kxmqllx29y", + "value": 2546637 + } + ], + "size": 351, + "weight": 756, + "sigops": 2, + "fee": 4206, + "status": { + "confirmed": true, + "block_height": 875626, + "block_hash": "0000000000000000000086de1f4815ff0f7f0411d846301c5efa1e437130dc22", + "block_time": 1734720142 + }, + "order": 81067521, + "vsize": 189, + "adjustedVsize": 189, + "feePerVsize": 22.253968253968253, + "adjustedFeePerVsize": 22.253968253968253, + "effectiveFeePerVsize": 22.253968253968253, + "firstSeen": 1734719830, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 134866.5 + }, + "flags": 1099511640074 + } + ], + "removed": [] + } + } +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "multi-address-transactions": { + "ex1qzq0h0wvnnh9xpd508fzxaft0nu9wjmdvzalu6f": { + "mempool": [], + "confirmed": [ + { + "txid": "d61ad73b64895ccabd32816643554c676891bdb52da0fba2b37079e04c4c4b2c", + "version": 2, + "locktime": 3171528, + "vin": [ + { + "txid": "4847a0627952a0bcad6c8947d46a0e5b13eefbcfbf76246ea16a1a7c82bcc49b", + "vout": 2, + "prevout": { + "scriptpubkey": "00144d72c2967e1a581c0e71e82d65e99523a9149d02", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 4d72c2967e1a581c0e71e82d65e99523a9149d02", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "ex1qf4ev99n7rfvpcrn3aqkkt6v4yw53f8gznv9paa", + "valuecommitment": "09af208bbc0b9809aff4368dee81f74f178f77f844e7dfc5d70615bc757fa8b2f9", + "assetcommitment": "0a2ca17c42fadd887373c371e44cf49c6cd64c3081e23eef3275bdace0b8c674b5" + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "30440220653c6e1bd3de5bd9a56cbb6eb246834724667a5c5d12dc07107edc7c72bd6634022008d1f770dc9ba624bb250bba3a5254aa633f01a9bcb2a85aedc5b251e338b7b301", + "03fb2f0245e19f9e886fce54894558bbbcf50bf9576245e60a4c9780f7447eaf22" + ], + "is_coinbase": false, + "sequence": 4294967294, + "is_pegin": false + }, + { + "txid": "ea5f690853ece5549807862a153357092c4f7dbe10886b86b84f87a3201dd8dc", + "vout": 0, + "prevout": { + "scriptpubkey": "0014a5996021b4001325b1aa85c3bf400516855a6e05", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 a5996021b4001325b1aa85c3bf400516855a6e05", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "ex1q5kvkqgd5qqfjtvd2shpm7sq9z6z45ms9ma7ywz", + "valuecommitment": "09f36fde0f51390cdf2ee6830b3c696569c3f9c5855ce26bd4f6d0280a83b86ecf", + "assetcommitment": "0bfcd8fcfaebfa41b596a89aa55fbf2eaa8c383ec71e8d9d0d461ea645d8d1bc45" + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "30440220033208f3e37c35009ba00472a67899222afbd5cc12b0d3906d2eec6f50a058510220607d4c5de43459e38158ee1cbe5e6a74c155041cecdbf961a077a952ee1e543601", + "0394d6ecb2f5db9fdeb0f7ac5301ea148704fc6986fdb8181bddc1d2eec9e99c32" + ], + "is_coinbase": false, + "sequence": 4294967294, + "is_pegin": false + }, + { + "txid": "23e63b888d5da3ce1193bb4a74a0762d78904cfa7a6307ff47e91054d961208b", + "vout": 2, + "prevout": { + "scriptpubkey": "00144990783e871e57fa2499f00c5f6f4ddc2602e7c8", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 4990783e871e57fa2499f00c5f6f4ddc2602e7c8", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "ex1qfxg8s058retl5fye7qx97m6dmsnq9e7gq0dcee", + "valuecommitment": "0816440695f0c47ce471c7e10a93d36aee4554b46ed269bfa8390dd9db69409537", + "assetcommitment": "0a5a0eb7cab779cb6ce5d6517c73e244075eea15fbd54a7beb34710862aef58359" + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "3044022051e5482a486f55cfd5ae25062b0252e13de9bfa11a9c7e5f608ac6e03c62dc8902201d2b47a27fc07999973ec44e4569b4b3fcc338b1c3977173cfeab9cccde0b3e301", + "025991a68daafc95494019c228855999db8f19c872fd3f58bac6ff149db7b53cff" + ], + "is_coinbase": false, + "sequence": 4294967294, + "is_pegin": false + }, + { + "txid": "2bbeb9440d3c08a1d3cd9acf5959ee740a6a64ffcaa4aa2b43e30026a2a40334", + "vout": 2, + "prevout": { + "scriptpubkey": "00144d72c2967e1a581c0e71e82d65e99523a9149d02", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 4d72c2967e1a581c0e71e82d65e99523a9149d02", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "ex1qf4ev99n7rfvpcrn3aqkkt6v4yw53f8gznv9paa", + "valuecommitment": "09d0c574d61d50065a2e398fb7252315b65176bf97de1d180d337f3aadfaa0e53e", + "assetcommitment": "0b82ede6b9a6cb9505a7f6bcc76f72caa3228de193debf4e586d60baebeaef0ab5" + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "3044022033ab9ea81a21b0f917792097ed69ab3724957a5e5d3a0430a0b2a16e0a74d8750220202859dc7e53998f5dc4b424321b7a711e3ff6517422a099504b337e30ec8acb01", + "03fb2f0245e19f9e886fce54894558bbbcf50bf9576245e60a4c9780f7447eaf22" + ], + "is_coinbase": false, + "sequence": 4294967294, + "is_pegin": false + } + ], + "vout": [ + { + "scriptpubkey": "a914e185d1192f34d55ba3fbd15408168f339683d80287", + "scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 e185d1192f34d55ba3fbd15408168f339683d802 OP_EQUAL", + "scriptpubkey_type": "p2sh", + "scriptpubkey_address": "H3jyk9ipDU5efhHW9n52xCY78HNFAQTy78", + "valuecommitment": "08f676101a27d784f1f89765ff33ad5a1e95ab2081da76b29ef97bdfaf309e1318", + "assetcommitment": "0a115106f540daae5a0a7cf66dcf07a69dc2faffb917e82f340bcdfc7da143228b" + }, + { + "scriptpubkey": "0014101f77b9939dca60b68f3a446ea56f9f0ae96dac", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 101f77b9939dca60b68f3a446ea56f9f0ae96dac", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "ex1qzq0h0wvnnh9xpd508fzxaft0nu9wjmdvzalu6f", + "valuecommitment": "09bec5886710680e125b52ac99f6aee452984847cddc57abaa96eb8cc360f80104", + "assetcommitment": "0adfd85f43988146c1878e98bc5e6206f368280cb03760c37e86ec0bd39005d0cd" + }, + { + "scriptpubkey": "00140fe27684e78285d508073f2b8a3a6c884515d1a9", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 0fe27684e78285d508073f2b8a3a6c884515d1a9", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "ex1qpl38dp88s2za2zq88u4c5wnv3pz3t5dfha22k9", + "valuecommitment": "08bef8c28296cf050802c943d46aa539d2f5280e9b9471db928746480815cf5457", + "assetcommitment": "0a5a032f72df6fba7f1acd7230f44cdf41ce27926e48e262ffdfea18efd19e0439" + }, + { + "scriptpubkey": "", + "scriptpubkey_asm": "", + "scriptpubkey_type": "fee", + "value": 394, + "asset": "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" + } + ], + "size": 13955, + "weight": 15713, + "sigops": 0, + "fee": 394, + "status": { + "confirmed": true, + "block_height": 3171530, + "block_hash": "400270631b0f66d70cd6a045f36bb3f37c9076688fd496669d5da2a7245392d9", + "block_time": 1734720368 + }, + "order": 743132236, + "vsize": 3929, + "adjustedVsize": 3928.25, + "feePerVsize": 0.10029911538216763, + "adjustedFeePerVsize": 0.10029911538216763, + "effectiveFeePerVsize": 0.10027996945787732, + "firstSeen": 1734720314, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 6972.5 + }, + "flags": 1099511633962, + "cpfpChecked": true, + "cpfpUpdated": 1734720355424 + } + ], + "removed": [] + } + } +}` + } + } + } + }, + { + type: "category", + category: "transactions", + fragment: "transactions", + title: "Transactions", + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: "endpoint", + category: "transactions", + fragment: "track-tx", + title: "Track Transaction", + description: { + default: "Subscribe to a transaction to receive live updates on its confirmation status and position in the mempool." + }, + payload: '{ "track-tx": "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07" }', + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "txPosition": { + "txid": [ + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07" + ], + "position": { + "block": 0, + "vsize": 726868 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + } +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "txPosition": { + "txid": [ + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07" + ], + "position": { + "block": 0, + "vsize": 726868 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + } +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "txPosition": { + "txid": [ + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07" + ], + "position": { + "block": 0, + "vsize": 726868 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + } +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "txPosition": { + "txid": [ + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07" + ], + "position": { + "block": 0, + "vsize": 726868 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + } +}` + } + } + } + }, + { + type: "endpoint", + category: "transactions", + fragment: "track-txs", + title: "Track Transactions", + description: { + default: "Subscribe to multiple transactions to receive live updates on their status and position in the mempool. Limits on the maximum number of tracked addresses apply. For higher tracking limits, consider upgrading to an enterprise sponsorship." + }, + payload: `{ + "track-txs": [ + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07", + "941df06064c290b4627e92bdbf3bff7c0e97aab33e273c2a20404f9cfd21b607" + ] + }`, showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "tracked-txs": { + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07": { + "position": { + "block": 0, + "vsize": 434494 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + }, + "941df06064c290b4627e92bdbf3bff7c0e97aab33e273c2a20404f9cfd21b607": { + "position": { + "block": 2, + "vsize": 932479.5 + } + } + } +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "tracked-txs": { + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07": { + "position": { + "block": 0, + "vsize": 434494 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + }, + "941df06064c290b4627e92bdbf3bff7c0e97aab33e273c2a20404f9cfd21b607": { + "position": { + "block": 2, + "vsize": 932479.5 + } + } + } +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "tracked-txs": { + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07": { + "position": { + "block": 0, + "vsize": 434494 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + }, + "941df06064c290b4627e92bdbf3bff7c0e97aab33e273c2a20404f9cfd21b607": { + "position": { + "block": 2, + "vsize": 932479.5 + } + } + } +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "tracked-txs": { + "8a4666c6d22ce74fa47e1c4fdb09af556a234cc6a606539a75caf66ba44a2d07": { + "position": { + "block": 0, + "vsize": 434494 + }, + "cpfp": { + "ancestors": [ + { + "txid": "d509a6b8f36149588f9f48dc25fa5e37fc00dee781aed6da1113066c56f04879", + "fee": 605, + "weight": 520 + } + ], + "bestDescendant": null, + "descendants": [ + { + "txid": "28d3c592a9a8103d53c784aa539908f4dc5f9c463e179f0eae5dc5f349bdb00f", + "fee": 2501, + "weight": 816 + } + ], + "effectiveFeePerVsize": 5.12063778580024, + "sigops": 0, + "adjustedVsize": 130 + } + }, + "941df06064c290b4627e92bdbf3bff7c0e97aab33e273c2a20404f9cfd21b607": { + "position": { + "block": 2, + "vsize": 932479.5 + } + } + } +}` + } + } + } + }, + { + type: "category", + category: "mempool", + fragment: "mempool", + title: "Mempool", + showConditions: bitcoinNetworks.concat(liquidNetworks) + }, + { + type: "endpoint", + category: "mempool", + fragment: "track-mempool", + title: "Track Mempool", + description: { + default: "Subscribe to new mempool events, such as new transactions entering the mempool. Available fields: added, removed, mined, replaced.
Because this is potentially a lot of data, consider using the track-mempool-txids endpoint described below instead, or upgrade to an enterprise sponsorship." + }, + payload: '{ "track-mempool": true }', + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-transactions": { + "sequence": 81419, + "added": [ + { + "txid": "6229c0784bc776be22a5ee84e0e3d9b8f9e17843f079a8444b03bdc98b77d229", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "b4b324e3bff7ee0a7e664e8c03df1fe3a0bd53e5685ea6b10abb5f89ba1b2ead", + "vout": 5, + "prevout": { + "scriptpubkey": "76a914b54afb58f0faa9d1bde2ed755bc56ef1e4a4e24188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 b54afb58f0faa9d1bde2ed755bc56ef1e4a4e241 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "1HXb8YtsgBhFWdYezjd6bt7Dw4UGKyZo54", + "value": 17000 + }, + "scriptsig": "4830450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a83649404012103cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "scriptsig_asm": "OP_PUSHBYTES_72 30450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a8364940401 OP_PUSHBYTES_33 03cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "is_coinbase": false, + "sequence": 4294967293 + } + ], + "vout": [ + { + "scriptpubkey": "76a91401603bd82a5d5a6e8c6df5d9ae662b9fc5db60f288ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 01603bd82a5d5a6e8c6df5d9ae662b9fc5db60f2 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "18GxdcLgNtRUc8v5TNJtPnvoi8jMVWxvb", + "value": 10419 + }, + { + "scriptpubkey": "76a914338ad842d236486627834bf9f5e182c7a8aa937188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 338ad842d236486627834bf9f5e182c7a8aa9371 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "15hXntT6oUKNhtk4FWvuGPQJDX47wpbAaa", + "value": 5396 + } + ], + "size": 226, + "weight": 904, + "sigops": 8, + "fee": 1185, + "status": { + "confirmed": false + }, + "order": 701659019, + "vsize": 226, + "adjustedVsize": 226, + "feePerVsize": 5.243362831858407, + "adjustedFeePerVsize": 5.243362831858407, + "effectiveFeePerVsize": 5.243362831858407, + "firstSeen": 1734893382, + "uid": 429139, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 125270 + }, + "flags": 1099511628809 + }, + ... + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-transactions": { + "sequence": 81419, + "added": [ + { + "txid": "6229c0784bc776be22a5ee84e0e3d9b8f9e17843f079a8444b03bdc98b77d229", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "b4b324e3bff7ee0a7e664e8c03df1fe3a0bd53e5685ea6b10abb5f89ba1b2ead", + "vout": 5, + "prevout": { + "scriptpubkey": "76a914b54afb58f0faa9d1bde2ed755bc56ef1e4a4e24188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 b54afb58f0faa9d1bde2ed755bc56ef1e4a4e241 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "1HXb8YtsgBhFWdYezjd6bt7Dw4UGKyZo54", + "value": 17000 + }, + "scriptsig": "4830450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a83649404012103cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "scriptsig_asm": "OP_PUSHBYTES_72 30450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a8364940401 OP_PUSHBYTES_33 03cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "is_coinbase": false, + "sequence": 4294967293 + } + ], + "vout": [ + { + "scriptpubkey": "76a91401603bd82a5d5a6e8c6df5d9ae662b9fc5db60f288ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 01603bd82a5d5a6e8c6df5d9ae662b9fc5db60f2 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "18GxdcLgNtRUc8v5TNJtPnvoi8jMVWxvb", + "value": 10419 + }, + { + "scriptpubkey": "76a914338ad842d236486627834bf9f5e182c7a8aa937188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 338ad842d236486627834bf9f5e182c7a8aa9371 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "15hXntT6oUKNhtk4FWvuGPQJDX47wpbAaa", + "value": 5396 + } + ], + "size": 226, + "weight": 904, + "sigops": 8, + "fee": 1185, + "status": { + "confirmed": false + }, + "order": 701659019, + "vsize": 226, + "adjustedVsize": 226, + "feePerVsize": 5.243362831858407, + "adjustedFeePerVsize": 5.243362831858407, + "effectiveFeePerVsize": 5.243362831858407, + "firstSeen": 1734893382, + "uid": 429139, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 125270 + }, + "flags": 1099511628809 + }, + ... + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-transactions": { + "sequence": 81419, + "added": [ + { + "txid": "6229c0784bc776be22a5ee84e0e3d9b8f9e17843f079a8444b03bdc98b77d229", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "b4b324e3bff7ee0a7e664e8c03df1fe3a0bd53e5685ea6b10abb5f89ba1b2ead", + "vout": 5, + "prevout": { + "scriptpubkey": "76a914b54afb58f0faa9d1bde2ed755bc56ef1e4a4e24188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 b54afb58f0faa9d1bde2ed755bc56ef1e4a4e241 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "1HXb8YtsgBhFWdYezjd6bt7Dw4UGKyZo54", + "value": 17000 + }, + "scriptsig": "4830450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a83649404012103cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "scriptsig_asm": "OP_PUSHBYTES_72 30450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a8364940401 OP_PUSHBYTES_33 03cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "is_coinbase": false, + "sequence": 4294967293 + } + ], + "vout": [ + { + "scriptpubkey": "76a91401603bd82a5d5a6e8c6df5d9ae662b9fc5db60f288ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 01603bd82a5d5a6e8c6df5d9ae662b9fc5db60f2 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "18GxdcLgNtRUc8v5TNJtPnvoi8jMVWxvb", + "value": 10419 + }, + { + "scriptpubkey": "76a914338ad842d236486627834bf9f5e182c7a8aa937188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 338ad842d236486627834bf9f5e182c7a8aa9371 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "15hXntT6oUKNhtk4FWvuGPQJDX47wpbAaa", + "value": 5396 + } + ], + "size": 226, + "weight": 904, + "sigops": 8, + "fee": 1185, + "status": { + "confirmed": false + }, + "order": 701659019, + "vsize": 226, + "adjustedVsize": 226, + "feePerVsize": 5.243362831858407, + "adjustedFeePerVsize": 5.243362831858407, + "effectiveFeePerVsize": 5.243362831858407, + "firstSeen": 1734893382, + "uid": 429139, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 125270 + }, + "flags": 1099511628809 + }, + ... + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-transactions": { + "sequence": 81419, + "added": [ + { + "txid": "6229c0784bc776be22a5ee84e0e3d9b8f9e17843f079a8444b03bdc98b77d229", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "b4b324e3bff7ee0a7e664e8c03df1fe3a0bd53e5685ea6b10abb5f89ba1b2ead", + "vout": 5, + "prevout": { + "scriptpubkey": "76a914b54afb58f0faa9d1bde2ed755bc56ef1e4a4e24188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 b54afb58f0faa9d1bde2ed755bc56ef1e4a4e241 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "1HXb8YtsgBhFWdYezjd6bt7Dw4UGKyZo54", + "value": 17000 + }, + "scriptsig": "4830450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a83649404012103cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "scriptsig_asm": "OP_PUSHBYTES_72 30450221008e9b91aae7b4705841c97dc99d6ab233f10ff9b97d7c139be08634d2f0f5f66f02205d67eae8c830ed0979e169403d13c0f43efd78edbb9a344390245f5a8364940401 OP_PUSHBYTES_33 03cf9fad8b202384de9ef010129a62b8249920a6205fe53cc0efbea9eb0db595e7", + "is_coinbase": false, + "sequence": 4294967293 + } + ], + "vout": [ + { + "scriptpubkey": "76a91401603bd82a5d5a6e8c6df5d9ae662b9fc5db60f288ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 01603bd82a5d5a6e8c6df5d9ae662b9fc5db60f2 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "18GxdcLgNtRUc8v5TNJtPnvoi8jMVWxvb", + "value": 10419 + }, + { + "scriptpubkey": "76a914338ad842d236486627834bf9f5e182c7a8aa937188ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 338ad842d236486627834bf9f5e182c7a8aa9371 OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "15hXntT6oUKNhtk4FWvuGPQJDX47wpbAaa", + "value": 5396 + } + ], + "size": 226, + "weight": 904, + "sigops": 8, + "fee": 1185, + "status": { + "confirmed": false + }, + "order": 701659019, + "vsize": 226, + "adjustedVsize": 226, + "feePerVsize": 5.243362831858407, + "adjustedFeePerVsize": 5.243362831858407, + "effectiveFeePerVsize": 5.243362831858407, + "firstSeen": 1734893382, + "uid": 429139, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 125270 + }, + "flags": 1099511628809 + }, + ... + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + } + } + }, + { + type: "endpoint", + category: "mempool", + fragment: "track-mempool-txids", + title: "Track Mempool Txids", + description: { + default: "Low-bandwith substitute to the above command track-mempool: subscribe to new mempool events, such as new transactions entering the mempool, but only transaction IDs are returned to save bandwith. Available fields: added, removed, mined, replaced." + }, + payload: '{ "track-mempool-txids": true }', + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-txids": { + "sequence": 79919, + "added": [ + "4bbb648ab194aaaf9188bccc6efcdcbb59c8485115a7384972c8287782206a0f", + "f7883f3784829d1e741e696bdceec488eeb53fe0b69b0eca574ac9f2e7e8e117", + "784e8e3b182c29798660bf42befb5c6479148c7d90c0d6eea032b89418e7cc3b", + "d3920a7be05269d859bd89b08a6546dc6d6dd523dbc5f7b62b9c0c5eedc43292", + "de6078d584cb5f4a27c3f0bb3d8bbb16b3d5f8303237391f390d0ee9e84d0099", + "39fcbd6e0ec0ad49405f19c72bb033f578147181b77dbe47044f80b0b7604ab5", + "47ed060004fab3fb5fa4885008aa2cadbe3335655f1303231abfe89b4b0c9bd9" + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-txids": { + "sequence": 79919, + "added": [ + "4bbb648ab194aaaf9188bccc6efcdcbb59c8485115a7384972c8287782206a0f", + "f7883f3784829d1e741e696bdceec488eeb53fe0b69b0eca574ac9f2e7e8e117", + "784e8e3b182c29798660bf42befb5c6479148c7d90c0d6eea032b89418e7cc3b", + "d3920a7be05269d859bd89b08a6546dc6d6dd523dbc5f7b62b9c0c5eedc43292", + "de6078d584cb5f4a27c3f0bb3d8bbb16b3d5f8303237391f390d0ee9e84d0099", + "39fcbd6e0ec0ad49405f19c72bb033f578147181b77dbe47044f80b0b7604ab5", + "47ed060004fab3fb5fa4885008aa2cadbe3335655f1303231abfe89b4b0c9bd9" + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-txids": { + "sequence": 79919, + "added": [ + "4bbb648ab194aaaf9188bccc6efcdcbb59c8485115a7384972c8287782206a0f", + "f7883f3784829d1e741e696bdceec488eeb53fe0b69b0eca574ac9f2e7e8e117", + "784e8e3b182c29798660bf42befb5c6479148c7d90c0d6eea032b89418e7cc3b", + "d3920a7be05269d859bd89b08a6546dc6d6dd523dbc5f7b62b9c0c5eedc43292", + "de6078d584cb5f4a27c3f0bb3d8bbb16b3d5f8303237391f390d0ee9e84d0099", + "39fcbd6e0ec0ad49405f19c72bb033f578147181b77dbe47044f80b0b7604ab5", + "47ed060004fab3fb5fa4885008aa2cadbe3335655f1303231abfe89b4b0c9bd9" + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "mempool-txids": { + "sequence": 79919, + "added": [ + "4bbb648ab194aaaf9188bccc6efcdcbb59c8485115a7384972c8287782206a0f", + "f7883f3784829d1e741e696bdceec488eeb53fe0b69b0eca574ac9f2e7e8e117", + "784e8e3b182c29798660bf42befb5c6479148c7d90c0d6eea032b89418e7cc3b", + "d3920a7be05269d859bd89b08a6546dc6d6dd523dbc5f7b62b9c0c5eedc43292", + "de6078d584cb5f4a27c3f0bb3d8bbb16b3d5f8303237391f390d0ee9e84d0099", + "39fcbd6e0ec0ad49405f19c72bb033f578147181b77dbe47044f80b0b7604ab5", + "47ed060004fab3fb5fa4885008aa2cadbe3335655f1303231abfe89b4b0c9bd9" + ], + "removed": [], + "mined": [], + "replaced": [] + } +}` + }, + } + } + }, + { + type: "endpoint", + category: "mempool", + fragment: "track-mempool-block", + title: "Track Mempool Block", + description: { + default: "Subscribe to live mempool projected block template, index 0 being the first mempool block.
A full set of stripped transactions in that block is returned when the subscription starts, and deltas (removed and added transactions) are then sent every time the mempool changes." + }, + payload: '{ "track-mempool-block": 0 }', + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "projected-block-transactions": { + "index": 0, + "sequence": 80270, + "delta": { + "added": [ + [ + "172b34fb099d80f61b65d1c107c4f25665c8f50e30c1371b2e6fbced62991d58", + 2000, + 171.25, + 5942725, + 11.68, + 1099511631877, + 1734881537 + ], + ... + ], + "removed": [ + "956a6eee382214631c3299e0410565e05fbd6328c89fa746efab6371705aca2a", + ... + ], + "changed": [] + } + } +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "projected-block-transactions": { + "index": 0, + "sequence": 80270, + "delta": { + "added": [ + [ + "172b34fb099d80f61b65d1c107c4f25665c8f50e30c1371b2e6fbced62991d58", + 2000, + 171.25, + 5942725, + 11.68, + 1099511631877, + 1734881537 + ], + ... + ], + "removed": [ + "956a6eee382214631c3299e0410565e05fbd6328c89fa746efab6371705aca2a", + ... + ], + "changed": [] + } + } +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "projected-block-transactions": { + "index": 0, + "sequence": 80270, + "delta": { + "added": [ + [ + "172b34fb099d80f61b65d1c107c4f25665c8f50e30c1371b2e6fbced62991d58", + 2000, + 171.25, + 5942725, + 11.68, + 1099511631877, + 1734881537 + ], + ... + ], + "removed": [ + "956a6eee382214631c3299e0410565e05fbd6328c89fa746efab6371705aca2a", + ... + ], + "changed": [] + } + } +}` + }, + codeSampleLiquid: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "projected-block-transactions": { + "index": 0, + "sequence": 80270, + "delta": { + "added": [ + [ + "172b34fb099d80f61b65d1c107c4f25665c8f50e30c1371b2e6fbced62991d58", + 2000, + 171.25, + 5942725, + 11.68, + 1099511631877, + 1734881537 + ], + ... + ], + "removed": [ + "956a6eee382214631c3299e0410565e05fbd6328c89fa746efab6371705aca2a", + ... + ], + "changed": [] + } + } +}` + }, + } + } + }, + { + type: "endpoint", + category: "mempool", + fragment: "track-rbf", + title: "Track Mempool RBF Transactions", + description: { + default: "Subscribe to new RBF events." + }, + payload: '{ "track-rbf": "all" }', + showConditions: bitcoinNetworks, + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "rbfLatest": [ + { + "tx": { + "txid": "cc6cb210f7ec32660fe4d46984ef64b64143fb02dc7ed70578c32b5f338ef6d6", + "fee": 8280, + "vsize": 204, + "value": 156397, + "rate": 10, + "time": 1734876576, + "rbf": true, + "fullRbf": false + }, + "time": 1734876576, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "4e94c23e075cf9c2b4ccaf32e3652b8b1bfecca6726390ccab821417f23b0876", + "fee": 4956, + "vsize": 204, + "value": 159721, + "rate": 9, + "time": 1734876204, + "rbf": true, + "fullRbf": false + }, + "time": 1734876204, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "9624fe4f9a183dcea2e8c6b640394eecaec37363aec883a64358f6953fba3145", + "fee": 1632, + "vsize": 204, + "value": 163045, + "rate": 8, + "time": 1734876081, + "rbf": true + }, + "time": 1734876081, + "interval": 123, + "fullRbf": false, + "replaces": [] + } + ], + "interval": 372 + } + ] + }, + ... + ] +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "rbfLatest": [ + { + "tx": { + "txid": "cc6cb210f7ec32660fe4d46984ef64b64143fb02dc7ed70578c32b5f338ef6d6", + "fee": 8280, + "vsize": 204, + "value": 156397, + "rate": 10, + "time": 1734876576, + "rbf": true, + "fullRbf": false + }, + "time": 1734876576, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "4e94c23e075cf9c2b4ccaf32e3652b8b1bfecca6726390ccab821417f23b0876", + "fee": 4956, + "vsize": 204, + "value": 159721, + "rate": 9, + "time": 1734876204, + "rbf": true, + "fullRbf": false + }, + "time": 1734876204, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "9624fe4f9a183dcea2e8c6b640394eecaec37363aec883a64358f6953fba3145", + "fee": 1632, + "vsize": 204, + "value": 163045, + "rate": 8, + "time": 1734876081, + "rbf": true + }, + "time": 1734876081, + "interval": 123, + "fullRbf": false, + "replaces": [] + } + ], + "interval": 372 + } + ] + }, + ... + ] +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "rbfLatest": [ + { + "tx": { + "txid": "cc6cb210f7ec32660fe4d46984ef64b64143fb02dc7ed70578c32b5f338ef6d6", + "fee": 8280, + "vsize": 204, + "value": 156397, + "rate": 10, + "time": 1734876576, + "rbf": true, + "fullRbf": false + }, + "time": 1734876576, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "4e94c23e075cf9c2b4ccaf32e3652b8b1bfecca6726390ccab821417f23b0876", + "fee": 4956, + "vsize": 204, + "value": 159721, + "rate": 9, + "time": 1734876204, + "rbf": true, + "fullRbf": false + }, + "time": 1734876204, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "9624fe4f9a183dcea2e8c6b640394eecaec37363aec883a64358f6953fba3145", + "fee": 1632, + "vsize": 204, + "value": 163045, + "rate": 8, + "time": 1734876081, + "rbf": true + }, + "time": 1734876081, + "interval": 123, + "fullRbf": false, + "replaces": [] + } + ], + "interval": 372 + } + ] + }, + ... + ] +}` + }, + codeSampleLiquid: emptyCodeSample + } + } + }, + { + type: "endpoint", + category: "mempool", + fragment: "track-full-rbf", + title: "Track Mempool Full RBF Transactions", + description: { + default: "Subscribe to new Full RBF events." + }, + payload: '{ "track-rbf": "fullRbf" }', + showConditions: bitcoinNetworks, + showJsExamples: false, + codeExample: { + default: { + codeTemplate: { + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "rbfLatest": [ + { + "tx": { + "txid": "ed9e1ec0e1635d465ee95c8872efff367d420fc2c4e624bada2c6e6e6c8e0629", + "fee": 4123, + "vsize": 587.75, + "value": 25545, + "rate": 7.014887282007656, + "time": 1734876941, + "rbf": false, + "fullRbf": true + }, + "time": 1734876941, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "495ad5d39d44286e99bc45d104605407325cd4790f842dc3287fbfdda8ee5795", + "fee": 1178, + "vsize": 587.25, + "value": 28490, + "rate": 2.0059599829714774, + "time": 1734853572, + "rbf": false, + "fullRbf": true + }, + "time": 1734853572, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "189751a7560a6c39deb9a93db2a27374842c646268d0007ba52aefa189833afa", + "fee": 589, + "vsize": 587.25, + "value": 29079, + "rate": 1.0029799914857387, + "time": 1734781955, + "rbf": false + }, + "time": 1734781955, + "interval": 71617, + "fullRbf": true, + "replaces": [] + } + ], + "interval": 23369 + } + ] + }, + ... + ] +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "rbfLatest": [ + { + "tx": { + "txid": "ed9e1ec0e1635d465ee95c8872efff367d420fc2c4e624bada2c6e6e6c8e0629", + "fee": 4123, + "vsize": 587.75, + "value": 25545, + "rate": 7.014887282007656, + "time": 1734876941, + "rbf": false, + "fullRbf": true + }, + "time": 1734876941, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "495ad5d39d44286e99bc45d104605407325cd4790f842dc3287fbfdda8ee5795", + "fee": 1178, + "vsize": 587.25, + "value": 28490, + "rate": 2.0059599829714774, + "time": 1734853572, + "rbf": false, + "fullRbf": true + }, + "time": 1734853572, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "189751a7560a6c39deb9a93db2a27374842c646268d0007ba52aefa189833afa", + "fee": 589, + "vsize": 587.25, + "value": 29079, + "rate": 1.0029799914857387, + "time": 1734781955, + "rbf": false + }, + "time": 1734781955, + "interval": 71617, + "fullRbf": true, + "replaces": [] + } + ], + "interval": 23369 + } + ] + }, + ... + ] +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + "rbfLatest": [ + { + "tx": { + "txid": "ed9e1ec0e1635d465ee95c8872efff367d420fc2c4e624bada2c6e6e6c8e0629", + "fee": 4123, + "vsize": 587.75, + "value": 25545, + "rate": 7.014887282007656, + "time": 1734876941, + "rbf": false, + "fullRbf": true + }, + "time": 1734876941, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "495ad5d39d44286e99bc45d104605407325cd4790f842dc3287fbfdda8ee5795", + "fee": 1178, + "vsize": 587.25, + "value": 28490, + "rate": 2.0059599829714774, + "time": 1734853572, + "rbf": false, + "fullRbf": true + }, + "time": 1734853572, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "189751a7560a6c39deb9a93db2a27374842c646268d0007ba52aefa189833afa", + "fee": 589, + "vsize": 587.25, + "value": 29079, + "rate": 1.0029799914857387, + "time": 1734781955, + "rbf": false + }, + "time": 1734781955, + "interval": 71617, + "fullRbf": true, + "replaces": [] + } + ], + "interval": 23369 + } + ] + }, + ... + ] +}` + }, + codeSampleLiquid: emptyCodeSample + } + } + }, + +]; export const restApiDocsData = [ { diff --git a/frontend/src/app/docs/api-docs/api-docs-nav.component.ts b/frontend/src/app/docs/api-docs/api-docs-nav.component.ts index 11e39b518..dd19d0b4f 100644 --- a/frontend/src/app/docs/api-docs/api-docs-nav.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs-nav.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Env, StateService } from '@app/services/state.service'; -import { restApiDocsData } from '@app/docs/api-docs/api-docs-data'; +import { restApiDocsData, wsApiDocsData } from '@app/docs/api-docs/api-docs-data'; import { faqData } from '@app/docs/api-docs/api-docs-data'; @Component({ @@ -28,6 +28,8 @@ export class ApiDocsNavComponent implements OnInit { this.auditEnabled = this.env.AUDIT; if (this.whichTab === 'rest') { this.tabData = restApiDocsData; + } else if (this.whichTab === 'websocket') { + this.tabData = wsApiDocsData; } else if (this.whichTab === 'faq') { this.tabData = faqData; } diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 38b351e37..75e37a3bd 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -108,18 +108,43 @@
-
-
-
-
-
Endpoint
- {{ wrapUrl(network.val, wsDocs, true) }} +
+ +
+ +
+ +
+ +
+

Get higher API limits with Mempool Enterprise®

+ -
-
Description
-
Default push: {{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }} to express what you want pushed. Available: blocks, mempool-blocks, live-2h-chart, and stats.

Push transactions related to address: {{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }} to receive all new transactions containing that address as input or output. Returns an array of transactions. address-transactions for new mempool transactions, and block-transactions for new block confirmed transactions.
+
+ +

Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} Websocket service running at {{ websocketUrl(network.val) }}.

+

Note that usage limits apply to our WebSocket API. Consider an enterprise sponsorship if you need higher API limits, such as higher tracking limits.

+ +
+
+

{{ item.title }}

+
+ {{ item.title }} {{ item.category }} +
+
+
Description
+
+
+
+
Payload
+
+
+ +
+
-
diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index ce8c37121..0d5ff93f1 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -470,3 +470,21 @@ dd { margin-left: 1em; } } + +code { + background-color: var(--bg); + font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New; +} + +pre { + display: block; + font-size: 87.5%; + color: #f18920; + background-color: var(--bg); + padding: 30px; + code{ + background-color: transparent; + white-space: break-spaces; + word-break: break-all; + } +} \ No newline at end of file diff --git a/frontend/src/app/docs/api-docs/api-docs.component.ts b/frontend/src/app/docs/api-docs/api-docs.component.ts index 0298fc9f3..75f71bbf5 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs.component.ts @@ -145,7 +145,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { if (document.getElementById( targetId + "-tab-header" )) { tabHeaderHeight = document.getElementById( targetId + "-tab-header" ).scrollHeight; } - if( ( window.innerWidth <= 992 ) && ( ( this.whichTab === 'rest' ) || ( this.whichTab === 'faq' ) ) && targetId ) { + if( ( window.innerWidth <= 992 ) && ( ( this.whichTab === 'rest' ) || ( this.whichTab === 'faq' ) || ( this.whichTab === 'websocket' ) ) && targetId ) { const endpointContainerEl = document.querySelector( "#" + targetId ); const endpointContentEl = document.querySelector( "#" + targetId + " .endpoint-content" ); const endPointContentElHeight = endpointContentEl.clientHeight; @@ -207,13 +207,29 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { text = text.replace('%{' + indexNumber + '}', curlText); } - if (websocket) { - const wsHostname = this.hostname.replace('https://', 'wss://'); - wsHostname.replace('http://', 'ws://'); - return `${wsHostname}${curlNetwork}${text}`; - } return `${this.hostname}${curlNetwork}${text}`; } + websocketUrl(network: string) { + let curlNetwork = ''; + if (this.env.BASE_MODULE === 'mempool') { + if (!['', 'mainnet'].includes(network)) { + curlNetwork = `/${network}`; + } + } else if (this.env.BASE_MODULE === 'liquid') { + if (!['', 'liquid'].includes(network)) { + curlNetwork = `/${network}`; + } + } + + if (network === this.env.ROOT_NETWORK) { + curlNetwork = ''; + } + + let wsHostname = this.hostname.replace('https://', 'wss://'); + wsHostname = wsHostname.replace('http://', 'ws://'); + return `${wsHostname}${curlNetwork}/api/v1/ws`; + } + } From 37ddc29c2c6ae8709c50e696baa3b4c9acc5c366 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Wed, 25 Dec 2024 16:38:53 +0800 Subject: [PATCH 07/60] [accelerator] print sca status for google payment --- .../accelerate-checkout.component.ts | 13 +++++++------ frontend/src/app/services/services-api.service.ts | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index d6ac7f54f..4c935c57f 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -613,7 +613,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return; } const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2)); - if (!verificationToken) { + if (!verificationToken || !verificationToken.token) { console.error(`SCA verification failed`); this.accelerateError = 'SCA Verification Failed. Payment Declined.'; this.processing = false; @@ -623,10 +623,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.servicesApiService.accelerateWithGooglePay$( this.tx.txid, tokenResult.token, - verificationToken, + verificationToken.token, cardTag, `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, - costUSD + costUSD, + verificationToken.userChallenged ).subscribe({ next: () => { this.processing = false; @@ -752,9 +753,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } /** - * Required in SCA Mandated Regions: Learn more at https://developer.squareup.com/docs/sca-overview + * https://developer.squareup.com/docs/sca-overview */ - async $verifyBuyer(payments, token, details, amount) { + async $verifyBuyer(payments, token, details, amount): Promise<{token: string, userChallenged: boolean}> { const verificationDetails = { amount: amount, currencyCode: 'USD', @@ -774,7 +775,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { token, verificationDetails, ); - return verificationResults.token; + return verificationResults; } /** diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index bec9d88a1..be6851dfc 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -143,8 +143,8 @@ export class ServicesApiServices { return this.httpClient.post(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD }); } - accelerateWithGooglePay$(txInput: string, token: string, verificationToken: string, cardTag: string, referenceId: string, userApprovedUSD: number) { - return this.httpClient.post(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD }); + accelerateWithGooglePay$(txInput: string, token: string, verificationToken: string, cardTag: string, referenceId: string, userApprovedUSD: number, userChallenged: boolean) { + return this.httpClient.post(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged }); } getAccelerations$(): Observable { From 6fa747b3034a94457a35e5de93ef53a46bfc1a4c Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 27 Dec 2024 15:58:53 +0700 Subject: [PATCH 08/60] remove unused lightweight-charts --- frontend/package-lock.json | 27 --------------------------- frontend/package.json | 1 - 2 files changed, 28 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a27bffcb4..82c9d78d5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -35,7 +35,6 @@ "domino": "^2.1.6", "echarts": "~5.5.0", "esbuild": "^0.24.0", - "lightweight-charts": "~3.8.0", "ngx-echarts": "~17.2.0", "ngx-infinite-scroll": "^17.0.0", "qrcode": "1.5.1", @@ -9921,11 +9920,6 @@ "node": ">=0.4.0" } }, - "node_modules/fancy-canvas": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-0.2.2.tgz", - "integrity": "sha512-50qi8xA0QkHbjmb8h7XQ6k2fvD7y/yMfiUw9YTarJ7rWrq6o5/3CCXPouYk+XSLASvvxtjyiQLRBFt3qkE3oyA==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -12106,14 +12100,6 @@ } } }, - "node_modules/lightweight-charts": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-3.8.0.tgz", - "integrity": "sha512-7yFGnYuE1RjRJG9RwUTBz5wvF1QtjBOSW4FFlikr8Dh+/TDNt4ci+HsWSYmStgQUpawpvkCJ3j5/W25GppGj9Q==", - "dependencies": { - "fancy-canvas": "0.2.2" - } - }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", @@ -25433,11 +25419,6 @@ "object-keys": "^1.0.6" } }, - "fancy-canvas": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-0.2.2.tgz", - "integrity": "sha512-50qi8xA0QkHbjmb8h7XQ6k2fvD7y/yMfiUw9YTarJ7rWrq6o5/3CCXPouYk+XSLASvvxtjyiQLRBFt3qkE3oyA==" - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -27015,14 +26996,6 @@ "webpack-sources": "^3.0.0" } }, - "lightweight-charts": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-3.8.0.tgz", - "integrity": "sha512-7yFGnYuE1RjRJG9RwUTBz5wvF1QtjBOSW4FFlikr8Dh+/TDNt4ci+HsWSYmStgQUpawpvkCJ3j5/W25GppGj9Q==", - "requires": { - "fancy-canvas": "0.2.2" - } - }, "limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6a0d7dc12..49b759177 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -87,7 +87,6 @@ "clipboard": "^2.0.11", "domino": "^2.1.6", "echarts": "~5.5.0", - "lightweight-charts": "~3.8.0", "ngx-echarts": "~17.2.0", "ngx-infinite-scroll": "^17.0.0", "qrcode": "1.5.1", From 3c84505579b4b7d889f513575596d8888fcac6ce Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 27 Dec 2024 18:44:37 +0700 Subject: [PATCH 09/60] upgrading fortawesome dep --- frontend/package-lock.json | 304 +++++++++++++++++++------------------ frontend/package.json | 8 +- 2 files changed, 159 insertions(+), 153 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 82c9d78d5..c59a85671 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,9 +23,9 @@ "@angular/router": "^17.3.1", "@angular/ssr": "^17.3.1", "@fortawesome/angular-fontawesome": "~0.14.1", - "@fortawesome/fontawesome-common-types": "~6.6.0", - "@fortawesome/fontawesome-svg-core": "~6.6.0", - "@fortawesome/free-solid-svg-icons": "~6.6.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": "^16.0.0", "@types/qrcode": "~1.5.0", @@ -61,7 +61,7 @@ "optionalDependencies": { "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", - "cypress": "^13.15.0", + "cypress": "^13.17.0", "cypress-fail-on-console-error": "~5.1.0", "cypress-wait-until": "^2.0.1", "mock-socket": "~9.3.1", @@ -3112,9 +3112,10 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz", - "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz", + "integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==", + "license": "Apache-2.0", "optional": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -3130,9 +3131,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.13.0", + "qs": "6.13.1", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -3140,6 +3141,22 @@ "node": ">= 6" } }, + "node_modules/@cypress/request/node_modules/qs": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@cypress/schematic": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.5.0.tgz", @@ -3673,30 +3690,33 @@ } }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", - "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", - "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "@fortawesome/fontawesome-common-types": "6.7.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", - "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "@fortawesome/fontawesome-common-types": "6.7.2" }, "engines": { "node": ">=6" @@ -5672,6 +5692,7 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": "~2.1.0" @@ -5706,6 +5727,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", "optional": true, "engines": { "node": ">=0.8" @@ -5826,6 +5848,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", "optional": true, "engines": { "node": "*" @@ -5835,6 +5858,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT", "optional": true }, "node_modules/axios": { @@ -5992,6 +6016,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", "optional": true, "dependencies": { "tweetnacl": "^0.14.3" @@ -7067,6 +7092,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0", "optional": true }, "node_modules/chai": { @@ -7169,15 +7195,16 @@ } }, "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -7952,13 +7979,14 @@ "peer": true }, "node_modules/cypress": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz", - "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", "hasInstallScript": true, + "license": "MIT", "optional": true, "dependencies": { - "@cypress/request": "^3.0.4", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -7969,6 +7997,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -7983,7 +8012,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -7998,6 +8026,7 @@ "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -8200,6 +8229,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0" @@ -8686,6 +8716,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", "optional": true, "dependencies": { "jsbn": "~0.1.0", @@ -9904,6 +9935,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "optional": true }, "node_modules/falafel": { @@ -10187,6 +10219,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", "optional": true, "engines": { "node": "*" @@ -10394,6 +10427,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0" @@ -10848,6 +10882,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0", @@ -11214,18 +11249,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "optional": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -11475,6 +11498,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT", "optional": true }, "node_modules/is-unicode-supported": { @@ -11539,6 +11563,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT", "optional": true }, "node_modules/istanbul-lib-coverage": { @@ -11672,6 +11697,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT", "optional": true }, "node_modules/jsesc": { @@ -11700,6 +11726,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)", "optional": true }, "node_modules/json-schema-traverse": { @@ -11717,6 +11744,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC", "optional": true }, "node_modules/json5": { @@ -11777,6 +11805,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "optional": true, "dependencies": { "assert-plus": "1.0.0", @@ -14096,6 +14125,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", "optional": true }, "node_modules/picocolors": { @@ -14526,12 +14556,6 @@ "node": ">= 0.10" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "optional": true - }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -14647,12 +14671,6 @@ "node": ">=0.4.x" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "optional": true - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -16014,6 +16032,7 @@ "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", "optional": true, "dependencies": { "asn1": "~0.2.3", @@ -16563,6 +16582,26 @@ "readable-stream": "3" } }, + "node_modules/tldts": { + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz", + "integrity": "sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tldts-core": "^6.1.70" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.70.tgz", + "integrity": "sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==", + "license": "MIT", + "optional": true + }, "node_modules/tlite": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/tlite/-/tlite-0.1.9.tgz", @@ -16607,27 +16646,16 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "license": "BSD-3-Clause", "optional": true, "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "optional": true, - "engines": { - "node": ">= 4.0.0" + "node": ">=16" } }, "node_modules/transform-ast": { @@ -16796,6 +16824,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", "optional": true, "dependencies": { "safe-buffer": "^5.0.1" @@ -16808,6 +16837,7 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense", "optional": true }, "node_modules/type": { @@ -17116,16 +17146,6 @@ "querystring": "0.2.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "optional": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", @@ -17193,6 +17213,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0", @@ -20334,9 +20355,9 @@ } }, "@cypress/request": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz", - "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz", + "integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==", "optional": true, "requires": { "aws-sign2": "~0.7.0", @@ -20352,11 +20373,22 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.13.0", + "qs": "6.13.1", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" + }, + "dependencies": { + "qs": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", + "optional": true, + "requires": { + "side-channel": "^1.0.6" + } + } } }, "@cypress/schematic": { @@ -20635,24 +20667,24 @@ } }, "@fortawesome/fontawesome-common-types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", - "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==" + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==" }, "@fortawesome/fontawesome-svg-core": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", - "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "requires": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "@fortawesome/fontawesome-common-types": "6.7.2" } }, "@fortawesome/free-solid-svg-icons": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", - "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", "requires": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "@fortawesome/fontawesome-common-types": "6.7.2" } }, "@goto-bus-stop/common-shake": { @@ -23284,9 +23316,9 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "optional": true }, "cipher-base": { @@ -23882,12 +23914,12 @@ "peer": true }, "cypress": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz", - "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", "optional": true, "requires": { - "@cypress/request": "^3.0.4", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -23898,6 +23930,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -23912,7 +23945,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -23927,6 +23959,7 @@ "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -26354,15 +26387,6 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "optional": true, - "requires": { - "ci-info": "^3.2.0" - } - }, "is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -28779,12 +28803,6 @@ "event-stream": "=3.3.4" } }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "optional": true - }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -28876,12 +28894,6 @@ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "optional": true - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -30346,6 +30358,21 @@ } } }, + "tldts": { + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz", + "integrity": "sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==", + "optional": true, + "requires": { + "tldts-core": "^6.1.70" + } + }, + "tldts-core": { + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.70.tgz", + "integrity": "sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==", + "optional": true + }, "tlite": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/tlite/-/tlite-0.1.9.tgz", @@ -30378,23 +30405,12 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "optional": true, "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "optional": true - } + "tldts": "^6.1.32" } }, "transform-ast": { @@ -30730,16 +30746,6 @@ } } }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "optional": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 49b759177..2910b8869 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -76,9 +76,9 @@ "@angular/router": "^17.3.1", "@angular/ssr": "^17.3.1", "@fortawesome/angular-fontawesome": "~0.14.1", - "@fortawesome/fontawesome-common-types": "~6.6.0", - "@fortawesome/fontawesome-svg-core": "~6.6.0", - "@fortawesome/free-solid-svg-icons": "~6.6.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": "^16.0.0", "@types/qrcode": "~1.5.0", @@ -114,7 +114,7 @@ "optionalDependencies": { "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", - "cypress": "^13.15.0", + "cypress": "^13.17.0", "cypress-fail-on-console-error": "~5.1.0", "cypress-wait-until": "^2.0.1", "mock-socket": "~9.3.1", From e77dd114f457ad9ab4c78882ab712ab585c1d69f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:15:20 +0000 Subject: [PATCH 10/60] Bump echarts from 5.5.0 to 5.6.0 in /frontend Bumps [echarts](https://github.com/apache/echarts) from 5.5.0 to 5.6.0. - [Release notes](https://github.com/apache/echarts/releases) - [Commits](https://github.com/apache/echarts/compare/5.5.0...5.6.0) --- updated-dependencies: - dependency-name: echarts dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 30 +++++++++++++++--------------- frontend/package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c59a85671..932979e2b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,7 +33,7 @@ "browserify": "^17.0.0", "clipboard": "^2.0.11", "domino": "^2.1.6", - "echarts": "~5.5.0", + "echarts": "~5.6.0", "esbuild": "^0.24.0", "ngx-echarts": "~17.2.0", "ngx-infinite-scroll": "^17.0.0", @@ -8724,12 +8724,12 @@ } }, "node_modules/echarts": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz", - "integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", "dependencies": { "tslib": "2.3.0", - "zrender": "5.5.0" + "zrender": "5.6.1" } }, "node_modules/echarts/node_modules/tslib": { @@ -18366,9 +18366,9 @@ } }, "node_modules/zrender": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz", - "integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", "dependencies": { "tslib": "2.3.0" } @@ -24485,12 +24485,12 @@ } }, "echarts": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz", - "integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", "requires": { "tslib": "2.3.0", - "zrender": "5.5.0" + "zrender": "5.6.1" }, "dependencies": { "tslib": { @@ -31485,9 +31485,9 @@ } }, "zrender": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz", - "integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", "requires": { "tslib": "2.3.0" }, diff --git a/frontend/package.json b/frontend/package.json index 2910b8869..b50085a54 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -86,7 +86,7 @@ "browserify": "^17.0.0", "clipboard": "^2.0.11", "domino": "^2.1.6", - "echarts": "~5.5.0", + "echarts": "~5.6.0", "ngx-echarts": "~17.2.0", "ngx-infinite-scroll": "^17.0.0", "qrcode": "1.5.1", From 65533444890d94adc17cb28d881c42b9a46f6a19 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 6 Jan 2025 18:41:46 +0000 Subject: [PATCH 11/60] add missing rxjs unsubscriptions --- .../acceleration-fees-graph.component.ts | 14 ++++--- .../block-overview-graph.component.ts | 3 +- .../block-view/block-view.component.ts | 2 +- .../block/block-preview.component.ts | 2 +- .../app/components/block/block.component.ts | 40 ++++++++++--------- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts index 6a99edbf1..05602d577 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts @@ -46,6 +46,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest aggregatedHistory$: Observable; statsSubscription: Subscription; + aggregatedHistorySubscription: Subscription; + fragmentSubscription: Subscription; isLoading = true; formatNumber = formatNumber; timespan = ''; @@ -79,8 +81,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest } this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); - - this.route.fragment.subscribe((fragment) => { + + this.fragmentSubscription = this.route.fragment.subscribe((fragment) => { if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) { this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); } @@ -113,7 +115,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest share(), ); - this.aggregatedHistory$.subscribe(); + this.aggregatedHistorySubscription = this.aggregatedHistory$.subscribe(); } ngOnChanges(changes: SimpleChanges): void { @@ -335,8 +337,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest } ngOnDestroy(): void { - if (this.statsSubscription) { - this.statsSubscription.unsubscribe(); - } + this.aggregatedHistorySubscription?.unsubscribe(); + this.fragmentSubscription?.unsubscribe(); + this.statsSubscription?.unsubscribe(); } } diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index d59e38c13..605a9c222 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -177,8 +177,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On if (this.canvas) { this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost); this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored); - this.themeChangedSubscription?.unsubscribe(); } + this.themeChangedSubscription?.unsubscribe(); + this.searchSubscription?.unsubscribe(); } clear(direction): void { diff --git a/frontend/src/app/components/block-view/block-view.component.ts b/frontend/src/app/components/block-view/block-view.component.ts index b5d5256ee..9c316fe1a 100644 --- a/frontend/src/app/components/block-view/block-view.component.ts +++ b/frontend/src/app/components/block-view/block-view.component.ts @@ -116,7 +116,7 @@ export class BlockViewComponent implements OnInit, OnDestroy { this.isLoadingBlock = false; this.isLoadingOverview = true; }), - shareReplay(1) + shareReplay({ bufferSize: 1, refCount: true }) ); this.overviewSubscription = block$.pipe( diff --git a/frontend/src/app/components/block/block-preview.component.ts b/frontend/src/app/components/block/block-preview.component.ts index b2fc3fb6f..42a47f3c4 100644 --- a/frontend/src/app/components/block/block-preview.component.ts +++ b/frontend/src/app/components/block/block-preview.component.ts @@ -117,7 +117,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { this.openGraphService.waitOver('block-data-' + this.rawId); }), throttleTime(50, asyncScheduler, { leading: true, trailing: true }), - shareReplay(1) + shareReplay({ bufferSize: 1, refCount: true }) ); this.overviewSubscription = block$.pipe( diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index dab3c00fa..072e76755 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core'; import { Location } from '@angular/common'; -import { ActivatedRoute, ParamMap, Router } from '@angular/router'; +import { ActivatedRoute, ParamMap, Params, Router } from '@angular/router'; import { ElectrsApiService } from '@app/services/electrs-api.service'; -import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators'; +import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter, take } from 'rxjs/operators'; import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs'; import { StateService } from '@app/services/state.service'; import { SeoService } from '@app/services/seo.service'; @@ -68,6 +68,7 @@ export class BlockComponent implements OnInit, OnDestroy { paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; numUnexpected: number = 0; mode: 'projected' | 'actual' = 'projected'; + currentQueryParams: Params; overviewSubscription: Subscription; accelerationsSubscription: Subscription; @@ -80,8 +81,8 @@ export class BlockComponent implements OnInit, OnDestroy { timeLtr: boolean; childChangeSubscription: Subscription; auditPrefSubscription: Subscription; + isAuditEnabledSubscription: Subscription; oobSubscription: Subscription; - priceSubscription: Subscription; blockConversion: Price; @@ -118,7 +119,7 @@ export class BlockComponent implements OnInit, OnDestroy { this.setAuditAvailable(this.auditSupported); if (this.auditSupported) { - this.isAuditEnabledFromParam().subscribe(auditParam => { + this.isAuditEnabledSubscription = this.isAuditEnabledFromParam().subscribe(auditParam => { if (this.auditParamEnabled) { this.auditModeEnabled = auditParam; } else { @@ -281,7 +282,7 @@ export class BlockComponent implements OnInit, OnDestroy { } }), throttleTime(300, asyncScheduler, { leading: true, trailing: true }), - shareReplay(1) + shareReplay({ bufferSize: 1, refCount: true }) ); this.overviewSubscription = this.block$.pipe( @@ -363,6 +364,7 @@ export class BlockComponent implements OnInit, OnDestroy { .subscribe((network) => this.network = network); this.queryParamsSubscription = this.route.queryParams.subscribe((params) => { + this.currentQueryParams = params; if (params.showDetails === 'true') { this.showDetails = true; } else { @@ -414,6 +416,7 @@ export class BlockComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.stateService.markBlock$.next({}); this.overviewSubscription?.unsubscribe(); + this.accelerationsSubscription?.unsubscribe(); this.keyNavigationSubscription?.unsubscribe(); this.blocksSubscription?.unsubscribe(); this.cacheBlocksSubscription?.unsubscribe(); @@ -421,8 +424,10 @@ export class BlockComponent implements OnInit, OnDestroy { this.queryParamsSubscription?.unsubscribe(); this.timeLtrSubscription?.unsubscribe(); this.childChangeSubscription?.unsubscribe(); - this.priceSubscription?.unsubscribe(); + this.auditPrefSubscription?.unsubscribe(); + this.isAuditEnabledSubscription?.unsubscribe(); this.oobSubscription?.unsubscribe(); + this.priceSubscription?.unsubscribe(); } // TODO - Refactor this.fees/this.reward for liquid because it is not @@ -733,19 +738,18 @@ export class BlockComponent implements OnInit, OnDestroy { toggleAuditMode(): void { this.stateService.hideAudit.next(this.auditModeEnabled); - this.route.queryParams.subscribe(params => { - const queryParams = { ...params }; - delete queryParams['audit']; + const queryParams = { ...this.currentQueryParams }; + delete queryParams['audit']; - let newUrl = this.router.url.split('?')[0]; - const queryString = new URLSearchParams(queryParams).toString(); - if (queryString) { - newUrl += '?' + queryString; - } - - this.location.replaceState(newUrl); - }); + let newUrl = this.router.url.split('?')[0]; + const queryString = new URLSearchParams(queryParams).toString(); + if (queryString) { + newUrl += '?' + queryString; + } + this.location.replaceState(newUrl); + // avoid duplicate subscriptions + this.auditPrefSubscription?.unsubscribe(); this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => { this.auditModeEnabled = !hide; this.showAudit = this.auditAvailable && this.auditModeEnabled; @@ -762,7 +766,7 @@ export class BlockComponent implements OnInit, OnDestroy { return this.route.queryParams.pipe( map(params => { this.auditParamEnabled = 'audit' in params; - + return this.auditParamEnabled ? !(params['audit'] === 'false') : true; }) ); From 9f5666f41043d0eb5c34269d39d867fb0d87530d Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 6 Jan 2025 19:02:24 +0000 Subject: [PATCH 12/60] explicitly destroy block scenes --- .../block-overview-graph.component.ts | 11 +++++++---- .../block-overview-graph/fast-vertex-array.ts | 8 ++++++++ .../app/components/block-view/block-view.component.ts | 3 +++ frontend/src/app/components/block/block.component.ts | 6 ++++++ .../components/eight-blocks/eight-blocks.component.ts | 3 +++ .../mempool-block-overview.component.ts | 1 + 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index 605a9c222..3d6a76531 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -172,12 +172,17 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On ngOnDestroy(): void { if (this.animationFrameRequest) { cancelAnimationFrame(this.animationFrameRequest); - clearTimeout(this.animationHeartBeat); } + clearTimeout(this.animationHeartBeat); if (this.canvas) { this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost); this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored); } + if (this.scene) { + this.scene.destroy(); + } + this.vertexArray.destroy(); + this.vertexArray = null; this.themeChangedSubscription?.unsubscribe(); this.searchSubscription?.unsubscribe(); } @@ -490,9 +495,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) { this.doRun(); } else { - if (this.animationHeartBeat) { - clearTimeout(this.animationHeartBeat); - } + clearTimeout(this.animationHeartBeat); this.animationHeartBeat = window.setTimeout(() => { this.start(); }, 1000); diff --git a/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts b/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts index 42439ef8d..98cf7ce4d 100644 --- a/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts +++ b/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts @@ -110,4 +110,12 @@ export class FastVertexArray { getVertexData(): Float32Array { return this.data; } + + destroy(): void { + this.data = null; + this.sprites = null; + this.freeSlots = null; + this.lastSlot = 0; + this.dirty = false; + } } diff --git a/frontend/src/app/components/block-view/block-view.component.ts b/frontend/src/app/components/block-view/block-view.component.ts index 9c316fe1a..19a18383e 100644 --- a/frontend/src/app/components/block-view/block-view.component.ts +++ b/frontend/src/app/components/block-view/block-view.component.ts @@ -176,5 +176,8 @@ export class BlockViewComponent implements OnInit, OnDestroy { if (this.queryParamsSubscription) { this.queryParamsSubscription.unsubscribe(); } + if (this.blockGraph) { + this.blockGraph.destroy(); + } } } diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 072e76755..ddcf023ed 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -428,6 +428,12 @@ export class BlockComponent implements OnInit, OnDestroy { this.isAuditEnabledSubscription?.unsubscribe(); this.oobSubscription?.unsubscribe(); this.priceSubscription?.unsubscribe(); + this.blockGraphProjected.forEach(graph => { + graph.destroy(); + }); + this.blockGraphActual.forEach(graph => { + graph.destroy(); + }); } // TODO - Refactor this.fees/this.reward for liquid because it is not diff --git a/frontend/src/app/components/eight-blocks/eight-blocks.component.ts b/frontend/src/app/components/eight-blocks/eight-blocks.component.ts index 8ca8437ac..0e0861382 100644 --- a/frontend/src/app/components/eight-blocks/eight-blocks.component.ts +++ b/frontend/src/app/components/eight-blocks/eight-blocks.component.ts @@ -162,6 +162,9 @@ export class EightBlocksComponent implements OnInit, OnDestroy { this.cacheBlocksSubscription?.unsubscribe(); this.networkChangedSubscription?.unsubscribe(); this.queryParamsSubscription?.unsubscribe(); + this.blockGraphs.forEach(graph => { + graph.destroy(); + }); } shiftTestBlocks(): void { diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts index fca8b279c..a46be2733 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts @@ -120,6 +120,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang } ngOnDestroy(): void { + this.blockGraph?.destroy(); this.blockSub.unsubscribe(); this.timeLtrSubscription.unsubscribe(); this.websocketService.stopTrackMempoolBlock(); From e18c57254989bea29c94d7d43922ea5fe873de6e Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Tue, 7 Jan 2025 16:31:03 +0900 Subject: [PATCH 13/60] add missing icon --- frontend/src/app/shared/shared.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index ce5ac0f65..bfd4b84de 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, - faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck } from '@fortawesome/free-solid-svg-icons'; + faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck, faMoneyBillTrendUp } 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'; @@ -451,5 +451,6 @@ export class SharedModule { library.addIcons(faTimeline); library.addIcons(faCircleXmark); library.addIcons(faCalendarCheck); + library.addIcons(faMoneyBillTrendUp); } } From 68ea7c59f35bc0556b36e374bc870421b490bd92 Mon Sep 17 00:00:00 2001 From: natsoni Date: Tue, 7 Jan 2025 11:21:04 +0100 Subject: [PATCH 14/60] Fix transaction filter logic for first seen fetching --- .../src/app/components/transaction/transaction.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 71ffaa2cd..ab71529c0 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -240,7 +240,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { retry({ count: 2, delay: 2000 }), // Try again until we either get a valid response, or the transaction is confirmed repeat({ delay: 2000 }), - filter((transactionTimes) => transactionTimes?.length && transactionTimes[0] > 0 && !this.tx.status?.confirmed), + filter((transactionTimes) => transactionTimes?.[0] > 0 || this.tx.status?.confirmed), take(1), )), ) From 7740908a4cdd852cb5a95ae6d0b1f74b8774baa4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 8 Jan 2025 13:08:33 +0000 Subject: [PATCH 15/60] fix access block scene after destroyed --- .../block-overview-graph.component.ts | 2 +- .../block-overview-graph/fast-vertex-array.ts | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index 3d6a76531..419a51995 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -453,7 +453,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } this.applyQueuedUpdates(); // skip re-render if there's no change to the scene - if (this.scene && this.gl) { + if (this.scene && this.gl && this.vertexArray) { /* SET UP SHADER UNIFORMS */ // screen dimensions this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight); diff --git a/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts b/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts index 98cf7ce4d..8f9978d13 100644 --- a/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts +++ b/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts @@ -19,6 +19,7 @@ export class FastVertexArray { freeSlots: number[]; lastSlot: number; dirty = false; + destroyed = false; constructor(length, stride) { this.length = length; @@ -32,6 +33,9 @@ export class FastVertexArray { } insert(sprite: TxSprite): number { + if (this.destroyed) { + return; + } this.count++; let position; @@ -45,11 +49,14 @@ export class FastVertexArray { } } this.sprites[position] = sprite; - return position; this.dirty = true; + return position; } remove(index: number): void { + if (this.destroyed) { + return; + } this.count--; this.clearData(index); this.freeSlots.push(index); @@ -61,20 +68,26 @@ export class FastVertexArray { } setData(index: number, dataChunk: number[]): void { + if (this.destroyed) { + return; + } this.data.set(dataChunk, (index * this.stride)); this.dirty = true; } - clearData(index: number): void { + private clearData(index: number): void { this.data.fill(0, (index * this.stride), ((index + 1) * this.stride)); this.dirty = true; } getData(index: number): Float32Array { + if (this.destroyed) { + return; + } return this.data.subarray(index, this.stride); } - expand(): void { + private expand(): void { this.length *= 2; const newData = new Float32Array(this.length * this.stride); newData.set(this.data); @@ -82,7 +95,7 @@ export class FastVertexArray { this.dirty = true; } - compact(): void { + private compact(): void { // New array length is the smallest power of 2 larger than the sprite count (but no smaller than 512) const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count)))); if (newLength !== this.length) { @@ -117,5 +130,6 @@ export class FastVertexArray { this.freeSlots = null; this.lastSlot = 0; this.dirty = false; + this.destroyed = true; } } From 9fbbe4980da852bcd3f1aa49997f592877d1d199 Mon Sep 17 00:00:00 2001 From: natsoni Date: Wed, 8 Jan 2025 16:01:59 +0100 Subject: [PATCH 16/60] Fix transaction amount change for P2PK addresses --- .../transactions-list/transactions-list.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index dfe19ca74..8e67ccdfc 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -202,12 +202,12 @@ export class TransactionsListComponent implements OnInit, OnChanges { for (const address of this.addresses) { switch (address.length) { case 130: { - if (v.scriptpubkey === '21' + address + 'ac') { + if (v.scriptpubkey === '41' + address + 'ac') { return v.value; } } break; case 66: { - if (v.scriptpubkey === '41' + address + 'ac') { + if (v.scriptpubkey === '21' + address + 'ac') { return v.value; } } break; @@ -224,12 +224,12 @@ export class TransactionsListComponent implements OnInit, OnChanges { for (const address of this.addresses) { switch (address.length) { case 130: { - if (v.prevout?.scriptpubkey === '21' + address + 'ac') { + if (v.prevout?.scriptpubkey === '41' + address + 'ac') { return v.prevout?.value; } } break; case 66: { - if (v.prevout?.scriptpubkey === '41' + address + 'ac') { + if (v.prevout?.scriptpubkey === '21' + address + 'ac') { return v.prevout?.value; } } break; From 4a4259fa7d9e54f83fabe36e085e58a3d5d8921e Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sun, 12 Jan 2025 12:16:51 +0900 Subject: [PATCH 17/60] add new robot icon, wrap import into new lines --- frontend/src/app/shared/shared.module.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index bfd4b84de..2bcfc1255 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -4,7 +4,10 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, - faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck, faMoneyBillTrendUp } from '@fortawesome/free-solid-svg-icons'; + faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, + faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, + faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, + faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot } 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'; @@ -452,5 +455,6 @@ export class SharedModule { library.addIcons(faCircleXmark); library.addIcons(faCalendarCheck); library.addIcons(faMoneyBillTrendUp); + library.addIcons(faRobot); } } From f6ab2caaf9e26b1cb7c6295f03ee17edbb2aee18 Mon Sep 17 00:00:00 2001 From: natsoni Date: Tue, 14 Jan 2025 10:42:40 +0900 Subject: [PATCH 18/60] Fix textarea keyboard navigation --- frontend/src/app/components/app/app.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/app/app.component.ts b/frontend/src/app/components/app/app.component.ts index 365c23972..01f887c58 100644 --- a/frontend/src/app/components/app/app.component.ts +++ b/frontend/src/app/components/app/app.component.ts @@ -41,7 +41,7 @@ export class AppComponent implements OnInit { @HostListener('document:keydown', ['$event']) handleKeyboardEvents(event: KeyboardEvent) { - if (event.target instanceof HTMLInputElement) { + if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) { return; } // prevent arrow key horizontal scrolling From 6a4aeaf7ed624c971df150d1386caf1ca2f504c3 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 13 Jan 2025 07:11:17 +0000 Subject: [PATCH 19/60] Fix unconfirmed badge on broken RBF txs --- .../src/app/components/transaction/transaction.component.html | 1 + .../components/confirmations/confirmations.component.html | 4 ++-- .../components/confirmations/confirmations.component.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 8c2d9de01..cce0e23eb 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -24,6 +24,7 @@ [height]="tx?.status?.block_height" [replaced]="replaced" [removed]="this.rbfInfo?.mined && !this.tx?.status?.confirmed" + [cached]="isCached" >
diff --git a/frontend/src/app/shared/components/confirmations/confirmations.component.html b/frontend/src/app/shared/components/confirmations/confirmations.component.html index 4ad3cb33a..282979824 100644 --- a/frontend/src/app/shared/components/confirmations/confirmations.component.html +++ b/frontend/src/app/shared/components/confirmations/confirmations.component.html @@ -11,9 +11,9 @@ - + - + \ No newline at end of file diff --git a/frontend/src/app/shared/components/confirmations/confirmations.component.ts b/frontend/src/app/shared/components/confirmations/confirmations.component.ts index 624c58278..d54f80b10 100644 --- a/frontend/src/app/shared/components/confirmations/confirmations.component.ts +++ b/frontend/src/app/shared/components/confirmations/confirmations.component.ts @@ -12,6 +12,7 @@ export class ConfirmationsComponent implements OnChanges { @Input() height: number; @Input() replaced: boolean = false; @Input() removed: boolean = false; + @Input() cached: boolean = false; @Input() hideUnconfirmed: boolean = false; @Input() buttonClass: string = ''; From 4dcbccd9b23acf012466be5ab24e80adae9db0cb Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 14 Jan 2025 06:41:34 +0000 Subject: [PATCH 20/60] avoid creating incomplete RBF trees --- backend/src/api/rbf-cache.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts index 944ad790e..df6e10c77 100644 --- a/backend/src/api/rbf-cache.ts +++ b/backend/src/api/rbf-cache.ts @@ -119,7 +119,11 @@ class RbfCache { public add(replaced: MempoolTransactionExtended[], newTxExtended: MempoolTransactionExtended): void { - if (!newTxExtended || !replaced?.length || this.txs.has(newTxExtended.txid)) { + if ( !newTxExtended + || !replaced?.length + || this.txs.has(newTxExtended.txid) + || !(replaced.some(tx => !this.replacedBy.has(tx.txid))) + ) { return; } From cd02d8923524852b39f0c86df39550b7cb3dbd3f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 14 Jan 2025 09:04:01 +0000 Subject: [PATCH 21/60] Fix time traveling balance charts --- .../address-graph/address-graph.component.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/address-graph/address-graph.component.ts b/frontend/src/app/components/address-graph/address-graph.component.ts index db9345b18..2bbfd5e34 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.ts +++ b/frontend/src/app/components/address-graph/address-graph.component.ts @@ -478,25 +478,30 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { } extendSummary(summary) { - let extendedSummary = summary.slice(); + const extendedSummary = summary.slice(); // Add a point at today's date to make the graph end at the current time extendedSummary.unshift({ time: Date.now() / 1000, value: 0 }); - extendedSummary.reverse(); - let oneHour = 60 * 60; + let maxTime = Date.now() / 1000; + + const oneHour = 60 * 60; // Fill gaps longer than interval for (let i = 0; i < extendedSummary.length - 1; i++) { - let hours = Math.floor((extendedSummary[i + 1].time - extendedSummary[i].time) / oneHour); + if (extendedSummary[i].time > maxTime) { + extendedSummary[i].time = maxTime - 30; + } + maxTime = extendedSummary[i].time; + const hours = Math.floor((extendedSummary[i].time - extendedSummary[i + 1].time) / oneHour); if (hours > 1) { for (let j = 1; j < hours; j++) { - let newTime = extendedSummary[i].time + oneHour * j; + const newTime = extendedSummary[i].time - oneHour * j; extendedSummary.splice(i + j, 0, { time: newTime, value: 0 }); } i += hours - 1; } } - return extendedSummary.reverse(); + return extendedSummary; } } From 8529b996750d27b8a5e34c6053711266945f2f29 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 25 Jul 2024 22:34:52 +0000 Subject: [PATCH 22/60] custom dashboard wallet widgets --- frontend/src/app/interfaces/node-api.interface.ts | 2 +- frontend/src/app/services/state.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index b39f8e0d3..4d85a938d 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -1,4 +1,4 @@ -import { AddressTxSummary, Block, ChainStats, Transaction } from "./electrs.interface"; +import { AddressTxSummary, Block, ChainStats } from "./electrs.interface"; export interface OptimizedMempoolStats { added: number; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 0d006b552..55f76d6b1 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -171,7 +171,7 @@ export class StateService { mempoolRemovedTransactions$ = new Subject(); multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>(); blockTransactions$ = new Subject(); - walletTransactions$ = new Subject(); + walletTransactions$ = new Subject>(); isLoadingWebSocket$ = new ReplaySubject(1); isLoadingMempool$ = new BehaviorSubject(true); vbytesPerSecond$ = new ReplaySubject(1); From c4ec50b7719f6e709582aae89aad25c6e901712b Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 4 Oct 2024 22:31:49 +0000 Subject: [PATCH 23/60] Restore transactions list to wallet page --- .../components/wallet/wallet.component.html | 30 ++++++++ .../app/components/wallet/wallet.component.ts | 77 ++++++++++++++++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/wallet/wallet.component.html b/frontend/src/app/components/wallet/wallet.component.html index 52b7b02a5..ed6b82456 100644 --- a/frontend/src/app/components/wallet/wallet.component.html +++ b/frontend/src/app/components/wallet/wallet.component.html @@ -74,6 +74,36 @@ +
+ +
+

Transactions

+
+ + + +
+ +
+
+
+ +
+
+ +
+
+
+ +
+ + +
+ +
+
+ +
diff --git a/frontend/src/app/components/wallet/wallet.component.ts b/frontend/src/app/components/wallet/wallet.component.ts index ce44250e9..bf5b67ae7 100644 --- a/frontend/src/app/components/wallet/wallet.component.ts +++ b/frontend/src/app/components/wallet/wallet.component.ts @@ -9,6 +9,8 @@ import { of, Observable, Subscription } from 'rxjs'; import { SeoService } from '@app/services/seo.service'; import { seoDescriptionNetwork } from '@app/shared/common.utils'; import { WalletAddress } from '@interfaces/node-api.interface'; +import { ElectrsApiService } from '@app/services/electrs-api.service'; +import { AudioService } from '@app/services/audio.service'; class WalletStats implements ChainStats { addresses: string[]; @@ -24,6 +26,7 @@ class WalletStats implements ChainStats { acc.funded_txo_sum += stat.funded_txo_sum; acc.spent_txo_count += stat.spent_txo_count; acc.spent_txo_sum += stat.spent_txo_sum; + acc.tx_count += stat.tx_count; return acc; }, { funded_txo_count: 0, @@ -109,12 +112,17 @@ export class WalletComponent implements OnInit, OnDestroy { addressStrings: string[] = []; walletName: string; isLoadingWallet = true; + isLoadingTransactions = true; + transactions: Transaction[]; + totalTransactionCount: number; + retryLoadMore = false; wallet$: Observable>; walletAddresses$: Observable>; walletSummary$: Observable; walletStats$: Observable; error: any; walletSubscription: Subscription; + transactionSubscription: Subscription; collapseAddresses: boolean = true; @@ -129,6 +137,8 @@ export class WalletComponent implements OnInit, OnDestroy { private websocketService: WebsocketService, private stateService: StateService, private apiService: ApiService, + private electrsApiService: ElectrsApiService, + private audioService: AudioService, private seoService: SeoService, ) { } @@ -172,6 +182,21 @@ export class WalletComponent implements OnInit, OnDestroy { }), switchMap(initial => this.stateService.walletTransactions$.pipe( startWith(null), + tap((transactions) => { + if (!transactions?.length) { + return; + } + for (const transaction of transactions) { + const tx = this.transactions.find((t) => t.txid === transaction.txid); + if (tx) { + tx.status = transaction.status; + } else { + this.transactions.unshift(transaction); + } + } + this.transactions = this.transactions.slice(); + this.audioService.playSound('magic'); + }), scan((wallet, walletTransactions) => { for (const tx of (walletTransactions || [])) { const funded: Record = {}; @@ -267,8 +292,57 @@ export class WalletComponent implements OnInit, OnDestroy { return stats; }, walletStats), ); - }), + }) ); + + this.transactionSubscription = this.wallet$.pipe( + switchMap(wallet => { + const addresses = Object.keys(wallet).map(addr => this.normalizeAddress(addr)); + return this.electrsApiService.getAddressesTransactions$(addresses); + }), + map(transactions => { + // only confirmed transactions supported for now + return transactions.filter(tx => tx.status.confirmed); + }), + catchError((error) => { + console.log(error); + this.error = error; + this.seoService.logSoft404(); + this.isLoadingWallet = false; + return of([]); + }) + ).subscribe((transactions: Transaction[] | null) => { + if (!transactions) { + return; + } + this.transactions = transactions; + this.isLoadingTransactions = false; + }); + } + + loadMore(): void { + if (this.isLoadingTransactions || this.fullyLoaded) { + return; + } + this.isLoadingTransactions = true; + this.retryLoadMore = false; + this.electrsApiService.getAddressesTransactions$(this.addressStrings, this.transactions[this.transactions.length - 1].txid) + .subscribe((transactions: Transaction[]) => { + if (transactions && transactions.length) { + this.transactions = this.transactions.concat(transactions); + } else { + this.fullyLoaded = true; + } + this.isLoadingTransactions = false; + }, + (error) => { + this.isLoadingTransactions = false; + this.retryLoadMore = true; + // In the unlikely event of the txid wasn't found in the mempool anymore and we must reload the page. + if (error.status === 422) { + window.location.reload(); + } + }); } deduplicateWalletTransactions(walletTransactions: AddressTxSummary[]): AddressTxSummary[] { @@ -299,5 +373,6 @@ export class WalletComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.websocketService.stopTrackingWallet(); this.walletSubscription.unsubscribe(); + this.transactionSubscription.unsubscribe(); } } From f77dc68ec71c026c9b7390e6eda79bd9510d2f56 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 4 Oct 2024 22:43:52 +0000 Subject: [PATCH 24/60] Add link to wallet page from custom dashboard txs widget --- .../custom-dashboard/custom-dashboard.component.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html index 13cdd97ce..8ca1a5ac4 100644 --- a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html +++ b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html @@ -281,9 +281,11 @@
From b65d00f2897969cdc6d2aaf1601dbc62d00f23d7 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 6 Oct 2024 22:29:32 +0000 Subject: [PATCH 25/60] switch multi-address APIs to use POST --- frontend/src/app/services/electrs-api.service.ts | 14 +++++++++----- frontend/src/app/services/state.service.ts | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index 3cd5b5abd..6e9697f49 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -142,12 +142,16 @@ export class ElectrsApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs', { params }); } - getAddressesTransactions$(addresses: string[], txid?: string): Observable { + getAddressesTransactions$(addresses: string[], txid?: string): Observable { let params = new HttpParams(); if (txid) { params = params.append('after_txid', txid); } - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs?addresses=${addresses.join(',')}`, { params }); + return this.httpClient.post( + this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs', + addresses, + { params } + ); } getAddressSummary$(address: string, txid?: string): Observable { @@ -163,7 +167,7 @@ export class ElectrsApiService { if (txid) { params = params.append('after_txid', txid); } - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs/summary?addresses=${addresses.join(',')}`, { params }); + return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs/summary', addresses, { params }); } getScriptHashTransactions$(script: string, txid?: string): Observable { @@ -182,7 +186,7 @@ export class ElectrsApiService { params = params.append('after_txid', txid); } return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe( - switchMap(scriptHashes => this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs?scripthashes=${scriptHashes.join(',')}`, { params })), + switchMap(scriptHashes => this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/scripthashes/txs', scriptHashes, { params })), ); } @@ -212,7 +216,7 @@ export class ElectrsApiService { params = params.append('after_txid', txid); } return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe( - switchMap(scriptHashes => this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs/summary?scripthashes=${scriptHashes.join(',')}`, { params })), + switchMap(scriptHashes => this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/scripthashes/txs/summary', scriptHashes, { params })), ); } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 55f76d6b1..0d006b552 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -171,7 +171,7 @@ export class StateService { mempoolRemovedTransactions$ = new Subject(); multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>(); blockTransactions$ = new Subject(); - walletTransactions$ = new Subject>(); + walletTransactions$ = new Subject(); isLoadingWebSocket$ = new ReplaySubject(1); isLoadingMempool$ = new BehaviorSubject(true); vbytesPerSecond$ = new ReplaySubject(1); From c248544fe806f362c16ab6af69d809588c668523 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 18 Oct 2024 04:39:31 +0000 Subject: [PATCH 26/60] Update wallet page title --- frontend/src/app/components/wallet/wallet.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/wallet/wallet.component.html b/frontend/src/app/components/wallet/wallet.component.html index ed6b82456..9aa82b818 100644 --- a/frontend/src/app/components/wallet/wallet.component.html +++ b/frontend/src/app/components/wallet/wallet.component.html @@ -1,6 +1,6 @@
-

Wallet

+

{{ walletName }}

From 204d54b1896e0a3eeaaa927d565b3bc3fcc1770c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 22 Dec 2024 10:47:36 +0000 Subject: [PATCH 27/60] fix wallet transactions ordering --- frontend/src/app/components/wallet/wallet.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/wallet/wallet.component.ts b/frontend/src/app/components/wallet/wallet.component.ts index bf5b67ae7..43cc7ee80 100644 --- a/frontend/src/app/components/wallet/wallet.component.ts +++ b/frontend/src/app/components/wallet/wallet.component.ts @@ -302,7 +302,7 @@ export class WalletComponent implements OnInit, OnDestroy { }), map(transactions => { // only confirmed transactions supported for now - return transactions.filter(tx => tx.status.confirmed); + return transactions.filter(tx => tx.status.confirmed).sort((a, b) => b.status.block_height - a.status.block_height); }), catchError((error) => { console.log(error); @@ -329,7 +329,7 @@ export class WalletComponent implements OnInit, OnDestroy { this.electrsApiService.getAddressesTransactions$(this.addressStrings, this.transactions[this.transactions.length - 1].txid) .subscribe((transactions: Transaction[]) => { if (transactions && transactions.length) { - this.transactions = this.transactions.concat(transactions); + this.transactions = this.transactions.concat(transactions.sort((a, b) => b.status.block_height - a.status.block_height)); } else { this.fullyLoaded = true; } From aca2f2ec7d7ab6af419683e23c4d3605bd0a7830 Mon Sep 17 00:00:00 2001 From: wiz Date: Wed, 15 Jan 2025 16:34:56 +0900 Subject: [PATCH 28/60] ops: Set expires -1 header on 2s server cached responses --- production/nginx/location-api.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/production/nginx/location-api.conf b/production/nginx/location-api.conf index 80f513147..b337c0f5b 100644 --- a/production/nginx/location-api.conf +++ b/production/nginx/location-api.conf @@ -140,7 +140,8 @@ location @mempool-api-v1-cache-normal { proxy_cache_valid 200 2s; proxy_redirect off; - expires 2s; + # cache for 2 seconds on server, but send expires -1 so browser doesn't cache + expires -1; } location @mempool-api-v1-cache-disabled { From 4ecf2eb679094291a4d5749778c5cb7f11e44ffc Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 2 Sep 2024 02:28:54 +0000 Subject: [PATCH 29/60] experimental stratum job visualization --- backend/src/api/services/stratum.ts | 101 ++++++++++ backend/src/api/websocket-handler.ts | 28 +++ backend/src/index.ts | 2 + .../stratum-list/stratum-list.component.html | 45 +++++ .../stratum-list/stratum-list.component.scss | 101 ++++++++++ .../stratum-list/stratum-list.component.ts | 185 ++++++++++++++++++ .../src/app/interfaces/websocket.interface.ts | 24 +++ frontend/src/app/master-page.module.ts | 7 +- frontend/src/app/services/mining.service.ts | 5 +- frontend/src/app/services/state.service.ts | 24 ++- .../src/app/services/websocket.service.ts | 24 +++ frontend/src/app/shared/shared.module.ts | 3 + 12 files changed, 543 insertions(+), 6 deletions(-) create mode 100644 backend/src/api/services/stratum.ts create mode 100644 frontend/src/app/components/stratum/stratum-list/stratum-list.component.html create mode 100644 frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss create mode 100644 frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts diff --git a/backend/src/api/services/stratum.ts b/backend/src/api/services/stratum.ts new file mode 100644 index 000000000..5fa3dd7a3 --- /dev/null +++ b/backend/src/api/services/stratum.ts @@ -0,0 +1,101 @@ +import { WebSocket } from 'ws'; +import logger from '../../logger'; +import websocketHandler from '../websocket-handler'; + +export interface StratumJob { + pool: number; + height: number; + coinbase: string; + scriptsig: string; + reward: number; + jobId: string; + extraNonce: string; + extraNonce2Size: number; + prevHash: string; + coinbase1: string; + coinbase2: string; + merkleBranches: string[]; + version: string; + bits: string; + time: string; + timestamp: number; + cleanJobs: boolean; + received: number; +} + +function isStratumJob(obj: any): obj is StratumJob { + return obj + && typeof obj === 'object' + && 'pool' in obj + && 'prevHash' in obj + && 'height' in obj + && 'received' in obj + && 'version' in obj + && 'timestamp' in obj + && 'bits' in obj + && 'merkleBranches' in obj + && 'cleanJobs' in obj; +} + +class StratumApi { + private ws: WebSocket | null = null; + private runWebsocketLoop: boolean = false; + private startedWebsocketLoop: boolean = false; + private websocketConnected: boolean = false; + private jobs: Record = {}; + + public constructor() {} + + public getJobs(): Record { + return this.jobs; + } + + private handleWebsocketMessage(msg: any): void { + if (isStratumJob(msg)) { + this.jobs[msg.pool] = msg; + websocketHandler.handleNewStratumJob(this.jobs[msg.pool]); + } + } + + public async connectWebsocket(): Promise { + this.runWebsocketLoop = true; + if (this.startedWebsocketLoop) { + return; + } + while (this.runWebsocketLoop) { + this.startedWebsocketLoop = true; + if (!this.ws) { + this.ws = new WebSocket(`http://localhost:3333`); + this.websocketConnected = true; + + this.ws.on('open', () => { + logger.info('Stratum websocket opened'); + }); + + this.ws.on('error', (error) => { + logger.err('Stratum websocket error: ' + error); + this.ws = null; + this.websocketConnected = false; + }); + + this.ws.on('close', () => { + logger.info('Stratum websocket closed'); + this.ws = null; + this.websocketConnected = false; + }); + + this.ws.on('message', (data, isBinary) => { + try { + const parsedMsg = JSON.parse((isBinary ? data : data.toString()) as string); + this.handleWebsocketMessage(parsedMsg); + } catch (e) { + logger.warn('Failed to parse stratum websocket message: ' + (e instanceof Error ? e.message : e)); + } + }); + } + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } +} + +export default new StratumApi(); \ No newline at end of file diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 13e27c360..390896caa 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -38,6 +38,7 @@ interface AddressTransactions { import bitcoinSecondClient from './bitcoin/bitcoin-second-client'; import { calculateMempoolTxCpfp } from './cpfp'; import { getRecentFirstSeen } from '../utils/file-read'; +import stratumApi, { StratumJob } from './services/stratum'; // valid 'want' subscriptions const wantable = [ @@ -403,6 +404,16 @@ class WebsocketHandler { delete client['track-mempool']; } + if (parsedMessage && parsedMessage['track-stratum'] != null) { + if (parsedMessage['track-stratum']) { + const sub = parsedMessage['track-stratum']; + client['track-stratum'] = sub; + response['stratumJobs'] = this.socketData['stratumJobs']; + } else { + client['track-stratum'] = false; + } + } + if (Object.keys(response).length) { client.send(this.serializeResponse(response)); } @@ -1384,6 +1395,23 @@ class WebsocketHandler { await statistics.runStatistics(); } + public handleNewStratumJob(job: StratumJob): void { + this.updateSocketDataFields({ 'stratumJobs': stratumApi.getJobs() }); + + for (const server of this.webSocketServers) { + server.clients.forEach((client) => { + if (client.readyState !== WebSocket.OPEN) { + return; + } + if (client['track-stratum'] && (client['track-stratum'] === 'all' || client['track-stratum'] === job.pool)) { + client.send(JSON.stringify({ + 'stratumJob': job + })); + } + }); + } + } + // takes a dictionary of JSON serialized values // and zips it together into a valid JSON object private serializeResponse(response): string { diff --git a/backend/src/index.ts b/backend/src/index.ts index c179b66bc..53c4c2f22 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -48,6 +48,7 @@ import accelerationRoutes from './api/acceleration/acceleration.routes'; import aboutRoutes from './api/about.routes'; import mempoolBlocks from './api/mempool-blocks'; import walletApi from './api/services/wallets'; +import stratumApi from './api/services/stratum'; class Server { private wss: WebSocket.Server | undefined; @@ -320,6 +321,7 @@ class Server { loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler)); accelerationApi.connectWebsocket(); + stratumApi.connectWebsocket(); } setUpHttpApiRoutes(): void { diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html new file mode 100644 index 000000000..6132035be --- /dev/null +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html @@ -0,0 +1,45 @@ +
+

Stratum Jobs

+ +
+ +
+ + + + + + + + + + + @for (row of rows; track row.job.pool) { + + + + @for (cell of row.merkleCells; track $index) { + + } + + + } + +
HeightReward + Merkle Branches + Pool
+ {{ row.job.height }} + + + +
+
+ @if (pools[row.job.pool]) { + + + {{ pools[row.job.pool].name}} + + } +
+
+
diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss new file mode 100644 index 000000000..da0e63967 --- /dev/null +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss @@ -0,0 +1,101 @@ +.stratum-table { + width: 100%; +} + +td { + position: relative; + height: 2em; + + &.height, &.reward { + padding: 0 5px; + } + + &.pool { + padding-left: 5px; + padding-right: 20px; + } + + &.merkle { + width: 100px; + .pipe-segment { + position: absolute; + border-color: white; + box-sizing: content-box; + + &.vertical { + top: 0; + right: 0; + width: 50%; + height: 100%; + border-left: solid 4px; + } + &.horizontal { + bottom: 0; + left: 0; + width: 100%; + height: 50%; + border-top: solid 4px; + } + &.branch-top { + bottom: 0; + right: 0; + width: 100%; + height: 50%; + border-top: solid 4px; + &::after { + content: ""; + position: absolute; + box-sizing: content-box; + top: -4px; + right: 0px; + bottom: 0; + width: 50%; + border-top: solid 4px; + border-left: solid 4px; + border-top-left-radius: 5px; + } + } + &.branch-mid { + bottom: 0; + right: 0px; + width: 50%; + height: 100%; + border-left: solid 4px; + &::after { + content: ""; + position: absolute; + box-sizing: content-box; + top: -4px; + left: -4px; + width: 100%; + height: 50%; + border-bottom: solid 4px; + border-left: solid 4px; + border-bottom-left-radius: 5px; + } + } + &.branch-end { + top: -4px; + right: 0; + width: 50%; + height: 50%; + border-bottom-left-radius: 5px; + border-bottom: solid 4px; + border-left: solid 4px; + } + } + } +} + +.badge { + position: relative; + color: #FFF; +} + +.pool-logo { + width: 15px; + height: 15px; + position: relative; + top: -1px; + margin-right: 2px; +} \ No newline at end of file diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts new file mode 100644 index 000000000..25bb42d1c --- /dev/null +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts @@ -0,0 +1,185 @@ +import { Component, OnInit, ChangeDetectionStrategy, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { StateService } from '../../../services/state.service'; +import { WebsocketService } from '../../../services/websocket.service'; +import { map, Observable } from 'rxjs'; +import { StratumJob } from '../../../interfaces/websocket.interface'; +import { MiningService } from '../../../services/mining.service'; +import { SinglePoolStats } from '../../../interfaces/node-api.interface'; + +type MerkleCellType = ' ' | '┬' | '├' | '└' | '│' | '─' | 'leaf'; + +interface MerkleCell { + hash: string; + type: MerkleCellType; + job?: StratumJob; +} + +interface MerkleTree { + hash?: string; + job: string; + size: number; + children?: MerkleTree[]; +} + +interface PoolRow { + job: StratumJob; + merkleCells: MerkleCell[]; +} + +@Component({ + selector: 'app-stratum-list', + templateUrl: './stratum-list.component.html', + styleUrls: ['./stratum-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class StratumList implements OnInit, OnDestroy { + rows$: Observable; + pools: { [id: number]: SinglePoolStats } = {}; + poolsReady: boolean = false; + + constructor( + private stateService: StateService, + private websocketService: WebsocketService, + private miningService: MiningService, + private cd: ChangeDetectorRef, + ) {} + + ngOnInit(): void { + this.miningService.getPools().subscribe(pools => { + this.pools = {}; + for (const pool of pools) { + this.pools[pool.unique_id] = pool; + } + this.poolsReady = true; + this.cd.markForCheck(); + }); + this.rows$ = this.stateService.stratumJobs$.pipe( + map((jobs) => this.processJobs(jobs)), + ); + this.websocketService.startTrackStratum('all'); + } + + processJobs(jobs: Record): PoolRow[] { + if (Object.keys(jobs).length === 0) { + return []; + } + + const numBranches = Math.max(...Object.values(jobs).map(job => job.merkleBranches.length)); + + let trees: MerkleTree[] = Object.keys(jobs).map(job => ({ + job, + size: 1, + })); + + // build tree from bottom up + for (let col = numBranches - 1; col >= 0; col--) { + const groups: Record = {}; + for (const tree of trees) { + const hash = jobs[tree.job].merkleBranches[col]; + if (!groups[hash]) { + groups[hash] = []; + } + groups[hash].push(tree); + } + trees = Object.values(groups).map(group => ({ + hash: jobs[group[0].job].merkleBranches[col], + job: group[0].job, + children: group, + size: group.reduce((acc, tree) => acc + tree.size, 0), + })); + } + + // initialize grid of cells + const rows: (MerkleCell | null)[][] = []; + for (let i = 0; i < Object.keys(jobs).length; i++) { + const row: (MerkleCell | null)[] = []; + for (let j = 0; j <= numBranches; j++) { + row.push(null); + } + rows.push(row); + } + + // fill in the cells + let colTrees = [trees.sort((a, b) => { + if (a.size !== b.size) { + return b.size - a.size; + } + return a.job.localeCompare(b.job); + })]; + for (let col = 0; col <= numBranches; col++) { + let row = 0; + const nextTrees: MerkleTree[][] = []; + for (let g = 0; g < colTrees.length; g++) { + for (let t = 0; t < colTrees[g].length; t++) { + const tree = colTrees[g][t]; + const isFirstTree = (t === 0); + const isLastTree = (t === colTrees[g].length - 1); + for (let i = 0; i < tree.size; i++) { + const isFirstCell = (i === 0); + const isLeaf = (col === numBranches); + rows[row][col] = { + hash: tree.hash, + job: isLeaf ? jobs[tree.job] : undefined, + type: 'leaf', + }; + if (col > 0) { + rows[row][col - 1].type = getCellType(isFirstCell, isFirstTree, isLastTree); + } + row++; + } + if (tree.children) { + nextTrees.push(tree.children.sort((a, b) => { + if (a.size !== b.size) { + return b.size - a.size; + } + return a.job.localeCompare(b.job); + })); + } + } + } + colTrees = nextTrees; + } + return rows.map(row => ({ + job: row[row.length - 1].job, + merkleCells: row.slice(0, -1), + })); + } + + pipeToClass(type: MerkleCellType): string { + return { + ' ': 'empty', + '┬': 'branch-top', + '├': 'branch-mid', + '└': 'branch-end', + '│': 'vertical', + '─': 'horizontal', + 'leaf': 'leaf' + }[type]; + } + + ngOnDestroy(): void { + this.websocketService.stopTrackStratum(); + } +} + +function getCellType(isFirstCell, isFirstTree, isLastTree): MerkleCellType { + if (isFirstCell) { + if (isFirstTree) { + if (isLastTree) { + return '─'; + } else { + return '┬'; + } + } else if (isLastTree) { + return '└'; + } else { + return '├'; + } + } else { + if (isLastTree) { + return ' '; + } else { + return '│'; + } + } +} diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index d61610a2e..9281f0fc7 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -21,6 +21,8 @@ export interface WebsocketResponse { rbfInfo?: RbfTree; rbfLatest?: RbfTree[]; rbfLatestSummary?: ReplacementInfo[]; + stratumJob?: StratumJob; + stratumJobs?: Record; utxoSpent?: object; transactions?: TransactionStripped[]; loadingIndicators?: ILoadingIndicators; @@ -37,6 +39,7 @@ export interface WebsocketResponse { 'track-rbf-summary'?: boolean; 'track-accelerations'?: boolean; 'track-wallet'?: string; + 'track-stratum'?: string | number; 'watch-mempool'?: boolean; 'refresh-blocks'?: boolean; } @@ -150,3 +153,24 @@ export interface HealthCheckHost { electrs?: string; } } + +export interface StratumJob { + pool: number; + height: number; + coinbase: string; + scriptsig: string; + reward: number; + jobId: string; + extraNonce: string; + extraNonce2Size: number; + prevHash: string; + coinbase1: string; + coinbase2: string; + merkleBranches: string[]; + version: string; + bits: string; + time: string; + timestamp: number; + cleanJobs: boolean; + received: number; +} diff --git a/frontend/src/app/master-page.module.ts b/frontend/src/app/master-page.module.ts index 2ee2e0bd8..35b632ab5 100644 --- a/frontend/src/app/master-page.module.ts +++ b/frontend/src/app/master-page.module.ts @@ -10,9 +10,10 @@ import { TestTransactionsComponent } from '@components/test-transactions/test-tr import { CalculatorComponent } from '@components/calculator/calculator.component'; import { BlocksList } from '@components/blocks-list/blocks-list.component'; import { RbfList } from '@components/rbf-list/rbf-list.component'; +import { StratumList } from '@components/stratum/stratum-list/stratum-list.component'; import { ServerHealthComponent } from '@components/server-health/server-health.component'; import { ServerStatusComponent } from '@components/server-health/server-status.component'; -import { FaucetComponent } from '@components/faucet/faucet.component' +import { FaucetComponent } from '@components/faucet/faucet.component'; const browserWindow = window || {}; // @ts-ignore @@ -56,6 +57,10 @@ const routes: Routes = [ path: 'rbf', component: RbfList, }, + { + path: 'stratum', + component: StratumList, + }, { path: 'terms-of-service', loadChildren: () => import('@components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule), diff --git a/frontend/src/app/services/mining.service.ts b/frontend/src/app/services/mining.service.ts index 760ce93cb..a181ef771 100644 --- a/frontend/src/app/services/mining.service.ts +++ b/frontend/src/app/services/mining.service.ts @@ -64,8 +64,8 @@ export class MiningService { ); } } - - /** + + /** * Get names and slugs of all pools */ public getPools(): Observable { @@ -75,7 +75,6 @@ export class MiningService { return this.poolsData; }) ); - } /** * Set the hashrate power of ten we want to display diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 0d006b552..9d2c2ed7b 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core'; import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs'; -import { AddressTxSummary, Transaction } from '@interfaces/electrs.interface'; -import { AccelerationDelta, HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockUpdate, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, isMempoolState } from '@interfaces/websocket.interface'; +import { Transaction } from '@interfaces/electrs.interface'; +import { AccelerationDelta, HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockUpdate, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, StratumJob, isMempoolState } from '@interfaces/websocket.interface'; import { Acceleration, AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '@interfaces/node-api.interface'; import { Router, NavigationStart } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; @@ -159,6 +159,8 @@ export class StateService { liveMempoolBlockTransactions$: Observable<{ block: number, transactions: { [txid: string]: TransactionStripped} }>; accelerations$ = new Subject(); liveAccelerations$: Observable; + stratumJobUpdate$ = new Subject<{ state: Record } | { job: StratumJob }>(); + stratumJobs$ = new BehaviorSubject>({}); txConfirmed$ = new Subject<[string, BlockExtended]>(); txReplaced$ = new Subject(); txRbfInfo$ = new Subject(); @@ -303,6 +305,24 @@ export class StateService { map((accMap) => Object.values(accMap).sort((a,b) => b.added - a.added)) ); + this.stratumJobUpdate$.pipe( + scan((acc: Record, update: { state: Record } | { job: StratumJob }) => { + if ('state' in update) { + // Replace the entire state + return update.state; + } else { + // Update or create a single job entry + return { + ...acc, + [update.job.pool]: update.job + }; + } + }, {}), + shareReplay(1) + ).subscribe(val => { + this.stratumJobs$.next(val); + }); + this.networkChanged$.subscribe((network) => { this.transactions$ = new BehaviorSubject(null); this.blocksSubject$.next([]); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 0f5368244..b82b32dd5 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -36,6 +36,7 @@ export class WebsocketService { private isTrackingAccelerations: boolean = false; private isTrackingWallet: boolean = false; private trackingWalletName: string; + private isTrackingStratum: string | number | false = false; private trackingMempoolBlock: number; private trackingMempoolBlockNetwork: string; private stoppingTrackMempoolBlock: any | null = null; @@ -143,6 +144,9 @@ export class WebsocketService { if (this.isTrackingWallet) { this.startTrackingWallet(this.trackingWalletName); } + if (this.isTrackingStratum !== false) { + this.startTrackStratum(this.isTrackingStratum); + } this.stateService.connectionState$.next(2); } @@ -289,6 +293,18 @@ export class WebsocketService { } } + startTrackStratum(pool: number | string) { + this.websocketSubject.next({ 'track-stratum': pool }); + this.isTrackingStratum = pool; + } + + stopTrackStratum() { + if (this.isTrackingStratum) { + this.websocketSubject.next({ 'track-stratum': null }); + this.isTrackingStratum = false; + } + } + fetchStatistics(historicalDate: string) { this.websocketSubject.next({ historicalDate }); } @@ -512,6 +528,14 @@ export class WebsocketService { this.stateService.previousRetarget$.next(response.previousRetarget); } + if (response.stratumJobs) { + this.stateService.stratumJobUpdate$.next({ state: response.stratumJobs }); + } + + if (response.stratumJob) { + this.stateService.stratumJobUpdate$.next({ job: response.stratumJob }); + } + if (response['tomahawk']) { this.stateService.serverHealth$.next(response['tomahawk']); } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 2bcfc1255..1d4f5fd99 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -83,6 +83,7 @@ import { AmountShortenerPipe } from '@app/shared/pipes/amount-shortener.pipe'; import { DifficultyAdjustmentsTable } from '@components/difficulty-adjustments-table/difficulty-adjustments-table.components'; import { BlocksList } from '@components/blocks-list/blocks-list.component'; import { RbfList } from '@components/rbf-list/rbf-list.component'; +import { StratumList } from '@components/stratum/stratum-list/stratum-list.component'; import { RewardStatsComponent } from '@components/reward-stats/reward-stats.component'; import { DataCyDirective } from '@app/data-cy.directive'; import { LoadingIndicatorComponent } from '@components/loading-indicator/loading-indicator.component'; @@ -201,6 +202,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/ DifficultyAdjustmentsTable, BlocksList, RbfList, + StratumList, DataCyDirective, RewardStatsComponent, LoadingIndicatorComponent, @@ -345,6 +347,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/ AmountShortenerPipe, DifficultyAdjustmentsTable, BlocksList, + StratumList, DataCyDirective, RewardStatsComponent, LoadingIndicatorComponent, From eddd7344ad95dee32c8816a87dd0be08a2d887a7 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 6 Sep 2024 17:44:56 +0000 Subject: [PATCH 30/60] stratum backend config --- backend/src/__fixtures__/mempool-config.template.json | 4 ++++ backend/src/__tests__/config.test.ts | 5 +++++ backend/src/api/services/stratum.ts | 6 +++++- backend/src/config.ts | 10 ++++++++++ backend/src/index.ts | 4 +++- 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index a9f246767..d3380a2ee 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -151,5 +151,9 @@ "ENABLED": true, "PAID": false, "API_KEY": "__MEMPOOL_CURRENCY_API_KEY__" + }, + "STRATUM": { + "ENABLED": false, + "API": "http://127.0.0.1:1234" } } diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index b3cf7e2a7..e76d22545 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -159,6 +159,11 @@ describe('Mempool Backend Config', () => { PAID: false, API_KEY: '', }); + + expect(config.STRATUM).toStrictEqual({ + ENABLED: false, + API: 'http://127.0.0.1:1234', + }); }); }); diff --git a/backend/src/api/services/stratum.ts b/backend/src/api/services/stratum.ts index 5fa3dd7a3..a8ee64106 100644 --- a/backend/src/api/services/stratum.ts +++ b/backend/src/api/services/stratum.ts @@ -1,5 +1,6 @@ import { WebSocket } from 'ws'; import logger from '../../logger'; +import config from '../../config'; import websocketHandler from '../websocket-handler'; export interface StratumJob { @@ -58,6 +59,9 @@ class StratumApi { } public async connectWebsocket(): Promise { + if (!config.STRATUM.ENABLED) { + return; + } this.runWebsocketLoop = true; if (this.startedWebsocketLoop) { return; @@ -65,7 +69,7 @@ class StratumApi { while (this.runWebsocketLoop) { this.startedWebsocketLoop = true; if (!this.ws) { - this.ws = new WebSocket(`http://localhost:3333`); + this.ws = new WebSocket(`${config.STRATUM.API}`); this.websocketConnected = true; this.ws.on('open', () => { diff --git a/backend/src/config.ts b/backend/src/config.ts index 794421551..0f1f44369 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -165,6 +165,10 @@ interface IConfig { WALLETS: { ENABLED: boolean; WALLETS: string[]; + }, + STRATUM: { + ENABLED: boolean; + API: string; } } @@ -332,6 +336,10 @@ const defaults: IConfig = { 'ENABLED': false, 'WALLETS': [], }, + 'STRATUM': { + 'ENABLED': false, + 'API': 'http://127.0.0.1:1234', + } }; class Config implements IConfig { @@ -354,6 +362,7 @@ class Config implements IConfig { REDIS: IConfig['REDIS']; FIAT_PRICE: IConfig['FIAT_PRICE']; WALLETS: IConfig['WALLETS']; + STRATUM: IConfig['STRATUM']; constructor() { const configs = this.merge(configFromFile, defaults); @@ -376,6 +385,7 @@ class Config implements IConfig { this.REDIS = configs.REDIS; this.FIAT_PRICE = configs.FIAT_PRICE; this.WALLETS = configs.WALLETS; + this.STRATUM = configs.STRATUM; } merge = (...objects: object[]): IConfig => { diff --git a/backend/src/index.ts b/backend/src/index.ts index 53c4c2f22..dc6a8ae1a 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -321,7 +321,9 @@ class Server { loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler)); accelerationApi.connectWebsocket(); - stratumApi.connectWebsocket(); + if (config.STRATUM.ENABLED) { + stratumApi.connectWebsocket(); + } } setUpHttpApiRoutes(): void { From 3ea491ad13b8a61927d7218192a0fdde1ed24ff9 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 9 Sep 2024 23:26:59 +0000 Subject: [PATCH 31/60] stratum frontend config --- frontend/mempool-frontend-config.sample.json | 1 + frontend/src/app/services/state.service.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/frontend/mempool-frontend-config.sample.json b/frontend/mempool-frontend-config.sample.json index f9f2576d6..70dc2edba 100644 --- a/frontend/mempool-frontend-config.sample.json +++ b/frontend/mempool-frontend-config.sample.json @@ -27,5 +27,6 @@ "ACCELERATOR": false, "ACCELERATOR_BUTTON": true, "PUBLIC_ACCELERATIONS": false, + "STRATUM_ENABLED": false, "SERVICES_API": "https://mempool.space/api/v1/services" } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 9d2c2ed7b..7f8f81744 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -81,6 +81,7 @@ export interface Env { ADDITIONAL_CURRENCIES: boolean; GIT_COMMIT_HASH_MEMPOOL_SPACE?: string; PACKAGE_JSON_VERSION_MEMPOOL_SPACE?: string; + STRATUM_ENABLED: boolean; SERVICES_API?: string; customize?: Customization; PROD_DOMAINS: string[]; @@ -123,6 +124,7 @@ const defaultEnv: Env = { 'ACCELERATOR_BUTTON': true, 'PUBLIC_ACCELERATIONS': false, 'ADDITIONAL_CURRENCIES': false, + 'STRATUM_ENABLED': false, 'SERVICES_API': 'https://mempool.space/api/v1/services', 'PROD_DOMAINS': [], }; From cb4bf0611e030143da0f7cbdbdcc46ef329c858a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 9 Sep 2024 23:27:29 +0000 Subject: [PATCH 32/60] add blockchain bar to stratum page --- .../stratum/stratum-list/stratum-list.component.ts | 1 + frontend/src/app/master-page.module.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts index 25bb42d1c..af34fd091 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts @@ -45,6 +45,7 @@ export class StratumList implements OnInit, OnDestroy { ) {} ngOnInit(): void { + this.websocketService.want(['stats', 'blocks', 'mempool-blocks']); this.miningService.getPools().subscribe(pools => { this.pools = {}; for (const pool of pools) { diff --git a/frontend/src/app/master-page.module.ts b/frontend/src/app/master-page.module.ts index 35b632ab5..f0af944cc 100644 --- a/frontend/src/app/master-page.module.ts +++ b/frontend/src/app/master-page.module.ts @@ -57,10 +57,16 @@ const routes: Routes = [ path: 'rbf', component: RbfList, }, - { + ...(browserWindowEnv.STRATUM_ENABLED ? [{ path: 'stratum', - component: StratumList, - }, + component: StartComponent, + children: [ + { + path: '', + component: StratumList, + } + ] + }] : []), { path: 'terms-of-service', loadChildren: () => import('@components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule), From 9ba7172b5bb829061aa6f88268a55f9dccc3f1e3 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 28 Sep 2024 14:51:05 +0000 Subject: [PATCH 33/60] add coinbase tag column to stratum table --- .../stratum-list/stratum-list.component.html | 4 +++ .../stratum-list/stratum-list.component.scss | 9 +++++- .../stratum-list/stratum-list.component.ts | 29 +++++++++++++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html index 6132035be..08d7fb0ef 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html @@ -9,6 +9,7 @@ Height Reward + Coinbase Tag Merkle Branches @@ -24,6 +25,9 @@ + + {{ row.job.tag }} + @for (cell of row.merkleCells; track $index) {
diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss index da0e63967..6679f2257 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss @@ -6,9 +6,16 @@ td { position: relative; height: 2em; - &.height, &.reward { + &.height, &.reward, &.tag { padding: 0 5px; } + + &.tag { + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } &.pool { padding-left: 5px; diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts index af34fd091..1ab1a1c94 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts @@ -8,10 +8,14 @@ import { SinglePoolStats } from '../../../interfaces/node-api.interface'; type MerkleCellType = ' ' | '┬' | '├' | '└' | '│' | '─' | 'leaf'; +interface TaggedStratumJob extends StratumJob { + tag: string; +} + interface MerkleCell { hash: string; type: MerkleCellType; - job?: StratumJob; + job?: TaggedStratumJob; } interface MerkleTree { @@ -22,10 +26,25 @@ interface MerkleTree { } interface PoolRow { - job: StratumJob; + job: TaggedStratumJob; merkleCells: MerkleCell[]; } +function parseTag(scriptSig: string): string { + const hex = scriptSig.slice(8).replace(/6d6d.{64}/, ''); + const bytes: number[] = []; + for (let i = 0; i < hex.length; i += 2) { + bytes.push(parseInt(hex.substr(i, 2), 16)); + } + const ascii = new TextDecoder('utf8').decode(Uint8Array.from(bytes)).replace(/\uFFFD/g, '').replace(/\\0/g, ''); + if (ascii.includes('/ViaBTC/')) { + return '/ViaBTC/'; + } else if (ascii.includes('SpiderPool/')) { + return 'SpiderPool/'; + } + return ascii.match(/\/.*\//)?.[0] || ascii; +} + @Component({ selector: 'app-stratum-list', templateUrl: './stratum-list.component.html', @@ -60,7 +79,11 @@ export class StratumList implements OnInit, OnDestroy { this.websocketService.startTrackStratum('all'); } - processJobs(jobs: Record): PoolRow[] { + processJobs(rawJobs: Record): PoolRow[] { + const jobs: Record = {}; + for (const [id, job] of Object.entries(rawJobs)) { + jobs[id] = { ...job, tag: parseTag(job.scriptsig) }; + } if (Object.keys(jobs).length === 0) { return []; } From d6283c54eef688fdfdf610327b7630d4d9b941fe Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 25 Oct 2024 07:40:54 +0000 Subject: [PATCH 34/60] fix stratum config --- backend/mempool-config.sample.json | 4 ++++ backend/src/__fixtures__/mempool-config.template.json | 2 +- backend/src/__tests__/config.test.ts | 2 +- backend/src/config.ts | 2 +- docker/backend/mempool-config.json | 4 ++++ docker/backend/start.sh | 8 ++++++++ 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index 7ad25dff0..c2715153b 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -155,6 +155,10 @@ "API": "https://mempool.space/api/v1/services", "ACCELERATIONS": false }, + "STRATUM": { + "ENABLED": false, + "API": "http://localhost:1234" + }, "FIAT_PRICE": { "ENABLED": true, "PAID": false, diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index d3380a2ee..0ca5654a5 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -154,6 +154,6 @@ }, "STRATUM": { "ENABLED": false, - "API": "http://127.0.0.1:1234" + "API": "http://localhost:1234" } } diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index e76d22545..e0437941f 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -162,7 +162,7 @@ describe('Mempool Backend Config', () => { expect(config.STRATUM).toStrictEqual({ ENABLED: false, - API: 'http://127.0.0.1:1234', + API: 'http://localhost:1234', }); }); }); diff --git a/backend/src/config.ts b/backend/src/config.ts index 0f1f44369..a1050a7d5 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -338,7 +338,7 @@ const defaults: IConfig = { }, 'STRATUM': { 'ENABLED': false, - 'API': 'http://127.0.0.1:1234', + 'API': 'http://localhost:1234', } }; diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index c7ade9b7b..ee8e329a6 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -148,6 +148,10 @@ "API": "__MEMPOOL_SERVICES_API__", "ACCELERATIONS": __MEMPOOL_SERVICES_ACCELERATIONS__ }, + "STRATUM": { + "ENABLED": __STRATUM_ENABLED__, + "API": "__STRATUM_API__" + }, "REDIS": { "ENABLED": __REDIS_ENABLED__, "UNIX_SOCKET_PATH": "__REDIS_UNIX_SOCKET_PATH__", diff --git a/docker/backend/start.sh b/docker/backend/start.sh index d4765972e..8adb631da 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -149,6 +149,10 @@ __REPLICATION_SERVERS__=${REPLICATION_SERVERS:=[]} __MEMPOOL_SERVICES_API__=${MEMPOOL_SERVICES_API:="https://mempool.space/api/v1/services"} __MEMPOOL_SERVICES_ACCELERATIONS__=${MEMPOOL_SERVICES_ACCELERATIONS:=false} +# STRATUM +__STRATUM_ENABLED__=${STRATUM_ENABLED:=false} +__STRATUM_API__=${STRATUM_API:="http://localhost:1234"} + # REDIS __REDIS_ENABLED__=${REDIS_ENABLED:=false} __REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=""} @@ -300,6 +304,10 @@ sed -i "s!__REPLICATION_SERVERS__!${__REPLICATION_SERVERS__}!g" mempool-config.j sed -i "s!__MEMPOOL_SERVICES_API__!${__MEMPOOL_SERVICES_API__}!g" mempool-config.json sed -i "s!__MEMPOOL_SERVICES_ACCELERATIONS__!${__MEMPOOL_SERVICES_ACCELERATIONS__}!g" mempool-config.json +# STRATUM +sed -i "s!__STRATUM_ENABLED__!${__STRATUM_ENABLED__}!g" mempool-config.json +sed -i "s!__STRATUM_API__!${__STRATUM_API__}!g" mempool-config.json + # REDIS sed -i "s!__REDIS_ENABLED__!${__REDIS_ENABLED__}!g" mempool-config.json sed -i "s!__REDIS_UNIX_SOCKET_PATH__!${__REDIS_UNIX_SOCKET_PATH__}!g" mempool-config.json From b254be2f49b7491086743cb5ddeb6b880ea2a813 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 6 Sep 2024 17:46:12 +0000 Subject: [PATCH 35/60] add stratum job to pool page --- .../app/components/pool/pool.component.html | 114 ++++++++- .../app/components/pool/pool.component.scss | 232 +++++++++++------- .../src/app/components/pool/pool.component.ts | 36 ++- .../stratum-list/stratum-list.component.ts | 5 +- 4 files changed, 292 insertions(+), 95 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index b3c6430a8..faa0003c4 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -10,7 +10,7 @@

{{ poolStats.pool.name }}

-
+
@@ -173,7 +173,119 @@
+ + +

Next block

+
+
+
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
HeightExpectedRewardTimestamp
+ {{ job.height }} + + + + + ~ + + + + +
+
+ + + + + + + + + + + + + + + + + +
Coinbase tagCleanPrevhashJob Received
+ {{ job.scriptsig | hex2ascii }} + + @if (job.cleanJobs) { + + } @else { + + } + + + + + + +
+
+ + + + + + + + + @for (branch of job.merkleBranches; track $index) { + + } + @for (_ of [].constructor(Math.max(0, 12 - job.merkleBranches.length)); track $index) { + + } + + +
+ + Merkle Branches +   + + +
+
+
+
+
+
+ +

Blocks

diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 5c2fedd26..31d12474f 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -49,111 +49,110 @@ div.scrollable { max-height: 75px; } -.box { - padding-bottom: 5px; +.pool-details { @media (min-width: 767.98px) { min-height: 187px; } -} -.label { - width: 25%; - @media (min-width: 767.98px) { - vertical-align: middle; + .label { + width: 25%; + @media (min-width: 767.98px) { + vertical-align: middle; + } + @media (max-width: 767.98px) { + font-weight: bold; + } } - @media (max-width: 767.98px) { - font-weight: bold; + .label.addresses { + vertical-align: top; + padding-top: 25px; + } + .addresses-data { + vertical-align: top; + font-family: monospace; + font-size: 14px; } -} -.label.addresses { - vertical-align: top; - padding-top: 25px; -} -.addresses-data { - vertical-align: top; - font-family: monospace; - font-size: 14px; -} -.data { - text-align: right; - padding-left: 5%; - @media (max-width: 992px) { - text-align: left; - padding-left: 12px; - } - @media (max-width: 450px) { + .data { text-align: right; + padding-left: 5%; + @media (max-width: 992px) { + text-align: left; + padding-left: 12px; + } + @media (max-width: 450px) { + text-align: right; + } } -} -.progress { - background-color: var(--secondary); -} + .progress { + background-color: var(--secondary); + } -.coinbase { - width: 20%; - @media (max-width: 875px) { - display: none; - } -} - -.height { - width: 10%; -} - -.timestamp { - @media (max-width: 875px) { - padding-left: 50px; - } - @media (max-width: 685px) { - display: none; - } -} - -.mined { - width: 13%; - @media (max-width: 1100px) { - display: none; - } -} - -.txs { - padding-right: 40px; - @media (max-width: 1100px) { - padding-right: 10px; - } - @media (max-width: 875px) { - padding-right: 20px; - } - @media (max-width: 567px) { - padding-right: 10px; - } -} - -.size { - width: 12%; - @media (max-width: 1000px) { - width: 15%; - } - @media (max-width: 875px) { + .coinbase { width: 20%; + @media (max-width: 875px) { + display: none; + } } - @media (max-width: 650px) { - width: 20%; - } - @media (max-width: 450px) { - display: none; - } -} -.scriptmessage { - overflow: hidden; - display: inline-block; - text-overflow: ellipsis; - vertical-align: middle; - width: auto; - text-align: left; + .height { + width: 10%; + } + + .timestamp { + @media (max-width: 875px) { + padding-left: 50px; + } + @media (max-width: 685px) { + display: none; + } + } + + .mined { + width: 13%; + @media (max-width: 1100px) { + display: none; + } + } + + .txs { + padding-right: 40px; + @media (max-width: 1100px) { + padding-right: 10px; + } + @media (max-width: 875px) { + padding-right: 20px; + } + @media (max-width: 567px) { + padding-right: 10px; + } + } + + .size { + width: 12%; + @media (max-width: 1000px) { + width: 15%; + } + @media (max-width: 875px) { + width: 20%; + } + @media (max-width: 650px) { + width: 20%; + } + @media (max-width: 450px) { + display: none; + } + } + + .scriptmessage { + overflow: hidden; + display: inline-block; + text-overflow: ellipsis; + vertical-align: middle; + width: auto; + text-align: left; + } } .skeleton-loader { @@ -214,4 +213,55 @@ div.scrollable { .taller-row { height: 75px; +} + +.stratum-table { + width: 100%; + + .merkle { + width: 100px; + } + + .empty-branch { + outline: solid 1px white; + outline-offset: -1px; + + &::after { + content: ""; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 100%; + background: linear-gradient(to top left, transparent, transparent 48%, white 49%, white 51%, transparent 52%, transparent); + } + } + + td { + position: relative; + height: 2em; + } +} + +.job-table { + td, th { + width: 25%; + max-width: 25%; + min-width: 25%; + overflow: hidden; + text-overflow: ellipsis; + padding: 0.1rem 0.2rem; + } + + @media (max-width: 767.98px) { + .expected, .timestamp, .clean, .job-received { + display: none; + } + } +} + +.title-link, .title-link:hover, .title-link:focus, .title-link:active { + display: block; + text-decoration: none; + color: inherit; } \ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 1893f0a48..23b795613 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -10,6 +10,9 @@ import { selectPowerOfTen } from '@app/bitcoin.utils'; import { formatNumber } from '@angular/common'; import { SeoService } from '@app/services/seo.service'; import { HttpErrorResponse } from '@angular/common/http'; +import { StratumJob } from '../../interfaces/websocket.interface'; +import { WebsocketService } from '../../services/websocket.service'; +import { MiningService } from '../../services/mining.service'; interface AccelerationTotal { cost: number, @@ -27,12 +30,16 @@ export class PoolComponent implements OnInit { @Input() left: number | string = 75; gfg = true; + stratumEnabled = this.stateService.env.STRATUM_ENABLED; formatNumber = formatNumber; + Math = Math; slugSubscription: Subscription; poolStats$: Observable; blocks$: Observable; oobFees$: Observable; + job$: Observable; + expectedBlockTime$: Observable; isLoading = true; error: HttpErrorResponse | null = null; @@ -53,6 +60,8 @@ export class PoolComponent implements OnInit { private apiService: ApiService, private route: ActivatedRoute, public stateService: StateService, + private websocketService: WebsocketService, + private miningService: MiningService, private seoService: SeoService, ) { this.auditAvailable = this.stateService.env.AUDIT; @@ -62,7 +71,7 @@ export class PoolComponent implements OnInit { this.slugSubscription = this.route.params.pipe(map((params) => params.slug)).subscribe((slug) => { this.isLoading = true; this.blocks = []; - this.chartOptions = {}; + this.chartOptions = {}; this.slug = slug; this.initializeObservables(); }); @@ -129,6 +138,31 @@ export class PoolComponent implements OnInit { }), filter(oob => oob.length === 3 && oob[2].count > 0) ); + + if (this.stratumEnabled) { + this.job$ = combineLatest([ + this.poolStats$.pipe( + tap((poolStats) => { + this.websocketService.startTrackStratum(poolStats.pool.unique_id); + }) + ), + this.stateService.stratumJobs$ + ]).pipe( + map(([poolStats, jobs]) => { + return jobs[poolStats.pool.unique_id]; + }) + ); + + this.expectedBlockTime$ = combineLatest([ + this.miningService.getMiningStats('1w'), + this.poolStats$, + this.stateService.difficultyAdjustment$ + ]).pipe( + map(([miningStats, poolStat, da]) => { + return (da.timeAvg / ((poolStat.estimatedHashrate || 0) / (miningStats.lastEstimatedHashrate * 1_000_000_000_000_000_000))) + Date.now() + da.timeOffset; + }) + ); + } } prepareChartOptions(hashrate, share) { diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts index 1ab1a1c94..0af9f0976 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts @@ -36,13 +36,14 @@ function parseTag(scriptSig: string): string { for (let i = 0; i < hex.length; i += 2) { bytes.push(parseInt(hex.substr(i, 2), 16)); } - const ascii = new TextDecoder('utf8').decode(Uint8Array.from(bytes)).replace(/\uFFFD/g, '').replace(/\\0/g, ''); + // eslint-disable-next-line no-control-regex + const ascii = new TextDecoder('utf8').decode(Uint8Array.from(bytes)).replace(/\uFFFD/g, '').replace(/\\0/g, '').replace(/[\x00-\x1F\x7F-\x9F]/g, ''); if (ascii.includes('/ViaBTC/')) { return '/ViaBTC/'; } else if (ascii.includes('SpiderPool/')) { return 'SpiderPool/'; } - return ascii.match(/\/.*\//)?.[0] || ascii; + return (ascii.match(/\/.*\//)?.[0] || ascii).trim(); } @Component({ From caa2d83247fcd245cbe0aabb1a106ca17da5d2d6 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 19 Jan 2025 02:34:05 -0800 Subject: [PATCH 36/60] Update staging hosts --- frontend/proxy.conf.staging.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js index e24662038..e6f33698b 100644 --- a/frontend/proxy.conf.staging.js +++ b/frontend/proxy.conf.staging.js @@ -3,8 +3,8 @@ const fs = require('fs'); let PROXY_CONFIG = require('./proxy.conf'); PROXY_CONFIG.forEach(entry => { - entry.target = entry.target.replace("mempool.space", "mempool-staging.fra.mempool.space"); - entry.target = entry.target.replace("liquid.network", "liquid-staging.fra.mempool.space"); + entry.target = entry.target.replace("mempool.space", "node201.fmt.mempool.space"); + entry.target = entry.target.replace("liquid.network", "liquid-staging.fmt.mempool.space"); }); module.exports = PROXY_CONFIG; From 671b5ea2f2eb3ce9515a233702c5b784093f77c1 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 12:24:26 -0800 Subject: [PATCH 37/60] Reroute testnet4 tests to a different server --- .github/workflows/ci.yml | 1 + frontend/proxy.conf.staging.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a29e9184..767cf2694 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -327,6 +327,7 @@ jobs: browser: "chrome" ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" env: + CYPRESS_REROUTE_TESTNET: ${{ matrix.spec == 'cypress/e2e/testnet4/*.spec.ts' }} COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js index e6f33698b..eda7d2b9d 100644 --- a/frontend/proxy.conf.staging.js +++ b/frontend/proxy.conf.staging.js @@ -3,7 +3,8 @@ const fs = require('fs'); let PROXY_CONFIG = require('./proxy.conf'); PROXY_CONFIG.forEach(entry => { - entry.target = entry.target.replace("mempool.space", "node201.fmt.mempool.space"); + const hostname = process.env.CYPRESS_REROUTE_TESTNET ? 'node201.fmt.mempool.space' : 'mempool-staging.fra.mempool.space'; + entry.target = entry.target.replace("mempool.space", hostname); entry.target = entry.target.replace("liquid.network", "liquid-staging.fmt.mempool.space"); }); From 34099e386112b781d7b70ca886f35cab8d23d969 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 12:32:37 -0800 Subject: [PATCH 38/60] Stop switching to testnet4 until we can check for the proxied server --- frontend/cypress/e2e/mainnet/mainnet.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/cypress/e2e/mainnet/mainnet.spec.ts b/frontend/cypress/e2e/mainnet/mainnet.spec.ts index a1082b769..7e17c09cd 100644 --- a/frontend/cypress/e2e/mainnet/mainnet.spec.ts +++ b/frontend/cypress/e2e/mainnet/mainnet.spec.ts @@ -344,7 +344,9 @@ describe('Mainnet', () => { cy.visit('/'); cy.waitForSkeletonGone(); - cy.changeNetwork('testnet4'); + //TODO(knorrium): add a check for the proxied server + // cy.changeNetwork('testnet4'); + cy.changeNetwork('signet'); cy.changeNetwork('mainnet'); }); From 7e766cc28db6e67386aab486a5d2e4e35eb3fda1 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 12:36:20 -0800 Subject: [PATCH 39/60] Change spec test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 767cf2694..af05a2448 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -327,7 +327,7 @@ jobs: browser: "chrome" ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" env: - CYPRESS_REROUTE_TESTNET: ${{ matrix.spec == 'cypress/e2e/testnet4/*.spec.ts' }} + CYPRESS_REROUTE_TESTNET: ${{ contains(matrix.spec, 'testnet') }} COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 517a30d2b0e76c6a88e25b0e0d4c4dfc61597038 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 13:25:29 -0800 Subject: [PATCH 40/60] Split module and spec matrix --- .github/workflows/ci.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af05a2448..594df2a9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -252,16 +252,23 @@ jobs: fail-fast: false matrix: module: ["mempool", "liquid"] + spec: + - "cypress/e2e/mainnet/*.spec.ts" + - "cypress/e2e/signet/*.spec.ts" + - "cypress/e2e/testnet4/*.spec.ts" + - "cypress/e2e/liquid/liquid.spec.ts" + - "cypress/e2e/liquidtestnet/liquidtestnet.spec.ts" include: - module: "mempool" - spec: | - cypress/e2e/mainnet/*.spec.ts - cypress/e2e/signet/*.spec.ts - cypress/e2e/testnet4/*.spec.ts + spec: "cypress/e2e/mainnet/*.spec.ts" + - module: "mempool" + spec: "cypress/e2e/signet/*.spec.ts" + - module: "mempool" + spec: "cypress/e2e/testnet4/*.spec.ts" - module: "liquid" - spec: | - cypress/e2e/liquid/liquid.spec.ts - cypress/e2e/liquidtestnet/liquidtestnet.spec.ts + spec: "cypress/e2e/liquid/liquid.spec.ts" + - module: "liquid" + spec: "cypress/e2e/liquidtestnet/liquidtestnet.spec.ts" name: E2E tests for ${{ matrix.module }} steps: From e9e8b0c758c476e705a5696268808838a7c3ff58 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 13:38:06 -0800 Subject: [PATCH 41/60] Use testnet as a matrix config instead --- .github/workflows/ci.yml | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 594df2a9f..7cff27841 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -251,24 +251,19 @@ jobs: strategy: fail-fast: false matrix: - module: ["mempool", "liquid"] - spec: - - "cypress/e2e/mainnet/*.spec.ts" - - "cypress/e2e/signet/*.spec.ts" - - "cypress/e2e/testnet4/*.spec.ts" - - "cypress/e2e/liquid/liquid.spec.ts" - - "cypress/e2e/liquidtestnet/liquidtestnet.spec.ts" + module: ["mempool", "liquid", "testnet4"] include: - module: "mempool" - spec: "cypress/e2e/mainnet/*.spec.ts" - - module: "mempool" - spec: "cypress/e2e/signet/*.spec.ts" - - module: "mempool" - spec: "cypress/e2e/testnet4/*.spec.ts" + spec: | + cypress/e2e/mainnet/*.spec.ts + cypress/e2e/signet/*.spec.ts + - module: "testnet4" + spec: | + cypress/e2e/testnet4/*.spec.ts - module: "liquid" - spec: "cypress/e2e/liquid/liquid.spec.ts" - - module: "liquid" - spec: "cypress/e2e/liquidtestnet/liquidtestnet.spec.ts" + spec: | + cypress/e2e/liquid/liquid.spec.ts + cypress/e2e/liquidtestnet/liquidtestnet.spec.ts name: E2E tests for ${{ matrix.module }} steps: @@ -334,7 +329,7 @@ jobs: browser: "chrome" ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" env: - CYPRESS_REROUTE_TESTNET: ${{ contains(matrix.spec, 'testnet') }} + CYPRESS_REROUTE_TESTNET: ${{ contains(matrix.spec, 'testnet4') }} COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7f6399093eafa5aeb74aa81ab2b136502c76bd6e Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 13:56:16 -0800 Subject: [PATCH 42/60] Fix YAML --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cff27841..784a02afc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -258,8 +258,8 @@ jobs: cypress/e2e/mainnet/*.spec.ts cypress/e2e/signet/*.spec.ts - module: "testnet4" - spec: | - cypress/e2e/testnet4/*.spec.ts + spec: | + cypress/e2e/testnet4/*.spec.ts - module: "liquid" spec: | cypress/e2e/liquid/liquid.spec.ts From f59e95fcc8419426550be6bad69c6507e87179c4 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 14:11:41 -0800 Subject: [PATCH 43/60] Run the default mempool config if we are running testnet4 tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 784a02afc..146372f40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -318,7 +318,7 @@ jobs: with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend - build: npm run config:defaults:${{ matrix.module }} + build: ${{ matrix.module == 'testnet4' && 'npm run config:defaults:mempool' || 'npm run config:defaults:${{ matrix.module }}' }} start: npm run start:local-staging wait-on: "http://localhost:4200" wait-on-timeout: 120 From 1098d2fe3c44b83624d3e17cda2ea73643d3dcf8 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 14:21:23 -0800 Subject: [PATCH 44/60] Change build step to shell script --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 146372f40..ac5326323 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -318,7 +318,12 @@ jobs: with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend - build: ${{ matrix.module == 'testnet4' && 'npm run config:defaults:mempool' || 'npm run config:defaults:${{ matrix.module }}' }} + build: | + if [[ "${{ matrix.module }}" == "testnet4" ]]; then + npm run config:defaults:mempool + else + npm run config:defaults:${{ matrix.module }} + fi start: npm run start:local-staging wait-on: "http://localhost:4200" wait-on-timeout: 120 From 3d1aacbd66e69f830a6b0034577fa284696dd2ff Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 19:21:02 -0800 Subject: [PATCH 45/60] Copypasta matrix for tests --- .github/workflows/ci.yml | 80 +++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac5326323..6d2fc387f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -252,18 +252,6 @@ jobs: fail-fast: false matrix: module: ["mempool", "liquid", "testnet4"] - include: - - module: "mempool" - spec: | - cypress/e2e/mainnet/*.spec.ts - cypress/e2e/signet/*.spec.ts - - module: "testnet4" - spec: | - cypress/e2e/testnet4/*.spec.ts - - module: "liquid" - spec: | - cypress/e2e/liquid/liquid.spec.ts - cypress/e2e/liquidtestnet/liquidtestnet.spec.ts name: E2E tests for ${{ matrix.module }} steps: @@ -312,29 +300,77 @@ jobs: - name: Unzip assets before building (src/resources) run: unzip -o promo-video-assets.zip -d ${{ matrix.module }}/frontend/src/resources/promo-video - + + # mempool - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'mempool' }} uses: cypress-io/github-action@v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend - build: | - if [[ "${{ matrix.module }}" == "testnet4" ]]; then - npm run config:defaults:mempool - else - npm run config:defaults:${{ matrix.module }} - fi + build: npm run config:defaults:${{ matrix.module }} start: npm run start:local-staging wait-on: "http://localhost:4200" wait-on-timeout: 120 record: true parallel: true - spec: ${{ matrix.spec }} + spec: | + cypress/e2e/mainnet/*.spec.ts + cypress/e2e/signet/*.spec.ts group: Tests on Chrome (${{ matrix.module }}) browser: "chrome" ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" env: - CYPRESS_REROUTE_TESTNET: ${{ contains(matrix.spec, 'testnet4') }} + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + + # liquid + - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'liquid' }} + uses: cypress-io/github-action@v5 + with: + tag: ${{ github.event_name }} + working-directory: ${{ matrix.module }}/frontend + build: npm run config:defaults:${{ matrix.module }} + start: npm run start:local-staging + wait-on: "http://localhost:4200" + wait-on-timeout: 120 + record: true + parallel: true + spec: | + cypress/e2e/liquid/liquid.spec.ts + cypress/e2e/liquidtestnet/liquidtestnet.spec.ts + group: Tests on Chrome (${{ matrix.module }}) + browser: "chrome" + ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" + env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + + # testnet + - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'testnet4' }} + uses: cypress-io/github-action@v5 + with: + tag: ${{ github.event_name }} + working-directory: ${{ matrix.module }}/frontend + build: npm run config:defaults:mempool + start: npm run start:local-staging + wait-on: "http://localhost:4200" + wait-on-timeout: 120 + record: true + parallel: true + spec: | + cypress/e2e/testnet4/*.spec.ts + group: Tests on Chrome (${{ matrix.module }}) + browser: "chrome" + ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" + env: + CYPRESS_REROUTE_TESTNET: true COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -367,4 +403,4 @@ jobs: - name: Validate JSON syntax run: | cat mempool-config.json | jq - working-directory: docker/docker/backend + working-directory: docker/docker/backend \ No newline at end of file From 227d99e9909d1401867f3cb6258e2c4c4b05ead9 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 19:35:08 -0800 Subject: [PATCH 46/60] Fix reroute logic --- frontend/proxy.conf.staging.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js index eda7d2b9d..260b222c0 100644 --- a/frontend/proxy.conf.staging.js +++ b/frontend/proxy.conf.staging.js @@ -3,7 +3,8 @@ const fs = require('fs'); let PROXY_CONFIG = require('./proxy.conf'); PROXY_CONFIG.forEach(entry => { - const hostname = process.env.CYPRESS_REROUTE_TESTNET ? 'node201.fmt.mempool.space' : 'mempool-staging.fra.mempool.space'; + const hostname = process.env.CYPRESS_REROUTE_TESTNET === 'true' ? 'mempool-staging.fra.mempool.space' : 'node201.fmt.mempool.space'; + console.log(`e2e tests running against ${hostname}`); entry.target = entry.target.replace("mempool.space", hostname); entry.target = entry.target.replace("liquid.network", "liquid-staging.fmt.mempool.space"); }); From 003956fd16caed93a243c37095cae48c667b544a Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sun, 19 Jan 2025 19:48:12 -0800 Subject: [PATCH 47/60] Update staging hosts Add rerouting logic for testnet4 tests Split CI e2e workflow matrix into mempool, liquid and testnet4 --- .github/workflows/ci.yml | 72 ++++++++++++++++---- frontend/cypress/e2e/mainnet/mainnet.spec.ts | 4 +- frontend/proxy.conf.staging.js | 6 +- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a29e9184..6d2fc387f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -251,17 +251,7 @@ jobs: strategy: fail-fast: false matrix: - module: ["mempool", "liquid"] - include: - - module: "mempool" - spec: | - cypress/e2e/mainnet/*.spec.ts - cypress/e2e/signet/*.spec.ts - cypress/e2e/testnet4/*.spec.ts - - module: "liquid" - spec: | - cypress/e2e/liquid/liquid.spec.ts - cypress/e2e/liquidtestnet/liquidtestnet.spec.ts + module: ["mempool", "liquid", "testnet4"] name: E2E tests for ${{ matrix.module }} steps: @@ -310,8 +300,10 @@ jobs: - name: Unzip assets before building (src/resources) run: unzip -o promo-video-assets.zip -d ${{ matrix.module }}/frontend/src/resources/promo-video - + + # mempool - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'mempool' }} uses: cypress-io/github-action@v5 with: tag: ${{ github.event_name }} @@ -322,7 +314,9 @@ jobs: wait-on-timeout: 120 record: true parallel: true - spec: ${{ matrix.spec }} + spec: | + cypress/e2e/mainnet/*.spec.ts + cypress/e2e/signet/*.spec.ts group: Tests on Chrome (${{ matrix.module }}) browser: "chrome" ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" @@ -332,6 +326,56 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + # liquid + - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'liquid' }} + uses: cypress-io/github-action@v5 + with: + tag: ${{ github.event_name }} + working-directory: ${{ matrix.module }}/frontend + build: npm run config:defaults:${{ matrix.module }} + start: npm run start:local-staging + wait-on: "http://localhost:4200" + wait-on-timeout: 120 + record: true + parallel: true + spec: | + cypress/e2e/liquid/liquid.spec.ts + cypress/e2e/liquidtestnet/liquidtestnet.spec.ts + group: Tests on Chrome (${{ matrix.module }}) + browser: "chrome" + ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" + env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + + # testnet + - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'testnet4' }} + uses: cypress-io/github-action@v5 + with: + tag: ${{ github.event_name }} + working-directory: ${{ matrix.module }}/frontend + build: npm run config:defaults:mempool + start: npm run start:local-staging + wait-on: "http://localhost:4200" + wait-on-timeout: 120 + record: true + parallel: true + spec: | + cypress/e2e/testnet4/*.spec.ts + group: Tests on Chrome (${{ matrix.module }}) + browser: "chrome" + ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" + env: + CYPRESS_REROUTE_TESTNET: true + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + validate_docker_json: if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" runs-on: "ubuntu-latest" @@ -359,4 +403,4 @@ jobs: - name: Validate JSON syntax run: | cat mempool-config.json | jq - working-directory: docker/docker/backend + working-directory: docker/docker/backend \ No newline at end of file diff --git a/frontend/cypress/e2e/mainnet/mainnet.spec.ts b/frontend/cypress/e2e/mainnet/mainnet.spec.ts index a1082b769..7e17c09cd 100644 --- a/frontend/cypress/e2e/mainnet/mainnet.spec.ts +++ b/frontend/cypress/e2e/mainnet/mainnet.spec.ts @@ -344,7 +344,9 @@ describe('Mainnet', () => { cy.visit('/'); cy.waitForSkeletonGone(); - cy.changeNetwork('testnet4'); + //TODO(knorrium): add a check for the proxied server + // cy.changeNetwork('testnet4'); + cy.changeNetwork('signet'); cy.changeNetwork('mainnet'); }); diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js index e24662038..260b222c0 100644 --- a/frontend/proxy.conf.staging.js +++ b/frontend/proxy.conf.staging.js @@ -3,8 +3,10 @@ const fs = require('fs'); let PROXY_CONFIG = require('./proxy.conf'); PROXY_CONFIG.forEach(entry => { - entry.target = entry.target.replace("mempool.space", "mempool-staging.fra.mempool.space"); - entry.target = entry.target.replace("liquid.network", "liquid-staging.fra.mempool.space"); + const hostname = process.env.CYPRESS_REROUTE_TESTNET === 'true' ? 'mempool-staging.fra.mempool.space' : 'node201.fmt.mempool.space'; + console.log(`e2e tests running against ${hostname}`); + entry.target = entry.target.replace("mempool.space", hostname); + entry.target = entry.target.replace("liquid.network", "liquid-staging.fmt.mempool.space"); }); module.exports = PROXY_CONFIG; From 390bbf1097ca2cc65bc2b5d6cb45c54fa7a5ef44 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 20 Jan 2025 15:20:29 +0900 Subject: [PATCH 48/60] add new fa icon --- frontend/src/app/shared/shared.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 1d4f5fd99..283f9eb54 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -7,7 +7,7 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, - faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot } from '@fortawesome/free-solid-svg-icons'; + faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes } 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'; @@ -459,5 +459,6 @@ export class SharedModule { library.addIcons(faCalendarCheck); library.addIcons(faMoneyBillTrendUp); library.addIcons(faRobot); + library.addIcons(faShareNodes); } } From 4e735cc8b03d87f850286461474d8f110580e444 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 20 Jan 2025 07:30:27 +0000 Subject: [PATCH 49/60] fix stratum tree rendering with different branch lengths --- .../stratum-list/stratum-list.component.ts | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts index 0af9f0976..6f252babe 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts @@ -8,8 +8,10 @@ import { SinglePoolStats } from '../../../interfaces/node-api.interface'; type MerkleCellType = ' ' | '┬' | '├' | '└' | '│' | '─' | 'leaf'; + interface TaggedStratumJob extends StratumJob { tag: string; + merkleBranchIds: string[]; } interface MerkleCell { @@ -46,6 +48,18 @@ function parseTag(scriptSig: string): string { return (ascii.match(/\/.*\//)?.[0] || ascii).trim(); } +function getMerkleBranchIds(merkleBranches: string[], numBranches: number): string[] { + let lastHash = ''; + const ids: string[] = []; + for (let i = 0; i < numBranches; i++) { + if (merkleBranches[i]) { + lastHash = merkleBranches[i]; + } + ids.push(`${i}-${lastHash}`); + } + return ids; +} + @Component({ selector: 'app-stratum-list', templateUrl: './stratum-list.component.html', @@ -81,16 +95,15 @@ export class StratumList implements OnInit, OnDestroy { } processJobs(rawJobs: Record): PoolRow[] { + const numBranches = Math.max(...Object.values(rawJobs).map(job => job.merkleBranches.length)); const jobs: Record = {}; for (const [id, job] of Object.entries(rawJobs)) { - jobs[id] = { ...job, tag: parseTag(job.scriptsig) }; + jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches) }; } if (Object.keys(jobs).length === 0) { return []; } - const numBranches = Math.max(...Object.values(jobs).map(job => job.merkleBranches.length)); - let trees: MerkleTree[] = Object.keys(jobs).map(job => ({ job, size: 1, @@ -100,12 +113,13 @@ export class StratumList implements OnInit, OnDestroy { for (let col = numBranches - 1; col >= 0; col--) { const groups: Record = {}; for (const tree of trees) { - const hash = jobs[tree.job].merkleBranches[col]; - if (!groups[hash]) { - groups[hash] = []; + const branchId = jobs[tree.job].merkleBranchIds[col]; + if (!groups[branchId]) { + groups[branchId] = []; } - groups[hash].push(tree); + groups[branchId].push(tree); } + trees = Object.values(groups).map(group => ({ hash: jobs[group[0].job].merkleBranches[col], job: group[0].job, From 36b691e25b4bfebac53e1b389228f6fb56f696b2 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 20 Jan 2025 17:20:23 +0900 Subject: [PATCH 50/60] ops: Enable stratum in prod config via localhost nginx proxy --- production/mempool-config.mainnet.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 39d82d8d1..5c164340e 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -154,5 +154,9 @@ "WALLETS": { "ENABLED": true, "WALLETS": ["BITB", "3350"] + }, + "STRATUM": { + "ENABLED": true, + "API": "http://127.0.0.1:81" } } From e53e810a5563c3a70be8ec33c880a8e68220a14d Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 20 Jan 2025 17:34:19 +0900 Subject: [PATCH 51/60] ops: Fix stratum server URL path in prod config --- production/mempool-config.mainnet.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 5c164340e..758887407 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -157,6 +157,6 @@ }, "STRATUM": { "ENABLED": true, - "API": "http://127.0.0.1:81" + "API": "http://127.0.0.1:81/api/v1/stratum/ws" } } From 5aeaa6825989139f18703ed027673f967591e8f5 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 20 Jan 2025 17:56:38 +0900 Subject: [PATCH 52/60] ops: Enable stratum in FOSS prod frontend config --- production/mempool-frontend-config.mainnet.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/production/mempool-frontend-config.mainnet.json b/production/mempool-frontend-config.mainnet.json index 79acaecc5..e0cdbf030 100644 --- a/production/mempool-frontend-config.mainnet.json +++ b/production/mempool-frontend-config.mainnet.json @@ -4,8 +4,7 @@ "TESTNET4_ENABLED": true, "LIQUID_ENABLED": false, "LIQUID_TESTNET_ENABLED": false, - "BISQ_ENABLED": true, - "BISQ_SEPARATE_BACKEND": true, + "STRATUM_ENABLED": true, "SIGNET_ENABLED": true, "MEMPOOL_WEBSITE_URL": "https://mempool.space", "LIQUID_WEBSITE_URL": "https://liquid.network", From b454fa09d2d8288e2d96ea4f88aae683b71197ac Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 21 Jan 2025 13:49:12 +0700 Subject: [PATCH 53/60] Remove babel backend dep --- backend/jest.config.ts | 2 +- backend/package-lock.json | 3 --- backend/package.json | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/backend/jest.config.ts b/backend/jest.config.ts index 14f932f98..43246c518 100644 --- a/backend/jest.config.ts +++ b/backend/jest.config.ts @@ -7,7 +7,7 @@ const config: Config.InitialOptions = { automock: false, collectCoverage: true, collectCoverageFrom: ["./src/**/**.ts"], - coverageProvider: "babel", + coverageProvider: "v8", coverageThreshold: { global: { lines: 1 diff --git a/backend/package-lock.json b/backend/package-lock.json index e0d28bfc9..08ebf0619 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,7 +10,6 @@ "hasInstallScript": true, "license": "GNU Affero General Public License v3.0", "dependencies": { - "@babel/core": "^7.25.2", "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", "axios": "1.7.2", @@ -26,8 +25,6 @@ "ws": "~8.18.0" }, "devDependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/core": "^7.25.2", "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", diff --git a/backend/package.json b/backend/package.json index 9ac3f9199..b1145cb13 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,6 @@ "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\"" }, "dependencies": { - "@babel/core": "^7.25.2", "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", "axios": "1.7.2", @@ -55,8 +54,6 @@ "ws": "~8.18.0" }, "devDependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/core": "^7.25.2", "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", From b42431f14a7bd6730ce89bd03933e50d5dc08852 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Tue, 21 Jan 2025 17:39:50 +0900 Subject: [PATCH 54/60] [auth] add login/signup with github support --- .../components/faucet/faucet.component.html | 10 +++----- .../github-login.component.html | 6 +++++ .../github-login.component.ts | 25 +++++++++++++++++++ frontend/src/app/shared/shared.module.ts | 3 +++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 frontend/src/app/components/github-login.component/github-login.component.html create mode 100644 frontend/src/app/components/github-login.component/github-login.component.ts diff --git a/frontend/src/app/components/faucet/faucet.component.html b/frontend/src/app/components/faucet/faucet.component.html index 3165ae9a7..19d76d9dd 100644 --- a/frontend/src/app/components/faucet/faucet.component.html +++ b/frontend/src/app/components/faucet/faucet.component.html @@ -21,10 +21,8 @@
To use the faucet, please  - login -  or
- +
} @else if (user && user.status === 'pending' && !user.email && user.snsId) { @@ -36,18 +34,18 @@ } @else if (error === 'not_available') { - +
To use the faucet, please
- +
} @else if (error === 'account_limited') {
- Your Twitter account does not allow you to access the faucet + Your account does not allow you to access the faucet
} diff --git a/frontend/src/app/components/github-login.component/github-login.component.html b/frontend/src/app/components/github-login.component/github-login.component.html new file mode 100644 index 000000000..de9c743b5 --- /dev/null +++ b/frontend/src/app/components/github-login.component/github-login.component.html @@ -0,0 +1,6 @@ + + + + + {{ buttonString }} + \ No newline at end of file diff --git a/frontend/src/app/components/github-login.component/github-login.component.ts b/frontend/src/app/components/github-login.component/github-login.component.ts new file mode 100644 index 000000000..52f2584b9 --- /dev/null +++ b/frontend/src/app/components/github-login.component/github-login.component.ts @@ -0,0 +1,25 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +@Component({ + selector: 'app-github-login', + templateUrl: './github-login.component.html', +}) +export class GithubLogin { + @Input() width: string | null = null; + @Input() customClass: string | null = null; + @Input() buttonString: string= 'unset'; + @Input() redirectTo: string | null = null; + @Output() clicked = new EventEmitter(); + @Input() disabled: boolean = false; + + constructor() {} + + githubLogin() { + this.clicked.emit(true); + if (this.redirectTo) { + location.replace(`/api/v1/services/auth/login/github?redirectTo=${encodeURIComponent(this.redirectTo)}`); + } else { + location.replace(`/api/v1/services/auth/login/github?redirectTo=${location.href}`); + } + return false; + } +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 283f9eb54..d63b54632 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -125,6 +125,7 @@ import { TwitterLogin } from '@components/twitter-login/twitter-login.component' import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-invoice.component'; import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/weight-directives/weight-directives'; +import { GithubLogin } from '../components/github-login.component/github-login.component'; @NgModule({ declarations: [ @@ -242,6 +243,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/ TwitterWidgetComponent, FaucetComponent, TwitterLogin, + GithubLogin, BitcoinInvoiceComponent, ], imports: [ @@ -376,6 +378,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/ HttpErrorComponent, TwitterWidgetComponent, TwitterLogin, + GithubLogin, BitcoinInvoiceComponent, BitcoinsatoshisPipe, From 665a12a040b5fcb743b5ffa5e1d54a1dce56b77c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:57:18 +0000 Subject: [PATCH 55/60] Bump mysql2 from 3.11.0 to 3.12.0 in /backend Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.11.0 to 3.12.0. - [Release notes](https://github.com/sidorares/node-mysql2/releases) - [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md) - [Commits](https://github.com/sidorares/node-mysql2/compare/v3.11.0...v3.12.0) --- updated-dependencies: - dependency-name: mysql2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- backend/package-lock.json | 52 ++++++++++++++++++++++----------------- backend/package.json | 2 +- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 08ebf0619..3f66fa25b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -17,7 +17,7 @@ "crypto-js": "~4.2.0", "express": "~4.21.1", "maxmind": "~4.3.11", - "mysql2": "~3.11.0", + "mysql2": "~3.12.0", "redis": "^4.7.0", "rust-gbt": "file:./rust-gbt", "socks-proxy-agent": "~7.0.0", @@ -5997,6 +5997,21 @@ "yallist": "^3.0.2" } }, + "node_modules/lru.min": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", + "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6158,16 +6173,17 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mysql2": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.0.tgz", - "integrity": "sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz", + "integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==", + "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", "long": "^5.2.1", - "lru-cache": "^8.0.0", + "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" @@ -6187,14 +6203,6 @@ "node": ">=0.10.0" } }, - "node_modules/mysql2/node_modules/lru-cache": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", - "engines": { - "node": ">=16.14" - } - }, "node_modules/named-placeholders": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", @@ -12210,6 +12218,11 @@ "yallist": "^3.0.2" } }, + "lru.min": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", + "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -12324,16 +12337,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql2": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.0.tgz", - "integrity": "sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz", + "integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==", "requires": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", "long": "^5.2.1", - "lru-cache": "^8.0.0", + "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" @@ -12346,11 +12359,6 @@ "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } - }, - "lru-cache": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" } } }, diff --git a/backend/package.json b/backend/package.json index b1145cb13..ee5944f93 100644 --- a/backend/package.json +++ b/backend/package.json @@ -46,7 +46,7 @@ "crypto-js": "~4.2.0", "express": "~4.21.1", "maxmind": "~4.3.11", - "mysql2": "~3.11.0", + "mysql2": "~3.12.0", "rust-gbt": "file:./rust-gbt", "redis": "^4.7.0", "socks-proxy-agent": "~7.0.0", From 23713a11c25504ca216a39ea3000cf961c165dac Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 22 Jan 2025 05:06:02 +0000 Subject: [PATCH 56/60] link to merkle branch first tx --- frontend/src/app/components/pool/pool.component.html | 6 +++++- frontend/src/app/components/pool/pool.component.scss | 1 + frontend/src/app/components/pool/pool.component.ts | 4 ++++ .../stratum/stratum-list/stratum-list.component.html | 8 +++++++- .../stratum/stratum-list/stratum-list.component.scss | 10 ++++++++++ .../stratum/stratum-list/stratum-list.component.ts | 4 ++++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index faa0003c4..f98794b68 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -267,7 +267,11 @@
@for (branch of job.merkleBranches; track $index) { - + @if ($index === 0 && branch) { + + } @else { + + } } @for (_ of [].constructor(Math.max(0, 12 - job.merkleBranches.length)); track $index) { diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 31d12474f..fa94227bd 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -220,6 +220,7 @@ div.scrollable { .merkle { width: 100px; + text-align: center; } .empty-branch { diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 23b795613..4b4b643a2 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -361,6 +361,10 @@ export class PoolComponent implements OnInit { return block.height; } + reverseHash(hash: string) { + return hash.match(/../g).reverse().join(''); + } + ngOnDestroy(): void { this.slugSubscription.unsubscribe(); } diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html index 08d7fb0ef..41707e37f 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html @@ -30,7 +30,13 @@ @for (cell of row.merkleCells; track $index) { }
-
+ @if ($index === 0 && cell.hash) { + +
+
+ } @else { +
+ }
diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss index 6679f2257..15ee074c2 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss @@ -92,6 +92,16 @@ td { } } } + + .cell-link { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + color: inherit; + text-decoration: none; + } } .badge { diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts index 6f252babe..b28f4ff11 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts @@ -196,6 +196,10 @@ export class StratumList implements OnInit, OnDestroy { }[type]; } + reverseHash(hash: string) { + return hash.match(/../g).reverse().join(''); + } + ngOnDestroy(): void { this.websocketService.stopTrackStratum(); } From cac62765a18d25dddd1be3e090b4c57fdf2115ad Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 22 Jan 2025 09:10:22 +0000 Subject: [PATCH 57/60] fix stratum tree branch level --- .../stratum/stratum-list/stratum-list.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts index b28f4ff11..481447b07 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.ts @@ -48,14 +48,16 @@ function parseTag(scriptSig: string): string { return (ascii.match(/\/.*\//)?.[0] || ascii).trim(); } -function getMerkleBranchIds(merkleBranches: string[], numBranches: number): string[] { +function getMerkleBranchIds(merkleBranches: string[], numBranches: number, poolId: number): string[] { let lastHash = ''; const ids: string[] = []; for (let i = 0; i < numBranches; i++) { if (merkleBranches[i]) { lastHash = merkleBranches[i]; + ids.push(`${i}-${lastHash}`); + } else { + ids.push(`${i}-${lastHash}-${poolId}`); } - ids.push(`${i}-${lastHash}`); } return ids; } @@ -98,7 +100,7 @@ export class StratumList implements OnInit, OnDestroy { const numBranches = Math.max(...Object.values(rawJobs).map(job => job.merkleBranches.length)); const jobs: Record = {}; for (const [id, job] of Object.entries(rawJobs)) { - jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches) }; + jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches, job.pool) }; } if (Object.keys(jobs).length === 0) { return []; From 2e44ea3f012bf599f468f06849a8d5c2513e4152 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 22 Jan 2025 09:13:44 +0000 Subject: [PATCH 58/60] reorder stratum job table --- .../stratum-list/stratum-list.component.html | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html index 41707e37f..24801cf2c 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html @@ -7,27 +7,18 @@ - - - + + + @for (row of rows; track row.job.pool) { - - - @for (cell of row.merkleCells; track $index) { + + + } From 363fa3d8779e07cc9d048234b31cf9d51f60b639 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 22 Jan 2025 09:16:25 +0000 Subject: [PATCH 59/60] improve stratum table layout on mobile --- .../stratum-list/stratum-list.component.scss | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss index 15ee074c2..3d274ef2a 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.scss @@ -104,6 +104,26 @@ td { } } +@media (max-width: 800px) { + .stratum-table { + td { + &.tag { + display: none; + } + } + } +} + +@media (max-width: 650px) { + .stratum-table { + td { + &.reward { + display: none; + } + } + } +} + .badge { position: relative; color: #FFF; From 3b91a1437aa72d7e0b8c8866fd13c7f0fe839eb4 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Wed, 22 Jan 2025 18:58:17 +0900 Subject: [PATCH 60/60] [accelerator] differentiate failed/canceled accelerations --- .../accelerations-list/accelerations-list.component.html | 3 ++- frontend/src/app/interfaces/node-api.interface.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html index 225bf1955..6756b23e4 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html @@ -64,7 +64,8 @@ Pending Completed ⌛ Mined ⌛ - Canceled ⌛ + Canceled ⌛ + Failed ⌛
HeightRewardCoinbase Tag Merkle Branches PoolCoinbase TagRewardHeight
- {{ row.job.height }} - - - - {{ row.job.tag }} - @if ($index === 0 && cell.hash) { @@ -47,6 +38,15 @@ } + {{ row.job.tag }} + + + + {{ row.job.height }} +
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 4d85a938d..05f0855a9 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -412,13 +412,13 @@ export interface Acceleration { feeDelta: number; blockHash: string; blockHeight: number; - acceleratedFeeRate?: number; boost?: number; bidBoost?: number; boostCost?: number; boostRate?: number; minedByPoolUniqueId?: number; + canceled?: number; } export interface AccelerationHistoryParams {