diff --git a/.bitcoin-version b/.bitcoin-version new file mode 100644 index 0000000..3680b9e --- /dev/null +++ b/.bitcoin-version @@ -0,0 +1 @@ +30.2 diff --git a/.env b/.env index 8ede2e6..a4035c6 100644 --- a/.env +++ b/.env @@ -1,16 +1,17 @@ # Mining Configuration -BLOCKPRODUCTIONDELAY=60 +# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported) +BLOCKPRODUCTIONDELAY=600 MINERENABLED=1 NBITS=1e0377ae -PRIVKEY=cQ78Y7oUASnzbgK465VPug4CrfxJPe95ftsDqwMwTJU8j1NoY76C -MINETO=tb1qhcuywsgshgs2ukv0ae2gw9egrfqx944qh6eurq -SIGNETCHALLENGE=5121021dc5794f479c961e47fd3ec343ecd7bb43c3abb991fb1e104f1e2403019e5e7551ae +PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS +MINETO= +SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae # RPC Configuration -RPCUSER=easepay-signet -RPCPASSWORD=Beautifulgirl456 +RPCUSER=bitcoin +RPCPASSWORD=bitcoin -UACOMMENT=Easepay +UACOMMENT=CustomSignet # ZMQ Configuration ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332 diff --git a/Dockerfile b/Dockerfile index db55767..acc7d5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,30 @@ -FROM debian:buster-slim as builder +FROM debian:bookworm-slim as builder -ARG BITCOIN_VERSION=${BITCOIN_VERSION:-25.1} +ARG BITCOIN_VERSION=${BITCOIN_VERSION:-30.2} -ARG TARGETPLATFORM +ARG TARGETPLATFORM RUN apt-get update && \ apt-get install -qq --no-install-recommends ca-certificates dirmngr gosu wget libc6 procps python3 WORKDIR /tmp # Install Bitcoin binaries based on platform -RUN case $TARGETPLATFORM in \ - linux/amd64) export TRIPLET="x86_64-linux-gnu";; \ - linux/arm64) export TRIPLET="aarch64-linux-gnu";; \ +RUN ARCH=$(uname -m) && \ + case $ARCH in \ + x86_64) export TRIPLET="x86_64-linux-gnu";; \ + aarch64) export TRIPLET="aarch64-linux-gnu";; \ + *) echo "Unsupported architecture: $ARCH" && exit 1;; \ esac && \ BITCOIN_URL="https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \ BITCOIN_FILE="bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \ wget -qO "${BITCOIN_FILE}" "${BITCOIN_URL}" && \ mkdir -p bin && \ tar -xzvf "${BITCOIN_FILE}" -C /tmp/bin --strip-components=2 "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli" "bitcoin-${BITCOIN_VERSION}/bin/bitcoind" "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-wallet" "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-util" - -FROM debian:buster-slim as custom-signet-bitcoin + +FROM debian:bookworm-slim as custom-signet-bitcoin -ENV BITCOIN_DIR /root/.bitcoin +ENV BITCOIN_DIR /root/.bitcoin ENV NBITS=${NBITS} ENV SIGNETCHALLENGE=${SIGNETCHALLENGE} @@ -45,14 +47,14 @@ ENV ADDNODE=${ADDNODE:-""} ENV BLOCKPRODUCTIONDELAY=${BLOCKPRODUCTIONDELAY:-""} ENV MINERENABLED=${MINERENABLED:-"1"} ENV MINETO=${MINETO:-""} -ENV EXTERNAL_IP=${EXTERNAL_IP:-""} +ENV EXTERNAL_IP=${EXTERNAL_IP:-""} VOLUME $BITCOIN_DIR EXPOSE 28332 28333 28334 38332 38333 38334 RUN apt-get update && \ - apt-get install -qq --no-install-recommends procps python3 python3-pip jq && \ + apt-get install -qq --no-install-recommends procps python3 python3-pip python3-setuptools jq && \ apt-get clean -COPY --from=builder "/tmp/bin" /usr/local/bin +COPY --from=builder "/tmp/bin" /usr/local/bin COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh @@ -69,9 +71,7 @@ COPY *.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/*.sh COPY rpcauth.py /usr/local/bin/rpcauth.py RUN chmod +x /usr/local/bin/rpcauth.py -RUN pip3 install setuptools ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] CMD ["run.sh"] - diff --git a/ETAT_SYSTEME.md b/ETAT_SYSTEME.md new file mode 100644 index 0000000..62ff708 --- /dev/null +++ b/ETAT_SYSTEME.md @@ -0,0 +1,87 @@ +# État du Système Bitcoin Signet + +**Date** : 2026-01-23 + +## ✅ État de la Chaîne + +- **Chaîne** : Signet +- **Hauteur** : 0 (bloc genesis) +- **Hash du meilleur bloc** : `00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6` +- **Statut** : ✅ **UP** - Chaîne opérationnelle + +## ✅ État du Miner + +- **bitcoind** : ✅ **UP** - Processus actif (PID visible) +- **mine.sh** : ✅ **UP** - Script de mining actif +- **Statut** : ✅ **UP** - Miner opérationnel + +## 💰 Balance du Miner + +- **Balance mature** : 0.00000000 BTC +- **Balance immature** : 0.00000000 BTC +- **Total** : 0.00000000 BTC + +**Note** : La balance est à 0 car aucun bloc n'a encore été miné depuis le bloc genesis. Le prochain bloc miné apportera la récompense de minage. + +## ⚠️ API d'Ancrage + +- **Port** : 3010 +- **Statut** : ❌ **DOWN** - API non accessible +- **Action requise** : Lancer l'API d'ancrage + +## ⚠️ Dashboard et API Faucet + +- **Node.js** : ❌ **NON INSTALLÉ** sur cette machine +- **Dashboard (port 3020)** : ❌ **NON LANCÉ** - Nécessite Node.js +- **API Faucet (port 3021)** : ❌ **NON LANCÉ** - Nécessite Node.js + +## Actions Requises + +### Pour lancer le Dashboard et l'API Faucet + +1. **Installer Node.js** (version >= 18.0.0) : + ```bash + # Sur Ubuntu/Debian + curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - + sudo apt-get install -y nodejs + ``` + +2. **Installer les dépendances** : + ```bash + cd signet-dashboard && npm install + cd ../api-faucet && npm install + ``` + +3. **Configurer les fichiers .env** : + ```bash + cd signet-dashboard && cp .env.example .env + cd ../api-faucet && cp .env.example .env + ``` + +4. **Lancer les services** : + ```bash + # Dashboard + cd signet-dashboard && npm start & + + # API Faucet + cd api-faucet && npm start & + ``` + +### Pour lancer l'API d'Ancrage + +Vérifier si l'API d'ancrage est configurée et la lancer si nécessaire. + +## Vérification + +Une fois les services lancés, vérifier avec : + +```bash +# Dashboard +curl http://localhost:3020/api/blockchain/info + +# API Faucet +curl http://localhost:3021/health + +# API Anchor +curl http://localhost:3010/health +``` diff --git a/START_DASHBOARD_AND_FAUCET.md b/START_DASHBOARD_AND_FAUCET.md new file mode 100644 index 0000000..9ae763e --- /dev/null +++ b/START_DASHBOARD_AND_FAUCET.md @@ -0,0 +1,101 @@ +# Guide de Démarrage Rapide - Dashboard et API Faucet + +## Vue d'ensemble + +Deux nouveaux services ont été créés pour Bitcoin Signet : + +1. **Dashboard de Supervision** (port 3020) +2. **API Faucet** (port 3021) + +## Installation Rapide + +### 1. Dashboard de Supervision + +```bash +cd signet-dashboard +npm install +cp .env.example .env +# Éditer .env avec vos paramètres +npm start +``` + +### 2. API Faucet + +```bash +cd api-faucet +npm install +cp .env.example .env +# Éditer .env avec vos paramètres +npm start +``` + +## Configuration Minimale + +### Dashboard (.env) + +```bash +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +DASHBOARD_PORT=3020 +ANCHOR_API_URL=http://localhost:3010 +ANCHOR_API_KEY=your-api-key-here +FAUCET_API_URL=http://localhost:3021 +``` + +### API Faucet (.env) + +```bash +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +FAUCET_API_PORT=3021 +FAUCET_AMOUNT=0.0005 +``` + +## Démarrage avec PM2 (Production) + +### Dashboard + +```bash +cd signet-dashboard +pm2 start src/server.js --name signet-dashboard +pm2 save +``` + +### API Faucet + +```bash +cd api-faucet +pm2 start src/server.js --name faucet-api +pm2 save +``` + +## Accès + +- **Dashboard** : http://localhost:3020 +- **API Faucet Health** : http://localhost:3021/health + +## Vérification + +```bash +# Vérifier que les services sont actifs +pm2 status + +# Vérifier les logs +pm2 logs signet-dashboard +pm2 logs faucet-api + +# Tester le dashboard +curl http://localhost:3020/api/blockchain/info + +# Tester l'API faucet +curl http://localhost:3021/health +``` + +## Documentation Complète + +- Dashboard : `signet-dashboard/README.md` +- API Faucet : `api-faucet/README.md` diff --git a/__pycache__/minercpython-311.pyc b/__pycache__/minercpython-311.pyc new file mode 100644 index 0000000..04a4d45 Binary files /dev/null and b/__pycache__/minercpython-311.pyc differ diff --git a/api-anchorage/.env.example b/api-anchorage/.env.example new file mode 100644 index 0000000..880c3d6 --- /dev/null +++ b/api-anchorage/.env.example @@ -0,0 +1,20 @@ +# Bitcoin RPC Configuration +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +BITCOIN_RPC_TIMEOUT=30000 + +# API Configuration +API_PORT=3010 +API_HOST=0.0.0.0 + +# API Keys (séparées par des virgules) +API_KEYS=your-api-key-here,another-api-key + +# Logging +LOG_LEVEL=info + +# Mining Configuration +MINING_ENABLED=true +MINING_FEE_RATE=0.00001 diff --git a/api-anchorage/.gitignore b/api-anchorage/.gitignore new file mode 100644 index 0000000..e2383c6 --- /dev/null +++ b/api-anchorage/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +.env +*.log +.DS_Store +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/api-anchorage/DEPLOYMENT.md b/api-anchorage/DEPLOYMENT.md new file mode 100644 index 0000000..0b04a91 --- /dev/null +++ b/api-anchorage/DEPLOYMENT.md @@ -0,0 +1,399 @@ +# Guide de Déploiement - API d'Ancrage Bitcoin Signet + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0 + +## Vue d'Ensemble + +Ce guide explique comment déployer l'API d'ancrage Bitcoin Signet sur le serveur de production (192.168.1.103) et la configurer pour être accessible via nginx sur le proxy (192.168.1.100) au domaine `certificator.4nkweb.com`. + +## Architecture + +``` +Internet + ↓ HTTPS +certificator.4nkweb.com (DNS → 4nk.myftp.biz) + ↓ +Nginx Proxy (192.168.1.100:443) + ↓ HTTP +API Anchor (192.168.1.103:3010) + ↓ RPC +Bitcoin Signet Node (192.168.1.103:38332) +``` + +## Prérequis + +- Serveur de production (192.168.1.103) accessible +- Nginx configuré sur le proxy (192.168.1.100) +- Nœud Bitcoin Signet fonctionnel sur 192.168.1.103:38332 +- Node.js >= 18.0.0 installé sur le serveur de production +- Accès SSH au serveur de production + +## Déploiement sur le Serveur de Production + +### 1. Préparation du Serveur + +```bash +# Se connecter au serveur de production +ssh prod + +# Vérifier Node.js +node --version # Doit être >= 18.0.0 + +# Installer Node.js si nécessaire +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt-get install -y nodejs +``` + +### 2. Cloner et Installer l'API + +```bash +# Créer le répertoire de déploiement +sudo mkdir -p /srv/4NK/certificator.4nkweb.com +sudo chown ncantu:ncantu /srv/4NK/certificator.4nkweb.com +cd /srv/4NK/certificator.4nkweb.com + +# Cloner le dépôt (ou copier les fichiers) +# Si le code est dans un dépôt git : +# git clone . + +# Ou copier les fichiers depuis votre machine locale +# scp -r api-anchorage/* ncantu@192.168.1.103:/srv/4NK/certificator.4nkweb.com/ +``` + +### 3. Installation des Dépendances + +```bash +cd /srv/4NK/certificator.4nkweb.com +npm install --production +``` + +### 4. Configuration + +```bash +# Créer le fichier .env +cp .env.example .env +nano .env +``` + +Configuration recommandée pour la production : + +```bash +# Bitcoin RPC Configuration +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +BITCOIN_RPC_TIMEOUT=30000 + +# API Configuration +API_PORT=3010 +API_HOST=0.0.0.0 + +# API Keys (séparées par des virgules) +# IMPORTANT: Générer des clés sécurisées pour la production +API_KEYS=prod-api-key-1,prod-api-key-2 + +# Logging +LOG_LEVEL=info + +# Mining Configuration +MINING_ENABLED=true +MINING_FEE_RATE=0.00001 +``` + +**Sécurité** : +- Générer des clés API fortes (ex: `openssl rand -hex 32`) +- Ne jamais commiter le fichier `.env` +- Utiliser des mots de passe RPC différents de ceux par défaut en production + +### 5. Test Local + +```bash +# Tester que l'API démarre +npm start + +# Dans un autre terminal, tester +curl http://localhost:3010/health +``` + +### 6. Déploiement avec PM2 (Recommandé) + +```bash +# Installer PM2 globalement +sudo npm install -g pm2 + +# Démarrer l'API avec PM2 +pm2 start src/server.js --name anchor-api + +# Sauvegarder la configuration PM2 +pm2 save + +# Configurer PM2 pour démarrer au boot +pm2 startup +# Suivre les instructions affichées +``` + +### 7. Vérification + +```bash +# Vérifier que l'API fonctionne +curl http://localhost:3010/health + +# Vérifier les logs PM2 +pm2 logs anchor-api + +# Vérifier le statut +pm2 status +``` + +## Configuration Nginx sur le Proxy + +### 1. Se Connecter au Proxy + +```bash +ssh proxy +``` + +### 2. Créer la Configuration Nginx + +```bash +sudo nano /etc/nginx/sites-available/certificator.4nkweb.com +``` + +Contenu : + +```nginx +# API Anchor sur certificator.4nkweb.com +server { + listen 443 ssl http2; + server_name certificator.4nkweb.com; + + # Certificats SSL (Let's Encrypt) + ssl_certificate /etc/letsencrypt/live/certificator.4nkweb.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/certificator.4nkweb.com/privkey.pem; + + # Configuration SSL + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Logs + access_log /var/log/nginx/certificator.4nkweb.com.access.log; + error_log /var/log/nginx/certificator.4nkweb.com.error.log; + + # Proxy vers l'API + location / { + proxy_pass http://192.168.1.103:3010; + proxy_http_version 1.1; + + # Headers + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # Cache + proxy_cache_bypass $http_upgrade; + } +} + +# Redirection HTTP vers HTTPS +server { + listen 80; + server_name certificator.4nkweb.com; + return 301 https://$server_name$request_uri; +} +``` + +### 3. Activer la Configuration + +```bash +# Créer le lien symbolique +sudo ln -s /etc/nginx/sites-available/certificator.4nkweb.com /etc/nginx/sites-enabled/ + +# Tester la configuration +sudo nginx -t + +# Recharger nginx +sudo systemctl reload nginx +``` + +### 4. Obtenir le Certificat SSL + +```bash +# Obtenir le certificat Let's Encrypt +sudo certbot --nginx -d certificator.4nkweb.com + +# Vérifier le renouvellement automatique +sudo certbot renew --dry-run +``` + +## Vérification Post-Déploiement + +### 1. Vérifier l'API Localement + +```bash +# Sur le serveur de production +curl http://localhost:3010/health +``` + +### 2. Vérifier via le Proxy + +```bash +# Depuis n'importe quelle machine +curl https://certificator.4nkweb.com/health +``` + +### 3. Tester l'Ancrage + +```bash +# Utiliser le client de test +cd /srv/4NK/certificator.4nkweb.com +export API_KEY=prod-api-key-1 +export API_URL=https://certificator.4nkweb.com +npm test +``` + +## Maintenance + +### Redémarrage de l'API + +```bash +# Via PM2 +pm2 restart anchor-api + +# Ou arrêter/démarrer +pm2 stop anchor-api +pm2 start anchor-api +``` + +### Mise à Jour + +```bash +# Arrêter l'API +pm2 stop anchor-api + +# Mettre à jour le code +cd /srv/4NK/certificator.4nkweb.com +git pull # ou copier les nouveaux fichiers + +# Mettre à jour les dépendances si nécessaire +npm install --production + +# Redémarrer +pm2 start anchor-api +``` + +### Logs + +```bash +# Voir les logs en temps réel +pm2 logs anchor-api + +# Voir les logs nginx +sudo tail -f /var/log/nginx/certificator.4nkweb.com.access.log +sudo tail -f /var/log/nginx/certificator.4nkweb.com.error.log +``` + +### Monitoring + +```bash +# Statut PM2 +pm2 status + +# Informations détaillées +pm2 info anchor-api + +# Monitoring en temps réel +pm2 monit +``` + +## Dépannage + +### L'API ne répond pas + +1. **Vérifier que l'API est démarrée** : + ```bash + pm2 status + ``` + +2. **Vérifier les logs** : + ```bash + pm2 logs anchor-api --lines 50 + ``` + +3. **Vérifier le port** : + ```bash + sudo netstat -tlnp | grep 3010 + ``` + +### Erreur de connexion Bitcoin RPC + +1. **Vérifier que le nœud Bitcoin est accessible** : + ```bash + curl -u bitcoin:bitcoin http://localhost:38332 + ``` + +2. **Vérifier les variables d'environnement** : + ```bash + grep BITCOIN_RPC .env + ``` + +### Erreur 502 Bad Gateway (Nginx) + +1. **Vérifier que l'API est accessible depuis le proxy** : + ```bash + # Depuis le proxy + curl http://192.168.1.103:3010/health + ``` + +2. **Vérifier les logs nginx** : + ```bash + sudo tail -f /var/log/nginx/certificator.4nkweb.com.error.log + ``` + +3. **Vérifier le firewall** : + ```bash + # Sur le serveur de production + sudo ufw status + # Le port 3010 doit être ouvert pour le réseau interne + ``` + +## Sécurité + +### Recommandations + +1. **Clés API** : + - Générer des clés fortes (32+ caractères aléatoires) + - Rotater régulièrement + - Ne jamais les exposer publiquement + +2. **RPC Bitcoin** : + - Utiliser des credentials différents en production + - Restreindre `RPCALLOWIP` dans bitcoin.conf + - Utiliser `rpcauth` au lieu de `rpcuser`/`rpcpassword` + +3. **HTTPS** : + - Toujours utiliser HTTPS en production + - Renouveler automatiquement les certificats Let's Encrypt + +4. **Firewall** : + - Ne pas exposer le port 3010 publiquement + - Utiliser nginx comme reverse proxy uniquement + +5. **Logs** : + - Ne pas logger les clés API + - Surveiller les tentatives d'accès non autorisées + +--- + +**Dernière mise à jour** : 2026-01-23 diff --git a/api-anchorage/README.md b/api-anchorage/README.md new file mode 100644 index 0000000..39dd6a4 --- /dev/null +++ b/api-anchorage/README.md @@ -0,0 +1,362 @@ +# API d'Ancrage Bitcoin Signet + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0 + +## Description + +API REST pour ancrer des documents sur la blockchain Bitcoin Signet. Cette API permet de créer des transactions Bitcoin qui incluent les hash de documents dans des outputs OP_RETURN, permettant ainsi de prouver l'existence d'un document à un moment donné. + +## Caractéristiques + +- **Port** : `3010` +- **Domaine** : `certificator.4nkweb.com` (via nginx sur proxy 192.168.1.100) +- **Authentification** : API Key via header `x-api-key` +- **Format** : JSON REST API +- **Bitcoin** : Connexion RPC au nœud Bitcoin Signet (port 38332) +- **Mempool** : Les transactions sont envoyées au mempool et retournées immédiatement (pas de callbacks) + +## Installation + +### Prérequis + +- Node.js >= 18.0.0 +- Accès au nœud Bitcoin Signet (RPC sur port 38332) +- Wallet Bitcoin avec des fonds pour créer des transactions + +### Installation des Dépendances + +```bash +cd api-anchorage +npm install +``` + +### Configuration + +1. Copier le fichier d'exemple : +```bash +cp .env.example .env +``` + +2. Éditer `.env` : +```bash +# Bitcoin RPC Configuration +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin + +# API Configuration +API_PORT=3010 +API_HOST=0.0.0.0 + +# API Keys (séparées par des virgules) +API_KEYS=your-api-key-here,another-api-key + +# Logging +LOG_LEVEL=info + +# Mining Configuration +MINING_ENABLED=true +MINING_FEE_RATE=0.00001 +``` + +**Important** : +- `BITCOIN_RPC_HOST` : Si l'API est dans un conteneur Docker, utiliser l'IP du conteneur Bitcoin ou `host.docker.internal` +- `API_KEYS` : Définir au moins une clé API valide + +## Démarrage + +### Mode Développement + +```bash +npm run dev +``` + +### Mode Production + +```bash +npm start +``` + +### Avec PM2 (recommandé pour production) + +```bash +pm2 start src/server.js --name anchor-api +pm2 save +pm2 startup +``` + +## Endpoints + +### GET /health + +Vérifie l'état de l'API et de la connexion Bitcoin. + +**Authentification** : Non requise + +**Réponse** : +```json +{ + "ok": true, + "service": "anchor-api", + "bitcoin": { + "connected": true, + "blocks": 152321, + "chain": "signet", + "networkactive": true, + "connections": 1 + }, + "timestamp": "2026-01-23T16:35:27.821Z" +} +``` + +### POST /api/anchor/document + +Ancre un document sur Bitcoin Signet. La transaction est créée et envoyée au mempool, puis retournée immédiatement. + +**Authentification** : Requise (header `x-api-key`) + +**Body** : +```json +{ + "documentUid": "doc-123456", + "hash": "a1b2c3d4e5f6..." +} +``` + +**Note** : La transaction est envoyée au mempool et retournée immédiatement. Aucun callback n'est supporté. + +**Réponse** : +```json +{ + "txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85", + "status": "confirmed", + "confirmations": 0, + "block_height": 152321 +} +``` + +**Erreurs** : +- `400 Bad Request` : Hash invalide +- `401 Unauthorized` : Clé API invalide ou manquante +- `402 Payment Required` : Solde insuffisant +- `500 Internal Server Error` : Erreur serveur + +### POST /api/anchor/verify + +Vérifie si un hash est ancré sur Bitcoin Signet. + +**Authentification** : Requise (header `x-api-key`) + +**Body** : +```json +{ + "hash": "a1b2c3d4e5f6...", + "txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85" // optionnel +} +``` + +**Réponse** (hash trouvé) : +```json +{ + "verified": true, + "anchor_info": { + "transaction_id": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85", + "block_height": 152321, + "confirmations": 0, + "current_block_height": 152321 + } +} +``` + +**Réponse** (hash non trouvé) : +```json +{ + "verified": false, + "message": "Hash not found in recent blocks" +} +``` + +## Utilisation + +### Exemple avec curl + +```bash +# Health check +curl http://localhost:3010/health + +# Ancrer un document +curl -X POST http://localhost:3010/api/anchor/document \ + -H "Content-Type: application/json" \ + -H "x-api-key: your-api-key-here" \ + -d '{ + "documentUid": "doc-123", + "hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890" + }' + +# Vérifier un ancrage +curl -X POST http://localhost:3010/api/anchor/verify \ + -H "Content-Type: application/json" \ + -H "x-api-key: your-api-key-here" \ + -d '{ + "hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890" + }' +``` + +### Exemple avec le client de test + +```bash +# Configurer la clé API +export API_KEY=your-api-key-here +export API_URL=http://localhost:3010 + +# Exécuter le test +npm test +``` + +## Configuration Nginx (sur proxy 192.168.1.100) + +Ajouter dans la configuration nginx du proxy : + +```nginx +# API Anchor sur certificator.4nkweb.com +server { + listen 443 ssl http2; + server_name certificator.4nkweb.com; + + ssl_certificate /etc/letsencrypt/live/certificator.4nkweb.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/certificator.4nkweb.com/privkey.pem; + + location / { + proxy_pass http://192.168.1.103:3010; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} +``` + +**Note** : Remplacer `192.168.1.103` par l'IP du serveur où l'API est déployée. + +## Architecture + +``` +Client + ↓ HTTPS +Nginx (proxy 192.168.1.100:443) + ↓ HTTP +API Anchor (port 3010) + ↓ RPC +Bitcoin Signet Node (port 38332) +``` + +## Sécurité + +### Clés API + +- Les clés API sont définies dans `.env` (variable `API_KEYS`) +- Plusieurs clés peuvent être définies (séparées par des virgules) +- La clé doit être envoyée dans le header `x-api-key` +- Le endpoint `/health` ne nécessite pas d'authentification + +### Recommandations + +1. **Ne jamais commiter `.env`** : Le fichier `.env` contient des secrets +2. **Utiliser HTTPS** : Via nginx avec certificats Let's Encrypt +3. **Restreindre RPCALLOWIP** : Dans la configuration Bitcoin, limiter l'accès RPC +4. **Rotater les clés API** : Changer régulièrement les clés API +5. **Monitoring** : Surveiller les logs pour détecter les abus + +## Dépannage + +### L'API ne peut pas se connecter à Bitcoin + +**Vérifier** : +```bash +# Vérifier que le nœud Bitcoin est accessible +curl -u bitcoin:bitcoin http://localhost:38332 + +# Vérifier les variables d'environnement +grep BITCOIN_RPC .env +``` + +### Erreur "Insufficient balance" + +Le wallet Bitcoin n'a pas assez de fonds pour créer des transactions. + +**Solution** : +- Miner des blocs pour obtenir des récompenses +- Recevoir des fonds depuis un autre wallet +- Vérifier le solde : `bitcoin-cli getbalance` + +### Erreur "Unauthorized" + +La clé API est invalide ou manquante. + +**Solution** : +- Vérifier que le header `x-api-key` est présent +- Vérifier que la clé est dans `API_KEYS` du `.env` + +## Logs + +Les logs sont affichés sur la console avec le format : +``` +[2026-01-23T16:35:27.821Z] [INFO] API d'ancrage Bitcoin Signet démarrée {"host":"0.0.0.0","port":3010} +``` + +Niveaux de log : +- `error` : Erreurs critiques +- `warn` : Avertissements +- `info` : Informations générales +- `debug` : Détails de débogage + +Contrôlé par la variable `LOG_LEVEL` dans `.env`. + +## Structure du Projet + +``` +api-anchorage/ +├── src/ +│ ├── server.js # Serveur Express principal +│ ├── bitcoin-rpc.js # Client Bitcoin RPC +│ ├── logger.js # Système de logging +│ └── routes/ +│ ├── health.js # Route health check +│ └── anchor.js # Routes d'ancrage +├── package.json +├── .env.example +├── .env # Configuration (ne pas commiter) +├── README.md +└── src/test-client.js # Client de test +``` + +## Développement + +### Tests Locaux + +```bash +# Démarrer l'API +npm run dev + +# Dans un autre terminal, tester +npm test +``` + +### Format de Transaction + +Les transactions d'ancrage utilisent un output OP_RETURN avec le format : +- Préfixe : `"ANCHOR:"` (7 bytes) +- Hash : Hash SHA256 du document (32 bytes) + +Total : 39 bytes dans l'output OP_RETURN. + +--- + +**Dernière mise à jour** : 2026-01-23 diff --git a/api-anchorage/package-lock.json b/api-anchorage/package-lock.json new file mode 100644 index 0000000..ba84415 --- /dev/null +++ b/api-anchorage/package-lock.json @@ -0,0 +1,1602 @@ +{ + "name": "bitcoin-signet-anchor-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bitcoin-signet-anchor-api", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "bitcoin-core": "^4.2.0", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@uphold/request-logger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@uphold/request-logger/-/request-logger-2.0.0.tgz", + "integrity": "sha512-UvGS+v87C7VTtQDcFHDLfvfl1zaZaLSwSmAnV35Ne7CzAVvotmZqt9lAIoNpMpaoRpdjVIcnUDwPSeIeA//EoQ==", + "license": "MIT", + "dependencies": { + "uuid": "^3.0.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "request": ">=2.27.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bitcoin-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/bitcoin-core/-/bitcoin-core-4.2.0.tgz", + "integrity": "sha512-QFmer//rZhyuYm0mBOVAmMhIG/EFmXDahC3a1LvNTwM8/KszxUGEAjvAT4tzm1FaDj4c7pQWMG/vOWtoKjeR3Q==", + "license": "MIT", + "dependencies": { + "@uphold/request-logger": "^2.0.0", + "debugnyan": "^1.0.0", + "json-bigint": "^1.0.0", + "lodash": "^4.0.0", + "request": "^2.53.0", + "semver": "^5.1.0", + "standard-error": "^1.1.0" + }, + "engines": { + "node": ">=7" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bunyan": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debugnyan": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/debugnyan/-/debugnyan-1.0.0.tgz", + "integrity": "sha512-dTvKxcLZCammDLFYi31NRVr5g6vjJ33uf1wcdbIPPxPxxnJ9/xj00Mh/YQkhFMw/VGavaG5KpjSC+4o9r/JjRg==", + "license": "MIT", + "dependencies": { + "bunyan": "^1.8.1", + "debug": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "nan": "^2.14.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nan": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", + "license": "MIT", + "optional": true + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "license": "MIT", + "optional": true, + "bin": { + "ncp": "bin/ncp" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^6.0.1" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "license": "MIT", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/standard-error/-/standard-error-1.1.0.tgz", + "integrity": "sha512-4v7qzU7oLJfMI5EltUSHCaaOd65J6S4BqKRWgzMi4EYaE5fvNabPxmAPGdxpGXqrcWjhDGI/H09CIdEuUOUeXg==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + } + } +} diff --git a/api-anchorage/package.json b/api-anchorage/package.json new file mode 100644 index 0000000..2495422 --- /dev/null +++ b/api-anchorage/package.json @@ -0,0 +1,29 @@ +{ + "name": "bitcoin-signet-anchor-api", + "version": "1.0.0", + "description": "API d'ancrage Bitcoin Signet pour certificator.4nkweb.com", + "main": "src/server.js", + "type": "module", + "scripts": { + "start": "node src/server.js", + "dev": "node --watch src/server.js", + "test": "node src/test-client.js" + }, + "keywords": [ + "bitcoin", + "signet", + "anchor", + "api" + ], + "author": "Équipe 4NK", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "bitcoin-core": "^4.2.0", + "dotenv": "^16.3.1", + "cors": "^2.8.5" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/api-anchorage/src/bitcoin-rpc.js b/api-anchorage/src/bitcoin-rpc.js new file mode 100644 index 0000000..b72631a --- /dev/null +++ b/api-anchorage/src/bitcoin-rpc.js @@ -0,0 +1,374 @@ +/** + * Client Bitcoin RPC + * + * Gère la connexion et les appels RPC vers le nœud Bitcoin Signet + */ + +import Client from 'bitcoin-core'; +import { logger } from './logger.js'; + +class BitcoinRPC { + constructor() { + this.client = new Client({ + host: process.env.BITCOIN_RPC_HOST || 'localhost', + port: parseInt(process.env.BITCOIN_RPC_PORT || '38332'), + username: process.env.BITCOIN_RPC_USER || 'bitcoin', + password: process.env.BITCOIN_RPC_PASSWORD || 'bitcoin', + timeout: parseInt(process.env.BITCOIN_RPC_TIMEOUT || '30000'), + }); + } + + /** + * Vérifie la connexion au nœud Bitcoin + * @returns {Promise} Informations sur le nœud + */ + async checkConnection() { + try { + const networkInfo = await this.client.getNetworkInfo(); + const blockchainInfo = await this.client.getBlockchainInfo(); + + return { + connected: true, + blocks: blockchainInfo.blocks, + chain: blockchainInfo.chain, + networkactive: networkInfo.networkactive, + connections: networkInfo.connections, + }; + } catch (error) { + logger.error('Bitcoin RPC connection error', { error: error.message }); + return { + connected: false, + error: error.message, + }; + } + } + + /** + * Obtient une nouvelle adresse depuis le wallet + * @returns {Promise} Adresse Bitcoin + */ + async getNewAddress() { + try { + return await this.client.getNewAddress(); + } catch (error) { + logger.error('Error getting new address', { error: error.message }); + throw new Error(`Failed to get new address: ${error.message}`); + } + } + + /** + * Obtient le solde du wallet + * @returns {Promise} Solde en BTC + */ + async getBalance() { + try { + return await this.client.getBalance(); + } catch (error) { + logger.error('Error getting balance', { error: error.message }); + throw new Error(`Failed to get balance: ${error.message}`); + } + } + + /** + * Crée une transaction d'ancrage + * + * @param {string} hash - Hash du document à ancrer (hex) + * @param {string} recipientAddress - Adresse de destination (optionnel, utilise getNewAddress si non fourni) + * @returns {Promise} Transaction créée avec txid + */ + async createAnchorTransaction(hash, recipientAddress = null) { + try { + // Vérifier que le hash est valide (64 caractères hex) + if (!/^[0-9a-fA-F]{64}$/.test(hash)) { + throw new Error('Invalid hash format. Must be 64 character hexadecimal string.'); + } + + // Obtenir une adresse de destination si non fournie + const address = recipientAddress || await this.getNewAddress(); + + // Obtenir le solde disponible + const balance = await this.getBalance(); + const feeRate = parseFloat(process.env.MINING_FEE_RATE || '0.00001'); + + if (balance < feeRate) { + throw new Error(`Insufficient balance. Required: ${feeRate} BTC, Available: ${balance} BTC`); + } + + // Créer une transaction avec le hash dans les données OP_RETURN + // Format: OP_RETURN + "ANCHOR:" + hash (32 bytes) + const hashBuffer = Buffer.from(hash, 'hex'); + const anchorData = Buffer.concat([ + Buffer.from('ANCHOR:', 'utf8'), + hashBuffer, + ]); + + // Obtenir les UTXOs disponibles (inclure les non confirmés pour avoir plus d'options) + // Utiliser fetch directement avec l'URL RPC incluant le wallet pour éviter les problèmes de wallet + const walletName = process.env.BITCOIN_RPC_WALLET || 'custom_signet'; + const host = process.env.BITCOIN_RPC_HOST || 'localhost'; + const port = process.env.BITCOIN_RPC_PORT || '38332'; + const username = process.env.BITCOIN_RPC_USER || 'bitcoin'; + const password = process.env.BITCOIN_RPC_PASSWORD || 'bitcoin'; + const rpcUrl = `http://${host}:${port}/wallet/${walletName}`; + + // Utiliser Basic Auth dans les headers (fetch ne supporte pas les credentials dans l'URL) + const auth = Buffer.from(`${username}:${password}`).toString('base64'); + + const rpcResponse = await fetch(rpcUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${auth}`, + }, + body: JSON.stringify({ + jsonrpc: '1.0', + id: 'listunspent', + method: 'listunspent', + params: [0], + }), + }); + + if (!rpcResponse.ok) { + const errorText = await rpcResponse.text(); + logger.error('HTTP error in listunspent', { status: rpcResponse.status, statusText: rpcResponse.statusText, response: errorText }); + throw new Error(`HTTP error fetching UTXOs: ${rpcResponse.status} ${rpcResponse.statusText}`); + } + + const rpcResult = await rpcResponse.json(); + if (rpcResult.error) { + logger.error('RPC error in listunspent', { error: rpcResult.error }); + throw new Error(`RPC error: ${rpcResult.error.message}`); + } + const unspent = rpcResult.result; + + logger.info('Fetched UTXOs', { count: unspent.length, firstFew: unspent.slice(0, 3).map(u => ({ txid: u.txid.substring(0, 16), vout: u.vout, amount: u.amount })) }); + + if (unspent.length === 0) { + throw new Error('No unspent outputs available'); + } + + // Log pour déboguer + logger.info('Available UTXOs', { + count: unspent.length, + amounts: unspent.map(u => u.amount).slice(0, 10), + largest: unspent.length > 0 ? Math.max(...unspent.map(u => u.amount)) : 0, + }); + + // Sélectionner un UTXO avec suffisamment de fonds + // Trier par montant décroissant pour prendre le plus grand UTXO disponible + const sortedUnspent = [...unspent].sort((a, b) => b.amount - a.amount); + const amount = 0.00001; // Montant minimal pour la transaction + const estimatedFee = 0.00005; // Estimation des frais (conservateur) + const totalNeeded = amount + estimatedFee; + + // Trouver un UTXO avec suffisamment de fonds + let utxo = sortedUnspent.find(u => u.amount >= totalNeeded); + if (!utxo) { + // Si aucun UTXO n'est suffisant, utiliser le plus grand disponible + utxo = sortedUnspent[0]; + logger.warn('Using largest available UTXO', { + required: totalNeeded, + available: utxo.amount, + allAmounts: sortedUnspent.map(u => u.amount).slice(0, 10), + }); + if (utxo.amount < totalNeeded) { + throw new Error(`Insufficient UTXO amount. Required: ${totalNeeded} BTC, Largest available: ${utxo.amount} BTC. All UTXOs: ${sortedUnspent.map(u => u.amount).join(', ')}`); + } + } + + // Créer la transaction raw avec les inputs et outputs (sans fundrawtransaction) + // Cela évite les erreurs de frais trop élevés avec la bibliothèque bitcoin-core + const inputs = [{ + txid: utxo.txid, + vout: utxo.vout, + }]; + + const outputs = { + [address]: amount, // Montant minimal pour la transaction + data: anchorData.toString('hex'), // OP_RETURN output + }; + + const tx = await this.client.command('createrawtransaction', inputs, outputs); + + // Signer la transaction + // Utiliser command() directement pour éviter les problèmes avec la bibliothèque + const signedTx = await this.client.command('signrawtransactionwithwallet', tx); + + if (!signedTx.complete) { + throw new Error('Transaction signing failed'); + } + + // Envoyer la transaction au mempool + // Utiliser command() avec maxfeerate comme deuxième paramètre (0 = accepter n'importe quel taux) + // Le test direct avec bitcoin-cli fonctionne avec cette syntaxe + const txid = await this.client.command('sendrawtransaction', signedTx.hex, 0); + + logger.info('Anchor transaction sent to mempool', { + txid, + hash: hash.substring(0, 16) + '...', + address, + }); + + // Obtenir les informations de la transaction (dans le mempool) + const txInfo = await this.getTransactionInfo(txid); + + return { + txid, + status: 'confirmed', // Transaction dans le mempool + confirmations: txInfo.confirmations || 0, + block_height: txInfo.blockheight || null, // null si pas encore dans un bloc + }; + } catch (error) { + logger.error('Error creating anchor transaction', { + error: error.message, + hash: hash?.substring(0, 16) + '...', + }); + throw error; + } + } + + /** + * Obtient les informations d'une transaction + * @param {string} txid - ID de la transaction + * @returns {Promise} Informations de la transaction + */ + async getTransactionInfo(txid) { + try { + const tx = await this.client.getTransaction(txid); + const blockchainInfo = await this.client.getBlockchainInfo(); + + return { + txid: tx.txid, + confirmations: tx.confirmations || 0, + blockheight: tx.blockheight || null, + blockhash: tx.blockhash || null, + time: tx.time || null, + currentBlockHeight: blockchainInfo.blocks, + }; + } catch (error) { + logger.error('Error getting transaction info', { error: error.message, txid }); + throw new Error(`Failed to get transaction info: ${error.message}`); + } + } + + /** + * Vérifie si un hash est ancré dans la blockchain + * + * @param {string} hash - Hash à vérifier + * @param {string} txid - ID de transaction optionnel pour accélérer la recherche + * @returns {Promise} Résultat de la vérification + */ + async verifyAnchor(hash, txid = null) { + try { + // Vérifier que le hash est valide + if (!/^[0-9a-fA-F]{64}$/.test(hash)) { + throw new Error('Invalid hash format. Must be 64 character hexadecimal string.'); + } + + // Si un txid est fourni, vérifier directement cette transaction + if (txid) { + try { + const tx = await this.client.getTransaction(txid, true); + const rawTx = await this.client.getRawTransaction(txid, true); + + // Vérifier si le hash est dans les outputs OP_RETURN + const hashFound = this.checkHashInTransaction(rawTx, hash); + + if (hashFound) { + return { + verified: true, + anchor_info: { + transaction_id: txid, + block_height: tx.blockheight || null, + confirmations: tx.confirmations || 0, + }, + }; + } + } catch (error) { + // Si la transaction n'existe pas, continuer la recherche + logger.warn('Transaction not found, searching blockchain', { txid, error: error.message }); + } + } + + // Rechercher dans les blocs récents (derniers 100 blocs) + const blockchainInfo = await this.client.getBlockchainInfo(); + const currentHeight = blockchainInfo.blocks; + const searchRange = 100; // Rechercher dans les 100 derniers blocs + + for (let height = currentHeight; height >= Math.max(0, currentHeight - searchRange); height--) { + try { + const blockHash = await this.client.getBlockHash(height); + const block = await this.client.getBlock(blockHash, 2); // Verbose level 2 + + // Parcourir toutes les transactions du bloc + for (const tx of block.tx || []) { + try { + const rawTx = await this.client.getRawTransaction(tx.txid, true); + const hashFound = this.checkHashInTransaction(rawTx, hash); + + if (hashFound) { + return { + verified: true, + anchor_info: { + transaction_id: tx.txid, + block_height: height, + confirmations: currentHeight - height + 1, + }, + }; + } + } catch (error) { + // Continuer avec la transaction suivante + logger.debug('Error checking transaction', { txid: tx.txid, error: error.message }); + } + } + } catch (error) { + // Continuer avec le bloc suivant + logger.debug('Error checking block', { height, error: error.message }); + } + } + + // Hash non trouvé + return { + verified: false, + message: 'Hash not found in recent blocks', + }; + } catch (error) { + logger.error('Error verifying anchor', { error: error.message, hash: hash?.substring(0, 16) + '...' }); + throw error; + } + } + + /** + * Vérifie si un hash est présent dans une transaction + * @param {Object} rawTx - Transaction brute + * @param {string} hash - Hash à rechercher + * @returns {boolean} True si le hash est trouvé + */ + checkHashInTransaction(rawTx, hash) { + try { + // Parcourir les outputs de la transaction + for (const output of rawTx.vout || []) { + // Chercher dans les scripts OP_RETURN + if (output.scriptPubKey && output.scriptPubKey.hex) { + const scriptHex = output.scriptPubKey.hex; + + // Vérifier si le script contient "ANCHOR:" suivi du hash + const anchorPrefix = Buffer.from('ANCHOR:', 'utf8').toString('hex'); + const hashHex = hash.toLowerCase(); + + if (scriptHex.includes(anchorPrefix + hashHex)) { + return true; + } + } + } + return false; + } catch (error) { + logger.error('Error checking hash in transaction', { error: error.message }); + return false; + } + } +} + +// Export class and singleton +export { BitcoinRPC }; +export const bitcoinRPC = new BitcoinRPC(); diff --git a/api-anchorage/src/logger.js b/api-anchorage/src/logger.js new file mode 100644 index 0000000..cf1e3a4 --- /dev/null +++ b/api-anchorage/src/logger.js @@ -0,0 +1,41 @@ +/** + * Logger simple + */ + +const LOG_LEVELS = { + error: 0, + warn: 1, + info: 2, + debug: 3, +}; + +const currentLevel = LOG_LEVELS[process.env.LOG_LEVEL || 'info'] || LOG_LEVELS.info; + +function formatMessage(level, message, meta = {}) { + const timestamp = new Date().toISOString(); + const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : ''; + return `[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`; +} + +export const logger = { + error: (message, meta = {}) => { + if (currentLevel >= LOG_LEVELS.error) { + console.error(formatMessage('error', message, meta)); + } + }, + warn: (message, meta = {}) => { + if (currentLevel >= LOG_LEVELS.warn) { + console.warn(formatMessage('warn', message, meta)); + } + }, + info: (message, meta = {}) => { + if (currentLevel >= LOG_LEVELS.info) { + console.log(formatMessage('info', message, meta)); + } + }, + debug: (message, meta = {}) => { + if (currentLevel >= LOG_LEVELS.debug) { + console.log(formatMessage('debug', message, meta)); + } + }, +}; diff --git a/api-anchorage/src/routes/anchor.js b/api-anchorage/src/routes/anchor.js new file mode 100644 index 0000000..8f5261e --- /dev/null +++ b/api-anchorage/src/routes/anchor.js @@ -0,0 +1,117 @@ +/** + * Routes d'ancrage + */ + +import express from 'express'; +import { bitcoinRPC } from '../bitcoin-rpc.js'; +import { logger } from '../logger.js'; + +export const anchorRouter = express.Router(); + +/** + * POST /api/anchor/document + * Ancre un document sur Bitcoin Signet + * + * La transaction est envoyée au mempool et retournée immédiatement. + * + * Body: + * - documentUid: string (identifiant du document) + * - hash: string (hash SHA256 du document en hex, 64 caractères) + */ +anchorRouter.post('/document', async (req, res) => { + try { + const { documentUid, hash } = req.body; + + // Validation + if (!hash) { + return res.status(400).json({ + error: 'Bad Request', + message: 'hash is required', + }); + } + + if (!/^[0-9a-fA-F]{64}$/.test(hash)) { + return res.status(400).json({ + error: 'Bad Request', + message: 'hash must be a 64 character hexadecimal string', + }); + } + + logger.info('Anchor request received', { + documentUid: documentUid || 'unknown', + hash: hash.substring(0, 16) + '...', + }); + + // Créer la transaction d'ancrage et l'envoyer au mempool + const result = await bitcoinRPC.createAnchorTransaction(hash); + + // Retourner le résultat immédiatement (transaction dans le mempool) + res.status(200).json({ + txid: result.txid, + status: result.status, + confirmations: result.confirmations, + block_height: result.block_height, + }); + } catch (error) { + logger.error('Anchor error', { error: error.message, stack: error.stack }); + + // Déterminer le code de statut approprié + let statusCode = 500; + if (error.message.includes('Insufficient balance')) { + statusCode = 402; // Payment Required + } else if (error.message.includes('Invalid')) { + statusCode = 400; // Bad Request + } + + res.status(statusCode).json({ + error: error.message.includes('Insufficient balance') ? 'Insufficient Balance' : 'Internal Server Error', + message: error.message, + }); + } +}); + +/** + * POST /api/anchor/verify + * Vérifie si un hash est ancré sur Bitcoin Signet + * + * Body: + * - hash: string (hash SHA256 à vérifier, 64 caractères hex) + * - txid: string (optionnel, ID de transaction pour accélérer la recherche) + */ +anchorRouter.post('/verify', async (req, res) => { + try { + const { hash, txid } = req.body; + + // Validation + if (!hash) { + return res.status(400).json({ + error: 'Bad Request', + message: 'hash is required', + }); + } + + if (!/^[0-9a-fA-F]{64}$/.test(hash)) { + return res.status(400).json({ + error: 'Bad Request', + message: 'hash must be a 64 character hexadecimal string', + }); + } + + logger.info('Verify request received', { + hash: hash.substring(0, 16) + '...', + txid: txid ? txid.substring(0, 16) + '...' : 'none', + }); + + // Vérifier l'ancrage + const result = await bitcoinRPC.verifyAnchor(hash, txid); + + res.status(200).json(result); + } catch (error) { + logger.error('Verify error', { error: error.message, stack: error.stack }); + + res.status(500).json({ + error: 'Internal Server Error', + message: error.message, + }); + } +}); diff --git a/api-anchorage/src/routes/health.js b/api-anchorage/src/routes/health.js new file mode 100644 index 0000000..af82da9 --- /dev/null +++ b/api-anchorage/src/routes/health.js @@ -0,0 +1,40 @@ +/** + * Route Health Check + */ + +import express from 'express'; +import { bitcoinRPC } from '../bitcoin-rpc.js'; +import { logger } from '../logger.js'; + +export const healthRouter = express.Router(); + +/** + * GET /health + * Vérifie l'état de l'API et de la connexion Bitcoin + */ +healthRouter.get('/', async (req, res) => { + try { + const bitcoinStatus = await bitcoinRPC.checkConnection(); + + const health = { + ok: bitcoinStatus.connected, + service: 'anchor-api', + bitcoin: { + connected: bitcoinStatus.connected, + blocks: bitcoinStatus.blocks || 0, + }, + timestamp: new Date().toISOString(), + }; + + const statusCode = bitcoinStatus.connected ? 200 : 503; + res.status(statusCode).json(health); + } catch (error) { + logger.error('Health check error', { error: error.message }); + res.status(503).json({ + ok: false, + service: 'anchor-api', + error: error.message, + timestamp: new Date().toISOString(), + }); + } +}); diff --git a/api-anchorage/src/server.js b/api-anchorage/src/server.js new file mode 100644 index 0000000..e8f533f --- /dev/null +++ b/api-anchorage/src/server.js @@ -0,0 +1,121 @@ +#!/usr/bin/env node + +/** + * API d'Ancrage Bitcoin Signet + * + * Cette API permet d'ancrer des documents sur la blockchain Bitcoin Signet + * en créant des transactions qui incluent les hash des documents. + * + * Port: 3010 + * Domaine: certificator.4nkweb.com + */ + +import express from 'express'; +import cors from 'cors'; +import dotenv from 'dotenv'; +import { BitcoinRPC } from './bitcoin-rpc.js'; +import { anchorRouter } from './routes/anchor.js'; +import { healthRouter } from './routes/health.js'; +import { logger } from './logger.js'; + +// Charger les variables d'environnement +dotenv.config(); + +const app = express(); +const PORT = process.env.API_PORT || 3010; +const HOST = process.env.API_HOST || '0.0.0.0'; + +// Middleware +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// Middleware de logging +app.use((req, res, next) => { + logger.info(`${req.method} ${req.path}`, { + ip: req.ip, + userAgent: req.get('user-agent'), + }); + next(); +}); + +// Middleware d'authentification API Key +app.use((req, res, next) => { + // Exclure /health de l'authentification + if (req.path === '/health' || req.path === '/') { + return next(); + } + + const apiKey = req.headers['x-api-key']; + const validKeys = process.env.API_KEYS?.split(',').map(k => k.trim()) || []; + + if (!apiKey || !validKeys.includes(apiKey)) { + logger.warn('Unauthorized API access attempt', { ip: req.ip, path: req.path }); + return res.status(401).json({ + error: 'Unauthorized', + message: 'Invalid or missing API key', + }); + } + + next(); +}); + +// Routes +app.use('/health', healthRouter); +app.use('/api/anchor', anchorRouter); + +// Route racine +app.get('/', (req, res) => { + res.json({ + service: 'bitcoin-signet-anchor-api', + version: '1.0.0', + endpoints: { + health: '/health', + anchor: '/api/anchor/document', + verify: '/api/anchor/verify', + }, + }); +}); + +// Gestion des erreurs +app.use((err, req, res, next) => { + logger.error('Unhandled error', { error: err.message, stack: err.stack }); + res.status(500).json({ + error: 'Internal Server Error', + message: process.env.NODE_ENV === 'development' ? err.message : 'An error occurred', + }); +}); + +// Gestion des routes non trouvées +app.use((req, res) => { + res.status(404).json({ + error: 'Not Found', + message: `Route ${req.method} ${req.path} not found`, + }); +}); + +// Démarrage du serveur +const server = app.listen(PORT, HOST, () => { + logger.info(`API d'ancrage Bitcoin Signet démarrée`, { + host: HOST, + port: PORT, + environment: process.env.NODE_ENV || 'production', + }); +}); + +// Gestion de l'arrêt propre +process.on('SIGTERM', () => { + logger.info('SIGTERM received, shutting down gracefully'); + server.close(() => { + logger.info('Server closed'); + process.exit(0); + }); +}); + +process.on('SIGINT', () => { + logger.info('SIGINT received, shutting down gracefully'); + server.close(() => { + logger.info('Server closed'); + process.exit(0); + }); +}); diff --git a/api-anchorage/src/test-client.js b/api-anchorage/src/test-client.js new file mode 100644 index 0000000..4a89057 --- /dev/null +++ b/api-anchorage/src/test-client.js @@ -0,0 +1,197 @@ +#!/usr/bin/env node + +/** + * Client de test pour l'API d'ancrage + * Usage: node src/test-client.js + */ + +import https from 'https'; +import http from 'http'; +import crypto from 'crypto'; + +// Configuration +const API_URL = process.env.API_URL || 'http://localhost:3010'; +const API_KEY = process.env.API_KEY || 'your-api-key-here'; + +/** + * Effectue une requête HTTP/HTTPS + */ +function makeRequest(method, path, data = null) { + return new Promise((resolve, reject) => { + const url = new URL(path, API_URL); + const isHttps = url.protocol === 'https:'; + const httpModule = isHttps ? https : http; + + const options = { + hostname: url.hostname, + port: url.port || (isHttps ? 443 : 80), + path: url.pathname, + method: method, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': API_KEY, + }, + }; + + const req = httpModule.request(options, (res) => { + let body = ''; + + res.on('data', (chunk) => { + body += chunk; + }); + + res.on('end', () => { + try { + const json = JSON.parse(body); + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(json); + } else { + reject(new Error(`HTTP ${res.statusCode}: ${json.error || body}`)); + } + } catch (e) { + reject(new Error(`Parse error: ${e.message}`)); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + if (data) { + req.write(JSON.stringify(data)); + } + + req.end(); + }); +} + +/** + * Vérifie l'état de l'API + */ +async function checkHealth() { + try { + console.log('🔍 Vérification de l\'état de l\'API...'); + const response = await makeRequest('GET', '/health'); + console.log('✅ API opérationnelle'); + console.log(` Bitcoin connecté: ${response.bitcoin.connected ? 'Oui' : 'Non'}`); + console.log(` Blocs: ${response.bitcoin.blocks}`); + return response; + } catch (error) { + console.error('❌ Erreur:', error.message); + throw error; + } +} + +/** + * Ancre un document sur Bitcoin Signet + * La transaction est envoyée au mempool et retournée immédiatement. + */ +async function anchorDocument(documentUid, hash) { + try { + console.log(`\n📎 Ancrage du document: ${documentUid}`); + console.log(` Hash: ${hash.substring(0, 16)}...`); + + const data = { + documentUid, + hash, + }; + + const response = await makeRequest('POST', '/api/anchor/document', data); + + console.log('✅ Document ancré avec succès'); + console.log(` Transaction ID: ${response.txid}`); + console.log(` Confirmations: ${response.confirmations}`); + if (response.block_height) { + console.log(` Block height: ${response.block_height}`); + } + + return response; + } catch (error) { + console.error('❌ Erreur lors de l\'ancrage:', error.message); + throw error; + } +} + +/** + * Vérifie si un hash est ancré + */ +async function verifyAnchor(hash, txid = null) { + try { + console.log(`\n🔍 Vérification de l'ancrage...`); + console.log(` Hash: ${hash.substring(0, 16)}...`); + + const data = { hash }; + if (txid) { + data.txid = txid; + console.log(` Transaction ID: ${txid.substring(0, 16)}...`); + } + + const response = await makeRequest('POST', '/api/anchor/verify', data); + + if (response.verified) { + console.log('✅ Hash vérifié et ancré'); + if (response.anchor_info) { + console.log(` Transaction ID: ${response.anchor_info.transaction_id}`); + console.log(` Block height: ${response.anchor_info.block_height}`); + console.log(` Confirmations: ${response.anchor_info.confirmations}`); + } + } else { + console.log('❌ Hash non trouvé dans la blockchain'); + } + + return response; + } catch (error) { + console.error('❌ Erreur lors de la vérification:', error.message); + throw error; + } +} + +/** + * Fonction principale + */ +async function main() { + console.log('=== Client API Anchor ===\n'); + + try { + // 1. Vérifier l'état de l'API + await checkHealth(); + + // 2. Générer un hash de test + const documentContent = `Document de test - ${new Date().toISOString()}`; + const hash = crypto.createHash('sha256').update(documentContent).digest('hex'); + const documentUid = `doc-${Date.now()}`; + + console.log(`\n📄 Document de test:`); + console.log(` UID: ${documentUid}`); + console.log(` Hash: ${hash}`); + + // 3. Ancrer le document + const anchorResult = await anchorDocument(documentUid, hash); + + // 4. Attendre un peu pour que la transaction soit propagée + console.log('\n⏳ Attente de 3 secondes pour la propagation...'); + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // 5. Vérifier l'ancrage + await verifyAnchor(hash, anchorResult.txid); + + console.log('\n✅ Tous les tests sont passés avec succès!'); + + } catch (error) { + console.error('\n❌ Erreur:', error.message); + process.exit(1); + } +} + +// Exécuter le script +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} + +// Exports pour utilisation comme module +export { + checkHealth, + anchorDocument, + verifyAnchor, +}; diff --git a/api-faucet/.env.example b/api-faucet/.env.example new file mode 100644 index 0000000..4e7c635 --- /dev/null +++ b/api-faucet/.env.example @@ -0,0 +1,17 @@ +# Bitcoin RPC Configuration +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +BITCOIN_RPC_TIMEOUT=30000 + +# API Configuration +FAUCET_API_PORT=3021 +FAUCET_API_HOST=0.0.0.0 + +# Faucet Configuration +FAUCET_AMOUNT=0.0005 # Montant en BTC (50000 sats par défaut) + +# Logging +LOG_LEVEL=info +NODE_ENV=production diff --git a/api-faucet/.gitignore b/api-faucet/.gitignore new file mode 100644 index 0000000..5c532ee --- /dev/null +++ b/api-faucet/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.env +*.log +.DS_Store +dist/ +build/ diff --git a/api-faucet/README.md b/api-faucet/README.md new file mode 100644 index 0000000..b7acf24 --- /dev/null +++ b/api-faucet/README.md @@ -0,0 +1,306 @@ +# API Faucet Bitcoin Signet + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0.0 + +## Description + +API REST pour distribuer des sats (50000 sats = 0.0005 BTC) sur la blockchain Bitcoin Signet. Cette API permet aux utilisateurs de recevoir des fonds de test pour développer et tester des applications Bitcoin. + +## Caractéristiques + +- **Port** : `3021` +- **Format** : JSON REST API +- **Bitcoin** : Connexion RPC au nœud Bitcoin Signet (port 38332) +- **Montant par défaut** : 50 000 sats (0.0005 BTC) + +## Installation + +### Prérequis + +- Node.js >= 18.0.0 +- Accès au nœud Bitcoin Signet (RPC sur port 38332) +- Wallet Bitcoin avec des fonds pour distribuer + +### Installation des Dépendances + +```bash +cd api-faucet +npm install +``` + +### Configuration + +1. Copier le fichier d'exemple : +```bash +cp .env.example .env +``` + +2. Éditer `.env` : +```bash +# Bitcoin RPC Configuration +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +BITCOIN_RPC_TIMEOUT=30000 + +# API Configuration +FAUCET_API_PORT=3021 +FAUCET_API_HOST=0.0.0.0 + +# Faucet Configuration +FAUCET_AMOUNT=0.0005 # Montant en BTC (50000 sats par défaut) + +# Logging +LOG_LEVEL=info +NODE_ENV=production +``` + +**Important** : +- `BITCOIN_RPC_HOST` : Si l'API est dans un conteneur Docker, utiliser l'IP du conteneur Bitcoin ou `host.docker.internal` +- `FAUCET_AMOUNT` : Montant à distribuer en BTC (par défaut 0.0005 = 50000 sats) + +## Démarrage + +### Mode Développement + +```bash +npm run dev +``` + +### Mode Production + +```bash +npm start +``` + +### Avec PM2 (recommandé pour production) + +```bash +# Installer PM2 globalement +sudo npm install -g pm2 + +# Démarrer l'API avec PM2 +pm2 start src/server.js --name faucet-api + +# Sauvegarder la configuration PM2 +pm2 save + +# Configurer PM2 pour démarrer au boot +pm2 startup +# Suivre les instructions affichées +``` + +## Endpoints + +### GET /health + +Vérifie l'état de l'API et de la connexion Bitcoin. + +**Authentification** : Non requise + +**Réponse** : +```json +{ + "status": "ok", + "service": "bitcoin-signet-faucet-api", + "version": "1.0.0", + "bitcoin": { + "connected": true, + "chain": "signet", + "blocks": 1234, + "networkactive": true, + "connections": 5, + "wallet_balance": 10.5 + }, + "timestamp": "2026-01-23T12:00:00.000Z" +} +``` + +### POST /api/faucet/request + +Demande des sats via le faucet. + +**Authentification** : Non requise + +**Body** : +```json +{ + "address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc" +} +``` + +**Réponse (succès)** : +```json +{ + "success": true, + "txid": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890", + "address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc", + "amount": 0.0005, + "amount_sats": 50000, + "status": "pending", + "confirmations": 0, + "block_height": null +} +``` + +**Réponse (erreur)** : +```json +{ + "error": "Bad Request", + "message": "Invalid Bitcoin address format" +} +``` + +## Exemples d'Utilisation + +### Avec curl + +```bash +# Vérifier l'état de l'API +curl http://localhost:3021/health + +# Demander des sats +curl -X POST http://localhost:3021/api/faucet/request \ + -H "Content-Type: application/json" \ + -d '{ + "address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc" + }' +``` + +### Avec JavaScript (fetch) + +```javascript +const response = await fetch('http://localhost:3021/api/faucet/request', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + address: 'tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc', + }), +}); + +const result = await response.json(); +console.log(result); +``` + +## Configuration Nginx (sur proxy 192.168.1.100) + +Ajouter dans la configuration nginx du proxy : + +```nginx +# API Faucet +server { + listen 443 ssl http2; + server_name faucet.signet.4nkweb.com; + + ssl_certificate /etc/letsencrypt/live/faucet.signet.4nkweb.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/faucet.signet.4nkweb.com/privkey.pem; + + location / { + proxy_pass http://192.168.1.103:3021; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} +``` + +**Note** : Remplacer `192.168.1.103` par l'IP du serveur où l'API est déployée. + +## Architecture + +``` +Client + ↓ HTTPS +Nginx (proxy 192.168.1.100:443) + ↓ HTTP +API Faucet (port 3021) + ↓ RPC +Bitcoin Signet Node (port 38332) +``` + +## Sécurité + +### Rate Limiting (Recommandé) + +Pour éviter les abus, il est recommandé d'implémenter un rate limiting. Exemple avec `express-rate-limit` : + +```bash +npm install express-rate-limit +``` + +```javascript +import rateLimit from 'express-rate-limit'; + +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 1, // 1 requête par adresse IP toutes les 15 minutes + message: 'Too many requests from this IP, please try again later.', +}); + +app.use('/api/faucet/request', limiter); +``` + +### Validation des Adresses + +L'API valide automatiquement les adresses Bitcoin avant d'envoyer les fonds. Seules les adresses valides sont acceptées. + +### Gestion des Erreurs + +- **400 Bad Request** : Adresse invalide +- **503 Service Unavailable** : Solde insuffisant +- **500 Internal Server Error** : Erreur serveur + +## Dépannage + +### L'API ne démarre pas + +- Vérifier que le port 3021 n'est pas utilisé : `netstat -tlnp | grep 3021` +- Vérifier les logs : `pm2 logs faucet-api` +- Vérifier la configuration dans `.env` + +### Erreur "Insufficient balance" + +- Vérifier le solde du wallet : `bitcoin-cli getbalance` +- Miner des blocs pour obtenir des fonds +- Vérifier que le wallet est déverrouillé + +### Erreur "Invalid Bitcoin address" + +- Vérifier que l'adresse est une adresse Signet valide (commence par `tb1`, `bcrt1`, `2`, ou `3`) +- Vérifier le format de l'adresse (25-62 caractères) + +### Transaction non envoyée + +- Vérifier les logs de l'API pour les erreurs RPC +- Vérifier que le nœud Bitcoin est accessible +- Vérifier que le wallet est déverrouillé et a des fonds + +## Structure des Fichiers + +``` +api-faucet/ +├── package.json +├── README.md +├── .env.example +└── src/ + ├── server.js # Serveur Express + ├── bitcoin-rpc.js # Client Bitcoin RPC + ├── logger.js # Logger + └── routes/ + ├── faucet.js # Routes du faucet + └── health.js # Routes de santé +``` + +## Licence + +MIT diff --git a/api-faucet/package-lock.json b/api-faucet/package-lock.json new file mode 100644 index 0000000..34d6c9c --- /dev/null +++ b/api-faucet/package-lock.json @@ -0,0 +1,1602 @@ +{ + "name": "bitcoin-signet-faucet-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bitcoin-signet-faucet-api", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "bitcoin-core": "^4.2.0", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@uphold/request-logger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@uphold/request-logger/-/request-logger-2.0.0.tgz", + "integrity": "sha512-UvGS+v87C7VTtQDcFHDLfvfl1zaZaLSwSmAnV35Ne7CzAVvotmZqt9lAIoNpMpaoRpdjVIcnUDwPSeIeA//EoQ==", + "license": "MIT", + "dependencies": { + "uuid": "^3.0.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "request": ">=2.27.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bitcoin-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/bitcoin-core/-/bitcoin-core-4.2.0.tgz", + "integrity": "sha512-QFmer//rZhyuYm0mBOVAmMhIG/EFmXDahC3a1LvNTwM8/KszxUGEAjvAT4tzm1FaDj4c7pQWMG/vOWtoKjeR3Q==", + "license": "MIT", + "dependencies": { + "@uphold/request-logger": "^2.0.0", + "debugnyan": "^1.0.0", + "json-bigint": "^1.0.0", + "lodash": "^4.0.0", + "request": "^2.53.0", + "semver": "^5.1.0", + "standard-error": "^1.1.0" + }, + "engines": { + "node": ">=7" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bunyan": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debugnyan": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/debugnyan/-/debugnyan-1.0.0.tgz", + "integrity": "sha512-dTvKxcLZCammDLFYi31NRVr5g6vjJ33uf1wcdbIPPxPxxnJ9/xj00Mh/YQkhFMw/VGavaG5KpjSC+4o9r/JjRg==", + "license": "MIT", + "dependencies": { + "bunyan": "^1.8.1", + "debug": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "nan": "^2.14.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nan": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", + "license": "MIT", + "optional": true + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "license": "MIT", + "optional": true, + "bin": { + "ncp": "bin/ncp" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^6.0.1" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "license": "MIT", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/standard-error/-/standard-error-1.1.0.tgz", + "integrity": "sha512-4v7qzU7oLJfMI5EltUSHCaaOd65J6S4BqKRWgzMi4EYaE5fvNabPxmAPGdxpGXqrcWjhDGI/H09CIdEuUOUeXg==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + } + } +} diff --git a/api-faucet/package.json b/api-faucet/package.json new file mode 100644 index 0000000..01cd115 --- /dev/null +++ b/api-faucet/package.json @@ -0,0 +1,28 @@ +{ + "name": "bitcoin-signet-faucet-api", + "version": "1.0.0", + "description": "API faucet pour Bitcoin Signet", + "main": "src/server.js", + "type": "module", + "scripts": { + "start": "node src/server.js", + "dev": "node --watch src/server.js" + }, + "keywords": [ + "bitcoin", + "signet", + "faucet", + "api" + ], + "author": "Équipe 4NK", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "bitcoin-core": "^4.2.0", + "dotenv": "^16.3.1", + "cors": "^2.8.5" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/api-faucet/src/bitcoin-rpc.js b/api-faucet/src/bitcoin-rpc.js new file mode 100644 index 0000000..046a861 --- /dev/null +++ b/api-faucet/src/bitcoin-rpc.js @@ -0,0 +1,136 @@ +/** + * Client Bitcoin RPC pour le faucet + * + * Gère la connexion et les appels RPC vers le nœud Bitcoin Signet + */ + +import Client from 'bitcoin-core'; +import { logger } from './logger.js'; + +class BitcoinRPC { + constructor() { + this.client = new Client({ + host: process.env.BITCOIN_RPC_HOST || 'localhost', + port: parseInt(process.env.BITCOIN_RPC_PORT || '38332'), + username: process.env.BITCOIN_RPC_USER || 'bitcoin', + password: process.env.BITCOIN_RPC_PASSWORD || 'bitcoin', + timeout: parseInt(process.env.BITCOIN_RPC_TIMEOUT || '30000'), + }); + } + + /** + * Vérifie la connexion au nœud Bitcoin + * @returns {Promise} Informations sur le nœud + */ + async checkConnection() { + try { + const networkInfo = await this.client.getNetworkInfo(); + const blockchainInfo = await this.client.getBlockchainInfo(); + + return { + connected: true, + blocks: blockchainInfo.blocks, + chain: blockchainInfo.chain, + networkactive: networkInfo.networkactive, + connections: networkInfo.connections, + }; + } catch (error) { + logger.error('Bitcoin RPC connection error', { error: error.message }); + return { + connected: false, + error: error.message, + }; + } + } + + /** + * Obtient le solde du wallet + * @returns {Promise} Solde en BTC + */ + async getBalance() { + try { + return await this.client.getBalance(); + } catch (error) { + logger.error('Error getting balance', { error: error.message }); + throw new Error(`Failed to get balance: ${error.message}`); + } + } + + /** + * Envoie des sats à une adresse + * @param {string} address - Adresse Bitcoin de destination + * @param {number} amount - Montant en BTC (par défaut 0.0005 BTC = 50000 sats) + * @returns {Promise} Transaction créée avec txid + */ + async sendToAddress(address, amount = 0.0005) { + try { + // Vérifier que l'adresse est valide + const validateResult = await this.client.validateAddress(address); + if (!validateResult.isvalid) { + throw new Error('Invalid Bitcoin address'); + } + + // Vérifier le solde disponible + const balance = await this.client.getBalance(); + if (balance < amount) { + throw new Error(`Insufficient balance. Required: ${amount} BTC, Available: ${balance} BTC`); + } + + // Envoyer les sats + const txid = await this.client.sendToAddress(address, amount); + + logger.info('Faucet transaction sent', { + txid, + address, + amount, + }); + + // Obtenir les informations de la transaction + const txInfo = await this.getTransactionInfo(txid); + + return { + txid, + address, + amount, + amount_sats: Math.round(amount * 100000000), + status: 'pending', + confirmations: txInfo.confirmations || 0, + block_height: txInfo.blockheight || null, + }; + } catch (error) { + logger.error('Error sending to address', { + error: error.message, + address, + amount, + }); + throw error; + } + } + + /** + * Obtient les informations d'une transaction + * @param {string} txid - ID de la transaction + * @returns {Promise} Informations de la transaction + */ + async getTransactionInfo(txid) { + try { + const tx = await this.client.getTransaction(txid); + const blockchainInfo = await this.client.getBlockchainInfo(); + + return { + txid: tx.txid, + confirmations: tx.confirmations || 0, + blockheight: tx.blockheight || null, + blockhash: tx.blockhash || null, + time: tx.time || null, + currentBlockHeight: blockchainInfo.blocks, + }; + } catch (error) { + logger.error('Error getting transaction info', { error: error.message, txid }); + throw new Error(`Failed to get transaction info: ${error.message}`); + } + } +} + +// Export singleton +export const bitcoinRPC = new BitcoinRPC(); diff --git a/api-faucet/src/logger.js b/api-faucet/src/logger.js new file mode 100644 index 0000000..3fa0579 --- /dev/null +++ b/api-faucet/src/logger.js @@ -0,0 +1,30 @@ +/** + * Logger simple pour l'API faucet + */ + +const LOG_LEVEL = process.env.LOG_LEVEL || 'info'; + +const levels = { + error: 0, + warn: 1, + info: 2, + debug: 3, +}; + +function log(level, message, meta = {}) { + const levelNum = levels[level] || 2; + const currentLevel = levels[LOG_LEVEL] || 2; + + if (levelNum <= currentLevel) { + const timestamp = new Date().toISOString(); + const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : ''; + console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`); + } +} + +export const logger = { + error: (message, meta) => log('error', message, meta), + warn: (message, meta) => log('warn', message, meta), + info: (message, meta) => log('info', message, meta), + debug: (message, meta) => log('debug', message, meta), +}; diff --git a/api-faucet/src/routes/faucet.js b/api-faucet/src/routes/faucet.js new file mode 100644 index 0000000..6e12190 --- /dev/null +++ b/api-faucet/src/routes/faucet.js @@ -0,0 +1,75 @@ +/** + * Routes du faucet + */ + +import express from 'express'; +import { bitcoinRPC } from '../bitcoin-rpc.js'; +import { logger } from '../logger.js'; + +export const faucetRouter = express.Router(); + +/** + * POST /api/faucet/request + * Demande des sats via le faucet + * + * Body: + * - address: string (adresse Bitcoin Signet valide) + */ +faucetRouter.post('/request', async (req, res) => { + try { + const { address } = req.body; + + // Validation + if (!address) { + return res.status(400).json({ + error: 'Bad Request', + message: 'address is required', + }); + } + + // Validation basique de l'adresse + const addressPattern = /^(tb1|bcrt1|2|3)[a-zA-HJ-NP-Z0-9]{25,62}$/; + if (!addressPattern.test(address)) { + return res.status(400).json({ + error: 'Bad Request', + message: 'Invalid Bitcoin address format', + }); + } + + logger.info('Faucet request received', { + address: address.substring(0, 10) + '...', + }); + + // Montant par défaut : 50000 sats = 0.0005 BTC + const amount = parseFloat(process.env.FAUCET_AMOUNT || '0.0005'); + + // Envoyer les sats + const result = await bitcoinRPC.sendToAddress(address, amount); + + res.status(200).json({ + success: true, + txid: result.txid, + address: result.address, + amount: result.amount, + amount_sats: result.amount_sats, + status: result.status, + confirmations: result.confirmations, + block_height: result.block_height, + }); + } catch (error) { + logger.error('Faucet error', { error: error.message, stack: error.stack }); + + // Déterminer le code de statut approprié + let statusCode = 500; + if (error.message.includes('Insufficient balance')) { + statusCode = 503; // Service Unavailable + } else if (error.message.includes('Invalid')) { + statusCode = 400; // Bad Request + } + + res.status(statusCode).json({ + error: error.message.includes('Insufficient balance') ? 'Insufficient Balance' : 'Internal Server Error', + message: error.message, + }); + } +}); diff --git a/api-faucet/src/routes/health.js b/api-faucet/src/routes/health.js new file mode 100644 index 0000000..0b2c051 --- /dev/null +++ b/api-faucet/src/routes/health.js @@ -0,0 +1,43 @@ +/** + * Routes de santé + */ + +import express from 'express'; +import { bitcoinRPC } from '../bitcoin-rpc.js'; +import { logger } from '../logger.js'; + +export const healthRouter = express.Router(); + +/** + * GET /health + * Vérifie l'état de l'API et de la connexion Bitcoin + */ +healthRouter.get('/', async (req, res) => { + try { + const connection = await bitcoinRPC.checkConnection(); + const balance = await bitcoinRPC.getBalance(); + + res.status(200).json({ + status: 'ok', + service: 'bitcoin-signet-faucet-api', + version: '1.0.0', + bitcoin: { + connected: connection.connected, + chain: connection.chain, + blocks: connection.blocks, + networkactive: connection.networkactive, + connections: connection.connections, + wallet_balance: balance, + }, + timestamp: new Date().toISOString(), + }); + } catch (error) { + logger.error('Health check error', { error: error.message }); + res.status(503).json({ + status: 'error', + service: 'bitcoin-signet-faucet-api', + error: error.message, + timestamp: new Date().toISOString(), + }); + } +}); diff --git a/api-faucet/src/server.js b/api-faucet/src/server.js new file mode 100644 index 0000000..1bfb65e --- /dev/null +++ b/api-faucet/src/server.js @@ -0,0 +1,98 @@ +#!/usr/bin/env node + +/** + * API Faucet Bitcoin Signet + * + * Cette API permet de distribuer des sats (50000 sats = 0.0005 BTC) + * sur la blockchain Bitcoin Signet. + * + * Port: 3021 + */ + +import express from 'express'; +import cors from 'cors'; +import dotenv from 'dotenv'; +import { bitcoinRPC } from './bitcoin-rpc.js'; +import { faucetRouter } from './routes/faucet.js'; +import { healthRouter } from './routes/health.js'; +import { logger } from './logger.js'; + +// Charger les variables d'environnement +dotenv.config(); + +const app = express(); +const PORT = process.env.FAUCET_API_PORT || 3021; +const HOST = process.env.FAUCET_API_HOST || '0.0.0.0'; + +// Middleware +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// Middleware de logging +app.use((req, res, next) => { + logger.info(`${req.method} ${req.path}`, { + ip: req.ip, + userAgent: req.get('user-agent'), + }); + next(); +}); + +// Routes +app.use('/health', healthRouter); +app.use('/api/faucet', faucetRouter); + +// Route racine +app.get('/', (req, res) => { + res.json({ + service: 'bitcoin-signet-faucet-api', + version: '1.0.0', + endpoints: { + health: '/health', + request: '/api/faucet/request', + }, + }); +}); + +// Gestion des erreurs +app.use((err, req, res, next) => { + logger.error('Unhandled error', { error: err.message, stack: err.stack }); + res.status(500).json({ + error: 'Internal Server Error', + message: process.env.NODE_ENV === 'development' ? err.message : 'An error occurred', + }); +}); + +// Gestion des routes non trouvées +app.use((req, res) => { + res.status(404).json({ + error: 'Not Found', + message: `Route ${req.method} ${req.path} not found`, + }); +}); + +// Démarrage du serveur +const server = app.listen(PORT, HOST, () => { + logger.info(`API Faucet Bitcoin Signet démarrée`, { + host: HOST, + port: PORT, + environment: process.env.NODE_ENV || 'production', + }); +}); + +// Gestion de l'arrêt propre +process.on('SIGTERM', () => { + logger.info('SIGTERM received, shutting down gracefully'); + server.close(() => { + logger.info('Server closed'); + process.exit(0); + }); +}); + +process.on('SIGINT', () => { + logger.info('SIGINT received, shutting down gracefully'); + server.close(() => { + logger.info('Server closed'); + process.exit(0); + }); +}); diff --git a/api-faucet/start.sh b/api-faucet/start.sh new file mode 100755 index 0000000..575195c --- /dev/null +++ b/api-faucet/start.sh @@ -0,0 +1,19 @@ +#!/bin/bash +cd "$(dirname "$0")" +export BITCOIN_RPC_HOST=${BITCOIN_RPC_HOST:-localhost} +export BITCOIN_RPC_PORT=${BITCOIN_RPC_PORT:-38332} +export BITCOIN_RPC_USER=${BITCOIN_RPC_USER:-bitcoin} +export BITCOIN_RPC_PASSWORD=${BITCOIN_RPC_PASSWORD:-bitcoin} +export FAUCET_API_PORT=${FAUCET_API_PORT:-3021} +export FAUCET_API_HOST=${FAUCET_API_HOST:-0.0.0.0} +export FAUCET_AMOUNT=${FAUCET_AMOUNT:-0.0005} +export LOG_LEVEL=${LOG_LEVEL:-info} +export NODE_ENV=${NODE_ENV:-production} + +if [ ! -f node_modules/.bin/node ]; then + echo "Installation des dépendances..." + npm install +fi + +echo "Démarrage de l'API Faucet sur le port $FAUCET_API_PORT..." +node src/server.js diff --git a/configure-nginx-proxy.sh b/configure-nginx-proxy.sh new file mode 100755 index 0000000..4dde86c --- /dev/null +++ b/configure-nginx-proxy.sh @@ -0,0 +1,234 @@ +#!/bin/bash + +# Script de configuration Nginx pour les sous-domaines certificator.4nkweb.com +# Usage: ./configure-nginx-proxy.sh + +set -e + +PROXY_HOST="192.168.1.100" +PROXY_USER="ncantu" +NGINX_SITES_AVAILABLE="/etc/nginx/sites-available" +NGINX_SITES_ENABLED="/etc/nginx/sites-enabled" +CERTBOT_BIN="/usr/bin/certbot" + +echo "=== Configuration Nginx pour certificator.4nkweb.com ===" +echo "" + +# Vérifier que nous sommes sur le proxy ou que nous pouvons y accéder +# Note: Le script peut être exécuté localement ou via SSH +CURRENT_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "") +if [ "$CURRENT_IP" != "192.168.1.100" ] && [ -z "$SSH_CONNECTION" ]; then + echo "ℹ️ Ce script peut être exécuté sur le proxy (192.168.1.100)" + echo " Ou via SSH: ssh ${PROXY_USER}@${PROXY_HOST} 'sudo bash -s' < $0" + echo "" +fi + +# Vérifier les permissions (sudo disponible pour ncantu) +if [ "$EUID" -ne 0 ]; then + if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then + echo "✅ Utilisation de sudo (droits non interactifs)" + # Le script continuera avec sudo pour les commandes nécessitant root + else + echo "⚠️ Ce script nécessite les permissions root pour configurer Nginx" + echo " Utilisez: sudo $0" + exit 1 + fi +fi + +# Fonction pour exécuter les commandes nécessitant root +SUDO_CMD="" +if [ "$EUID" -ne 0 ]; then + SUDO_CMD="sudo" +fi + +echo "✅ Vérification de Nginx..." +# Vérifier Nginx (peut être dans /usr/sbin/nginx) +NGINX_BIN="" +if command -v nginx &> /dev/null; then + NGINX_BIN="nginx" +elif [ -f /usr/sbin/nginx ]; then + NGINX_BIN="/usr/sbin/nginx" +elif [ -f /usr/bin/nginx ]; then + NGINX_BIN="/usr/bin/nginx" +else + echo "❌ Nginx n'est pas installé" + exit 1 +fi +echo " Nginx trouvé: ${NGINX_BIN}" + +echo "✅ Vérification de Certbot..." +# Vérifier Certbot (peut être dans /usr/bin/certbot) +CERTBOT_BIN="" +if command -v certbot &> /dev/null; then + CERTBOT_BIN="certbot" +elif [ -f /usr/bin/certbot ]; then + CERTBOT_BIN="/usr/bin/certbot" +else + echo "⚠️ Certbot n'est pas installé. Installation..." + ${SUDO_CMD} apt-get update + ${SUDO_CMD} apt-get install -y certbot python3-certbot-nginx + CERTBOT_BIN="certbot" +fi +echo " Certbot trouvé: ${CERTBOT_BIN}" + +# Créer les configurations Nginx pour chaque sous-domaine + +# 1. Dashboard (port 3020) +echo "" +echo "📝 Configuration de dashboard.certificator.4nkweb.com..." +${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/dashboard.certificator.4nkweb.com" > /dev/null << 'EOF' +# Dashboard Bitcoin Signet +server { + listen 80; + server_name dashboard.certificator.4nkweb.com; + + # Logs + access_log /var/log/nginx/dashboard.certificator.4nkweb.com.access.log; + error_log /var/log/nginx/dashboard.certificator.4nkweb.com.error.log; + + # Proxy vers le service Node.js (port 3020) + # Note: Les services tournent sur 192.168.1.105 + location / { + proxy_pass http://192.168.1.105:3020; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 300s; + proxy_connect_timeout 75s; + } +} +EOF + +# 2. Faucet (port 3021) +echo "📝 Configuration de faucet.certificator.4nkweb.com..." +${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/faucet.certificator.4nkweb.com" > /dev/null << 'EOF' +# API Faucet Bitcoin Signet +server { + listen 80; + server_name faucet.certificator.4nkweb.com; + + # Logs + access_log /var/log/nginx/faucet.certificator.4nkweb.com.access.log; + error_log /var/log/nginx/faucet.certificator.4nkweb.com.error.log; + + # Proxy vers le service Node.js (port 3021) + # Note: Les services tournent sur 192.168.1.105 + location / { + proxy_pass http://192.168.1.105:3021; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 300s; + proxy_connect_timeout 75s; + } +} +EOF + +# 3. Anchorage (port 3010) +echo "📝 Configuration de anchorage.certificator.4nkweb.com..." +${SUDO_CMD} tee "${NGINX_SITES_AVAILABLE}/anchorage.certificator.4nkweb.com" > /dev/null << 'EOF' +# API Anchorage Bitcoin Signet +server { + listen 80; + server_name anchorage.certificator.4nkweb.com; + + # Logs + access_log /var/log/nginx/anchorage.certificator.4nkweb.com.access.log; + error_log /var/log/nginx/anchorage.certificator.4nkweb.com.error.log; + + # Proxy vers le service Node.js (port 3010) + # Note: Les services tournent sur 192.168.1.105 + location / { + proxy_pass http://192.168.1.105:3010; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 300s; + proxy_connect_timeout 75s; + } +} +EOF + +# Activer les sites +echo "" +echo "🔗 Activation des sites..." +${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/dashboard.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/dashboard.certificator.4nkweb.com" +${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/faucet.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/faucet.certificator.4nkweb.com" +${SUDO_CMD} ln -sf "${NGINX_SITES_AVAILABLE}/anchorage.certificator.4nkweb.com" "${NGINX_SITES_ENABLED}/anchorage.certificator.4nkweb.com" + +# Tester la configuration Nginx +echo "" +echo "🔍 Test de la configuration Nginx..." +if ${SUDO_CMD} ${NGINX_BIN} -t; then + echo "✅ Configuration Nginx valide" +else + echo "❌ Erreur dans la configuration Nginx" + exit 1 +fi + +# Recharger Nginx (configuration HTTP uniquement pour l'instant) +echo "" +echo "🔄 Rechargement de Nginx (configuration HTTP)..." +${SUDO_CMD} systemctl reload nginx || ${SUDO_CMD} service nginx reload + +# Générer les certificats SSL avec Certbot +echo "" +echo "🔐 Génération des certificats SSL avec Certbot..." +echo " Note: Certbot va automatiquement créer les configurations HTTPS" +echo "" + +# Générer les certificats (un par un pour éviter les erreurs) +DOMAINS=( + "dashboard.certificator.4nkweb.com" + "faucet.certificator.4nkweb.com" + "anchorage.certificator.4nkweb.com" +) + +for domain in "${DOMAINS[@]}"; do + echo "📜 Génération du certificat pour ${domain}..." + # Certbot va automatiquement modifier la config pour ajouter HTTPS et redirection + if ${SUDO_CMD} ${CERTBOT_BIN} --nginx -d "${domain}" --non-interactive --agree-tos --email admin@4nkweb.com --redirect; then + echo "✅ Certificat généré et configuration HTTPS créée pour ${domain}" + else + echo "⚠️ Erreur lors de la génération du certificat pour ${domain}" + echo " Vous pouvez le générer manuellement avec:" + echo " sudo ${CERTBOT_BIN} --nginx -d ${domain}" + fi +done + +# Recharger Nginx final +echo "" +echo "🔄 Rechargement final de Nginx..." +${SUDO_CMD} systemctl reload nginx || ${SUDO_CMD} service nginx reload + +echo "" +echo "✅ Configuration terminée !" +echo "" +echo "📋 Résumé:" +echo " - dashboard.certificator.4nkweb.com -> http://192.168.1.105:3020" +echo " - faucet.certificator.4nkweb.com -> http://192.168.1.105:3021" +echo " - anchorage.certificator.4nkweb.com -> http://192.168.1.105:3010" +echo "" +echo "⚠️ Note: Si les services tournent sur une autre machine," +echo " modifiez les IP dans les fichiers de configuration Nginx" +echo "" +echo "🔍 Vérification:" +echo " - Test Nginx: nginx -t" +echo " - Status: systemctl status nginx" +echo " - Logs: tail -f /var/log/nginx/*.error.log" +echo "" diff --git a/docs/COMPATIBILITY_CHECK.md b/docs/COMPATIBILITY_CHECK.md new file mode 100644 index 0000000..da7ade5 --- /dev/null +++ b/docs/COMPATIBILITY_CHECK.md @@ -0,0 +1,80 @@ +# Vérification de Compatibilité - SIGNETCHALLENGE et PRIVKEY + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0 + +## Résumé + +Vérification de la compatibilité entre le SIGNETCHALLENGE et la PRIVKEY configurés avant l'utilisation des descriptor wallets avec Bitcoin Core 30.2. + +## Résultat + +✅ **COMPATIBLE** - Les clés sont compatibles et correctement configurées. + +## Détails de la Vérification + +### 1. Format SIGNETCHALLENGE + +Le SIGNETCHALLENGE a le format : `5121[PUBKEY]51ae` + +- **5121** : Préfixe pour script P2PK +- **[PUBKEY]** : Clé publique compressée (66 caractères hex) +- **51ae** : Suffixe OP_CHECKSIG + +**Exemple** : +``` +SIGNETCHALLENGE: 5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae +Clé publique extraite: 028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6 +``` + +### 2. Correspondance PRIVKEY → PUBKEY + +La PRIVKEY doit pouvoir dériver la clé publique qui correspond à celle dans le SIGNETCHALLENGE. + +**Vérification** : +```bash +PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2) +bitcoin-cli getdescriptorinfo "pk($PRIVKEY)" | jq -r '.descriptor' +# Doit contenir la même clé publique que dans SIGNETCHALLENGE +``` + +**Résultat** : ✅ La PRIVKEY dérive correctement vers la clé publique du SIGNETCHALLENGE. + +### 3. Descriptor Wallet + +Le descriptor `pk()` est importé dans le wallet avec : +- `hasprivatekeys: true` ✅ +- `issolvable: true` ✅ +- `descriptor: pk(028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6)#...` ✅ + +## Conclusion + +Les clés configurées avant l'utilisation des descriptor wallets sont **compatibles** avec Bitcoin Core 30.2 et les descriptor wallets. + +**Pas besoin de redémarrer une nouvelle chaîne.** + +Le problème de mining ("PSBT signing failed") vient d'ailleurs, probablement lié à : +- La façon dont Bitcoin Core 30+ gère les descriptors pour signer les transactions signet +- Un problème avec `walletprocesspsbt` et les PSBT spéciaux du signet +- Une limitation ou un bug dans Bitcoin Core 30.2 pour le mining signet avec descriptor wallets + +## Commandes de Vérification + +```bash +# Vérifier la correspondance +PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2) +SIGNETCHALLENGE=$(grep SIGNETCHALLENGE .env | cut -d'=' -f2) +PUBKEY_FROM_CHALLENGE="${SIGNETCHALLENGE:4:66}" +PUBKEY_FROM_PRIVKEY=$(bitcoin-cli getdescriptorinfo "pk($PRIVKEY)" | jq -r '.descriptor' | grep -o "02[0-9a-f]\{64\}") + +if [ "$PUBKEY_FROM_CHALLENGE" = "$PUBKEY_FROM_PRIVKEY" ]; then + echo "✅ Compatible" +else + echo "❌ Incompatible" +fi +``` + +--- + +**Dernière mise à jour** : 2026-01-23 diff --git a/docs/INSTALLATION_NEW_NODE.md b/docs/INSTALLATION_NEW_NODE.md new file mode 100644 index 0000000..42eb1a7 --- /dev/null +++ b/docs/INSTALLATION_NEW_NODE.md @@ -0,0 +1,595 @@ +# Installation d'un Nouveau Nœud et Configuration du Mining + +**Auteur** : Équipe 4NK +**Date** : 2026-01-09 +**Version** : 1.0 + +Ce guide explique comment installer un nouveau nœud Bitcoin Signet custom et le configurer pour miner sur la chaîne existante. + +## Table des Matières + +1. [Prérequis](#prérequis) +2. [Installation Initiale](#installation-initiale) +3. [Configuration pour Rejoindre une Chaîne Existante](#configuration-pour-rejoindre-une-chaîne-existante) +4. [Configuration du Mining](#configuration-du-mining) +5. [Démarrage du Nœud](#démarrage-du-nœud) +6. [Vérification](#vérification) +7. [Dépannage](#dépannage) + +--- + +## Prérequis + +### Logiciels Requis + +- **Docker** : Version 20.10 ou supérieure +- **Git** : Pour cloner le dépôt +- **jq** : Pour le traitement JSON (installé automatiquement dans le conteneur) +- **Accès réseau** : Ports 38332 (RPC), 38333 (P2P), 28332-28334 (ZMQ) + +### Informations Nécessaires + +Pour rejoindre une chaîne signet existante, vous devez obtenir du nœud administrateur : + +1. **SIGNETCHALLENGE** : Le challenge du signet (identifie la chaîne) +2. **PRIVKEY** : La clé privée du signer (nécessaire pour miner) +3. **Adresse IP du nœud** : Pour se connecter au réseau (optionnel mais recommandé) + +--- + +## Installation Initiale + +### 1. Cloner le Dépôt + +```bash +git clone https://github.com/Easepay/easepay-custom-signet.git bitcoin-signet +cd bitcoin-signet +``` + +### 2. Créer le Fichier de Configuration + +Créez un fichier `.env` à la racine du projet : + +```bash +cp env.example .env +``` + +### 3. Construire l'Image Docker + +```bash +sudo docker build -t bitcoin-signet . +``` + +Cette étape peut prendre plusieurs minutes car elle télécharge Bitcoin Core 30.2. + +--- + +## Configuration pour Rejoindre une Chaîne Existante + +### Option A : Nœud Simple (Sans Mining) + +Si vous voulez juste un nœud qui se synchronise avec la chaîne sans miner : + +```bash +# Éditer .env +nano .env +``` + +Configuration minimale : + +```bash +# Mining Configuration +BLOCKPRODUCTIONDELAY=600 +MINERENABLED=0 +NBITS=1e0377ae +PRIVKEY= +MINETO= +SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae + +# RPC Configuration +RPCUSER=bitcoin +RPCPASSWORD=bitcoin + +UACOMMENT=CustomSignet + +# ZMQ Configuration +ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332 +ZMQPUBRAWTX=tcp://0.0.0.0:28333 +ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334 + +# Additional Configuration +RPCBIND=0.0.0.0:38332 +RPCALLOWIP=0.0.0.0/0 +WHITELIST=0.0.0.0/0 +ADDNODE=:38333 +EXTERNAL_IP= +``` + +**Points importants** : +- `MINERENABLED=0` : Désactive le mining +- `SIGNETCHALLENGE` : Doit correspondre exactement à celui de la chaîne +- `ADDNODE` : Adresse IP et port du nœud administrateur (ex: `192.168.1.103:38333`) + +### Option B : Nœud avec Mining + +Si vous voulez miner sur la chaîne : + +```bash +# Éditer .env +nano .env +``` + +Configuration complète : + +```bash +# Mining Configuration +# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported) +BLOCKPRODUCTIONDELAY=600 +MINERENABLED=1 +NBITS=1e0377ae +PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS +MINETO= +SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae + +# RPC Configuration +RPCUSER=bitcoin +RPCPASSWORD=bitcoin + +UACOMMENT=CustomSignet + +# ZMQ Configuration +ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332 +ZMQPUBRAWTX=tcp://0.0.0.0:28333 +ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334 + +# Additional Configuration +RPCBIND=0.0.0.0:38332 +RPCALLOWIP=0.0.0.0/0 +WHITELIST=0.0.0.0/0 +ADDNODE=:38333 +EXTERNAL_IP= +``` + +**Points importants** : +- `MINERENABLED=1` : Active le mining +- `PRIVKEY` : Doit être la même que celle utilisée par le nœud administrateur +- `SIGNETCHALLENGE` : Doit correspondre exactement à celui de la chaîne +- `BLOCKPRODUCTIONDELAY` : Délai entre les blocs (600 = 10 minutes) +- `ADDNODE` : Adresse IP et port du nœud administrateur + +--- + +## Configuration du Mining + +### Paramètres de Mining + +Les paramètres suivants contrôlent le comportement du mining : + +#### BLOCKPRODUCTIONDELAY + +Délai en secondes entre la génération de chaque bloc. + +- **600** : 10 minutes (simule le mainnet) +- **60** : 1 minute (pour tests rapides) +- **0** : Pas de délai (mining continu) + +**Exemple** : +```bash +BLOCKPRODUCTIONDELAY=600 # 10 minutes +``` + +#### NBITS + +Difficulté minimale pour le mining (format hexadécimal). + +**Recommandation** : Ne pas modifier sauf nécessité spécifique. + +**Valeur par défaut** : `1e0377ae` + +#### MINETO + +Adresse Bitcoin où envoyer la récompense de minage. + +- **Vide** : Une nouvelle adresse est générée pour chaque bloc +- **Adresse spécifique** : Tous les blocs minent vers cette adresse + +**Exemple** : +```bash +MINETO=tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc +``` + +### Modification Dynamique du Délai + +Vous pouvez modifier le délai de production de blocs sans redémarrer le conteneur : + +```bash +# Modifier le délai à 60 secondes +sudo docker exec bitcoin-signet-instance bash -c "echo 60 > /root/.bitcoin/BLOCKPRODUCTIONDELAY.txt" + +# Le script mine.sh détectera automatiquement ce changement +``` + +--- + +## Démarrage du Nœud + +### Premier Démarrage + +```bash +sudo docker run --env-file .env -d \ + --name bitcoin-signet-instance \ + -p 38332:38332 \ + -p 38333:38333 \ + -p 28332:28332 \ + -p 28333:28333 \ + -p 28334:28334 \ + bitcoin-signet +``` + +### Vérifier les Logs + +```bash +# Voir les logs en temps réel +sudo docker logs -f bitcoin-signet-instance + +# Voir les derniers logs +sudo docker logs bitcoin-signet-instance --tail 50 +``` + +### Arrêt du Nœud + +```bash +# Arrêter le conteneur +sudo docker stop bitcoin-signet-instance + +# Arrêter et supprimer le conteneur +sudo docker stop bitcoin-signet-instance && sudo docker rm bitcoin-signet-instance +``` + +### Redémarrage + +```bash +# Redémarrer le conteneur +sudo docker restart bitcoin-signet-instance + +# Ou recréer le conteneur +sudo docker stop bitcoin-signet-instance +sudo docker rm bitcoin-signet-instance +sudo docker run --env-file .env -d \ + --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet +``` + +--- + +## Vérification + +### 1. Vérifier que le Conteneur Fonctionne + +```bash +# Vérifier le statut +sudo docker ps | grep bitcoin-signet + +# Vérifier les logs +sudo docker logs bitcoin-signet-instance --tail 20 +``` + +### 2. Vérifier la Connexion au Réseau + +```bash +# Obtenir des informations sur le réseau +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo + +# Vérifier les connexions +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep -E "(connections|networkactive)" +``` + +**Résultat attendu** : +- `networkactive: true` +- `connections: 1` ou plus (si connecté au nœud administrateur) + +### 3. Vérifier la Synchronisation + +```bash +# Obtenir des informations sur la blockchain +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo + +# Vérifier le dernier bloc +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash +``` + +**Résultat attendu** : +- `chain: "signet"` +- `verificationprogress: 1` (une fois synchronisé) +- `blocks: X` (nombre de blocs synchronisés) + +### 4. Vérifier le Wallet (Si Mining Activé) + +```bash +# Obtenir des informations sur le wallet +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo + +# Vérifier que la clé privée est importée +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listdescriptors | grep -i "028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6" +``` + +**Résultat attendu** : +- `walletname: "custom_signet"` +- `format: "sqlite"` (descriptor wallet) +- Descriptor avec la clé publique du signet présent + +### 5. Vérifier le Mining (Si Activé) + +```bash +# Vérifier que le script de mining est actif +sudo docker exec bitcoin-signet-instance ps aux | grep -E "(mine|bitcoind)" | grep -v grep + +# Voir les logs de mining +sudo docker logs bitcoin-signet-instance | grep -E "(Mine|Delay|block|Importing)" +``` + +**Résultat attendu** : +- Processus `mine.sh` en cours d'exécution +- Messages "Delay before next block" dans les logs +- Message "Private key imported successfully into descriptor wallet" + +--- + +## Dépannage + +### Le Nœud ne se Connecte pas au Réseau + +**Symptômes** : +- `connections: 0` +- `networkactive: true` mais pas de connexions + +**Solutions** : + +1. **Vérifier ADDNODE dans .env** : + ```bash + grep ADDNODE .env + ``` + Doit contenir l'adresse IP et le port du nœud administrateur : `ADDNODE=192.168.1.103:38333` + +2. **Vérifier la connectivité réseau** : + ```bash + # Tester la connexion au nœud administrateur + nc -zv 38333 + ``` + +3. **Vérifier le firewall** : + ```bash + # Vérifier que le port 38333 est ouvert + sudo netstat -tlnp | grep 38333 + ``` + +4. **Vérifier SIGNETCHALLENGE** : + ```bash + # Le SIGNETCHALLENGE doit être exactement le même que celui du nœud administrateur + grep SIGNETCHALLENGE .env + ``` + +### Le Mining ne Fonctionne pas + +**Symptômes** : +- Pas de processus `mine.sh` +- Erreurs dans les logs concernant la clé privée + +**Solutions** : + +1. **Vérifier MINERENABLED** : + ```bash + grep MINERENABLED .env + ``` + Doit être `MINERENABLED=1` + +2. **Vérifier PRIVKEY** : + ```bash + grep PRIVKEY .env + ``` + Doit contenir la clé privée exacte du signet + +3. **Vérifier l'import de la clé** : + ```bash + sudo docker logs bitcoin-signet-instance | grep -i "importing\|private key" + ``` + Doit afficher "Private key imported successfully into descriptor wallet" + +4. **Vérifier manuellement l'import** : + ```bash + sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listdescriptors | grep -i "wpkh" + ``` + Doit contenir le descriptor avec la clé publique du signet + +### Erreur "Cannot obtain a lock" + +**Symptômes** : +- Erreur dans les logs : "Cannot obtain a lock on data directory" + +**Solutions** : + +1. **Arrêter tous les processus bitcoind** : + ```bash + sudo docker exec bitcoin-signet-instance pkill bitcoind + sudo docker restart bitcoin-signet-instance + ``` + +2. **Vérifier qu'aucun autre conteneur n'utilise les mêmes ports** : + ```bash + sudo docker ps | grep -E "(38332|38333)" + ``` + +### Le Wallet n'Existe pas + +**Symptômes** : +- Erreur "No wallet is loaded" + +**Solutions** : + +1. **Créer le wallet manuellement** : + ```bash + sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \ + -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=true + ``` + +2. **Charger le wallet** : + ```bash + sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin loadwallet custom_signet + ``` + +3. **Importer la clé privée manuellement** : + ```bash + PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2) + DESCRIPTOR_INFO=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "wpkh($PRIVKEY)") + CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum') + sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \ + importdescriptors "[{\"desc\":\"wpkh($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]" + ``` + +### Synchronisation Lente + +**Symptômes** : +- `verificationprogress` reste faible +- `blocks` n'augmente pas + +**Solutions** : + +1. **Vérifier les connexions** : + ```bash + sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep connections + ``` + Doit être supérieur à 0 + +2. **Ajouter plusieurs nœuds dans ADDNODE** : + ```bash + ADDNODE=192.168.1.103:38333,192.168.1.104:38333 + ``` + +3. **Vérifier la bande passante réseau** + +--- + +## Commandes Utiles + +### Accès au Conteneur + +```bash +# Accéder au shell du conteneur +sudo docker exec -it bitcoin-signet-instance bash + +# Exécuter bitcoin-cli +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin +``` + +### Commandes RPC Courantes + +```bash +# État de la blockchain +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo + +# État du réseau +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo + +# État du wallet +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo + +# Obtenir une nouvelle adresse +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnewaddress + +# Obtenir le solde +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbalance + +# Liste des transactions +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listtransactions +``` + +### Monitoring + +```bash +# Voir les logs en temps réel +sudo docker logs -f bitcoin-signet-instance + +# Voir les logs de debug Bitcoin +sudo docker exec bitcoin-signet-instance tail -f /root/.bitcoin/signet/debug.log + +# Vérifier les processus +sudo docker exec bitcoin-signet-instance ps aux | grep -E "(bitcoind|mine)" +``` + +--- + +## Partage d'Informations avec d'Autres Nœuds + +Pour permettre à d'autres nœuds de rejoindre votre chaîne, partagez : + +1. **SIGNETCHALLENGE** : Identifie la chaîne + ```bash + sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/SIGNETCHALLENGE.txt + ``` + +2. **PRIVKEY** : Nécessaire pour miner (partager uniquement avec les nœuds de confiance) + ```bash + sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/PRIVKEY.txt + ``` + +3. **Adresse IP et Port** : Pour la connexion P2P + - IP : Votre adresse IP publique ou locale + - Port : `38333` (port P2P) + +4. **RPC Credentials** (optionnel) : Si vous voulez permettre l'accès RPC + - RPCUSER : `bitcoin` (ou personnalisé) + - RPCPASSWORD : `bitcoin` (ou personnalisé) + - Port RPC : `38332` + +**Sécurité** : Ne partagez jamais la PRIVKEY publiquement. Elle permet de miner et de signer des blocs sur votre chaîne. + +--- + +## Exemple de Configuration Complète + +Voici un exemple complet de fichier `.env` pour un nœud avec mining : + +```bash +# Mining Configuration +# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported) +BLOCKPRODUCTIONDELAY=600 +MINERENABLED=1 +NBITS=1e0377ae +PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS +MINETO= +SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae + +# RPC Configuration +RPCUSER=bitcoin +RPCPASSWORD=bitcoin + +UACOMMENT=CustomSignet + +# ZMQ Configuration +ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332 +ZMQPUBRAWTX=tcp://0.0.0.0:28333 +ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334 + +# Additional Configuration +RPCBIND=0.0.0.0:38332 +RPCALLOWIP=0.0.0.0/0 +WHITELIST=0.0.0.0/0 +ADDNODE=192.168.1.103:38333 +EXTERNAL_IP= +``` + +--- + +## Prochaines Étapes + +Une fois le nœud installé et fonctionnel : + +1. **Lire la documentation de maintenance** : `docs/MAINTENANCE.md` +2. **Configurer la sauvegarde** : Voir section "Sauvegarde et Restauration" +3. **Configurer le monitoring** : Surveiller les logs et l'état du nœud +4. **Partager les informations** : Si vous voulez que d'autres nœuds rejoignent + +--- + +**Dernière mise à jour** : 2026-01-09 diff --git a/docs/INTERFACES.md b/docs/INTERFACES.md new file mode 100644 index 0000000..c5efb2b --- /dev/null +++ b/docs/INTERFACES.md @@ -0,0 +1,461 @@ +# Interfaces et IHM Disponibles + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0 + +## Vue d'Ensemble + +Ce document liste toutes les interfaces et IHM (Interfaces Homme-Machine) disponibles dans le projet Bitcoin Signet Custom. + +--- + +## 1. API REST d'Ancrage + +### Description +API REST HTTP/JSON pour ancrer des documents sur la blockchain Bitcoin Signet. + +### Accès +- **URL** : `https://certificator.4nkweb.com` (via nginx proxy) +- **Port local** : `3010` +- **Protocole** : HTTPS (production) / HTTP (développement) +- **Format** : JSON + +### Endpoints + +#### GET `/` +Informations sur l'API + +**Réponse** : +```json +{ + "service": "bitcoin-signet-anchor-api", + "version": "1.0.0", + "endpoints": { + "health": "/health", + "anchor": "/api/anchor/document", + "verify": "/api/anchor/verify" + } +} +``` + +#### GET `/health` +Vérifie l'état de l'API et de la connexion Bitcoin + +**Authentification** : Non requise + +**Réponse** : +```json +{ + "ok": true, + "service": "anchor-api", + "bitcoin": { + "connected": true, + "blocks": 152321 + }, + "timestamp": "2026-01-23T16:35:27.821Z" +} +``` + +#### POST `/api/anchor/document` +Ancre un document sur Bitcoin Signet + +**Authentification** : Requise (header `x-api-key`) + +**Body** : +```json +{ + "documentUid": "doc-123456", + "hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890" +} +``` + +**Réponse** : +```json +{ + "txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85", + "status": "confirmed", + "confirmations": 0, + "block_height": 152321 +} +``` + +#### POST `/api/anchor/verify` +Vérifie si un hash est ancré sur Bitcoin Signet + +**Authentification** : Requise (header `x-api-key`) + +**Body** : +```json +{ + "hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890", + "txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85" +} +``` + +**Réponse** : +```json +{ + "verified": true, + "anchor_info": { + "transaction_id": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85", + "block_height": 152321, + "confirmations": 0 + } +} +``` + +### Documentation +- **Fichier** : `api-anchorage/README.md` +- **Client de test** : `api-anchorage/src/test-client.js` + +### Exemple d'utilisation + +```bash +# Health check +curl https://certificator.4nkweb.com/health + +# Ancrer un document +curl -X POST https://certificator.4nkweb.com/api/anchor/document \ + -H "Content-Type: application/json" \ + -H "x-api-key: your-api-key" \ + -d '{ + "documentUid": "doc-123", + "hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890" + }' +``` + +--- + +## 2. Interface RPC Bitcoin Core + +### Description +Interface JSON-RPC pour contrôler et interagir avec le nœud Bitcoin Signet. + +### Accès +- **Port** : `38332` +- **Protocole** : HTTP +- **Format** : JSON-RPC 2.0 +- **Authentification** : Basic Auth (RPCUSER/RPCPASSWORD) + +### Commandes Principales + +#### Informations sur la Blockchain +```bash +# État de la blockchain +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo + +# Dernier bloc +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash + +# Informations sur un bloc +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblock +``` + +#### Informations sur le Réseau +```bash +# État du réseau +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo + +# Connexions actives +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getconnectioncount +``` + +#### Gestion du Wallet +```bash +# Informations sur le wallet +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo + +# Solde +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbalance + +# Nouvelle adresse +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnewaddress + +# Transactions +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listtransactions +``` + +#### Accès depuis l'Extérieur +```bash +bitcoin-cli -rpcconnect=192.168.1.103 -rpcport=38332 \ + -rpcuser=bitcoin -rpcpassword=bitcoin \ + getblockchaininfo +``` + +### Documentation +- **Référence complète** : [Bitcoin Core RPC API](https://developer.bitcoin.org/reference/rpc/) +- **Documentation locale** : `docs/MAINTENANCE.md` (section "Accès RPC et API") + +--- + +## 3. Interface Ligne de Commande (CLI) + +### Description +Scripts shell et commandes pour interagir avec le système. + +### Scripts Disponibles + +#### `bitcoin-cli` +Interface en ligne de commande pour Bitcoin Core + +**Utilisation** : +```bash +# Depuis le conteneur +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin + +# Exemples +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo +``` + +#### `logtail.sh` +Visualisation des logs en temps réel + +**Utilisation** : +```bash +./logtail.sh +``` + +#### `update-signet.sh` +Script de mise à jour du Bitcoin Signet + +**Utilisation** : +```bash +./update-signet.sh [version] +./update-signet.sh --help +``` + +#### `mine.sh` +Script de mining (utilisé automatiquement par le conteneur) + +**Utilisation** : +```bash +# Modifier le délai de production +sudo docker exec bitcoin-signet-instance bash -c "echo 60 > /root/.bitcoin/BLOCKPRODUCTIONDELAY.txt" +``` + +### Documentation +- **Scripts** : Voir les fichiers `.sh` à la racine du projet +- **Documentation** : `docs/MAINTENANCE.md` + +--- + +## 4. Interface ZMQ (ZeroMQ) + +### Description +Interface de messagerie pour recevoir des événements en temps réel du nœud Bitcoin. + +### Accès +- **Ports** : `28332`, `28333`, `28334` +- **Protocole** : TCP +- **Format** : Messages binaires + +### Topics Disponibles + +#### `ZMQPUBRAWBLOCK` (port 28332) +Publication des blocs bruts + +**Configuration** : +```bash +ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332 +``` + +#### `ZMQPUBRAWTX` (port 28333) +Publication des transactions brutes + +**Configuration** : +```bash +ZMQPUBRAWTX=tcp://0.0.0.0:28333 +``` + +#### `ZMQPUBHASHBLOCK` (port 28334) +Publication des hash de blocs + +**Configuration** : +```bash +ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334 +``` + +### Utilisation + +**Exemple Python** : +```python +import zmq + +context = zmq.Context() +socket = context.socket(zmq.SUB) +socket.connect("tcp://192.168.1.103:28332") +socket.setsockopt(zmq.SUBSCRIBE, b"rawblock") + +while True: + topic, body = socket.recv_multipart() + print(f"Received block: {body.hex()}") +``` + +### Documentation +- **Référence** : [Bitcoin Core ZMQ](https://github.com/bitcoin/bitcoin/blob/master/doc/zmq.md) + +--- + +## 5. Interface Docker + +### Description +Commandes Docker pour gérer le conteneur Bitcoin Signet. + +### Commandes Principales + +#### Gestion du Conteneur +```bash +# Démarrer +sudo docker run --env-file .env -d \ + --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 \ + -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet + +# Arrêter +sudo docker stop bitcoin-signet-instance + +# Redémarrer +sudo docker restart bitcoin-signet-instance + +# Supprimer +sudo docker rm bitcoin-signet-instance +``` + +#### Logs +```bash +# Voir les logs +sudo docker logs bitcoin-signet-instance + +# Suivre les logs en temps réel +sudo docker logs -f bitcoin-signet-instance + +# Dernières lignes +sudo docker logs bitcoin-signet-instance --tail 50 +``` + +#### Accès au Shell +```bash +# Accéder au shell du conteneur +sudo docker exec -it bitcoin-signet-instance bash + +# Exécuter une commande +sudo docker exec bitcoin-signet-instance +``` + +### Documentation +- **Documentation** : `docs/MAINTENANCE.md` (section "Gestion du Conteneur") + +--- + +## 6. Interface de Configuration (.env) + +### Description +Fichier de configuration pour personnaliser le comportement du système. + +### Accès +- **Fichier** : `.env` (à la racine du projet) +- **Format** : Variables d'environnement (KEY=VALUE) + +### Variables Principales + +#### Mining +```bash +BLOCKPRODUCTIONDELAY=600 +MINERENABLED=1 +NBITS=1e0377ae +PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS +MINETO= +SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae +``` + +#### RPC +```bash +RPCUSER=bitcoin +RPCPASSWORD=bitcoin +RPCBIND=0.0.0.0:38332 +RPCALLOWIP=0.0.0.0/0 +``` + +#### ZMQ +```bash +ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332 +ZMQPUBRAWTX=tcp://0.0.0.0:28333 +ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334 +``` + +### Documentation +- **Exemple** : `env.example` +- **Documentation** : `docs/MAINTENANCE.md` (section "Configuration") + +--- + +## 7. Interface de Logs + +### Description +Système de logs pour surveiller l'activité du nœud. + +### Accès + +#### Logs Docker +```bash +# Logs du conteneur +sudo docker logs bitcoin-signet-instance + +# Logs en temps réel +sudo docker logs -f bitcoin-signet-instance +``` + +#### Logs Bitcoin Core +```bash +# Logs de debug Bitcoin +sudo docker exec bitcoin-signet-instance tail -f /root/.bitcoin/signet/debug.log +``` + +#### Logs API +```bash +# Logs de l'API d'ancrage (si déployée avec PM2) +pm2 logs anchor-api +``` + +### Documentation +- **Script** : `logtail.sh` +- **Documentation** : `docs/MAINTENANCE.md` (section "Monitoring") + +--- + +## Résumé des Ports + +| Port | Protocole | Interface | Description | +|------|-----------|-----------|-------------| +| 3010 | HTTP/HTTPS | API REST | API d'ancrage (via nginx) | +| 38332 | HTTP | RPC Bitcoin | Interface JSON-RPC | +| 38333 | TCP | P2P Bitcoin | Réseau peer-to-peer | +| 28332 | TCP | ZMQ | Publication blocs bruts | +| 28333 | TCP | ZMQ | Publication transactions brutes | +| 28334 | TCP | ZMQ | Publication hash de blocs | + +--- + +## Recommandations d'Utilisation + +### Pour les Développeurs +- **API REST** : Pour intégrer l'ancrage dans des applications +- **RPC Bitcoin** : Pour des opérations avancées sur la blockchain +- **CLI** : Pour la maintenance et le dépannage + +### Pour les Administrateurs +- **Docker CLI** : Pour gérer le conteneur +- **RPC Bitcoin** : Pour surveiller l'état du nœud +- **Logs** : Pour le dépannage et le monitoring + +### Pour les Intégrations +- **API REST** : Interface principale pour les applications externes +- **ZMQ** : Pour recevoir des événements en temps réel +- **RPC Bitcoin** : Pour des opérations spécifiques non couvertes par l'API + +--- + +**Dernière mise à jour** : 2026-01-23 diff --git a/docs/MAINTENANCE.md b/docs/MAINTENANCE.md new file mode 100644 index 0000000..2e7fe0c --- /dev/null +++ b/docs/MAINTENANCE.md @@ -0,0 +1,814 @@ +# Documentation de Maintenance - Bitcoin Signet Custom + +**Auteur** : Équipe 4NK +**Date** : 2026-01-09 +**Version** : 1.0 + +## Table des Matières + +1. [Vue d'Ensemble](#vue-densemble) +2. [Architecture](#architecture) +3. [Configuration](#configuration) +4. [Commandes de Maintenance](#commandes-de-maintenance) +5. [Gestion du Conteneur](#gestion-du-conteneur) +6. [Gestion des Clés et du Signet](#gestion-des-clés-et-du-signet) +7. [Mining](#mining) +8. [Accès RPC et API](#accès-rpc-et-api) +9. [Mise à Jour](#mise-à-jour) +10. [Dépannage](#dépannage) +11. [Modifications Apportées](#modifications-apportées) +12. [Sauvegarde et Restauration](#sauvegarde-et-restauration) + +--- + +## Vue d'Ensemble + +Ce projet installe et configure un Bitcoin Signet custom basé sur le dépôt [Easepay/easepay-custom-signet](https://github.com/Easepay/easepay-custom-signet.git). + +Un Signet est un réseau de test Bitcoin qui permet de tester des applications Bitcoin sans risquer de fonds réels et sans l'imprévisibilité du testnet public. + +### Caractéristiques + +- **Version Bitcoin Core** : 30.2 +- **Type de Wallet** : Descriptor wallets (legacy wallets non supportés depuis Bitcoin Core 30+) +- **Réseau** : Signet custom +- **Mining** : Activé avec délai configurable +- **Containerisation** : Docker +- **Base OS** : Debian Bookworm + +--- + +## Architecture + +### Structure des Fichiers + +``` +bitcoin/ +├── Dockerfile # Image Docker avec Bitcoin Core +├── docker-entrypoint.sh # Point d'entrée du conteneur +├── install.sh # Script d'installation initiale +├── run.sh # Script de démarrage du nœud +├── setup-signet.sh # Configuration du signet +├── gen-signet-keys.sh # Génération des clés du signet +├── gen-bitcoind-conf.sh # Génération de bitcoin.conf +├── mine.sh # Script de mining +├── mine-genesis.sh # Mining du bloc genesis +├── logtail.sh # Visualisation des logs +├── rpcauth.py # Génération d'authentification RPC +├── miner # Script Python de mining +├── miner_imports/ # Imports pour le miner +├── .env # Variables d'environnement +├── env.example # Exemple de configuration +└── docs/ # Documentation +``` + +### Ports Exposés + +| Port | Protocole | Service | Description | +|------|-----------|---------|-------------| +| 38332 | TCP | RPC | Interface JSON-RPC pour contrôler le nœud | +| 38333 | TCP/UDP | P2P | Réseau peer-to-peer Bitcoin Signet | +| 28332 | TCP | ZMQ | Publication des blocs bruts | +| 28333 | TCP | ZMQ | Publication des transactions brutes | +| 28334 | TCP | ZMQ | Publication des hash de blocs | + +### Répertoires Importants dans le Conteneur + +- `/root/.bitcoin/` : Répertoire de données Bitcoin + - `signet/` : Données de la chaîne signet + - `bitcoin.conf` : Configuration du nœud + - `PRIVKEY.txt` : Clé privée du signer + - `SIGNETCHALLENGE.txt` : Challenge du signet + - `MAGIC.txt` : Magic number du réseau + - `install_done` : Marqueur d'installation complète + +--- + +## Configuration + +### Fichier `.env` + +Le fichier `.env` contient toutes les variables de configuration nécessaires au fonctionnement du signet. + +#### Variables de Mining + +```bash +# Délai entre la génération de chaque bloc (en secondes) +BLOCKPRODUCTIONDELAY=600 + +# Activer le mining (1 = activé, 0 = désactivé) +MINERENABLED=1 + +# Difficulté minimale pour le mining (format hexadécimal) +NBITS=1e0377ae + +# Clé privée du signer (générée automatiquement si vide) +PRIVKEY=cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS + +# Adresse de minage (générée automatiquement si vide) +MINETO= + +# Challenge du signet (généré automatiquement si vide) +SIGNETCHALLENGE=5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae +``` + +#### Variables RPC + +```bash +# Utilisateur RPC +RPCUSER=bitcoin + +# Mot de passe RPC +RPCPASSWORD=bitcoin +``` + +#### Variables ZMQ + +```bash +# Publication des blocs bruts +ZMQPUBRAWBLOCK=tcp://0.0.0.0:28332 + +# Publication des transactions brutes +ZMQPUBRAWTX=tcp://0.0.0.0:28333 + +# Publication des hash de blocs +ZMQPUBHASHBLOCK=tcp://0.0.0.0:28334 +``` + +#### Variables Réseau + +```bash +# Commentaire User-Agent +UACOMMENT=CustomSignet + +# Binding RPC +RPCBIND=0.0.0.0:38332 + +# IPs autorisées pour RPC +RPCALLOWIP=0.0.0.0/0 + +# IPs whitelistées +WHITELIST=0.0.0.0/0 + +# Nœuds à ajouter (séparés par des virgules) +ADDNODE= + +# IP externe (pour l'annonce publique) +EXTERNAL_IP= +``` + +### Génération des Clés + +Les clés sont générées automatiquement lors de la première installation si `PRIVKEY` et `SIGNETCHALLENGE` sont vides dans le `.env`. + +**Important** : Une fois générées, ces clés doivent être conservées et partagées avec les autres nœuds qui souhaitent rejoindre le même signet. + +--- + +## Commandes de Maintenance + +### Vérification de l'État + +```bash +# Vérifier que le conteneur est en cours d'exécution +sudo docker ps | grep bitcoin-signet + +# Vérifier l'état de la blockchain +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo + +# Vérifier l'état du réseau +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo + +# Vérifier l'état du wallet +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo + +# Vérifier les processus en cours +sudo docker exec bitcoin-signet-instance ps aux | grep -E "(bitcoind|mine)" +``` + +### Logs + +```bash +# Voir les logs en temps réel +sudo docker logs -f bitcoin-signet-instance + +# Voir les derniers logs +sudo docker logs bitcoin-signet-instance --tail 50 + +# Voir les logs de debug Bitcoin +sudo docker exec bitcoin-signet-instance tail -f /root/.bitcoin/signet/debug.log +``` + +### Accès au Conteneur + +```bash +# Accéder au shell du conteneur +sudo docker exec -it bitcoin-signet-instance bash + +# Exécuter bitcoin-cli depuis l'hôte +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin +``` + +--- + +## Gestion du Conteneur + +### Démarrage + +```bash +cd /home/ncantu/Bureau/code/bitcoin +sudo docker run --env-file .env -d \ + --name bitcoin-signet-instance \ + -p 38332:38332 \ + -p 38333:38333 \ + -p 28332:28332 \ + -p 28333:28333 \ + -p 28334:28334 \ + bitcoin-signet +``` + +### Arrêt + +```bash +# Arrêter le conteneur +sudo docker stop bitcoin-signet-instance + +# Arrêter et supprimer le conteneur +sudo docker stop bitcoin-signet-instance && sudo docker rm bitcoin-signet-instance +``` + +### Redémarrage + +```bash +# Redémarrer le conteneur +sudo docker restart bitcoin-signet-instance + +# Redémarrer après modification du .env +sudo docker stop bitcoin-signet-instance +sudo docker rm bitcoin-signet-instance +sudo docker run --env-file .env -d --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet +``` + +### Reconstruction de l'Image + +```bash +cd /home/ncantu/Bureau/code/bitcoin +sudo docker build -t bitcoin-signet . +``` + +--- + +## Gestion des Clés et du Signet + +### Récupération des Clés Générées + +```bash +# Récupérer la clé privée +sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/PRIVKEY.txt + +# Récupérer le challenge du signet +sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/SIGNETCHALLENGE.txt + +# Récupérer le magic number +sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/MAGIC.txt +``` + +### Mise à Jour du `.env` avec les Clés + +```bash +# Mettre à jour PRIVKEY dans .env +PRIVKEY=$(sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/PRIVKEY.txt) +sed -i "s/^PRIVKEY=.*/PRIVKEY=$PRIVKEY/" .env + +# Mettre à jour SIGNETCHALLENGE dans .env +SIGNETCHALLENGE=$(sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/SIGNETCHALLENGE.txt) +sed -i "s/^SIGNETCHALLENGE=.*/SIGNETCHALLENGE=$SIGNETCHALLENGE/" .env +``` + +### Partage du Signet avec d'Autres Nœuds + +Pour qu'un autre nœud rejoigne le même signet, il doit avoir dans son `.env` : + +1. Le même `SIGNETCHALLENGE` +2. Le même `PRIVKEY` (si mining activé) +3. L'adresse IP du nœud dans `ADDNODE` (ex: `ADDNODE=192.168.1.100:38333`) + +--- + +## Mining + +### Configuration du Mining + +Le mining est contrôlé par les variables suivantes dans `.env` : + +- `MINERENABLED=1` : Active le mining +- `BLOCKPRODUCTIONDELAY=600` : Délai en secondes entre chaque bloc (600 = 10 minutes) +- `NBITS=1e0377ae` : Difficulté minimale (ne pas modifier sauf nécessité) +- `MINETO=` : Adresse de minage (vide = nouvelle adresse par bloc) +- `PRIVKEY` : Clé privée du signer (importée automatiquement dans le descriptor wallet) + +**Note Bitcoin Core 30+** : La PRIVKEY est automatiquement importée dans le descriptor wallet au démarrage via `importdescriptors`. Cette importation est nécessaire car le miner utilise `walletprocesspsbt` pour signer les blocs, ce qui nécessite que la clé soit dans le wallet. + +### Modification Dynamique du Délai + +Pour modifier le délai de production de blocs sans redémarrer : + +```bash +# Modifier le délai à 60 secondes (1 minute) +sudo docker exec bitcoin-signet-instance bash -c "echo 60 > /root/.bitcoin/BLOCKPRODUCTIONDELAY.txt" +``` + +Le script `mine.sh` lit automatiquement ce fichier s'il existe. + +### Vérification du Mining + +```bash +# Vérifier que le script de mining est actif +sudo docker exec bitcoin-signet-instance ps aux | grep mine.sh + +# Voir les logs de mining +sudo docker logs bitcoin-signet-instance | grep -E "(Mine|Delay|block)" + +# Vérifier le dernier bloc miné +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash +``` + +### Arrêt du Mining + +Pour arrêter le mining, modifier `.env` : + +```bash +MINERENABLED=0 +``` + +Puis redémarrer le conteneur. + +--- + +## Accès RPC et API + +### Commandes RPC Courantes + +```bash +# Obtenir des informations sur la blockchain +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo + +# Obtenir des informations sur le réseau +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo + +# Obtenir des informations sur le wallet +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo + +# Obtenir le solde +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbalance + +# Obtenir une nouvelle adresse +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnewaddress + +# Obtenir la liste des transactions +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listtransactions + +# Obtenir le dernier bloc +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getbestblockhash + +# Obtenir un bloc spécifique +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblock +``` + +### Accès RPC depuis l'Extérieur + +Pour accéder au RPC depuis un autre hôte, utiliser : + +```bash +bitcoin-cli -rpcconnect= -rpcport=38332 \ + -rpcuser=bitcoin -rpcpassword=bitcoin \ + getblockchaininfo +``` + +**Sécurité** : En production, restreindre `RPCALLOWIP` dans `.env` et utiliser une authentification plus forte. + +### ZMQ + +Les notifications ZMQ sont disponibles sur les ports 28332, 28333, 28334. + +Exemple avec Python : + +```python +import zmq + +context = zmq.Context() +socket = context.socket(zmq.SUB) +socket.connect("tcp://localhost:28332") +socket.setsockopt(zmq.SUBSCRIBE, b"rawblock") + +while True: + message = socket.recv() + print(f"New block: {message.hex()}") +``` + +--- + +## Mise à Jour + +### Script de Mise à Jour Automatique + +Un script de mise à jour automatique est disponible : `update-signet.sh` + +#### Utilisation + +```bash +# Mise à jour vers la dernière version disponible +./update-signet.sh + +# Mise à jour vers une version spécifique +./update-signet.sh 30.2 + +# Aide +./update-signet.sh --help +``` + +#### Fonctionnalités du Script + +Le script `update-signet.sh` effectue automatiquement : + +1. **Vérification des prérequis** : Docker, fichier `.env` +2. **Détection de la version actuelle** : Depuis `.bitcoin-version` ou le Dockerfile +3. **Récupération de la dernière version** : Depuis les sources officielles Bitcoin +4. **Sauvegarde automatique** : Données du conteneur et fichier `.env` +5. **Mise à jour du Dockerfile** : Modification de la version Bitcoin Core +6. **Reconstruction de l'image** : Build de la nouvelle image Docker +7. **Redémarrage du conteneur** : Arrêt propre et redémarrage avec la nouvelle version +8. **Vérification post-mise à jour** : Contrôle de l'état du nœud + +#### Procédure Manuelle de Mise à Jour + +Si vous préférez effectuer la mise à jour manuellement : + +##### 1. Vérifier la Version Actuelle + +```bash +# Version dans le Dockerfile +grep BITCOIN_VERSION Dockerfile + +# Version enregistrée +cat .bitcoin-version 2>/dev/null || echo "Non enregistrée" + +# Version en cours d'exécution +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep subversion +``` + +##### 2. Récupérer la Dernière Version + +Consultez les sources officielles : +- [Bitcoin Core Releases](https://bitcoincore.org/en/download/) +- [GitHub Releases](https://github.com/bitcoin/bitcoin/releases) + +##### 3. Sauvegarder les Données + +```bash +# Créer un répertoire de sauvegarde +mkdir -p backups + +# Sauvegarder les données du conteneur +sudo docker exec bitcoin-signet-instance tar czf /tmp/bitcoin-backup.tar.gz /root/.bitcoin/ +sudo docker cp bitcoin-signet-instance:/tmp/bitcoin-backup.tar.gz backups/signet-backup-$(date +%Y%m%d-%H%M%S).tar.gz + +# Sauvegarder le .env +cp .env backups/.env.backup-$(date +%Y%m%d-%H%M%S) +``` + +##### 4. Mettre à Jour le Dockerfile + +```bash +# Modifier la version dans le Dockerfile +sed -i 's/ARG BITCOIN_VERSION=\${BITCOIN_VERSION:-[0-9]\+\.[0-9]\+}/ARG BITCOIN_VERSION=${BITCOIN_VERSION:-30.2}/' Dockerfile + +# Vérifier la modification +grep BITCOIN_VERSION Dockerfile +``` + +##### 5. Reconstruire l'Image + +```bash +# Reconstruire l'image Docker +sudo docker build -t bitcoin-signet . +``` + +##### 6. Redémarrer le Conteneur + +```bash +# Arrêter le conteneur actuel +sudo docker stop bitcoin-signet-instance +sudo docker rm bitcoin-signet-instance + +# Démarrer avec la nouvelle image +sudo docker run --env-file .env -d \ + --name bitcoin-signet-instance \ + -p 38332:38332 \ + -p 38333:38333 \ + -p 28332:28332 \ + -p 28333:28333 \ + -p 28334:28334 \ + bitcoin-signet +``` + +##### 7. Vérifier la Mise à Jour + +```bash +# Attendre quelques secondes pour le démarrage +sleep 10 + +# Vérifier la version +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep subversion + +# Vérifier l'état de la blockchain +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo + +# Vérifier les logs +sudo docker logs bitcoin-signet-instance --tail 50 +``` + +##### 8. Enregistrer la Version + +```bash +# Enregistrer la nouvelle version +echo "30.2" > .bitcoin-version +``` + +### Notes Importantes sur les Mises à Jour + +#### Compatibilité des Versions + +- **Bitcoin Core 26.0+** : Peut avoir des changements de compatibilité avec les wallets legacy +- **Bitcoin Core 30.2** : Version actuelle recommandée +- Vérifiez toujours les [release notes](https://bitcoincore.org/en/releases/) avant de mettre à jour + +#### Impact sur le Mining + +- Le mining reprend automatiquement après la mise à jour +- Les blocs minés avant la mise à jour restent valides +- Aucune perte de données si la sauvegarde est effectuée + +#### Rollback en Cas de Problème + +Si la mise à jour pose problème : + +```bash +# Restaurer l'ancienne version du Dockerfile +git checkout HEAD -- Dockerfile + +# Restaurer les données si nécessaire +sudo docker stop bitcoin-signet-instance +sudo docker rm bitcoin-signet-instance +sudo docker cp backups/signet-backup-YYYYMMDD-HHMMSS.tar.gz bitcoin-signet-instance:/tmp/ +sudo docker exec bitcoin-signet-instance tar xzf /tmp/signet-backup-YYYYMMDD-HHMMSS.tar.gz -C / + +# Reconstruire avec l'ancienne version +sudo docker build -t bitcoin-signet . + +# Redémarrer +sudo docker run --env-file .env -d --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet +``` + +### Vérification Régulière des Mises à Jour + +Pour vérifier régulièrement si une nouvelle version est disponible : + +```bash +# Script de vérification +#!/bin/bash +CURRENT=$(cat .bitcoin-version 2>/dev/null || echo "unknown") +LATEST=$(curl -s https://bitcoincore.org/bin/ | grep -oP 'bitcoin-core-\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1) + +if [ "$CURRENT" != "$LATEST" ]; then + echo "Nouvelle version disponible: $LATEST (actuelle: $CURRENT)" + echo "Exécutez: ./update-signet.sh $LATEST" +else + echo "Déjà à jour: $CURRENT" +fi +``` + +--- + +## Dépannage + +### Problèmes Courants + +#### Le conteneur ne démarre pas + +```bash +# Vérifier les logs +sudo docker logs bitcoin-signet-instance + +# Vérifier que les ports ne sont pas déjà utilisés +sudo netstat -tlnp | grep -E "(38332|38333|28332|28333|28334)" + +# Vérifier que le fichier .env existe +ls -la .env +``` + +#### Bitcoind ne démarre pas + +```bash +# Vérifier les logs de debug +sudo docker exec bitcoin-signet-instance tail -100 /root/.bitcoin/signet/debug.log + +# Vérifier la configuration +sudo docker exec bitcoin-signet-instance cat /root/.bitcoin/bitcoin.conf + +# Vérifier les permissions +sudo docker exec bitcoin-signet-instance ls -la /root/.bitcoin/ +``` + +#### Le mining ne fonctionne pas + +```bash +# Vérifier que MINERENABLED=1 dans .env +grep MINERENABLED .env + +# Vérifier que la clé privée est importée +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listprivkeys + +# Vérifier que le script mine.sh est actif +sudo docker exec bitcoin-signet-instance ps aux | grep mine.sh +``` + +#### Erreur "Cannot obtain a lock" + +Cette erreur indique qu'un autre processus bitcoind est déjà en cours d'exécution. + +```bash +# Arrêter tous les processus bitcoind dans le conteneur +sudo docker exec bitcoin-signet-instance pkill bitcoind + +# Redémarrer le conteneur +sudo docker restart bitcoin-signet-instance +``` + +#### Le wallet n'existe pas + +```bash +# Créer le wallet manuellement (Bitcoin Core 30+ nécessite descriptor wallets) +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \ + -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=true + +# Charger le wallet si nécessaire +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin loadwallet custom_signet + +# Importer la clé privée si mining activé (Bitcoin Core 30+ nécessite descriptor wallets) +PRIVKEY=$(cat .env | grep PRIVKEY | cut -d'=' -f2) +DESCRIPTOR_INFO=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "wpkh($PRIVKEY)") +CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum') +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \ + importdescriptors "[{\"desc\":\"wpkh($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]" +``` + +### Réinitialisation Complète + +Si vous devez tout réinitialiser : + +```bash +# Arrêter et supprimer le conteneur +sudo docker stop bitcoin-signet-instance +sudo docker rm bitcoin-signet-instance + +# Supprimer le volume de données (ATTENTION : perte de données) +sudo docker volume rm $(sudo docker volume ls -q | grep bitcoin) + +# Ou supprimer manuellement les données +sudo rm -rf /var/lib/docker/volumes/*/bitcoin-signet-instance/_data + +# Relancer avec un nouveau .env (clés seront régénérées) +sudo docker run --env-file .env -d --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet +``` + +--- + +## Modifications Apportées + +### Modifications du Dockerfile + +1. **Mise à jour de Debian** : `buster-slim` → `bookworm-slim` + - Raison : Debian Buster n'est plus supporté + +2. **Détection automatique de l'architecture** : Remplacement de `TARGETPLATFORM` par `uname -m` + - Raison : `TARGETPLATFORM` n'est pas défini par défaut dans `docker build` + +3. **Installation de setuptools** : Via `apt` au lieu de `pip` + - Raison : PEP 668 empêche l'installation système via pip + +### Modifications des Scripts + +1. **gen-signet-keys.sh** : + - Correction de `ATADIR` → `DATADIR` (ligne 1) + - Mise à jour pour utiliser `descriptors=true` (Bitcoin Core 30+) + - Raison : Faute de frappe empêchait la création du répertoire temporaire, et Bitcoin Core 30+ nécessite des descriptor wallets + +2. **setup-signet.sh** : Suppression du démarrage de bitcoind + - Raison : Éviter les conflits de verrou, bitcoind est démarré par `run.sh` + +3. **run.sh** : Ajout de la création du wallet descriptor et import automatique de la clé privée + - Raison : Le wallet doit être créé après le démarrage de bitcoind + - Bitcoin Core 30+ nécessite des descriptor wallets (legacy wallets non supportés) + - La clé privée est importée automatiquement dans le descriptor wallet pour permettre le mining + - L'import utilise `importdescriptors` avec le checksum requis (obtenu via `getdescriptorinfo`) + - Le miner utilise `walletprocesspsbt` qui nécessite que la clé soit dans le wallet + +--- + +## Sauvegarde et Restauration + +### Sauvegarde + +```bash +# Sauvegarder les données Bitcoin +sudo docker exec bitcoin-signet-instance tar czf /tmp/bitcoin-backup.tar.gz /root/.bitcoin/ + +# Copier la sauvegarde hors du conteneur +sudo docker cp bitcoin-signet-instance:/tmp/bitcoin-backup.tar.gz ./bitcoin-backup-$(date +%Y%m%d).tar.gz + +# Sauvegarder le fichier .env +cp .env .env.backup-$(date +%Y%m%d) +``` + +### Restauration + +```bash +# Arrêter le conteneur +sudo docker stop bitcoin-signet-instance +sudo docker rm bitcoin-signet-instance + +# Créer un nouveau conteneur +sudo docker run --env-file .env -d --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet + +# Attendre que le conteneur démarre +sleep 5 + +# Restaurer les données +sudo docker cp bitcoin-backup-YYYYMMDD.tar.gz bitcoin-signet-instance:/tmp/ +sudo docker exec bitcoin-signet-instance tar xzf /tmp/bitcoin-backup-YYYYMMDD.tar.gz -C / + +# Redémarrer le conteneur +sudo docker restart bitcoin-signet-instance +``` + +### Sauvegarde des Clés + +**Important** : Toujours sauvegarder séparément : + +- Le fichier `.env` (contient `PRIVKEY` et `SIGNETCHALLENGE`) +- Les fichiers `/root/.bitcoin/PRIVKEY.txt` et `/root/.bitcoin/SIGNETCHALLENGE.txt` + +Ces clés sont essentielles pour maintenir la cohérence du signet. + +--- + +## Commandes Utiles + +### Script de Vérification Rapide + +```bash +#!/bin/bash +echo "=== État du Conteneur ===" +sudo docker ps | grep bitcoin-signet + +echo -e "\n=== État de la Blockchain ===" +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo | grep -E "(chain|blocks|bestblockhash)" + +echo -e "\n=== État du Réseau ===" +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo | grep -E "(networkactive|connections)" + +echo -e "\n=== État du Wallet ===" +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getwalletinfo | grep -E "(walletname|balance)" + +echo -e "\n=== Processus de Mining ===" +sudo docker exec bitcoin-signet-instance ps aux | grep -E "(bitcoind|mine)" | grep -v grep +``` + +### Script de Monitoring + +```bash +#!/bin/bash +watch -n 5 'sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo | grep -E "(chain|blocks|bestblockhash|difficulty)"' +``` + +--- + +## Références + +- [Dépôt Source](https://github.com/Easepay/easepay-custom-signet.git) +- [Documentation Bitcoin Signet](https://en.bitcoin.it/wiki/Signet) +- [Bitcoin Core RPC API](https://developer.bitcoin.org/reference/rpc/) +- [Documentation Docker](https://docs.docker.com/) + +--- + +**Dernière mise à jour** : 2026-01-09 diff --git a/docs/PROMPT_IA_RECHERCHE.md b/docs/PROMPT_IA_RECHERCHE.md new file mode 100644 index 0000000..06dffb4 --- /dev/null +++ b/docs/PROMPT_IA_RECHERCHE.md @@ -0,0 +1,152 @@ +# Prompt pour Recherche IA - Problème Mining Signet Bitcoin Core 30.2 + +**Contexte** : Problème de mining Bitcoin Signet avec Bitcoin Core 30.2 et descriptor wallets + +--- + +## PROMPT COMPLET + +Je rencontre un problème avec le mining Bitcoin Signet sur Bitcoin Core 30.2 utilisant des descriptor wallets. Le miner échoue systématiquement avec "PSBT signing failed" malgré une configuration apparemment correcte. + +### Contexte Technique + +**Environnement** : +- Bitcoin Core 30.2 (dernière version) +- Descriptor wallets (legacy wallets non supportés depuis Bitcoin Core 30+) +- Bitcoin Signet custom +- Docker container (Debian Bookworm) +- Script de mining Python basé sur `contrib/signet/miner.py` de Bitcoin Core + +### Configuration Actuelle + +**SIGNETCHALLENGE** : `5121028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a651ae` +- Format : `5121[PUBKEY]51ae` (script P2PK) +- Clé publique : `028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6` + +**PRIVKEY** : `cVCKcgQf2ewV5miairzhrHJCPv4kMbMMBZeJvW5SMhFMSWVtCvXS` +- Format WIF (Wallet Import Format) +- Dérive correctement vers la clé publique du SIGNETCHALLENGE ✅ + +**Wallet Descriptor** : +```json +{ + "desc": "pk(028b8d4cea1b3d8582babc8405bc618fbbb281c0f64e6561aa85968251931cd0a6)#vflt9jvv", + "timestamp": 1, + "active": false, + "has_private_keys": true, + "is_solvable": true +} +``` + +### Problème Observé + +Le miner Bitcoin Signet utilise `walletprocesspsbt` pour signer des transactions spéciales vers le SIGNETCHALLENGE. Le processus échoue à chaque tentative : + +**Logs** : +``` +PSBT signing failed +Delay before next block 600 seconds. +``` + +**Code du miner** (ligne 476-482) : +```python +psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time) +input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') +psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) +if not psbt_signed.get("complete",False): + logging.debug("Generated PSBT: %s" % (psbt,)) + sys.stderr.write("PSBT signing failed\n") + return 1 +``` + +### Structure du PSBT Signet + +Le miner crée un PSBT spécial qui contient : +1. Une transaction `to_spend` vers le SIGNETCHALLENGE (script P2PK) +2. Une transaction `spend` qui dépense `to_spend` +3. Le bloc à miner dans un champ propriétaire PSBT (`PSBT_SIGNET_BLOCK`) + +**Code de génération** (ligne 134-166) : +```python +def signet_txs(block, challenge): + # ... crée to_spend et spend transactions + to_spend.vout = [CTxOut(0, challenge)] # challenge est le SIGNETCHALLENGE (P2PK script) + return spend, to_spend +``` + +### Vérifications Effectuées + +✅ **Compatibilité des clés** : +- La PRIVKEY dérive correctement vers la clé publique du SIGNETCHALLENGE +- Le descriptor `pk()` est importé dans le wallet +- `hasprivatekeys: true` et `issolvable: true` + +✅ **Import du descriptor** : +- Utilisation de `importdescriptors` avec `pk($PRIVKEY)#$CHECKSUM` +- Le descriptor est présent dans le wallet (mais `active: false`) + +❌ **Problème** : +- `walletprocesspsbt` retourne `complete: false` +- Le wallet ne peut pas signer le PSBT signet + +### Tentatives de Résolution + +1. **Import comme `pk()` au lieu de `wpkh()`** ✅ + - Nécessaire car SIGNETCHALLENGE utilise P2PK, pas P2WPKH + - Le descriptor est importé avec succès + +2. **Tentative d'activation du descriptor** ❌ + - Erreur : "Active descriptors must be ranged" + - Les descriptors non-rangés ne peuvent pas être actifs + +3. **Vérification de compatibilité** ✅ + - Les clés sont compatibles + - Pas besoin de régénérer la chaîne + +### Questions Spécifiques + +1. **Bitcoin Core 30.2 et descriptor wallets** : + - Y a-t-il des limitations connues pour signer des transactions P2PK avec des descriptors `pk()` non-actifs ? + - Le wallet peut-il signer des transactions vers des scripts qui ne sont pas dans son UTXO set ? + +2. **PSBT Signet et walletprocesspsbt** : + - `walletprocesspsbt` peut-il signer des PSBT spéciaux contenant des transactions virtuelles (comme `to_spend` dans signet) ? + - Y a-t-il des paramètres spécifiques nécessaires pour signer des PSBT signet ? + +3. **Descriptor `pk()` non-actif** : + - Un descriptor `pk()` avec `active: false` peut-il être utilisé pour signer ? + - Faut-il importer la clé différemment pour le mining signet ? + +4. **Alternatives** : + - Existe-t-il une autre méthode pour signer les blocs signet avec Bitcoin Core 30.2 ? + - Faut-il utiliser une approche différente pour les descriptor wallets ? + +### Informations Supplémentaires + +**Version Bitcoin Core** : 30.2.0 +**Type de wallet** : Descriptor wallet (sqlite) +**Méthode d'import** : `importdescriptors` avec descriptor `pk($PRIVKEY)#$CHECKSUM` +**Script de mining** : Basé sur `contrib/signet/miner.py` de Bitcoin Core + +**Commandes de test** : +```bash +# Vérifier le descriptor +bitcoin-cli listdescriptors | grep "pk(" + +# Tester walletprocesspsbt (échoue) +miner --cli="bitcoin-cli" generate --nbits=1e0377ae --set-block-time=$(date +%s) +``` + +### Résultat Attendu + +Le wallet devrait pouvoir signer le PSBT signet via `walletprocesspsbt`, permettant au miner de créer et soumettre des blocs sur le signet. + +### Ressources Consultées + +- Documentation Bitcoin Core RPC : `walletprocesspsbt`, `importdescriptors` +- Code source : `contrib/signet/miner.py` +- Documentation descriptor wallets Bitcoin Core 30+ + +--- + +**Question principale** : Comment faire fonctionner le mining Bitcoin Signet avec Bitcoin Core 30.2 et descriptor wallets lorsque `walletprocesspsbt` échoue à signer le PSBT signet malgré un descriptor `pk()` correctement importé avec les clés privées ? diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9bc03d6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,65 @@ +# Documentation Bitcoin Signet Custom + +Ce dossier contient toute la documentation nécessaire pour la maintenance et l'utilisation du Bitcoin Signet custom. + +## Fichiers de Documentation + +- **[MAINTENANCE.md](./MAINTENANCE.md)** : Documentation complète de maintenance + - Architecture et structure + - Configuration et variables d'environnement + - Commandes de maintenance + - Gestion du conteneur + - Mining + - Accès RPC et API + - Mise à jour + - Dépannage + - Sauvegarde et restauration + +- **[INSTALLATION_NEW_NODE.md](./INSTALLATION_NEW_NODE.md)** : Guide d'installation d'un nouveau nœud + - Installation initiale + - Configuration pour rejoindre une chaîne existante + - Configuration du mining + - Vérification et dépannage + - Partage d'informations avec d'autres nœuds + +- **[INTERFACES.md](./INTERFACES.md)** : Documentation des interfaces disponibles + - API REST d'ancrage + - Interface RPC Bitcoin Core + - Interface ligne de commande (CLI) + - Interface ZMQ (ZeroMQ) + - Interface Docker + - Interface de configuration + - Interface de logs + +## Démarrage Rapide + +### Installation + +```bash +cd /home/ncantu/Bureau/code/bitcoin +sudo docker build -t bitcoin-signet . +sudo docker run --env-file .env -d --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet +``` + +### Vérification + +```bash +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo +``` + +### Logs + +```bash +sudo docker logs -f bitcoin-signet-instance +``` + +## Informations Importantes + +- **Ports** : RPC (38332), P2P (38333), ZMQ (28332-28334) +- **Configuration** : Fichier `.env` à la racine du projet +- **Données** : Stockées dans `/root/.bitcoin/` dans le conteneur +- **Clés** : Générées automatiquement, stockées dans `.env` et `/root/.bitcoin/PRIVKEY.txt` + +Pour plus de détails, consultez [MAINTENANCE.md](./MAINTENANCE.md). diff --git a/docs/SOLUTION_MINING.md b/docs/SOLUTION_MINING.md new file mode 100644 index 0000000..0ebb193 --- /dev/null +++ b/docs/SOLUTION_MINING.md @@ -0,0 +1,104 @@ +# Solution au Problème de Mining Signet - Bitcoin Core 30.2 + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0 + +## Problème Résolu + +Le mining Bitcoin Signet échouait avec "PSBT signing failed" sur Bitcoin Core 30.2 avec descriptor wallets. + +## Cause Racine + +Le problème venait de l'utilisation de `walletprocesspsbt` qui ne peut pas signer les PSBT signet contenant des transactions artificielles (`to_spend`/`spend`). Ces transactions ne sont pas des UTXOs réels dans le wallet, donc `walletprocesspsbt` ne peut pas les reconnaître et signer. + +## Solution + +**Remplacer `walletprocesspsbt` par `descriptorprocesspsbt`** + +`descriptorprocesspsbt` est un RPC de nœud (pas wallet) qui signe directement avec des descriptors fournis, sans dépendre du wallet et de son modèle UTXO. + +### Modifications Apportées + +#### 1. Fichier `miner` (ligne ~476-521) + +**Avant** : +```python +psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) +``` + +**Après** : +```python +# Use descriptorprocesspsbt by default (works with Bitcoin Core 30+ and descriptor wallets) +privkey = os.environ.get('PRIVKEY', '') +if not privkey and hasattr(args, 'privkey') and args.privkey: + privkey = args.privkey +if privkey: + descriptor = "pk(%s)" % privkey + descriptors_array = [descriptor] + psbt_signed = json.loads(args.bcli("descriptorprocesspsbt", psbt, json.dumps(descriptors_array), "ALL", "true", "true")) +else: + # Fallback to walletprocesspsbt + input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') + psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) +``` + +#### 2. Fichier `mine.sh` + +**Modification** : Export de PRIVKEY pour qu'il soit disponible dans l'environnement du miner + +```bash +export PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt 2>/dev/null || echo "")} +miner --cli="bitcoin-cli" generate ... +``` + +### Fonctionnement + +1. Le miner génère un PSBT signet avec les transactions artificielles `to_spend`/`spend` +2. Au lieu d'utiliser `walletprocesspsbt`, il utilise `descriptorprocesspsbt` +3. `descriptorprocesspsbt` reçoit : + - Le PSBT (base64) + - Un array de descriptors : `["pk(PRIVKEY)"]` + - Les paramètres : `"ALL"`, `true`, `true` (sighashtype, bip32derivs, finalize) +4. Le PSBT est signé avec succès (`complete: true`) +5. Le bloc est miné et soumis + +### Avantages + +- ✅ Fonctionne avec Bitcoin Core 30+ et descriptor wallets +- ✅ Ne dépend pas du wallet pour reconnaître les UTXOs +- ✅ Signe directement avec le descriptor contenant la clé privée +- ✅ Compatible avec les transactions artificielles Signet + +### Vérification + +**Test manuel** : +```bash +PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2) +PSBT=$(miner genpsbt ...) +bitcoin-cli -signet descriptorprocesspsbt "$PSBT" '["pk('$PRIVKEY')"]' "ALL" true true +# Résultat: {"psbt": "...", "complete": true, ...} +``` + +**Test automatique** : +```bash +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo | grep blocks +# Doit afficher blocks > 0 après quelques minutes +``` + +### Notes Techniques + +- `descriptorprocesspsbt` est un RPC de nœud, pas de wallet +- Il accepte les descriptors avec clés privées WIF +- Il peut signer des PSBT avec des entrées qui ne sont pas dans le wallet +- Parfait pour les cas d'usage comme Signet où les transactions sont artificielles + +### Références + +- [Bitcoin Core descriptorprocesspsbt](https://bitcoincore.org/en/doc/28.0.0/rpc/rawtransactions/descriptorprocesspsbt/) +- [Bitcoin Core PSBT Documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md) +- [GitHub Issue #28911](https://github.com/bitcoin/bitcoin/issues/28911) + +--- + +**Dernière mise à jour** : 2026-01-23 diff --git a/docs/TROUBLESHOOTING_MINING.md b/docs/TROUBLESHOOTING_MINING.md new file mode 100644 index 0000000..6aa04ae --- /dev/null +++ b/docs/TROUBLESHOOTING_MINING.md @@ -0,0 +1,94 @@ +# Dépannage du Mining - Bitcoin Signet + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0 + +## Problème : Aucun bloc n'est miné - "PSBT signing failed" + +### Symptômes + +- Les logs affichent : `PSBT signing failed` +- Aucun bloc n'est miné malgré `MINERENABLED=1` +- Le processus `mine.sh` est actif mais échoue à chaque tentative + +### Cause + +Le miner Bitcoin Signet utilise `walletprocesspsbt` pour signer des transactions spéciales vers le SIGNETCHALLENGE. Ces transactions utilisent un script P2PK (Pay-to-Public-Key), pas P2WPKH (Pay-to-Witness-Public-Key-Hash). + +**Problème** : Si la clé privée est importée uniquement comme `wpkh()` (P2WPKH), le wallet ne peut pas signer les transactions P2PK nécessaires pour le signet. + +### Solution + +La clé privée doit être importée comme `pk()` (P2PK) dans le wallet descriptor pour permettre la signature des transactions signet. + +**Fichier modifié** : `run.sh` + +**Avant** (incorrect) : +```bash +DESCRIPTOR_INFO=$(bitcoin-cli -datadir=$DATADIR getdescriptorinfo "wpkh($PRIVKEY)") +IMPORT_RESULT=$(bitcoin-cli -datadir=$DATADIR importdescriptors "[{\"desc\":\"wpkh($PRIVKEY)#$CHECKSUM\",...}]") +``` + +**Après** (correct) : +```bash +DESCRIPTOR_INFO=$(bitcoin-cli -datadir=$DATADIR getdescriptorinfo "pk($PRIVKEY)") +IMPORT_RESULT=$(bitcoin-cli -datadir=$DATADIR importdescriptors "[{\"desc\":\"pk($PRIVKEY)#$CHECKSUM\",...}]") +``` + +### Vérification + +1. **Vérifier que le descriptor pk() est importé** : +```bash +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin listdescriptors | grep "pk(" +``` + +2. **Vérifier que la clé correspond au SIGNETCHALLENGE** : +```bash +PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2) +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "pk($PRIVKEY)" | jq -r '.descriptor' +# Doit contenir la clé publique du SIGNETCHALLENGE +``` + +3. **Vérifier les logs** : +```bash +sudo docker logs bitcoin-signet-instance | grep -E "(PSBT|signing|Mine)" +``` + +### Correction Manuelle (si nécessaire) + +Si le problème persiste après la modification de `run.sh`, importer manuellement : + +```bash +PRIVKEY=$(grep PRIVKEY .env | cut -d'=' -f2) +DESCRIPTOR_INFO=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getdescriptorinfo "pk($PRIVKEY)") +CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum') +sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin \ + importdescriptors "[{\"desc\":\"pk($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]" +``` + +### Redémarrage + +Après modification de `run.sh`, reconstruire l'image et redémarrer : + +```bash +sudo docker stop bitcoin-signet-instance +sudo docker rm bitcoin-signet-instance +sudo docker build -t bitcoin-signet . +sudo docker run --env-file .env -d \ + --name bitcoin-signet-instance \ + -p 38332:38332 -p 38333:38333 \ + -p 28332:28332 -p 28333:28333 -p 28334:28334 \ + bitcoin-signet +``` + +### Notes Techniques + +- **SIGNETCHALLENGE** : Contient un script P2PK avec la clé publique du signet +- **Miner** : Crée une transaction `to_spend` vers ce script P2PK qui doit être signée +- **walletprocesspsbt** : Nécessite que le wallet ait la clé privée correspondante au script P2PK +- **Descriptor pk()** : Permet au wallet de signer pour les scripts P2PK + +--- + +**Dernière mise à jour** : 2026-01-23 diff --git a/env.example b/env.example index d3f67b6..d1d0845 100644 --- a/env.example +++ b/env.example @@ -1,8 +1,10 @@ # Mining Configuration +# Note: Bitcoin Core 30+ uses descriptor wallets (legacy wallets no longer supported) BLOCKPRODUCTIONDELAY= MINERENABLED=1 NBITS= PRIVKEY= +# Note: PRIVKEY is automatically imported into descriptor wallet at startup for mining MINETO= SIGNETCHALLENGE= diff --git a/gen-bitcoind-conf.sh b/gen-bitcoind-conf.sh index c5721b8..c99be7d 100644 --- a/gen-bitcoind-conf.sh +++ b/gen-bitcoind-conf.sh @@ -46,7 +46,7 @@ if [[ "$ADDNODE" != "" ]]; then echo "addnode=$node" done fi - + if [[ "$I2PSAM" != "" ]]; then echo "i2psam=$I2PSAM" diff --git a/gen-signet-keys.sh b/gen-signet-keys.sh index cf78c94..5bed480 100644 --- a/gen-signet-keys.sh +++ b/gen-signet-keys.sh @@ -1,4 +1,4 @@ -ATADIR=${DATADIR:-"regtest-temp"} +DATADIR=${DATADIR:-"regtest-temp"} BITCOINCLI=${BITCOINCLI:-"bitcoin-cli -regtest -datadir=$DATADIR "} BITCOIND=${BITCOIND:-"bitcoind -datadir=$DATADIR -regtest -daemon "} @@ -33,9 +33,8 @@ if [[ "$MINERENABLED" == "1" && ("$SIGNETCHALLENGE" == "" || "$PRIVKEY" == "") ] $BITCOIND -wallet="temp" #wait a bit for startup sleep 5s - #create wallet - # todo, redo to work with descriptors - $BITCOINCLI -named createwallet wallet_name="tmp" descriptors=false + #create wallet (Bitcoin Core 30+ requires descriptor wallets) + $BITCOINCLI -named createwallet wallet_name="tmp" descriptors=true #export future signet seeding key data ADDR=$($BITCOINCLI getnewaddress) PRIVKEY=$($BITCOINCLI dumpprivkey $ADDR) diff --git a/mempool b/mempool new file mode 160000 index 0000000..912f74b --- /dev/null +++ b/mempool @@ -0,0 +1 @@ +Subproject commit 912f74bea073b2857b49d850cdd53be748188ccf diff --git a/mine.sh b/mine.sh index b6ca184..9b3fb3c 100644 --- a/mine.sh +++ b/mine.sh @@ -15,5 +15,9 @@ while true; do fi fi echo "Mine To:" $ADDR + # PRIVKEY should be available from .env via run.sh environment + # The miner will automatically use PRIVKEY from environment for descriptorprocesspsbt + # Export PRIVKEY to ensure it's available to the miner process + export PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt 2>/dev/null || echo "")} miner --cli="bitcoin-cli" generate --grind-cmd="bitcoin-util grind" --address=$ADDR --nbits=$NBITS --set-block-time=$(date +%s) done \ No newline at end of file diff --git a/miner b/miner index ec6df9e..bf4bcd9 100644 --- a/miner +++ b/miner @@ -319,7 +319,7 @@ def seconds_to_hms(s): return out def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson): - this_interval = 0.000001 + this_interval = 0.000001 return this_interval def next_block_is_mine(last_hash, my_blocks): @@ -474,8 +474,53 @@ def do_generate(args): logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine) mined_blocks += 1 psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time) - input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') - psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) + + # Use descriptorprocesspsbt instead of walletprocesspsbt for Bitcoin Core 30+ + # descriptorprocesspsbt can sign PSBT with artificial UTXOs (to_spend/spend) used in Signet + # It doesn't require the wallet to recognize the UTXO, just the descriptor with private key + if hasattr(args, 'signer') and args.signer == 'descriptor': + # Get private key from descriptor or use provided one + privkey = args.descriptor if args.descriptor and not '*' in args.descriptor else None + if privkey is None and hasattr(args, 'privkey') and args.privkey: + privkey = args.privkey + if privkey: + # Extract WIF from descriptor if needed (pk(WIF) or wpkh(WIF)) + if privkey.startswith('pk(') or privkey.startswith('wpkh('): + privkey = privkey.split('(')[1].split(')')[0].split('#')[0] + descriptor = "pk(%s)" % privkey + # Use descriptorprocesspsbt: PSBT, descriptors array (JSON), sighashtype, bip32derivs, finalize + descriptors_array = [descriptor] + psbt_signed = json.loads(args.bcli("descriptorprocesspsbt", psbt, json.dumps(descriptors_array), "ALL", "true", "true")) + else: + # Fallback to walletprocesspsbt if no descriptor/privkey provided + input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') + psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) + else: + # Use descriptorprocesspsbt by default (works with Bitcoin Core 30+ and descriptor wallets) + # This avoids the wallet UTXO recognition issue with artificial Signet transactions + try: + # Get privkey from environment or args + privkey = os.environ.get('PRIVKEY', '') + if not privkey and hasattr(args, 'privkey') and args.privkey: + privkey = args.privkey + if privkey: + descriptor = "pk(%s)" % privkey + # Use descriptorprocesspsbt: PSBT, descriptors array (JSON), sighashtype, bip32derivs, finalize + # bitcoin_cli adds -signet automatically and converts args to strings + descriptors_array = [descriptor] + psbt_signed = json.loads(args.bcli("descriptorprocesspsbt", psbt, json.dumps(descriptors_array), "ALL", "true", "true")) + logging.debug("Used descriptorprocesspsbt for signing") + else: + # Fallback to walletprocesspsbt if no PRIVKEY available + logging.debug("No PRIVKEY found, falling back to walletprocesspsbt") + input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') + psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) + except Exception as e: + # If descriptorprocesspsbt fails, try walletprocesspsbt as fallback + logging.debug("descriptorprocesspsbt failed, trying walletprocesspsbt: %s" % str(e)) + input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') + psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) + if not psbt_signed.get("complete",False): logging.debug("Generated PSBT: %s" % (psbt,)) sys.stderr.write("PSBT signing failed\n") @@ -584,6 +629,10 @@ def main(): args.bcli = lambda *a, input=b"", **kwargs: bitcoin_cli(args.cli.split(" "), list(a), input=input, **kwargs) + # Get PRIVKEY from environment or argument for descriptor signing + if not hasattr(args, 'privkey') or args.privkey is None: + args.privkey = os.environ.get('PRIVKEY', '') + if hasattr(args, "address") and hasattr(args, "descriptor"): if args.address is None and args.descriptor is None: sys.stderr.write("Must specify --address or --descriptor\n") diff --git a/run.sh b/run.sh index 44cf5c0..2e4c823 100644 --- a/run.sh +++ b/run.sh @@ -3,8 +3,46 @@ # run bitcoind bitcoind --daemonwait sleep 5 + +# Create wallet if it doesn't exist +DATADIR=${DATADIR:-~/.bitcoin/} +if ! bitcoin-cli -datadir=$DATADIR listwallets | grep -q "custom_signet"; then + echo "Creating wallet custom_signet" + bitcoin-cli -datadir=$DATADIR -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=true +fi + +# Load wallet if not already loaded +if ! bitcoin-cli -datadir=$DATADIR listwallets | grep -q "custom_signet"; then + bitcoin-cli -datadir=$DATADIR loadwallet custom_signet +fi + +# Import private key if in mining mode (Bitcoin Core 30+ requires descriptor wallets) +# The miner needs the private key in the wallet to sign blocks via walletprocesspsbt +# IMPORTANT: For signet mining, we need to import as pk() (P2PK) not wpkh() (P2WPKH) +# because the SIGNETCHALLENGE uses a P2PK script that must be signed +if [[ "$MINERENABLED" == "1" ]]; then + PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt)} + if [[ -n "$PRIVKEY" ]]; then + echo "Importing private key for mining (descriptor wallet)" + # Import as pk() for P2PK signing (required for signet challenge) + # The signet miner needs to sign transactions to the SIGNETCHALLENGE which is a P2PK script + # Note: pk() descriptors cannot be active (must be ranged), but they can still be used for signing + DESCRIPTOR_INFO=$(bitcoin-cli -datadir=$DATADIR getdescriptorinfo "pk($PRIVKEY)") + CHECKSUM=$(echo "$DESCRIPTOR_INFO" | jq -r '.checksum') + # Import descriptor with checksum (required for Bitcoin Core 30+) + # The descriptor doesn't need to be active to sign - it just needs to be in the wallet + IMPORT_RESULT=$(bitcoin-cli -datadir=$DATADIR importdescriptors "[{\"desc\":\"pk($PRIVKEY)#$CHECKSUM\",\"timestamp\":0,\"internal\":false}]") + if echo "$IMPORT_RESULT" | jq -e '.[0].success == true' > /dev/null; then + echo "Private key imported successfully into descriptor wallet (P2PK for signet)" + else + echo "Warning: Private key import may have failed" + echo "Import result: $IMPORT_RESULT" + fi + fi +fi + echo "get magic" -magic=$(cat /root/.bitcoin/signet/debug.log | grep -m1 magic) +magic=$(cat /root/.bitcoin/signet/debug.log | grep -m1 magic) magic=${magic:(-8)} echo $magic > /root/.bitcoin/MAGIC.txt diff --git a/setup-signet.sh b/setup-signet.sh index 58ff735..6b16b54 100755 --- a/setup-signet.sh +++ b/setup-signet.sh @@ -1,17 +1,4 @@ PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt)} DATADIR=${DATADIR:-~/.bitcoin/} -bitcoind -datadir=$DATADIR --daemonwait -persistmempool -bitcoin-cli -datadir=$DATADIR -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=false -# bitcoin-cli -datadir=$DATADIR loadwallet "custom_signet" - -#only used in case of mining node -if [[ "$MINERENABLED" == "1" ]]; then - bitcoin-cli -datadir=$DATADIR importprivkey $PRIVKEY - ## for future with descriptor wallets, cannot seem to get it working yet - # descinfo=$(bitcoin-cli getdescriptorinfo "wpkh(${PRIVKEY})") - # checksum=$(echo "$descinfo" | jq .checksum | tr -d '"' | tr -d "\n") - # desc='[{"desc":"wpkh('$PRIVKEY')#'$checksum'","timestamp":0,"internal":false}]' - # bitcoin-cli -datadir=$DATADIR importdescriptors $desc -fi - - +# Note: bitcoind will be started by run.sh, setup-signet.sh just prepares the configuration +# The wallet creation and key import will be done in run.sh after bitcoind starts diff --git a/signet-dashboard/.env.example b/signet-dashboard/.env.example new file mode 100644 index 0000000..c39c944 --- /dev/null +++ b/signet-dashboard/.env.example @@ -0,0 +1,21 @@ +# Bitcoin RPC Configuration +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +BITCOIN_RPC_TIMEOUT=30000 + +# Dashboard Configuration +DASHBOARD_PORT=3020 +DASHBOARD_HOST=0.0.0.0 + +# API Anchor Configuration (pour les tests d'ancrage) +ANCHOR_API_URL=http://localhost:3010 +ANCHOR_API_KEY=your-api-key-here + +# API Faucet Configuration +FAUCET_API_URL=http://localhost:3021 + +# Logging +LOG_LEVEL=info +NODE_ENV=production diff --git a/signet-dashboard/.gitignore b/signet-dashboard/.gitignore new file mode 100644 index 0000000..5c532ee --- /dev/null +++ b/signet-dashboard/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.env +*.log +.DS_Store +dist/ +build/ diff --git a/signet-dashboard/README.md b/signet-dashboard/README.md new file mode 100644 index 0000000..cde90ee --- /dev/null +++ b/signet-dashboard/README.md @@ -0,0 +1,348 @@ +# Dashboard de Supervision Bitcoin Signet + +**Auteur** : Équipe 4NK +**Date** : 2026-01-23 +**Version** : 1.0.0 + +## Description + +Interface web de supervision et de test pour Bitcoin Signet. Ce dashboard permet de : + +- Surveiller l'état de la blockchain Signet en temps réel +- Tester l'API d'ancrage avec génération de hash +- Utiliser le faucet pour recevoir des sats + +## Caractéristiques + +- **Port** : `3020` +- **Interface** : Web (HTML/CSS/JavaScript) +- **Backend** : Node.js/Express +- **Rafraîchissement** : Automatique toutes les 30 secondes + +## Fonctionnalités + +### Supervision de la Blockchain + +- **Hauteur de bloc** : Affiche le nombre de blocs minés +- **Date du dernier bloc miné** : Timestamp du dernier bloc +- **Nombre de transactions dans le dernier bloc** : Compte des transactions +- **Balance du miner** : + - Balance mature (disponible) + - Balance immature (en attente de confirmation) +- **Nombre d'ancrages** : Compte approximatif des ancrages OP_RETURN +- **Nombre de pairs** : Nombre de connexions P2P actives + +### Test de l'API d'Ancrage + +- **Saisie de texte** : Entrer un texte à ancrer +- **Sélection de fichier** : Choisir un fichier à ancrer +- **Génération de hash** : Génération automatique du hash SHA256 +- **Ancrage** : Envoi du hash à l'API d'ancrage (port 3010) +- **Résultat** : Affichage du TXID et du statut de l'ancrage + +### Faucet + +- **Adresse Bitcoin** : Saisie d'une adresse Signet valide +- **Montant** : 50 000 sats (0.0005 BTC) par défaut +- **Envoi** : Transaction automatique via l'API faucet (port 3021) + +## Installation + +### Prérequis + +- Node.js >= 18.0.0 +- Accès au nœud Bitcoin Signet (RPC sur port 38332) +- API d'ancrage accessible (port 3010) +- API faucet accessible (port 3021) + +### Installation des Dépendances + +```bash +cd signet-dashboard +npm install +``` + +### Configuration + +1. Copier le fichier d'exemple : +```bash +cp .env.example .env +``` + +2. Éditer `.env` : +```bash +# Bitcoin RPC Configuration +BITCOIN_RPC_HOST=localhost +BITCOIN_RPC_PORT=38332 +BITCOIN_RPC_USER=bitcoin +BITCOIN_RPC_PASSWORD=bitcoin +BITCOIN_RPC_TIMEOUT=30000 + +# Dashboard Configuration +DASHBOARD_PORT=3020 +DASHBOARD_HOST=0.0.0.0 + +# API Anchor Configuration (pour les tests d'ancrage) +ANCHOR_API_URL=http://localhost:3010 +ANCHOR_API_KEY=your-api-key-here + +# API Faucet Configuration +FAUCET_API_URL=http://localhost:3021 + +# Logging +LOG_LEVEL=info +NODE_ENV=production +``` + +**Important** : +- `BITCOIN_RPC_HOST` : Si le dashboard est dans un conteneur Docker, utiliser l'IP du conteneur Bitcoin ou `host.docker.internal` +- `ANCHOR_API_KEY` : Clé API pour l'API d'ancrage (port 3010) +- `FAUCET_API_URL` : URL de l'API faucet (port 3021) + +## Démarrage + +### Mode Développement + +```bash +npm run dev +``` + +### Mode Production + +```bash +npm start +``` + +### Avec PM2 (recommandé pour production) + +```bash +# Installer PM2 globalement +sudo npm install -g pm2 + +# Démarrer le dashboard avec PM2 +pm2 start src/server.js --name signet-dashboard + +# Sauvegarder la configuration PM2 +pm2 save + +# Configurer PM2 pour démarrer au boot +pm2 startup +# Suivre les instructions affichées +``` + +## Accès + +Une fois démarré, le dashboard est accessible à l'adresse : + +``` +http://localhost:3020 +``` + +## Architecture + +``` +Client (Navigateur) + ↓ HTTP +Dashboard (port 3020) + ↓ RPC +Bitcoin Signet Node (port 38332) + ↓ HTTP +API Anchor (port 3010) + ↓ HTTP +API Faucet (port 3021) +``` + +## API Endpoints + +### GET /api/blockchain/info + +Obtient les informations sur la blockchain. + +**Réponse** : +```json +{ + "chain": "signet", + "blocks": 1234, + "bestblockhash": "...", + ... +} +``` + +### GET /api/blockchain/latest-block + +Obtient les informations sur le dernier bloc miné. + +**Réponse** : +```json +{ + "hash": "...", + "height": 1234, + "time": 1234567890, + "tx_count": 5, + ... +} +``` + +### GET /api/wallet/balance + +Obtient le solde du wallet (mature et immature). + +**Réponse** : +```json +{ + "mature": 1.5, + "immature": 0.1, + "unconfirmed": 0.0, + "total": 1.6 +} +``` + +### GET /api/network/peers + +Obtient le nombre de pairs connectés. + +**Réponse** : +```json +{ + "connections": 5, + "peers": [...] +} +``` + +### GET /api/anchor/count + +Obtient le nombre d'ancrages (approximatif). + +**Réponse** : +```json +{ + "count": 42 +} +``` + +### POST /api/hash/generate + +Génère un hash SHA256 à partir d'un texte ou d'un fichier. + +**Body** : +```json +{ + "text": "Texte à hasher" +} +``` +ou +```json +{ + "fileContent": "Contenu du fichier" +} +``` + +**Réponse** : +```json +{ + "hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890" +} +``` + +### POST /api/anchor/test + +Teste l'ancrage d'un document via l'API d'ancrage. + +**Body** : +```json +{ + "hash": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890" +} +``` + +**Réponse** : +```json +{ + "txid": "...", + "status": "confirmed", + "confirmations": 1, + "block_height": 1234 +} +``` + +## Configuration Nginx (sur proxy 192.168.1.100) + +Ajouter dans la configuration nginx du proxy : + +```nginx +# Dashboard Signet +server { + listen 443 ssl http2; + server_name dashboard.signet.4nkweb.com; + + ssl_certificate /etc/letsencrypt/live/dashboard.signet.4nkweb.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/dashboard.signet.4nkweb.com/privkey.pem; + + location / { + proxy_pass http://192.168.1.103:3020; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} +``` + +**Note** : Remplacer `192.168.1.103` par l'IP du serveur où le dashboard est déployé. + +## Sécurité + +- Le dashboard est accessible publiquement (pas d'authentification) +- Les appels à l'API d'ancrage nécessitent une clé API (configurée dans `.env`) +- Les appels à l'API faucet sont publics (gestion de rate limiting recommandée côté API faucet) + +## Dépannage + +### Le dashboard ne se charge pas + +- Vérifier que le serveur est démarré : `pm2 status` ou `ps aux | grep node` +- Vérifier les logs : `pm2 logs signet-dashboard` +- Vérifier que le port 3020 n'est pas utilisé : `netstat -tlnp | grep 3020` + +### Les données ne se mettent pas à jour + +- Vérifier la connexion RPC Bitcoin : `curl http://localhost:3020/api/blockchain/info` +- Vérifier les logs pour les erreurs RPC +- Vérifier que le nœud Bitcoin est accessible + +### L'ancrage ne fonctionne pas + +- Vérifier que l'API d'ancrage est accessible : `curl http://localhost:3010/health` +- Vérifier que la clé API est correcte dans `.env` +- Vérifier les logs du dashboard pour les erreurs + +### Le faucet ne fonctionne pas + +- Vérifier que l'API faucet est accessible : `curl http://localhost:3021/health` +- Vérifier que le wallet a suffisamment de fonds +- Vérifier les logs de l'API faucet + +## Structure des Fichiers + +``` +signet-dashboard/ +├── package.json +├── README.md +├── .env.example +├── src/ +│ ├── server.js # Serveur Express +│ ├── bitcoin-rpc.js # Client Bitcoin RPC +│ └── logger.js # Logger +└── public/ + ├── index.html # Interface web + ├── styles.css # Styles CSS + └── app.js # JavaScript client +``` + +## Licence + +MIT diff --git a/signet-dashboard/package-lock.json b/signet-dashboard/package-lock.json new file mode 100644 index 0000000..f20665e --- /dev/null +++ b/signet-dashboard/package-lock.json @@ -0,0 +1,1602 @@ +{ + "name": "bitcoin-signet-dashboard", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bitcoin-signet-dashboard", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "bitcoin-core": "^4.2.0", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@uphold/request-logger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@uphold/request-logger/-/request-logger-2.0.0.tgz", + "integrity": "sha512-UvGS+v87C7VTtQDcFHDLfvfl1zaZaLSwSmAnV35Ne7CzAVvotmZqt9lAIoNpMpaoRpdjVIcnUDwPSeIeA//EoQ==", + "license": "MIT", + "dependencies": { + "uuid": "^3.0.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "request": ">=2.27.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bitcoin-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/bitcoin-core/-/bitcoin-core-4.2.0.tgz", + "integrity": "sha512-QFmer//rZhyuYm0mBOVAmMhIG/EFmXDahC3a1LvNTwM8/KszxUGEAjvAT4tzm1FaDj4c7pQWMG/vOWtoKjeR3Q==", + "license": "MIT", + "dependencies": { + "@uphold/request-logger": "^2.0.0", + "debugnyan": "^1.0.0", + "json-bigint": "^1.0.0", + "lodash": "^4.0.0", + "request": "^2.53.0", + "semver": "^5.1.0", + "standard-error": "^1.1.0" + }, + "engines": { + "node": ">=7" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bunyan": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debugnyan": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/debugnyan/-/debugnyan-1.0.0.tgz", + "integrity": "sha512-dTvKxcLZCammDLFYi31NRVr5g6vjJ33uf1wcdbIPPxPxxnJ9/xj00Mh/YQkhFMw/VGavaG5KpjSC+4o9r/JjRg==", + "license": "MIT", + "dependencies": { + "bunyan": "^1.8.1", + "debug": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "nan": "^2.14.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nan": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", + "license": "MIT", + "optional": true + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "license": "MIT", + "optional": true, + "bin": { + "ncp": "bin/ncp" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^6.0.1" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "license": "MIT", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/standard-error/-/standard-error-1.1.0.tgz", + "integrity": "sha512-4v7qzU7oLJfMI5EltUSHCaaOd65J6S4BqKRWgzMi4EYaE5fvNabPxmAPGdxpGXqrcWjhDGI/H09CIdEuUOUeXg==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + } + } +} diff --git a/signet-dashboard/package.json b/signet-dashboard/package.json new file mode 100644 index 0000000..94cfa0d --- /dev/null +++ b/signet-dashboard/package.json @@ -0,0 +1,29 @@ +{ + "name": "bitcoin-signet-dashboard", + "version": "1.0.0", + "description": "Dashboard de supervision et faucet pour Bitcoin Signet", + "main": "src/server.js", + "type": "module", + "scripts": { + "start": "node src/server.js", + "dev": "node --watch src/server.js" + }, + "keywords": [ + "bitcoin", + "signet", + "dashboard", + "faucet", + "supervision" + ], + "author": "Équipe 4NK", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "bitcoin-core": "^4.2.0", + "dotenv": "^16.3.1", + "cors": "^2.8.5" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/signet-dashboard/public/app.js b/signet-dashboard/public/app.js new file mode 100644 index 0000000..db4fa50 --- /dev/null +++ b/signet-dashboard/public/app.js @@ -0,0 +1,361 @@ +/** + * Application JavaScript pour le Dashboard Bitcoin Signet + */ + +const API_BASE_URL = window.location.origin; +// Utiliser le même origin pour le faucet (sera configuré via le proxy) +// Si on est sur dashboard.certificator.4nkweb.com, utiliser faucet.certificator.4nkweb.com +let FAUCET_API_URL; +if (window.location.hostname.includes('dashboard.certificator.4nkweb.com')) { + FAUCET_API_URL = window.location.origin.replace('dashboard.certificator.4nkweb.com', 'faucet.certificator.4nkweb.com'); +} else if (window.location.hostname.includes('localhost') || window.location.hostname === '127.0.0.1') { + FAUCET_API_URL = 'http://localhost:3021'; +} else { + FAUCET_API_URL = window.location.origin; +} + +let selectedFile = null; + +// Initialisation +document.addEventListener('DOMContentLoaded', () => { + loadData(); + setInterval(loadData, 30000); // Rafraîchir toutes les 30 secondes +}); + +/** + * Charge toutes les données de supervision + */ +async function loadData() { + try { + await Promise.all([ + loadBlockchainInfo(), + loadLatestBlock(), + loadWalletBalance(), + loadAnchorCount(), + loadNetworkPeers(), + ]); + + updateLastUpdateTime(); + } catch (error) { + console.error('Error loading data:', error); + } +} + +/** + * Charge les informations de la blockchain + */ +async function loadBlockchainInfo() { + try { + const response = await fetch(`${API_BASE_URL}/api/blockchain/info`); + const data = await response.json(); + + document.getElementById('block-height').textContent = data.blocks || 0; + } catch (error) { + console.error('Error loading blockchain info:', error); + document.getElementById('block-height').textContent = 'Erreur'; + } +} + +/** + * Charge les informations du dernier bloc + */ +async function loadLatestBlock() { + try { + const response = await fetch(`${API_BASE_URL}/api/blockchain/latest-block`); + const data = await response.json(); + + if (data.time) { + const date = new Date(data.time * 1000); + document.getElementById('last-block-time').textContent = date.toLocaleString('fr-FR'); + } else { + document.getElementById('last-block-time').textContent = 'Aucun bloc'; + } + + document.getElementById('last-block-tx-count').textContent = data.tx_count || 0; + } catch (error) { + console.error('Error loading latest block:', error); + document.getElementById('last-block-time').textContent = 'Erreur'; + document.getElementById('last-block-tx-count').textContent = 'Erreur'; + } +} + +/** + * Charge le solde du wallet + */ +async function loadWalletBalance() { + try { + const response = await fetch(`${API_BASE_URL}/api/wallet/balance`); + const data = await response.json(); + + document.getElementById('balance-mature').textContent = formatBTC(data.mature || 0); + document.getElementById('balance-immature').textContent = formatBTC(data.immature || 0); + } catch (error) { + console.error('Error loading wallet balance:', error); + document.getElementById('balance-mature').textContent = 'Erreur'; + document.getElementById('balance-immature').textContent = 'Erreur'; + } +} + +/** + * Charge le nombre d'ancrages + */ +async function loadAnchorCount() { + try { + const response = await fetch(`${API_BASE_URL}/api/anchor/count`); + const data = await response.json(); + + document.getElementById('anchor-count').textContent = data.count || 0; + } catch (error) { + console.error('Error loading anchor count:', error); + document.getElementById('anchor-count').textContent = 'Erreur'; + } +} + +/** + * Charge les informations sur les pairs + */ +async function loadNetworkPeers() { + try { + const response = await fetch(`${API_BASE_URL}/api/network/peers`); + const data = await response.json(); + + document.getElementById('peer-count').textContent = data.connections || 0; + } catch (error) { + console.error('Error loading network peers:', error); + document.getElementById('peer-count').textContent = 'Erreur'; + } +} + +/** + * Formate un montant en BTC + */ +function formatBTC(btc) { + if (btc === 0) return '0 BTC'; + if (btc < 0.000001) return `${(btc * 100000000).toFixed(0)} sats`; + return `${btc.toFixed(8)} BTC`; +} + +/** + * Met à jour l'heure de dernière mise à jour + */ +function updateLastUpdateTime() { + const now = new Date(); + document.getElementById('last-update').textContent = now.toLocaleString('fr-FR'); +} + +/** + * Change d'onglet + */ +function switchTab(tab, buttonElement) { + // Désactiver tous les onglets + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + document.querySelectorAll('.tab-button').forEach(button => { + button.classList.remove('active'); + }); + + // Activer l'onglet sélectionné + document.getElementById(`${tab}-tab`).classList.add('active'); + // Activer le bouton correspondant + if (buttonElement) { + buttonElement.classList.add('active'); + } +} + +/** + * Gère la sélection de fichier + */ +function handleFileSelect(event) { + const file = event.target.files[0]; + if (file) { + selectedFile = file; + const fileInfo = document.getElementById('file-info'); + fileInfo.innerHTML = ` + Fichier sélectionné : ${file.name}
+ Taille : ${formatFileSize(file.size)}
+ Type : ${file.type || 'Non spécifié'} + `; + } +} + +/** + * Formate la taille d'un fichier + */ +function formatFileSize(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; +} + +/** + * Génère le hash depuis le texte + */ +async function generateHashFromText() { + const text = document.getElementById('anchor-text').value; + + if (!text.trim()) { + showResult('anchor-result', 'error', 'Veuillez entrer un texte à ancrer.'); + return; + } + + try { + const response = await fetch(`${API_BASE_URL}/api/hash/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ text }), + }); + + const data = await response.json(); + + if (data.hash) { + document.getElementById('anchor-hash').value = data.hash; + showResult('anchor-result', 'success', `Hash généré avec succès : ${data.hash}`); + } else { + showResult('anchor-result', 'error', 'Erreur lors de la génération du hash.'); + } + } catch (error) { + showResult('anchor-result', 'error', `Erreur : ${error.message}`); + } +} + +/** + * Génère le hash depuis le fichier + */ +async function generateHashFromFile() { + if (!selectedFile) { + showResult('anchor-result', 'error', 'Veuillez sélectionner un fichier.'); + return; + } + + try { + const reader = new FileReader(); + reader.onload = async (e) => { + const fileContent = e.target.result; + + try { + const response = await fetch(`${API_BASE_URL}/api/hash/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ fileContent }), + }); + + const data = await response.json(); + + if (data.hash) { + document.getElementById('anchor-hash').value = data.hash; + showResult('anchor-result', 'success', `Hash généré avec succès : ${data.hash}`); + } else { + showResult('anchor-result', 'error', 'Erreur lors de la génération du hash.'); + } + } catch (error) { + showResult('anchor-result', 'error', `Erreur : ${error.message}`); + } + }; + + reader.readAsText(selectedFile); + } catch (error) { + showResult('anchor-result', 'error', `Erreur lors de la lecture du fichier : ${error.message}`); + } +} + +/** + * Ancre le document + */ +async function anchorDocument() { + const hash = document.getElementById('anchor-hash').value; + + if (!hash || !/^[0-9a-fA-F]{64}$/.test(hash)) { + showResult('anchor-result', 'error', 'Veuillez générer un hash valide (64 caractères hexadécimaux).'); + return; + } + + try { + showResult('anchor-result', 'info', 'Ancrage en cours...'); + + const response = await fetch(`${API_BASE_URL}/api/anchor/test`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ hash }), + }); + + const data = await response.json(); + + if (response.ok && data.txid) { + showResult('anchor-result', 'success', + `Document ancré avec succès !
+ TXID : ${data.txid}
+ Statut : ${data.status}
+ Confirmations : ${data.confirmations || 0}`); + + // Recharger le nombre d'ancrages après un court délai + setTimeout(loadAnchorCount, 2000); + } else { + showResult('anchor-result', 'error', data.message || data.error || 'Erreur lors de l\'ancrage.'); + } + } catch (error) { + showResult('anchor-result', 'error', `Erreur : ${error.message}`); + } +} + +/** + * Demande des sats via le faucet + */ +async function requestFaucet() { + const address = document.getElementById('faucet-address').value.trim(); + + if (!address) { + showResult('faucet-result', 'error', 'Veuillez entrer une adresse Bitcoin.'); + return; + } + + // Validation basique de l'adresse + const addressPattern = /^(tb1|bcrt1|2|3)[a-zA-HJ-NP-Z0-9]{25,62}$/; + if (!addressPattern.test(address)) { + showResult('faucet-result', 'error', 'Adresse Bitcoin invalide.'); + return; + } + + try { + showResult('faucet-result', 'info', 'Demande en cours...'); + + const response = await fetch(`${FAUCET_API_URL}/api/faucet/request`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ address }), + }); + + const data = await response.json(); + + if (response.ok && data.txid) { + showResult('faucet-result', 'success', + `50 000 sats envoyés avec succès !
+ TXID : ${data.txid}
+ Montant : ${data.amount || '50000'} sats
+ Statut : ${data.status || 'En attente de confirmation'}`); + } else { + showResult('faucet-result', 'error', data.message || data.error || 'Erreur lors de la demande.'); + } + } catch (error) { + showResult('faucet-result', 'error', `Erreur : ${error.message}`); + } +} + +/** + * Affiche un résultat + */ +function showResult(elementId, type, message) { + const element = document.getElementById(elementId); + element.className = `result ${type}`; + element.innerHTML = message; +} diff --git a/signet-dashboard/public/index.html b/signet-dashboard/public/index.html new file mode 100644 index 0000000..2848c46 --- /dev/null +++ b/signet-dashboard/public/index.html @@ -0,0 +1,105 @@ + + + + + + Bitcoin Signet - Dashboard de Supervision + + + +
+
+

Bitcoin Signet - Dashboard de Supervision

+

Surveillance de la blockchain et outils de test

+
+ +
+ +
+

État de la Blockchain

+
+
+

Hauteur de Bloc

+

-

+
+
+

Dernier Bloc Miné

+

-

+
+
+

Transactions dans le Dernier Bloc

+

-

+
+
+

Balance Mature

+

-

+
+
+

Balance Immature

+

-

+
+
+

Nombre d'Ancrages

+

-

+
+
+

Nombre de Pairs

+

-

+
+
+
+ + +
+

Test de l'API d'Ancrage

+
+
+ + +
+ +
+ + + +
+ +
+ + +
+ +
+ +
+ + + +
+ +
+
+
+ + +
+

Faucet Bitcoin Signet

+
+

Recevez 50 000 sats (0.0005 BTC) sur votre adresse Bitcoin Signet

+ + + +
+
+
+
+ +
+

Bitcoin Signet Dashboard - Équipe 4NK

+

Dernière mise à jour : -

+
+
+ + + + diff --git a/signet-dashboard/public/styles.css b/signet-dashboard/public/styles.css new file mode 100644 index 0000000..4c86890 --- /dev/null +++ b/signet-dashboard/public/styles.css @@ -0,0 +1,267 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #f7931a; + --secondary-color: #1a1a1a; + --background-color: #f5f5f5; + --card-background: #ffffff; + --text-color: #333333; + --border-color: #e0e0e0; + --success-color: #28a745; + --error-color: #dc3545; + --warning-color: #ffc107; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background-color: var(--background-color); + color: var(--text-color); + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +header { + text-align: center; + margin-bottom: 40px; + padding: 30px 0; + background: linear-gradient(135deg, var(--primary-color), #ff6b35); + color: white; + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +header h1 { + font-size: 2.5em; + margin-bottom: 10px; +} + +.subtitle { + font-size: 1.2em; + opacity: 0.9; +} + +main { + margin-bottom: 40px; +} + +section { + margin-bottom: 40px; +} + +section h2 { + font-size: 1.8em; + margin-bottom: 20px; + color: var(--secondary-color); + border-bottom: 3px solid var(--primary-color); + padding-bottom: 10px; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.card { + background: var(--card-background); + border-radius: 10px; + padding: 25px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.card h3 { + font-size: 1.1em; + margin-bottom: 15px; + color: var(--secondary-color); + font-weight: 600; +} + +.value { + font-size: 2em; + font-weight: bold; + color: var(--primary-color); + word-break: break-all; +} + +.tabs { + display: flex; + gap: 10px; + margin-bottom: 20px; + border-bottom: 2px solid var(--border-color); +} + +.tab-button { + padding: 10px 20px; + background: none; + border: none; + cursor: pointer; + font-size: 1em; + color: var(--text-color); + border-bottom: 3px solid transparent; + transition: all 0.3s; +} + +.tab-button:hover { + color: var(--primary-color); +} + +.tab-button.active { + color: var(--primary-color); + border-bottom-color: var(--primary-color); + font-weight: 600; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: var(--secondary-color); +} + +input[type="text"], +input[type="file"], +textarea { + width: 100%; + padding: 12px; + border: 2px solid var(--border-color); + border-radius: 5px; + font-size: 1em; + margin-bottom: 15px; + transition: border-color 0.3s; +} + +input[type="text"]:focus, +textarea:focus { + outline: none; + border-color: var(--primary-color); +} + +textarea { + resize: vertical; + min-height: 100px; + font-family: monospace; +} + +button { + background-color: var(--primary-color); + color: white; + border: none; + padding: 12px 24px; + border-radius: 5px; + font-size: 1em; + cursor: pointer; + transition: background-color 0.3s; + font-weight: 600; +} + +button:hover { + background-color: #e8840a; +} + +button:disabled { + background-color: #ccc; + cursor: not-allowed; +} + +.hash-section { + margin-top: 20px; + padding-top: 20px; + border-top: 2px solid var(--border-color); +} + +.file-info { + margin: 15px 0; + padding: 10px; + background-color: var(--background-color); + border-radius: 5px; + font-size: 0.9em; +} + +.result { + margin-top: 20px; + padding: 15px; + border-radius: 5px; + display: none; +} + +.result.success { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; + display: block; +} + +.result.error { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; + display: block; +} + +.result.info { + background-color: #d1ecf1; + color: #0c5460; + border: 1px solid #bee5eb; + display: block; +} + +footer { + text-align: center; + padding: 20px; + background-color: var(--card-background); + border-radius: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + color: #666; +} + +.loading { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: white; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +@media (max-width: 768px) { + .grid { + grid-template-columns: 1fr; + } + + header h1 { + font-size: 2em; + } + + .container { + padding: 10px; + } +} diff --git a/signet-dashboard/src/bitcoin-rpc.js b/signet-dashboard/src/bitcoin-rpc.js new file mode 100644 index 0000000..b972e46 --- /dev/null +++ b/signet-dashboard/src/bitcoin-rpc.js @@ -0,0 +1,199 @@ +/** + * Client Bitcoin RPC pour le dashboard + * + * Gère la connexion et les appels RPC vers le nœud Bitcoin Signet + */ + +import Client from 'bitcoin-core'; +import { logger } from './logger.js'; + +class BitcoinRPC { + constructor() { + this.client = new Client({ + host: process.env.BITCOIN_RPC_HOST || 'localhost', + port: parseInt(process.env.BITCOIN_RPC_PORT || '38332'), + username: process.env.BITCOIN_RPC_USER || 'bitcoin', + password: process.env.BITCOIN_RPC_PASSWORD || 'bitcoin', + timeout: parseInt(process.env.BITCOIN_RPC_TIMEOUT || '30000'), + }); + } + + /** + * Obtient les informations sur la blockchain + * @returns {Promise} Informations sur la blockchain + */ + async getBlockchainInfo() { + try { + return await this.client.getBlockchainInfo(); + } catch (error) { + logger.error('Error getting blockchain info', { error: error.message }); + throw new Error(`Failed to get blockchain info: ${error.message}`); + } + } + + /** + * Obtient le dernier bloc miné + * @returns {Promise} Informations sur le dernier bloc + */ + async getLatestBlock() { + try { + const blockchainInfo = await this.client.getBlockchainInfo(); + const bestBlockHash = blockchainInfo.bestblockhash; + const block = await this.client.getBlock(bestBlockHash, 2); // Verbose level 2 + + return { + hash: block.hash, + height: block.height, + time: block.time, + mediantime: block.mediantime, + tx_count: block.tx ? block.tx.length : 0, + size: block.size, + weight: block.weight, + }; + } catch (error) { + logger.error('Error getting latest block', { error: error.message }); + throw new Error(`Failed to get latest block: ${error.message}`); + } + } + + /** + * Obtient le solde du wallet (mature et immature) + * @returns {Promise} Solde du wallet + */ + async getWalletBalance() { + try { + // Utiliser command() pour appeler directement getbalances() qui retourne mature, immature, et unconfirmed + // Si getbalances() n'est pas disponible, utiliser getBalance() avec différents minconf + let balances; + try { + // Utiliser command() pour appeler getbalances() directement (méthode RPC de Bitcoin Core) + balances = await this.client.command('getbalances'); + // getbalances() retourne { "mine": { "trusted": ..., "untrusted_pending": ..., "immature": ... } } + const mine = balances.mine || {}; + return { + mature: mine.trusted || 0, + immature: mine.immature || 0, + unconfirmed: mine.untrusted_pending || 0, + total: (mine.trusted || 0) + (mine.immature || 0) + (mine.untrusted_pending || 0), + }; + } catch (error) { + // Fallback si getbalances() n'est pas disponible + logger.debug('getbalances() not available, using getBalance()', { error: error.message }); + + // getBalance() retourne le solde mature (confirmé avec au moins 1 confirmation) + const balance = await this.client.getBalance(); + + // getBalance avec minconf=0 retourne le solde total (mature + immature) + const totalBalance = await this.client.getBalance('*', 0); + + // Calculer le solde immature + const immatureBalance = Math.max(0, totalBalance - balance); + + // Obtenir les transactions non confirmées depuis listUnspent + let unconfirmedBalance = 0; + try { + const unspent = await this.client.listUnspent(0); // 0 = inclure les non confirmés + for (const utxo of unspent) { + if (utxo.confirmations === 0) { + unconfirmedBalance += utxo.amount; + } + } + } catch (error) { + // Si listUnspent échoue, unconfirmedBalance reste à 0 + logger.debug('Could not get unconfirmed balance', { error: error.message }); + } + + return { + mature: balance, + immature: immatureBalance, + unconfirmed: unconfirmedBalance, + total: totalBalance + unconfirmedBalance, + }; + } + } catch (error) { + logger.error('Error getting wallet balance', { error: error.message }); + throw new Error(`Failed to get wallet balance: ${error.message}`); + } + } + + /** + * Obtient le nombre de pairs connectés + * @returns {Promise} Informations sur les pairs + */ + async getNetworkPeers() { + try { + const networkInfo = await this.client.getNetworkInfo(); + const peerInfo = await this.client.getPeerInfo(); + + return { + connections: networkInfo.connections, + peers: peerInfo.map(peer => ({ + addr: peer.addr, + services: peer.services, + version: peer.version, + subver: peer.subver, + })), + }; + } catch (error) { + logger.error('Error getting network peers', { error: error.message }); + throw new Error(`Failed to get network peers: ${error.message}`); + } + } + + /** + * Obtient le nombre d'ancrages (approximatif en comptant les transactions OP_RETURN) + * @returns {Promise} Nombre d'ancrages + */ + async getAnchorCount() { + try { + const blockchainInfo = await this.client.getBlockchainInfo(); + const currentHeight = blockchainInfo.blocks; + + // Rechercher dans les 1000 derniers blocs pour compter les ancrages + let anchorCount = 0; + const searchRange = Math.min(1000, currentHeight + 1); + + for (let height = currentHeight; height >= Math.max(0, currentHeight - searchRange); height--) { + try { + const blockHash = await this.client.getBlockHash(height); + const block = await this.client.getBlock(blockHash, 2); + + if (block.tx) { + for (const tx of block.tx) { + try { + const rawTx = await this.client.getRawTransaction(tx.txid, true); + + // Vérifier si la transaction contient un OP_RETURN avec "ANCHOR:" + for (const output of rawTx.vout || []) { + if (output.scriptPubKey && output.scriptPubKey.hex) { + const scriptHex = output.scriptPubKey.hex; + const anchorPrefix = Buffer.from('ANCHOR:', 'utf8').toString('hex'); + + if (scriptHex.includes(anchorPrefix)) { + anchorCount++; + break; // Compter une seule fois par transaction + } + } + } + } catch (error) { + // Continuer avec la transaction suivante + logger.debug('Error checking transaction for anchor', { txid: tx.txid, error: error.message }); + } + } + } + } catch (error) { + // Continuer avec le bloc suivant + logger.debug('Error checking block for anchors', { height, error: error.message }); + } + } + + return anchorCount; + } catch (error) { + logger.error('Error getting anchor count', { error: error.message }); + throw new Error(`Failed to get anchor count: ${error.message}`); + } + } +} + +// Export singleton +export const bitcoinRPC = new BitcoinRPC(); diff --git a/signet-dashboard/src/logger.js b/signet-dashboard/src/logger.js new file mode 100644 index 0000000..ded9369 --- /dev/null +++ b/signet-dashboard/src/logger.js @@ -0,0 +1,30 @@ +/** + * Logger simple pour le dashboard + */ + +const LOG_LEVEL = process.env.LOG_LEVEL || 'info'; + +const levels = { + error: 0, + warn: 1, + info: 2, + debug: 3, +}; + +function log(level, message, meta = {}) { + const levelNum = levels[level] || 2; + const currentLevel = levels[LOG_LEVEL] || 2; + + if (levelNum <= currentLevel) { + const timestamp = new Date().toISOString(); + const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : ''; + console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`); + } +} + +export const logger = { + error: (message, meta) => log('error', message, meta), + warn: (message, meta) => log('warn', message, meta), + info: (message, meta) => log('info', message, meta), + debug: (message, meta) => log('debug', message, meta), +}; diff --git a/signet-dashboard/src/server.js b/signet-dashboard/src/server.js new file mode 100644 index 0000000..7788b82 --- /dev/null +++ b/signet-dashboard/src/server.js @@ -0,0 +1,208 @@ +#!/usr/bin/env node + +/** + * Serveur web de supervision et faucet Bitcoin Signet + * + * Ce serveur fournit une interface web pour : + * - Superviser l'état de la blockchain Signet + * - Tester l'API d'ancrage + * - Utiliser le faucet pour recevoir des sats + * + * Port: 3020 + */ + +import express from 'express'; +import cors from 'cors'; +import dotenv from 'dotenv'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import crypto from 'crypto'; +import { bitcoinRPC } from './bitcoin-rpc.js'; +import { logger } from './logger.js'; + +// Charger les variables d'environnement +dotenv.config(); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const app = express(); +const PORT = process.env.DASHBOARD_PORT || 3020; +const HOST = process.env.DASHBOARD_HOST || '0.0.0.0'; + +// Middleware +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// Servir les fichiers statiques +app.use(express.static(join(__dirname, '../public'))); + +// Middleware de logging +app.use((req, res, next) => { + logger.info(`${req.method} ${req.path}`, { + ip: req.ip, + userAgent: req.get('user-agent'), + }); + next(); +}); + +// Route pour la page principale +app.get('/', (req, res) => { + res.sendFile(join(__dirname, '../public/index.html')); +}); + +// API Routes +app.get('/api/blockchain/info', async (req, res) => { + try { + const blockchainInfo = await bitcoinRPC.getBlockchainInfo(); + res.json(blockchainInfo); + } catch (error) { + logger.error('Error getting blockchain info', { error: error.message }); + res.status(500).json({ error: error.message }); + } +}); + +app.get('/api/blockchain/latest-block', async (req, res) => { + try { + const latestBlock = await bitcoinRPC.getLatestBlock(); + res.json(latestBlock); + } catch (error) { + logger.error('Error getting latest block', { error: error.message }); + res.status(500).json({ error: error.message }); + } +}); + +app.get('/api/wallet/balance', async (req, res) => { + try { + const balance = await bitcoinRPC.getWalletBalance(); + res.json(balance); + } catch (error) { + logger.error('Error getting wallet balance', { error: error.message }); + res.status(500).json({ error: error.message }); + } +}); + +app.get('/api/network/peers', async (req, res) => { + try { + const peers = await bitcoinRPC.getNetworkPeers(); + res.json(peers); + } catch (error) { + logger.error('Error getting network peers', { error: error.message }); + res.status(500).json({ error: error.message }); + } +}); + +app.get('/api/anchor/count', async (req, res) => { + try { + const count = await bitcoinRPC.getAnchorCount(); + res.json({ count }); + } catch (error) { + logger.error('Error getting anchor count', { error: error.message }); + res.status(500).json({ error: error.message }); + } +}); + +// Route pour générer un hash SHA256 +app.post('/api/hash/generate', (req, res) => { + try { + const { text, fileContent } = req.body; + + if (!text && !fileContent) { + return res.status(400).json({ error: 'text or fileContent is required' }); + } + + const content = text || fileContent; + const hash = crypto.createHash('sha256').update(content, 'utf8').digest('hex'); + + res.json({ hash }); + } catch (error) { + logger.error('Error generating hash', { error: error.message }); + res.status(500).json({ error: error.message }); + } +}); + +// Route pour tester l'ancrage (appelle l'API d'ancrage externe) +app.post('/api/anchor/test', async (req, res) => { + try { + const { hash } = req.body; + + if (!hash) { + return res.status(400).json({ error: 'hash is required' }); + } + + if (!/^[0-9a-fA-F]{64}$/.test(hash)) { + return res.status(400).json({ error: 'hash must be a 64 character hexadecimal string' }); + } + + // Utiliser l'URL HTTPS du sous-domaine si disponible, sinon localhost + const anchorApiUrl = process.env.ANCHOR_API_URL || + (process.env.NODE_ENV === 'production' + ? 'https://anchorage.certificator.4nkweb.com' + : 'http://localhost:3010'); + const anchorApiKey = process.env.ANCHOR_API_KEY || ''; + + const response = await fetch(`${anchorApiUrl}/api/anchor/document`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': anchorApiKey, + }, + body: JSON.stringify({ hash }), + }); + + const result = await response.json(); + + if (!response.ok) { + return res.status(response.status).json(result); + } + + res.json(result); + } catch (error) { + logger.error('Error testing anchor', { error: error.message }); + res.status(500).json({ error: error.message }); + } +}); + +// Gestion des erreurs +app.use((err, req, res, next) => { + logger.error('Unhandled error', { error: err.message, stack: err.stack }); + res.status(500).json({ + error: 'Internal Server Error', + message: process.env.NODE_ENV === 'development' ? err.message : 'An error occurred', + }); +}); + +// Gestion des routes non trouvées +app.use((req, res) => { + res.status(404).json({ + error: 'Not Found', + message: `Route ${req.method} ${req.path} not found`, + }); +}); + +// Démarrage du serveur +const server = app.listen(PORT, HOST, () => { + logger.info(`Dashboard Bitcoin Signet démarré`, { + host: HOST, + port: PORT, + environment: process.env.NODE_ENV || 'production', + }); +}); + +// Gestion de l'arrêt propre +process.on('SIGTERM', () => { + logger.info('SIGTERM received, shutting down gracefully'); + server.close(() => { + logger.info('Server closed'); + process.exit(0); + }); +}); + +process.on('SIGINT', () => { + logger.info('SIGINT received, shutting down gracefully'); + server.close(() => { + logger.info('Server closed'); + process.exit(0); + }); +}); diff --git a/signet-dashboard/start.sh b/signet-dashboard/start.sh new file mode 100755 index 0000000..a44198e --- /dev/null +++ b/signet-dashboard/start.sh @@ -0,0 +1,21 @@ +#!/bin/bash +cd "$(dirname "$0")" +export BITCOIN_RPC_HOST=${BITCOIN_RPC_HOST:-localhost} +export BITCOIN_RPC_PORT=${BITCOIN_RPC_PORT:-38332} +export BITCOIN_RPC_USER=${BITCOIN_RPC_USER:-bitcoin} +export BITCOIN_RPC_PASSWORD=${BITCOIN_RPC_PASSWORD:-bitcoin} +export DASHBOARD_PORT=${DASHBOARD_PORT:-3020} +export DASHBOARD_HOST=${DASHBOARD_HOST:-0.0.0.0} +export ANCHOR_API_URL=${ANCHOR_API_URL:-http://localhost:3010} +export ANCHOR_API_KEY=${ANCHOR_API_KEY:-} +export FAUCET_API_URL=${FAUCET_API_URL:-http://localhost:3021} +export LOG_LEVEL=${LOG_LEVEL:-info} +export NODE_ENV=${NODE_ENV:-production} + +if [ ! -f node_modules/.bin/node ]; then + echo "Installation des dépendances..." + npm install +fi + +echo "Démarrage du Dashboard sur le port $DASHBOARD_PORT..." +node src/server.js diff --git a/update-signet.sh b/update-signet.sh new file mode 100755 index 0000000..c75fac3 --- /dev/null +++ b/update-signet.sh @@ -0,0 +1,275 @@ +#!/bin/bash +set -euo pipefail + +# Script de mise à jour du Bitcoin Signet Custom +# Auteur: Équipe 4NK +# Date: 2026-01-09 + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Couleurs pour les messages +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Fonction d'affichage +info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Vérification des prérequis +check_prerequisites() { + info "Vérification des prérequis..." + + if ! command -v docker &> /dev/null; then + error "Docker n'est pas installé" + exit 1 + fi + + if ! sudo docker ps &> /dev/null; then + error "Impossible d'accéder à Docker. Vérifiez les permissions." + exit 1 + fi + + if [ ! -f ".env" ]; then + error "Le fichier .env n'existe pas" + exit 1 + fi + + success "Prérequis vérifiés" +} + +# Récupération de la version actuelle +get_current_version() { + if [ -f ".bitcoin-version" ]; then + cat .bitcoin-version + else + grep -oP 'BITCOIN_VERSION=\$\{BITCOIN_VERSION:-?\K[0-9]+\.[0-9]+' Dockerfile | head -1 || echo "unknown" + fi +} + +# Récupération de la dernière version disponible +get_latest_version() { + local version + + # Essayer plusieurs méthodes pour récupérer la version + version=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest 2>/dev/null | \ + grep -oP '"tag_name": "\Kv?[0-9]+\.[0-9]+\.[0-9]+' | head -1 | sed 's/^v//') + + if [ -z "$version" ]; then + version=$(curl -s https://bitcoincore.org/bin/ 2>/dev/null | \ + grep -oP 'bitcoin-core-\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1) + fi + + if [ -z "$version" ]; then + # Version par défaut si aucune méthode ne fonctionne + version="30.2" + warning "Impossible de récupérer la version automatiquement, utilisation de la version par défaut: $version" + fi + + echo "$version" +} + +# Sauvegarde avant mise à jour +backup_data() { + info "Sauvegarde des données avant mise à jour..." + + local backup_dir="backups" + local backup_file="${backup_dir}/signet-backup-$(date +%Y%m%d-%H%M%S).tar.gz" + + mkdir -p "$backup_dir" + + if sudo docker ps | grep -q bitcoin-signet-instance; then + info "Sauvegarde des données du conteneur..." + sudo docker exec bitcoin-signet-instance tar czf /tmp/bitcoin-backup.tar.gz /root/.bitcoin/ 2>/dev/null || true + sudo docker cp bitcoin-signet-instance:/tmp/bitcoin-backup.tar.gz "$backup_file" 2>/dev/null || true + fi + + # Sauvegarder le .env + cp .env "${backup_dir}/.env.backup-$(date +%Y%m%d-%H%M%S)" + + success "Sauvegarde effectuée: $backup_file" +} + +# Mise à jour du Dockerfile +update_dockerfile() { + local new_version=$1 + + info "Mise à jour du Dockerfile avec Bitcoin Core $new_version..." + + # Mettre à jour la version dans le Dockerfile + sed -i "s/ARG BITCOIN_VERSION=\${BITCOIN_VERSION:-[0-9]\+\.[0-9]\+}/ARG BITCOIN_VERSION=\${BITCOIN_VERSION:-${new_version}}/" Dockerfile + + success "Dockerfile mis à jour" +} + +# Reconstruction de l'image +rebuild_image() { + info "Reconstruction de l'image Docker..." + + if sudo docker build -t bitcoin-signet .; then + success "Image reconstruite avec succès" + else + error "Échec de la reconstruction de l'image" + exit 1 + fi +} + +# Redémarrage du conteneur +restart_container() { + info "Redémarrage du conteneur..." + + # Arrêter le conteneur existant + if sudo docker ps | grep -q bitcoin-signet-instance; then + info "Arrêt du conteneur existant..." + sudo docker stop bitcoin-signet-instance + sudo docker rm bitcoin-signet-instance + fi + + # Démarrer le nouveau conteneur + info "Démarrage du nouveau conteneur..." + sudo docker run --env-file .env -d \ + --name bitcoin-signet-instance \ + -p 38332:38332 \ + -p 38333:38333 \ + -p 28332:28332 \ + -p 28333:28333 \ + -p 28334:28334 \ + bitcoin-signet + + success "Conteneur redémarré" +} + +# Vérification post-mise à jour +verify_update() { + info "Vérification de la mise à jour..." + + sleep 10 + + # Vérifier que le conteneur est en cours d'exécution + if ! sudo docker ps | grep -q bitcoin-signet-instance; then + error "Le conteneur n'est pas en cours d'exécution" + return 1 + fi + + # Vérifier la version de Bitcoin + local version_info + version_info=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin -version 2>/dev/null || \ + sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getnetworkinfo 2>/dev/null | grep -oP '"subversion": "\K[^"]+' || echo "unknown") + + if [ "$version_info" != "unknown" ]; then + success "Version Bitcoin détectée: $version_info" + else + warning "Impossible de vérifier la version, mais le conteneur fonctionne" + fi + + # Vérifier l'état de la blockchain + local blockchain_info + blockchain_info=$(sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo 2>/dev/null | grep -oP '"chain": "\K[^"]+' || echo "unknown") + + if [ "$blockchain_info" = "signet" ]; then + success "Blockchain signet opérationnelle" + else + warning "État de la blockchain: $blockchain_info" + fi +} + +# Fonction principale +main() { + local target_version="${1:-}" + local current_version + local latest_version + + echo "==========================================" + echo " Mise à jour du Bitcoin Signet Custom" + echo "==========================================" + echo "" + + check_prerequisites + + current_version=$(get_current_version) + info "Version actuelle: $current_version" + + if [ -n "$target_version" ]; then + latest_version="$target_version" + info "Version cible spécifiée: $latest_version" + else + info "Récupération de la dernière version disponible..." + latest_version=$(get_latest_version) + info "Dernière version disponible: $latest_version" + fi + + if [ "$current_version" = "$latest_version" ]; then + success "Déjà à jour (version $current_version)" + exit 0 + fi + + echo "" + warning "ATTENTION: Cette mise à jour va:" + warning " 1. Arrêter le conteneur actuel" + warning " 2. Reconstruire l'image Docker" + warning " 3. Redémarrer le conteneur" + echo "" + read -p "Continuer? (o/N): " -n 1 -r + echo "" + + if [[ ! $REPLY =~ ^[OoYy]$ ]]; then + info "Mise à jour annulée" + exit 0 + fi + + echo "" + backup_data + echo "" + update_dockerfile "$latest_version" + echo "" + rebuild_image + echo "" + restart_container + echo "" + verify_update + echo "" + + # Enregistrer la nouvelle version + echo "$latest_version" > .bitcoin-version + success "Version enregistrée: $latest_version" + + echo "" + success "Mise à jour terminée avec succès!" + info "Pour voir les logs: sudo docker logs -f bitcoin-signet-instance" + info "Pour vérifier l'état: sudo docker exec bitcoin-signet-instance bitcoin-cli -datadir=/root/.bitcoin getblockchaininfo" +} + +# Gestion des arguments +if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then + echo "Usage: $0 [VERSION]" + echo "" + echo "Mise à jour du Bitcoin Signet Custom" + echo "" + echo "Arguments:" + echo " VERSION Version spécifique de Bitcoin Core (ex: 27.0)" + echo " Si non spécifié, utilise la dernière version disponible" + echo "" + echo "Options:" + echo " -h, --help Affiche cette aide" + echo "" + exit 0 +fi + +# Exécution du script principal +main "$@"