ncantu 937646cc45 Daily backup to git cron, backup/restore scripts, docs
**Motivations:**
- Export Signet and mining wallet backups to git with only 2 versions kept
- Document and add backup/restore scripts for signet and mining wallet

**Correctifs:**
- Backup-to-git uses SSH URL for passwordless cron; copy timestamped files only; prune to 2 versions; remove *-latest from backup repo

**Evolutions:**
- data/backup-to-git-cron.sh: daily export to git.4nkweb.com/4nk/backup
- save-signet-datadir-backup.sh, restore-signet-from-backup.sh, export-mining-wallet.sh, import-mining-wallet.sh
- features/backup-to-git-daily-cron.md, docs/MAINTENANCE.md backup section
- .gitignore: data/backup-to-git.log

**Pages affectées:**
- .gitignore, data/backup-to-git-cron.sh, docs/MAINTENANCE.md, features/backup-to-git-daily-cron.md
- save-signet-datadir-backup.sh, restore-signet-from-backup.sh, export-mining-wallet.sh, import-mining-wallet.sh
- Plus autres fichiers modifiés ou non suivis déjà présents dans le working tree
2026-02-04 03:07:57 +01:00

1892 lines
82 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documentation API d'Ancrage - Bitcoin Ancrage</title>
<link rel="stylesheet" href="styles.css">
<style>
.api-docs-section {
margin-bottom: 40px;
}
.endpoint-card {
background: var(--card-background);
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.endpoint-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid var(--border-color);
}
.method-badge {
display: inline-block;
padding: 5px 15px;
border-radius: 5px;
font-weight: bold;
font-size: 0.9em;
margin-right: 15px;
min-width: 70px;
text-align: center;
}
.method-get {
background: #28a745;
color: white;
}
.method-post {
background: #007bff;
color: white;
}
.endpoint-path {
font-family: 'Courier New', monospace;
font-size: 1.2em;
color: var(--primary-color);
font-weight: bold;
}
.endpoint-description {
margin: 20px 0;
color: var(--text-color);
line-height: 1.6;
}
.endpoint-params {
margin: 20px 0;
}
.endpoint-params h4 {
color: var(--primary-color);
margin-bottom: 10px;
}
.param-table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
.param-table th,
.param-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.param-table th {
background: var(--card-background);
font-weight: bold;
color: var(--text-color);
}
.param-name {
font-family: 'Courier New', monospace;
color: var(--primary-color);
font-weight: bold;
}
.param-required {
color: var(--error-color);
font-weight: bold;
}
.param-optional {
color: var(--text-color);
}
.code-block {
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
border-radius: 5px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
line-height: 1.6;
overflow-x: auto;
margin: 20px 0;
}
.code-block pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
.response-example {
margin: 20px 0;
}
.response-example h4 {
color: var(--primary-color);
margin-bottom: 10px;
}
.info-box {
background: rgba(13, 202, 240, 0.15);
border-left: 4px solid #0dcaf0;
padding: 15px;
margin: 20px 0;
border-radius: 5px;
color: var(--text-color);
}
.warning-box {
background: rgba(255, 193, 7, 0.15);
border-left: 4px solid #ffc107;
padding: 15px;
margin: 20px 0;
border-radius: 5px;
color: var(--text-color);
}
.error-box {
background: rgba(220, 53, 69, 0.2);
border-left: 4px solid #dc3545;
padding: 15px;
margin: 20px 0;
border-radius: 5px;
color: #ff6b6b;
}
.auth-section {
background: var(--card-background);
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.copy-button {
background: var(--primary-color);
color: white;
border: none;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 0.9em;
margin-top: 10px;
transition: background 0.3s;
}
.copy-button:hover {
background: #e0820d;
}
.status-code {
display: inline-block;
padding: 3px 8px;
border-radius: 3px;
font-size: 0.85em;
font-weight: bold;
margin-right: 10px;
}
.status-200 {
background: #28a745;
color: white;
}
.status-400 {
background: #ffc107;
color: var(--background-color);
}
.status-401 {
background: #dc3545;
color: white;
}
.status-402 {
background: #ff9800;
color: white;
}
.status-500 {
background: #dc3545;
color: white;
}
.status-503 {
background: #ff9800;
color: white;
}
</style>
</head>
<body>
<div class="container">
<a href="/" class="back-link">← Retour au Dashboard</a>
<header>
<h1>Documentation API d'Ancrage</h1>
<p class="subtitle">API REST pour ancrer et vérifier des documents sur Bitcoin Signet, et utiliser le faucet</p>
</header>
<main>
<!-- Section Authentification -->
<section class="api-docs-section">
<div class="auth-section">
<h2>🔐 Authentification</h2>
<p>Toutes les requêtes vers l'API d'ancrage (sauf les endpoints publics) nécessitent une clé API dans le header <code>X-API-Key</code>.</p>
<div class="code-block">
<pre>X-API-Key: votre-clé-api-ici</pre>
</div>
<div class="info-box">
<p><strong>Endpoints publics (sans authentification) :</strong></p>
<ul style="margin-left: 20px; margin-top: 10px;">
<li><code>GET /health</code> - Vérification de santé (API d'ancrage)</li>
<li><code>GET /health/detailed</code> - Vérification de santé détaillée avec état mutex et UTXOs</li>
<li><code>GET /api/anchor/locked-utxos</code> - Liste des UTXO verrouillés</li>
<li><code>GET /health</code> - Vérification de santé (API faucet)</li>
</ul>
<p style="margin-top: 10px;"><strong>Endpoints nécessitant une clé API :</strong></p>
<ul style="margin-left: 20px; margin-top: 10px;">
<li><code>POST /api/anchor/document</code> - Ancrer un document</li>
<li><code>POST /api/anchor/verify</code> - Vérifier un hash</li>
<li><code>POST /api/faucet/request</code> - Demander des sats</li>
<li><code>POST /api/watermark/document</code> - Ajouter un filigrane et ancrer un document</li>
</ul>
</div>
<div class="warning-box">
<p><strong>⚠️ Important :</strong> Conservez votre clé API secrète et ne la partagez jamais publiquement.</p>
</div>
</div>
</section>
<!-- Endpoint: Health -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/health</span>
</div>
<div class="endpoint-description">
<p>Vérifie l'état de santé de l'API. Cet endpoint est public et ne nécessite pas d'authentification.</p>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"ok": true,
"service": "anchor-api",
"bitcoin": {
"connected": true,
"blocks": 12345
},
"timestamp": "2026-01-25T12:00:00.000Z"
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (503 Service Unavailable) - Bitcoin non connecté</h4>
<div class="code-block">
<pre>{
"ok": false,
"service": "anchor-api",
"error": "Bitcoin RPC connection failed",
"timestamp": "2026-01-25T12:00:00.000Z"
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Health Detailed -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/health/detailed</span>
</div>
<div class="endpoint-description">
<p>Vérifie l'état détaillé de l'API, incluant l'état du mutex, des UTXOs verrouillés et de la connexion Bitcoin. Cet endpoint est public et ne nécessite pas d'authentification.</p>
</div>
<div class="response-example">
<h4>Réponse (200 OK) - Tout fonctionne</h4>
<div class="code-block">
<pre>{
"ok": true,
"service": "anchor-api",
"mutex": {
"locked": false,
"waiting": 0,
"timeout": 180000
},
"utxos": {
"locked": 0,
"locked_since": null,
"stale_locks": 0,
"stale_locks_details": []
},
"bitcoin": {
"connected": true,
"blocks": 12345,
"chain": "signet",
"rpc_timeout": 60000
},
"timestamp": "2026-01-25T12:00:00.000Z"
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (200 OK) - Warning (UTXOs verrouillés mais < 10 min)</h4>
<div class="code-block">
<pre>{
"ok": true,
"service": "anchor-api",
"mutex": {
"locked": false,
"waiting": 0,
"timeout": 180000
},
"utxos": {
"locked": 3,
"locked_since": "2026-01-25T11:55:00.000Z",
"stale_locks": 0,
"stale_locks_details": []
},
"bitcoin": {
"connected": true,
"blocks": 12345,
"chain": "signet",
"rpc_timeout": 60000
},
"timestamp": "2026-01-25T12:00:00.000Z"
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (503 Service Unavailable) - UTXOs verrouillés depuis > 10 min</h4>
<div class="code-block">
<pre>{
"ok": false,
"service": "anchor-api",
"mutex": {
"locked": false,
"waiting": 0,
"timeout": 180000
},
"utxos": {
"locked": 5,
"locked_since": "2026-01-25T11:45:00.000Z",
"stale_locks": 5,
"stale_locks_details": [
{
"txid": "abc123...",
"vout": 0,
"minutes_locked": 15.5
}
]
},
"bitcoin": {
"connected": true,
"blocks": 12345,
"chain": "signet",
"rpc_timeout": 60000
},
"timestamp": "2026-01-25T12:00:00.000Z"
}</pre>
</div>
</div>
<div class="info-box">
<p><strong>Codes de statut :</strong></p>
<ul style="margin-left: 20px; margin-top: 10px;">
<li><code>200</code> - Tout fonctionne correctement</li>
<li><code>200</code> - Warning si 5-10 UTXOs verrouillés (mais < 10 min)</li>
<li><code>503</code> - Service Unavailable si UTXOs verrouillés depuis > 10 min ou > 10 UTXOs verrouillés</li>
<li><code>503</code> - Service Unavailable si Bitcoin non connecté</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: Anchor Document -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/anchor/document</span>
</div>
<div class="endpoint-description">
<p>Ancre un document sur la blockchain Bitcoin Signet en créant une transaction qui inclut le hash du document dans un OP_RETURN.</p>
</div>
<div class="endpoint-params">
<h4>Paramètres (Body JSON)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">hash</td>
<td>string</td>
<td><span class="param-required">Oui</span></td>
<td>Hash SHA256 du document en hexadécimal (64 caractères)</td>
</tr>
<tr>
<td class="param-name">documentUid</td>
<td>string</td>
<td><span class="param-optional">Non</span></td>
<td>Identifiant optionnel du document (pour le logging)</td>
</tr>
<tr>
<td class="param-name">skipIfExists</td>
<td>boolean</td>
<td><span class="param-optional">Non</span></td>
<td>Si <code>true</code>, ne réancrera pas un hash déjà ancré ; retourne les infos existantes en base avec <code>old: true</code>. Défaut <code>false</code>.</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X POST https://certificator.4nkweb.com/api/anchor/document \
-H "Content-Type: application/json" \
-H "X-API-Key: votre-clé-api" \
-d '{
"hash": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
"documentUid": "doc-12345",
"skipIfExists": true
}'</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK) nouvelle transaction</h4>
<div class="code-block">
<pre>{
"ok": true,
"txid": "abc123def456...",
"status": "pending",
"confirmations": 0,
"block_height": null,
"outputs": [...],
"fee": 0.000012,
"fee_sats": 1200,
"old": false
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (200 OK) hash déjà ancré (<code>skipIfExists: true</code>)</h4>
<div class="code-block">
<pre>{
"ok": true,
"txid": "abc123def456...",
"status": "confirmed",
"confirmations": 42,
"block_height": 12345,
"old": true
}</pre>
</div>
<p>Aucune transaction n'est créée ; les données viennent de la base. <code>old: true</code> indique un ancrage préexistant.</p>
</div>
<div class="response-example">
<h4>Codes de statut possibles</h4>
<ul style="margin-left: 20px;">
<li><span class="status-code status-200">200</span> Succès transaction créée et envoyée au mempool (<code>old: false</code>), ou hash déjà ancré avec <code>skipIfExists: true</code> (<code>old: true</code>)</li>
<li><span class="status-code status-400">400</span> Requête invalide - Hash manquant ou format incorrect</li>
<li><span class="status-code status-401">401</span> Non autorisé - Clé API manquante ou invalide</li>
<li><span class="status-code status-402">402</span> Solde insuffisant - Pas assez de fonds pour créer la transaction</li>
<li><span class="status-code status-503">503</span> Service indisponible - Erreur "too-long-mempool-chain" (trop d'ancêtres non confirmés)</li>
<li><span class="status-code status-500">500</span> Erreur serveur - Erreur interne lors de la création de la transaction</li>
</ul>
</div>
<div class="error-box">
<h4>Exemple d'erreur (402 Payment Required)</h4>
<div class="code-block">
<pre>{
"error": "Insufficient Balance",
"message": "Insufficient balance to create anchor transaction"
}</pre>
</div>
</div>
<div class="error-box">
<h4>Exemple d'erreur (503 Service Unavailable) - Too Long Mempool Chain</h4>
<div class="code-block">
<pre>{
"error": "Service Unavailable",
"message": "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]"
}</pre>
</div>
<p style="margin-top: 10px;">Cette erreur se produit lorsque l'API tente d'utiliser un UTXO non confirmé qui a trop d'ancêtres non confirmés dans le mempool. Bitcoin Core limite la chaîne d'ancêtres à 25 transactions pour éviter les attaques par spam.</p>
<p><strong>Solution :</strong> L'API utilise maintenant uniquement des UTXOs confirmés (au moins 1 confirmation) pour éviter cette erreur. Attendez qu'un bloc soit miné pour que les UTXOs soient confirmés.</p>
</div>
<div class="info-box">
<h4> skipIfExists</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Avec <code>skipIfExists: true</code>, l'API consulte la base ; si le hash existe déjà, elle retourne <code>old: true</code> et les infos (txid, block_height, confirmations, etc.) sans créer de transaction.</li>
<li>Utile pour éviter les réancrages en double (retries, idempotence). Par défaut <code>false</code> : comportement inchangé.</li>
</ul>
</div>
<div class="info-box">
<h4> Gestion des UTXOs</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>L'API utilise uniquement des UTXOs <strong>confirmés</strong> (au moins 1 confirmation) pour éviter les erreurs "too-long-mempool-chain"</li>
<li>Les UTXOs non confirmés sont automatiquement exclus de la sélection</li>
<li>Chaque transaction d'ancrage provisionne automatiquement 7 UTXOs de 2500 sats pour les ancrages futurs</li>
<li>Les UTXOs provisionnés deviendront utilisables après confirmation dans un bloc</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: Verify -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/anchor/verify</span>
</div>
<div class="endpoint-description">
<p>Vérifie si un hash est ancré sur la blockchain Bitcoin Signet. Recherche dans les transactions OP_RETURN pour trouver le hash.</p>
</div>
<div class="endpoint-params">
<h4>Paramètres (Body JSON)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">hash</td>
<td>string</td>
<td><span class="param-required">Oui</span></td>
<td>Hash SHA256 à vérifier (64 caractères hex)</td>
</tr>
<tr>
<td class="param-name">txid</td>
<td>string</td>
<td><span class="param-optional">Non</span></td>
<td>ID de transaction optionnel pour accélérer la recherche</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X POST https://certificator.4nkweb.com/api/anchor/verify \
-H "Content-Type: application/json" \
-H "X-API-Key: votre-clé-api" \
-d '{
"hash": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
"txid": "abc123def456..."
}'</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK) - Hash trouvé</h4>
<div class="code-block">
<pre>{
"found": true,
"txid": "abc123def456...",
"block_height": 12345,
"confirmations": 100,
"timestamp": "2026-01-25T10:00:00.000Z"
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (200 OK) - Hash non trouvé</h4>
<div class="code-block">
<pre>{
"found": false,
"txid": null,
"block_height": null,
"confirmations": null,
"timestamp": null
}</pre>
</div>
</div>
<div class="response-example">
<h4>Codes de statut possibles</h4>
<ul style="margin-left: 20px;">
<li><span class="status-code status-200">200</span> Succès - Vérification effectuée</li>
<li><span class="status-code status-400">400</span> Requête invalide - Hash manquant ou format incorrect</li>
<li><span class="status-code status-401">401</span> Non autorisé - Clé API manquante ou invalide</li>
<li><span class="status-code status-500">500</span> Erreur serveur - Erreur interne lors de la vérification</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: Locked UTXOs -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/anchor/locked-utxos</span>
</div>
<div class="endpoint-description">
<p>Retourne la liste des UTXO actuellement verrouillés par le mutex de l'API. Cet endpoint est public et ne nécessite pas d'authentification.</p>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X GET https://certificator.4nkweb.com/api/anchor/locked-utxos</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"locked": [
{
"txid": "abc123def456...",
"vout": 0
},
{
"txid": "def456abc123...",
"vout": 1
}
],
"count": 2
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Faucet Request -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/faucet/request</span>
</div>
<div class="endpoint-description">
<p>Demande des sats (testnet coins) via le faucet. Distribue 50 000 sats (0.0005 BTC) par défaut sur une adresse Bitcoin Signet valide. Nécessite une clé API valide dans le header <code>x-api-key</code>.</p>
</div>
<div class="endpoint-params">
<h4>Paramètres (Body JSON)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">address</td>
<td>string</td>
<td><span class="param-required">Oui</span></td>
<td>Adresse Bitcoin Signet valide (commence par <code>tb1</code>, <code>bcrt1</code>, <code>2</code> ou <code>3</code>)</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X POST https://certificator.4nkweb.com/api/faucet/request \
-H "Content-Type: application/json" \
-H "x-api-key: votre-clé-api" \
-d '{
"address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc"
}'</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"success": true,
"txid": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890",
"address": "tb1qwe0nv3s0ewedd63w20r8kwnv22uw8dp2tnj3qc",
"amount": 0.0005,
"amount_sats": 50000,
"status": "pending",
"confirmations": 0,
"block_height": null
}</pre>
</div>
</div>
<div class="response-example">
<h4>Codes de statut possibles</h4>
<ul style="margin-left: 20px;">
<li><span class="status-code status-200">200</span> Succès - Transaction créée et envoyée au mempool</li>
<li><span class="status-code status-400">400</span> Requête invalide - Adresse manquante ou format incorrect</li>
<li><span class="status-code status-401">401</span> Non autorisé - Clé API manquante ou invalide</li>
<li><span class="status-code status-503">503</span> Service indisponible - Solde insuffisant dans le wallet du faucet</li>
<li><span class="status-code status-500">500</span> Erreur serveur - Erreur interne lors de la création de la transaction</li>
</ul>
</div>
<div class="error-box">
<h4>Exemple d'erreur (401 Unauthorized)</h4>
<div class="code-block">
<pre>{
"error": "Unauthorized",
"message": "Invalid or missing API key"
}</pre>
</div>
</div>
<div class="error-box">
<h4>Exemple d'erreur (503 Service Unavailable)</h4>
<div class="code-block">
<pre>{
"error": "Insufficient Balance",
"message": "Insufficient balance to send coins"
}</pre>
</div>
</div>
<div class="info-box">
<h4> Notes importantes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Le montant par défaut est de 50 000 sats (0.0005 BTC)</li>
<li>L'adresse doit être une adresse Bitcoin Signet valide</li>
<li>La transaction est envoyée au mempool immédiatement</li>
<li>Le statut "pending" signifie que la transaction est dans le mempool mais pas encore confirmée</li>
<li>Les confirmations augmentent à mesure que les blocs sont minés</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: Watermark Document -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/watermark/document</span>
</div>
<div class="endpoint-description">
<p>Ajoute un filigrane à un document, le convertit en PDF, et l'ancre sur la blockchain Bitcoin Signet. Les fichiers sont automatiquement scannés avec ClamAV avant traitement.</p>
</div>
<div class="endpoint-params">
<h4>Paramètres (Body JSON)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">apiKey</td>
<td>string</td>
<td><span class="param-required">Oui</span></td>
<td>Clé API pour l'authentification (peut aussi être dans le header <code>X-API-Key</code>)</td>
</tr>
<tr>
<td class="param-name">fileData</td>
<td>string</td>
<td><span class="param-optional">Conditionnel</span></td>
<td>Fichier encodé en base64 (requis si <code>textContent</code> n'est pas fourni)</td>
</tr>
<tr>
<td class="param-name">fileName</td>
<td>string</td>
<td><span class="param-optional">Non</span></td>
<td>Nom du fichier (requis si <code>fileData</code> est fourni)</td>
</tr>
<tr>
<td class="param-name">mimeType</td>
<td>string</td>
<td><span class="param-optional">Non</span></td>
<td>Type MIME du fichier (requis si <code>fileData</code> est fourni)</td>
</tr>
<tr>
<td class="param-name">textContent</td>
<td>string</td>
<td><span class="param-optional">Conditionnel</span></td>
<td>Contenu texte à convertir en PDF (requis si <code>fileData</code> n'est pas fourni)</td>
</tr>
<tr>
<td class="param-name">watermarkOptions</td>
<td>object</td>
<td><span class="param-required">Oui</span></td>
<td>Options de filigrane (voir détails ci-dessous)</td>
</tr>
</tbody>
</table>
</div>
<div class="info-box">
<h4>Structure de watermarkOptions</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">enabled</td>
<td>boolean</td>
<td>Doit être <code>true</code></td>
</tr>
<tr>
<td class="param-name">text</td>
<td>string</td>
<td>Texte libre du filigrane (optionnel)</td>
</tr>
<tr>
<td class="param-name">signature</td>
<td>string</td>
<td>Signature cryptographique (optionnel)</td>
</tr>
<tr>
<td class="param-name">depositor</td>
<td>string</td>
<td>Nom du dépositaire (optionnel)</td>
</tr>
<tr>
<td class="param-name">dateUTC</td>
<td>boolean</td>
<td>Ajouter la date UTC dans le filigrane</td>
</tr>
<tr>
<td class="param-name">dateLocal</td>
<td>boolean</td>
<td>Ajouter la date locale dans le filigrane</td>
</tr>
<tr>
<td class="param-name">blockNumber</td>
<td>boolean</td>
<td>Ajouter le numéro de bloc dans le filigrane</td>
</tr>
<tr>
<td class="param-name">blockHash</td>
<td>boolean</td>
<td>Ajouter le hash du bloc dans le filigrane</td>
</tr>
<tr>
<td class="param-name">documentHash</td>
<td>boolean</td>
<td>Ajouter le hash du document d'origine dans le filigrane</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X POST https://watermark.certificator.4nkweb.com/api/watermark/document \
-H "Content-Type: application/json" \
-H "X-API-Key: votre-clé-api" \
-d '{
"apiKey": "votre-clé-api",
"fileData": "base64_encoded_file_data",
"fileName": "document.pdf",
"mimeType": "application/pdf",
"watermarkOptions": {
"enabled": true,
"text": "Document confidentiel",
"depositor": "John Doe",
"dateUTC": true,
"dateLocal": true,
"blockNumber": true,
"documentHash": true
}
}'</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"success": true,
"antivirusScan": {
"scanned": true,
"clean": true,
"infected": false,
"viruses": [],
"error": null
},
"original": {
"txid": "abc123def456...",
"status": "pending",
"confirmations": 0,
"hash": "a1b2c3d4e5f6...",
"file": {
"name": "document.pdf",
"extension": "pdf",
"data": "base64_encoded_pdf"
}
},
"watermarked": {
"txid": "def456abc123...",
"status": "pending",
"confirmations": 0,
"hash": "b2c3d4e5f6a1...",
"file": {
"name": "document.pdf",
"extension": "pdf",
"data": "base64_encoded_pdf"
}
},
"certificate": {
"name": "certificat-document.pdf",
"extension": "pdf",
"data": "base64_encoded_pdf"
},
"merged": {
"name": "document-avec-certificat.pdf",
"extension": "pdf",
"data": "base64_encoded_pdf"
}
}</pre>
</div>
</div>
<div class="response-example">
<h4>Codes de statut possibles</h4>
<ul style="margin-left: 20px;">
<li><span class="status-code status-200">200</span> Succès - Document filigrané et ancré</li>
<li><span class="status-code status-400">400</span> Requête invalide - Fichier infecté, paramètres manquants ou incorrects</li>
<li><span class="status-code status-401">401</span> Non autorisé - Clé API manquante ou invalide</li>
<li><span class="status-code status-500">500</span> Erreur serveur - Erreur interne lors du traitement</li>
</ul>
</div>
<div class="error-box">
<h4>Exemple d'erreur (400 Bad Request) - Fichier infecté</h4>
<div class="code-block">
<pre>{
"error": "Bad Request",
"message": "File contains viruses",
"viruses": ["Trojan.Example"],
"antivirusScan": {
"scanned": true,
"clean": false,
"infected": true,
"viruses": ["Trojan.Example"],
"error": null
}
}</pre>
</div>
</div>
<div class="info-box">
<h4> Notes importantes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Tous les fichiers sont automatiquement scannés avec ClamAV avant traitement</li>
<li>Les fichiers infectés sont rejetés avec une erreur 400</li>
<li>Si ClamAV est indisponible, le traitement continue en mode dégradé (antivirusScan.scanned = false)</li>
<li>Le résultat inclut 4 fichiers PDF : original, filigrané, certificat, et fusionné (filigrané + certificat)</li>
<li>Les fichiers sont retournés en base64 dans le champ <code>data</code></li>
<li>Les TXID sont des liens cliquables vers mempool.4nkweb.com dans les résultats</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: ClamAV Scan -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/scan/buffer</span>
</div>
<div class="endpoint-description">
<p>Scanne un buffer de données (base64) pour détecter les virus avec ClamAV. Cette API est utilisée en interne par l'API filigrane.</p>
<p><strong>Base URL :</strong> <code>https://antivir.certificator.4nkweb.com</code></p>
</div>
<div class="endpoint-params">
<h4>Paramètres (Body JSON)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">data</td>
<td>string</td>
<td><span class="param-required">Oui</span></td>
<td>Données encodées en base64 à scanner</td>
</tr>
<tr>
<td class="param-name">filename</td>
<td>string</td>
<td><span class="param-optional">Non</span></td>
<td>Nom du fichier (pour le logging)</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X POST https://antivir.certificator.4nkweb.com/api/scan/buffer \
-H "Content-Type: application/json" \
-d '{
"data": "base64_encoded_data",
"filename": "document.pdf"
}'</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK) - Fichier propre</h4>
<div class="code-block">
<pre>{
"clean": true,
"infected": false,
"viruses": [],
"filename": "document.pdf",
"size": 12345
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (200 OK) - Fichier infecté</h4>
<div class="code-block">
<pre>{
"clean": false,
"infected": true,
"viruses": ["Trojan.Example"],
"filename": "document.pdf",
"size": 12345
}</pre>
</div>
</div>
<div class="response-example">
<h4>Codes de statut possibles</h4>
<ul style="margin-left: 20px;">
<li><span class="status-code status-200">200</span> Succès - Scan effectué (fichier propre ou infecté)</li>
<li><span class="status-code status-400">400</span> Requête invalide - Données manquantes ou format incorrect</li>
<li><span class="status-code status-503">503</span> Service indisponible - ClamAV non disponible</li>
<li><span class="status-code status-500">500</span> Erreur serveur - Erreur interne lors du scan</li>
</ul>
</div>
<div class="info-box">
<h4> Notes importantes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Cette API est principalement utilisée en interne par l'API filigrane</li>
<li>Le port fixe est 3023</li>
<li>Si ClamAV est indisponible, l'API retourne une erreur 503</li>
<li>Les fichiers sont scannés en mémoire (pas de fichiers temporaires sur disque)</li>
</ul>
</div>
</div>
</section>
<!-- Section API Dashboard -->
<section class="api-docs-section">
<h2>📊 API Dashboard (signet-dashboard)</h2>
<p>Les endpoints ci-dessous sont servis par le Dashboard (<code>https://dashboard.certificator.4nkweb.com</code>, port 3020). Données issues de la base SQLite et du RPC Bitcoin. Pas dauthentification requise.</p>
</section>
<!-- Endpoint: UTXO Count -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/utxo/count</span>
</div>
<div class="endpoint-description">
<p>Retourne uniquement les compteurs UTXO (ancrages, disponibles pour ancrage, confirmés). Optimisé pour éviter de charger la liste complète.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X GET https://dashboard.certificator.4nkweb.com/api/utxo/count</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"availableForAnchor": 180,
"confirmedAvailableForAnchor": 175,
"anchors": 150
}</pre>
</div>
</div>
<div class="info-box">
<h4> Notes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li><code>anchors</code> : UTXOs catégorie ancrages (≥ 2000 sats, confirmés, non dépensés, non verrouillés)</li>
<li><code>confirmedAvailableForAnchor</code> : comme <code>availableForAnchor</code> mais avec ≥ 6 confirmations</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: UTXO List -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/utxo/list</span>
</div>
<div class="endpoint-description">
<p>Liste des UTXOs du wallet par catégorie, avec pagination côté serveur. Données en base SQLite.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="endpoint-params">
<h4>Paramètres (query)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">category</td>
<td>string</td>
<td><span class="param-optional">Non</span></td>
<td><code>all</code> (défaut) | <code>bloc_rewards</code> | <code>ancrages</code> | <code>changes</code> | <code>fees</code>. <code>anchor</code> / <code>change</code> acceptés.</td>
</tr>
<tr>
<td class="param-name">page</td>
<td>number</td>
<td><span class="param-optional">Non</span></td>
<td>Numéro de page (défaut 1). Ignoré si <code>category=all</code>.</td>
</tr>
<tr>
<td class="param-name">limit</td>
<td>number</td>
<td><span class="param-optional">Non</span></td>
<td>Éléments par page (défaut 50, max 1000). Ignoré si <code>category=all</code>.</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Exemple (counts seulement, category=all)</h4>
<div class="code-block">
<pre>curl -X GET "https://dashboard.certificator.4nkweb.com/api/utxo/list"</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK) category=all</h4>
<div class="code-block">
<pre>{
"blocRewards": [],
"anchors": [],
"changes": [],
"fees": [],
"counts": {
"blocRewards": 10,
"anchors": 150,
"changes": 25,
"fees": 5,
"total": 190,
"availableForAnchor": 180,
"confirmedAvailableForAnchor": 175
},
"page": 1,
"limit": 0,
"totalPages": 0,
"message": "Use ?category=bloc_rewards|ancrages|changes|fees&page=X&limit=Y to get paginated data"
}</pre>
</div>
</div>
<div class="response-example">
<h4>Exemple (données paginées)</h4>
<div class="code-block">
<pre>curl -X GET "https://dashboard.certificator.4nkweb.com/api/utxo/list?category=ancrages&page=1&limit=50"</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK) category=ancrages (ex.)</h4>
<div class="code-block">
<pre>{
"blocRewards": [],
"anchors": [{"txid":"...","vout":0,"address":"...","amount":0.000025,"confirmations":12,"category":"ancrages","isSpentOnchain":false,"isLockedInMutex":false,"blockHeight":null,"blockTime":1234567890,"isAnchorChange":false}],
"changes": [],
"fees": [],
"counts": {...},
"pagination": {
"category": "ancrages",
"page": 1,
"limit": 50,
"total": 150,
"totalPages": 3
}
}</pre>
</div>
</div>
<div class="info-box">
<h4> Notes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li><code>category=fees</code> : objets <code>txid</code>, <code>fee</code>, <code>fee_sats</code>, <code>blockHeight</code>, <code>blockTime</code>, <code>confirmations</code>, <code>changeAddress</code>, <code>changeAmount</code>.</li>
<li><code>availableForAnchor</code> / <code>confirmedAvailableForAnchor</code> : mêmes définitions que <code>/api/utxo/count</code>.</li>
<li>Tri : bloc_rewards/ancrages par montant décroissant ; changes par <code>is_anchor_change</code> puis montant ; fees par <code>block_height</code> décroissant.</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: Small UTXOs Info -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/utxo/small-info</span>
</div>
<div class="endpoint-description">
<p>Obtient les informations sur les UTXOs de moins de 2500 sats disponibles pour consolidation (nombre et montant total).</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X GET https://dashboard.certificator.4nkweb.com/api/utxo/small-info</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"count": 45,
"totalAmount": 0.0001125,
"totalSats": 11250
}</pre>
</div>
</div>
<div class="info-box">
<h4> Notes importantes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Seuls les UTXOs confirmés (< 2500 sats, non dépensés, non verrouillés) sont comptés</li>
<li><code>count</code> : Nombre d'UTXOs disponibles pour consolidation</li>
<li><code>totalAmount</code> : Montant total en BTC</li>
<li><code>totalSats</code> : Montant total en satoshis</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: Consolidate UTXOs -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/utxo/consolidate</span>
</div>
<div class="endpoint-description">
<p>Consolide tous les UTXOs de moins de 2500 sats en un seul gros UTXO. Cette opération optimise le wallet en réduisant le nombre de petits UTXOs.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Exemple de requête</h4>
<div class="code-block">
<pre>curl -X POST https://dashboard.certificator.4nkweb.com/api/utxo/consolidate</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK) - Succès</h4>
<div class="code-block">
<pre>{
"success": true,
"txid": "a1b2c3d4e5f6...",
"inputCount": 45,
"totalInputAmount": 0.0001125,
"changeAmount": 0.0001025,
"estimatedFee": 0.00001
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (500) - Erreur</h4>
<div class="code-block">
<pre>{
"success": false,
"error": "No small UTXOs available for consolidation"
}</pre>
</div>
</div>
<div class="response-example">
<h4>Codes de statut possibles</h4>
<ul style="margin-left: 20px;">
<li><span class="status-code status-200">200</span> Succès - Consolidation effectuée</li>
<li><span class="status-code status-500">500</span> Erreur - Aucun UTXO disponible ou erreur lors de la consolidation</li>
</ul>
</div>
<div class="info-box">
<h4> Notes importantes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Seuls les UTXOs confirmés de moins de 2500 sats sont consolidés</li>
<li>Les UTXOs verrouillés ou déjà dépensés sont exclus</li>
<li>La transaction est envoyée au mempool immédiatement</li>
<li>Le montant consolidé est retourné comme change (moins les frais estimés)</li>
<li>Cette opération optimise le wallet en réduisant le nombre de petits UTXOs</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: UTXO Fees -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/utxo/fees</span>
</div>
<div class="endpoint-description">
<p>Liste des frais issus des transactions d'ancrage (métadonnées OP_RETURN). Données fournies par le RPC / cache.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"fees": [
{"txid":"...","fee":0.000012,"fee_sats":1200,"blockHeight":12345,"blockTime":1234567890,"confirmations":10,"changeAddress":"...","changeAmount":0.00002}
],
"count": 1
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: UTXO Fees Update -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/utxo/fees/update</span>
</div>
<div class="endpoint-description">
<p>Récupère les frais depuis les transactions d'ancrage (OP_RETURN) et les enregistre. Optionnel : limite au bloc donné.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="endpoint-params">
<h4>Paramètres (Body JSON)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">sinceBlockHeight</td>
<td>number</td>
<td><span class="param-optional">Non</span></td>
<td>Ne traiter que les ancrages depuis ce bloc (inclus). Si absent, tous.</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"success": true,
"newFees": 5,
"totalFees": 120,
"processed": 50
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (500)</h4>
<div class="code-block">
<pre>{
"success": false,
"error": "Error message"
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Hash List -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/hash/list</span>
</div>
<div class="endpoint-description">
<p>Liste des hash ancrés, paginée (base SQLite).</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="endpoint-params">
<h4>Paramètres (query)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">page</td>
<td>number</td>
<td><span class="param-optional">Non</span></td>
<td>Numéro de page (défaut 1).</td>
</tr>
<tr>
<td class="param-name">limit</td>
<td>number</td>
<td><span class="param-optional">Non</span></td>
<td>Éléments par page (défaut 50, max 1000).</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Exemple</h4>
<div class="code-block">
<pre>curl -X GET "https://dashboard.certificator.4nkweb.com/api/hash/list?page=1&limit=50"</pre>
</div>
<button class="copy-button" onclick="copyCode(this)">📋 Copier</button>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"hashes": [
{"hash":"a1b2...","txid":"...","blockHeight":12345,"confirmations":10,"date":"2026-01-28T12:00:00.000Z"}
],
"count": 50,
"total": 32000,
"page": 1,
"limit": 50,
"totalPages": 640
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Hash Generate -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-post">POST</span>
<span class="endpoint-path">/api/hash/generate</span>
</div>
<div class="endpoint-description">
<p>Calcule le hash SHA256 d'un texte ou d'un fichier (base64). Un seul des deux doit être fourni.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="endpoint-params">
<h4>Paramètres (Body JSON)</h4>
<table class="param-table">
<thead>
<tr>
<th>Paramètre</th>
<th>Type</th>
<th>Requis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="param-name">text</td>
<td>string</td>
<td><span class="param-optional">Conditionnel</span></td>
<td>Texte UTF-8 à hasher. Exclure si <code>fileContent</code> fourni.</td>
</tr>
<tr>
<td class="param-name">fileContent</td>
<td>string</td>
<td><span class="param-optional">Conditionnel</span></td>
<td>Contenu fichier (base64 ou UTF-8). Exclure si <code>text</code> fourni.</td>
</tr>
<tr>
<td class="param-name">isBase64</td>
<td>boolean</td>
<td><span class="param-optional">Non</span></td>
<td>Si <code>true</code>, <code>fileContent</code> est décodé en base64 avant hash. Sinon traité en UTF-8.</td>
</tr>
</tbody>
</table>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"hash": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
}</pre>
</div>
</div>
<div class="response-example">
<h4>Codes de statut</h4>
<ul style="margin-left: 20px;">
<li><span class="status-code status-200">200</span> Succès</li>
<li><span class="status-code status-400">400</span> <code>text</code> et <code>fileContent</code> absents ou tous deux fournis ; format invalide.</li>
<li><span class="status-code status-500">500</span> Erreur serveur</li>
</ul>
</div>
</div>
</section>
<!-- Endpoint: Mining Difficulty -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/mining/difficulty</span>
</div>
<div class="endpoint-description">
<p>Difficulté de minage courante (RPC <code>getblockchaininfo</code>).</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"difficulty": 0.0002441371325370144
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Mining Status -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/mining/status</span>
</div>
<div class="endpoint-description">
<p>État du miner : inféré depuis lâge du dernier bloc. <code>active</code> si le dernier bloc a moins de 30 minutes, sinon <code>inactive</code>.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"status": "active",
"blocks": 11535,
"lastBlockTime": 1769730315,
"lastBlockAgeSeconds": 120,
"message": "Dernier bloc récent, miner probablement actif"
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Mining Avg Block Time -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/mining/avg-block-time</span>
</div>
<div class="endpoint-description">
<p>Temps moyen entre blocs (source : Mempool <code>/api/v1/difficulty-adjustment</code>).</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"timeAvg": 600000,
"timeAvgSeconds": 600,
"formatted": "10m"
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Transactions Avg Fee / Avg Amount -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/transactions/avg-fee</span> · <span class="endpoint-path">/api/transactions/avg-amount</span>
</div>
<div class="endpoint-description">
<p>Frais moyen (sats) et montant moyen (sats) pour les ancrages. Valeurs fixes (1200 sats / 1000 sats) pour ce contexte.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Réponse avg-fee (200 OK)</h4>
<div class="code-block">
<pre>{
"avgFee": 1200,
"avgFeeRate": 0,
"txCount": 0
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse avg-amount (200 OK)</h4>
<div class="code-block">
<pre>{
"avgAmount": 1000,
"txCount": 0
}</pre>
</div>
</div>
</div>
</section>
<!-- Endpoint: Anchor Example -->
<section class="api-docs-section">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge method-get">GET</span>
<span class="endpoint-path">/api/anchor/example</span>
</div>
<div class="endpoint-description">
<p>Exemple de transaction d'ancrage (la plus récente de la liste des hash) : txid, bloc, confirmations, entrées/sorties. Pour la page Learn.</p>
<p><strong>Base URL :</strong> <code>https://dashboard.certificator.4nkweb.com</code></p>
</div>
<div class="response-example">
<h4>Réponse (200 OK)</h4>
<div class="code-block">
<pre>{
"txid": "...",
"blockHeight": 12345,
"confirmations": 10,
"hash": "a1b2...",
"inputs": [{"txid":"...","vout":0,"value":0.001}],
"outputs": [{"address":"...","value":0.000025,"type":"nulldata","isOpReturn":true}]
}</pre>
</div>
</div>
<div class="response-example">
<h4>Réponse (404)</h4>
<div class="code-block">
<pre>{
"error": "No anchor transactions found"
}</pre>
</div>
</div>
</div>
</section>
<!-- Section Informations -->
<section class="api-docs-section">
<div class="endpoint-card">
<h2> Informations Complémentaires</h2>
<div class="info-box">
<h4>Format du Hash</h4>
<p>Le hash doit être un hash SHA256 en format hexadécimal, exactement 64 caractères.</p>
<div class="code-block">
<pre>Exemple valide: a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
Longueur: 64 caractères
Format: hexadécimal (0-9, a-f, A-F)</pre>
</div>
</div>
<div class="info-box">
<h4>Format de la Transaction</h4>
<p>Les transactions d'ancrage incluent :</p>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Un output OP_RETURN contenant "ANCHOR:" suivi du hash</li>
<li>Un output d'ancrage de 2500 sats</li>
<li>7 outputs de provisionnement de 2500 sats chacun</li>
<li>Un output de change (si nécessaire)</li>
</ul>
</div>
<div class="info-box">
<h4>Base URLs</h4>
<p>Les APIs sont accessibles aux adresses suivantes :</p>
<div class="code-block">
<pre>Dashboard : https://dashboard.certificator.4nkweb.com (port 3020)
API d'Ancrage : https://anchorage.certificator.4nkweb.com (port 3010, machine bitcoin 192.168.1.105)
API Faucet : https://faucet.certificator.4nkweb.com (port 3021)
API Filigrane : https://watermark.certificator.4nkweb.com (port 3022)
API ClamAV : https://antivir.certificator.4nkweb.com (port 3023)</pre>
</div>
<p><code>/health</code> est exposé par lAPI dAncrage et lAPI Faucet, pas par le Dashboard.</p>
</div>
<div class="info-box">
<h4>Ports fixes</h4>
<p>Tous les ports sont fixes et ne peuvent pas être modifiés :</p>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Dashboard : Port 3020</li>
<li>API d'Ancrage : Port 3010</li>
<li>API Faucet : Port 3021</li>
<li>API Filigrane : Port 3022</li>
<li>API ClamAV : Port 3023</li>
</ul>
</div>
<div class="warning-box">
<h4>⚠️ Notes importantes</h4>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Les transactions sont envoyées au mempool immédiatement</li>
<li>Le statut "pending" signifie que la transaction est dans le mempool mais pas encore confirmée</li>
<li>Les confirmations augmentent à mesure que les blocs sont minés</li>
<li>En cas d'erreur 402 (Solde insuffisant), vous devez approvisionner le wallet de l'API</li>
</ul>
</div>
</div>
</section>
</main>
<footer>
<p>Bitcoin Ancrage Dashboard - Équipe 4NK</p>
<a href="https://git.4nkweb.com/4nk/anchorage_layer_simple.git" target="_blank" rel="noopener noreferrer" class="git-link" title="Voir le code source sur Git">
<svg class="git-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M23.546 10.93L13.067.452c-.604-.603-1.582-.603-2.188 0L8.708 2.627l2.76 2.76c.645-.215 1.379-.07 1.889.441.516.515.658 1.258.438 1.9l2.658 2.66c.645-.223 1.387-.083 1.9.435.721.72.721 1.884 0 2.604-.719.719-1.881.719-2.6 0-.539-.541-.674-1.337-.404-1.996L12.86 8.955v6.525c.176.086.342.203.488.348.713.721.713 1.883 0 2.6-.719.721-1.884.721-2.599 0-.72-.719-.72-1.879 0-2.598.182-.18.387-.316.605-.406V8.835c-.217-.091-.424-.222-.6-.401-.545-.545-.676-1.342-.396-2.011L7.636 3.7.45 10.881c-.6.605-.6 1.584 0 2.189l10.48 10.477c.604.604 1.582.604 2.186 0l10.43-10.43c.605-.603.605-1.582 0-2.187"/>
</svg>
</a>
</footer>
</div>
<script>
function copyCode(button) {
const codeBlock = button.previousElementSibling;
const code = codeBlock.querySelector('pre')?.textContent || codeBlock.textContent;
navigator.clipboard.writeText(code).then(() => {
const originalText = button.textContent;
button.textContent = '✅ Copié !';
setTimeout(() => {
button.textContent = originalText;
}, 2000);
}).catch(err => {
console.error('Erreur lors de la copie:', err);
alert('Erreur lors de la copie. Veuillez sélectionner et copier manuellement.');
});
}
</script>
</body>
</html>