Update consolidate button with dynamic info and implement API call
**Motivations:**
- Afficher le nombre d'UTXOs et le montant total concernés par la consolidation
- Rendre le bouton de consolidation plus informatif
**Root causes:**
- Le bouton affichait un texte statique sans information sur ce qui sera consolidé
- Pas de visibilité sur le nombre d'UTXOs et le montant concernés
**Correctifs:**
- Création de la méthode getSmallUtxosInfo() pour obtenir les infos sans consolider
- Ajout de la route GET /api/utxo/small-info pour exposer ces informations
**Evolutions:**
- Bouton de consolidation avec texte dynamique : "Consolider la capacité d'ancrage résiduelle (X UTXOs, Y ✅)"
- Fonction loadSmallUtxosInfo() qui charge les infos depuis l'API
- Bouton désactivé avec message "Aucun UTXO à consolider" si aucun UTXO disponible
- Chargement automatique des infos au chargement de la page et après refresh
- Gestion d'erreur avec message "Erreur de chargement" si l'API échoue
**Pages affectées:**
- signet-dashboard/src/bitcoin-rpc.js: Méthode getSmallUtxosInfo() pour obtenir les infos des petits UTXOs
- signet-dashboard/src/server.js: Route GET /api/utxo/small-info
- signet-dashboard/public/utxo-list.html: Fonction loadSmallUtxosInfo() et mise à jour du bouton avec texte dynamique
This commit is contained in:
parent
15edb1bac1
commit
3e3bfc72d3
@ -264,7 +264,7 @@
|
|||||||
<div class="info-section">
|
<div class="info-section">
|
||||||
<p><strong>Total d'UTXO :</strong> <span id="utxo-count">-</span></p>
|
<p><strong>Total d'UTXO :</strong> <span id="utxo-count">-</span></p>
|
||||||
<p><strong>Capacité d'ancrage restante :</strong> <span id="available-for-anchor">-</span> ancrages (<span id="confirmed-available-for-anchor">-</span> UTXOs confirmés)</p>
|
<p><strong>Capacité d'ancrage restante :</strong> <span id="available-for-anchor">-</span> ancrages (<span id="confirmed-available-for-anchor">-</span> UTXOs confirmés)</p>
|
||||||
<button class="consolidate-button" onclick="consolidateSmallUtxos()" style="margin-left: 10px; background: #ffc107; color: #000; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 1em; margin-top: 10px;">Consolider les UTXOs < 2500 sats</button>
|
<button class="consolidate-button" id="consolidate-button" onclick="consolidateSmallUtxos()" style="margin-left: 10px; background: #ffc107; color: #000; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 1em; margin-top: 10px;">Chargement...</button>
|
||||||
<p><strong>Montant total :</strong> <span id="total-amount" class="total-amount">-</span></p>
|
<p><strong>Montant total :</strong> <span id="total-amount" class="total-amount">-</span></p>
|
||||||
<p><strong>Dernière mise à jour :</strong> <span id="last-update">-</span></p>
|
<p><strong>Dernière mise à jour :</strong> <span id="last-update">-</span></p>
|
||||||
<button class="refresh-button" onclick="loadUtxoList()">Actualiser</button>
|
<button class="refresh-button" onclick="loadUtxoList()">Actualiser</button>
|
||||||
@ -295,6 +295,7 @@
|
|||||||
// Charger la liste au chargement de la page
|
// Charger la liste au chargement de la page
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
loadUtxoList();
|
loadUtxoList();
|
||||||
|
loadSmallUtxosInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
function getSortState(categoryName) {
|
function getSortState(categoryName) {
|
||||||
@ -520,6 +521,34 @@
|
|||||||
return tableHTML;
|
return tableHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadSmallUtxosInfo() {
|
||||||
|
const button = document.getElementById('consolidate-button');
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/utxo/small-info`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const count = data.count || 0;
|
||||||
|
const totalSats = data.totalSats || 0;
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
button.textContent = `Consolider la capacité d'ancrage résiduelle (${count.toLocaleString('fr-FR')} UTXOs, ${totalSats.toLocaleString('fr-FR')} ✅)`;
|
||||||
|
button.disabled = false;
|
||||||
|
} else {
|
||||||
|
button.textContent = 'Aucun UTXO à consolider';
|
||||||
|
button.disabled = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading small UTXOs info:', error);
|
||||||
|
button.textContent = 'Erreur de chargement';
|
||||||
|
button.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadUtxoList() {
|
async function loadUtxoList() {
|
||||||
const contentDiv = document.getElementById('content');
|
const contentDiv = document.getElementById('content');
|
||||||
const refreshButton = document.querySelector('.refresh-button');
|
const refreshButton = document.querySelector('.refresh-button');
|
||||||
@ -527,6 +556,9 @@
|
|||||||
refreshButton.disabled = true;
|
refreshButton.disabled = true;
|
||||||
contentDiv.innerHTML = '<div class="loading">Chargement des UTXO...</div>';
|
contentDiv.innerHTML = '<div class="loading">Chargement des UTXO...</div>';
|
||||||
|
|
||||||
|
// Charger les infos des petits UTXOs en parallèle
|
||||||
|
loadSmallUtxosInfo();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/utxo/list`);
|
const response = await fetch(`${API_BASE_URL}/api/utxo/list`);
|
||||||
|
|
||||||
@ -572,6 +604,8 @@
|
|||||||
contentDiv.innerHTML = `<div class="error">Erreur lors du chargement de la liste des UTXO : ${error.message}</div>`;
|
contentDiv.innerHTML = `<div class="error">Erreur lors du chargement de la liste des UTXO : ${error.message}</div>`;
|
||||||
} finally {
|
} finally {
|
||||||
refreshButton.disabled = false;
|
refreshButton.disabled = false;
|
||||||
|
// Recharger les infos des petits UTXOs après le chargement de la liste
|
||||||
|
loadSmallUtxosInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -744,6 +744,135 @@ class BitcoinRPC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient les informations sur les UTXOs de moins de 2500 sats disponibles pour consolidation
|
||||||
|
* @returns {Promise<Object>} Nombre et montant total des petits UTXOs
|
||||||
|
*/
|
||||||
|
async getSmallUtxosInfo() {
|
||||||
|
try {
|
||||||
|
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}`;
|
||||||
|
const auth = Buffer.from(`${username}:${password}`).toString('base64');
|
||||||
|
|
||||||
|
// Récupérer les UTXOs confirmés
|
||||||
|
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: [1], // Minimum 1 confirmation
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rpcResponse.ok) {
|
||||||
|
const errorText = await rpcResponse.text();
|
||||||
|
logger.error('HTTP error in listunspent for small UTXOs info', {
|
||||||
|
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 for small UTXOs info', { error: rpcResult.error });
|
||||||
|
throw new Error(`RPC error: ${rpcResult.error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unspent = rpcResult.result || [];
|
||||||
|
|
||||||
|
// Récupérer les UTXOs verrouillés depuis l'API d'ancrage
|
||||||
|
let lockedUtxos = new Set();
|
||||||
|
try {
|
||||||
|
const anchorApiUrl = process.env.ANCHOR_API_URL || 'http://localhost:3010';
|
||||||
|
const anchorApiKey = process.env.ANCHOR_API_KEY || '';
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
if (anchorApiKey) {
|
||||||
|
headers['x-api-key'] = anchorApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lockedResponse = await fetch(`${anchorApiUrl}/api/anchor/locked-utxos`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lockedResponse.ok) {
|
||||||
|
const lockedData = await lockedResponse.json();
|
||||||
|
for (const locked of lockedData.locked || []) {
|
||||||
|
lockedUtxos.add(`${locked.txid}:${locked.vout}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug('Error getting locked UTXOs for small UTXOs info', { error: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtrer les UTXOs de moins de 2500 sats (0.000025 BTC), confirmés et non verrouillés
|
||||||
|
const maxAmount = 0.000025; // 2500 sats
|
||||||
|
const smallUtxos = unspent.filter(utxo => {
|
||||||
|
const utxoKey = `${utxo.txid}:${utxo.vout}`;
|
||||||
|
return utxo.amount < maxAmount &&
|
||||||
|
(utxo.confirmations || 0) > 0 &&
|
||||||
|
!lockedUtxos.has(utxoKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vérifier si l'UTXO est dépensé onchain et calculer le montant total
|
||||||
|
let count = 0;
|
||||||
|
let totalAmount = 0;
|
||||||
|
for (const utxo of smallUtxos) {
|
||||||
|
try {
|
||||||
|
const txOutResponse = await fetch(rpcUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Basic ${auth}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
jsonrpc: '1.0',
|
||||||
|
id: 'gettxout',
|
||||||
|
method: 'gettxout',
|
||||||
|
params: [utxo.txid, utxo.vout],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (txOutResponse.ok) {
|
||||||
|
const txOutResult = await txOutResponse.json();
|
||||||
|
// Si gettxout retourne null, l'UTXO est dépensé
|
||||||
|
if (txOutResult.result !== null) {
|
||||||
|
count++;
|
||||||
|
totalAmount += utxo.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug('Error checking if UTXO is spent for small UTXOs info', { txid: utxo.txid, vout: utxo.vout, error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalSats = Math.round(totalAmount * 100000000);
|
||||||
|
|
||||||
|
return {
|
||||||
|
count,
|
||||||
|
totalAmount,
|
||||||
|
totalSats,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error getting small UTXOs info', { error: error.message });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consolide les UTXOs de moins de 2500 sats en un gros UTXO
|
* Consolide les UTXOs de moins de 2500 sats en un gros UTXO
|
||||||
* @returns {Promise<Object>} Transaction créée avec txid
|
* @returns {Promise<Object>} Transaction créée avec txid
|
||||||
|
|||||||
@ -298,6 +298,23 @@ app.get('/api/utxo/list', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Route pour obtenir les informations sur les petits UTXOs (< 2500 sats)
|
||||||
|
app.get('/api/utxo/small-info', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const info = await bitcoinRPC.getSmallUtxosInfo();
|
||||||
|
res.json({
|
||||||
|
count: info.count,
|
||||||
|
totalAmount: info.totalAmount,
|
||||||
|
totalSats: info.totalSats,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error getting small UTXOs info', { error: error.message });
|
||||||
|
res.status(500).json({
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Route pour consolider les UTXOs de moins de 2500 sats
|
// Route pour consolider les UTXOs de moins de 2500 sats
|
||||||
app.post('/api/utxo/consolidate', async (req, res) => {
|
app.post('/api/utxo/consolidate', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user