Compare commits

..

5 Commits

Author SHA1 Message Date
wiz
541555051b Merge branch 'master' into knorrium/update-node-matrix 2025-01-20 19:26:56 +09:00
Felipe Knorr Kuhn
5f5e96984a Merge branch 'master' into knorrium/update-node-matrix 2025-01-19 23:41:46 -08:00
Felipe Knorr Kuhn
f5a54ae62b Update the local nvmrc to node 22 2025-01-15 21:52:54 -08:00
Felipe Knorr Kuhn
4da145ff5c Update the node version to run e2e tests 2025-01-15 21:52:38 -08:00
Felipe Knorr Kuhn
a898701830 Update node matrix to v20 and v22 2025-01-15 21:33:25 -08:00
26 changed files with 86 additions and 459 deletions

View File

@@ -9,7 +9,7 @@ jobs:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy:
matrix:
node: ["20", "21"]
node: ["20", "22"]
flavor: ["dev", "prod"]
fail-fast: false
runs-on: "ubuntu-latest"
@@ -160,7 +160,7 @@ jobs:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy:
matrix:
node: ["20", "21"]
node: ["20", "22"]
flavor: ["dev", "prod"]
fail-fast: false
runs-on: "ubuntu-latest"
@@ -263,7 +263,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 20
node-version: 22
cache: "npm"
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json

2
.nvmrc
View File

@@ -1 +1 @@
v20.8.0
v22.13.0

View File

@@ -7,7 +7,7 @@ const config: Config.InitialOptions = {
automock: false,
collectCoverage: true,
collectCoverageFrom: ["./src/**/**.ts"],
coverageProvider: "v8",
coverageProvider: "babel",
coverageThreshold: {
global: {
lines: 1

View File

@@ -10,6 +10,7 @@
"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",
@@ -17,7 +18,7 @@
"crypto-js": "~4.2.0",
"express": "~4.21.1",
"maxmind": "~4.3.11",
"mysql2": "~3.12.0",
"mysql2": "~3.11.0",
"redis": "^4.7.0",
"rust-gbt": "file:./rust-gbt",
"socks-proxy-agent": "~7.0.0",
@@ -25,6 +26,8 @@
"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",
@@ -5997,21 +6000,6 @@
"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",
@@ -6173,17 +6161,16 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mysql2": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz",
"integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==",
"license": "MIT",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.0.tgz",
"integrity": "sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==",
"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.min": "^1.0.0",
"lru-cache": "^8.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
@@ -6203,6 +6190,14 @@
"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",
@@ -12218,11 +12213,6 @@
"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",
@@ -12337,16 +12327,16 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"mysql2": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz",
"integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.0.tgz",
"integrity": "sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==",
"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.min": "^1.0.0",
"lru-cache": "^8.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
@@ -12359,6 +12349,11 @@
"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=="
}
}
},

View File

@@ -39,6 +39,7 @@
"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",
@@ -46,7 +47,7 @@
"crypto-js": "~4.2.0",
"express": "~4.21.1",
"maxmind": "~4.3.11",
"mysql2": "~3.12.0",
"mysql2": "~3.11.0",
"rust-gbt": "file:./rust-gbt",
"redis": "^4.7.0",
"socks-proxy-agent": "~7.0.0",
@@ -54,6 +55,8 @@
"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",

View File

@@ -45,7 +45,6 @@ __SERVICES_API__=${SERVICES_API:=https://mempool.space/api/v1/services}
__PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false}
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
__ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
__STRATUM_ENABLED__=${STRATUM_ENABLED:=false}
# Export as environment variables to be used by envsubst
export __MAINNET_ENABLED__
@@ -77,7 +76,6 @@ export __SERVICES_API__
export __PUBLIC_ACCELERATIONS__
export __HISTORICAL_PRICE__
export __ADDITIONAL_CURRENCIES__
export __STRATUM_ENABLED__
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
echo ${folder}

View File

