From 79d37f5dd3fb6f13b2fd0dcbe4b4feeea2cb6a86 Mon Sep 17 00:00:00 2001 From: Unheard0840 Date: Sat, 29 Aug 2026 10:50:03 +0000 Subject: [PATCH] Update server/addresses.js Add Fulcrum address data queries --- server/addresses.js | 228 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/server/addresses.js b/server/addresses.js index c562216..394523d 100644 --- a/server/addresses.js +++ b/server/addresses.js @@ -1,7 +1,9 @@ const crypto = require('crypto') const fs = require('fs/promises') +const net = require('net') const storePath = '/data/store.json' +const fulcrumTimeoutMs = 3000 async function readStore() { try { @@ -122,8 +124,234 @@ async function removeAddress(address) { return true } +function queryFulcrum(fulcrumUrl, method, params = []) { + return new Promise((resolve) => { + if (!fulcrumUrl) { + resolve({ + ok: false, + result: null, + error: 'Fulcrum URL is not configured', + }) + return + } + + let parsed + + try { + parsed = new URL(fulcrumUrl) + } catch { + resolve({ + ok: false, + result: null, + error: 'Invalid Fulcrum URL', + }) + return + } + + if (parsed.protocol !== 'tcp:') { + resolve({ + ok: false, + result: null, + error: 'Fulcrum URL must use tcp://', + }) + return + } + + const host = parsed.hostname + const port = Number(parsed.port) + + if (!host || !port) { + resolve({ + ok: false, + result: null, + error: 'Fulcrum URL is missing host or port', + }) + return + } + + const socket = net.createConnection({ + host, + port, + }) + + let response = '' + let settled = false + + const finish = (value) => { + if (settled) { + return + } + + settled = true + socket.destroy() + resolve(value) + } + + socket.setTimeout(fulcrumTimeoutMs) + + socket.on('connect', () => { + const request = JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method, + params, + }) + '\n' + + socket.write(request) + }) + + socket.on('data', (data) => { + response += data.toString() + + const lines = response.split('\n') + + for (const line of lines) { + if (!line.trim()) { + continue + } + + let message + + try { + message = JSON.parse(line) + } catch { + continue + } + + if (message.error) { + finish({ + ok: false, + result: null, + error: message.error, + }) + + return + } + + if ( + Object.prototype.hasOwnProperty.call( + message, + 'result', + ) + ) { + finish({ + ok: true, + result: message.result, + error: null, + }) + + return + } + } + }) + + socket.on('timeout', () => { + finish({ + ok: false, + result: null, + error: 'Connection timed out', + }) + }) + + socket.on('error', (error) => { + finish({ + ok: false, + result: null, + error: error.message, + }) + }) + + socket.on('close', () => { + finish({ + ok: false, + result: null, + error: 'Connection closed before a response was received', + }) + }) + }) +} + +async function getAddressData(address) { + const fulcrumUrl = process.env.MUNIN_FULCRUM_URL || null + + if (!fulcrumUrl) { + throw new Error('Fulcrum URL is not configured') + } + + const history = await queryFulcrum( + fulcrumUrl, + 'blockchain.address.get_history', + [address], + ) + + if (!history.ok) { + throw new Error( + `Failed to query address history: ${JSON.stringify(history.error)}`, + ) + } + + const utxos = await queryFulcrum( + fulcrumUrl, + 'blockchain.address.listunspent', + [address], + ) + + if (!utxos.ok) { + throw new Error( + `Failed to query address UTXOs: ${JSON.stringify(utxos.error)}`, + ) + } + + const confirmedBalance = utxos.result.reduce( + (total, item) => total + Number(item.value || 0), + 0, + ) + + return { + address, + confirmedBalance, + unconfirmedBalance: 0, + utxos: utxos.result, + history: history.result, + } +} + +async function getAddressesWithData() { + const addresses = await listAddresses() + + const results = [] + + for (const entry of addresses) { + try { + const data = await getAddressData(entry.address) + + results.push({ + ...entry, + confirmedBalance: data.confirmedBalance, + unconfirmedBalance: data.unconfirmedBalance, + utxos: data.utxos, + history: data.history, + error: null, + }) + } catch (error) { + results.push({ + ...entry, + confirmedBalance: null, + unconfirmedBalance: null, + utxos: [], + history: [], + error: error.message, + }) + } + } + + return results +} + module.exports = { listAddresses, addAddress, removeAddress, + getAddressData, + getAddressesWithData, } \ No newline at end of file