Skip to content

Commit fe60b88

Browse files
committed
feat: add Qbit explorer support
1 parent 26e282a commit fe60b88

19 files changed

Lines changed: 1588 additions & 2093 deletions

File tree

.dockerignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.git
2+
.github
3+
.env
4+
coverage
5+
logs
6+
node_modules
7+
npm-debug.log*
8+
test

.env-sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#BTCEXP_SECURE_SITE=false
2020

2121

22-
# The active coin. Only officially supported value is "BTC".
22+
# The active coin. Supported values in this fork are "BTC" and "QBT".
2323
# Default: BTC
2424
#BTCEXP_COIN=BTC
2525

.github/workflows/docker-image.yml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
name: Build and publish Docker image
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/docker-image.yml"
7+
- "app.js"
8+
- "app/**"
9+
- "bin/**"
10+
- "routes/**"
11+
- "public/**"
12+
- "test/**"
13+
- "views/**"
14+
- ".dockerignore"
15+
- "Dockerfile"
16+
- "docker-compose.qbit.yml"
17+
- "npm-shrinkwrap.json"
18+
- "package.json"
19+
push:
20+
branches:
21+
- master
22+
tags:
23+
- "v*"
24+
workflow_dispatch:
25+
26+
concurrency:
27+
group: ${{ github.workflow }}-${{ github.ref }}
28+
cancel-in-progress: true
29+
30+
env:
31+
IMAGE_NAME: ghcr.io/luxorlabs/luxor-qbit-explorer
32+
33+
jobs:
34+
test-build-publish:
35+
name: Test, build, and publish image
36+
runs-on: tenki-standard-large-plus-16c-32g
37+
timeout-minutes: 30
38+
permissions:
39+
contents: read
40+
packages: write
41+
42+
steps:
43+
- name: Repository checkout
44+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
45+
46+
- name: Set up Node.js
47+
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
48+
with:
49+
node-version: 20
50+
cache: npm
51+
cache-dependency-path: npm-shrinkwrap.json
52+
53+
- name: Install dependencies
54+
run: npm ci
55+
56+
- name: Run tests
57+
run: |
58+
npm test
59+
npm run test:qbit
60+
61+
- name: Set up Docker Buildx
62+
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
63+
64+
- name: Login to GitHub Container Registry
65+
if: github.event_name != 'pull_request'
66+
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
67+
with:
68+
registry: ghcr.io
69+
username: ${{ github.actor }}
70+
password: ${{ secrets.GITHUB_TOKEN }}
71+
72+
- name: Generate image metadata
73+
id: meta
74+
shell: bash
75+
run: |
76+
short_sha="${GITHUB_SHA:0:7}"
77+
created="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
78+
{
79+
echo "created=${created}"
80+
echo "tags<<EOF"
81+
echo "${IMAGE_NAME}:sha-${short_sha}"
82+
if [[ "${GITHUB_REF}" == "refs/heads/master" ]]; then
83+
echo "${IMAGE_NAME}:latest"
84+
fi
85+
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
86+
echo "${IMAGE_NAME}:${GITHUB_REF_NAME}"
87+
echo "${IMAGE_NAME}:${GITHUB_REF_NAME#v}"
88+
fi
89+
echo "EOF"
90+
} >> "$GITHUB_OUTPUT"
91+
92+
- name: Build and publish image
93+
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
94+
with:
95+
context: .
96+
file: Dockerfile
97+
platforms: linux/amd64,linux/arm64
98+
push: ${{ github.event_name != 'pull_request' }}
99+
tags: ${{ steps.meta.outputs.tags }}
100+
cache-from: type=gha
101+
cache-to: type=gha,mode=max
102+
labels: |
103+
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
104+
org.opencontainers.image.revision=${{ github.sha }}
105+
org.opencontainers.image.created=${{ steps.meta.outputs.created }}