@@ -33,7 +33,7 @@
"browserify": "^17.0.0",
"clipboard": "^2.0.11",
"domino": "^2.1.6",
"echarts": "~5.6.0",
"echarts": "~5.5.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.6.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz",
"integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
"zrender": "5.5.0"
}
},
"node_modules/echarts/node_modules/tslib": {
@@ -18366,9 +18366,9 @@
}
},
"node_modules/zrender": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz",
"integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
"dependencies": {
"tslib": "2.3.0"
}
@@ -24485,12 +24485,12 @@
}
},
"echarts": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz",
"integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
"requires": {
"tslib": "2.3.0",
"zrender": "5.6.1"
"zrender": "5.5.0"
},
"dependencies": {
"tslib": {
@@ -31485,9 +31485,9 @@
}
},
"zrender": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz",
"integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
"requires": {
"tslib": "2.3.0"
},

View File

@@ -86,7 +86,7 @@
"browserify": "^17.0.0",
"clipboard": "^2.0.11",
"domino": "^2.1.6",
"echarts": "~5.6.0",
"echarts": "~5.5.0",
"ngx-echarts": "~17.2.0",
"ngx-infinite-scroll": "^17.0.0",
"qrcode": "1.5.1",

View File

@@ -397,13 +397,13 @@
</div>
}
</div>
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) {
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) {
<div class="col-sm text-center flex-grow-0 d-flex flex-column justify-content-center align-items-center">
<p class="text-nowrap">&mdash;<span i18n="or">OR</span>&mdash;</p>
</div>
}
}
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) {
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container>&nbsp;<app-fiat [value]="cost"></app-fiat> with</p>
@if (canPayWithCashapp) {
@@ -421,13 +421,6 @@
<img src="/resources/google-pay.png" height=37>
</div>
}
@if (canPayWithCardOnFile) {
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { <span class="mt-1 mb-1"></span> }
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px" (click)="moveToStep('cardonfile')">
<fa-icon style="font-size: 24px; color: white" [icon]="['fas', 'credit-card']"></fa-icon>
<span class="ml-2" style="font-size: 22px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.brand }} {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span>
</div>
}
</div>
}
</div>
@@ -450,7 +443,7 @@
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('summary')" i18n="go-back">Go back</button>
</div>
</div>
} @else if (step === 'cashapp' || step === 'applepay' || step === 'googlepay' || step === 'cardonfile') {
} @else if (step === 'cashapp' || step === 'applepay' || step === 'googlepay') {
<!-- Show checkout page -->
<div class="row mb-md-1 text-center" id="confirm-title">
<div class="col-sm" id="confirm-payment-title">
@@ -466,7 +459,7 @@
</div>
</div>
@if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay || step === 'googlepay' && !loadingGooglePay || step === 'cardonfile' && !loadingCardOnFile) {
@if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay || step === 'googlepay' && !loadingGooglePay) {
<div class="row text-center mt-1">
<div class="col-sm">
<div class="form-group w-100">
@@ -491,13 +484,8 @@
<div id="cash-app-pay" class="d-inline-block" style="height: 50px" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
} @else if (step === 'googlepay') {
<div id="google-pay-button" class="d-inline-block" style="height: 50px" [style]="loadingGooglePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
} @else if (step === 'cardonfile') {
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center ml-auto mr-auto" style="width: 200px; height: 55px" (click)="requestCardOnFilePayment()" [style]="loadingCardOnFile ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''">
<fa-icon style="font-size: 24px; color: white" [icon]="['fas', 'credit-card']"></fa-icon>
<span class="ml-2" style="font-size: 22px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.brand }} {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span>
</div>
}
@if (loadingCashapp || loadingApplePay || loadingGooglePay || loadingCardOnFile) {
@if (loadingCashapp || loadingApplePay || loadingGooglePay) {
<div display="d-flex flex-row justify-content-center">
<span i18n="accelerator.loading-payment-method">Loading payment method...</span>
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>

View File

@@ -13,7 +13,7 @@ import { EnterpriseService } from '@app/services/enterprise.service';
import { ApiService } from '@app/services/api.service';
import { isDevMode } from '@angular/core';
export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay' | 'cardOnFile';
export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay';
export type AccelerationEstimate = {
hasAccess: boolean;
@@ -26,7 +26,7 @@ export type AccelerationEstimate = {
mempoolBaseFee: number;
vsizeFee: number;
pools: number[];
availablePaymentMethods: Record<PaymentMethod, {min: number, max: number, card?: {card_id: string, last_4: string, brand: string, name: string, billing: any}}>;
availablePaymentMethods: Record<PaymentMethod, {min: number, max: number}>;
unavailable?: boolean;
options: { // recommended bid options
fee: number; // recommended userBid in sats
@@ -49,7 +49,7 @@ export const MIN_BID_RATIO = 1;
export const DEFAULT_BID_RATIO = 2;
export const MAX_BID_RATIO = 4;
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'cardonfile' | 'processing' | 'paid' | 'success';
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'processing' | 'paid' | 'success';
@Component({
selector: 'app-accelerate-checkout',
@@ -65,7 +65,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() cashappEnabled: boolean = true;
@Input() applePayEnabled: boolean = false;
@Input() googlePayEnabled: boolean = true;
@Input() cardOnFileEnabled: boolean = true;
@Input() advancedEnabled: boolean = false;
@Input() forceMobile: boolean = false;
@Input() showDetails: boolean = false;
@@ -118,7 +117,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
loadingCashapp = false;
loadingApplePay = false;
loadingGooglePay = false;
loadingCardOnFile = false;
payments: any;
cashAppPay: any;
applePay: any;
@@ -236,10 +234,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.loadingGooglePay = true;
this.setupSquare();
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
} else if (this._step === 'cardonfile' && this.cardOnFileEnabled) {
this.loadingCardOnFile = true;
this.setupSquare();
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
} else if (this._step === 'paid') {
this.timePaid = Date.now();
this.timeoutTimer = setTimeout(() => {
@@ -460,8 +454,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
await this.requestApplePayPayment();
} else if (this._step === 'googlepay') {
await this.requestGooglePayPayment();
} else if (this._step === 'cardonfile') {
this.loadingCardOnFile = false;
}
},
error: () => {
@@ -718,109 +710,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
);
}
/**
* Card On File
*/
async requestCardOnFilePayment(): Promise<void> {
if (this.processing) {
return;
}
if (this.conversionsSubscription) {
this.conversionsSubscription.unsubscribe();
}
this.processing = true;
this.conversionsSubscription = this.stateService.conversions$.subscribe(
async (conversions) => {
this.conversions = conversions;
const costUSD = this.cost / 100_000_000 * conversions.USD;
if (this.isCheckoutLocked > 0) {
return;
}
const cardOnFile = this.estimate?.availablePaymentMethods?.cardOnFile;
if (!cardOnFile?.card) {
this.accelerateError = 'card_on_file_not_found';
return;
}
this.loadingCardOnFile = false;
try {
this.isCheckoutLocked += 2;
this.isTokenizing += 2;
const nameParts = cardOnFile.card.name.split(' ');
const assumedGivenName = nameParts[0];
const assumedFamilyName = nameParts.length > 1 ? nameParts[1] : undefined;
const verificationDetails = {
card: {
billing: {
givenName: assumedGivenName,
familyName: assumedFamilyName,
addressLines: [cardOnFile.card.billing.addressLine1 ?? ''],
city: cardOnFile.card.billing.locality ?? '',
state: cardOnFile.card.billing.administrativeDistrictLevel1 ?? '',
countyCode: cardOnFile.card.billing.country,
}
}
};
const verificationToken = await this.$verifyBuyer(this.payments, cardOnFile.card.card_id, verificationDetails, costUSD.toFixed(2));
if (!verificationToken || !verificationToken.token) {
console.error(`SCA verification failed`);
this.accelerateError = 'SCA Verification Failed. Payment Declined.';
this.processing = false;
return;
}
this.servicesApiService.accelerateWithCardOnFile$(
this.tx.txid,
cardOnFile.card.card_id,
verificationToken.token,
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
costUSD,
verificationToken.userChallenged
).subscribe({
next: () => {
this.processing = false;
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
this.audioService.playSound('ascend-chime-cartoon');
setTimeout(() => {
this.isCheckoutLocked--;
this.isTokenizing--;
this.moveToStep('paid', true);
}, 1000);
},
error: (response) => {
this.processing = false;
this.accelerateError = response.error;
this.isCheckoutLocked--;
this.isTokenizing--;
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);
}
}
});
} catch (e) {
console.log(e);
this.isCheckoutLocked--;
this.isTokenizing--;
this.processing = false;
this.accelerateError = e.message;
} finally {
// always unlock the checkout once we're finished
this.isCheckoutLocked--;
this.isTokenizing--;
}
}
);
}
/**
* CASHAPP
*/
@@ -1066,22 +955,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return false;
}
get canPayWithCardOnFile(): boolean {
if (!this.cardOnFileEnabled || !this.conversions || (!this.isProdDomain && !isDevMode())) {
return false;
}
const paymentMethod = this.estimate?.availablePaymentMethods?.cardOnFile;
if (paymentMethod) {
const costUSD = (this.cost / 100_000_000 * this.conversions.USD);
if (costUSD >= paymentMethod.min && costUSD <= paymentMethod.max) {
return true;
}
}
return false;
}
get canPayWithBalance(): boolean {
if (!this.hasAccessToBalanceMode) {
return false;

View File

@@ -14,7 +14,7 @@
<th class="time text-right" i18n="accelerator.requested">Requested</th>
</ng-container>
<ng-container *ngIf="!pending">
<th class="fee text-right text-truncate" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
<th class="block text-right" i18n="shared.block-title">Block</th>
<th class="pool text-right" i18n="mining.pool-name" *ngIf="!this.widget">Pool</th>
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
@@ -64,8 +64,7 @@
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
<span *ngIf="acceleration.status.includes('completed') && acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]" class="badge badge-success"><ng-container i18n="accelerator.completed">Completed</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;</span></span>
<span *ngIf="acceleration.status.includes('completed') && (!acceleration.minedByPoolUniqueId || !pools[acceleration.minedByPoolUniqueId])" class="badge badge-success"><ng-container i18n="transaction.rbf.mined">Mined</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;</span></span>
<span *ngIf="acceleration.status.includes('failed') && acceleration.canceled" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Canceled</ng-container><span *ngIf="acceleration.status === 'failed_provisional'">&nbsp;</span></span>
<span *ngIf="acceleration.status.includes('failed') && !acceleration.canceled" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Failed</ng-container><span *ngIf="acceleration.status === 'failed_provisional'">&nbsp;</span></span>
<span *ngIf="acceleration.status.includes('failed')" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Canceled</ng-container><span *ngIf="acceleration.status === 'failed_provisional'">&nbsp;</span></span>
</td>
<td class="date text-right" *ngIf="!this.widget">
<app-time kind="since" [time]="acceleration.added" [fastRender]="true" [showTooltip]="true"></app-time>

View File

@@ -21,8 +21,10 @@
<div class="alert alert-mempool d-block text-center w-100">
<div class="d-inline align-middle">
<span>To use the faucet, please&nbsp;</span>
<a routerLink="/login" [queryParams]="{'redirectTo': '/testnet4/faucet'}">login</a>
<span class="mr-2">&nbsp;or</span>
</div>
<app-github-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Github"></app-github-login>
<app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login>
</div>
}
@else if (user && user.status === 'pending' && !user.email && user.snsId) {
@@ -34,18 +36,18 @@
</div>
}
@else if (error === 'not_available') {
<!-- User logged in but not a paid user or did not link its Github account -->
<!-- User logged in but not a paid user or did not link its Twitter account -->
<div class="alert alert-mempool d-block text-center w-100">
<div class="d-inline align-middle">
<span class="mb-2 mr-2">To use the faucet, please</span>
</div>
<app-github-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Github"></app-github-login>
<app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login>
</div>
}
@else if (error === 'account_limited') {
<div class="alert alert-mempool d-block text-center w-100">
<div class="d-inline align-middle">
<span class="mb-2 mr-2">Your account does not allow you to access the faucet</span>
<span class="mb-2 mr-2">Your Twitter account does not allow you to access the faucet</span>
</div>
</div>
}

View File

@@ -1,6 +0,0 @@
<a href="#" (click)="githubLogin()" [class]="(disabled ? 'disabled': '') + (customClass ? customClass : 'w-100 btn mt-1 d-flex justify-content-center align-items-center')" style="background-color: rgb(31, 35, 40)" [style]="width ? 'width: ' + width : ''">
<svg height="32" viewBox="0 0 18 16" width="32" style="fill: white;">
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
</svg>
<span class="ml-2 text-light align-middle">{{ buttonString }}</span>
</a>

View File

@@ -1,25 +0,0 @@
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<boolean>();
@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;
}
}

View File

@@ -267,11 +267,7 @@
<tbody>
<tr>
@for (branch of job.merkleBranches; track $index) {
@if ($index === 0 && branch) {
<a [routerLink]="['/tx' | relativeUrl, reverseHash(branch)]"><td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''"><fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 14px; color: white"></fa-icon></td></a>
} @else {
<td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''"></td>
}
<td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''"></td>
}
@for (_ of [].constructor(Math.max(0, 12 - job.merkleBranches.length)); track $index) {
<td class="merkle empty-branch"></td>

View File

@@ -220,7 +220,6 @@ div.scrollable {
.merkle {
width: 100px;
text-align: center;
}
.empty-branch {

View File

@@ -361,10 +361,6 @@ export class PoolComponent implements OnInit {
return block.height;
}
reverseHash(hash: string) {
return hash.match(/../g).reverse().join('');
}
ngOnDestroy(): void {
this.slugSubscription.unsubscribe();
}

View File

@@ -7,27 +7,30 @@
<table *ngIf="poolsReady && (rows$ | async) as rows;" class="stratum-table">
<thead>
<tr>
<td class="height">Height</td>
<td class="reward">Reward</td>
<td class="tag">Coinbase Tag</td>
<td class="merkle" [attr.colspan]="rows[0]?.merkleCells?.length || 4">
Merkle Branches
</td>
<td class="pool">Pool</td>
<td class="tag">Coinbase Tag</td>
<td class="reward">Reward</td>
<td class="height">Height</td>
</tr>
</thead>
<tbody>
@for (row of rows; track row.job.pool) {
<tr>
<td class="height">
{{ row.job.height }}
</td>
<td class="reward">
<app-amount [satoshis]="row.job.reward"></app-amount>
</td>
<td class="tag">
{{ row.job.tag }}
</td>
@for (cell of row.merkleCells; track $index) {
<td class="merkle" [style.background-color]="cell.hash ? '#' + cell.hash.slice(0, 6) : ''">
@if ($index === 0 && cell.hash) {
<a [routerLink]="['/tx' | relativeUrl, reverseHash(cell.hash)]" class="cell-link">
<div class="pipe-segment" [class]="pipeToClass(cell.type)"></div>
</a>
} @else {
<div class="pipe-segment" [class]="pipeToClass(cell.type)"></div>
}
<div class="pipe-segment" [class]="pipeToClass(cell.type)"></div>
</td>
}
<td class="pool">
@@ -38,15 +41,6 @@
</a>
}
</td>
<td class="tag">
{{ row.job.tag }}
</td>
<td class="reward">
<app-amount [satoshis]="row.job.reward"></app-amount>
</td>
<td class="height">
{{ row.job.height }}
</td>
</tr>
}
</tbody>

View File

@@ -92,36 +92,6 @@ td {
}
}
}
.cell-link {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
color: inherit;
text-decoration: none;
}
}
@media (max-width: 800px) {
.stratum-table {
td {
&.tag {
display: none;
}
}
}
}
@media (max-width: 650px) {
.stratum-table {
td {
&.reward {
display: none;
}
}
}
}
.badge {

View File

@@ -48,16 +48,14 @@ function parseTag(scriptSig: string): string {
return (ascii.match(/\/.*\//)?.[0] || ascii).trim();
}
function getMerkleBranchIds(merkleBranches: string[], numBranches: number, poolId: number): string[] {
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}`);
} else {
ids.push(`${i}-${lastHash}-${poolId}`);
}
ids.push(`${i}-${lastHash}`);
}
return ids;
}
@@ -100,7 +98,7 @@ export class StratumList implements OnInit, OnDestroy {
const numBranches = Math.max(...Object.values(rawJobs).map(job => job.merkleBranches.length));
const jobs: Record<string, TaggedStratumJob> = {};
for (const [id, job] of Object.entries(rawJobs)) {
jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches, job.pool) };
jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches) };
}
if (Object.keys(jobs).length === 0) {
return [];
@@ -198,10 +196,6 @@ export class StratumList implements OnInit, OnDestroy {
}[type];
}
reverseHash(hash: string) {
return hash.match(/../g).reverse().join('');
}
ngOnDestroy(): void {
this.websocketService.stopTrackStratum();
}

View File

@@ -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 {

View File

@@ -58,7 +58,6 @@ export class AuthServiceMempool {
setAuth(auth: any) {
if (!auth) {
localStorage.removeItem('auth');
localStorage.removeItem('authenticatorStatus');
} else {
localStorage.setItem('auth', JSON.stringify(auth));
}

View File

@@ -146,10 +146,6 @@ export class ServicesApiServices {
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged });
}
accelerateWithCardOnFile$(txInput: string, token: string, verificationToken: string, referenceId: string, userApprovedUSD: number, userChallenged: boolean) {
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cardOnFile`, { txInput: txInput, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged });
}
getAccelerations$(): Observable<Acceleration[]> {
return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations`);
}

View File

@@ -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, faShareNodes, faCreditCard } 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';
@@ -125,7 +125,6 @@ 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: [
@@ -243,7 +242,6 @@ import { GithubLogin } from '../components/github-login.component/github-login.c
TwitterWidgetComponent,
FaucetComponent,
TwitterLogin,
GithubLogin,
BitcoinInvoiceComponent,
],
imports: [
@@ -378,7 +376,6 @@ import { GithubLogin } from '../components/github-login.component/github-login.c
HttpErrorComponent,
TwitterWidgetComponent,
TwitterLogin,
GithubLogin,
BitcoinInvoiceComponent,
BitcoinsatoshisPipe,
@@ -463,6 +460,5 @@ export class SharedModule {
library.addIcons(faMoneyBillTrendUp);
library.addIcons(faRobot);
library.addIcons(faShareNodes);
library.addIcons(faCreditCard);
}
}

View File

@@ -3,21 +3,11 @@
<head>
<meta charset="utf-8">
<!-- LOAD BEARING COMMENTS - DO NOT TOUCH! -->
<!-- TITLE -->
<title>mempool - Bitcoin Explorer</title>
<!-- END TITLE -->
<!-- CONFIG -->
<script src="/resources/config.js"></script>
<!-- END CONFIG -->
<!-- CUSTOMIZATION -->
<script src="/resources/customize.js"></script>
<!-- END CUSTOMIZATION -->
<base href="/">
<!-- META -->
<meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more." />
<meta property="og:image" content="https://mempool.space/resources/previews/mempool-space-preview.jpg" />
<meta property="og:image:type" content="image/jpeg" />
@@ -31,21 +21,19 @@
<meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more." />
<meta name="twitter:image" content="https://mempool.space/resources/previews/mempool-space-preview.jpg" />
<meta name="twitter:domain" content="mempool.space">
<!-- END META -->
<!-- FAVICONS -->
<link rel="apple-touch-icon" sizes="180x180" href="/resources/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/resources/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/resources/favicons/favicon-16x16.png">
<link rel="manifest" href="/resources/favicons/site.webmanifest">
<link rel="shortcut icon" href="/resources/favicons/favicon.ico">
<link id="canonical" rel="canonical" href="https://mempool.space">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-config" content="/resources/favicons/browserconfig.xml">
<meta name="theme-color" content="#1d1f31">
<!-- END FAVICONS -->
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>

View File

@@ -1,128 +0,0 @@
import fs from 'fs';
const defaultConfig = {
"domains": ["mempool.space"],
"theme": "default",
"enterprise": "mempool",
"branding": {
"name": "mempool",
"title": "mempool",
"site_id": 5,
},
"meta": {
"title": "mempool - Bitcoin Explorer",
"description": "Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more.",
},
"unfurls": {
"preview": {
"src": "https://mempool.space/resources/previews/mempool-space-preview.jpg",
"type": "image/jpeg",
"width": "2000",
"height": "1000"
}
}
}
function deepMerge(target, source) {
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
target[key] = target[key] || {};
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
function addDefaultsToConfig(config) {
return deepMerge(structuredClone(defaultConfig), config);
}
function substitute(indexhtml, config) {
let newhtml = indexhtml;
// substitute title
newhtml = newhtml.replace(/\<\!\-\- TITLE \-\-\>.*\<\!\-\- END TITLE \-\-\>/gis, `<title>${config.meta.title}</title>`);
// substitute customization script
newhtml = newhtml.replace(/\<\!\-\- CUSTOMIZATION \-\-\>.*\<\!\-\- END CUSTOMIZATION \-\-\>/gis, `<script>
window.__env = window.__env || {};
window.__env.CUSTOMIZATION = 'auto';
window.__env.customize = ${JSON.stringify(config)};
</script>`);
// substitute meta tags
newhtml = newhtml.replace(/\<\!\-\- META \-\-\>.*\<\!\-\- END META \-\-\>/gis, `<meta name="description" content="${config.meta.description}" />
<meta property="og:image" content="${config.unfurls.preview.src}" />
<meta property="og:image:type" content="${config.unfurls.preview.type}" />
<meta property="og:image:width" content="${config.unfurls.preview.width}" />
<meta property="og:image:height" content="${config.unfurls.preview.height}" />
<meta property="og:description" content="${config.meta.description}" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@mempool">
<meta name="twitter:creator" content="@mempool">
<meta name="twitter:title" content="${config.meta.title}">
<meta name="twitter:description" content="${config.meta.description}" />
<meta name="twitter:image" content="${config.unfurls.preview.src}" />
<meta name="twitter:domain" content="${config.domains[0]}">`);
// substitute favicons
newhtml = newhtml.replace(/\<\!\-\- FAVICONS -->.*\<\!\-\- END FAVICONS \-\-\>/gis, `<link rel="apple-touch-icon" sizes="180x180" href="/resources/${config.enterprise}/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/resources/${config.enterprise}/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/resources/${config.enterprise}/favicons/favicon-16x16.png">
<link rel="manifest" href="/resources/${config.enterprise}/favicons/site.webmanifest">
<link rel="shortcut icon" href="/resources/${config.enterprise}/favicons/favicon.ico">
<link id="canonical" rel="canonical" href="${config.domains[0]}">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-config" content="/resources/${config.enterprise}/favicons/browserconfig.xml">
<meta name="theme-color" content="#1d1f31">`);
return newhtml;
}
async function run() {
const servicesHost = process.argv[2] || 'http://localhost:9000';
const mempoolDir = process.argv[3] || '../';
console.log('fetching list of custom builds');
const customBuilds = await (await fetch(`${servicesHost}/api/v1/services/custom/list`)).json();
// fetch config for each custom build from `$SERVICES/api/v1/services/custom/config/<custom_build_id>`
const customConfigs = await Promise.all(customBuilds.map(async (build) => {
console.log(`fetching config for ${build} `);
return addDefaultsToConfig(await (await fetch(`${servicesHost}/api/v1/services/custom/config/${build}`)).json());
}));
// for each custom build config:
let i = 0;
for (const config of customConfigs) {
console.log(`generating ${config.enterprise} build (${i + 1}/${customConfigs.length})`);
const browserDir = `${mempoolDir}/frontend/dist/mempool/browser`;
const locales = fs.readdirSync(browserDir)
.filter(file => fs.statSync(`${browserDir}/${file}`).isDirectory())
.filter(file => fs.existsSync(`${browserDir}/${file}/index.html`));
// Process each locale's index.html
for (const locale of locales) {
const indexPath = `${browserDir}/${locale}/index.html`;
const indexContent = fs.readFileSync(indexPath, 'utf-8').toString();
const processedHtml = substitute(indexContent, config);
// Save processed HTML
const outputPath = `${browserDir}/${locale}/index.${config.enterprise}.html`;
fs.writeFileSync(outputPath, processedHtml);
console.log(`updated index.${config.enterprise}.html for locale ${locale}`);
}
console.log(`finished generating ${config.enterprise} build`);
i++;
}
console.log('finished updating custom builds');
}
run();