mirror of
https://github.com/mempool/mempool.git
synced 2025-03-12 10:31:03 +01:00
Merge branch 'master' into natsoni/block-first-seen-audit
This commit is contained in:
commit
198d79f149
27 changed files with 445 additions and 88 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
import { IBitcoinApi, SubmitPackageResult, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
|
|
||||||
export interface AbstractBitcoinApi {
|
export interface AbstractBitcoinApi {
|
||||||
|
@ -23,6 +23,7 @@ export interface AbstractBitcoinApi {
|
||||||
$getScriptHashTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
|
$getScriptHashTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
|
||||||
$sendRawTransaction(rawTransaction: string): Promise<string>;
|
$sendRawTransaction(rawTransaction: string): Promise<string>;
|
||||||
$testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]>;
|
$testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]>;
|
||||||
|
$submitPackage(rawTransactions: string[], maxfeerate?: number, maxburnamount?: number): Promise<SubmitPackageResult>;
|
||||||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend>;
|
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend>;
|
||||||
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]>;
|
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]>;
|
||||||
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
||||||
|
|
|
@ -218,3 +218,21 @@ export interface TestMempoolAcceptResult {
|
||||||
},
|
},
|
||||||
['reject-reason']?: string,
|
['reject-reason']?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SubmitPackageResult {
|
||||||
|
package_msg: string;
|
||||||
|
"tx-results": { [wtxid: string]: TxResult };
|
||||||
|
"replaced-transactions"?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TxResult {
|
||||||
|
txid: string;
|
||||||
|
"other-wtxid"?: string;
|
||||||
|
vsize?: number;
|
||||||
|
fees?: {
|
||||||
|
base: number;
|
||||||
|
"effective-feerate"?: number;
|
||||||
|
"effective-includes"?: string[];
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||||
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
import { IBitcoinApi, SubmitPackageResult, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import blocks from '../blocks';
|
import blocks from '../blocks';
|
||||||
import mempool from '../mempool';
|
import mempool from '../mempool';
|
||||||
|
@ -196,6 +196,10 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$submitPackage(rawTransactions: string[], maxfeerate?: number, maxburnamount?: number): Promise<SubmitPackageResult> {
|
||||||
|
return this.bitcoindClient.submitPackage(rawTransactions, maxfeerate ?? undefined, maxburnamount ?? undefined);
|
||||||
|
}
|
||||||
|
|
||||||
async $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
async $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
||||||
const txOut = await this.bitcoindClient.getTxOut(txId, vout, false);
|
const txOut = await this.bitcoindClient.getTxOut(txId, vout, false);
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -48,6 +48,8 @@ class BitcoinRoutes {
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
||||||
|
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
||||||
|
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage)
|
||||||
;
|
;
|
||||||
|
|
||||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
|
@ -794,6 +796,19 @@ class BitcoinRoutes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $submitPackage(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const rawTxs = Common.getTransactionsFromRequest(req);
|
||||||
|
const maxfeerate = parseFloat(req.query.maxfeerate as string);
|
||||||
|
const maxburnamount = parseFloat(req.query.maxburnamount as string);
|
||||||
|
const result = await bitcoinClient.submitPackage(rawTxs, maxfeerate ?? undefined, maxburnamount ?? undefined);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e: any) {
|
||||||
|
handleError(req, res, 400, e.message && e.code ? 'submitpackage RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||||
|
: (e.message || 'Error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new BitcoinRoutes();
|
export default new BitcoinRoutes();
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-fact
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import { Common } from '../common';
|
import { Common } from '../common';
|
||||||
import { TestMempoolAcceptResult } from './bitcoin-api.interface';
|
import { SubmitPackageResult, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||||
|
|
||||||
interface FailoverHost {
|
interface FailoverHost {
|
||||||
host: string,
|
host: string,
|
||||||
|
@ -332,6 +332,10 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$submitPackage(rawTransactions: string[]): Promise<SubmitPackageResult> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
||||||
return this.failoverRouter.$get<IEsploraApi.Outspend>('/tx/' + txId + '/outspend/' + vout);
|
return this.failoverRouter.$get<IEsploraApi.Outspend>('/tx/' + txId + '/outspend/' + vout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ module.exports = {
|
||||||
signRawTransaction: 'signrawtransaction', // bitcoind v0.7.0+
|
signRawTransaction: 'signrawtransaction', // bitcoind v0.7.0+
|
||||||
stop: 'stop',
|
stop: 'stop',
|
||||||
submitBlock: 'submitblock', // bitcoind v0.7.0+
|
submitBlock: 'submitblock', // bitcoind v0.7.0+
|
||||||
|
submitPackage: 'submitpackage',
|
||||||
validateAddress: 'validateaddress',
|
validateAddress: 'validateaddress',
|
||||||
verifyChain: 'verifychain', // bitcoind v0.9.0+
|
verifyChain: 'verifychain', // bitcoind v0.9.0+
|
||||||
verifyMessage: 'verifymessage',
|
verifyMessage: 'verifymessage',
|
||||||
|
|
|
@ -525,7 +525,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||||
tokenResult.token,
|
tokenResult.token,
|
||||||
cardTag,
|
cardTag,
|
||||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
this.accelerationUUID
|
this.accelerationUUID,
|
||||||
|
costUSD
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
@ -624,7 +625,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||||
tokenResult.token,
|
tokenResult.token,
|
||||||
cardTag,
|
cardTag,
|
||||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
this.accelerationUUID
|
this.accelerationUUID,
|
||||||
|
costUSD
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
@ -714,7 +716,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||||
tokenResult.token,
|
tokenResult.token,
|
||||||
tokenResult.details.cashAppPay.cashtag,
|
tokenResult.details.cashAppPay.cashtag,
|
||||||
tokenResult.details.cashAppPay.referenceId,
|
tokenResult.details.cashAppPay.referenceId,
|
||||||
this.accelerationUUID
|
this.accelerationUUID,
|
||||||
|
costUSD
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="interval">
|
<div class="interval">
|
||||||
<div class="interval-time">
|
<div class="interval-time">
|
||||||
@if (eta) {
|
@if (eta) {
|
||||||
~<app-time [time]="eta?.wait / 1000"></app-time> <!-- <span *ngIf="accelerateRatio > 1" class="compare"> ({{ accelerateRatio }}x faster)</span> -->
|
~<app-time [time]="eta?.wait / 1000"></app-time>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,8 +48,6 @@
|
||||||
<div class="interval-time">
|
<div class="interval-time">
|
||||||
<app-time [time]="acceleratedToMined"></app-time>
|
<app-time [time]="acceleratedToMined"></app-time>
|
||||||
</div>
|
</div>
|
||||||
} @else if (standardETA && !tx.status.confirmed) {
|
|
||||||
<!-- ~<app-time [time]="standardETA / 1000 - now"></app-time> -->
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,19 +11,14 @@ import { MiningService } from '../../services/mining.service';
|
||||||
})
|
})
|
||||||
export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||||
@Input() transactionTime: number;
|
@Input() transactionTime: number;
|
||||||
|
@Input() acceleratedAt: number;
|
||||||
@Input() tx: Transaction;
|
@Input() tx: Transaction;
|
||||||
@Input() accelerationInfo: Acceleration;
|
@Input() accelerationInfo: Acceleration;
|
||||||
@Input() eta: ETA;
|
@Input() eta: ETA;
|
||||||
// A mined transaction has standard ETA and accelerated ETA undefined
|
|
||||||
// A transaction in mempool has either standardETA defined (if accelerated) or acceleratedETA defined (if not accelerated yet)
|
|
||||||
@Input() standardETA: number;
|
|
||||||
@Input() acceleratedETA: number;
|
|
||||||
|
|
||||||
acceleratedAt: number;
|
|
||||||
now: number;
|
now: number;
|
||||||
accelerateRatio: number;
|
accelerateRatio: number;
|
||||||
useAbsoluteTime: boolean = false;
|
useAbsoluteTime: boolean = false;
|
||||||
interval: number;
|
|
||||||
firstSeenToAccelerated: number;
|
firstSeenToAccelerated: number;
|
||||||
acceleratedToMined: number;
|
acceleratedToMined: number;
|
||||||
|
|
||||||
|
@ -36,30 +31,17 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000;
|
this.updateTimes();
|
||||||
|
|
||||||
this.miningService.getPools().subscribe(pools => {
|
this.miningService.getPools().subscribe(pools => {
|
||||||
for (const pool of pools) {
|
for (const pool of pools) {
|
||||||
this.poolsData[pool.unique_id] = pool;
|
this.poolsData[pool.unique_id] = pool;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateTimes();
|
|
||||||
this.interval = window.setInterval(this.updateTimes.bind(this), 60000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes): void {
|
ngOnChanges(changes): void {
|
||||||
// Hide standard ETA while we don't have a proper standard ETA calculation, see https://github.com/mempool/mempool/issues/65
|
this.updateTimes();
|
||||||
|
|
||||||
// if (changes?.eta?.currentValue || changes?.standardETA?.currentValue || changes?.acceleratedETA?.currentValue) {
|
|
||||||
// if (changes?.eta?.currentValue) {
|
|
||||||
// if (changes?.acceleratedETA?.currentValue) {
|
|
||||||
// this.accelerateRatio = Math.floor((Math.floor(changes.eta.currentValue.time / 1000) - this.now) / (Math.floor(changes.acceleratedETA.currentValue / 1000) - this.now));
|
|
||||||
// } else if (changes?.standardETA?.currentValue) {
|
|
||||||
// this.accelerateRatio = Math.floor((Math.floor(changes.standardETA.currentValue / 1000) - this.now) / (Math.floor(changes.eta.currentValue.time / 1000) - this.now));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTimes(): void {
|
updateTimes(): void {
|
||||||
|
@ -68,10 +50,6 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||||
this.firstSeenToAccelerated = Math.max(0, this.acceleratedAt - this.transactionTime);
|
this.firstSeenToAccelerated = Math.max(0, this.acceleratedAt - this.transactionTime);
|
||||||
this.acceleratedToMined = Math.max(0, this.tx.status.block_time - this.acceleratedAt);
|
this.acceleratedToMined = Math.max(0, this.tx.status.block_time - this.acceleratedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
onHover(event, status: string): void {
|
onHover(event, status: string): void {
|
||||||
if (status === 'seen') {
|
if (status === 'seen') {
|
||||||
|
|
|
@ -27,6 +27,14 @@
|
||||||
<app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login>
|
<app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@else if (user && user.status === 'pending' && !user.email && user.snsId) {
|
||||||
|
<div class="alert alert-danger w-100 col d-flex justify-content-center text-left">
|
||||||
|
<span class="d-flex">
|
||||||
|
<fa-icon [icon]="['fas', 'exclamation-triangle']" [fixedWidth]="true" class="mr-1"></fa-icon>
|
||||||
|
<span>Please <a class="text-primary" [routerLink]="['/services/account/settings']">verify your account</a> by providing a valid email address. To mitigate spam, we delete unverified accounts at regular intervals.</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@else if (error === 'not_available') {
|
@else if (error === 'not_available') {
|
||||||
<!-- User logged in but not a paid user or did not link its Twitter 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="alert alert-mempool d-block text-center w-100">
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Component, OnDestroy, OnInit, ChangeDetectorRef } from "@angular/core";
|
import { Component, OnDestroy, OnInit, ChangeDetectorRef } from "@angular/core";
|
||||||
import { FormBuilder, FormGroup, Validators, ValidatorFn, AbstractControl, ValidationErrors } from "@angular/forms";
|
import { FormBuilder, FormGroup, Validators, ValidatorFn, AbstractControl, ValidationErrors } from "@angular/forms";
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { StorageService } from "../../services/storage.service";
|
|
||||||
import { ServicesApiServices } from "../../services/services-api.service";
|
import { ServicesApiServices } from "../../services/services-api.service";
|
||||||
import { getRegex } from "../../shared/regex.utils";
|
import { getRegex } from "../../shared/regex.utils";
|
||||||
import { StateService } from "../../services/state.service";
|
import { StateService } from "../../services/state.service";
|
||||||
|
@ -34,7 +33,6 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private storageService: StorageService,
|
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
@ -56,14 +54,17 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.user = this.storageService.getAuth()?.user ?? null;
|
this.servicesApiService.userSubject$.subscribe(user => {
|
||||||
if (!this.user) {
|
this.user = user;
|
||||||
this.loading = false;
|
if (!user) {
|
||||||
return;
|
this.loading = false;
|
||||||
}
|
this.cd.markForCheck();
|
||||||
|
return;
|
||||||
// Setup form
|
}
|
||||||
this.updateFaucetStatus();
|
// Setup form
|
||||||
|
this.updateFaucetStatus();
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
// Track transaction
|
// Track transaction
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
|
@ -145,9 +146,6 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
||||||
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4')), this.getNotFaucetAddressValidator(faucetAddress)]],
|
'address': ['', [Validators.required, Validators.pattern(getRegex('address', 'testnet4')), this.getNotFaucetAddressValidator(faucetAddress)]],
|
||||||
'satoshis': [min, [Validators.required, Validators.min(min), Validators.max(max)]]
|
'satoshis': [min, [Validators.required, Validators.min(min), Validators.max(max)]]
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
this.cd.markForCheck();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateForm(min, max, faucetAddress: string): void {
|
updateForm(min, max, faucetAddress: string): void {
|
||||||
|
@ -160,6 +158,8 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
||||||
this.faucetForm.get('satoshis').updateValueAndValidity();
|
this.faucetForm.get('satoshis').updateValueAndValidity();
|
||||||
this.faucetForm.get('satoshis').markAsDirty();
|
this.faucetForm.get('satoshis').markAsDirty();
|
||||||
}
|
}
|
||||||
|
this.loading = false;
|
||||||
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
setAmount(value: number): void {
|
setAmount(value: number): void {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
@if (runestone?.etching.premine > 0) {
|
@if (runestone?.etching.premine > 0) {
|
||||||
<ng-container i18n="ord.premine-n-runes">
|
<ng-container i18n="ord.premine-n-runes">
|
||||||
<span>Premine</span>
|
<span>Premine</span>
|
||||||
<span class="amount"> {{ runestone.etching.premine >= 100000 ? (toNumber(runestone.etching.premine) | amountShortener:undefined:undefined:true) : runestone.etching.premine }} </span>
|
<span class="amount"> {{ getAmount(runestone.etching.premine, runestone.etching.divisibility || 0) >= 100000 ? (getAmount(runestone.etching.premine, runestone.etching.divisibility || 0) | amountShortener:undefined:undefined:true) : getAmount(runestone.etching.premine, runestone.etching.divisibility || 0) }} </span>
|
||||||
{{ runestone.etching.symbol }}
|
{{ runestone.etching.symbol }}
|
||||||
<span class="name">{{ runestone.etching.spacedName }}</span>
|
<span class="name">{{ runestone.etching.spacedName }}</span>
|
||||||
<span> ({{ toNumber(runestone.etching.premine) / toNumber(runestone.etching.supply) * 100 | amountShortener:0}}% of total supply)</span>
|
<span> ({{ toNumber(runestone.etching.premine) / toNumber(runestone.etching.supply) * 100 | amountShortener:0}}% of total supply)</span>
|
||||||
|
|
|
@ -9,4 +9,66 @@
|
||||||
<p class="red-color d-inline">{{ error }}</p> <a *ngIf="txId" [routerLink]="['/tx/' | relativeUrl, txId]">{{ txId }}</a>
|
<p class="red-color d-inline">{{ error }}</p> <a *ngIf="txId" [routerLink]="['/tx/' | relativeUrl, txId]">{{ txId }}</a>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@if (network === '' || network === 'testnet' || network === 'testnet4' || network === 'signet') {
|
||||||
|
<br>
|
||||||
|
<h1 class="text-left" style="margin-top: 1rem;" i18n="shared.submit-transactions|Submit Package">Submit Package</h1>
|
||||||
|
|
||||||
|
<form [formGroup]="submitTxsForm" (submit)="submitTxsForm.valid && submitTxs()" novalidate>
|
||||||
|
<div class="mb-3">
|
||||||
|
<textarea formControlName="txs" class="form-control" rows="5" i18n-placeholder="transaction.test-transactions" placeholder="Comma-separated list of raw transactions"></textarea>
|
||||||
|
</div>
|
||||||
|
<label i18n="test.tx.max-fee-rate">Maximum fee rate (sat/vB)</label>
|
||||||
|
<input type="number" class="form-control input-dark" formControlName="maxfeerate" id="maxfeerate"
|
||||||
|
[value]="10000" placeholder="10,000 s/vb" [class]="{invalid: invalidMaxfeerate}">
|
||||||
|
<label i18n="submitpackage.tx.max-burn-amount">Maximum burn amount (sats)</label>
|
||||||
|
<input type="number" class="form-control input-dark" formControlName="maxburnamount" id="maxburnamount"
|
||||||
|
[value]="0" placeholder="0 sat" [class]="{invalid: invalidMaxburnamount}">
|
||||||
|
<br>
|
||||||
|
<button [disabled]="isLoadingPackage" type="submit" class="btn btn-primary mr-2" i18n="shared.submit-transactions|Submit Package">Submit Package</button>
|
||||||
|
<p *ngIf="errorPackage" class="red-color d-inline">{{ errorPackage }}</p>
|
||||||
|
<p *ngIf="packageMessage" class="d-inline">{{ packageMessage }}</p>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="box" *ngIf="results?.length">
|
||||||
|
<table class="accept-results table table-fixed table-borderless table-striped">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th class="allowed" i18n="test-tx.is-allowed">Allowed?</th>
|
||||||
|
<th class="txid" i18n="dashboard.latest-transactions.txid">TXID</th>
|
||||||
|
<th class="rate" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</th>
|
||||||
|
<th class="reason" i18n="test-tx.rejection-reason">Rejection reason</th>
|
||||||
|
</tr>
|
||||||
|
<ng-container *ngFor="let result of results;">
|
||||||
|
<tr>
|
||||||
|
<td class="allowed">
|
||||||
|
@if (result.error == null) {
|
||||||
|
<span>✅</span>
|
||||||
|
}
|
||||||
|
@else {
|
||||||
|
<span>❌</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="txid">
|
||||||
|
@if (!result.error) {
|
||||||
|
<a [routerLink]="['/tx/' | relativeUrl, result.txid]"><app-truncate [text]="result.txid"></app-truncate></a>
|
||||||
|
} @else {
|
||||||
|
<app-truncate [text]="result.txid"></app-truncate>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="rate">
|
||||||
|
<app-fee-rate *ngIf="result.fees?.['effective-feerate'] != null" [fee]="result.fees?.['effective-feerate'] * 100000"></app-fee-rate>
|
||||||
|
<span *ngIf="result.fees?.['effective-feerate'] == null">-</span>
|
||||||
|
</td>
|
||||||
|
<td class="reason">
|
||||||
|
{{ result.error || '-' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
|
@ -7,6 +7,7 @@ import { OpenGraphService } from '../../services/opengraph.service';
|
||||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
import { TxResult } from '../../interfaces/node-api.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-push-transaction',
|
selector: 'app-push-transaction',
|
||||||
|
@ -19,6 +20,16 @@ export class PushTransactionComponent implements OnInit {
|
||||||
txId: string = '';
|
txId: string = '';
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|
||||||
|
submitTxsForm: UntypedFormGroup;
|
||||||
|
errorPackage: string = '';
|
||||||
|
packageMessage: string = '';
|
||||||
|
results: TxResult[] = [];
|
||||||
|
invalidMaxfeerate = false;
|
||||||
|
invalidMaxburnamount = false;
|
||||||
|
isLoadingPackage = false;
|
||||||
|
|
||||||
|
network = this.stateService.network;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
|
@ -35,6 +46,14 @@ export class PushTransactionComponent implements OnInit {
|
||||||
txHash: ['', Validators.required],
|
txHash: ['', Validators.required],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.submitTxsForm = this.formBuilder.group({
|
||||||
|
txs: ['', Validators.required],
|
||||||
|
maxfeerate: ['', Validators.min(0)],
|
||||||
|
maxburnamount: ['', Validators.min(0)],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
this.seoService.setTitle($localize`:@@meta.title.push-tx:Broadcast Transaction`);
|
this.seoService.setTitle($localize`:@@meta.title.push-tx:Broadcast Transaction`);
|
||||||
this.seoService.setDescription($localize`:@@meta.description.push-tx:Broadcast a transaction to the ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} network using the transaction's hash.`);
|
this.seoService.setDescription($localize`:@@meta.description.push-tx:Broadcast a transaction to the ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} network using the transaction's hash.`);
|
||||||
this.ogService.setManualOgImage('tx-push.jpg');
|
this.ogService.setManualOgImage('tx-push.jpg');
|
||||||
|
@ -59,7 +78,7 @@ export class PushTransactionComponent implements OnInit {
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
if (typeof error.error === 'string') {
|
if (typeof error.error === 'string') {
|
||||||
const matchText = error.error.match('"message":"(.*?)"');
|
const matchText = error.error.replace(/\\/g, '').match('"message":"(.*?)"');
|
||||||
this.error = 'Failed to broadcast transaction, reason: ' + (matchText && matchText[1] || error.error);
|
this.error = 'Failed to broadcast transaction, reason: ' + (matchText && matchText[1] || error.error);
|
||||||
} else if (error.message) {
|
} else if (error.message) {
|
||||||
this.error = 'Failed to broadcast transaction, reason: ' + error.message;
|
this.error = 'Failed to broadcast transaction, reason: ' + error.message;
|
||||||
|
@ -70,6 +89,67 @@ export class PushTransactionComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submitTxs() {
|
||||||
|
let txs: string[] = [];
|
||||||
|
try {
|
||||||
|
txs = (this.submitTxsForm.get('txs')?.value as string).split(',').map(hex => hex.trim());
|
||||||
|
if (txs?.length === 1) {
|
||||||
|
this.pushTxForm.get('txHash').setValue(txs[0]);
|
||||||
|
this.submitTxsForm.get('txs').setValue('');
|
||||||
|
this.postTx();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.errorPackage = e?.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxfeerate;
|
||||||
|
let maxburnamount;
|
||||||
|
this.invalidMaxfeerate = false;
|
||||||
|
this.invalidMaxburnamount = false;
|
||||||
|
try {
|
||||||
|
const maxfeerateVal = this.submitTxsForm.get('maxfeerate')?.value;
|
||||||
|
if (maxfeerateVal != null && maxfeerateVal !== '') {
|
||||||
|
maxfeerate = parseFloat(maxfeerateVal) / 100_000;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.invalidMaxfeerate = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const maxburnamountVal = this.submitTxsForm.get('maxburnamount')?.value;
|
||||||
|
if (maxburnamountVal != null && maxburnamountVal !== '') {
|
||||||
|
maxburnamount = parseInt(maxburnamountVal) / 100_000_000;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.invalidMaxburnamount = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoadingPackage = true;
|
||||||
|
this.errorPackage = '';
|
||||||
|
this.results = [];
|
||||||
|
this.apiService.submitPackage$(txs, maxfeerate === 0.1 ? null : maxfeerate, maxburnamount === 0 ? null : maxburnamount)
|
||||||
|
.subscribe((result) => {
|
||||||
|
this.isLoadingPackage = false;
|
||||||
|
|
||||||
|
this.packageMessage = result['package_msg'];
|
||||||
|
for (let wtxid in result['tx-results']) {
|
||||||
|
this.results.push(result['tx-results'][wtxid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitTxsForm.reset();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (typeof error.error?.error === 'string') {
|
||||||
|
const matchText = error.error.error.replace(/\\/g, '').match('"message":"(.*?)"');
|
||||||
|
this.errorPackage = matchText && matchText[1] || error.error.error;
|
||||||
|
} else if (error.message) {
|
||||||
|
this.errorPackage = error.message;
|
||||||
|
}
|
||||||
|
this.isLoadingPackage = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async handleColdcardPushTx(fragmentParams: URLSearchParams): Promise<boolean> {
|
private async handleColdcardPushTx(fragmentParams: URLSearchParams): Promise<boolean> {
|
||||||
// maybe conforms to Coldcard nfc-pushtx spec
|
// maybe conforms to Coldcard nfc-pushtx spec
|
||||||
if (fragmentParams && fragmentParams.get('t')) {
|
if (fragmentParams && fragmentParams.get('t')) {
|
||||||
|
|
|
@ -74,7 +74,7 @@ export class TestTransactionsComponent implements OnInit {
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
if (typeof error.error === 'string') {
|
if (typeof error.error === 'string') {
|
||||||
const matchText = error.error.match('"message":"(.*?)"');
|
const matchText = error.error.replace(/\\/g, '').match('"message":"(.*?)"');
|
||||||
this.error = matchText && matchText[1] || error.error;
|
this.error = matchText && matchText[1] || error.error;
|
||||||
} else if (error.message) {
|
} else if (error.message) {
|
||||||
this.error = error.message;
|
this.error = error.message;
|
||||||
|
|
|
@ -164,12 +164,12 @@
|
||||||
<br>
|
<br>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="transactionTime && isAcceleration">
|
<ng-container *ngIf="transactionTime > 0 && tx.acceleratedAt > 0 && isAcceleration">
|
||||||
<div class="title float-left">
|
<div class="title float-left">
|
||||||
<h2 id="acceleration-timeline" i18n="transaction.acceleration-timeline|Acceleration Timeline">Acceleration Timeline</h2>
|
<h2 id="acceleration-timeline" i18n="transaction.acceleration-timeline|Acceleration Timeline">Acceleration Timeline</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
<app-acceleration-timeline [transactionTime]="transactionTime" [tx]="tx" [accelerationInfo]="accelerationInfo" [eta]="(ETA$ | async)" [standardETA]="(standardETA$ | async)?.time"></app-acceleration-timeline>
|
<app-acceleration-timeline [transactionTime]="transactionTime" [acceleratedAt]="tx.acceleratedAt" [tx]="tx" [accelerationInfo]="accelerationInfo" [eta]="(ETA$ | async)"></app-acceleration-timeline>
|
||||||
<br>
|
<br>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
txChanged$ = new BehaviorSubject<boolean>(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself)
|
txChanged$ = new BehaviorSubject<boolean>(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself)
|
||||||
isAccelerated$ = new BehaviorSubject<boolean>(false); // refactor this to make isAccelerated an observable itself
|
isAccelerated$ = new BehaviorSubject<boolean>(false); // refactor this to make isAccelerated an observable itself
|
||||||
ETA$: Observable<ETA | null>;
|
ETA$: Observable<ETA | null>;
|
||||||
standardETA$: Observable<ETA | null>;
|
|
||||||
isCached: boolean = false;
|
isCached: boolean = false;
|
||||||
now = Date.now();
|
now = Date.now();
|
||||||
da$: Observable<DifficultyAdjustment>;
|
da$: Observable<DifficultyAdjustment>;
|
||||||
|
@ -883,21 +882,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
this.miningStats = stats;
|
this.miningStats = stats;
|
||||||
this.isAccelerated$.next(this.isAcceleration); // hack to trigger recalculation of ETA without adding another source observable
|
this.isAccelerated$.next(this.isAcceleration); // hack to trigger recalculation of ETA without adding another source observable
|
||||||
});
|
});
|
||||||
if (!this.tx.status?.confirmed) {
|
|
||||||
this.standardETA$ = combineLatest([
|
|
||||||
this.stateService.mempoolBlocks$.pipe(startWith(null)),
|
|
||||||
this.stateService.difficultyAdjustment$.pipe(startWith(null)),
|
|
||||||
]).pipe(
|
|
||||||
map(([mempoolBlocks, da]) => {
|
|
||||||
return this.etaService.calculateUnacceleratedETA(
|
|
||||||
this.tx,
|
|
||||||
mempoolBlocks,
|
|
||||||
da,
|
|
||||||
this.cpfpInfo,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.isAccelerated$.next(this.isAcceleration);
|
this.isAccelerated$.next(this.isAcceleration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -452,4 +452,22 @@ export interface TestMempoolAcceptResult {
|
||||||
"effective-includes": string[],
|
"effective-includes": string[],
|
||||||
},
|
},
|
||||||
['reject-reason']?: string,
|
['reject-reason']?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SubmitPackageResult {
|
||||||
|
package_msg: string;
|
||||||
|
"tx-results": { [wtxid: string]: TxResult };
|
||||||
|
"replaced-transactions"?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TxResult {
|
||||||
|
txid: string;
|
||||||
|
"other-wtxid"?: string;
|
||||||
|
vsize?: number;
|
||||||
|
fees?: {
|
||||||
|
base: number;
|
||||||
|
"effective-feerate"?: number;
|
||||||
|
"effective-includes"?: string[];
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights,
|
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights,
|
||||||
RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult } from '../interfaces/node-api.interface';
|
RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult,
|
||||||
|
SubmitPackageResult} from '../interfaces/node-api.interface';
|
||||||
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs';
|
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
|
@ -244,6 +245,19 @@ export class ApiService {
|
||||||
return this.httpClient.post<TestMempoolAcceptResult[]>(this.apiBaseUrl + this.apiBasePath + `/api/txs/test${maxfeerate != null ? '?maxfeerate=' + maxfeerate.toFixed(8) : ''}`, rawTxs);
|
return this.httpClient.post<TestMempoolAcceptResult[]>(this.apiBaseUrl + this.apiBasePath + `/api/txs/test${maxfeerate != null ? '?maxfeerate=' + maxfeerate.toFixed(8) : ''}`, rawTxs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submitPackage$(rawTxs: string[], maxfeerate?: number, maxburnamount?: number): Observable<SubmitPackageResult> {
|
||||||
|
const queryParams = [];
|
||||||
|
|
||||||
|
if (maxfeerate) {
|
||||||
|
queryParams.push(`maxfeerate=${maxfeerate}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxburnamount) {
|
||||||
|
queryParams.push(`maxburnamount=${maxburnamount}`);
|
||||||
|
}
|
||||||
|
return this.httpClient.post<SubmitPackageResult>(this.apiBaseUrl + this.apiBasePath + '/api/v1/txs/package' + (queryParams.length > 0 ? `?${queryParams.join('&')}` : ''), rawTxs);
|
||||||
|
}
|
||||||
|
|
||||||
getTransactionStatus$(txid: string): Observable<any> {
|
getTransactionStatus$(txid: string): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx/' + txid + '/status');
|
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx/' + txid + '/status');
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,16 +135,16 @@ export class ServicesApiServices {
|
||||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID });
|
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID });
|
||||||
}
|
}
|
||||||
|
|
||||||
accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string) {
|
accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) {
|
||||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD });
|
||||||
}
|
}
|
||||||
|
|
||||||
accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string) {
|
accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) {
|
||||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD });
|
||||||
}
|
}
|
||||||
|
|
||||||
accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string) {
|
accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string, userApprovedUSD: number) {
|
||||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID, userApprovedUSD: userApprovedUSD });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccelerations$(): Observable<Acceleration[]> {
|
getAccelerations$(): Observable<Acceleration[]> {
|
||||||
|
|
|
@ -70,6 +70,12 @@ export class GeolocationComponent implements OnChanges {
|
||||||
if (this.type === 'node') {
|
if (this.type === 'node') {
|
||||||
const city = this.data.city ? this.data.city : '';
|
const city = this.data.city ? this.data.city : '';
|
||||||
|
|
||||||
|
// Handle city-states like Singapore or Hong Kong
|
||||||
|
if (city && city === this.data?.country) {
|
||||||
|
this.formattedLocation = `${this.data.country} ${getFlagEmoji(this.data.iso)}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// City
|
// City
|
||||||
this.formattedLocation = `${city}`;
|
this.formattedLocation = `${city}`;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 HAUS HOPPE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
|
||||||
// Adapted from https://github.com/ordpool-space/ordpool-parser/tree/ce04d7a5b6bb1cf37b9fdadd77ba430f5bd6e7d6/src
|
// Adapted from https://github.com/ordpool-space/ordpool-parser/tree/ce04d7a5b6bb1cf37b9fdadd77ba430f5bd6e7d6/src
|
||||||
// Utils functions to decode ord inscriptions
|
// Utils functions to decode ord inscriptions
|
||||||
|
|
||||||
|
|
|
@ -313,20 +313,24 @@ export function getRegex(type: RegexType, network?: Network): RegExp {
|
||||||
}
|
}
|
||||||
regex += `)`; // End the non-capturing group
|
regex += `)`; // End the non-capturing group
|
||||||
break;
|
break;
|
||||||
// Match a date in the format YYYY-MM-DD (optional: HH:MM)
|
// Match a date in the format YYYY-MM-DD (optional: HH:MM or HH:MM:SS)
|
||||||
// [Testing Order]: any order is fine
|
// [Testing Order]: any order is fine
|
||||||
case `date`:
|
case `date`:
|
||||||
regex += `(?:`; // Start a non-capturing group
|
regex += `(?:`; // Start a non-capturing group
|
||||||
regex += `${NUMBER_CHARS}{4}`; // Exactly 4 digits
|
regex += `${NUMBER_CHARS}{4}`; // Exactly 4 digits
|
||||||
regex += `[-/]`; // 1 instance of the symbol "-" or "/"
|
regex += `[-/]`; // 1 instance of the symbol "-" or "/"
|
||||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||||
regex += `[-/]`; // 1 instance of the symbol "-" or "/"
|
regex += `[-/]`; // 1 instance of the symbol "-" or "/"
|
||||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||||
regex += `(?:`; // Start a non-capturing group
|
regex += `(?:`; // Start a non-capturing group
|
||||||
regex += ` `; // 1 instance of the symbol " "
|
regex += ` `; // 1 instance of the symbol " "
|
||||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||||
regex += `:`; // 1 instance of the symbol ":"
|
regex += `:`; // 1 instance of the symbol ":"
|
||||||
regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits
|
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||||
|
regex += `(?:`; // Start a non-capturing group for optional seconds
|
||||||
|
regex += `:`; // 1 instance of the symbol ":"
|
||||||
|
regex += `${NUMBER_CHARS}{1,2}`; // 1 or 2 digits
|
||||||
|
regex += `)?`; // End the non-capturing group
|
||||||
regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times
|
regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times
|
||||||
regex += `)`; // End the non-capturing group
|
regex += `)`; // End the non-capturing group
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@reboot sleep 5 ; /usr/local/bin/bitcoind -testnet >/dev/null 2>&1
|
@reboot sleep 5 ; /usr/local/bin/bitcoind -testnet >/dev/null 2>&1
|
||||||
|
@reboot sleep 5 ; /usr/local/bin/bitcoind -testnet4 >/dev/null 2>&1
|
||||||
@reboot sleep 5 ; /usr/local/bin/bitcoind -signet >/dev/null 2>&1
|
@reboot sleep 5 ; /usr/local/bin/bitcoind -signet >/dev/null 2>&1
|
||||||
@reboot sleep 10 ; screen -dmS mainnet /bitcoin/electrs/start mainnet
|
@reboot sleep 10 ; screen -dmS mainnet /bitcoin/electrs/start mainnet
|
||||||
@reboot sleep 10 ; screen -dmS testnet /bitcoin/electrs/start testnet
|
@reboot sleep 10 ; screen -dmS testnet /bitcoin/electrs/start testnet
|
||||||
|
|
|
@ -47,6 +47,7 @@ UNFURL_INSTALL=ON
|
||||||
BITCOIN_MAINNET_ENABLE=ON
|
BITCOIN_MAINNET_ENABLE=ON
|
||||||
BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
||||||
BITCOIN_TESTNET_ENABLE=ON
|
BITCOIN_TESTNET_ENABLE=ON
|
||||||
|
BITCOIN_TESTNET4_ENABLE=ON
|
||||||
BITCOIN_SIGNET_ENABLE=ON
|
BITCOIN_SIGNET_ENABLE=ON
|
||||||
BITCOIN_MAINNET_LIGHTNING_ENABLE=ON
|
BITCOIN_MAINNET_LIGHTNING_ENABLE=ON
|
||||||
BITCOIN_TESTNET_LIGHTNING_ENABLE=ON
|
BITCOIN_TESTNET_LIGHTNING_ENABLE=ON
|
||||||
|
@ -100,6 +101,13 @@ BITCOIN_TESTNET_P2P_PORT=18333
|
||||||
BITCOIN_TESTNET_RPC_HOST=127.0.0.1
|
BITCOIN_TESTNET_RPC_HOST=127.0.0.1
|
||||||
BITCOIN_TESTNET_RPC_PORT=18332
|
BITCOIN_TESTNET_RPC_PORT=18332
|
||||||
|
|
||||||
|
# used for firewall configuration
|
||||||
|
BITCOIN_TESTNET4_P2P_HOST=127.0.0.1
|
||||||
|
BITCOIN_TESTNET4_P2P_PORT=48333
|
||||||
|
# used for RPC communication
|
||||||
|
BITCOIN_TESTNET4_RPC_HOST=127.0.0.1
|
||||||
|
BITCOIN_TESTNET4_RPC_PORT=48332
|
||||||
|
|
||||||
# used for firewall configuration
|
# used for firewall configuration
|
||||||
BITCOIN_SIGNET_P2P_HOST=127.0.0.1
|
BITCOIN_SIGNET_P2P_HOST=127.0.0.1
|
||||||
BITCOIN_SIGNET_P2P_PORT=18333
|
BITCOIN_SIGNET_P2P_PORT=18333
|
||||||
|
@ -139,6 +147,11 @@ ELECTRS_LIQUID_HTTP_PORT=3001
|
||||||
ELECTRS_TESTNET_HTTP_HOST=127.0.0.1
|
ELECTRS_TESTNET_HTTP_HOST=127.0.0.1
|
||||||
ELECTRS_TESTNET_HTTP_PORT=3002
|
ELECTRS_TESTNET_HTTP_PORT=3002
|
||||||
|
|
||||||
|
# set either socket or TCP host/port, not both
|
||||||
|
#ELECTRS_TESTNET4_HTTP_SOCK=/tmp/bitcoin.testnet4.electrs
|
||||||
|
ELECTRS_TESTNET4_HTTP_HOST=127.0.0.1
|
||||||
|
ELECTRS_TESTNET4_HTTP_PORT=3005
|
||||||
|
|
||||||
# set either socket or TCP host/port, not both
|
# set either socket or TCP host/port, not both
|
||||||
#ELECTRS_SIGNET_HTTP_SOCK=/tmp/bitcoin.testnet.electrs
|
#ELECTRS_SIGNET_HTTP_SOCK=/tmp/bitcoin.testnet.electrs
|
||||||
ELECTRS_SIGNET_HTTP_HOST=127.0.0.1
|
ELECTRS_SIGNET_HTTP_HOST=127.0.0.1
|
||||||
|
@ -164,6 +177,11 @@ MEMPOOL_LIQUID_HTTP_PORT=8998
|
||||||
MEMPOOL_TESTNET_HTTP_HOST=127.0.0.1
|
MEMPOOL_TESTNET_HTTP_HOST=127.0.0.1
|
||||||
MEMPOOL_TESTNET_HTTP_PORT=8997
|
MEMPOOL_TESTNET_HTTP_PORT=8997
|
||||||
|
|
||||||
|
# set either socket or TCP host/port, not both
|
||||||
|
#MEMPOOL_TESTNET4_HTTP_SOCK=/tmp/bitcoin.testnet.mempool
|
||||||
|
MEMPOOL_TESTNET4_HTTP_HOST=127.0.0.1
|
||||||
|
MEMPOOL_TESTNET4_HTTP_PORT=8990
|
||||||
|
|
||||||
# set either socket or TCP host/port, not both
|
# set either socket or TCP host/port, not both
|
||||||
#MEMPOOL_BISQ_HTTP_SOCK=/tmp/bitcoin.bisq.mempool
|
#MEMPOOL_BISQ_HTTP_SOCK=/tmp/bitcoin.bisq.mempool
|
||||||
MEMPOOL_BISQ_HTTP_HOST=127.0.0.1
|
MEMPOOL_BISQ_HTTP_HOST=127.0.0.1
|
||||||
|
@ -231,6 +249,7 @@ MYSQL_GROUP=mysql
|
||||||
# mempool mysql user/password
|
# mempool mysql user/password
|
||||||
MEMPOOL_MAINNET_USER='mempool'
|
MEMPOOL_MAINNET_USER='mempool'
|
||||||
MEMPOOL_TESTNET_USER='mempool_testnet'
|
MEMPOOL_TESTNET_USER='mempool_testnet'
|
||||||
|
MEMPOOL_TESTNET4_USER='mempool_testnet4'
|
||||||
MEMPOOL_SIGNET_USER='mempool_signet'
|
MEMPOOL_SIGNET_USER='mempool_signet'
|
||||||
MEMPOOL_MAINNET_LIGHTNING_USER='mempool_mainnet_lightning'
|
MEMPOOL_MAINNET_LIGHTNING_USER='mempool_mainnet_lightning'
|
||||||
MEMPOOL_TESTNET_LIGHTNING_USER='mempool_testnet_lightning'
|
MEMPOOL_TESTNET_LIGHTNING_USER='mempool_testnet_lightning'
|
||||||
|
@ -241,6 +260,7 @@ MEMPOOL_BISQ_USER='mempool_bisq'
|
||||||
# generate random hex string
|
# generate random hex string
|
||||||
MEMPOOL_MAINNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_MAINNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_TESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_TESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
|
MEMPOOL_TESTNET4_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_SIGNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_SIGNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_MAINNET_LIGHTNING_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_MAINNET_LIGHTNING_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_TESTNET_LIGHTNING_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_TESTNET_LIGHTNING_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
|
@ -265,7 +285,9 @@ BITCOIN_HOME=/bitcoin
|
||||||
|
|
||||||
# bitcoin testnet data
|
# bitcoin testnet data
|
||||||
BITCOIN_TESTNET_DATA=${BITCOIN_HOME}/testnet3
|
BITCOIN_TESTNET_DATA=${BITCOIN_HOME}/testnet3
|
||||||
# bitcoin testnet data
|
# bitcoin testnet4 data
|
||||||
|
BITCOIN_TESTNET4_DATA=${BITCOIN_HOME}/testnet4
|
||||||
|
# bitcoin signet data
|
||||||
BITCOIN_SIGNET_DATA=${BITCOIN_HOME}/signet
|
BITCOIN_SIGNET_DATA=${BITCOIN_HOME}/signet
|
||||||
|
|
||||||
# bitcoin electrs source/binaries
|
# bitcoin electrs source/binaries
|
||||||
|
@ -279,6 +301,9 @@ ELECTRS_MAINNET_DATA=${ELECTRS_DATA_ROOT}/mainnet
|
||||||
# bitcoin testnet electrs database, only a few GB
|
# bitcoin testnet electrs database, only a few GB
|
||||||
ELECTRS_TESTNET_ZPOOL=${ZPOOL}
|
ELECTRS_TESTNET_ZPOOL=${ZPOOL}
|
||||||
ELECTRS_TESTNET_DATA=${ELECTRS_DATA_ROOT}/testnet
|
ELECTRS_TESTNET_DATA=${ELECTRS_DATA_ROOT}/testnet
|
||||||
|
# bitcoin testnet4 electrs database, only a few GB
|
||||||
|
ELECTRS_TESTNET4_ZPOOL=${ZPOOL}
|
||||||
|
ELECTRS_TESTNET4_DATA=${ELECTRS_DATA_ROOT}/testnet4
|
||||||
# bitcoin signet electrs database, only a few GB
|
# bitcoin signet electrs database, only a few GB
|
||||||
ELECTRS_SIGNET_ZPOOL=${ZPOOL}
|
ELECTRS_SIGNET_ZPOOL=${ZPOOL}
|
||||||
ELECTRS_SIGNET_DATA=${ELECTRS_DATA_ROOT}/signet
|
ELECTRS_SIGNET_DATA=${ELECTRS_DATA_ROOT}/signet
|
||||||
|
@ -332,7 +357,7 @@ BITCOIN_REPO_URL=https://github.com/bitcoin/bitcoin
|
||||||
BITCOIN_REPO_NAME=bitcoin
|
BITCOIN_REPO_NAME=bitcoin
|
||||||
BITCOIN_REPO_BRANCH=master
|
BITCOIN_REPO_BRANCH=master
|
||||||
#BITCOIN_LATEST_RELEASE=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest|grep tag_name|head -1|cut -d '"' -f4)
|
#BITCOIN_LATEST_RELEASE=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest|grep tag_name|head -1|cut -d '"' -f4)
|
||||||
BITCOIN_LATEST_RELEASE=v25.1
|
BITCOIN_LATEST_RELEASE=v28.0rc2
|
||||||
echo -n '.'
|
echo -n '.'
|
||||||
|
|
||||||
BISQ_REPO_URL=https://github.com/bisq-network/bisq
|
BISQ_REPO_URL=https://github.com/bisq-network/bisq
|
||||||
|
@ -567,6 +592,15 @@ zfsCreateFilesystems()
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Bitcoin Testnet4
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
zfs create -o "mountpoint=${BITCOIN_TESTNET4_DATA}" "${ZPOOL}/bitcoin/testnet4"
|
||||||
|
for folder in chainstate indexes blocks
|
||||||
|
do
|
||||||
|
zfs create -o "mountpoint=${BITCOIN_TESTNET4_DATA}/${folder}" "${ZPOOL}/bitcoin/testnet4/${folder}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# Bitcoin Signet
|
# Bitcoin Signet
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
zfs create -o "mountpoint=${BITCOIN_SIGNET_DATA}" "${ZPOOL}/bitcoin/signet"
|
zfs create -o "mountpoint=${BITCOIN_SIGNET_DATA}" "${ZPOOL}/bitcoin/signet"
|
||||||
|
@ -594,6 +628,15 @@ zfsCreateFilesystems()
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# electrs testnet4 data
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
zfs create -o "mountpoint=${ELECTRS_TESTNET4_DATA}" "${ELECTRS_TESTNET4_ZPOOL}/electrs/testnet4"
|
||||||
|
for folder in cache history txstore
|
||||||
|
do
|
||||||
|
zfs create -o "mountpoint=${ELECTRS_TESTNET4_DATA}/newindex/${folder}" "${ELECTRS_TESTNET4_ZPOOL}/electrs/testnet4/${folder}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# electrs signet data
|
# electrs signet data
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
zfs create -o "mountpoint=${ELECTRS_SIGNET_DATA}" "${ELECTRS_SIGNET_ZPOOL}/electrs/signet"
|
zfs create -o "mountpoint=${ELECTRS_SIGNET_DATA}" "${ELECTRS_SIGNET_ZPOOL}/electrs/signet"
|
||||||
|
@ -651,6 +694,15 @@ ext4CreateDir()
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Bitcoin Testnet4
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
mkdir -p "${BITCOIN_TESTNET4_DATA}"
|
||||||
|
for folder in chainstate indexes blocks
|
||||||
|
do
|
||||||
|
mkdir -p "${BITCOIN_TESTNET4_DATA}/${folder}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# Bitcoin Signet
|
# Bitcoin Signet
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
mkdir -p "${BITCOIN_SIGNET_DATA}"
|
mkdir -p "${BITCOIN_SIGNET_DATA}"
|
||||||
|
@ -678,6 +730,15 @@ ext4CreateDir()
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# electrs testnet4 data
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
mkdir -p "${ELECTRS_TESTNET4_DATA}"
|
||||||
|
for folder in cache history txstore
|
||||||
|
do
|
||||||
|
mkdir -p "${ELECTRS_TESTNET4_DATA}/newindex/${folder}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# electrs signet data
|
# electrs signet data
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
mkdir -p "${ELECTRS_SIGNET_DATA}"
|
mkdir -p "${ELECTRS_SIGNET_DATA}"
|
||||||
|
@ -769,6 +830,7 @@ LN-Mainnet:Enable Bitcoin Mainnet Lightning:ON
|
||||||
LN-Testnet:Enable Bitcoin Testnet Lightning:ON
|
LN-Testnet:Enable Bitcoin Testnet Lightning:ON
|
||||||
LN-Signet:Enable Bitcoin Signet Lightning:ON
|
LN-Signet:Enable Bitcoin Signet Lightning:ON
|
||||||
Testnet:Enable Bitcoin Testnet:ON
|
Testnet:Enable Bitcoin Testnet:ON
|
||||||
|
Testnet4:Enable Bitcoin Testnet4:ON
|
||||||
Signet:Enable Bitcoin Signet:ON
|
Signet:Enable Bitcoin Signet:ON
|
||||||
Liquid:Enable Elements Liquid:ON
|
Liquid:Enable Elements Liquid:ON
|
||||||
Liquidtestnet:Enable Elements Liquidtestnet:ON
|
Liquidtestnet:Enable Elements Liquidtestnet:ON
|
||||||
|
@ -818,13 +880,19 @@ else
|
||||||
BITCOIN_TESTNET_ENABLE=OFF
|
BITCOIN_TESTNET_ENABLE=OFF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if grep Testnet4 $tempfile >/dev/null 2>&1;then
|
||||||
|
BITCOIN_TESTNET4_ENABLE=ON
|
||||||
|
else
|
||||||
|
BITCOIN_TESTNET4_ENABLE=OFF
|
||||||
|
fi
|
||||||
|
|
||||||
if grep Signet $tempfile >/dev/null 2>&1;then
|
if grep Signet $tempfile >/dev/null 2>&1;then
|
||||||
BITCOIN_SIGNET_ENABLE=ON
|
BITCOIN_SIGNET_ENABLE=ON
|
||||||
else
|
else
|
||||||
BITCOIN_SIGNET_ENABLE=OFF
|
BITCOIN_SIGNET_ENABLE=OFF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
BITCOIN_INSTALL=ON
|
BITCOIN_INSTALL=ON
|
||||||
else
|
else
|
||||||
BITCOIN_INSTALL=OFF
|
BITCOIN_INSTALL=OFF
|
||||||
|
@ -872,7 +940,7 @@ else
|
||||||
CLN_INSTALL=OFF
|
CLN_INSTALL=OFF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
BITCOIN_ELECTRS_INSTALL=ON
|
BITCOIN_ELECTRS_INSTALL=ON
|
||||||
else
|
else
|
||||||
BITCOIN_ELECTRS_INSTALL=OFF
|
BITCOIN_ELECTRS_INSTALL=OFF
|
||||||
|
@ -1216,6 +1284,9 @@ if [ "${BITCOIN_ELECTRS_INSTALL}" = ON ];then
|
||||||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_TESTNET_DATA}"
|
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_TESTNET_DATA}"
|
||||||
fi
|
fi
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_TESTNET4_DATA}"
|
||||||
|
fi
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_SIGNET_DATA}"
|
osSudo "${ROOT_USER}" chown -R "${BITCOIN_USER}:${BITCOIN_GROUP}" "${ELECTRS_SIGNET_DATA}"
|
||||||
fi
|
fi
|
||||||
|
@ -1520,7 +1591,7 @@ fi
|
||||||
# Bitcoin instance for Mainnet Minfee #
|
# Bitcoin instance for Mainnet Minfee #
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON ];then
|
||||||
echo "[*] Installing Bitcoin Minfee service"
|
echo "[*] Installing Bitcoin Minfee service"
|
||||||
case $OS in
|
case $OS in
|
||||||
|
|
||||||
|
@ -1550,6 +1621,23 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#################################
|
||||||
|
# Bitcoin instance for Testnet4 #
|
||||||
|
#################################
|
||||||
|
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Installing Bitcoin Testnet service"
|
||||||
|
case $OS in
|
||||||
|
|
||||||
|
FreeBSD)
|
||||||
|
;;
|
||||||
|
|
||||||
|
Debian)
|
||||||
|
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/bitcoin-testnet4.service" "${DEBIAN_SERVICE_HOME}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# Bitcoin instance for Signet #
|
# Bitcoin instance for Signet #
|
||||||
###############################
|
###############################
|
||||||
|
@ -1616,6 +1704,14 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
echo "[*] FIXME: must only crontab enabled daemons"
|
echo "[*] FIXME: must only crontab enabled daemons"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#########################################
|
||||||
|
# Electrs instance for Bitcoin Testnet4 #
|
||||||
|
#########################################
|
||||||
|
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
echo "[*] FIXME: must only crontab enabled daemons"
|
||||||
|
fi
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Electrs instance for Bitcoin Signet #
|
# Electrs instance for Bitcoin Signet #
|
||||||
#######################################
|
#######################################
|
||||||
|
@ -1668,11 +1764,15 @@ case $OS in
|
||||||
echo "[*] Installing Electrs Testnet Cronjob"
|
echo "[*] Installing Electrs Testnet Cronjob"
|
||||||
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/start testnet\n"
|
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/start testnet\n"
|
||||||
fi
|
fi
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Installing Electrs Testnet4 Cronjob"
|
||||||
|
crontab_bitcoin+="@reboot sleep 110 ; screen -dmS testnet4 /bitcoin/electrs/start testnet4\n"
|
||||||
|
fi
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
echo "[*] Installing Electrs Signet Cronjob"
|
echo "[*] Installing Electrs Signet Cronjob"
|
||||||
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/start signet\n"
|
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/start signet\n"
|
||||||
fi
|
fi
|
||||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
|
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -1700,7 +1800,7 @@ fi
|
||||||
|
|
||||||
##### Mempool -> Bitcoin Mainnet instance
|
##### Mempool -> Bitcoin Mainnet instance
|
||||||
|
|
||||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_TESTNET4_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
echo "[*] Creating Mempool instance for Bitcoin Mainnet"
|
echo "[*] Creating Mempool instance for Bitcoin Mainnet"
|
||||||
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/mainnet"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/mainnet"
|
||||||
|
@ -1727,6 +1827,15 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/testnet && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/testnet && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Creating Mempool instance for Bitcoin Testnet4"
|
||||||
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/testnet4"
|
||||||
|
|
||||||
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Testnet4"
|
||||||
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/testnet4 && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "${BITCOIN_TESTNET_LIGHTNING_ENABLE}" = ON ];then
|
if [ "${BITCOIN_TESTNET_LIGHTNING_ENABLE}" = ON ];then
|
||||||
echo "[*] Creating Mempool instance for Lightning Network on Bitcoin Testnet"
|
echo "[*] Creating Mempool instance for Lightning Network on Bitcoin Testnet"
|
||||||
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
|
@ -1804,6 +1913,9 @@ grant all on mempool.* to '${MEMPOOL_MAINNET_USER}'@'localhost' identified by '$
|
||||||
create database mempool_testnet;
|
create database mempool_testnet;
|
||||||
grant all on mempool_testnet.* to '${MEMPOOL_TESTNET_USER}'@'localhost' identified by '${MEMPOOL_TESTNET_PASS}';
|
grant all on mempool_testnet.* to '${MEMPOOL_TESTNET_USER}'@'localhost' identified by '${MEMPOOL_TESTNET_PASS}';
|
||||||
|
|
||||||
|
create database mempool_testnet4;
|
||||||
|
grant all on mempool_testnet4.* to '${MEMPOOL_TESTNET4_USER}'@'localhost' identified by '${MEMPOOL_TESTNET4_PASS}';
|
||||||
|
|
||||||
create database mempool_signet;
|
create database mempool_signet;
|
||||||
grant all on mempool_signet.* to '${MEMPOOL_SIGNET_USER}'@'localhost' identified by '${MEMPOOL_SIGNET_PASS}';
|
grant all on mempool_signet.* to '${MEMPOOL_SIGNET_USER}'@'localhost' identified by '${MEMPOOL_SIGNET_PASS}';
|
||||||
|
|
||||||
|
@ -1832,6 +1944,8 @@ declare -x MEMPOOL_MAINNET_USER="${MEMPOOL_MAINNET_USER}"
|
||||||
declare -x MEMPOOL_MAINNET_PASS="${MEMPOOL_MAINNET_PASS}"
|
declare -x MEMPOOL_MAINNET_PASS="${MEMPOOL_MAINNET_PASS}"
|
||||||
declare -x MEMPOOL_TESTNET_USER="${MEMPOOL_TESTNET_USER}"
|
declare -x MEMPOOL_TESTNET_USER="${MEMPOOL_TESTNET_USER}"
|
||||||
declare -x MEMPOOL_TESTNET_PASS="${MEMPOOL_TESTNET_PASS}"
|
declare -x MEMPOOL_TESTNET_PASS="${MEMPOOL_TESTNET_PASS}"
|
||||||
|
declare -x MEMPOOL_TESTNET4_USER="${MEMPOOL_TESTNET4_USER}"
|
||||||
|
declare -x MEMPOOL_TESTNET4_PASS="${MEMPOOL_TESTNET4_PASS}"
|
||||||
declare -x MEMPOOL_SIGNET_USER="${MEMPOOL_SIGNET_USER}"
|
declare -x MEMPOOL_SIGNET_USER="${MEMPOOL_SIGNET_USER}"
|
||||||
declare -x MEMPOOL_SIGNET_PASS="${MEMPOOL_SIGNET_PASS}"
|
declare -x MEMPOOL_SIGNET_PASS="${MEMPOOL_SIGNET_PASS}"
|
||||||
declare -x MEMPOOL_MAINNET_LIGHTNING_USER="${MEMPOOL_MAINNET_LIGHTNING_USER}"
|
declare -x MEMPOOL_MAINNET_LIGHTNING_USER="${MEMPOOL_MAINNET_LIGHTNING_USER}"
|
||||||
|
@ -1932,6 +2046,9 @@ EOF
|
||||||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet.service
|
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet.service
|
||||||
fi
|
fi
|
||||||
|
if [ "${BITCOIN_TESTNET4_ENABLE}" = ON ];then
|
||||||
|
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet4.service
|
||||||
|
fi
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" systemctl enable bitcoin-signet.service
|
osSudo "${ROOT_USER}" systemctl enable bitcoin-signet.service
|
||||||
fi
|
fi
|
||||||
|
|
22
production/linux/bitcoin-testnet4.service
Normal file
22
production/linux/bitcoin-testnet4.service
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Bitcoind-testnet4
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/bitcoind -conf=bitcoin.conf -daemon -testnet4 -printtoconsole -pid=/bitcoin/bitcoind-testnet4.pid
|
||||||
|
ExecStop=/usr/local/bin/bitcoin-cli -testnet4 stop
|
||||||
|
|
||||||
|
Type=forking
|
||||||
|
PIDFile=/bitcoin/bitcoind-testnet4.pid
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
User=bitcoin
|
||||||
|
Group=bitcoin
|
||||||
|
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=full
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -10,6 +10,9 @@
|
||||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||||
|
"MAINNET_BLOCK_AUDIT_START_HEIGHT": 773911,
|
||||||
|
"TESTNET_BLOCK_AUDIT_START_HEIGHT": 2417829,
|
||||||
|
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 127609,
|
||||||
"ITEMS_PER_PAGE": 25,
|
"ITEMS_PER_PAGE": 25,
|
||||||
"LIGHTNING": true,
|
"LIGHTNING": true,
|
||||||
"ACCELERATOR": true,
|
"ACCELERATOR": true,
|
||||||
|
|
Loading…
Add table
Reference in a new issue