mirror of
https://github.com/Retropex/mempool.git
synced 2025-05-12 18:20:41 +02:00
Merge branch 'master' into simon/fix-database-disabled
This commit is contained in:
commit
fcebe001d8
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@ -3,16 +3,19 @@ name: CI Pipeline for the Backend and Frontend
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, review_requested, synchronize]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'"
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["20", "21"]
|
||||
node: ["22"]
|
||||
flavor: ["dev", "prod"]
|
||||
fail-fast: false
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: Backend (${{ matrix.flavor }}) - node ${{ matrix.node }}
|
||||
steps:
|
||||
@ -66,7 +69,7 @@ jobs:
|
||||
|
||||
cache:
|
||||
name: "Cache assets for builds"
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@ -157,13 +160,13 @@ jobs:
|
||||
|
||||
frontend:
|
||||
needs: cache
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'"
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["20", "21"]
|
||||
node: ["22"]
|
||||
flavor: ["dev", "prod"]
|
||||
fail-fast: false
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: Frontend (${{ matrix.flavor }}) - node ${{ matrix.node }}
|
||||
steps:
|
||||
@ -245,8 +248,8 @@ jobs:
|
||||
VERBOSE: 1
|
||||
|
||||
e2e:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
runs-on: "ubuntu-latest"
|
||||
if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'"
|
||||
runs-on: ubuntu-latest
|
||||
needs: frontend
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -263,7 +266,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
|
||||
|
||||
@ -309,7 +312,7 @@ jobs:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:local-staging
|
||||
start: npm run start:parameterized
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
@ -334,7 +337,7 @@ jobs:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:local-staging
|
||||
start: npm run start:parameterized
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
@ -359,7 +362,7 @@ jobs:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:mempool
|
||||
start: npm run start:local-staging
|
||||
start: npm run start:parameterized
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
@ -375,10 +378,9 @@ jobs:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||
|
||||
validate_docker_json:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
runs-on: "ubuntu-latest"
|
||||
if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'"
|
||||
runs-on: ubuntu-latest
|
||||
name: Validate generated backend Docker JSON
|
||||
|
||||
steps:
|
||||
|
273
.github/workflows/e2e_parameterized.yml
vendored
Normal file
273
.github/workflows/e2e_parameterized.yml
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
name: 'Parameterized e2e tests'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Branch name or Pull Request number (e.g., master or 6102)'
|
||||
required: true
|
||||
default: 'master'
|
||||
type: string
|
||||
mempool_hostname:
|
||||
description: 'Mempool Hostname'
|
||||
required: true
|
||||
default: 'mempool.space'
|
||||
type: string
|
||||
liquid_hostname:
|
||||
description: 'Liquid Hostname'
|
||||
required: true
|
||||
default: 'liquid.network'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
cache:
|
||||
name: "Cache assets for builds"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine checkout ref
|
||||
id: determine-ref
|
||||
run: |
|
||||
REF_INPUT="${{ github.event.inputs.ref }}"
|
||||
if [[ "$REF_INPUT" =~ ^[0-9]+$ ]]; then
|
||||
echo "ref=refs/pull/$REF_INPUT/head" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ref=$REF_INPUT" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ steps.determine-ref.outputs.ref }}
|
||||
path: assets
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install (Prod dependencies only)
|
||||
run: npm ci --omit=dev --omit=optional
|
||||
working-directory: assets/frontend
|
||||
|
||||
- name: Restore cached mining pool assets
|
||||
continue-on-error: true
|
||||
id: cache-mining-pool-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
mining-pool-assets.zip
|
||||
key: mining-pool-assets-cache
|
||||
|
||||
- name: Restore promo video assets
|
||||
continue-on-error: true
|
||||
id: cache-promo-video-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
promo-video-assets.zip
|
||||
key: promo-video-assets-cache
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
continue-on-error: true
|
||||
run: unzip -o mining-pool-assets.zip -d assets/frontend/src/resources/mining-pools
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
continue-on-error: true
|
||||
run: unzip -o promo-video-assets.zip -d assets/frontend/src/resources/promo-video
|
||||
|
||||
# - name: Unzip assets before building (dist)
|
||||
# continue-on-error: true
|
||||
# run: unzip assets.zip -d assets/frontend/dist/mempool/browser/resources
|
||||
|
||||
- name: Sync-assets
|
||||
run: npm run sync-assets-dev
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MEMPOOL_CDN: 1
|
||||
VERBOSE: 1
|
||||
working-directory: assets/frontend
|
||||
|
||||
- name: Zip mining-pool assets
|
||||
run: zip -jrq mining-pool-assets.zip assets/frontend/src/resources/mining-pools/*
|
||||
|
||||
- name: Zip promo-video assets
|
||||
run: zip -jrq promo-video-assets.zip assets/frontend/src/resources/promo-video/*
|
||||
|
||||
- name: Upload mining pool assets as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mining-pool-assets
|
||||
path: mining-pool-assets.zip
|
||||
|
||||
- name: Upload promo video assets as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: promo-video-assets
|
||||
path: promo-video-assets.zip
|
||||
|
||||
- name: Save mining pool assets cache
|
||||
id: cache-mining-pool-save
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
mining-pool-assets.zip
|
||||
key: mining-pool-assets-cache
|
||||
|
||||
- name: Save promo video assets cache
|
||||
id: cache-promo-video-save
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
promo-video-assets.zip
|
||||
key: promo-video-assets-cache
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: cache
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module: ["mempool", "liquid", "testnet4"]
|
||||
|
||||
name: E2E tests for ${{ matrix.module }}
|
||||
steps:
|
||||
- name: Determine checkout ref
|
||||
id: determine-ref
|
||||
run: |
|
||||
REF_INPUT="${{ github.event.inputs.ref }}"
|
||||
if [[ "$REF_INPUT" =~ ^[0-9]+$ ]]; then
|
||||
echo "ref=refs/pull/$REF_INPUT/head" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ref=$REF_INPUT" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ steps.determine-ref.outputs.ref }}
|
||||
path: ${{ matrix.module }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||
|
||||
- name: Restore cached mining pool assets
|
||||
continue-on-error: true
|
||||
id: cache-mining-pool-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
mining-pool-assets.zip
|
||||
key: mining-pool-assets-cache
|
||||
|
||||
- name: Restore cached promo video assets
|
||||
continue-on-error: true
|
||||
id: cache-promo-video-restore
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
promo-video-assets.zip
|
||||
key: promo-video-assets-cache
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: mining-pool-assets
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
run: unzip -o mining-pool-assets.zip -d ${{ matrix.module }}/frontend/src/resources/mining-pools
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: promo-video-assets
|
||||
|
||||
- name: Unzip assets before building (src/resources)
|
||||
run: unzip -o promo-video-assets.zip -d ${{ matrix.module }}/frontend/src/resources/promo-video
|
||||
|
||||
# mempool
|
||||
- name: Chrome browser tests (${{ matrix.module }})
|
||||
if: ${{ matrix.module == 'mempool' }}
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:parameterized
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: |
|
||||
cypress/e2e/mainnet/*.spec.ts
|
||||
cypress/e2e/signet/*.spec.ts
|
||||
group: Tests on Chrome (${{ matrix.module }})
|
||||
browser: "chrome"
|
||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||
env:
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event_name }} (${{ github.sha }}) - ref ${{ github.event.inputs.ref }} - ${{ github.event.inputs.mempool_hostname }} - ${{ github.event.inputs.liquid_hostname }}
|
||||
MEMPOOL_HOSTNAME: ${{ github.event.inputs.mempool_hostname }}
|
||||
LIQUID_HOSTNAME: ${{ github.event.inputs.liquid_hostname }}
|
||||
MEMPOOL_CI_API_KEY: ${{ secrets.MEMPOOL_CI_API_KEY }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||
|
||||
# liquid
|
||||
- name: Chrome browser tests (${{ matrix.module }})
|
||||
if: ${{ matrix.module == 'liquid' }}
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:parameterized
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: |
|
||||
cypress/e2e/liquid/liquid.spec.ts
|
||||
cypress/e2e/liquidtestnet/liquidtestnet.spec.ts
|
||||
group: Tests on Chrome (${{ matrix.module }})
|
||||
browser: "chrome"
|
||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||
env:
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event_name }} (${{ github.sha }}) - ref ${{ github.event.inputs.ref }} - ${{ github.event.inputs.mempool_hostname }} - ${{ github.event.inputs.liquid_hostname }}
|
||||
MEMPOOL_HOSTNAME: ${{ github.event.inputs.mempool_hostname }}
|
||||
LIQUID_HOSTNAME: ${{ github.event.inputs.liquid_hostname }}
|
||||
MEMPOOL_CI_API_KEY: ${{ secrets.MEMPOOL_CI_API_KEY }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||
|
||||
# testnet
|
||||
- name: Chrome browser tests (${{ matrix.module }})
|
||||
if: ${{ matrix.module == 'testnet4' }}
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:mempool
|
||||
start: npm run start:parameterized
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: |
|
||||
cypress/e2e/testnet4/*.spec.ts
|
||||
group: Tests on Chrome (${{ matrix.module }})
|
||||
browser: "chrome"
|
||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||
env:
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event_name }} (${{ github.sha }}) - ref ${{ github.event.inputs.ref }} - ${{ github.event.inputs.mempool_hostname }} - ${{ github.event.inputs.liquid_hostname }}
|
||||
MEMPOOL_HOSTNAME: ${{ github.event.inputs.mempool_hostname }}
|
||||
LIQUID_HOSTNAME: ${{ github.event.inputs.liquid_hostname }}
|
||||
MEMPOOL_CI_API_KEY: ${{ secrets.MEMPOOL_CI_API_KEY }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
@ -4,7 +4,7 @@ on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
print-backend-sha:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: ubuntu-latest
|
||||
name: Get block height
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
2
.github/workflows/get_backend_hash.yml
vendored
2
.github/workflows/get_backend_hash.yml
vendored
@ -4,7 +4,7 @@ on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
print-backend-sha:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: ubuntu-latest
|
||||
name: Print backend hashes
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
2
.github/workflows/get_image_digest.yml
vendored
2
.github/workflows/get_image_digest.yml
vendored
@ -10,7 +10,7 @@ on:
|
||||
type: string
|
||||
jobs:
|
||||
print-images-sha:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: ubuntu-latest
|
||||
name: Print digest for images
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
2
LICENSE
2
LICENSE
@ -10,7 +10,7 @@ However, this copyright license does not include an implied right or license
|
||||
to use any trademarks, service marks, logos, or trade names of Mempool Space K.K.
|
||||
or any other contributor to The Mempool Open Source Project.
|
||||
|
||||
The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®,
|
||||
The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®,
|
||||
Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full
|
||||
Bitcoin ecosystem™, Mempool Goggles™, the mempool Logo, the mempool Square Logo,
|
||||
the mempool block visualization Logo, the mempool Blocks Logo, the mempool
|
||||
|
@ -406,8 +406,8 @@ class BitcoinRoutes {
|
||||
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * cacheDuration).toUTCString());
|
||||
res.json(block);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block');
|
||||
} catch (e: any) {
|
||||
handleError(req, res, e?.response?.status === 404 ? 404 : 500, 'Failed to get block');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1469,11 +1469,11 @@ class Blocks {
|
||||
if (rows && Array.isArray(rows)) {
|
||||
return rows.map(r => r.definition_hash);
|
||||
} else {
|
||||
logger.debug(`Unable to retreive list of blocks.definition_hash from db (no result)`);
|
||||
logger.debug(`Unable to retrieve list of blocks.definition_hash from db (no result)`);
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.debug(`Unable to retreive list of blocks.definition_hash from db (exception: ${e})`);
|
||||
logger.debug(`Unable to retrieve list of blocks.definition_hash from db (exception: ${e})`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1484,11 +1484,11 @@ class Blocks {
|
||||
if (rows && Array.isArray(rows)) {
|
||||
return rows.map(r => r.hash);
|
||||
} else {
|
||||
logger.debug(`Unable to retreive list of blocks for definition hash ${definitionHash} from db (no result)`);
|
||||
logger.debug(`Unable to retrieve list of blocks for definition hash ${definitionHash} from db (no result)`);
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.debug(`Unable to retreive list of blocks for definition hash ${definitionHash} from db (exception: ${e})`);
|
||||
logger.debug(`Unable to retrieve list of blocks for definition hash ${definitionHash} from db (exception: ${e})`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const POLL_FREQUENCY = 5 * 60 * 1000; // 5 minutes
|
||||
class WalletApi {
|
||||
private wallets: Record<string, Wallet> = {};
|
||||
private syncing = false;
|
||||
private lastSync = 0;
|
||||
|
||||
constructor() {
|
||||
this.wallets = config.WALLETS.ENABLED ? (config.WALLETS.WALLETS as string[]).reduce((acc, wallet) => {
|
||||
@ -47,7 +48,38 @@ class WalletApi {
|
||||
if (!config.WALLETS.ENABLED || this.syncing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncing = true;
|
||||
|
||||
if (config.WALLETS.AUTO && (Date.now() - this.lastSync) > POLL_FREQUENCY) {
|
||||
try {
|
||||
// update list of active wallets
|
||||
this.lastSync = Date.now();
|
||||
const response = await axios.get(config.MEMPOOL_SERVICES.API + `/wallets`);
|
||||
const walletList: string[] = response.data;
|
||||
if (walletList) {
|
||||
// create a quick lookup dictionary of active wallets
|
||||
const newWallets: Record<string, boolean> = Object.fromEntries(
|
||||
walletList.map(wallet => [wallet, true])
|
||||
);
|
||||
for (const wallet of walletList) {
|
||||
// don't overwrite existing wallets
|
||||
if (!this.wallets[wallet]) {
|
||||
this.wallets[wallet] = { name: wallet, addresses: {}, lastPoll: 0 };
|
||||
}
|
||||
}
|
||||
// remove wallets that are no longer active
|
||||
for (const wallet of Object.keys(this.wallets)) {
|
||||
if (!newWallets[wallet]) {
|
||||
delete this.wallets[wallet];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Error updating active wallets: ${(e instanceof Error ? e.message : e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const walletKey of Object.keys(this.wallets)) {
|
||||
const wallet = this.wallets[walletKey];
|
||||
if (wallet.lastPoll < (Date.now() - POLL_FREQUENCY)) {
|
||||
@ -72,6 +104,7 @@ class WalletApi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.syncing = false;
|
||||
}
|
||||
|
||||
|
@ -164,6 +164,7 @@ interface IConfig {
|
||||
},
|
||||
WALLETS: {
|
||||
ENABLED: boolean;
|
||||
AUTO: boolean;
|
||||
WALLETS: string[];
|
||||
},
|
||||
STRATUM: {
|
||||
@ -334,6 +335,7 @@ const defaults: IConfig = {
|
||||
},
|
||||
'WALLETS': {
|
||||
'ENABLED': false,
|
||||
'AUTO': false,
|
||||
'WALLETS': [],
|
||||
},
|
||||
'STRATUM': {
|
||||
|
@ -131,6 +131,9 @@ class Server {
|
||||
this.app
|
||||
.use((req: Request, res: Response, next: NextFunction) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With');
|
||||
res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count,X-Mempool-Auth');
|
||||
next();
|
||||
})
|
||||
.use(express.urlencoded({ extended: true }))
|
||||
|
@ -93,7 +93,7 @@ class HashratesRepository {
|
||||
const [rows]: any[] = await DB.query(query);
|
||||
return rows.map(row => row.timestamp);
|
||||
} catch (e) {
|
||||
logger.err('Cannot retreive indexed weekly hashrate timestamps. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
logger.err('Cannot retrieve indexed weekly hashrate timestamps. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -261,20 +261,14 @@
|
||||
"proxyConfig": "proxy.conf.mixed.js",
|
||||
"verbose": true
|
||||
},
|
||||
"staging": {
|
||||
"proxyConfig": "proxy.conf.js",
|
||||
"disableHostCheck": true,
|
||||
"host": "0.0.0.0",
|
||||
"verbose": true
|
||||
},
|
||||
"local-prod": {
|
||||
"proxyConfig": "proxy.conf.js",
|
||||
"disableHostCheck": true,
|
||||
"host": "0.0.0.0",
|
||||
"verbose": false
|
||||
},
|
||||
"local-staging": {
|
||||
"proxyConfig": "proxy.conf.staging.js",
|
||||
"parameterized": {
|
||||
"proxyConfig": "proxy.conf.parameterized.js",
|
||||
"disableHostCheck": true,
|
||||
"host": "0.0.0.0",
|
||||
"verbose": false
|
||||
@ -371,4 +365,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,10 +16,10 @@
|
||||
"mobileOrder": 4
|
||||
},
|
||||
{
|
||||
"component": "balance",
|
||||
"component": "walletBalance",
|
||||
"mobileOrder": 1,
|
||||
"props": {
|
||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
||||
"wallet": "ONBTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,21 +30,22 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"component": "address",
|
||||
"component": "wallet",
|
||||
"mobileOrder": 2,
|
||||
"props": {
|
||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo",
|
||||
"period": "1m"
|
||||
"wallet": "ONBTC",
|
||||
"period": "1m",
|
||||
"label": "bitcoin.gob.sv"
|
||||
}
|
||||
},
|
||||
{
|
||||
"component": "blocks"
|
||||
},
|
||||
{
|
||||
"component": "addressTransactions",
|
||||
"component": "walletTransactions",
|
||||
"mobileOrder": 3,
|
||||
"props": {
|
||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
||||
"wallet": "ONBTC"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -57,11 +57,6 @@ describe('Liquid', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv page - desktop', () => {
|
||||
cy.visit(`${basePath}/tv`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the graphs page - mobile', () => {
|
||||
cy.visit(`${basePath}`)
|
||||
cy.waitForSkeletonGone();
|
||||
|
@ -57,11 +57,6 @@ describe('Liquid Testnet', () => {
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the tv page - desktop', () => {
|
||||
cy.visit(`${basePath}/tv`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the graphs page - mobile', () => {
|
||||
cy.visit(`${basePath}`)
|
||||
cy.waitForSkeletonGone();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { emitMempoolInfo, dropWebSocket } from '../../support/websocket';
|
||||
import { emitMempoolInfo, dropWebSocket, receiveWebSocketMessageFromServer } from '../../support/websocket';
|
||||
|
||||
const baseModule = Cypress.env('BASE_MODULE');
|
||||
|
||||
@ -216,6 +216,69 @@ describe('Mainnet', () => {
|
||||
cy.get('[data-cy="tx-1"] .table-tx-vout .highlight').its('length').should('equal', 2);
|
||||
cy.get('[data-cy="tx-1"] .table-tx-vout .highlight').invoke('text').should('contain', `${address}`);
|
||||
});
|
||||
|
||||
describe('address poisoning', () => {
|
||||
it('highlights potential address poisoning attacks on outputs, prefix and infix', () => {
|
||||
const txid = '152a5dea805f95d6f83e50a9fd082630f542a52a076ebabdb295723eaf53fa30';
|
||||
const prefix = '1DonatePLease';
|
||||
const infix1 = 'SenderAddressXVXCmAY';
|
||||
const infix2 = '5btcToSenderXXWBoKhB';
|
||||
|
||||
cy.visit(`/tx/${txid}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.alert-mempool').should('exist');
|
||||
cy.get('.poison-alert').its('length').should('equal', 2);
|
||||
|
||||
cy.get('.prefix')
|
||||
.should('have.length', 2)
|
||||
.each(($el) => {
|
||||
cy.wrap($el).should('have.text', prefix);
|
||||
});
|
||||
|
||||
cy.get('.infix')
|
||||
.should('have.length', 2)
|
||||
.then(($infixes) => {
|
||||
cy.wrap($infixes[0]).should('have.text', infix1);
|
||||
cy.wrap($infixes[1]).should('have.text', infix2);
|
||||
});
|
||||
});
|
||||
|
||||
it('highlights potential address poisoning attacks on inputs and outputs, prefix, infix and postfix', () => {
|
||||
const txid = '44544516084ea916ff1eb69c675c693e252addbbaf77102ffff86e3979ac6132';
|
||||
const prefix = 'bc1qge8';
|
||||
const infix1 = '6gqjnk8aqs3nvv7ejrvcd4zq6qur3';
|
||||
const infix2 = 'xyxjex6zzzx5g8hh65vsel4e548p2';
|
||||
const postfix1 = '6p6e3r';
|
||||
const postfix2 = '6p6e3r';
|
||||
|
||||
cy.visit(`/tx/${txid}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.alert-mempool').should('exist');
|
||||
cy.get('.poison-alert').its('length').should('equal', 2);
|
||||
|
||||
cy.get('.prefix')
|
||||
.should('have.length', 2)
|
||||
.each(($el) => {
|
||||
cy.wrap($el).should('have.text', prefix);
|
||||
});
|
||||
|
||||
cy.get('.infix')
|
||||
.should('have.length', 2)
|
||||
.then(($infixes) => {
|
||||
cy.wrap($infixes[0]).should('have.text', infix1);
|
||||
cy.wrap($infixes[1]).should('have.text', infix2);
|
||||
});
|
||||
|
||||
cy.get('.postfix')
|
||||
.should('have.length', 2)
|
||||
.then(($postfixes) => {
|
||||
cy.wrap($postfixes[0]).should('include.text', postfix1);
|
||||
cy.wrap($postfixes[1]).should('include.text', postfix2);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('blocks navigation', () => {
|
||||
@ -397,6 +460,7 @@ describe('Mainnet', () => {
|
||||
cy.get('#dropdownFees').should('be.visible');
|
||||
cy.get('.btn-group').should('be.visible');
|
||||
});
|
||||
|
||||
it('check buttons - tablet', () => {
|
||||
cy.viewport('ipad-2');
|
||||
cy.visit('/graphs');
|
||||
@ -405,6 +469,7 @@ describe('Mainnet', () => {
|
||||
cy.get('#dropdownFees').should('be.visible');
|
||||
cy.get('.btn-group').should('be.visible');
|
||||
});
|
||||
|
||||
it('check buttons - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/graphs');
|
||||
@ -415,26 +480,6 @@ describe('Mainnet', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/graphs/mempool');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#btn-tv').click().then(() => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.get('.chart-holder');
|
||||
cy.get('.blockchain-wrapper').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.visit('/tv');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.chart-holder');
|
||||
cy.get('.blockchain-wrapper').should('not.visible');
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
@ -516,7 +561,44 @@ describe('Mainnet', () => {
|
||||
});
|
||||
|
||||
describe('RBF transactions', () => {
|
||||
it('shows RBF transactions properly (mobile)', () => {
|
||||
it('RBF page gets updated over websockets', () => {
|
||||
cy.intercept('/api/v1/replacements', {
|
||||
statusCode: 200,
|
||||
body: []
|
||||
});
|
||||
|
||||
cy.intercept('/api/v1/fullrbf/replacements', {
|
||||
statusCode: 200,
|
||||
body: []
|
||||
});
|
||||
|
||||
cy.mockMempoolSocketV2();
|
||||
|
||||
cy.visit('/rbf');
|
||||
cy.get('.no-replacements');
|
||||
cy.get('.tree').should('have.length', 0);
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'rbf_page/rbf_01.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('.tree').should('have.length', 1);
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'rbf_page/rbf_02.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
cy.get('.tree').should('have.length', 2);
|
||||
});
|
||||
|
||||
it('shows RBF transactions properly (mobile - details)', () => {
|
||||
cy.intercept('/api/v1/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f/cached', {
|
||||
fixture: 'mainnet_tx_cached.json'
|
||||
}).as('cached_tx');
|
||||
@ -527,7 +609,7 @@ describe('Mainnet', () => {
|
||||
|
||||
cy.viewport('iphone-xr');
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit('/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f');
|
||||
cy.visit('/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f?mode=details');
|
||||
|
||||
cy.waitForSkeletonGone();
|
||||
|
||||
@ -545,7 +627,120 @@ describe('Mainnet', () => {
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('.alert-mempool').should('be.visible');
|
||||
});
|
||||
|
||||
it('shows RBF transactions properly (mobile - tracker)', () => {
|
||||
cy.mockMempoolSocketV2();
|
||||
cy.viewport('iphone-xr');
|
||||
|
||||
// API Mocks
|
||||
cy.intercept('/api/v1/mining/pools/1w', {
|
||||
fixture: 'details_rbf/api_mining_pools_1w.json'
|
||||
}).as('api_mining_1w');
|
||||
|
||||
cy.intercept('/api/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29', {
|
||||
statusCode: 404,
|
||||
body: 'Transaction not found'
|
||||
}).as('api_tx01_404');
|
||||
|
||||
cy.intercept('/api/v1/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29/cached', {
|
||||
fixture: 'details_rbf/tx01_api_cached.json'
|
||||
}).as('api_tx01_cached');
|
||||
|
||||
cy.intercept('/api/v1/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29/rbf', {
|
||||
fixture: 'details_rbf/tx01_api_rbf.json'
|
||||
}).as('api_tx01_rbf');
|
||||
|
||||
cy.visit('/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29?mode=tracker');
|
||||
cy.wait('@api_tx01_rbf');
|
||||
|
||||
// Start sending mocked WS messages
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'details_rbf/tx01_ws_stratum_jobs.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'details_rbf/tx01_ws_blocks_01.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'details_rbf/tx01_ws_tx_replaced.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'details_rbf/tx01_ws_mempool_blocks_01.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('.alert-replaced').should('be.visible');
|
||||
cy.get('.explainer').should('be.visible');
|
||||
cy.get('svg[data-icon=timeline]').should('be.visible');
|
||||
|
||||
// Second TX setup
|
||||
cy.intercept('/api/tx/b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698', {
|
||||
fixture: 'details_rbf/tx02_api_tx.json'
|
||||
}).as('tx02_api');
|
||||
|
||||
cy.intercept('/api/v1/transaction-times?txId%5B%5D=b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698', {
|
||||
fixture: 'details_rbf/tx02_api_tx_times.json'
|
||||
}).as('tx02_api_tx_times');
|
||||
|
||||
cy.intercept('/api/v1/tx/b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698/rbf', {
|
||||
fixture: 'details_rbf/tx02_api_rbf.json'
|
||||
}).as('tx02_api_rbf');
|
||||
|
||||
cy.intercept('/api/v1/cpfp/b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698', {
|
||||
fixture: 'details_rbf/tx02_api_cpfp.json'
|
||||
}).as('tx02_api_cpfp');
|
||||
|
||||
// Go to the replacement tx
|
||||
cy.get('.alert-replaced a').click();
|
||||
|
||||
cy.wait('@tx02_api_cpfp');
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'details_rbf/tx02_ws_tx_position.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'details_rbf/tx02_ws_mempool_blocks_01.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('svg[data-icon=hourglass-half]').should('be.visible');
|
||||
|
||||
receiveWebSocketMessageFromServer({
|
||||
params: {
|
||||
file: {
|
||||
path: 'details_rbf/tx02_ws_block.json'
|
||||
}
|
||||
}
|
||||
});
|
||||
cy.get('app-confirmations');
|
||||
cy.get('svg[data-icon=circle-check]').should('be.visible');
|
||||
});
|
||||
|
||||
it('shows RBF transactions properly (desktop)', () => {
|
||||
|
@ -60,30 +60,6 @@ describe('Signet', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('tv mode', () => {
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/signet/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#btn-tv').click().then(() => {
|
||||
cy.get('.chart-holder').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.visit('/signet/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#btn-tv').click().then(() => {
|
||||
cy.viewport('iphone-8');
|
||||
cy.get('.chart-holder').should('be.visible');
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
|
@ -60,30 +60,6 @@ describe('Testnet4', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('tv mode', () => {
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/testnet4/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#btn-tv').click().then(() => {
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.visit('/testnet4/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#btn-tv').click().then(() => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/testnet4');
|
||||
cy.waitForSkeletonGone();
|
||||
|
@ -0,0 +1,60 @@
|
||||
{
|
||||
"txSummary": {
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698",
|
||||
"effectiveVsize": 224,
|
||||
"effectiveFee": 960,
|
||||
"ancestorCount": 1
|
||||
},
|
||||
"cost": 1000,
|
||||
"targetFeeRate": 3,
|
||||
"nextBlockFee": 672,
|
||||
"userBalance": 0,
|
||||
"mempoolBaseFee": 50000,
|
||||
"vsizeFee": 0,
|
||||
"pools": [
|
||||
36,
|
||||
102,
|
||||
112,
|
||||
44,
|
||||
4,
|
||||
2,
|
||||
6,
|
||||
94,
|
||||
143,
|
||||
43,
|
||||
105,
|
||||
115,
|
||||
142,
|
||||
111
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"fee": 1000
|
||||
},
|
||||
{
|
||||
"fee": 2000
|
||||
},
|
||||
{
|
||||
"fee": 10000
|
||||
}
|
||||
],
|
||||
"hasAccess": false,
|
||||
"availablePaymentMethods": {
|
||||
"bitcoin": {
|
||||
"enabled": true,
|
||||
"min": 1000,
|
||||
"max": 10000000
|
||||
},
|
||||
"applePay": {
|
||||
"enabled": true,
|
||||
"min": 10,
|
||||
"max": 1000
|
||||
},
|
||||
"googlePay": {
|
||||
"enabled": true,
|
||||
"min": 10,
|
||||
"max": 1000
|
||||
}
|
||||
},
|
||||
"unavailable": false
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"gitCommit": "62f80296"
|
||||
}
|
@ -0,0 +1 @@
|
||||
[]
|
260
frontend/cypress/fixtures/details_rbf/api_mining_pools_1w.json
Normal file
260
frontend/cypress/fixtures/details_rbf/api_mining_pools_1w.json
Normal file
@ -0,0 +1,260 @@
|
||||
{
|
||||
"pools": [
|
||||
{
|
||||
"poolId": 112,
|
||||
"name": "Foundry USA",
|
||||
"link": "https://foundrydigital.com",
|
||||
"blockCount": 323,
|
||||
"rank": 1,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "foundryusa",
|
||||
"avgMatchRate": 99.96,
|
||||
"avgFeeDelta": "-0.01971455",
|
||||
"poolUniqueId": 111
|
||||
},
|
||||
{
|
||||
"poolId": 45,
|
||||
"name": "AntPool",
|
||||
"link": "https://www.antpool.com",
|
||||
"blockCount": 171,
|
||||
"rank": 2,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "antpool",
|
||||
"avgMatchRate": 99.99,
|
||||
"avgFeeDelta": "-0.04227368",
|
||||
"poolUniqueId": 44
|
||||
},
|
||||
{
|
||||
"poolId": 74,
|
||||
"name": "ViaBTC",
|
||||
"link": "https://viabtc.com",
|
||||
"blockCount": 166,
|
||||
"rank": 3,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "viabtc",
|
||||
"avgMatchRate": 99.99,
|
||||
"avgFeeDelta": "-0.02530964",
|
||||
"poolUniqueId": 73
|
||||
},
|
||||
{
|
||||
"poolId": 37,
|
||||
"name": "F2Pool",
|
||||
"link": "https://www.f2pool.com",
|
||||
"blockCount": 104,
|
||||
"rank": 4,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "f2pool",
|
||||
"avgMatchRate": 99.99,
|
||||
"avgFeeDelta": "-0.03299327",
|
||||
"poolUniqueId": 36
|
||||
},
|
||||
{
|
||||
"poolId": 116,
|
||||
"name": "MARA Pool",
|
||||
"link": "https://marapool.com",
|
||||
"blockCount": 66,
|
||||
"rank": 5,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "marapool",
|
||||
"avgMatchRate": 99.97,
|
||||
"avgFeeDelta": "0.02366061",
|
||||
"poolUniqueId": 115
|
||||
},
|
||||
{
|
||||
"poolId": 103,
|
||||
"name": "SpiderPool",
|
||||
"link": "https://www.spiderpool.com",
|
||||
"blockCount": 46,
|
||||
"rank": 6,
|
||||
"emptyBlocks": 1,
|
||||
"slug": "spiderpool",
|
||||
"avgMatchRate": 97.82,
|
||||
"avgFeeDelta": "-0.07258913",
|
||||
"poolUniqueId": 102
|
||||
},
|
||||
{
|
||||
"poolId": 142,
|
||||
"name": "SECPOOL",
|
||||
"link": "https://www.secpool.com",
|
||||
"blockCount": 30,
|
||||
"rank": 7,
|
||||
"emptyBlocks": 1,
|
||||
"slug": "secpool",
|
||||
"avgMatchRate": 96.67,
|
||||
"avgFeeDelta": "-0.06596000",
|
||||
"poolUniqueId": 141
|
||||
},
|
||||
{
|
||||
"poolId": 106,
|
||||
"name": "Binance Pool",
|
||||
"link": "https://pool.binance.com",
|
||||
"blockCount": 28,
|
||||
"rank": 8,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "binancepool",
|
||||
"avgMatchRate": 99.99,
|
||||
"avgFeeDelta": "-0.05834286",
|
||||
"poolUniqueId": 105
|
||||
},
|
||||
{
|
||||
"poolId": 5,
|
||||
"name": "Luxor",
|
||||
"link": "https://mining.luxor.tech",
|
||||
"blockCount": 28,
|
||||
"rank": 9,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "luxor",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.05496071",
|
||||
"poolUniqueId": 4
|
||||
},
|
||||
{
|
||||
"poolId": 143,
|
||||
"name": "OCEAN",
|
||||
"link": "https://ocean.xyz/",
|
||||
"blockCount": 12,
|
||||
"rank": 10,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "ocean",
|
||||
"avgMatchRate": 91.9,
|
||||
"avgFeeDelta": "-0.14650833",
|
||||
"poolUniqueId": 142
|
||||
},
|
||||
{
|
||||
"poolId": 44,
|
||||
"name": "Braiins Pool",
|
||||
"link": "https://braiins.com/pool",
|
||||
"blockCount": 12,
|
||||
"rank": 11,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "braiinspool",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.03553333",
|
||||
"poolUniqueId": 43
|
||||
},
|
||||
{
|
||||
"poolId": 113,
|
||||
"name": "SBI Crypto",
|
||||
"link": "https://sbicrypto.com",
|
||||
"blockCount": 8,
|
||||
"rank": 12,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "sbicrypto",
|
||||
"avgMatchRate": 98.65,
|
||||
"avgFeeDelta": "-0.04246250",
|
||||
"poolUniqueId": 112
|
||||
},
|
||||
{
|
||||
"poolId": 152,
|
||||
"name": "Carbon Negative",
|
||||
"link": "https://github.com/bitcoin-data/mining-pools/issues/48",
|
||||
"blockCount": 7,
|
||||
"rank": 13,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "carbonnegative",
|
||||
"avgMatchRate": 99.75,
|
||||
"avgFeeDelta": "-0.04407143",
|
||||
"poolUniqueId": 151
|
||||
},
|
||||
{
|
||||
"poolId": 7,
|
||||
"name": "BTC.com",
|
||||
"link": "https://pool.btc.com",
|
||||
"blockCount": 5,
|
||||
"rank": 14,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "btccom",
|
||||
"avgMatchRate": 99.98,
|
||||
"avgFeeDelta": "-0.02496000",
|
||||
"poolUniqueId": 6
|
||||
},
|
||||
{
|
||||
"poolId": 162,
|
||||
"name": "Mining Squared",
|
||||
"link": "https://pool.bsquared.network/",
|
||||
"blockCount": 4,
|
||||
"rank": 15,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "miningsquared",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.00915000",
|
||||
"poolUniqueId": 161
|
||||
},
|
||||
{
|
||||
"poolId": 95,
|
||||
"name": "Poolin",
|
||||
"link": "https://www.poolin.com",
|
||||
"blockCount": 4,
|
||||
"rank": 16,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "poolin",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.26485000",
|
||||
"poolUniqueId": 94
|
||||
},
|
||||
{
|
||||
"poolId": 1,
|
||||
"name": "Unknown",
|
||||
"link": "https://learnmeabitcoin.com/technical/coinbase-transaction",
|
||||
"blockCount": 4,
|
||||
"rank": 17,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "unknown",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.06490000",
|
||||
"poolUniqueId": 0
|
||||
},
|
||||
{
|
||||
"poolId": 144,
|
||||
"name": "WhitePool",
|
||||
"link": "https://whitebit.com/mining-pool",
|
||||
"blockCount": 3,
|
||||
"rank": 18,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "whitepool",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.01293333",
|
||||
"poolUniqueId": 143
|
||||
},
|
||||
{
|
||||
"poolId": 3,
|
||||
"name": "ULTIMUSPOOL",
|
||||
"link": "https://www.ultimuspool.com",
|
||||
"blockCount": 1,
|
||||
"rank": 19,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "ultimuspool",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.16130000",
|
||||
"poolUniqueId": 2
|
||||
},
|
||||
{
|
||||
"poolId": 50,
|
||||
"name": "Solo CK",
|
||||
"link": "https://solo.ckpool.org",
|
||||
"blockCount": 1,
|
||||
"rank": 20,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "solock",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.01510000",
|
||||
"poolUniqueId": 49
|
||||
},
|
||||
{
|
||||
"poolId": 158,
|
||||
"name": "BitFuFuPool",
|
||||
"link": "https://www.bitfufu.com/pool",
|
||||
"blockCount": 1,
|
||||
"rank": 21,
|
||||
"emptyBlocks": 0,
|
||||
"slug": "bitfufupool",
|
||||
"avgMatchRate": 100,
|
||||
"avgFeeDelta": "-0.01630000",
|
||||
"poolUniqueId": 157
|
||||
}
|
||||
],
|
||||
"blockCount": 1024,
|
||||
"lastEstimatedHashrate": 786391245138648900000,
|
||||
"lastEstimatedHashrate3d": 797683179385121300000,
|
||||
"lastEstimatedHashrate1w": 827836055441520300000
|
||||
}
|
55
frontend/cypress/fixtures/details_rbf/tx01_api_cached.json
Normal file
55
frontend/cypress/fixtures/details_rbf/tx01_api_cached.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"txid": "242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29",
|
||||
"version": 2,
|
||||
"locktime": 0,
|
||||
"vin": [
|
||||
{
|
||||
"txid": "fb16c141d22a3a7af5e44e7478a8663866c35d554cd85107ec8a99b97e5a72e9",
|
||||
"vout": 0,
|
||||
"prevout": {
|
||||
"scriptpubkey": "76a914099f831c49289c7b9cc07a9a632867ecd51a105a88ac",
|
||||
"scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 099f831c49289c7b9cc07a9a632867ecd51a105a OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"scriptpubkey_type": "p2pkh",
|
||||
"scriptpubkey_address": "1stAprEZapjCYGUACUcXohQqSHF9MU5Kj",
|
||||
"value": 50000
|
||||
},
|
||||
"scriptsig": "483045022100f44a50e894c30b28400933da120b872ff15bd2e66dd034ffbbb925fd054dd60f02206ba477a9da3fae68a9f420f53bc25493270bd987d13b75fef39cf514aea9cdb3014104ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df",
|
||||
"scriptsig_asm": "OP_PUSHBYTES_72 3045022100f44a50e894c30b28400933da120b872ff15bd2e66dd034ffbbb925fd054dd60f02206ba477a9da3fae68a9f420f53bc25493270bd987d13b75fef39cf514aea9cdb301 OP_PUSHBYTES_65 04ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df",
|
||||
"is_coinbase": false,
|
||||
"sequence": 4294967293
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"scriptpubkey": "5120a2fc5cb445755389eed295ab9d594b0facd3e00840a3e477fa7c025412c53795",
|
||||
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 a2fc5cb445755389eed295ab9d594b0facd3e00840a3e477fa7c025412c53795",
|
||||
"scriptpubkey_type": "v1_p2tr",
|
||||
"scriptpubkey_address": "bc1p5t79edz9w4fcnmkjjk4e6k2tp7kd8cqggz37gal60sp9gyk9x72sk4mk0f",
|
||||
"value": 49394
|
||||
}
|
||||
],
|
||||
"size": 233,
|
||||
"weight": 932,
|
||||
"sigops": 0,
|
||||
"fee": 606,
|
||||
"status": {
|
||||
"confirmed": false
|
||||
},
|
||||
"order": 701313494,
|
||||
"vsize": 233,
|
||||
"adjustedVsize": 233,
|
||||
"feePerVsize": 2.6008583690987126,
|
||||
"adjustedFeePerVsize": 2.6008583690987126,
|
||||
"effectiveFeePerVsize": 2.6008583690987126,
|
||||
"firstSeen": 1743541407,
|
||||
"inputs": [],
|
||||
"cpfpDirty": false,
|
||||
"ancestors": [],
|
||||
"descendants": [],
|
||||
"bestDescendant": null,
|
||||
"position": {
|
||||
"block": 0,
|
||||
"vsize": 318595.5
|
||||
},
|
||||
"flags": 1099511645193
|
||||
}
|
34
frontend/cypress/fixtures/details_rbf/tx01_api_rbf.json
Normal file
34
frontend/cypress/fixtures/details_rbf/tx01_api_rbf.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"replacements": {
|
||||
"tx": {
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698",
|
||||
"fee": 960,
|
||||
"vsize": 224,
|
||||
"value": 49040,
|
||||
"rate": 4.285714285714286,
|
||||
"time": 1743541726,
|
||||
"rbf": true,
|
||||
"fullRbf": false
|
||||
},
|
||||
"time": 1743541726,
|
||||
"fullRbf": false,
|
||||
"replaces": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29",
|
||||
"fee": 606,
|
||||
"vsize": 233,
|
||||
"value": 49394,
|
||||
"rate": 2.6008583690987126,
|
||||
"time": 1743541407,
|
||||
"rbf": true
|
||||
},
|
||||
"time": 1743541407,
|
||||
"interval": 319,
|
||||
"fullRbf": false,
|
||||
"replaces": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"replaces": null
|
||||
}
|
579
frontend/cypress/fixtures/details_rbf/tx01_ws_blocks_01.json
Normal file
579
frontend/cypress/fixtures/details_rbf/tx01_ws_blocks_01.json
Normal file
@ -0,0 +1,579 @@
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"id": "0000000000000000000079f5b74b6533abb0b1ece06570d8d157b5bebd1460b4",
|
||||
"height": 890440,
|
||||
"version": 559235072,
|
||||
"timestamp": 1743535677,
|
||||
"bits": 386038124,
|
||||
"nonce": 2920325684,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "c793d5fdbfb1ebe99e14a13a6d65370057d311774d33c71da166663b18722474",
|
||||
"tx_count": 3823,
|
||||
"size": 1578209,
|
||||
"weight": 3993461,
|
||||
"previousblockhash": "000000000000000000020fb2e24425793e17e60e188205dc1694d221790348b2",
|
||||
"mediantime": 1743532406,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 319838750,
|
||||
"coinbaseRaw": "0348960d082f5669614254432f2cfabe6d6d294719da11c017243828bf32c405341db7f19387fee92c25413c45e114907f9810000000000000001058bf9601429f9fa7a6c160d10d00000000000000",
|
||||
"orphans": [],
|
||||
"medianFee": 4,
|
||||
"feeRange": [
|
||||
3,
|
||||
3,
|
||||
3.0191082802547773,
|
||||
3.980952380952381,
|
||||
5,
|
||||
10,
|
||||
427.748502994012
|
||||
],
|
||||
"totalFees": 7338750,
|
||||
"avgFee": 1920,
|
||||
"avgFeeRate": 7,
|
||||
"utxoSetChange": 4093,
|
||||
"avgTxSize": 412.71000000000004,
|
||||
"totalInputs": 7430,
|
||||
"totalOutputs": 11523,
|
||||
"totalOutputAmt": 547553568373,
|
||||
"segwitTotalTxs": 3432,
|
||||
"segwitTotalSize": 1467920,
|
||||
"segwitTotalWeight": 3552413,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998365.25,
|
||||
"coinbaseAddress": "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4",
|
||||
"coinbaseAddresses": [
|
||||
"1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4"
|
||||
],
|
||||
"coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 fb37342f6275b13936799def06f2eb4c0f201515 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"coinbaseSignatureAscii": "\u0003H\r\b/ViaBTC/,ú¾mm)G\u0019Ú\u0011À\u0017$8(¿2Ä\u00054\u001d·ñþé,%A<Eá\u0014\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0010X¿\u0001B§¦Á`Ñ\r\u0000\u0000\u0000\u0000\u0000\u0000\u0000",
|
||||
"header": "00405521b248037921d29416dc0582180ee6173e792544e2b20f02000000000000000000742472183b6666a11dc7334d7711d3570037656d3aa1149ee9ebb1bffdd593c73d3eec676c79021734a210ae",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 73,
|
||||
"name": "ViaBTC",
|
||||
"slug": "viabtc",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 7342711,
|
||||
"expectedWeight": 3991920,
|
||||
"similarity": 0.9978345275528634
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "00000000000000000000ef5a27459785ea4c91e05f64adfad306af6dfc0cd19c",
|
||||
"height": 890441,
|
||||
"version": 540188672,
|
||||
"timestamp": 1743535803,
|
||||
"bits": 386038124,
|
||||
"nonce": 3418993591,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "25457e2f9e9b55cacde6166acc58ebc2367007a7af5d9b39f46dc1ce060fd63e",
|
||||
"tx_count": 2813,
|
||||
"size": 1671284,
|
||||
"weight": 3993398,
|
||||
"previousblockhash": "0000000000000000000079f5b74b6533abb0b1ece06570d8d157b5bebd1460b4",
|
||||
"mediantime": 1743532471,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 315271317,
|
||||
"coinbaseRaw": "0349960d04bb3eec672f466f756e6472792055534120506f6f6c202364726f70676f6c642f01530a4561e7020000000000",
|
||||
"orphans": [],
|
||||
"medianFee": 2.0116279069767438,
|
||||
"feeRange": [
|
||||
1.0567375886524824,
|
||||
1.1917098445595855,
|
||||
1.9007678676904902,
|
||||
2.1298076923076925,
|
||||
2.825034262220192,
|
||||
3.175202156334232,
|
||||
121
|
||||
],
|
||||
"totalFees": 2771317,
|
||||
"avgFee": 985,
|
||||
"avgFeeRate": 2,
|
||||
"utxoSetChange": 37,
|
||||
"avgTxSize": 593.97,
|
||||
"totalInputs": 8679,
|
||||
"totalOutputs": 8716,
|
||||
"totalOutputAmt": 299780221694,
|
||||
"segwitTotalTxs": 2379,
|
||||
"segwitTotalSize": 1535232,
|
||||
"segwitTotalWeight": 3449298,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998349.5,
|
||||
"coinbaseAddress": "bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63",
|
||||
"coinbaseAddresses": [
|
||||
"bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63",
|
||||
"bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38"
|
||||
],
|
||||
"coinbaseSignature": "OP_PUSHNUM_1 OP_PUSHBYTES_32 0f9dab1a72f7c48da8a1df2f913bef649bfc0d77072dffd11329b8048293d7a3",
|
||||
"coinbaseSignatureAscii": "\u0003I\r\u0004»>ìg/Foundry USA Pool #dropgold/\u0001S\nEaç\u0002\u0000\u0000\u0000\u0000\u0000",
|
||||
"header": "00a03220b46014bdbeb557d1d87065e0ecb1b0ab33654bb7f579000000000000000000003ed60f06cec16df4399b5dafa7077036c2eb58cc6a16e6cdca559b9e2f7e4525bb3eec676c790217b7b3c9cb",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 111,
|
||||
"name": "Foundry USA",
|
||||
"slug": "foundryusa",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 2792968,
|
||||
"expectedWeight": 3991959,
|
||||
"similarity": 0.9951416839808291
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "000000000000000000014845978a876b3d8bf5d489b9d87e88873952b06ddfce",
|
||||
"height": 890442,
|
||||
"version": 557981696,
|
||||
"timestamp": 1743536834,
|
||||
"bits": 386038124,
|
||||
"nonce": 470697326,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "5e92e681c1db2797a5b3e5016729059f8b60a256cafb51d835dac2b3964c0db4",
|
||||
"tx_count": 3566,
|
||||
"size": 1628328,
|
||||
"weight": 3993552,
|
||||
"previousblockhash": "00000000000000000000ef5a27459785ea4c91e05f64adfad306af6dfc0cd19c",
|
||||
"mediantime": 1743532867,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 318057766,
|
||||
"coinbaseRaw": "034a960d194d696e656420627920416e74506f6f6c204d000201e15e2989fabe6d6dd599e9dfa40be51f1517c8f512c5c3d51c7656182f1df335d34b98ee02c527db080000000000000000004f92b702000000000000",
|
||||
"orphans": [],
|
||||
"medianFee": 3.00860164711668,
|
||||
"feeRange": [
|
||||
1.5174418604651163,
|
||||
2.0140845070422535,
|
||||
2.492354740061162,
|
||||
3,
|
||||
4.020942408376963,
|
||||
7,
|
||||
200
|
||||
],
|
||||
"totalFees": 5557766,
|
||||
"avgFee": 1558,
|
||||
"avgFeeRate": 5,
|
||||
"utxoSetChange": 1971,
|
||||
"avgTxSize": 456.48,
|
||||
"totalInputs": 7938,
|
||||
"totalOutputs": 9909,
|
||||
"totalOutputAmt": 900044492230,
|
||||
"segwitTotalTxs": 3214,
|
||||
"segwitTotalSize": 1526463,
|
||||
"segwitTotalWeight": 3586200,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998388,
|
||||
"coinbaseAddress": "37jKPSmbEGwgfacCr2nayn1wTaqMAbA94Z",
|
||||
"coinbaseAddresses": [
|
||||
"37jKPSmbEGwgfacCr2nayn1wTaqMAbA94Z",
|
||||
"39C7fxSzEACPjM78Z7xdPxhf7mKxJwvfMJ"
|
||||
],
|
||||
"coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 42402a28dd61f2718a4b27ae72a4791d5bbdade7 OP_EQUAL",
|
||||
"coinbaseSignatureAscii": "\u0003J\r\u0019Mined by AntPool M\u0000\u0002\u0001á^)ú¾mmÕéߤ\u000bå\u001f\u0015\u0017Èõ\u0012ÅÃÕ\u001cvV\u0018/\u001dó5ÓKî\u0002Å'Û\b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000O·\u0002\u0000\u0000\u0000\u0000\u0000\u0000",
|
||||
"header": "002042219cd10cfc6daf06d3faad645fe0914cea859745275aef00000000000000000000b40d4c96b3c2da35d851fbca56a2608b9f05296701e5b3a59727dbc181e6925ec242ec676c7902176e450e1c",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 44,
|
||||
"name": "AntPool",
|
||||
"slug": "antpool",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 5764747,
|
||||
"expectedWeight": 3991786,
|
||||
"similarity": 0.9029319155137951
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "000000000000000000026e08b270834273511b353bed30d54706211adc96f5f6",
|
||||
"height": 890443,
|
||||
"version": 706666496,
|
||||
"timestamp": 1743537197,
|
||||
"bits": 386038124,
|
||||
"nonce": 321696065,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "3d7574f7eca741fa94b4690868a242e5b286f8a0417ad0275d4ab05893e96350",
|
||||
"tx_count": 2155,
|
||||
"size": 1700002,
|
||||
"weight": 3993715,
|
||||
"previousblockhash": "000000000000000000014845978a876b3d8bf5d489b9d87e88873952b06ddfce",
|
||||
"mediantime": 1743533789,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 315112344,
|
||||
"coinbaseRaw": "034b960d21202020204d696e656420627920536563706f6f6c2020202070000b05e388958c01fabe6d6db7ae4bfa7b1294e16e800b4563f1f5ddeb5c0740319eba45600f3f05d2d7272910000000000000000000c2cb7e020000",
|
||||
"orphans": [],
|
||||
"medianFee": 1.4360674424569184,
|
||||
"feeRange": [
|
||||
1,
|
||||
1.0135135135135136,
|
||||
1.09717868338558,
|
||||
2.142857142857143,
|
||||
3.009584664536741,
|
||||
4.831858407079646,
|
||||
196.07843137254903
|
||||
],
|
||||
"totalFees": 2612344,
|
||||
"avgFee": 1212,
|
||||
"avgFeeRate": 2,
|
||||
"utxoSetChange": -2880,
|
||||
"avgTxSize": 788.64,
|
||||
"totalInputs": 9773,
|
||||
"totalOutputs": 6893,
|
||||
"totalOutputAmt": 264603969671,
|
||||
"segwitTotalTxs": 1933,
|
||||
"segwitTotalSize": 1556223,
|
||||
"segwitTotalWeight": 3418707,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998428.75,
|
||||
"coinbaseAddress": "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9",
|
||||
"coinbaseAddresses": [
|
||||
"3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9",
|
||||
"3Awm3FNpmwrbvAFVThRUFqgpbVuqWisni9"
|
||||
],
|
||||
"coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 8ee90177614ecde53314fd67c46162f315852a07 OP_EQUAL",
|
||||
"coinbaseSignatureAscii": "\u0003K\r! Mined by Secpool p\u0000\u000b\u0005ã\u0001ú¾mm·®Kú{\u0012án\u000bEcñõÝë\\\u0007@1ºE`\u000f?\u0005Ò×')\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000ÂË~\u0002\u0000\u0000",
|
||||
"header": "00e01e2acedf6db0523987887ed8b989d4f58b3d6b878a974548010000000000000000005063e99358b04a5d27d07a41a0f886b2e542a2680869b494fa41a7ecf774753d2d44ec676c79021741b12c13",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 141,
|
||||
"name": "SECPOOL",
|
||||
"slug": "secpool",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 2623934,
|
||||
"expectedWeight": 3991917,
|
||||
"similarity": 0.9951244468050102
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "00000000000000000001b36ec470ae4ec39f5d975f665d6f40e9d35bfa65290d",
|
||||
"height": 890444,
|
||||
"version": 671080448,
|
||||
"timestamp": 1743539347,
|
||||
"bits": 386038124,
|
||||
"nonce": 994357124,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "c891d4bf68e22916274b667eb3287d50da2ddd63f8dad892da045cc2ad4a7b21",
|
||||
"tx_count": 3797,
|
||||
"size": 1500309,
|
||||
"weight": 3993525,
|
||||
"previousblockhash": "000000000000000000026e08b270834273511b353bed30d54706211adc96f5f6",
|
||||
"mediantime": 1743533986,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 318708524,
|
||||
"coinbaseRaw": "034c960d082f5669614254432f2cfabe6d6d45b7fd7ab53a0914da7dcc9d21fe44f0936f5354169a56df9d5139f07afbc2b41000000000000000106fc0eb03f0ac2e851d18d8d9f85ad70000000000",
|
||||
"orphans": [],
|
||||
"medianFee": 4.064775540157046,
|
||||
"feeRange": [
|
||||
3.014354066985646,
|
||||
3.18368700265252,
|
||||
3.602836879432624,
|
||||
4.231825525040388,
|
||||
5.581730769230769,
|
||||
10,
|
||||
697.7151162790698
|
||||
],
|
||||
"totalFees": 6208524,
|
||||
"avgFee": 1635,
|
||||
"avgFeeRate": 6,
|
||||
"utxoSetChange": 5755,
|
||||
"avgTxSize": 395.02,
|
||||
"totalInputs": 6681,
|
||||
"totalOutputs": 12436,
|
||||
"totalOutputAmt": 835839828101,
|
||||
"segwitTotalTxs": 3351,
|
||||
"segwitTotalSize": 1354446,
|
||||
"segwitTotalWeight": 3410181,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998381.25,
|
||||
"coinbaseAddress": "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4",
|
||||
"coinbaseAddresses": [
|
||||
"1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4"
|
||||
],
|
||||
"coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 fb37342f6275b13936799def06f2eb4c0f201515 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"coinbaseSignatureAscii": "\u0003L\r\b/ViaBTC/,ú¾mmE·ýzµ:\t\u0014Ú}Ì!þDðoST\u0016VßQ9ðzû´\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0010oÀë\u0003ð¬.
\u001d\u0018ØÙøZ×\u0000\u0000\u0000\u0000\u0000",
|
||||
"header": "00e0ff27f6f596dc1a210647d530ed3b351b5173428370b2086e02000000000000000000217b4aadc25c04da92d8daf863dd2dda507d28b37e664b271629e268bfd491c8934cec676c79021784af443b",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 73,
|
||||
"name": "ViaBTC",
|
||||
"slug": "viabtc",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 6253024,
|
||||
"expectedWeight": 3991868,
|
||||
"similarity": 0.9862862477811569
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "00000000000000000001402065f940b9475159bdc962c92a66d3c6652ffa338a",
|
||||
"height": 890445,
|
||||
"version": 601202688,
|
||||
"timestamp": 1743539574,
|
||||
"bits": 386038124,
|
||||
"nonce": 1647397133,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "61d8294afa8f6bafa4d979a77d187dee5f75a6392f957ea647d96eefbbbc5e9b",
|
||||
"tx_count": 3579,
|
||||
"size": 1659862,
|
||||
"weight": 3993406,
|
||||
"previousblockhash": "00000000000000000001b36ec470ae4ec39f5d975f665d6f40e9d35bfa65290d",
|
||||
"mediantime": 1743535677,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 315617086,
|
||||
"coinbaseRaw": "034d960d04764dec672f466f756e6472792055534120506f6f6c202364726f70676f6c642f4fac7c451540000000000000",
|
||||
"orphans": [],
|
||||
"medianFee": 2.5565329189526835,
|
||||
"feeRange": [
|
||||
1.521613832853026,
|
||||
2,
|
||||
2.2411347517730498,
|
||||
3,
|
||||
3,
|
||||
3.954954954954955,
|
||||
162.78343949044586
|
||||
],
|
||||
"totalFees": 3117086,
|
||||
"avgFee": 871,
|
||||
"avgFeeRate": 3,
|
||||
"utxoSetChange": 1881,
|
||||
"avgTxSize": 463.65000000000003,
|
||||
"totalInputs": 7893,
|
||||
"totalOutputs": 9774,
|
||||
"totalOutputAmt": 324878597485,
|
||||
"segwitTotalTxs": 3189,
|
||||
"segwitTotalSize": 1538741,
|
||||
"segwitTotalWeight": 3509030,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998351.5,
|
||||
"coinbaseAddress": "bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63",
|
||||
"coinbaseAddresses": [
|
||||
"bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63",
|
||||
"bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38"
|
||||
],
|
||||
"coinbaseSignature": "OP_PUSHNUM_1 OP_PUSHBYTES_32 0f9dab1a72f7c48da8a1df2f913bef649bfc0d77072dffd11329b8048293d7a3",
|
||||
"coinbaseSignatureAscii": "\u0003M\r\u0004vMìg/Foundry USA Pool #dropgold/O¬|E\u0015@\u0000\u0000\u0000\u0000\u0000\u0000",
|
||||
"header": "00a0d5230d2965fa5bd3e9406f5d665f975d9fc34eae70c46eb3010000000000000000009b5ebcbbef6ed947a67e952f39a6755fee7d187da779d9a4af6b8ffa4a29d861764dec676c7902170d493162",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 111,
|
||||
"name": "Foundry USA",
|
||||
"slug": "foundryusa",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 3145370,
|
||||
"expectedWeight": 3991903,
|
||||
"similarity": 0.9903353189076812
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "00000000000000000001fe3d26b02146d0fc2192db06cbc12478d4b347f5306b",
|
||||
"height": 890446,
|
||||
"version": 537722880,
|
||||
"timestamp": 1743541107,
|
||||
"bits": 386038124,
|
||||
"nonce": 826569764,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "d9b320d7cb5aace80ca20b934b13b4a272121fbdd59f3aaba690e0326ca2c144",
|
||||
"tx_count": 3998,
|
||||
"size": 1541360,
|
||||
"weight": 3993545,
|
||||
"previousblockhash": "00000000000000000001402065f940b9475159bdc962c92a66d3c6652ffa338a",
|
||||
"mediantime": 1743535803,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 317976882,
|
||||
"coinbaseRaw": "034e960d20202020204d696e656420627920536563706f6f6c2020202070001b04fad5fdfefabe6d6d59dd8ebce6e5aab8fb943bbdcede474b6f2d00a395a717970104a6958c17f1ca100000000000000000008089c9350200",
|
||||
"orphans": [],
|
||||
"medianFee": 3.3750830641948864,
|
||||
"feeRange": [
|
||||
2.397163120567376,
|
||||
3,
|
||||
3,
|
||||
3.463647199046484,
|
||||
4.49438202247191,
|
||||
7.213930348258707,
|
||||
476.1904761904762
|
||||
],
|
||||
"totalFees": 5476882,
|
||||
"avgFee": 1370,
|
||||
"avgFeeRate": 5,
|
||||
"utxoSetChange": 4951,
|
||||
"avgTxSize": 385.41,
|
||||
"totalInputs": 7054,
|
||||
"totalOutputs": 12005,
|
||||
"totalOutputAmt": 983289729453,
|
||||
"segwitTotalTxs": 3538,
|
||||
"segwitTotalSize": 1396505,
|
||||
"segwitTotalWeight": 3414233,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998386.25,
|
||||
"coinbaseAddress": "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9",
|
||||
"coinbaseAddresses": [
|
||||
"3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9",
|
||||
"3Awm3FNpmwrbvAFVThRUFqgpbVuqWisni9"
|
||||
],
|
||||
"coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 8ee90177614ecde53314fd67c46162f315852a07 OP_EQUAL",
|
||||
"coinbaseSignatureAscii": "\u0003N\r Mined by Secpool p\u0000\u001b\u0004úÕýþú¾mmYݼæåª¸û;½ÎÞGKo-\u0000£§\u0017\u0001\u0004¦\u0017ñÊ\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000É5\u0002\u0000",
|
||||
"header": "00000d208a33fa2f65c6d3662ac962c9bd595147b940f96520400100000000000000000044c1a26c32e090a6ab3a9fd5bd1f1272a2b4134b930ba20ce8ac5acbd720b3d97353ec676c79021724744431",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 141,
|
||||
"name": "SECPOOL",
|
||||
"slug": "secpool",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 5601814,
|
||||
"expectedWeight": 3991928,
|
||||
"similarity": 0.9537877497871488
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab",
|
||||
"height": 890447,
|
||||
"version": 568860672,
|
||||
"timestamp": 1743541240,
|
||||
"bits": 386038124,
|
||||
"nonce": 4008077709,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "8c3b098e4e50b67075a4fc52bf4cd603aaa450c240c18a865c9ddc0f27104f5f",
|
||||
"tx_count": 1919,
|
||||
"size": 1747789,
|
||||
"weight": 3993172,
|
||||
"previousblockhash": "00000000000000000001fe3d26b02146d0fc2192db06cbc12478d4b347f5306b",
|
||||
"mediantime": 1743536834,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 314435106,
|
||||
"coinbaseRaw": "034f960d0f2f736c7573682f65000002fba05ef1fabe6d6df8d29032ea6f9ab1debd223651f30887df779c6195869e70a7b787b3a15f4b1710000000000000000000ee5f0c00b20200000000",
|
||||
"orphans": [],
|
||||
"medianFee": 1.4653828213500366,
|
||||
"feeRange": [
|
||||
1.0845070422535212,
|
||||
1.2,
|
||||
1.51,
|
||||
2.0141129032258065,
|
||||
2.3893805309734515,
|
||||
4.025477707006369,
|
||||
300.0065359477124
|
||||
],
|
||||
"totalFees": 1935106,
|
||||
"avgFee": 1008,
|
||||
"avgFeeRate": 1,
|
||||
"utxoSetChange": -4244,
|
||||
"avgTxSize": 910.58,
|
||||
"totalInputs": 9909,
|
||||
"totalOutputs": 5665,
|
||||
"totalOutputAmt": 210763861504,
|
||||
"segwitTotalTxs": 1720,
|
||||
"segwitTotalSize": 1629450,
|
||||
"segwitTotalWeight": 3519924,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998293,
|
||||
"coinbaseAddress": "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11",
|
||||
"coinbaseAddresses": [
|
||||
"34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11"
|
||||
],
|
||||
"coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 1f0cbbec8bc4c945e4e16249b11eee911eded55f OP_EQUAL",
|
||||
"coinbaseSignatureAscii": "\u0003O\r\u000f/slush/e\u0000\u0000\u0002û ^ñú¾mmøÒ2êo±Þ½\"6Qó\bßwap§·³¡_K\u0017\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000î_\f\u0000²\u0002\u0000\u0000\u0000\u0000",
|
||||
"header": "0020e8216b30f547b3d47824c1cb06db9221fcd04621b0263dfe010000000000000000005f4f10270fdc9d5c868ac140c250a4aa03d64cbf52fca47570b6504e8e093b8cf853ec676c7902178d69e6ee",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 43,
|
||||
"name": "Braiins Pool",
|
||||
"slug": "braiinspool",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 2059571,
|
||||
"expectedWeight": 3991720,
|
||||
"similarity": 0.9149852183486826
|
||||
}
|
||||
}
|
||||
],
|
||||
"mempool-blocks": [
|
||||
{
|
||||
"blockSize": 1779311,
|
||||
"blockVSize": 997968.5,
|
||||
"nTx": 2132,
|
||||
"totalFees": 2902870,
|
||||
"medianFee": 2.0479263387949875,
|
||||
"feeRange": [
|
||||
1.0721153846153846,
|
||||
1.9980563654033041,
|
||||
2.2195704057279237,
|
||||
3.009493670886076,
|
||||
3.4955223880597015,
|
||||
6.0246913580246915,
|
||||
218.1818181818182
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1959636,
|
||||
"blockVSize": 997903.5,
|
||||
"nTx": 497,
|
||||
"totalFees": 1093076,
|
||||
"medianFee": 1.102049424602265,
|
||||
"feeRange": [
|
||||
1.0401794819498267,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.0761096766260911,
|
||||
1.1021605957228275
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1477260,
|
||||
"blockVSize": 997997.25,
|
||||
"nTx": 720,
|
||||
"totalFees": 1016195,
|
||||
"medianFee": 1.007409072434199,
|
||||
"feeRange": [
|
||||
1,
|
||||
1.0019120458891013,
|
||||
1.0040863981319323,
|
||||
1.0081019768823594,
|
||||
1.018450184501845,
|
||||
1.0203327171903882,
|
||||
1.0485018498190837
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1021308,
|
||||
"blockVSize": 431071.5,
|
||||
"nTx": 823,
|
||||
"totalFees": 432342,
|
||||
"medianFee": 0,
|
||||
"feeRange": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1.0028011204481793,
|
||||
1.0042075736325387,
|
||||
1.0053475935828877,
|
||||
1.0068649885583525
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
{
|
||||
"mempool-blocks": [
|
||||
{
|
||||
"blockSize": 1780038,
|
||||
"blockVSize": 997989.75,
|
||||
"nTx": 2134,
|
||||
"totalFees": 2919589,
|
||||
"medianFee": 2.0479263387949875,
|
||||
"feeRange": [
|
||||
1.0101010101010102,
|
||||
2,
|
||||
2.235576923076923,
|
||||
3.010452961672474,
|
||||
3.5240274599542336,
|
||||
6.032085561497326,
|
||||
218.1818181818182
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1958446,
|
||||
"blockVSize": 997996,
|
||||
"nTx": 503,
|
||||
"totalFees": 1093277,
|
||||
"medianFee": 1.102049424602265,
|
||||
"feeRange": [
|
||||
1.0101010101010102,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.067677314564158,
|
||||
1.0761096766260911,
|
||||
1.1021605957228275
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1477611,
|
||||
"blockVSize": 997927.5,
|
||||
"nTx": 725,
|
||||
"totalFees": 1016311,
|
||||
"medianFee": 1.0075971559364956,
|
||||
"feeRange": [
|
||||
1,
|
||||
1.0019334049409236,
|
||||
1.0042075736325387,
|
||||
1.0081019768823594,
|
||||
1.018450184501845,
|
||||
1.0203327171903882,
|
||||
1.0548148148148149
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1028219,
|
||||
"blockVSize": 435137,
|
||||
"nTx": 833,
|
||||
"totalFees": 436414,
|
||||
"medianFee": 0,
|
||||
"feeRange": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1.0028011204481793,
|
||||
1.004231311706629,
|
||||
1.0053475935828877,
|
||||
1.0068649885583525
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
1235
frontend/cypress/fixtures/details_rbf/tx01_ws_stratum_jobs.json
Normal file
1235
frontend/cypress/fixtures/details_rbf/tx01_ws_stratum_jobs.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@
|
||||
{
|
||||
"txReplaced": {
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698"
|
||||
}
|
||||
}
|
9
frontend/cypress/fixtures/details_rbf/tx02_api_cpfp.json
Normal file
9
frontend/cypress/fixtures/details_rbf/tx02_api_cpfp.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"ancestors": [],
|
||||
"bestDescendant": null,
|
||||
"descendants": [],
|
||||
"effectiveFeePerVsize": 4.285714285714286,
|
||||
"sigops": 4,
|
||||
"fee": 960,
|
||||
"adjustedVsize": 224
|
||||
}
|
36
frontend/cypress/fixtures/details_rbf/tx02_api_rbf.json
Normal file
36
frontend/cypress/fixtures/details_rbf/tx02_api_rbf.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"replacements": {
|
||||
"tx": {
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698",
|
||||
"fee": 960,
|
||||
"vsize": 224,
|
||||
"value": 49040,
|
||||
"rate": 4.285714285714286,
|
||||
"time": 1743541726,
|
||||
"rbf": true,
|
||||
"fullRbf": false
|
||||
},
|
||||
"time": 1743541726,
|
||||
"fullRbf": false,
|
||||
"replaces": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29",
|
||||
"fee": 606,
|
||||
"vsize": 233,
|
||||
"value": 49394,
|
||||
"rate": 2.6008583690987126,
|
||||
"time": 1743541407,
|
||||
"rbf": true
|
||||
},
|
||||
"time": 1743541407,
|
||||
"interval": 319,
|
||||
"fullRbf": false,
|
||||
"replaces": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"replaces": [
|
||||
"242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29"
|
||||
]
|
||||
}
|
38
frontend/cypress/fixtures/details_rbf/tx02_api_tx.json
Normal file
38
frontend/cypress/fixtures/details_rbf/tx02_api_tx.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698",
|
||||
"version": 2,
|
||||
"locktime": 0,
|
||||
"vin": [
|
||||
{
|
||||
"txid": "fb16c141d22a3a7af5e44e7478a8663866c35d554cd85107ec8a99b97e5a72e9",
|
||||
"vout": 0,
|
||||
"prevout": {
|
||||
"scriptpubkey": "76a914099f831c49289c7b9cc07a9a632867ecd51a105a88ac",
|
||||
"scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 099f831c49289c7b9cc07a9a632867ecd51a105a OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"scriptpubkey_type": "p2pkh",
|
||||
"scriptpubkey_address": "1stAprEZapjCYGUACUcXohQqSHF9MU5Kj",
|
||||
"value": 50000
|
||||
},
|
||||
"scriptsig": "483045022100ab59cf33b33eab1f76286a0fa6962c12171f2133931fec3616f96883551e6c9d02205cacbae788359f2a32ff629e732be0d95843440a3d1c222a63095f83fb69f438014104ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df",
|
||||
"scriptsig_asm": "OP_PUSHBYTES_72 3045022100ab59cf33b33eab1f76286a0fa6962c12171f2133931fec3616f96883551e6c9d02205cacbae788359f2a32ff629e732be0d95843440a3d1c222a63095f83fb69f43801 OP_PUSHBYTES_65 04ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df",
|
||||
"is_coinbase": false,
|
||||
"sequence": 4294967293
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"scriptpubkey": "76a914099f831c49289c7b9cc07a9a632867ecd51a105a88ac",
|
||||
"scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 099f831c49289c7b9cc07a9a632867ecd51a105a OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"scriptpubkey_type": "p2pkh",
|
||||
"scriptpubkey_address": "1stAprEZapjCYGUACUcXohQqSHF9MU5Kj",
|
||||
"value": 49040
|
||||
}
|
||||
],
|
||||
"size": 224,
|
||||
"weight": 896,
|
||||
"sigops": 4,
|
||||
"fee": 960,
|
||||
"status": {
|
||||
"confirmed": false
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
[
|
||||
1743541726
|
||||
]
|
116
frontend/cypress/fixtures/details_rbf/tx02_ws_block.json
Normal file
116
frontend/cypress/fixtures/details_rbf/tx02_ws_block.json
Normal file
@ -0,0 +1,116 @@
|
||||
{
|
||||
"block": {
|
||||
"id": "000000000000000000019bfe551da67e0d93dda4b355d16f1fdb4031ba7e5d61",
|
||||
"height": 890448,
|
||||
"version": 626941952,
|
||||
"timestamp": 1743541850,
|
||||
"bits": 386038124,
|
||||
"nonce": 1177284424,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "563d862ef4ec95ea97735ca5561e347ca6e216cb56bd451e8bedb1014078d6c3",
|
||||
"tx_count": 2229,
|
||||
"size": 1763153,
|
||||
"weight": 3993275,
|
||||
"previousblockhash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab",
|
||||
"mediantime": 1743537197,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 315498786,
|
||||
"coinbaseRaw": "0350960d0f2f736c7573682fe500c702ff43d142fabe6d6def42d9effd800b9839c832dcfdc6899e137547f15c8a598dbe3a1363f999a0a310000000000000000000e9a2050064dc00000000",
|
||||
"orphans": [],
|
||||
"medianFee": 2.144206217858874,
|
||||
"feeRange": [
|
||||
1.0845921450151057,
|
||||
2,
|
||||
2.2448979591836733,
|
||||
3,
|
||||
3.5985915492957745,
|
||||
6,
|
||||
217.0212765957447
|
||||
],
|
||||
"totalFees": 2998786,
|
||||
"avgFee": 1345,
|
||||
"avgFeeRate": 3,
|
||||
"utxoSetChange": -3073,
|
||||
"avgTxSize": 790.84,
|
||||
"totalInputs": 9558,
|
||||
"totalOutputs": 6485,
|
||||
"totalOutputAmt": 442206797883,
|
||||
"segwitTotalTxs": 1986,
|
||||
"segwitTotalSize": 1676431,
|
||||
"segwitTotalWeight": 3646495,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998318.75,
|
||||
"coinbaseAddress": "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11",
|
||||
"coinbaseAddresses": [
|
||||
"34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11"
|
||||
],
|
||||
"coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 1f0cbbec8bc4c945e4e16249b11eee911eded55f OP_EQUAL",
|
||||
"coinbaseSignatureAscii": "\u0003P\r\u000f/slush/å\u0000Ç\u0002ÿCÑBú¾mmïBÙïý\u000b9È2ÜýÆ\u0013uGñ\\Y¾:\u0013cù £\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000é¢\u0005\u0000dÜ\u0000\u0000\u0000\u0000",
|
||||
"header": "00605e25abe2e310c10e724cfb641859658fee6570ec6062cc0400000000000000000000c3d6784001b1ed8b1e45bd56cb16e2a67c341e56a55c7397ea95ecf42e863d565a56ec676c79021748ef2b46",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 43,
|
||||
"name": "Braiins Pool",
|
||||
"slug": "braiinspool",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 3287140,
|
||||
"expectedWeight": 3991809,
|
||||
"similarity": 0.9079894021278392
|
||||
}
|
||||
},
|
||||
"mempool-blocks": [
|
||||
{
|
||||
"blockSize": 1979907,
|
||||
"blockVSize": 997974.75,
|
||||
"nTx": 461,
|
||||
"totalFees": 1429178,
|
||||
"medianFee": 1.1020797417745662,
|
||||
"feeRange": [
|
||||
1.0666666666666667,
|
||||
1.0746847720659554,
|
||||
1.102059530141031,
|
||||
3,
|
||||
3.4233409610983982,
|
||||
5.017605633802817,
|
||||
148.4084084084084
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1363691,
|
||||
"blockVSize": 997986.5,
|
||||
"nTx": 1080,
|
||||
"totalFees": 1023741,
|
||||
"medianFee": 1.014827018121911,
|
||||
"feeRange": [
|
||||
1,
|
||||
1.0036011703803736,
|
||||
1.0054683365672958,
|
||||
1.0186757215619695,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.068146618482189
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1337253,
|
||||
"blockVSize": 563516.25,
|
||||
"nTx": 901,
|
||||
"totalFees": 564834,
|
||||
"medianFee": 1.0028011204481793,
|
||||
"feeRange": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1.0025062656641603,
|
||||
1.004231311706629,
|
||||
1.0053475935828877,
|
||||
1.0068649885583525
|
||||
]
|
||||
}
|
||||
],
|
||||
"txConfirmed": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698"
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
{
|
||||
"block": {
|
||||
"id": "000000000000000000019bfe551da67e0d93dda4b355d16f1fdb4031ba7e5d61",
|
||||
"height": 890448,
|
||||
"version": 626941952,
|
||||
"timestamp": 1743541850,
|
||||
"bits": 386038124,
|
||||
"nonce": 1177284424,
|
||||
"difficulty": 113757508810854,
|
||||
"merkle_root": "563d862ef4ec95ea97735ca5561e347ca6e216cb56bd451e8bedb1014078d6c3",
|
||||
"tx_count": 2229,
|
||||
"size": 1763153,
|
||||
"weight": 3993275,
|
||||
"previousblockhash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab",
|
||||
"mediantime": 1743537197,
|
||||
"stale": false,
|
||||
"extras": {
|
||||
"reward": 315498786,
|
||||
"coinbaseRaw": "0350960d0f2f736c7573682fe500c702ff43d142fabe6d6def42d9effd800b9839c832dcfdc6899e137547f15c8a598dbe3a1363f999a0a310000000000000000000e9a2050064dc00000000",
|
||||
"orphans": [],
|
||||
"medianFee": 2.144206217858874,
|
||||
"feeRange": [
|
||||
1.0845921450151057,
|
||||
2,
|
||||
2.2448979591836733,
|
||||
3,
|
||||
3.5985915492957745,
|
||||
6,
|
||||
217.0212765957447
|
||||
],
|
||||
"totalFees": 2998786,
|
||||
"avgFee": 1345,
|
||||
"avgFeeRate": 3,
|
||||
"utxoSetChange": -3073,
|
||||
"avgTxSize": 790.84,
|
||||
"totalInputs": 9558,
|
||||
"totalOutputs": 6485,
|
||||
"totalOutputAmt": 442206797883,
|
||||
"segwitTotalTxs": 1986,
|
||||
"segwitTotalSize": 1676431,
|
||||
"segwitTotalWeight": 3646495,
|
||||
"feePercentiles": null,
|
||||
"virtualSize": 998318.75,
|
||||
"coinbaseAddress": "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11",
|
||||
"coinbaseAddresses": [
|
||||
"34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11"
|
||||
],
|
||||
"coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 1f0cbbec8bc4c945e4e16249b11eee911eded55f OP_EQUAL",
|
||||
"coinbaseSignatureAscii": "\u0003P\r\u000f/slush/å\u0000Ç\u0002ÿCÑBú¾mmïBÙïý\u000b9È2ÜýÆ\u0013uGñ\\Y¾:\u0013cù £\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000é¢\u0005\u0000dÜ\u0000\u0000\u0000\u0000",
|
||||
"header": "00605e25abe2e310c10e724cfb641859658fee6570ec6062cc0400000000000000000000c3d6784001b1ed8b1e45bd56cb16e2a67c341e56a55c7397ea95ecf42e863d565a56ec676c79021748ef2b46",
|
||||
"utxoSetSize": null,
|
||||
"totalInputAmt": null,
|
||||
"pool": {
|
||||
"id": 43,
|
||||
"name": "Braiins Pool",
|
||||
"slug": "braiinspool",
|
||||
"minerNames": null
|
||||
},
|
||||
"matchRate": 100,
|
||||
"expectedFees": 3287140,
|
||||
"expectedWeight": 3991809,
|
||||
"similarity": 0.9079894021278392
|
||||
}
|
||||
},
|
||||
"mempool-blocks": [
|
||||
{
|
||||
"blockSize": 1979907,
|
||||
"blockVSize": 997974.75,
|
||||
"nTx": 461,
|
||||
"totalFees": 1429178,
|
||||
"medianFee": 1.1020797417745662,
|
||||
"feeRange": [
|
||||
1.0666666666666667,
|
||||
1.0746847720659554,
|
||||
1.102059530141031,
|
||||
3,
|
||||
3.4233409610983982,
|
||||
5.017605633802817,
|
||||
148.4084084084084
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1363691,
|
||||
"blockVSize": 997986.5,
|
||||
"nTx": 1080,
|
||||
"totalFees": 1023741,
|
||||
"medianFee": 1.014827018121911,
|
||||
"feeRange": [
|
||||
1,
|
||||
1.0036011703803736,
|
||||
1.0054683365672958,
|
||||
1.0186757215619695,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.068146618482189
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1337253,
|
||||
"blockVSize": 563516.25,
|
||||
"nTx": 901,
|
||||
"totalFees": 564834,
|
||||
"medianFee": 1.0028011204481793,
|
||||
"feeRange": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1.0025062656641603,
|
||||
1.004231311706629,
|
||||
1.0053475935828877,
|
||||
1.0068649885583525
|
||||
]
|
||||
}
|
||||
],
|
||||
"txConfirmed": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698"
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
{
|
||||
"mempool-blocks": [
|
||||
{
|
||||
"blockSize": 1779823,
|
||||
"blockVSize": 997995.25,
|
||||
"nTx": 2133,
|
||||
"totalFees": 2922926,
|
||||
"medianFee": 2.0479263387949875,
|
||||
"feeRange": [
|
||||
1.0825892857142858,
|
||||
2,
|
||||
2.2439024390243905,
|
||||
3.010452961672474,
|
||||
3.554973821989529,
|
||||
6.032085561497326,
|
||||
218.1818181818182
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1957833,
|
||||
"blockVSize": 997953,
|
||||
"nTx": 500,
|
||||
"totalFees": 1093270,
|
||||
"medianFee": 1.102049424602265,
|
||||
"feeRange": [
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.067677314564158,
|
||||
1.0766488413547237,
|
||||
1.1021605957228275
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1477864,
|
||||
"blockVSize": 997999,
|
||||
"nTx": 730,
|
||||
"totalFees": 1016458,
|
||||
"medianFee": 1.0075971559364956,
|
||||
"feeRange": [
|
||||
1,
|
||||
1.0019552465783186,
|
||||
1.004255319148936,
|
||||
1.0081019768823594,
|
||||
1.018450184501845,
|
||||
1.0203327171903882,
|
||||
1.0548148148148149
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1030954,
|
||||
"blockVSize": 436613.5,
|
||||
"nTx": 838,
|
||||
"totalFees": 437891,
|
||||
"medianFee": 0,
|
||||
"feeRange": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1.0026525198938991,
|
||||
1.004231311706629,
|
||||
1.0053475935828877,
|
||||
1.0068649885583525
|
||||
]
|
||||
}
|
||||
],
|
||||
"txPosition": {
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698",
|
||||
"position": {
|
||||
"block": 0,
|
||||
"vsize": 111102
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
{
|
||||
"mempool-blocks": [
|
||||
{
|
||||
"blockSize": 1719945,
|
||||
"blockVSize": 997952.25,
|
||||
"nTx": 2558,
|
||||
"totalFees": 3287140,
|
||||
"medianFee": 2.4046448299072485,
|
||||
"feeRange": [
|
||||
1.073446327683616,
|
||||
2,
|
||||
2.2567567567567566,
|
||||
3.0106761565836297,
|
||||
3.6169014084507043,
|
||||
6.015037593984962,
|
||||
218.1818181818182
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 2022898,
|
||||
"blockVSize": 997983.25,
|
||||
"nTx": 131,
|
||||
"totalFees": 1098129,
|
||||
"medianFee": 1.1020797417745662,
|
||||
"feeRange": [
|
||||
1.0625,
|
||||
1.0691217722793642,
|
||||
1.073436083408885,
|
||||
1.0761096766260911,
|
||||
1.080091533180778,
|
||||
1.102110739151618,
|
||||
1.1021909190121146
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1363844,
|
||||
"blockVSize": 997998.5,
|
||||
"nTx": 1073,
|
||||
"totalFees": 1023651,
|
||||
"medianFee": 1.014827018121911,
|
||||
"feeRange": [
|
||||
1,
|
||||
1.003584229390681,
|
||||
1.0054683365672958,
|
||||
1.0186757215619695,
|
||||
1.0548148148148149,
|
||||
1.0548148148148149,
|
||||
1.068146618482189
|
||||
]
|
||||
},
|
||||
{
|
||||
"blockSize": 1335390,
|
||||
"blockVSize": 562453.5,
|
||||
"nTx": 902,
|
||||
"totalFees": 563772,
|
||||
"medianFee": 1.0028011204481793,
|
||||
"feeRange": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1.0025402201524132,
|
||||
1.004231311706629,
|
||||
1.0053475935828877,
|
||||
1.0068649885583525
|
||||
]
|
||||
}
|
||||
],
|
||||
"txPosition": {
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698",
|
||||
"position": {
|
||||
"block": 0,
|
||||
"vsize": 128920
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"txPosition": {
|
||||
"txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698",
|
||||
"position": {
|
||||
"block": 0,
|
||||
"vsize": 110880
|
||||
}
|
||||
}
|
||||
}
|
37
frontend/cypress/fixtures/rbf_page/rbf_01.json
Normal file
37
frontend/cypress/fixtures/rbf_page/rbf_01.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"rbfLatest": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "f4bae4f626036250fd00d68490e572f65f66417452003a0f4c4d76f17a9fde68",
|
||||
"fee": 1185,
|
||||
"vsize": 223,
|
||||
"value": 41729,
|
||||
"rate": 5.313901345291479,
|
||||
"time": 1743587177,
|
||||
"rbf": true,
|
||||
"fullRbf": false,
|
||||
"mined": true
|
||||
},
|
||||
"time": 1743587177,
|
||||
"fullRbf": true,
|
||||
"replaces": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "12945412dfc455e0ed6049dc2ee8737756c8d9e2d9a2eb26f366cd5019a0369f",
|
||||
"fee": 504,
|
||||
"vsize": 222,
|
||||
"value": 42410,
|
||||
"rate": 2.27027027027027,
|
||||
"time": 1743586081,
|
||||
"rbf": true
|
||||
},
|
||||
"time": 1743586081,
|
||||
"interval": 1096,
|
||||
"fullRbf": false,
|
||||
"replaces": []
|
||||
}
|
||||
],
|
||||
"mined": true
|
||||
}
|
||||
]
|
||||
}
|
68
frontend/cypress/fixtures/rbf_page/rbf_02.json
Normal file
68
frontend/cypress/fixtures/rbf_page/rbf_02.json
Normal file
@ -0,0 +1,68 @@
|
||||
{
|
||||
"rbfLatest": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "d313b479acfbae719afb488a078e0fe0e052a67b9f65f73f7c75d3d95fd36acc",
|
||||
"fee": 672,
|
||||
"vsize": 167.25,
|
||||
"value": 29996328,
|
||||
"rate": 4.017937219730942,
|
||||
"time": 1743587365,
|
||||
"rbf": true,
|
||||
"fullRbf": false
|
||||
},
|
||||
"time": 1743587365,
|
||||
"fullRbf": false,
|
||||
"replaces": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "eb5aa786cabda307cc9642cfb9c41a3b405ac20a391eefbe54be7930bea61865",
|
||||
"fee": 336,
|
||||
"vsize": 167.5,
|
||||
"value": 29996664,
|
||||
"rate": 2.005970149253731,
|
||||
"time": 1743586424,
|
||||
"rbf": true
|
||||
},
|
||||
"time": 1743586424,
|
||||
"interval": 941,
|
||||
"fullRbf": false,
|
||||
"replaces": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tx": {
|
||||
"txid": "f4bae4f626036250fd00d68490e572f65f66417452003a0f4c4d76f17a9fde68",
|
||||
"fee": 1185,
|
||||
"vsize": 223,
|
||||
"value": 41729,
|
||||
"rate": 5.313901345291479,
|
||||
"time": 1743587177,
|
||||
"rbf": true,
|
||||
"fullRbf": false,
|
||||
"mined": true
|
||||
},
|
||||
"time": 1743587177,
|
||||
"fullRbf": true,
|
||||
"replaces": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "12945412dfc455e0ed6049dc2ee8737756c8d9e2d9a2eb26f366cd5019a0369f",
|
||||
"fee": 504,
|
||||
"vsize": 222,
|
||||
"value": 42410,
|
||||
"rate": 2.27027027027027,
|
||||
"time": 1743586081,
|
||||
"rbf": true
|
||||
},
|
||||
"time": 1743586081,
|
||||
"interval": 1096,
|
||||
"fullRbf": false,
|
||||
"replaces": []
|
||||
}
|
||||
],
|
||||
"mined": true
|
||||
}
|
||||
]
|
||||
}
|
@ -44,6 +44,7 @@
|
||||
|
||||
import { PageIdleDetector } from './PageIdleDetector';
|
||||
import { mockWebSocket } from './websocket';
|
||||
import { mockWebSocketV2 } from './websocket';
|
||||
|
||||
/* global Cypress */
|
||||
const codes = {
|
||||
@ -72,6 +73,10 @@ Cypress.Commands.add('mockMempoolSocket', () => {
|
||||
mockWebSocket();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('mockMempoolSocketV2', () => {
|
||||
mockWebSocketV2();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('changeNetwork', (network: "testnet" | "testnet4" | "signet" | "liquid" | "mainnet") => {
|
||||
cy.get('.dropdown-toggle').click().then(() => {
|
||||
cy.get(`a.${network}`).click().then(() => {
|
||||
|
1
frontend/cypress/support/index.d.ts
vendored
1
frontend/cypress/support/index.d.ts
vendored
@ -5,6 +5,7 @@ declare namespace Cypress {
|
||||
waitForSkeletonGone(): Chainable<any>
|
||||
waitForPageIdle(): Chainable<any>
|
||||
mockMempoolSocket(): Chainable<any>
|
||||
mockMempoolSocketV2(): Chainable<any>
|
||||
changeNetwork(network: "testnet"|"testnet4"|"signet"|"liquid"|"mainnet"): Chainable<any>
|
||||
}
|
||||
}
|
@ -27,6 +27,37 @@ const createMock = (url: string) => {
|
||||
return mocks[url];
|
||||
};
|
||||
|
||||
export const mockWebSocketV2 = () => {
|
||||
cy.on('window:before:load', (win) => {
|
||||
const winWebSocket = win.WebSocket;
|
||||
cy.stub(win, 'WebSocket').callsFake((url) => {
|
||||
console.log(url);
|
||||
if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0)) {
|
||||
const { server, websocket } = createMock(url);
|
||||
|
||||
win.mockServer = server;
|
||||
win.mockServer.on('connection', (socket) => {
|
||||
win.mockSocket = socket;
|
||||
});
|
||||
|
||||
win.mockServer.on('message', (message) => {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
return websocket;
|
||||
} else {
|
||||
return new winWebSocket(url);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
cy.on('window:before:unload', () => {
|
||||
for (const url in mocks) {
|
||||
cleanupMock(url);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const mockWebSocket = () => {
|
||||
cy.on('window:before:load', (win) => {
|
||||
const winWebSocket = win.WebSocket;
|
||||
@ -65,6 +96,27 @@ export const mockWebSocket = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const receiveWebSocketMessageFromServer = ({
|
||||
params
|
||||
}: { params?: any } = {}) => {
|
||||
cy.window().then((win) => {
|
||||
if (params.message) {
|
||||
console.log('sending message');
|
||||
win.mockSocket.send(params.message.contents);
|
||||
}
|
||||
|
||||
if (params.file) {
|
||||
cy.readFile(`cypress/fixtures/${params.file.path}`, 'utf-8').then((fixture) => {
|
||||
console.log('sending payload');
|
||||
win.mockSocket.send(JSON.stringify(fixture));
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
export const emitMempoolInfo = ({
|
||||
params
|
||||
}: { params?: any } = {}) => {
|
||||
@ -82,16 +134,22 @@ export const emitMempoolInfo = ({
|
||||
switch (params.command) {
|
||||
case "init": {
|
||||
win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}');
|
||||
cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'ascii').then((fixture) => {
|
||||
cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'utf-8').then((fixture) => {
|
||||
win.mockSocket.send(JSON.stringify(fixture));
|
||||
});
|
||||
cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'ascii').then((fixture) => {
|
||||
cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'utf-8').then((fixture) => {
|
||||
win.mockSocket.send(JSON.stringify(fixture));
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "rbfTransaction": {
|
||||
cy.readFile('cypress/fixtures/mainnet_rbf.json', 'ascii').then((fixture) => {
|
||||
cy.readFile('cypress/fixtures/mainnet_rbf.json', 'utf-8').then((fixture) => {
|
||||
win.mockSocket.send(JSON.stringify(fixture));
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'trackTx': {
|
||||
cy.readFile('cypress/fixtures/track_tx.json', 'utf-8').then((fixture) => {
|
||||
win.mockSocket.send(JSON.stringify(fixture));
|
||||
});
|
||||
break;
|
||||
|
@ -25,14 +25,12 @@
|
||||
"i18n-extract-from-source": "npm run ng -- extract-i18n --out-file ./src/locale/messages.xlf",
|
||||
"i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1 --force",
|
||||
"serve": "npm run generate-config && npm run ng -- serve -c local",
|
||||
"serve:stg": "npm run generate-config && npm run ng -- serve -c staging",
|
||||
"serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod",
|
||||
"serve:local-staging": "npm run generate-config && npm run ng -- serve -c local-staging",
|
||||
"serve:parameterized": "npm run generate-config && npm run ng -- serve -c parameterized",
|
||||
"start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local",
|
||||
"start:parameterized": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized",
|
||||
"start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora",
|
||||
"start:stg": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c staging",
|
||||
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod",
|
||||
"start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging",
|
||||
"start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed",
|
||||
"build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets && npm run build-mempool.js",
|
||||
"sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'",
|
||||
@ -58,8 +56,8 @@
|
||||
"cypress:run:record": "cypress run --record",
|
||||
"cypress:open:ci": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:open",
|
||||
"cypress:run:ci": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record",
|
||||
"cypress:open:ci:staging": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:open",
|
||||
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record"
|
||||
"cypress:open:ci:parameterized": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:parameterized 4200 cypress:open",
|
||||
"cypress:run:ci:parameterized": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:parameterized 4200 cypress:run:record"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular-devkit/build-angular": "^17.3.1",
|
||||
@ -123,4 +121,4 @@
|
||||
"scarfSettings": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
36
frontend/proxy.conf.parameterized.js
Normal file
36
frontend/proxy.conf.parameterized.js
Normal file
@ -0,0 +1,36 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const PROXY_CONFIG = require('./proxy.conf');
|
||||
|
||||
const addApiKeyHeader = (proxyReq, req, res) => {
|
||||
if (process.env.MEMPOOL_CI_API_KEY) {
|
||||
proxyReq.setHeader('X-Mempool-Auth', process.env.MEMPOOL_CI_API_KEY);
|
||||
}
|
||||
};
|
||||
|
||||
PROXY_CONFIG.forEach((entry) => {
|
||||
const mempoolHostname = process.env.MEMPOOL_HOSTNAME
|
||||
? process.env.MEMPOOL_HOSTNAME
|
||||
: 'mempool.space';
|
||||
|
||||
const liquidHostname = process.env.LIQUID_HOSTNAME
|
||||
? process.env.LIQUID_HOSTNAME
|
||||
: 'liquid.network';
|
||||
|
||||
entry.target = entry.target.replace('mempool.space', mempoolHostname);
|
||||
entry.target = entry.target.replace('liquid.network', liquidHostname);
|
||||
|
||||
if (entry.onProxyReq) {
|
||||
const originalProxyReq = entry.onProxyReq;
|
||||
entry.onProxyReq = (proxyReq, req, res) => {
|
||||
originalProxyReq(proxyReq, req, res);
|
||||
if (process.env.MEMPOOL_CI_API_KEY) {
|
||||
proxyReq.setHeader('X-Mempool-Auth', process.env.MEMPOOL_CI_API_KEY);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
entry.onProxyReq = addApiKeyHeader;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
@ -1,12 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
let PROXY_CONFIG = require('./proxy.conf');
|
||||
|
||||
PROXY_CONFIG.forEach(entry => {
|
||||
const hostname = process.env.CYPRESS_REROUTE_TESTNET === 'true' ? 'mempool-staging.fra.mempool.space' : 'node201.va1.mempool.space';
|
||||
console.log(`e2e tests running against ${hostname}`);
|
||||
entry.target = entry.target.replace("mempool.space", hostname);
|
||||
entry.target = entry.target.replace("liquid.network", "liquid-staging.va1.mempool.space");
|
||||
});
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
@ -147,6 +147,15 @@
|
||||
</svg>
|
||||
<span>Bull Bitcoin</span>
|
||||
</a>
|
||||
<a href="https://fortris.com/" target="_blank" title="Fortris">
|
||||
<svg id="fortris-logo" viewBox="0 0 140.08 129.13" version="1.1" width="74px" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs><style>.fortris-cls-1{fill:#29384a;}.fortris-cls-1,.fortris-cls-2{stroke-width:0px;}.fortris-cls-2{fill:#ff6e72;}</style></defs>
|
||||
<rect class="fortris-cls-2" x="0" y="0" width="140.08" height="30.59" rx="15.29" ry="15.29" />
|
||||
<rect class="fortris-cls-2" x="0" y="98.540001" width="69.779999" height="30.59" rx="15.29" ry="15.29" />
|
||||
<rect class="fortris-cls-2" x="0" y="49.27" width="109.5" height="30.59" rx="15.29" ry="15.29" />
|
||||
</svg>
|
||||
<span>Fortris</span>
|
||||
</a>
|
||||
<a href="https://exodus.com/" target="_blank" title="Exodus">
|
||||
<svg width="80" height="80" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg" class="image">
|
||||
<circle cx="250" cy="250" r="250" fill="#1F2033"/>
|
||||
@ -451,7 +460,7 @@
|
||||
Trademark Notice<br>
|
||||
</div>
|
||||
<p>
|
||||
The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
|
||||
The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
|
||||
</p>
|
||||
<p>
|
||||
While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our <a href="https://mempool.space/trademark-policy">Trademark Policy and Guidelines</a> for more details, published on <https://mempool.space/trademark-policy>.
|
||||
|
@ -264,6 +264,6 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
max-width: 800px;
|
||||
max-width: 850px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +158,7 @@
|
||||
|
||||
<!-- MEMPOOL BASE FEE -->
|
||||
<tr>
|
||||
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator™ fees</td>
|
||||
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator® fees</td>
|
||||
</tr>
|
||||
<tr class="info" [class.group-last]="!estimate.vsizeFee" [class.dashed-bottom]="!estimate.vsizeFee">
|
||||
<td class="info">
|
||||
@ -567,14 +567,29 @@
|
||||
} @else if (step === 'success') {
|
||||
<div class="row mb-1 text-center">
|
||||
<div class="col-sm">
|
||||
<h1 style="font-size: larger;"><ng-content select="[slot='accelerated-title']"></ng-content><span class="default-slot" i18n="accelerator.success-message">Your transaction is being accelerated!</span></h1>
|
||||
<h1 style="font-size: larger;"><ng-content select="[slot='accelerated-title']"></ng-content>
|
||||
@if (accelerationResponse) {
|
||||
<span class="default-slot" i18n="accelerator.success-message">Your transaction is being accelerated!</span>
|
||||
} @else {
|
||||
<span class="default-slot" i18n="accelerator.success-message-third-party">Transaction is already being accelerated!</span>
|
||||
}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center mt-1">
|
||||
<div class="col-sm">
|
||||
<div class="d-flex flex-row justify-content-center align-items-center">
|
||||
<span i18n="accelerator.confirmed-acceleration-with-miners">Your transaction has been accepted for acceleration by our mining pool partners.</span>
|
||||
@if (accelerationResponse) {
|
||||
<span i18n="accelerator.confirmed-acceleration-with-miners">Your transaction has been accepted for acceleration by our mining pool partners.</span>
|
||||
} @else {
|
||||
<span i18n="accelerator.confirmed-acceleration-with-miners-third-party">Transaction has already been accepted for acceleration by our mining pool partners.</span>
|
||||
}
|
||||
</div>
|
||||
@if (accelerationResponse?.receiptUrl) {
|
||||
<div class="d-flex flex-row justify-content-center align-items-center">
|
||||
<span i18n="accelerator.receipt-label"><a [href]="accelerationResponse.receiptUrl" target="_blank">Click here to get a receipt.</a></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
@ -87,6 +87,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
math = Math;
|
||||
isMobile: boolean = window.innerWidth <= 767.98;
|
||||
isProdDomain = false;
|
||||
accelerationResponse: { receiptUrl: string | null } | undefined;
|
||||
|
||||
private _step: CheckoutStep = 'summary';
|
||||
simpleMode: boolean = true;
|
||||
@ -194,16 +195,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
this.scrollToElement('acceleratePreviewAnchor', 'start');
|
||||
}
|
||||
if (changes.accelerating && this.accelerating) {
|
||||
if (this.step === 'processing' || this.step === 'paid') {
|
||||
this.moveToStep('success', true);
|
||||
} else { // Edge case where the transaction gets accelerated by someone else or on another session
|
||||
this.closeModal();
|
||||
}
|
||||
this.moveToStep('success', true);
|
||||
}
|
||||
}
|
||||
|
||||
moveToStep(step: CheckoutStep, force: boolean = false): void {
|
||||
if (this.isCheckoutLocked > 0 && !force) {
|
||||
if (this.isCheckoutLocked > 0 && !force || this.step === 'success') {
|
||||
return;
|
||||
}
|
||||
this.processing = false;
|
||||
@ -525,7 +522,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
if (tokenResult?.status === 'OK') {
|
||||
const card = tokenResult.details?.card;
|
||||
if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
|
||||
console.error(`Cannot retreive payment card details`);
|
||||
console.error(`Cannot retrieve payment card details`);
|
||||
this.accelerateError = 'apple_pay_no_card_details';
|
||||
this.processing = false;
|
||||
return;
|
||||
@ -541,7 +538,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||
costUSD
|
||||
).subscribe({
|
||||
next: () => {
|
||||
next: (response) => {
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
this.audioService.playSound('ascend-chime-cartoon');
|
||||
@ -643,7 +641,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
if (tokenResult?.status === 'OK') {
|
||||
const card = tokenResult.details?.card;
|
||||
if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
|
||||
console.error(`Cannot retreive payment card details`);
|
||||
console.error(`Cannot retrieve payment card details`);
|
||||
this.accelerateError = 'apple_pay_no_card_details';
|
||||
this.processing = false;
|
||||
return;
|
||||
@ -668,7 +666,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
costUSD,
|
||||
verificationToken.userChallenged
|
||||
).subscribe({
|
||||
next: () => {
|
||||
next: (response) => {
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
this.audioService.playSound('ascend-chime-cartoon');
|
||||
@ -777,7 +776,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
costUSD,
|
||||
verificationToken.userChallenged
|
||||
).subscribe({
|
||||
next: () => {
|
||||
next: (response) => {
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
this.audioService.playSound('ascend-chime-cartoon');
|
||||
@ -870,7 +870,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
tokenResult.details.cashAppPay.referenceId,
|
||||
costUSD
|
||||
).subscribe({
|
||||
next: () => {
|
||||
next: (response) => {
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
this.audioService.playSound('ascend-chime-cartoon');
|
||||
@ -936,7 +937,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
this.loadingBtcpayInvoice = true;
|
||||
this.servicesApiService.generateBTCPayAcceleratorInvoice$(this.tx.txid, this.userBid).pipe(
|
||||
switchMap(response => {
|
||||
return this.servicesApiService.retreiveInvoice$(response.btcpayInvoiceId);
|
||||
return this.servicesApiService.retrieveInvoice$(response.btcpayInvoiceId);
|
||||
}),
|
||||
catchError(error => {
|
||||
console.log(error);
|
||||
|
@ -44,6 +44,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
@Input() right: number | string = 10;
|
||||
@Input() left: number | string = 70;
|
||||
@Input() widget: boolean = false;
|
||||
@Input() label: string = '';
|
||||
@Input() defaultFiat: boolean = false;
|
||||
@Input() showLegend: boolean = true;
|
||||
@Input() showYAxis: boolean = true;
|
||||
@ -55,6 +56,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
hoverData: any[] = [];
|
||||
conversions: any;
|
||||
allowZoom: boolean = false;
|
||||
labelGraphic: any;
|
||||
|
||||
selected = { [$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`]: true, 'Fiat': false };
|
||||
|
||||
@ -85,6 +87,18 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.isLoading = true;
|
||||
this.labelGraphic = this.label ? {
|
||||
type: 'text',
|
||||
right: '36px',
|
||||
bottom: '36px',
|
||||
z: 100,
|
||||
silent: true,
|
||||
style: {
|
||||
fill: '#fff',
|
||||
text: this.label,
|
||||
font: '24px sans-serif'
|
||||
}
|
||||
} : undefined;
|
||||
if (!this.addressSummary$ && (!this.address || !this.stats)) {
|
||||
return;
|
||||
}
|
||||
@ -205,6 +219,10 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
right: this.adjustedRight,
|
||||
left: this.adjustedLeft,
|
||||
},
|
||||
graphic: this.labelGraphic ? [{
|
||||
...this.labelGraphic,
|
||||
right: this.adjustedRight + 22 + 'px',
|
||||
}] : undefined,
|
||||
legend: (this.showLegend && !this.stateService.isAnyTestnet()) ? {
|
||||
data: [
|
||||
{
|
||||
@ -443,6 +461,10 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
right: this.adjustedRight,
|
||||
left: this.adjustedLeft,
|
||||
},
|
||||
graphic: this.labelGraphic ? [{
|
||||
...this.labelGraphic,
|
||||
right: this.adjustedRight + 22 + 'px',
|
||||
}] : undefined,
|
||||
legend: {
|
||||
selected: this.selected,
|
||||
},
|
||||
|
@ -238,7 +238,7 @@
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||
</a>
|
||||
<app-address-graph [address]="widget.props.address" [addressSummary$]="addressSummary$" [period]="widget.props.period || 'all'" [stats]="address ? address.chain_stats : null" [widget]="true" [defaultFiat]="true" [height]="graphHeight"></app-address-graph>
|
||||
<app-address-graph [address]="widget.props.address" [addressSummary$]="addressSummary$" [period]="widget.props.period || 'all'" [stats]="address ? address.chain_stats : null" [widget]="true" [defaultFiat]="true" [height]="graphHeight" [label]="widget.props.label"></app-address-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -272,7 +272,7 @@
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||
</a>
|
||||
<app-address-graph [addressSummary$]="walletSummary$" [period]="widget.props.period || 'all'" [widget]="true" [height]="graphHeight"></app-address-graph>
|
||||
<app-address-graph [addressSummary$]="walletSummary$" [period]="widget.props.period || 'all'" [widget]="true" [height]="graphHeight" [label]="widget.props.label"></app-address-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -70,11 +70,6 @@
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
|
||||
</li>
|
||||
<!--
|
||||
<li class="nav-item d-none d-lg-block" routerLinkActive="active">
|
||||
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon></a>
|
||||
</li>
|
||||
-->
|
||||
<li class="nav-item" routerLinkActive="active" id="btn-assets">
|
||||
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
|
||||
</li>
|
||||
|
@ -45,14 +45,14 @@
|
||||
|
||||
<br>
|
||||
|
||||
<h4>USING MEMPOOL ACCELERATOR™</h4>
|
||||
<h4>USING MEMPOOL ACCELERATOR®</h4>
|
||||
|
||||
<p *ngIf="officialMempoolSpace">If you use Mempool Accelerator™ your acceleration request will be sent to us and relayed to Mempool's mining pool partners. We will store the TXID of the transactions you accelerate with us. We share this information with our mining pool partners, and publicly display accelerated transaction details on our website and APIs. No personal information or account identifiers will be shared with any third party including mining pool partners.</p>
|
||||
<p *ngIf="officialMempoolSpace">If you use Mempool Accelerator® your acceleration request will be sent to us and relayed to Mempool's mining pool partners. We will store the TXID of the transactions you accelerate with us. We share this information with our mining pool partners, and publicly display accelerated transaction details on our website and APIs. No personal information or account identifiers will be shared with any third party including mining pool partners.</p>
|
||||
|
||||
<p *ngIf="!officialMempoolSpace">When using Mempool Accelerator™ the mempool.space privacy policy will apply: <a href="https://mempool.space/privacy-policy">https://mempool.space/privacy-policy</a>.</p>
|
||||
<p *ngIf="!officialMempoolSpace">When using Mempool Accelerator® the mempool.space privacy policy will apply: <a href="https://mempool.space/privacy-policy">https://mempool.space/privacy-policy</a>.</p>
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<ng-container *ngIf="officialMempoolSpace">
|
||||
|
||||
<h4>SIGNING UP FOR AN ACCOUNT ON MEMPOOL.SPACE</h4>
|
||||
@ -67,7 +67,7 @@
|
||||
|
||||
<li>If you sign up for a subscription to Mempool Enterprise™ we also collect your company name which is not shared with any third-party.</li>
|
||||
|
||||
<li>If you sign up for an account on mempool.space and use Mempool Accelerator™ Pro your accelerated transactions will be associated with your account for the purposes of accounting.</li>
|
||||
<li>If you sign up for an account on mempool.space and use Mempool Accelerator® Pro your accelerated transactions will be associated with your account for the purposes of accounting.</li>
|
||||
|
||||
</ul>
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
|
||||
<p>We aim to retain your data only as long as necessary:</p>
|
||||
<ul>
|
||||
<li>An account is considered inactive if all of the following conditions are met: a) No login activity within the past 6 months, b) No active subscriptions associated with the account, c) No Mempool Accelerator™ Pro account credit</li>
|
||||
<li>An account is considered inactive if all of the following conditions are met: a) No login activity within the past 6 months, b) No active subscriptions associated with the account, c) No Mempool Accelerator® Pro account credit</li>
|
||||
<li>If an account meets the criteria for inactivity as defined above, we will automatically delete the associated account data after a period of 6 months of continuous inactivity, except in the case of payment disputes or account irregularities.</li>
|
||||
</ul>
|
||||
|
||||
|
@ -16,9 +16,6 @@
|
||||
<a class="btn btn-primary btn-sm mb-0" [routerLink]="['/clock/mempool/0' | relativeUrl]" style="color: white" id="btn-clock">
|
||||
<fa-icon [icon]="['fas', 'clock']" [fixedWidth]="true" i18n-title="master-page.clockview" i18n-title="footer.clock-mempool" title="Clock (Mempool)"></fa-icon>
|
||||
</a>
|
||||
<a *ngIf="!isMobile()" class="btn btn-primary btn-sm mb-0" [routerLink]="['/tv' | relativeUrl]" style="color: white" id="btn-tv">
|
||||
<fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-toggle-rows" name="radioBasic">
|
||||
<div class="btn-group btn-group-toggle">
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,25 +0,0 @@
|
||||
<div id="tv-wrapper">
|
||||
<div class="tv-container">
|
||||
<div class="chart-holder">
|
||||
<app-mempool-graph
|
||||
[template]="'advanced'"
|
||||
[height]="600"
|
||||
[left]="60"
|
||||
[right]="10"
|
||||
[data]="statsSubscription$ | async"
|
||||
[showZoom]="false"
|
||||
></app-mempool-graph>
|
||||
</div>
|
||||
<div class="blockchain-wrapper" [dir]="timeLtr ? 'rtl' : 'ltr'" [class.time-ltr]="timeLtr">
|
||||
<div class="position-container">
|
||||
<span>
|
||||
<div class="blocks-wrapper">
|
||||
<app-mempool-blocks></app-mempool-blocks>
|
||||
<app-blockchain-blocks></app-blockchain-blocks>
|
||||
</div>
|
||||
<div id="divider"></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,80 +0,0 @@
|
||||
|
||||
.loading {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#tv-wrapper {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-holder {
|
||||
position: relative;
|
||||
height: 655px;
|
||||
width: 100%;
|
||||
margin: 30px auto 0;
|
||||
}
|
||||
|
||||
.blockchain-wrapper {
|
||||
display: block;
|
||||
height: 100%;
|
||||
min-height: 240px;
|
||||
position: relative;
|
||||
top: 30px;
|
||||
|
||||
.position-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 170px;
|
||||
transform: translateX(50vw);
|
||||
}
|
||||
|
||||
#divider {
|
||||
width: 2px;
|
||||
height: 175px;
|
||||
left: 0;
|
||||
top: -40px;
|
||||
position: absolute;
|
||||
img {
|
||||
position: absolute;
|
||||
left: -100px;
|
||||
top: -28px;
|
||||
}
|
||||
}
|
||||
|
||||
&.time-ltr {
|
||||
.blocks-wrapper {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.ltr-layout) {
|
||||
.blockchain-wrapper.time-ltr .blocks-wrapper,
|
||||
.blockchain-wrapper .blocks-wrapper {
|
||||
direction: ltr;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.rtl-layout) {
|
||||
.blockchain-wrapper.time-ltr .blocks-wrapper,
|
||||
.blockchain-wrapper .blocks-wrapper {
|
||||
direction: rtl;
|
||||
}
|
||||
}
|
||||
|
||||
.tv-container {
|
||||
display: flex;
|
||||
margin-top: 0px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,86 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { WebsocketService } from '@app/services/websocket.service';
|
||||
import { OptimizedMempoolStats } from '@interfaces/node-api.interface';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { ApiService } from '@app/services/api.service';
|
||||
import { SeoService } from '@app/services/seo.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { map, scan, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { interval, merge, Observable, Subscription } from 'rxjs';
|
||||
import { ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-television',
|
||||
templateUrl: './television.component.html',
|
||||
styleUrls: ['./television.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TelevisionComponent implements OnInit, OnDestroy {
|
||||
|
||||
mempoolStats: OptimizedMempoolStats[] = [];
|
||||
statsSubscription$: Observable<OptimizedMempoolStats[]>;
|
||||
fragment: string;
|
||||
timeLtrSubscription: Subscription;
|
||||
timeLtr: boolean = this.stateService.timeLtr.value;
|
||||
|
||||
constructor(
|
||||
private websocketService: WebsocketService,
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
private seoService: SeoService,
|
||||
private route: ActivatedRoute
|
||||
) { }
|
||||
|
||||
refreshStats(time: number, fn: Observable<OptimizedMempoolStats[]>) {
|
||||
return interval(time).pipe(startWith(0), switchMap(() => fn));
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.tv:See Bitcoin blocks and mempool congestion in real-time in a simplified format perfect for a TV.`);
|
||||
this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']);
|
||||
|
||||
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||
this.timeLtr = !!ltr;
|
||||
});
|
||||
|
||||
this.statsSubscription$ = merge(
|
||||
this.stateService.live2Chart$.pipe(map(stats => [stats])),
|
||||
this.route.fragment
|
||||
.pipe(
|
||||
tap(fragment => { this.fragment = fragment ?? '2h'; }),
|
||||
switchMap((fragment) => {
|
||||
const minute = 60000; const hour = 3600000;
|
||||
switch (fragment) {
|
||||
case '24h': return this.apiService.list24HStatistics$();
|
||||
case '1w': return this.refreshStats(5 * minute, this.apiService.list1WStatistics$());
|
||||
case '1m': return this.refreshStats(30 * minute, this.apiService.list1MStatistics$());
|
||||
case '3m': return this.refreshStats(2 * hour, this.apiService.list3MStatistics$());
|
||||
case '6m': return this.refreshStats(3 * hour, this.apiService.list6MStatistics$());
|
||||
case '1y': return this.refreshStats(8 * hour, this.apiService.list1YStatistics$());
|
||||
case '2y': return this.refreshStats(8 * hour, this.apiService.list2YStatistics$());
|
||||
case '3y': return this.refreshStats(12 * hour, this.apiService.list3YStatistics$());
|
||||
default /* 2h */: return this.apiService.list2HStatistics$();
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
.pipe(
|
||||
scan((mempoolStats, newStats) => {
|
||||
if (newStats.length > 1) {
|
||||
mempoolStats = newStats;
|
||||
} else if (['2h', '24h'].includes(this.fragment)) {
|
||||
mempoolStats.unshift(newStats[0]);
|
||||
const now = Date.now() / 1000;
|
||||
const start = now - (this.fragment === '2h' ? (2 * 60 * 60) : (24 * 60 * 60) );
|
||||
mempoolStats = mempoolStats.filter(p => p.added >= start);
|
||||
}
|
||||
return mempoolStats;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.timeLtrSubscription.unsubscribe();
|
||||
}
|
||||
}
|
@ -67,9 +67,9 @@
|
||||
|
||||
</ng-container>
|
||||
|
||||
<h4>MEMPOOL ACCELERATOR™</h4>
|
||||
<h4>MEMPOOL ACCELERATOR®</h4>
|
||||
|
||||
<p><a href="https://mempool.space/accelerator">Mempool Accelerator™</a> enables members of the Bitcoin community to submit requests for transaction prioritization. </p>
|
||||
<p><a href="https://mempool.space/accelerator">Mempool Accelerator®</a> enables members of the Bitcoin community to submit requests for transaction prioritization. </p>
|
||||
<ul>
|
||||
|
||||
<li>Mempool will use reasonable commercial efforts to relay user acceleration requests to Mempool's mining pool partners, but it is at the discretion of Mempool and Mempool's mining pool partners to accept acceleration requests. </li>
|
||||
@ -84,11 +84,11 @@
|
||||
|
||||
<br>
|
||||
|
||||
<li>All acceleration payments and Mempool Accelerator™ account credit top-ups are non-refundable. </li>
|
||||
<li>All acceleration payments and Mempool Accelerator® account credit top-ups are non-refundable. </li>
|
||||
|
||||
<br>
|
||||
|
||||
<li>Mempool Accelerator™ account credit top-ups are prepayment for future accelerations and cannot be withdrawn or transferred.</li>
|
||||
<li>Mempool Accelerator® account credit top-ups are prepayment for future accelerations and cannot be withdrawn or transferred.</li>
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div *ngIf="officialMempoolSpace">
|
||||
<h2>Trademark Policy and Guidelines</h2>
|
||||
<h5>The Mempool Open Source Project ®</h5>
|
||||
<h6>Updated: August 19, 2024</h6>
|
||||
<h6>Updated: February 11, 2025</h6>
|
||||
<br>
|
||||
|
||||
<div class="text-left">
|
||||
@ -59,6 +59,7 @@
|
||||
<tr><td>Mempool Accelerator</td></tr>
|
||||
<tr><td>Mempool Enterprise</td></tr>
|
||||
<tr><td>Mempool Liquidity</td></tr>
|
||||
<tr><td>Mempool</td></tr>
|
||||
<tr><td>mempool.space</td></tr>
|
||||
<tr><td>Be your own explorer</td></tr>
|
||||
<tr><td>Explore the full Bitcoin ecosystem</td></tr>
|
||||
@ -340,7 +341,8 @@
|
||||
|
||||
<p>Also, if you are using our Marks in a way described in the sections "Uses for Which We Are Granting a License," you must include the following trademark attribution at the foot of the webpage where you have used the Mark (or, if in a book, on the credits page), on any packaging or labeling, and on advertising or marketing materials:</p>
|
||||
|
||||
<p>"The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, the Mempool Accelerator logo;, the Mempool Goggles logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."</p>
|
||||
<p>"The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Liquidity™, Mempool®, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem®, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, the Mempool Accelerator logo;, the Mempool Goggles logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."</p>
|
||||
|
||||
<li>What to Do When You See Abuse</li>
|
||||
|
||||
<br>
|
||||
|
@ -170,7 +170,7 @@
|
||||
@if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) {
|
||||
<div class="d-flex accelerate">
|
||||
<a class="btn btn-sm accelerateDeepMempool btn-small-height" [class.disabled]="!eligibleForAcceleration" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
|
||||
<a *ngIf="!eligibleForAcceleration" href="https://mempool.space/accelerator#why-cant-accelerate" target="_blank" class="info-badges ml-1" i18n-ngbTooltip="Mempool Accelerator™ tooltip" ngbTooltip="This transaction cannot be accelerated">
|
||||
<a *ngIf="!eligibleForAcceleration" href="https://mempool.space/accelerator#why-cant-accelerate" target="_blank" class="info-badges ml-1" i18n-ngbTooltip="Mempool Accelerator® tooltip" ngbTooltip="This transaction cannot be accelerated">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
</div>
|
||||
@ -318,4 +318,4 @@
|
||||
<tr>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
@ -30,7 +30,6 @@
|
||||
</span>
|
||||
|
||||
<div class="container-buttons">
|
||||
<button *ngIf="successBroadcast" type="button" class="btn btn-sm btn-success no-cursor" i18n="transaction.broadcasted|Broadcasted">Broadcasted</button>
|
||||
<button class="btn btn-sm" style="margin-left: 10px; padding: 0;" (click)="resetForm()">✕</button>
|
||||
</div>
|
||||
|
||||
@ -40,14 +39,18 @@
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div *ngIf="!successBroadcast" class="alert alert-mempool" style="align-items: center;">
|
||||
<div class="alert alert-mempool" style="align-items: center;">
|
||||
<span>
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
<ng-container i18n="transaction.local-tx|This transaction is stored locally in your browser.">
|
||||
<ng-container *ngIf="!successBroadcast" i18n="transaction.local-tx|This transaction is stored locally in your browser.">
|
||||
This transaction is stored locally in your browser. Broadcast it to add it to the mempool.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="successBroadcast" i18n="transaction.redirecting|Redirecting to transaction page...">
|
||||
Redirecting to transaction page...
|
||||
</ng-container>
|
||||
</span>
|
||||
<button [disabled]="isLoadingBroadcast" type="button" class="btn btn-sm btn-primary" i18n="transaction.broadcast|Broadcast" (click)="postTx()">Broadcast</button>
|
||||
<button *ngIf="!successBroadcast" [disabled]="isLoadingBroadcast" type="button" class="btn btn-sm btn-primary btn-broadcast" i18n="transaction.broadcast|Broadcast" (click)="postTx()">Broadcast</button>
|
||||
<button *ngIf="successBroadcast" type="button" class="btn btn-sm btn-success no-cursor btn-broadcast" i18n="transaction.broadcasted|Broadcasted">Broadcasted</button>
|
||||
</div>
|
||||
|
||||
@if (!hasPrevouts) {
|
||||
|
@ -191,4 +191,12 @@
|
||||
.no-cursor {
|
||||
cursor: default !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn-broadcast {
|
||||
margin-left: 5px;
|
||||
@media (max-width: 567px) {
|
||||
margin-left: 0;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ import { Transaction, Vout } from '@interfaces/electrs.interface';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Filter, toFilters } from '../../shared/filters.utils';
|
||||
import { decodeRawTransaction, getTransactionFlags, addInnerScriptsToVin, countSigops } from '../../shared/transaction.utils';
|
||||
import { firstValueFrom, Subscription } from 'rxjs';
|
||||
import { catchError, firstValueFrom, Subscription, switchMap, tap, throwError, timer } from 'rxjs';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
@ -36,6 +36,7 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
||||
isLoadingBroadcast: boolean;
|
||||
errorBroadcast: string;
|
||||
successBroadcast: boolean;
|
||||
broadcastSubscription: Subscription;
|
||||
|
||||
isMobile: boolean;
|
||||
@ViewChild('graphContainer')
|
||||
@ -82,7 +83,7 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
||||
this.resetState();
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const { tx, hex } = decodeRawTransaction(this.pushTxForm.get('txRaw').value, this.stateService.network);
|
||||
const { tx, hex } = decodeRawTransaction(this.pushTxForm.get('txRaw').value.trim(), this.stateService.network);
|
||||
await this.fetchPrevouts(tx);
|
||||
await this.fetchCpfpInfo(tx);
|
||||
this.processTransaction(tx, hex);
|
||||
@ -207,18 +208,22 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
async postTx(): Promise<string> {
|
||||
postTx(): void {
|
||||
this.isLoadingBroadcast = true;
|
||||
this.errorBroadcast = null;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.apiService.postTransaction$(this.rawHexTransaction)
|
||||
.subscribe((result) => {
|
||||
|
||||
this.broadcastSubscription = this.apiService.postTransaction$(this.rawHexTransaction).pipe(
|
||||
tap((txid: string) => {
|
||||
this.isLoadingBroadcast = false;
|
||||
this.successBroadcast = true;
|
||||
this.transaction.txid = result;
|
||||
resolve(result);
|
||||
},
|
||||
(error) => {
|
||||
this.transaction.txid = txid;
|
||||
}),
|
||||
switchMap((txid: string) =>
|
||||
timer(2000).pipe(
|
||||
tap(() => this.router.navigate([this.relativeUrlPipe.transform('/tx/' + txid)])),
|
||||
)
|
||||
),
|
||||
catchError((error) => {
|
||||
if (typeof error.error === 'string') {
|
||||
const matchText = error.error.replace(/\\/g, '').match('"message":"(.*?)"');
|
||||
this.errorBroadcast = 'Failed to broadcast transaction, reason: ' + (matchText && matchText[1] || error.error);
|
||||
@ -226,9 +231,9 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
||||
this.errorBroadcast = 'Failed to broadcast transaction, reason: ' + error.message;
|
||||
}
|
||||
this.isLoadingBroadcast = false;
|
||||
reject(this.error);
|
||||
});
|
||||
});
|
||||
return throwError(() => error);
|
||||
})
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
resetState() {
|
||||
@ -253,6 +258,7 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
||||
this.missingPrevouts = [];
|
||||
this.stateService.markBlock$.next({});
|
||||
this.mempoolBlocksSubscription?.unsubscribe();
|
||||
this.broadcastSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
@ -308,6 +314,7 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
||||
this.mempoolBlocksSubscription?.unsubscribe();
|
||||
this.flowPrefSubscription?.unsubscribe();
|
||||
this.stateService.markBlock$.next({});
|
||||
this.broadcastSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,11 @@
|
||||
<div class="header-bg box" [attr.data-cy]="'tx-' + i">
|
||||
|
||||
<div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div>
|
||||
@if (similarityMatches.get(tx.txid)?.size) {
|
||||
<div class="alert alert-mempool" role="alert">
|
||||
<span i18n="transaction.poison.warning">Warning! This transaction involves deceptively similar addresses. It may be an address poisoning attack.</span>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-fixed table-borderless smaller-text table-sm table-tx-vin">
|
||||
@ -68,9 +73,11 @@
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #defaultAddress>
|
||||
<a class="address" *ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType" [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
|
||||
<app-truncate [text]="vin.prevout.scriptpubkey_address" [lastChars]="8"></app-truncate>
|
||||
</a>
|
||||
<app-address-text
|
||||
*ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType"
|
||||
[address]="vin.prevout.scriptpubkey_address"
|
||||
[similarity]="similarityMatches.get(tx.txid)?.get(vin.prevout.scriptpubkey_address)"
|
||||
></app-address-text>
|
||||
<ng-template #vinScriptPubkeyType>
|
||||
{{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
|
||||
</ng-template>
|
||||
@ -217,9 +224,11 @@
|
||||
'highlight': this.addresses.length && ((vout.scriptpubkey_type !== 'p2pk' && addresses.includes(vout.scriptpubkey_address)) || this.addresses.includes(vout.scriptpubkey.slice(2, -2)))
|
||||
}">
|
||||
<td class="address-cell">
|
||||
<a class="address" *ngIf="vout.scriptpubkey_address; else pubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
||||
<app-truncate [text]="vout.scriptpubkey_address" [lastChars]="8"></app-truncate>
|
||||
</a>
|
||||
<app-address-text
|
||||
*ngIf="vout.scriptpubkey_address; else pubkey_type"
|
||||
[address]="vout.scriptpubkey_address"
|
||||
[similarity]="similarityMatches.get(tx.txid)?.get(vout.scriptpubkey_address)"
|
||||
></app-address-text>
|
||||
<ng-template #pubkey_type>
|
||||
<ng-container *ngIf="vout.scriptpubkey_type === 'p2pk'; else scriptpubkey_type">
|
||||
P2PK <a class="address p2pk-address" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey.slice(2, -2)]" title="{{ vout.scriptpubkey.slice(2, -2) }}">
|
||||
|
@ -14,6 +14,7 @@ import { StorageService } from '@app/services/storage.service';
|
||||
import { OrdApiService } from '@app/services/ord-api.service';
|
||||
import { Inscription } from '@app/shared/ord/inscription.utils';
|
||||
import { Etching, Runestone } from '@app/shared/ord/rune.utils';
|
||||
import { ADDRESS_SIMILARITY_THRESHOLD, AddressMatch, AddressSimilarity, AddressType, AddressTypeInfo, checkedCompareAddressStrings, detectAddressType } from '@app/shared/address-utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transactions-list',
|
||||
@ -55,6 +56,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
showFullScript: { [vinIndex: number]: boolean } = {};
|
||||
showFullWitness: { [vinIndex: number]: { [witnessIndex: number]: boolean } } = {};
|
||||
showOrdData: { [key: string]: { show: boolean; inscriptions?: Inscription[]; runestone?: Runestone, runeInfo?: { [id: string]: { etching: Etching; txid: string; } }; } } = {};
|
||||
similarityMatches: Map<string, Map<string, { score: number, match: AddressMatch, group: number }>> = new Map();
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
@ -144,6 +146,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
this.currency = currency;
|
||||
this.refreshPrice();
|
||||
});
|
||||
|
||||
this.updateAddressSimilarities();
|
||||
}
|
||||
|
||||
refreshPrice(): void {
|
||||
@ -183,6 +187,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
if (changes.transactions || changes.addresses) {
|
||||
this.similarityMatches.clear();
|
||||
this.updateAddressSimilarities();
|
||||
if (!this.transactions || !this.transactions.length) {
|
||||
return;
|
||||
}
|
||||
@ -296,6 +302,56 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
updateAddressSimilarities(): void {
|
||||
if (!this.transactions || !this.transactions.length) {
|
||||
return;
|
||||
}
|
||||
for (const tx of this.transactions) {
|
||||
if (this.similarityMatches.get(tx.txid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const similarityGroups: Map<string, number> = new Map();
|
||||
let lastGroup = 0;
|
||||
|
||||
// Check for address poisoning similarity matches
|
||||
this.similarityMatches.set(tx.txid, new Map());
|
||||
const comparableVouts = [
|
||||
...tx.vout.slice(0, 20),
|
||||
...this.addresses.map(addr => ({ scriptpubkey_address: addr, scriptpubkey_type: detectAddressType(addr, this.stateService.network) }))
|
||||
].filter(v => ['p2pkh', 'p2sh', 'v0_p2wpkh', 'v0_p2wsh', 'v1_p2tr'].includes(v.scriptpubkey_type));
|
||||
const comparableVins = tx.vin.slice(0, 20).map(v => v.prevout).filter(v => ['p2pkh', 'p2sh', 'v0_p2wpkh', 'v0_p2wsh', 'v1_p2tr'].includes(v?.scriptpubkey_type));
|
||||
for (const vout of comparableVouts) {
|
||||
const address = vout.scriptpubkey_address;
|
||||
const addressType = vout.scriptpubkey_type;
|
||||
if (this.similarityMatches.get(tx.txid)?.has(address)) {
|
||||
continue;
|
||||
}
|
||||
for (const compareAddr of [
|
||||
...comparableVouts.filter(v => v.scriptpubkey_type === addressType && v.scriptpubkey_address !== address),
|
||||
...comparableVins.filter(v => v.scriptpubkey_type === addressType && v.scriptpubkey_address !== address)
|
||||
]) {
|
||||
const similarity = checkedCompareAddressStrings(address, compareAddr.scriptpubkey_address, addressType as AddressType, this.stateService.network);
|
||||
if (similarity?.status === 'comparable' && similarity.score > ADDRESS_SIMILARITY_THRESHOLD) {
|
||||
let group = similarityGroups.get(address) || lastGroup++;
|
||||
similarityGroups.set(address, group);
|
||||
const bestVout = this.similarityMatches.get(tx.txid)?.get(address);
|
||||
if (!bestVout || bestVout.score < similarity.score) {
|
||||
this.similarityMatches.get(tx.txid)?.set(address, { score: similarity.score, match: similarity.left, group });
|
||||
}
|
||||
// opportunistically update the entry for the compared address
|
||||
const bestCompare = this.similarityMatches.get(tx.txid)?.get(compareAddr.scriptpubkey_address);
|
||||
if (!bestCompare || bestCompare.score < similarity.score) {
|
||||
group = similarityGroups.get(compareAddr.scriptpubkey_address) || lastGroup++;
|
||||
similarityGroups.set(compareAddr.scriptpubkey_address, group);
|
||||
this.similarityMatches.get(tx.txid)?.set(compareAddr.scriptpubkey_address, { score: similarity.score, match: similarity.right, group });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onScroll(): void {
|
||||
this.loadMore.emit();
|
||||
}
|
||||
|
@ -11803,7 +11803,7 @@ export const restApiDocsData = [
|
||||
fragment: "accelerator-cancel",
|
||||
title: "POST Cancel Acceleration (Pro)",
|
||||
description: {
|
||||
default: "<p>Sends a request to cancel an acceleration in the <code>accelerating</code> status.<br>You can retreive eligible acceleration <code>id</code> using the history endpoint GET <code>/api/v1/services/accelerator/history?status=accelerating</code>."
|
||||
default: "<p>Sends a request to cancel an acceleration in the <code>accelerating</code> status.<br>You can retrieve eligible acceleration <code>id</code> using the history endpoint GET <code>/api/v1/services/accelerator/history?status=accelerating</code>."
|
||||
},
|
||||
urlString: "/v1/services/accelerator/cancel",
|
||||
showConditions: [""],
|
||||
|
@ -219,7 +219,7 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="how-to-get-transaction-confirmed-quickly">
|
||||
<p>To get your transaction confirmed quicker, you will need to increase its effective feerate.</p><p>If your transaction was created with RBF enabled, your stuck transaction can simply be replaced with a new one that has a higher fee. Otherwise, if you control any of the stuck transaction's outputs, you can use CPFP to increase your stuck transaction's effective feerate.</p><p>If you are not sure how to do RBF or CPFP, work with the tool you used to make the transaction (wallet software, exchange company, etc).</p><p>Another option to get your transaction confirmed more quickly is <a [href]="[ isMempoolSpaceBuild ? '/accelerator' : 'https://mempool.space/accelerator']" [target]="isMempoolSpaceBuild ? '' : 'blank'">Mempool Accelerator™</a>.</p>
|
||||
<p>To get your transaction confirmed quicker, you will need to increase its effective feerate.</p><p>If your transaction was created with RBF enabled, your stuck transaction can simply be replaced with a new one that has a higher fee. Otherwise, if you control any of the stuck transaction's outputs, you can use CPFP to increase your stuck transaction's effective feerate.</p><p>If you are not sure how to do RBF or CPFP, work with the tool you used to make the transaction (wallet software, exchange company, etc).</p><p>Another option to get your transaction confirmed more quickly is <a [href]="[ isMempoolSpaceBuild ? '/accelerator' : 'https://mempool.space/accelerator']" [target]="isMempoolSpaceBuild ? '' : 'blank'">Mempool Accelerator®</a>.</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="how-prevent-stuck-transaction">
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Import tree-shakeable echarts
|
||||
import * as echarts from 'echarts/core';
|
||||
import { LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart, CustomChart } from 'echarts/charts';
|
||||
import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, GeoComponent, DataZoomComponent, VisualMapComponent, MarkLineComponent } from 'echarts/components';
|
||||
import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, GeoComponent, DataZoomComponent, VisualMapComponent, MarkLineComponent, GraphicComponent } from 'echarts/components';
|
||||
import { SVGRenderer, CanvasRenderer } from 'echarts/renderers';
|
||||
// Typescript interfaces
|
||||
import { EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption } from 'echarts';
|
||||
@ -13,6 +13,6 @@ echarts.use([
|
||||
LegendComponent, GeoComponent, DataZoomComponent,
|
||||
VisualMapComponent, MarkLineComponent,
|
||||
LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart,
|
||||
CustomChart,
|
||||
CustomChart, GraphicComponent
|
||||
]);
|
||||
export { echarts, EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption };
|
@ -26,7 +26,6 @@ import { StatisticsComponent } from '@components/statistics/statistics.component
|
||||
import { MempoolBlockComponent } from '@components/mempool-block/mempool-block.component';
|
||||
import { PoolRankingComponent } from '@components/pool-ranking/pool-ranking.component';
|
||||
import { PoolComponent } from '@components/pool/pool.component';
|
||||
import { TelevisionComponent } from '@components/television/television.component';
|
||||
import { DashboardComponent } from '@app/dashboard/dashboard.component';
|
||||
import { CustomDashboardComponent } from '@components/custom-dashboard/custom-dashboard.component';
|
||||
import { MiningDashboardComponent } from '@components/mining-dashboard/mining-dashboard.component';
|
||||
@ -56,7 +55,6 @@ import { CommonModule } from '@angular/common';
|
||||
AcceleratorDashboardComponent,
|
||||
PoolComponent,
|
||||
PoolRankingComponent,
|
||||
TelevisionComponent,
|
||||
|
||||
StatisticsComponent,
|
||||
GraphsComponent,
|
||||
|
@ -16,7 +16,6 @@ import { PoolRankingComponent } from '@components/pool-ranking/pool-ranking.comp
|
||||
import { PoolComponent } from '@components/pool/pool.component';
|
||||
import { StartComponent } from '@components/start/start.component';
|
||||
import { StatisticsComponent } from '@components/statistics/statistics.component';
|
||||
import { TelevisionComponent } from '@components/television/television.component';
|
||||
import { DashboardComponent } from '@app/dashboard/dashboard.component';
|
||||
import { CustomDashboardComponent } from '@components/custom-dashboard/custom-dashboard.component';
|
||||
import { AccelerationFeesGraphComponent } from '@components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component';
|
||||
@ -180,11 +179,6 @@ const routes: Routes = [
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'tv',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: TelevisionComponent
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -213,7 +213,7 @@ export class ServicesApiServices {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/payments/bitcoin`, params);
|
||||
}
|
||||
|
||||
retreiveInvoice$(invoiceId: string): Observable<any[]> {
|
||||
retrieveInvoice$(invoiceId: string): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/invoice?id=${invoiceId}`);
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,7 @@ const p2trRegex = RegExp('^p' + BECH32_CHARS_LW + '{58}$');
|
||||
const pubkeyRegex = RegExp('^' + `(04${HEX_CHARS}{128})|(0[23]${HEX_CHARS}{64})$`);
|
||||
|
||||
export function detectAddressType(address: string, network: string): AddressType {
|
||||
network = network || 'mainnet';
|
||||
// normal address types
|
||||
const firstChar = address.substring(0, 1);
|
||||
if (ADDRESS_PREFIXES[network].base58.pubkey.includes(firstChar) && base58Regex.test(address.slice(1))) {
|
||||
@ -211,6 +212,18 @@ export class AddressTypeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
public compareTo(other: AddressTypeInfo): AddressSimilarityResult {
|
||||
return compareAddresses(this.address, other.address, this.network);
|
||||
}
|
||||
|
||||
public compareToString(other: string): AddressSimilarityResult {
|
||||
if (other === this.address) {
|
||||
return { status: 'identical' };
|
||||
}
|
||||
const otherInfo = new AddressTypeInfo(this.network, other);
|
||||
return this.compareTo(otherInfo);
|
||||
}
|
||||
|
||||
private processScript(script: ScriptInfo): void {
|
||||
this.scripts.set(script.key, script);
|
||||
if (script.template?.type === 'multisig') {
|
||||
@ -218,3 +231,135 @@ export class AddressTypeInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface AddressMatch {
|
||||
prefix: string;
|
||||
postfix: string;
|
||||
}
|
||||
|
||||
export interface AddressSimilarity {
|
||||
status: 'comparable';
|
||||
score: number;
|
||||
left: AddressMatch;
|
||||
right: AddressMatch;
|
||||
}
|
||||
export type AddressSimilarityResult =
|
||||
| { status: 'identical' }
|
||||
| { status: 'incomparable' }
|
||||
| AddressSimilarity;
|
||||
|
||||
export const ADDRESS_SIMILARITY_THRESHOLD = 10_000_000; // 1 false positive per ~10 million comparisons
|
||||
|
||||
function fuzzyPrefixMatch(a: string, b: string, rtl: boolean = false): { score: number, matchA: string, matchB: string } {
|
||||
let score = 0;
|
||||
let gap = false;
|
||||
let done = false;
|
||||
|
||||
let ai = 0;
|
||||
let bi = 0;
|
||||
let prefixA = '';
|
||||
let prefixB = '';
|
||||
if (rtl) {
|
||||
a = a.split('').reverse().join('');
|
||||
b = b.split('').reverse().join('');
|
||||
}
|
||||
|
||||
while (ai < a.length && bi < b.length && !done) {
|
||||
if (a[ai] === b[bi]) {
|
||||
// matching characters
|
||||
prefixA += a[ai];
|
||||
prefixB += b[bi];
|
||||
score++;
|
||||
ai++;
|
||||
bi++;
|
||||
} else if (!gap) {
|
||||
// try looking ahead in both strings to find the best match
|
||||
const nextMatchA = (ai + 1 < a.length && a[ai + 1] === b[bi]);
|
||||
const nextMatchB = (bi + 1 < b.length && a[ai] === b[bi + 1]);
|
||||
const nextMatchBoth = (ai + 1 < a.length && bi + 1 < b.length && a[ai + 1] === b[bi + 1]);
|
||||
if (nextMatchBoth) {
|
||||
// single differing character
|
||||
prefixA += a[ai];
|
||||
prefixB += b[bi];
|
||||
ai++;
|
||||
bi++;
|
||||
} else if (nextMatchA) {
|
||||
// character missing in b
|
||||
prefixA += a[ai];
|
||||
ai++;
|
||||
} else if (nextMatchB) {
|
||||
// character missing in a
|
||||
prefixB += b[bi];
|
||||
bi++;
|
||||
} else {
|
||||
ai++;
|
||||
bi++;
|
||||
}
|
||||
gap = true;
|
||||
} else {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rtl) {
|
||||
prefixA = prefixA.split('').reverse().join('');
|
||||
prefixB = prefixB.split('').reverse().join('');
|
||||
}
|
||||
|
||||
return { score, matchA: prefixA, matchB: prefixB };
|
||||
}
|
||||
|
||||
export function compareAddressInfo(a: AddressTypeInfo, b: AddressTypeInfo): AddressSimilarityResult {
|
||||
if (a.address === b.address) {
|
||||
return { status: 'identical' };
|
||||
}
|
||||
if (a.type !== b.type) {
|
||||
return { status: 'incomparable' };
|
||||
}
|
||||
if (!['p2pkh', 'p2sh', 'p2sh-p2wpkh', 'p2sh-p2wsh', 'v0_p2wpkh', 'v0_p2wsh', 'v1_p2tr'].includes(a.type)) {
|
||||
return { status: 'incomparable' };
|
||||
}
|
||||
const isBase58 = a.type === 'p2pkh' || a.type === 'p2sh';
|
||||
|
||||
const left = fuzzyPrefixMatch(a.address, b.address);
|
||||
const right = fuzzyPrefixMatch(a.address, b.address, true);
|
||||
// depending on address type, some number of matching prefix characters are guaranteed
|
||||
const prefixScore = isBase58 ? 1 : ADDRESS_PREFIXES[a.network || 'mainnet'].bech32.length;
|
||||
|
||||
// add the two scores together
|
||||
const totalScore = left.score + right.score - prefixScore;
|
||||
|
||||
// adjust for the size of the alphabet (58 vs 32)
|
||||
const normalizedScore = Math.pow(isBase58 ? 58 : 32, totalScore);
|
||||
|
||||
return {
|
||||
status: 'comparable',
|
||||
score: normalizedScore,
|
||||
left: {
|
||||
prefix: left.matchA,
|
||||
postfix: right.matchA,
|
||||
},
|
||||
right: {
|
||||
prefix: left.matchB,
|
||||
postfix: right.matchB,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function compareAddresses(a: string, b: string, network: string): AddressSimilarityResult {
|
||||
if (a === b) {
|
||||
return { status: 'identical' };
|
||||
}
|
||||
const aInfo = new AddressTypeInfo(network, a);
|
||||
return aInfo.compareToString(b);
|
||||
}
|
||||
|
||||
// avoids the overhead of creating AddressTypeInfo objects for each address,
|
||||
// but a and b *MUST* be valid normalized addresses, of the same valid type
|
||||
export function checkedCompareAddressStrings(a: string, b: string, type: AddressType, network: string): AddressSimilarityResult {
|
||||
return compareAddressInfo(
|
||||
{ address: a, type: type, network: network } as AddressTypeInfo,
|
||||
{ address: b, type: type, network: network } as AddressTypeInfo,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
|
||||
@if (similarity) {
|
||||
<div class="address-text">
|
||||
<a class="address" style="display: contents;" [routerLink]="['/address/' | relativeUrl, address]" title="{{ address }}">
|
||||
<span class="prefix">{{ similarity.match.prefix }}</span>
|
||||
<span class="infix" [ngStyle]="{'text-decoration-color': groupColors[similarity.group % (groupColors.length)]}">{{ address.slice(similarity.match.prefix.length || 0, -similarity.match.postfix.length || undefined) }}</span>
|
||||
<span class="postfix"> {{ similarity.match.postfix }}</span>
|
||||
</a>
|
||||
<span class="poison-alert" *ngIf="similarity" i18n-ngbTooltip="address-poisoning.warning-tooltip" ngbTooltip="This address is deceptively similar to another output. It may be part of an address poisoning attack.">
|
||||
<fa-icon [icon]="['fas', 'exclamation-triangle']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</div>
|
||||
} @else {
|
||||
<a class="address" [routerLink]="['/address/' | relativeUrl, address]" title="{{ address }}">
|
||||
<app-truncate [text]="address" [lastChars]="8"></app-truncate>
|
||||
</a>
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
.address-text {
|
||||
text-overflow: unset;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
position: relative;
|
||||
|
||||
font-family: monospace;
|
||||
|
||||
.infix {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
|
||||
text-decoration: underline 2px;
|
||||
}
|
||||
|
||||
.prefix, .postfix {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
user-select: none;
|
||||
|
||||
text-decoration: underline var(--red) 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.poison-alert {
|
||||
margin-left: .5em;
|
||||
color: var(--yellow);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AddressMatch, AddressTypeInfo } from '@app/shared/address-utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-address-text',
|
||||
templateUrl: './address-text.component.html',
|
||||
styleUrls: ['./address-text.component.scss']
|
||||
})
|
||||
export class AddressTextComponent {
|
||||
@Input() address: string;
|
||||
@Input() info: AddressTypeInfo | null;
|
||||
@Input() similarity: { score: number, match: AddressMatch, group: number } | null;
|
||||
|
||||
groupColors: string[] = [
|
||||
'var(--primary)',
|
||||
'var(--success)',
|
||||
'var(--info)',
|
||||
'white',
|
||||
];
|
||||
}
|
@ -256,6 +256,11 @@ export function detectScriptTemplate(type: ScriptType, script_asm: string, witne
|
||||
return ScriptTemplates.multisig(tapscriptMultisig.m, tapscriptMultisig.n);
|
||||
}
|
||||
|
||||
const tapscriptUnanimousMultisig = parseTapscriptUnanimousMultisig(script_asm);
|
||||
if (tapscriptUnanimousMultisig) {
|
||||
return ScriptTemplates.multisig(tapscriptUnanimousMultisig, tapscriptUnanimousMultisig);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -310,11 +315,13 @@ export function parseTapscriptMultisig(script: string): undefined | { m: number,
|
||||
}
|
||||
|
||||
const ops = script.split(' ');
|
||||
// At minimum, one pubkey group (3 tokens) + m push + final opcode = 5 tokens
|
||||
if (ops.length < 5) return;
|
||||
// At minimum, 2 pubkey group (3 tokens) + m push + final opcode = 8 tokens
|
||||
if (ops.length < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
const finalOp = ops.pop();
|
||||
if (finalOp !== 'OP_NUMEQUAL' && finalOp !== 'OP_GREATERTHANOREQUAL') {
|
||||
if (!['OP_NUMEQUAL', 'OP_NUMEQUALVERIFY', 'OP_GREATERTHANOREQUAL', 'OP_GREATERTHAN', 'OP_EQUAL', 'OP_EQUALVERIFY'].includes(finalOp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -329,6 +336,10 @@ export function parseTapscriptMultisig(script: string): undefined | { m: number,
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalOp === 'OP_GREATERTHAN') {
|
||||
m += 1;
|
||||
}
|
||||
|
||||
if (ops.length % 3 !== 0) {
|
||||
return;
|
||||
}
|
||||
@ -360,6 +371,53 @@ export function parseTapscriptMultisig(script: string): undefined | { m: number,
|
||||
return { m, n };
|
||||
}
|
||||
|
||||
export function parseTapscriptUnanimousMultisig(script: string): undefined | number {
|
||||
if (!script) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ops = script.split(' ');
|
||||
// At minimum, 2 pubkey group (3 tokens) = 6 tokens
|
||||
if (ops.length < 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ops.length % 3 !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const n = ops.length / 3;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const pushOp = ops.shift();
|
||||
const pubkey = ops.shift();
|
||||
const sigOp = ops.shift();
|
||||
|
||||
if (pushOp !== 'OP_PUSHBYTES_32') {
|
||||
return;
|
||||
}
|
||||
if (!/^[0-9a-fA-F]{64}$/.test(pubkey)) {
|
||||
return;
|
||||
}
|
||||
if (i < n - 1) {
|
||||
if (sigOp !== 'OP_CHECKSIGVERIFY') {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Last opcode can be either CHECKSIG or CHECKSIGVERIFY
|
||||
if (!(sigOp === 'OP_CHECKSIGVERIFY' || sigOp === 'OP_CHECKSIG')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ops.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
export function getVarIntLength(n: number): number {
|
||||
if (n < 0xfd) {
|
||||
return 1;
|
||||
|
@ -3,11 +3,11 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
|
||||
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
|
||||
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
|
||||
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft,
|
||||
faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck,
|
||||
faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline,
|
||||
faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes, faCreditCard, faMicroscope } from '@fortawesome/free-solid-svg-icons';
|
||||
faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes, faCreditCard, faMicroscope, faExclamationTriangle } 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';
|
||||
@ -94,6 +94,7 @@ import { SatsComponent } from '@app/shared/components/sats/sats.component';
|
||||
import { BtcComponent } from '@app/shared/components/btc/btc.component';
|
||||
import { FeeRateComponent } from '@app/shared/components/fee-rate/fee-rate.component';
|
||||
import { AddressTypeComponent } from '@app/shared/components/address-type/address-type.component';
|
||||
import { AddressTextComponent } from '@app/shared/components/address-text/address-text.component';
|
||||
import { TruncateComponent } from '@app/shared/components/truncate/truncate.component';
|
||||
import { SearchResultsComponent } from '@components/search-form/search-results/search-results.component';
|
||||
import { TimestampComponent } from '@app/shared/components/timestamp/timestamp.component';
|
||||
@ -214,6 +215,7 @@ import { GithubLogin } from '../components/github-login.component/github-login.c
|
||||
BtcComponent,
|
||||
FeeRateComponent,
|
||||
AddressTypeComponent,
|
||||
AddressTextComponent,
|
||||
TruncateComponent,
|
||||
SearchResultsComponent,
|
||||
TimestampComponent,
|
||||
@ -360,6 +362,7 @@ import { GithubLogin } from '../components/github-login.component/github-login.c
|
||||
BtcComponent,
|
||||
FeeRateComponent,
|
||||
AddressTypeComponent,
|
||||
AddressTextComponent,
|
||||
TruncateComponent,
|
||||
SearchResultsComponent,
|
||||
TimestampComponent,
|
||||
@ -395,7 +398,6 @@ export class SharedModule {
|
||||
constructor(library: FaIconLibrary) {
|
||||
library.addIcons(faInfoCircle);
|
||||
library.addIcons(faChartArea);
|
||||
library.addIcons(faTv);
|
||||
library.addIcons(faClock);
|
||||
library.addIcons(faTachometerAlt);
|
||||
library.addIcons(faCubes);
|
||||
@ -465,5 +467,6 @@ export class SharedModule {
|
||||
library.addIcons(faShareNodes);
|
||||
library.addIcons(faCreditCard);
|
||||
library.addIcons(faMicroscope);
|
||||
library.addIcons(faExclamationTriangle);
|
||||
}
|
||||
}
|
||||
|
@ -1567,7 +1567,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="bdb8bbb38e4ca3c73e19dc4167fbe4aec316f818" datatype="html">
|
||||
<source>Total Bid Boost</source>
|
||||
<target>Augmentation totale des frais</target>
|
||||
<target>Total frais ajoutés</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html</context>
|
||||
<context context-type="linenumber">11</context>
|
||||
@ -1728,7 +1728,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="57cde27765d527a0d9195212fa5a7ce06408c827" datatype="html">
|
||||
<source>Bid Boost</source>
|
||||
<target>Augmentation des frais</target>
|
||||
<target>Frais ajoutés</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/acceleration/accelerations-list/accelerations-list.component.html</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
@ -6739,7 +6739,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="date-base.just-now" datatype="html">
|
||||
<source>Just now</source>
|
||||
<target>Juste maintenant</target>
|
||||
<target>À l'instant</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/time/time.component.ts</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
@ -9847,7 +9847,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="8e623d3cfecb7c560c114390db53c1f430ffd0de" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> confirmation</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>confirmation</target>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> confirmation</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/shared/components/confirmations/confirmations.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
|
@ -84,11 +84,11 @@ pkg install -y zsh sudo git screen curl wget neovim rsync nginx openssl openssh-
|
||||
|
||||
### Node.js + npm
|
||||
|
||||
Build Node.js v20.17.0 and npm v9 from source using `nvm`:
|
||||
Build Node.js v22.14.0 and npm v9 from source using `nvm`:
|
||||
```
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | zsh
|
||||
source $HOME/.zshrc
|
||||
nvm install v20.17.0 --shared-zlib
|
||||
nvm install v22.14.0 --shared-zlib
|
||||
nvm alias default node
|
||||
```
|
||||
|
||||
|
@ -378,7 +378,7 @@ ELEMENTS_REPO_URL=https://github.com/ElementsProject/elements
|
||||
ELEMENTS_REPO_NAME=elements
|
||||
ELEMENTS_REPO_BRANCH=master
|
||||
#ELEMENTS_LATEST_RELEASE=$(curl -s https://api.github.com/repos/ElementsProject/elements/releases/latest|grep tag_name|head -1|cut -d '"' -f4)
|
||||
ELEMENTS_LATEST_RELEASE=elements-23.2.6
|
||||
ELEMENTS_LATEST_RELEASE=elements-23.2.7
|
||||
echo -n '.'
|
||||
|
||||
BITCOIN_ELECTRS_REPO_URL=https://github.com/mempool/electrs
|
||||
@ -1116,8 +1116,8 @@ echo "[*] Installing nvm.sh from GitHub"
|
||||
osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh'
|
||||
|
||||
echo "[*] Building NodeJS via nvm.sh"
|
||||
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.12.0 --shared-zlib'
|
||||
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm alias default 20.12.0'
|
||||
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v22.14.0 --shared-zlib'
|
||||
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm alias default 22.14.0'
|
||||
|
||||
####################
|
||||
# Tor installation #
|
||||
@ -1565,7 +1565,7 @@ EOF
|
||||
osSudo "${UNFURL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh'
|
||||
|
||||
echo "[*] Building NodeJS via nvm.sh"
|
||||
osSudo "${UNFURL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.12.0 --shared-zlib'
|
||||
osSudo "${UNFURL_USER}" zsh -c 'source ~/.zshrc ; nvm install v22.14.0 --shared-zlib'
|
||||
|
||||
;;
|
||||
esac
|
||||
|
@ -159,6 +159,7 @@
|
||||
},
|
||||
"WALLETS": {
|
||||
"ENABLED": true,
|
||||
"AUTO": true,
|
||||
"WALLETS": ["BITB", "3350"]
|
||||
},
|
||||
"STRATUM": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env zsh
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
source "$NVM_DIR/nvm.sh"
|
||||
nvm use v20.12.0
|
||||
nvm use v22.14.0
|
||||
|
||||
# start all mempool backends that exist
|
||||
for site in mainnet mainnet-lightning testnet testnet-lightning testnet4 signet signet-lightning liquid liquidtestnet;do
|
||||
|
Loading…
Reference in New Issue
Block a user