Dockerfile

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
FROM node:20 as builder
1+
FROM node:20-bookworm AS builder
2+
23
WORKDIR /workspace
4+
5+
RUN apt-get update && \
6+
apt-get install --no-install-recommends --yes ca-certificates git && \
7+
rm -rf /var/lib/apt/lists/*
8+
9+
COPY package.json npm-shrinkwrap.json ./
10+
RUN npm ci --omit=dev
11+
312
COPY . .
4-
RUN npm install
513

6-
FROM node:20-alpine
14+
FROM node:20-bookworm-slim
15+
716
WORKDIR /workspace
8-
COPY --from=builder /workspace .
9-
RUN apk --update add git
10-
CMD npm start
17+
ENV NODE_ENV=production
18+
19+
COPY --from=builder --chown=node:node /workspace /workspace
20+
21+
USER node
22+
1123
EXPOSE 3002
24+
CMD ["npm", "start"]

README-QBIT.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Qbit Explorer
2+
3+
This Luxor fork adds a `QBT` profile to btc-rpc-explorer v3. It connects directly to Qbit Core JSON-RPC and does not require a separate explorer database.
4+
5+
## Supported Qbit behavior
6+
7+
- Mainnet, testnet3, testnet4, signet, and regtest network identities
8+
- QBT, mQBT, µQBT, and bits display units
9+
- 60-second aggregate block target
10+
- 210 QBT initial block subsidy
11+
- Subsidy step-down every 43,200 blocks by `598/625` (regtest uses 150 blocks)
12+
- P2MR addresses returned by Qbit Core's current RPC shape
13+
- Block, transaction, mempool, peer, node-status, and RPC views
14+
15+
Select the Qbit profile with:
16+
17+
```text
18+
BTCEXP_COIN=QBT
19+
```
20+
21+
## Run Qbit mainnet and the explorer
22+
23+
The included Compose file starts the Luxor Qbit v1.0.0 image, enables `txindex`, retains mainnet data in a named volume, and exposes only the explorer HTTP port on localhost:
24+
25+
```bash
26+
export QBIT_RPC_PASSWORD="replace-with-a-local-secret"
27+
docker compose -f docker-compose.qbit.yml up --build -d
28+
```
29+
30+
Open <http://127.0.0.1:3002>.
31+
32+
Check synchronization:
33+
34+
```bash
35+
docker compose -f docker-compose.qbit.yml exec qbit \
36+
sh -lc 'qbit-cli -rpcuser="$QBIT_RPC_USER" -rpcpassword="$QBIT_RPC_PASSWORD" getblockchaininfo'
37+
```
38+
39+
Stop services without deleting chain data:
40+
41+
```bash
42+
docker compose -f docker-compose.qbit.yml down
43+
```
44+
45+
Delete the downloaded chain data only when intentional:
46+
47+
```bash
48+
docker compose -f docker-compose.qbit.yml down --volumes
49+
```
50+
51+
The Qbit daemon image currently targets `linux/amd64`. Docker Desktop can emulate it on Apple Silicon. The explorer image is built for both `linux/amd64` and `linux/arm64`.
52+
53+
## Connect to an existing Qbit node
54+
55+
Build the explorer:
56+
57+
```bash
58+
docker build -t luxor-qbit-explorer:local .
59+
```
60+
61+
If the node and explorer share a Docker network named `qbit-local`:
62+
63+
```bash
64+
docker run --rm \
65+
--name qbit-explorer \
66+
--network qbit-local \
67+
-p 127.0.0.1:3002:3002 \
68+
-e BTCEXP_COIN=QBT \
69+
-e BTCEXP_HOST=0.0.0.0 \
70+
-e BTCEXP_BITCOIND_HOST=qbit-mainnet \
71+
-e BTCEXP_BITCOIND_PORT=8352 \
72+
-e BTCEXP_BITCOIND_USER=qbitexplorer \
73+
-e BTCEXP_BITCOIND_PASS="replace-with-your-rpc-password" \
74+
-e BTCEXP_BITCOIND_RPC_TIMEOUT=30000 \
75+
-e BTCEXP_NO_RATES=true \
76+
-e BTCEXP_PRIVACY_MODE=true \
77+
luxor-qbit-explorer:local
78+
```
79+
80+
For a host-installed node, point `BTCEXP_BITCOIND_HOST` at an address reachable from the container. Do not expose Qbit RPC to the public internet.
81+
82+
## Required Qbit node settings
83+
84+
The explorer requires RPC access. An archive node with transaction indexing provides the complete experience:
85+
86+
```ini
87+
server=1
88+
txindex=1
89+
```
90+
91+
Qbit mainnet uses RPC port `8352` and P2P port `8355`.
92+
93+
## Container image
94+
95+
The GitHub workflow publishes to:
96+
97+
```text
98+
ghcr.io/luxorlabs/luxor-qbit-explorer
99+
```
100+
101+
Pushes to `master` publish `latest` and `sha-<short-sha>`. Tags such as `v1.2.0` publish both `v1.2.0` and `1.2.0`.
102+
103+
## Limitations
104+
105+
- Address transaction history requires a Qbit-compatible Electrum server. Without one, block and transaction exploration still works through Qbit Core and `txindex`.
106+
- Bitcoin-specific historical and external exchange-rate integrations are disabled for Qbit.
107+
- Keep the UI bound to localhost or protect it with `BTCEXP_BASIC_AUTH_PASSWORD` before exposing it beyond a trusted network.

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# BTC RPC Explorer
22

3+
> This Luxor fork adds Qbit Core support. See [README-QBIT.md](README-QBIT.md) for the Qbit mainnet Docker Compose setup, configuration, and published image.
4+
35
## Self-Hosted Bitcoin explorer for everyone running [Bitcoin Core](https://github.com/bitcoin/bitcoin).
46

57
[![npm version][npm-ver-img]][npm-ver-url] [![NPM downloads][npm-dl-alltime-img]][npm-dl-url]
@@ -144,6 +146,8 @@ This will cause users to be redirected to your login page if needed.
144146
1. `docker build -t btc-rpc-explorer .`
145147
2. `docker run -it -p 3002:3002 -e BTCEXP_HOST=0.0.0.0 btc-rpc-explorer`
146148

149+
For Qbit, use `docker compose -f docker-compose.qbit.yml up --build -d` as described in [README-QBIT.md](README-QBIT.md).
150+
147151

148152
## Reverse proxy with HTTPS
149153

@@ -172,4 +176,3 @@ thanks@donate.btc21.org
172176
[npm-dl-monthly-img]: https://badgen.net/npm/dm/btc-rpc-explorer?icon=npm&cache=300
173177
[npm-dl-yearly-img]: https://badgen.net/npm/dy/btc-rpc-explorer?icon=npm&cache=300
174178
[npm-dl-alltime-img]: https://badgen.net/npm/dt/btc-rpc-explorer?icon=npm&cache=300&label=total%20downloads
175-

app.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,14 @@ async function onRpcConnectionVerified(getnetworkinfo, getblockchaininfo) {
513513
global.pruneHeight = getblockchaininfo.pruneheight;
514514
}
515515

516+
if (global.coinConfig.rpcSemverOverride) {
517+
global.btcNodeVersion = getnetworkinfo.subversion;
518+
global.btcNodeSemver = global.coinConfig.rpcSemverOverride;
519+
}
520+
516521
var bitcoinCoreVersionRegex = /^.*\/Satoshi\:(.*)\/.*$/;
517522

518-
var match = bitcoinCoreVersionRegex.exec(getnetworkinfo.subversion);
523+
var match = global.coinConfig.rpcSemverOverride ? null : bitcoinCoreVersionRegex.exec(getnetworkinfo.subversion);
519524
if (match) {
520525
global.btcNodeVersion = match[1];
521526

@@ -547,7 +552,7 @@ async function onRpcConnectionVerified(getnetworkinfo, getblockchaininfo) {
547552
global.btcNodeSemver = "1000.1000.0"
548553
}
549554
}
550-
} else {
555+
} else if (!global.coinConfig.rpcSemverOverride) {
551556
// short-circuit: force all RPC calls to pass their version checks - this will likely lead to errors / instability / unexpected results
552557
global.btcNodeSemver = "1000.1000.0"
553558

app/coins.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"use strict";
22

33
const btc = require("./coins/btc.js");
4+
const qbit = require("./coins/qbit.js");
45

56
module.exports = {
67
"BTC": btc,
8+
"QBT": qbit,
79

8-
"coins":["BTC"]
9-
};
10+
"coins":["BTC", "QBT"]
11+
};

0 commit comments

Comments
 (0)