mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Merge branch 'master' into nymkappa/health-check
This commit is contained in:
commit
3afd7911d4
56 changed files with 1190 additions and 1045 deletions
181
.github/workflows/docker_update_latest_tag.yml
vendored
181
.github/workflows/docker_update_latest_tag.yml
vendored
|
|
@ -1,181 +0,0 @@
|
|||
name: Docker - Update latest tag
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'The Docker image tag to pull'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
retag-and-push:
|
||||
strategy:
|
||||
matrix:
|
||||
service:
|
||||
- frontend
|
||||
- backend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
id: buildx
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Get source image manifest and SHAs
|
||||
id: source-manifest
|
||||
run: |
|
||||
set -e
|
||||
echo "Fetching source manifest..."
|
||||
MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:${{ github.event.inputs.tag }})
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No manifest found. Assuming single-arch image."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Original source manifest:"
|
||||
echo "$MANIFEST" | jq .
|
||||
|
||||
AMD64_SHA=$(echo "$MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest')
|
||||
ARM64_SHA=$(echo "$MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="arm64" and .platform.os=="linux") | .digest')
|
||||
|
||||
if [ -z "$AMD64_SHA" ] || [ -z "$ARM64_SHA" ]; then
|
||||
echo "Source image is not multi-arch (missing amd64 or arm64)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Source amd64 manifest digest: $AMD64_SHA"
|
||||
echo "Source arm64 manifest digest: $ARM64_SHA"
|
||||
|
||||
echo "amd64_sha=$AMD64_SHA" >> $GITHUB_OUTPUT
|
||||
echo "arm64_sha=$ARM64_SHA" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Pull and retag architecture-specific images
|
||||
run: |
|
||||
set -e
|
||||
|
||||
docker buildx inspect --bootstrap
|
||||
|
||||
# Remove any existing local images to avoid cache interference
|
||||
echo "Removing existing local images if they exist..."
|
||||
docker image rm ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:${{ github.event.inputs.tag }} || true
|
||||
|
||||
# Pull amd64 image by digest
|
||||
echo "Pulling amd64 image by digest..."
|
||||
docker pull --platform linux/amd64 ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }}
|
||||
PULLED_AMD64_MANIFEST_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} --format '{{index .RepoDigests 0}}' | cut -d@ -f2)
|
||||
PULLED_AMD64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} --format '{{.Id}}')
|
||||
echo "Pulled amd64 manifest digest: $PULLED_AMD64_MANIFEST_DIGEST"
|
||||
echo "Pulled amd64 image ID (sha256): $PULLED_AMD64_IMAGE_ID"
|
||||
|
||||
# Pull arm64 image by digest
|
||||
echo "Pulling arm64 image by digest..."
|
||||
docker pull --platform linux/arm64 ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }}
|
||||
PULLED_ARM64_MANIFEST_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} --format '{{index .RepoDigests 0}}' | cut -d@ -f2)
|
||||
PULLED_ARM64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} --format '{{.Id}}')
|
||||
echo "Pulled arm64 manifest digest: $PULLED_ARM64_MANIFEST_DIGEST"
|
||||
echo "Pulled arm64 image ID (sha256): $PULLED_ARM64_IMAGE_ID"
|
||||
|
||||
# Tag the images
|
||||
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64
|
||||
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64
|
||||
|
||||
# Verify tagged images
|
||||
TAGGED_AMD64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 --format '{{.Id}}')
|
||||
TAGGED_ARM64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 --format '{{.Id}}')
|
||||
echo "Tagged amd64 image ID (sha256): $TAGGED_AMD64_IMAGE_ID"
|
||||
echo "Tagged arm64 image ID (sha256): $TAGGED_ARM64_IMAGE_ID"
|
||||
|
||||
- name: Push architecture-specific images
|
||||
run: |
|
||||
set -e
|
||||
|
||||
echo "Pushing amd64 image..."
|
||||
docker push ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64
|
||||
PUSHED_AMD64_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 --format '{{index .RepoDigests 0}}' | cut -d@ -f2)
|
||||
echo "Pushed amd64 manifest digest (local): $PUSHED_AMD64_DIGEST"
|
||||
|
||||
# Fetch manifest from registry after push
|
||||
echo "Fetching pushed amd64 manifest from registry..."
|
||||
PUSHED_AMD64_REGISTRY_MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64)
|
||||
PUSHED_AMD64_REGISTRY_DIGEST=$(echo "$PUSHED_AMD64_REGISTRY_MANIFEST" | jq -r '.config.digest')
|
||||
echo "Pushed amd64 manifest digest (registry): $PUSHED_AMD64_REGISTRY_DIGEST"
|
||||
|
||||
echo "Pushing arm64 image..."
|
||||
docker push ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64
|
||||
PUSHED_ARM64_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 --format '{{index .RepoDigests 0}}' | cut -d@ -f2)
|
||||
echo "Pushed arm64 manifest digest (local): $PUSHED_ARM64_DIGEST"
|
||||
|
||||
# Fetch manifest from registry after push
|
||||
echo "Fetching pushed arm64 manifest from registry..."
|
||||
PUSHED_ARM64_REGISTRY_MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64)
|
||||
PUSHED_ARM64_REGISTRY_DIGEST=$(echo "$PUSHED_ARM64_REGISTRY_MANIFEST" | jq -r '.config.digest')
|
||||
echo "Pushed arm64 manifest digest (registry): $PUSHED_ARM64_REGISTRY_DIGEST"
|
||||
|
||||
- name: Create and push multi-arch manifest with original digests
|
||||
run: |
|
||||
set -e
|
||||
|
||||
echo "Creating multi-arch manifest with original digests..."
|
||||
docker manifest create ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest \
|
||||
${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} \
|
||||
${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }}
|
||||
|
||||
echo "Pushing multi-arch manifest..."
|
||||
docker manifest push ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest
|
||||
|
||||
- name: Clean up intermediate tags
|
||||
if: success()
|
||||
run: |
|
||||
docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 || true
|
||||
docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 || true
|
||||
docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:${{ github.event.inputs.tag }} || true
|
||||
|
||||
- name: Verify final manifest
|
||||
run: |
|
||||
set -e
|
||||
echo "Fetching final generated manifest..."
|
||||
FINAL_MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest)
|
||||
echo "Generated final manifest:"
|
||||
echo "$FINAL_MANIFEST" | jq .
|
||||
|
||||
FINAL_AMD64_SHA=$(echo "$FINAL_MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest')
|
||||
FINAL_ARM64_SHA=$(echo "$FINAL_MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="arm64" and .platform.os=="linux") | .digest')
|
||||
|
||||
echo "Final amd64 manifest digest: $FINAL_AMD64_SHA"
|
||||
echo "Final arm64 manifest digest: $FINAL_ARM64_SHA"
|
||||
|
||||
# Compare all digests
|
||||
echo "Comparing digests..."
|
||||
echo "Source amd64 digest: ${{ steps.source-manifest.outputs.amd64_sha }}"
|
||||
echo "Pulled amd64 manifest digest: $PULLED_AMD64_MANIFEST_DIGEST"
|
||||
echo "Pushed amd64 manifest digest (local): $PUSHED_AMD64_DIGEST"
|
||||
echo "Pushed amd64 manifest digest (registry): $PUSHED_AMD64_REGISTRY_DIGEST"
|
||||
echo "Final amd64 digest: $FINAL_AMD64_SHA"
|
||||
echo "Source arm64 digest: ${{ steps.source-manifest.outputs.arm64_sha }}"
|
||||
echo "Pulled arm64 manifest digest: $PULLED_ARM64_MANIFEST_DIGEST"
|
||||
echo "Pushed arm64 manifest digest (local): $PUSHED_ARM64_DIGEST"
|
||||
echo "Pushed arm64 manifest digest (registry): $PUSHED_ARM64_REGISTRY_DIGEST"
|
||||
echo "Final arm64 digest: $FINAL_ARM64_SHA"
|
||||
|
||||
if [ "$FINAL_AMD64_SHA" != "${{ steps.source-manifest.outputs.amd64_sha }}" ] || [ "$FINAL_ARM64_SHA" != "${{ steps.source-manifest.outputs.arm64_sha }}" ]; then
|
||||
echo "Error: Final manifest SHAs do not match source SHAs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully created multi-arch ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest from ${{ github.event.inputs.tag }}"
|
||||
41
.github/workflows/on-tag.yml
vendored
41
.github/workflows/on-tag.yml
vendored
|
|
@ -24,6 +24,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
name: Build and push to DockerHub
|
||||
outputs:
|
||||
image-digest-frontend: ${{ matrix.service == 'frontend' && steps.docker-build.outputs.digest || '' }}
|
||||
image-digest-backend: ${{ matrix.service == 'backend' && steps.docker-build.outputs.digest || '' }}
|
||||
steps:
|
||||
- name: Replace the current swap file
|
||||
shell: bash
|
||||
|
|
@ -99,15 +102,51 @@ jobs:
|
|||
${{ runner.os }}-buildx-${{ matrix.service }}-
|
||||
|
||||
- name: Run Docker buildx for ${{ matrix.service }} against tag
|
||||
id: docker-build
|
||||
run: |
|
||||
docker buildx build \
|
||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
||||
--cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
|
||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
|
||||
--build-context rustgbt=./rust \
|
||||
--build-context backend=./backend \
|
||||
--output "type=registry,push=true" \
|
||||
--build-arg commitHash=$SHORT_SHA \
|
||||
./${{ matrix.service }}/
|
||||
|
||||
tag-latest:
|
||||
needs: build
|
||||
if: ${{ needs.build.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
name: Tag images as latest
|
||||
strategy:
|
||||
matrix:
|
||||
service:
|
||||
- frontend
|
||||
- backend
|
||||
steps:
|
||||
- name: Set env variables
|
||||
run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Setup Docker buildx action
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
driver-opts: |
|
||||
network=host
|
||||
|
||||
- name: Login to Docker Hub
|
||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
|
||||
- name: Tag multi-arch image as latest for ${{ matrix.service }}
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
|
||||
${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG
|
||||
265
backend/package-lock.json
generated
265
backend/package-lock.json
generated
|
|
@ -12,7 +12,7 @@
|
|||
"dependencies": {
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^18.15.3",
|
||||
"axios": "1.10.0",
|
||||
"axios": "1.12.2",
|
||||
"bitcoinjs-lib": "~6.1.3",
|
||||
"crypto-js": "~4.2.0",
|
||||
"express": "~4.21.1",
|
||||
|
|
@ -2275,12 +2275,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
|
||||
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -2645,6 +2645,18 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
|
|
@ -2997,6 +3009,19 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
|
@ -3044,12 +3069,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
|
|
@ -3062,6 +3084,31 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
|
|
@ -3683,12 +3730,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -3774,15 +3823,20 @@
|
|||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -3800,6 +3854,18 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
|
|
@ -3874,11 +3940,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
|
|
@ -3928,10 +3994,10 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
|
|
@ -3939,10 +4005,13 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
|
|
@ -6041,6 +6110,14 @@
|
|||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/maxmind": {
|
||||
"version": "4.3.11",
|
||||
"resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.11.tgz",
|
||||
|
|
@ -9457,12 +9534,12 @@
|
|||
"integrity": "sha512-+H+kuK34PfMaI9PNU/NSjBKL5hh/KDM9J72kwYeYEm0A8B1AC4fuCy3qsjnA7lxklgyXsB68yn8Z2xoZEjgwCQ=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
|
||||
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -9744,6 +9821,15 @@
|
|||
"set-function-length": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
|
|
@ -9991,6 +10077,16 @@
|
|||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
|
@ -10029,18 +10125,34 @@
|
|||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
}
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
|
|
@ -10513,12 +10625,14 @@
|
|||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
|
|
@ -10576,15 +10690,20 @@
|
|||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-package-type": {
|
||||
|
|
@ -10593,6 +10712,15 @@
|
|||
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"requires": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
|
|
@ -10643,12 +10771,9 @@
|
|||
}
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
}
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
|
|
@ -10685,15 +10810,18 @@
|
|||
"es-define-property": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q=="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
|
|
@ -12245,6 +12373,11 @@
|
|||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
|
||||
},
|
||||
"maxmind": {
|
||||
"version": "4.3.11",
|
||||
"resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.11.tgz",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
"dependencies": {
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^18.15.3",
|
||||
"axios": "1.10.0",
|
||||
"axios": "1.12.2",
|
||||
"bitcoinjs-lib": "~6.1.3",
|
||||
"crypto-js": "~4.2.0",
|
||||
"express": "~4.21.1",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export interface AbstractBitcoinApi {
|
|||
$getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]>;
|
||||
$getAllMempoolTransactions(lastTxid?: string, max_txs?: number);
|
||||
$getTransactionHex(txId: string): Promise<string>;
|
||||
$getTransactionMerkleProof(txId: string): Promise<IEsploraApi.MerkleProof>;
|
||||
$getBlockHeightTip(): Promise<number>;
|
||||
$getBlockHashTip(): Promise<string>;
|
||||
$getTxIdsForBlock(hash: string): Promise<string[]>;
|
||||
|
|
|
|||
|
|
@ -95,6 +95,10 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||
});
|
||||
}
|
||||
|
||||
$getTransactionMerkleProof(txId: string): Promise<IEsploraApi.MerkleProof> {
|
||||
throw new Error('Method getTransactionMerkleProof not supported by the Bitcoin RPC API.');
|
||||
}
|
||||
|
||||
$getBlockHeightTip(): Promise<number> {
|
||||
return this.bitcoindClient.getBlockCount();
|
||||
}
|
||||
|
|
@ -294,7 +298,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||
is_coinbase: !!vin.coinbase,
|
||||
prevout: null,
|
||||
scriptsig: vin.scriptSig && vin.scriptSig.hex || vin.coinbase || '',
|
||||
scriptsig_asm: vin.scriptSig && transactionUtils.convertScriptSigAsm(vin.scriptSig.hex) || '',
|
||||
scriptsig_asm: vin.scriptSig ? transactionUtils.convertScriptSigAsm(vin.scriptSig.hex) : (vin.coinbase ? transactionUtils.convertScriptSigAsm(vin.coinbase) : ''),
|
||||
sequence: vin.sequence,
|
||||
txid: vin.txid || '',
|
||||
vout: vin.vout || 0,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { calculateMempoolTxCpfp } from '../cpfp';
|
|||
import { handleError } from '../../utils/api';
|
||||
import poolsUpdater from '../../tasks/pools-updater';
|
||||
import BlocksRepository from '../../repositories/BlocksRepository';
|
||||
import chainTips from '../chain-tips';
|
||||
|
||||
const TXID_REGEX = /^[a-f0-9]{64}$/i;
|
||||
const BLOCK_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
||||
|
|
@ -56,6 +57,7 @@ class BitcoinRoutes {
|
|||
.post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'chain-tips', this.getChainTips.bind(this))
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'prevouts', this.$getPrevouts)
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'cpfp', this.getCpfpLocalTxs)
|
||||
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
||||
|
|
@ -77,6 +79,7 @@ class BitcoinRoutes {
|
|||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', this.getTransactionStatus)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', this.getTransactionOutspends)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/merkle-proof', this.getTransactionMerkleProof)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'txs/outspends', this.$getBatchedOutspends)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', this.getBlockHeader)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/hash', this.getBlockTipHash)
|
||||
|
|
@ -608,6 +611,26 @@ class BitcoinRoutes {
|
|||
}
|
||||
}
|
||||
|
||||
private async getChainTips(req: Request, res: Response) {
|
||||
try {
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
const tips = await chainTips.getChainTips();
|
||||
if (tips.length > 0) {
|
||||
res.json(tips);
|
||||
} else {
|
||||
handleError(req, res, 503, `Temporarily unavailable`);
|
||||
return;
|
||||
}
|
||||
} else { // Liquid
|
||||
handleError(req, res, 404, `This API is only available for Bitcoin networks`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get chain tips');
|
||||
}
|
||||
}
|
||||
|
||||
private async getLegacyBlocks(req: Request, res: Response) {
|
||||
try {
|
||||
const returnBlocks: IEsploraApi.Block[] = [];
|
||||
|
|
@ -1006,6 +1029,19 @@ class BitcoinRoutes {
|
|||
}
|
||||
}
|
||||
|
||||
private async getTransactionMerkleProof(req: Request, res: Response): Promise<void> {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await bitcoinApi.$getTransactionMerkleProof(req.params.txId);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : 'Failed to get transaction merkle proof');
|
||||
}
|
||||
}
|
||||
|
||||
private getDifficultyChange(req: Request, res: Response) {
|
||||
try {
|
||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||
|
|
|
|||
|
|
@ -197,6 +197,11 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
|||
}
|
||||
}
|
||||
|
||||
async $getTransactionMerkleProof(txId: string): Promise<IEsploraApi.MerkleProof> {
|
||||
const tx = await this.$getRawTransaction(txId);
|
||||
return this.electrumClient.blockchainTransaction_getMerkle(txId, tx.status.block_height);
|
||||
}
|
||||
|
||||
private $getScriptHashBalance(scriptHash: string): Promise<IElectrumApi.ScriptHashBalance> {
|
||||
return this.electrumClient.blockchainScripthash_getBalance(this.encodeScriptHash(scriptHash));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,4 +186,10 @@ export namespace IEsploraApi {
|
|||
time: number;
|
||||
tx_position?: number;
|
||||
}
|
||||
|
||||
export interface MerkleProof {
|
||||
merkle: string[];
|
||||
block_height: number;
|
||||
pos: number;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import config from '../../config';
|
||||
import axios, { isAxiosError } from 'axios';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import logger from '../../logger';
|
||||
|
|
@ -23,6 +24,7 @@ interface FailoverHost {
|
|||
publicDomain: string,
|
||||
hashes: {
|
||||
frontend?: string,
|
||||
hybrid?: string,
|
||||
backend?: string,
|
||||
electrs?: string,
|
||||
lastUpdated: number,
|
||||
|
|
@ -142,7 +144,8 @@ class FailoverRouter {
|
|||
if (Date.now() - host.hashes.lastUpdated > this.gitHashInterval) {
|
||||
await Promise.all([
|
||||
this.$updateFrontendGitHash(host),
|
||||
this.$updateBackendGitHash(host)
|
||||
this.$updateBackendGitHash(host),
|
||||
config.MEMPOOL.OFFICIAL ? this.$updateHybridGitHash(host) : Promise.resolve(),
|
||||
]);
|
||||
host.hashes.lastUpdated = Date.now();
|
||||
}
|
||||
|
|
@ -251,6 +254,47 @@ class FailoverRouter {
|
|||
if (match && match[1]?.length) {
|
||||
host.hashes.frontend = match[1];
|
||||
}
|
||||
const hybridMatch = response.data.match(/GIT_COMMIT_HASH_MEMPOOL_SPACE\s*=\s*['"](.*?)['"]/);
|
||||
if (hybridMatch && hybridMatch[1]?.length) {
|
||||
host.hashes.hybrid = hybridMatch[1];
|
||||
}
|
||||
} catch (e) {
|
||||
// failed to get frontend build hash - do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private async $updateHybridGitHash(host: FailoverHost): Promise<void> {
|
||||
try {
|
||||
const response: string = await new Promise((resolve, reject) => {
|
||||
const req = https.request({
|
||||
hostname: host.publicDomain.replace('https://', '').replace('http://', ''),
|
||||
port: 443,
|
||||
path: '/en-US/resources/config.js',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Host': 'mempool.space'
|
||||
},
|
||||
timeout: config.ESPLORA.FALLBACK_TIMEOUT,
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 200) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(new Error(`Failed to get hybrid git hash: ${res.statusCode}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
const match = response.match(/GIT_COMMIT_HASH_MEMPOOL_SPACE\s*=\s*['"](.*?)['"]/);
|
||||
if (match && match[1]?.length) {
|
||||
host.hashes.hybrid = match[1];
|
||||
}
|
||||
} catch (e) {
|
||||
// failed to get frontend build hash - do nothing
|
||||
}
|
||||
|
|
@ -352,6 +396,10 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||
return this.failoverRouter.$get<string>('/tx/' + txId + '/hex');
|
||||
}
|
||||
|
||||
$getTransactionMerkleProof(txId: string): Promise<IEsploraApi.MerkleProof> {
|
||||
return this.failoverRouter.$get<IEsploraApi.MerkleProof>('/tx/' + txId + '/merkle-proof');
|
||||
}
|
||||
|
||||
$getBlockHeightTip(): Promise<number> {
|
||||
return this.failoverRouter.$get<number>('/blocks/tip/height');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ class ChainTips {
|
|||
|
||||
return this.orphansByHeight[height] || [];
|
||||
}
|
||||
|
||||
public getChainTips(): ChainTip[] {
|
||||
return this.chainTips;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChainTips();
|
||||
|
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 100;
|
||||
private static currentVersion = 101;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
|
|
@ -1299,6 +1299,10 @@ class DatabaseMigration {
|
|||
queries.push(`DELETE FROM state WHERE name = 'last_weekly_hashrates_indexing'`);
|
||||
}
|
||||
|
||||
if (version < 101) {
|
||||
queries.push(`DELETE FROM prices WHERE USD = -1`);
|
||||
}
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -180,7 +180,6 @@ class WalletApi {
|
|||
|
||||
// update list of treasuries
|
||||
const treasuriesResponse = await axios.get(config.MEMPOOL_SERVICES.API + `/treasuries`);
|
||||
console.log(treasuriesResponse.data);
|
||||
this.treasuries = treasuriesResponse.data || [];
|
||||
} catch (e) {
|
||||
logger.err(`Error updating active wallets: ${(e instanceof Error ? e.message : e)}`);
|
||||
|
|
|
|||
|
|
@ -887,7 +887,9 @@ class BlocksRepository {
|
|||
SELECT UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.height
|
||||
FROM blocks
|
||||
LEFT JOIN blocks_prices ON blocks.height = blocks_prices.height
|
||||
LEFT JOIN prices ON blocks_prices.price_id = prices.id
|
||||
WHERE blocks_prices.height IS NULL
|
||||
OR prices.id IS NULL
|
||||
ORDER BY blocks.height
|
||||
`);
|
||||
return rows;
|
||||
|
|
@ -907,6 +909,7 @@ class BlocksRepository {
|
|||
query += ` (${price.height}, ${price.priceId}),`;
|
||||
}
|
||||
query = query.slice(0, -1);
|
||||
query += ` ON DUPLICATE KEY UPDATE price_id = VALUES(price_id)`;
|
||||
await DB.query(query);
|
||||
} catch (e: any) {
|
||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||
|
|
|
|||
|
|
@ -296,6 +296,7 @@ class PricesRepository {
|
|||
id,
|
||||
USD
|
||||
FROM prices
|
||||
WHERE USD >= 0
|
||||
ORDER BY time
|
||||
`);
|
||||
return times as {time: number, id: number, USD: number}[];
|
||||
|
|
@ -305,6 +306,7 @@ class PricesRepository {
|
|||
const [rates] = await DB.query(`
|
||||
SELECT ${ApiPriceFields}
|
||||
FROM prices
|
||||
WHERE USD >= 0
|
||||
ORDER BY time DESC
|
||||
LIMIT 1`
|
||||
);
|
||||
|
|
@ -321,6 +323,7 @@ class PricesRepository {
|
|||
SELECT ${ApiPriceFields}
|
||||
FROM prices
|
||||
WHERE UNIX_TIMESTAMP(time) < ?
|
||||
AND USD >= 0
|
||||
ORDER BY time DESC
|
||||
LIMIT 1`,
|
||||
[timestamp]
|
||||
|
|
@ -332,6 +335,7 @@ class PricesRepository {
|
|||
const [latestPrices] = await DB.query(`
|
||||
SELECT ${ApiPriceFields}
|
||||
FROM prices
|
||||
WHERE USD >= 0
|
||||
ORDER BY time DESC
|
||||
LIMIT 1
|
||||
`);
|
||||
|
|
@ -429,6 +433,7 @@ class PricesRepository {
|
|||
const [rates] = await DB.query(`
|
||||
SELECT ${ApiPriceFields}
|
||||
FROM prices
|
||||
WHERE USD >= 0
|
||||
ORDER BY time DESC
|
||||
`);
|
||||
if (!Array.isArray(rates)) {
|
||||
|
|
|
|||
|
|
@ -271,11 +271,6 @@ class PriceUpdater {
|
|||
}
|
||||
|
||||
this.latestPrices.time = Math.round(new Date().getTime() / 1000);
|
||||
logger.info(`Latest BTC fiat averaged price: ${JSON.stringify(this.latestPrices)}`);
|
||||
|
||||
if (this.ratesChangedCallback) {
|
||||
this.ratesChangedCallback(this.latestPrices);
|
||||
}
|
||||
|
||||
if (!forceUpdate) {
|
||||
this.cyclePosition++;
|
||||
|
|
@ -283,6 +278,13 @@ class PriceUpdater {
|
|||
|
||||
if (this.latestPrices.USD === -1) {
|
||||
this.latestPrices = await PricesRepository.$getLatestConversionRates();
|
||||
logger.warn(`No BTC price available, falling back to latest known price: ${JSON.stringify(this.latestPrices)}`);
|
||||
} else {
|
||||
logger.info(`Latest BTC fiat averaged price: ${JSON.stringify(this.latestPrices)}`);
|
||||
}
|
||||
|
||||
if (this.ratesChangedCallback && this.latestPrices.USD > 0) {
|
||||
this.ratesChangedCallback(this.latestPrices);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export async function query(path, throwOnFail: boolean = false): Promise<object
|
|||
headers: {
|
||||
'User-Agent': (config.MEMPOOL.USER_AGENT === 'mempool') ? `mempool/v${backendInfo.getBackendInfo().version}` : `${config.MEMPOOL.USER_AGENT}`
|
||||
},
|
||||
timeout: config.SOCKS5PROXY.ENABLED ? 30000 : 10000
|
||||
timeout: config.SOCKS5PROXY.ENABLED ? 30000 : 20000
|
||||
};
|
||||
let retry = 0;
|
||||
let lastError: any = null;
|
||||
|
|
|
|||
1266
frontend/package-lock.json
generated
1266
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -116,7 +116,7 @@
|
|||
"cypress-fail-on-console-error": "~5.1.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"mock-socket": "~9.3.1",
|
||||
"start-server-and-test": "~2.0.0"
|
||||
"start-server-and-test": "~2.1.0"
|
||||
},
|
||||
"scarfSettings": {
|
||||
"enabled": false
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import { MempoolBlockViewComponent } from '@components/mempool-block-view/mempoo
|
|||
import { ClockComponent } from '@components/clock/clock.component';
|
||||
import { StatusViewComponent } from '@components/status-view/status-view.component';
|
||||
import { AddressGroupComponent } from '@components/address-group/address-group.component';
|
||||
import { TrackerComponent } from '@components/tracker/tracker.component';
|
||||
import { AccelerateCheckout } from '@components/accelerate-checkout/accelerate-checkout.component';
|
||||
import { TrackerGuard } from '@app/route-guards';
|
||||
|
||||
const browserWindow = window || {};
|
||||
|
|
|
|||
|
|
@ -140,14 +140,11 @@
|
|||
<span>Metaplanet</span>
|
||||
</a>
|
||||
<a href="https://bullbitcoin.com/" target="_blank" title="Bull Bitcoin">
|
||||
<svg aria-hidden="true" class="image" viewBox="0 -5 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#a)" fill-rule="evenodd" clip-rule="evenodd" fill="#e21924">
|
||||
<path d="M21.92 14.59a1.18 1.18 0 0 0-1.18-1.18h-1.82v2.36h1.82a1.18 1.18 0 0 0 1.18-1.18ZM21 17.07h-2v2.45h2a1.23 1.23 0 1 0 0-2.45Z"/>
|
||||
<path d="M36.43 0 35 5.59l-8 2.64-2.43-3.61-4.74 2.05-4.74-2.05-2.43 3.61-8-2.64L3.21 0 0 7.86l7.89 5.86-5.56 4 5.56 1.12 2.69-.49v3.17l3.59 4.38.68 3.19 5 2.87 5-2.87.68-3.19 3.59-4.38v-3.17l2.7.49 5.56-1.12-5.56-4 7.89-5.86zM24.69 18.45a2.5 2.5 0 0 1-2.5 2.5h-1.11v1.56h-1.26V21h-.9v1.56h-1.27V21H15.3v-1.42h.64a.9.9 0 0 0 .9-.9V14.3a.901.901 0 0 0-.9-.91h-.64V12h2.35v-1.5h1.27V12h.9v-1.5h1.26V12h.68A2.269 2.269 0 0 1 24 14.31a2.25 2.25 0 0 1-.92 1.82 2.52 2.52 0 0 1 1.58 2.32z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a"><path fill="#fff" d="M0 0h160v32H0z"/></clipPath>
|
||||
</defs>
|
||||
<svg width="80" version="1.1" viewBox="0 0 1128 1052.59" xmlns="http://www.w3.org/2000/svg">
|
||||
<path transform="matrix(.13333 0 0 -.13333 0 952.59)" d="m4322.7 2375.3-38.64 2787.8 931.44 396 506.88-738.72 1706.8 581.4-474.93 1742.6 1506-2083.5-1805.8-1329.1 1015.4-775.2-1035.4-258.72-482.22 126.18-1829.7-448.74" style="fill:#b7000b"/>
|
||||
<path transform="matrix(.13333 0 0 -.13333 0 952.59)" d="m4137.1 2378.5 40.86 2782.8-932.64 398.04-505.92-737.76-1709.1 580.92 475.26 1741.3-1505.6-2083.1 1805.6-1328.2-1015.7-775.92 1035.8-259.08 485.76 127.56 1825.6-446.52" style="fill:#b7000b"/>
|
||||
<path transform="matrix(.13333 0 0 -.13333 0 952.59)" d="m2345.9 2694.6 5.04-585.36 722.52-883.08 142.56-656.64 958.32-569.52-15.36 1765.7-1813.1 928.92" style="fill:#b7000b"/>
|
||||
<path transform="matrix(.13333 0 0 -.13333 0 952.59)" d="m6114.4 2694.6-5.04-585.36-722.52-883.08-142.56-656.64-958.32-569.52 15.36 1765.7 1813.1 928.92" style="fill:#b7000b"/>
|
||||
</svg>
|
||||
<span>Bull Bitcoin</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -474,19 +474,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
if (this.processing) {
|
||||
return;
|
||||
}
|
||||
if (this.conversionsSubscription) {
|
||||
this.conversionsSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.processing = true;
|
||||
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||
async (conversions) => {
|
||||
this.conversions = conversions;
|
||||
|
||||
if (this.applePay) {
|
||||
this.applePay.destroy();
|
||||
}
|
||||
|
||||
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
||||
const costUSD = this.cost / 100_000_000 * this.conversions.USD;
|
||||
const paymentRequest = this.payments.paymentRequest({
|
||||
countryCode: 'US',
|
||||
currencyCode: 'USD',
|
||||
|
|
@ -585,8 +580,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
this.processing = false;
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -596,19 +589,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
if (this.processing) {
|
||||
return;
|
||||
}
|
||||
if (this.conversionsSubscription) {
|
||||
this.conversionsSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.processing = true;
|
||||
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||
async (conversions) => {
|
||||
this.conversions = conversions;
|
||||
|
||||
if (this.googlePay) {
|
||||
this.googlePay.destroy();
|
||||
}
|
||||
|
||||
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
||||
const costUSD = this.cost / 100_000_000 * this.conversions.USD;
|
||||
const paymentRequest = this.payments.paymentRequest({
|
||||
countryCode: 'US',
|
||||
currencyCode: 'USD',
|
||||
|
|
@ -710,8 +698,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
this.isCheckoutLocked--;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -721,16 +707,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
if (this.processing) {
|
||||
return;
|
||||
}
|
||||
if (this.conversionsSubscription) {
|
||||
this.conversionsSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.processing = true;
|
||||
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||
async (conversions) => {
|
||||
this.conversions = conversions;
|
||||
|
||||
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
||||
const costUSD = this.cost / 100_000_000 * this.conversions.USD;
|
||||
if (this.isCheckoutLocked > 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -815,8 +795,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
this.isCheckoutLocked--;
|
||||
this.isTokenizing--;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -826,20 +804,15 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
if (this.processing) {
|
||||
return;
|
||||
}
|
||||
if (this.conversionsSubscription) {
|
||||
this.conversionsSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.processing = true;
|
||||
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||
async (conversions) => {
|
||||
this.conversions = conversions;
|
||||
|
||||
if (this.cashAppPay) {
|
||||
this.cashAppPay.destroy();
|
||||
}
|
||||
|
||||
const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`;
|
||||
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
||||
const costUSD = this.cost / 100_000_000 * this.conversions.USD;
|
||||
const paymentRequest = this.payments.paymentRequest({
|
||||
countryCode: 'US',
|
||||
currencyCode: 'USD',
|
||||
|
|
@ -902,8 +875,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container-lg widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
||||
<div class="container-lg widget-container p-0" [class.widget]="widget" [class.full-height]="!widget">
|
||||
<h1 *ngIf="!widget" class="float-left" i18n="accelerator.accelerations">Accelerations</h1>
|
||||
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ tr, td, th {
|
|||
}
|
||||
|
||||
.fee {
|
||||
width: 30%;
|
||||
width: 25%;
|
||||
text-align: end !important;
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ tr, td, th {
|
|||
}
|
||||
|
||||
.status {
|
||||
width: 20%
|
||||
width: 25%
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@
|
|||
|
||||
<ng-template #volumeRow>
|
||||
<td i18n="address.total-received">Total received</td>
|
||||
<td *ngIf="chainStats.funded_txo_sum !== undefined; else confidentialTd" class="wrap-cell"><app-amount [satoshis]="chainStats.totalReceived"></app-amount></td>
|
||||
<td *ngIf="chainStats.funded_txo_sum !== undefined; else confidentialTd" class="wrap-cell"><app-amount [satoshis]="chainStats.totalReceived" [noFiat]="true"></app-amount></td>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #typeRow>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.any-mode]="filterMode === 'or'" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
|
||||
<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.any-mode]="filterMode === 'or'" [class.none-mode]="filterMode === 'nor'" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
|
||||
<a *ngIf="menuOpen" [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-mempool-goggles-work" class="info-badges float-right" i18n-ngbTooltip="Mempool Goggles™ tooltip" ngbTooltip="select filter categories to highlight matching transactions">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
|
||||
</a>
|
||||
|
|
@ -23,6 +23,9 @@
|
|||
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
||||
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')"><ng-container i18n="mempool-goggles.any">Any</ng-container>
|
||||
</label>
|
||||
<label class="btn btn-xs red mode-toggle" [class.active]="filterMode === 'nor'">
|
||||
<input type="radio" [value]="'nor'" fragment="nor" (click)="setFilterMode('nor')"><ng-container i18n="mempool-goggles.none">None</ng-container>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-element">
|
||||
|
|
|
|||
|
|
@ -94,6 +94,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.none-mode {
|
||||
.filter-tag {
|
||||
border: solid 1px var(--danger);
|
||||
&.selected {
|
||||
background-color: var(--danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
font-size: 0.9em;
|
||||
margin-right: 0.25em;
|
||||
|
|
@ -126,6 +135,12 @@
|
|||
background: var(--success);
|
||||
}
|
||||
}
|
||||
&.red {
|
||||
border: solid 1px var(--danger);
|
||||
&.active {
|
||||
background: var(--danger);
|
||||
}
|
||||
}
|
||||
&.yellow {
|
||||
border: solid 1px #bf7815;
|
||||
&.active {
|
||||
|
|
|
|||
|
|
@ -656,7 +656,19 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||
|
||||
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
||||
return (tx: TxView) => {
|
||||
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
||||
let matches = false;
|
||||
switch (this.filterMode) {
|
||||
case 'and':
|
||||
matches = (tx.bigintFlags & flags) === flags;
|
||||
break;
|
||||
case 'or':
|
||||
matches = flags === 0n || (tx.bigintFlags & flags) > 0n;
|
||||
break;
|
||||
case 'nor':
|
||||
matches = (tx.bigintFlags & flags) === 0n;
|
||||
break;
|
||||
}
|
||||
if (matches) {
|
||||
if (this.themeService.theme !== 'contrast' && this.themeService.theme !== 'bukele') {
|
||||
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
<th class="rtt only-small">RTT</th>
|
||||
<th class="rtt only-large">RTT</th>
|
||||
<th class="height">Height</th>
|
||||
<th class="hybrid only-large">Hybrid</th>
|
||||
<th class="frontend only-large">Front</th>
|
||||
<th class="backend only-large">Back</th>
|
||||
<th class="electrs only-large">Electrs</th>
|
||||
|
|
@ -31,10 +32,10 @@
|
|||
<td class="rtt only-small">{{ (host.rtt / 1000) | number : '1.1-1' }} {{ host.rtt == null ? '' : 's'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
|
||||
<td class="rtt only-large">{{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
|
||||
<td class="height">{{ host.latestHeight }} {{ !host.checked ? '⏳' : (host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < maxHeight ? '🟧' : '✅')) }}</td>
|
||||
<ng-container *ngFor="let type of ['frontend', 'backend', 'electrs']">
|
||||
<ng-container *ngFor="let type of ['hybrid', 'frontend', 'backend', 'electrs']">
|
||||
<td class="{{type}} only-large" [style.background-color]="host.hashes?.[type] ? '#' + host.hashes[type].slice(0, 6) : ''">
|
||||
@if (host.hashes?.[type]) {
|
||||
<a [style.color]="'white'" href="https://github.com/mempool/{{type === 'electrs' ? 'electrs' : 'mempool'}}/commit/{{ host.hashes[type] }}" target="_blank">{{ host.hashes[type].slice(0, 8) || '?' }}</a>
|
||||
<a [style.color]="'white'" href="https://github.com/mempool/{{repoMap[type]}}/commit/{{ host.hashes[type] }}" target="_blank">{{ host.hashes[type].slice(0, 8) || '?' }}</a>
|
||||
} @else {
|
||||
<span>?</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ export class ServerHealthComponent implements OnInit {
|
|||
interval: number;
|
||||
now: number = Date.now();
|
||||
|
||||
repoMap = {
|
||||
frontend: 'mempool',
|
||||
hybrid: 'mempool.space',
|
||||
backend: 'mempool',
|
||||
electrs: 'electrs',
|
||||
};
|
||||
|
||||
constructor(
|
||||
private websocketService: WebsocketService,
|
||||
private stateService: StateService,
|
||||
|
|
|
|||
|
|
@ -326,9 +326,18 @@ export class TaprootAddressScriptsComponent implements OnChanges {
|
|||
|
||||
let hiddenScriptsMessage = '';
|
||||
if (node.tooltip[0].label === 'Hash') {
|
||||
const remaining = 128 - (node.depth ?? 0);
|
||||
let upperBoundHtml: string;
|
||||
if (remaining === 0) {
|
||||
upperBoundHtml = '1';
|
||||
} else if (remaining <= 39) {
|
||||
upperBoundHtml = (2 ** remaining).toLocaleString();
|
||||
} else {
|
||||
upperBoundHtml = `2<sup style="font-size: 0.85em;">${remaining}</sup>`;
|
||||
}
|
||||
hiddenScriptsMessage = `
|
||||
<div style="margin-top: 8px; color: #888; font-size: 11px; line-height: 1.3; font-style: italic; border-top: 1px solid #333; padding-top: 6px; word-break: break-word; white-space: normal">
|
||||
This node might commit to one or more scripts that have not been revealed yet.
|
||||
This node might commit to ${upperBoundHtml === '1' ? 'exactly 1 script' : `at most ${upperBoundHtml} scripts`}.
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -341,7 +341,22 @@
|
|||
@if (vout.isRunestone) {
|
||||
<button (click)="toggleOrdData(tx.txid, 'vout', vindex)" type="button" class="btn btn-sm badge badge-ord">Runestone</button>
|
||||
} @else {
|
||||
<a placement="bottom" [ngbTooltip]="vout.scriptpubkey_asm | slice:0:200 | hex2ascii"><span *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" class="badge badge-secondary scriptmessage">{{ vout.scriptpubkey_asm | slice:0:200 | hex2ascii }}</span></a>
|
||||
<a placement="bottom" style="cursor: pointer;">
|
||||
@if ((vout.scriptpubkey_asm | hex2ascii).length > 200) {
|
||||
<span *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" class="badge badge-secondary scriptmessage opreturn-msg" (click)="toggleShowFullOpReturnPreview(vindex)">{{ vout.scriptpubkey_asm | hex2ascii | slice:0:200 }}</span>
|
||||
<ng-container *ngIf="(showDetails$ | async) === false">
|
||||
@if ((vout.scriptpubkey_asm | hex2ascii).length > 200) {
|
||||
@if (!showFullOpReturnPreview[vindex]) {
|
||||
<span class="badge badge-primary" (click)="toggleShowFullOpReturnPreview(vindex)">Show more</span>
|
||||
} @else {
|
||||
<span class="badge badge-primary" (click)="toggleShowFullOpReturnPreview(vindex)">Show less</span>
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
} @else {
|
||||
<span *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" [ngbTooltip]="vout.scriptpubkey_asm | hex2ascii" class="badge badge-secondary scriptmessage">{{ vout.scriptpubkey_asm | hex2ascii }}</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
</ng-template>
|
||||
<ng-template #otherPubkeyType>{{ vout.scriptpubkey_type | scriptpubkeyType }}</ng-template>
|
||||
|
|
@ -392,6 +407,15 @@
|
|||
<app-ord-data [runestone]="showOrdData[tx.txid + '-vout-' + vindex]['runestone']" [runeInfo]="showOrdData[tx.txid + '-vout-' + vindex]['runeInfo']" [type]="'vout'"></app-ord-data>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="vout.scriptpubkey_type === 'op_return' && showFullOpReturnPreview[vindex]" [ngClass]="{
|
||||
'assetBox': assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex,
|
||||
'highlight': addresses?.length && (addresses.includes(vout.scriptpubkey_address)),
|
||||
'sigged': selectedSig && selectedSig.txIndex === i && sigHighlights.vout[vindex],
|
||||
}">
|
||||
<td colspan="3" style="white-space: pre-line;">
|
||||
{{ vout.scriptpubkey_asm | hex2ascii | slice:0:8000 }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="(showDetails$ | async) === true">
|
||||
<td [attr.colspan]="3" class="details-container">
|
||||
<table class="table table-striped table-borderless details-table mb-3">
|
||||
|
|
@ -428,7 +452,7 @@
|
|||
</tr>
|
||||
<tr *ngIf="vout.scriptpubkey_type == 'op_return'">
|
||||
<td>OP_RETURN <span>data</span></td>
|
||||
<td style="text-align: left; word-break: break-word;">
|
||||
<td style="text-align: left; word-break: break-word; white-space: pre-line;">
|
||||
@if (!showFullOpReturnData[vindex]) {
|
||||
{{ (vout.scriptpubkey_asm | hex2ascii | slice:0:1000) }}
|
||||
} @else {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
.col {
|
||||
&:first-child {
|
||||
padding-right: 0;
|
||||
@media (max-width: 992px) {
|
||||
padding-right: 15px;
|
||||
}
|
||||
td:last-child {
|
||||
padding-right: calc(0.3em + 15px);
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
padding-left: 0;
|
||||
@media (max-width: 992px) {
|
||||
padding-left: 15px;
|
||||
}
|
||||
td:first-child {
|
||||
padding-left: calc(0.3em + 15px);
|
||||
}
|
||||
|
|
@ -324,3 +330,25 @@ h2 {
|
|||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.scriptmessage.opreturn-msg {
|
||||
max-width: 20px;
|
||||
@media (min-width: 476px) {
|
||||
max-width: 110px;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
max-width: 190px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
max-width: 330px;
|
||||
}
|
||||
@media (min-width: 850px) {
|
||||
max-width: 452px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
max-width: 120px;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
showFullScriptPubkeyAsm: { [voutIndex: number]: boolean } = {};
|
||||
showFullScriptPubkeyHex: { [voutIndex: number]: boolean } = {};
|
||||
showFullOpReturnData: { [voutIndex: number]: boolean } = {};
|
||||
showFullOpReturnPreview: { [voutIndex: 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();
|
||||
|
||||
|
|
@ -342,11 +343,11 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
const isScriptSpend = vin.witness.length > (hasAnnex ? 2 : 1);
|
||||
if (isScriptSpend) {
|
||||
const controlBlock = hasAnnex ? vin.witness[vin.witness.length - 2] : vin.witness[vin.witness.length - 1];
|
||||
const scriptHex = hasAnnex ? vin.witness[vin.witness.length - 3] : vin.witness[vin.witness.length - 2];
|
||||
const scriptHex = hasAnnex ? vin.witness[vin.witness.length - 3] : vin.witness[vin.witness.length - 2]; // bip341 script element
|
||||
const tapleafVersion = parseInt(controlBlock.slice(0, 2), 16) & 0xfe;
|
||||
// simplicity script spend
|
||||
if (tapleafVersion === 0xbe) {
|
||||
vin.inner_simplicityscript = scriptHex;
|
||||
vin.inner_simplicityscript = vin.witness[1]; // simplicity program is the second witness element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -551,6 +552,10 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.showFullOpReturnData[voutIndex] = !this.showFullOpReturnData[voutIndex];
|
||||
}
|
||||
|
||||
toggleShowFullOpReturnPreview(voutIndex: number): void {
|
||||
this.showFullOpReturnPreview[voutIndex] = !this.showFullOpReturnPreview[voutIndex];
|
||||
}
|
||||
|
||||
toggleOrdData(txid: string, type: 'vin' | 'vout', index: number) {
|
||||
const tx = this.transactions.find((tx) => tx.txid === txid);
|
||||
if (!tx) {
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export class TreasuriesGraphComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
this.seriesNameToLabel = {};
|
||||
for (const treasury of this.treasuries) {
|
||||
this.seriesNameToLabel[treasury.wallet] = treasury.enterprise || treasury.name;
|
||||
this.seriesNameToLabel[treasury.wallet] = treasury.name || treasury.enterprise || treasury.wallet;
|
||||
}
|
||||
|
||||
const legendData = this.treasuries.map(treasury => ({
|
||||
|
|
@ -301,7 +301,7 @@ export class TreasuriesGraphComponent implements OnInit, OnChanges, OnDestroy {
|
|||
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${markerColor};"></span>`;
|
||||
|
||||
tooltip += `<div style="display: flex; justify-content: space-between;">
|
||||
<span style="text-align: left; margin-right: 10px;">${marker} ${treasury.enterprise || treasury.name}:</span>
|
||||
<span style="text-align: left; margin-right: 10px;">${marker} ${treasury.name || treasury.enterprise || treasury.wallet}:</span>
|
||||
<span style="text-align: right;">${this.formatBTC(balance)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export class TreasuriesPieComponent implements OnChanges {
|
|||
}[] = this.treasuries.map((treasury, index) => ({
|
||||
treasury,
|
||||
id: treasury.wallet,
|
||||
label: treasury.enterprise || treasury.name,
|
||||
label: treasury.name || treasury.enterprise || treasury.wallet,
|
||||
balance: this.walletBalance[treasury.wallet],
|
||||
share: (this.walletBalance[treasury.wallet] / total) * 100,
|
||||
color: chartColors[index % chartColors.length],
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<span class="position-number">{{i + 1}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-cell-name">{{ treasury.enterprise || treasury.name }}</td>
|
||||
<td class="table-cell-name">{{ treasury.name || treasury.enterprise || treasury.wallet }}</td>
|
||||
<td class="table-cell-balance">
|
||||
<app-amount [satoshis]="walletStats[treasury.wallet]?.balance || 0" digitsInfo="1.2-4" [noFiat]="true"></app-amount>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ export class AddressTypeInfo {
|
|||
this.processScript(new ScriptInfo('inner_witnessscript', scriptHex, v.inner_witnessscript_asm, v.witness, controlBlock, vinIds?.[i]));
|
||||
} else if (this.network === 'liquid' || this.network === 'liquidtestnet' && tapleafVersion === 0xbe) {
|
||||
this.simplicity = true;
|
||||
v.inner_simplicityscript = scriptHex;
|
||||
v.inner_simplicityscript = v.witness[1];
|
||||
this.processScript(new ScriptInfo('inner_simplicityscript', scriptHex, null, v.witness, controlBlock, vinIds?.[i]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export interface Filter {
|
|||
txPage?: boolean,
|
||||
}
|
||||
|
||||
export type FilterMode = 'and' | 'or';
|
||||
export type FilterMode = 'and' | 'or' | 'nor';
|
||||
|
||||
export type GradientMode = 'fee' | 'age';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faCogs, faDatabase, faExchangeAlt, faInfoCircle,
|
||||
|
|
@ -9,7 +8,7 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa
|
|||
faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck,
|
||||
faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faTimeline,
|
||||
faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes, faCreditCard, faMicroscope, faExclamationTriangle, faLockOpen, faPaperclip, faAddressCard,
|
||||
faMedal, faBug, faFilePdf } from '@fortawesome/free-solid-svg-icons';
|
||||
faMedal, faBug, faFilePdf, faPiggyBank, faLayerGroup, faHeart, faCashRegister } 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';
|
||||
|
|
@ -478,5 +477,9 @@ export class SharedModule {
|
|||
library.addIcons(faAddressCard);
|
||||
library.addIcons(faBug);
|
||||
library.addIcons(faFilePdf);
|
||||
library.addIcons(faPiggyBank);
|
||||
library.addIcons(faLayerGroup);
|
||||
library.addIcons(faHeart);
|
||||
library.addIcons(faCashRegister);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@ try {
|
|||
}
|
||||
|
||||
function download(filename, url) {
|
||||
if (!filename || !url) {
|
||||
if (verbose) {
|
||||
console.log('skipping malformed download request: ', filename, url);
|
||||
}
|
||||
return;
|
||||
}
|
||||
https.get(url, (response) => {
|
||||
if (response.statusCode < 200 || response.statusCode > 299) {
|
||||
throw new Error('HTTP Error ' + response.statusCode + ' while fetching \'' + filename + '\'');
|
||||
|
|
@ -122,6 +128,9 @@ function downloadMiningPoolLogos$() {
|
|||
}
|
||||
let downloadedCount = 0;
|
||||
for (const poolLogo of poolLogos) {
|
||||
if (poolLogo.type !== 'file' || poolLogo.download_url == null) {
|
||||
continue;
|
||||
}
|
||||
if (verbose) {
|
||||
console.log(`${LOG_TAG} Processing ${poolLogo.name}`);
|
||||
}
|
||||
|
|
@ -217,6 +226,9 @@ function downloadPromoVideoSubtiles$() {
|
|||
}
|
||||
let downloadedCount = 0;
|
||||
for (const language of videoLanguages) {
|
||||
if (language.type !== 'file' || language.download_url == null) {
|
||||
continue;
|
||||
}
|
||||
if (verbose) {
|
||||
console.log(`${LOG_TAG} Processing ${language.name}`);
|
||||
}
|
||||
|
|
@ -253,7 +265,7 @@ function downloadPromoVideoSubtiles$() {
|
|||
|
||||
let download_url = language.download_url;
|
||||
if (MEMPOOL_CDN) {
|
||||
download_url = downloadownload_url = download_url.replace("raw.githubusercontent.com/mempool/mempool-promo/master/subtitles", "mempool.space/resources/promo-video");
|
||||
download_url = download_url.replace("raw.githubusercontent.com/mempool/mempool-promo/master/subtitles", "mempool.space/resources/promo-video");
|
||||
}
|
||||
if (DRY_RUN) {
|
||||
console.log(`${LOG_TAG} \tDRY_RUN is set, not downloading ${language.name} but we should`);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Deploying an Enterprise Production Instance
|
||||
|
||||
These instructions are for setting up a serious production Mempool website for Bitcoin (mainnet, testnet, signet), Liquid (mainnet, testnet), and Bisq.
|
||||
These instructions are for setting up a serious production Mempool website for Bitcoin (mainnet, testnet, signet), Liquid (mainnet, testnet).
|
||||
|
||||
Again, this setup is no joke—home users should use [one of the other installation methods](../#installation-methods). Support is only provided to project sponsors through [Mempool Enterprise®](https://mempool.space/enterprise).
|
||||
|
||||
|
|
@ -38,7 +38,6 @@ nvm 3.62T 1.25T 2.38T - - 2% 34% 1.00x ONLINE
|
|||
For maximum flexibility of configuration, I recommend separate partitions for each data folder:
|
||||
```
|
||||
Filesystem Size Used Avail Capacity Mounted on
|
||||
nvm/bisq 766G 1.1G 765G 0% /bisq
|
||||
nvm/bitcoin 766G 648M 765G 0% /bitcoin
|
||||
nvm/bitcoin/blocks 1.1T 375G 765G 33% /bitcoin/blocks
|
||||
nvm/bitcoin/chainstate 770G 4.5G 765G 1% /bitcoin/chainstate
|
||||
|
|
@ -72,7 +71,6 @@ nvm/elements/liquidv1 777G 11G 765G 1% /elements/li
|
|||
nvm/mempool 789G 24G 765G 3% /mempool
|
||||
nvm/mysql 766G 648M 765G 0% /mysql
|
||||
tmpfs 1.0G 1.3M 1.0G 0% /var/cache/nginx
|
||||
tmpfs 3.0G 1.9G 1.1G 63% /bisq/statsnode-data/btc_mainnet/db/json
|
||||
```
|
||||
|
||||
### Build Dependencies
|
||||
|
|
@ -124,10 +122,6 @@ HiddenServiceDir /var/db/tor/mempool
|
|||
HiddenServicePort 80 127.0.0.1:81
|
||||
HiddenServiceVersion 3
|
||||
|
||||
HiddenServiceDir /var/db/tor/bisq
|
||||
HiddenServicePort 80 127.0.0.1:82
|
||||
HiddenServiceVersion 3
|
||||
|
||||
HiddenServiceDir /var/db/tor/liquid
|
||||
HiddenServicePort 80 127.0.0.1:83
|
||||
HiddenServiceVersion 3
|
||||
|
|
@ -262,15 +256,6 @@ grant all on mempool_liquidtestnet.* to 'mempool_liquidtestnet'@'localhost' iden
|
|||
```
|
||||
|
||||
|
||||
### Bisq
|
||||
|
||||
Build bisq-statsnode normally and run using options like this:
|
||||
```
|
||||
./bisq-statsnode --dumpBlockchainData=true --dumpStatistics=true
|
||||
```
|
||||
|
||||
If Bisq is happy, it should dump JSON files for Bisq Markets and BSQ data into `/bisq` for the Mempool backend to use.
|
||||
|
||||
### Mempool
|
||||
|
||||
After all 3 electrs instances are fully indexed, install your 3 Mempool nodes:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# start elements on reboot
|
||||
@reboot sleep 5 ; /usr/local/bin/elementsd -chain=liquidv1 >/dev/null 2>&1
|
||||
@reboot sleep 5 ; /usr/local/bin/elementsd -chain=liquidtestnet >/dev/null 2>&1
|
||||
@reboot sleep 15 ; /usr/local/bin/elementsd -chain=liquidv1 >/dev/null 2>&1
|
||||
@reboot sleep 25 ; /usr/local/bin/elementsd -chain=liquidtestnet >/dev/null 2>&1
|
||||
|
||||
# start electrs on reboot
|
||||
@reboot sleep 20 ; screen -dmS liquidv1 /elements/electrs/start liquid
|
||||
@reboot sleep 20 ; screen -dmS liquidtestnet /elements/electrs/start liquidtestnet
|
||||
@reboot sleep 35 ; screen -dmS liquidv1 /elements/electrs/start liquid
|
||||
@reboot sleep 45 ; screen -dmS liquidtestnet /elements/electrs/start liquidtestnet
|
||||
|
||||
# hourly asset update and electrs restart
|
||||
6 * * * * cd $HOME/asset_registry_db && git pull --quiet origin master && cd $HOME/asset_registry_testnet_db && git pull --quiet origin master && killall electrs
|
||||
|
|
|
|||
|
|
@ -110,8 +110,6 @@ build_backend()
|
|||
-e "s!__MEMPOOL_LIQUID_PASS__!${MEMPOOL_LIQUID_PASS}!" \
|
||||
-e "s!__MEMPOOL_LIQUIDTESTNET_USER__!${MEMPOOL_LIQUIDTESTNET_USER}!" \
|
||||
-e "s!__MEMPOOL_LIQUIDTESTNET_PASS__!${MEMPOOL_LIQUIDTESTNET_PASS}!" \
|
||||
-e "s!__MEMPOOL_BISQ_USER__!${MEMPOOL_BISQ_USER}!" \
|
||||
-e "s!__MEMPOOL_BISQ_PASS__!${MEMPOOL_BISQ_PASS}!" \
|
||||
"mempool-config.json"
|
||||
fi
|
||||
npm install --omit=dev --omit=optional || exit 1
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"NETWORK": "bisq",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8996,
|
||||
"MINED_BLOCKS_CACHE": 144,
|
||||
"API_URL_PREFIX": "/api/v1/",
|
||||
"POLL_RATE_MS": 2000
|
||||
},
|
||||
"SYSLOG" : {
|
||||
"MIN_PRIORITY": "debug"
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"ESPLORA": {
|
||||
"REST_API_URL": "http://127.0.0.1:5000",
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": false,
|
||||
"HOST": "127.0.0.1",
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_BISQ_USER__",
|
||||
"PASSWORD": "__MEMPOOL_BISQ_PASS__",
|
||||
"DATABASE": "mempool_bisq"
|
||||
},
|
||||
"STATISTICS": {
|
||||
"ENABLED": true,
|
||||
"TX_PER_SECOND_SAMPLE_PERIOD": 150
|
||||
},
|
||||
"BISQ": {
|
||||
"ENABLED": true,
|
||||
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,9 @@
|
|||
"TESTNET4_ENABLED": true,
|
||||
"LIQUID_ENABLED": true,
|
||||
"LIQUID_TESTNET_ENABLED": true,
|
||||
"BISQ_ENABLED": true,
|
||||
"BISQ_SEPARATE_BACKEND": true,
|
||||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true,
|
||||
"ACCELERATOR": true,
|
||||
|
|
|
|||
|
|
@ -5,12 +5,9 @@
|
|||
"TESTNET4_ENABLED": true,
|
||||
"LIQUID_ENABLED": true,
|
||||
"LIQUID_TESTNET_ENABLED": true,
|
||||
"BISQ_ENABLED": true,
|
||||
"BISQ_SEPARATE_BACKEND": true,
|
||||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 2,
|
||||
"KEEP_BLOCKS_AMOUNT": 16
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"MAINNET_BLOCK_AUDIT_START_HEIGHT": 773911,
|
||||
"TESTNET_BLOCK_AUDIT_START_HEIGHT": 2417829,
|
||||
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 127609,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,9 @@
|
|||
"TESTNET4_ENABLED": true,
|
||||
"LIQUID_ENABLED": true,
|
||||
"LIQUID_TESTNET_ENABLED": true,
|
||||
"BISQ_ENABLED": true,
|
||||
"BISQ_SEPARATE_BACKEND": true,
|
||||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true,
|
||||
"ACCELERATOR": true,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,9 @@
|
|||
"TESTNET4_ENABLED": true,
|
||||
"LIQUID_ENABLED": true,
|
||||
"LIQUID_TESTNET_ENABLED": true,
|
||||
"BISQ_ENABLED": true,
|
||||
"BISQ_SEPARATE_BACKEND": true,
|
||||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true,
|
||||
"ACCELERATOR": true,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,9 @@
|
|||
"TESTNET4_ENABLED": true,
|
||||
"LIQUID_ENABLED": true,
|
||||
"LIQUID_TESTNET_ENABLED": true,
|
||||
"BISQ_ENABLED": true,
|
||||
"BISQ_SEPARATE_BACKEND": true,
|
||||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true,
|
||||
"ACCELERATOR": true,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,9 @@
|
|||
"TESTNET4_ENABLED": true,
|
||||
"LIQUID_ENABLED": true,
|
||||
"LIQUID_TESTNET_ENABLED": true,
|
||||
"BISQ_ENABLED": true,
|
||||
"BISQ_SEPARATE_BACKEND": true,
|
||||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true,
|
||||
"ACCELERATOR": true,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,9 @@
|
|||
"TESTNET4_ENABLED": true,
|
||||
"LIQUID_ENABLED": true,
|
||||
"LIQUID_TESTNET_ENABLED": true,
|
||||
"BISQ_ENABLED": true,
|
||||
"BISQ_SEPARATE_BACKEND": true,
|
||||
"SIGNET_ENABLED": true,
|
||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true,
|
||||
"ACCELERATOR": true,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
/var/log/nginx/access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
|
||||
/var/log/nginx/error.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
|
||||
/var/log/nginx/bisq-access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
|
||||
/var/log/nginx/bisq-error.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
|
||||
/var/log/nginx/liquid-access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
|
||||
/var/log/nginx/liquid-error.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
|
||||
/var/log/nginx/mempool-access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
|
||||
|
|
|
|||
|
|
@ -13,13 +13,6 @@ location = /liquidtestnet {
|
|||
return 308;
|
||||
}
|
||||
|
||||
# redirect mempool.space/bisq to bisq.markets
|
||||
location = /bisq {
|
||||
rewrite /bisq/(.*) https://bisq.markets/$1;
|
||||
rewrite /bisq https://bisq.markets/;
|
||||
return 308;
|
||||
}
|
||||
|
||||
# redirect /api to /docs/api
|
||||
location = /api {
|
||||
return 308 https://$host/docs/api;
|
||||
|
|
|
|||
24
unfurler/package-lock.json
generated
24
unfurler/package-lock.json
generated
|
|
@ -482,9 +482,9 @@
|
|||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
|
@ -1233,9 +1233,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
|
|
@ -3105,9 +3105,9 @@
|
|||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
|
@ -3663,9 +3663,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue