Files
munin-bitcoin/server/addresses.js
T
Unheard0840 cf3acaa59e Update server/addresses.js
Validate Bitcoin addresses before storing
2026-08-29 11:25:59 +00:00

155 lines
2.7 KiB
JavaScript

const crypto = require('crypto')
const fs = require('fs/promises')
const {
addressToScriptPubKey,
} = require('./bitcoin-address')
const storePath = '/data/store.json'
async function readStore() {
try {
const data = await fs.readFile(storePath, 'utf8')
return JSON.parse(data)
} catch (error) {
if (error.code === 'ENOENT') {
return {}
}
throw error
}
}
async function writeStore(store) {
const temporaryPath =
`${storePath}.${crypto.randomBytes(8).toString('hex')}.tmp`
await fs.writeFile(
temporaryPath,
JSON.stringify(store, null, 2) + '\n',
{
mode: 0o600,
},
)
await fs.rename(temporaryPath, storePath)
}
function normalizeAddress(address) {
if (typeof address !== 'string') {
return null
}
const normalized = address.trim()
if (!normalized) {
return null
}
return normalized
}
function normalizeLabel(label) {
if (typeof label !== 'string') {
return undefined
}
const normalized = label.trim()
return normalized || undefined
}
function validateBitcoinAddress(address) {
try {
addressToScriptPubKey(address)
} catch (error) {
throw new Error(
error instanceof Error
? error.message
: 'Invalid Bitcoin address',
)
}
}
async function listAddresses() {
const store = await readStore()
return Array.isArray(store.addresses)
? store.addresses
: []
}
async function addAddress(address, label) {
const normalizedAddress = normalizeAddress(address)
if (!normalizedAddress) {
throw new Error('Bitcoin address is required')
}
validateBitcoinAddress(normalizedAddress)
const addresses = await listAddresses()
if (
addresses.some(
(item) => item.address === normalizedAddress,
)
) {
throw new Error(
'Bitcoin address is already being watched',
)
}
const entry = {
address: normalizedAddress,
}
const normalizedLabel = normalizeLabel(label)
if (normalizedLabel) {
entry.label = normalizedLabel
}
const store = await readStore()
store.addresses = [...addresses, entry]
await writeStore(store)
return entry
}
async function removeAddress(address) {
const normalizedAddress = normalizeAddress(address)
if (!normalizedAddress) {
throw new Error('Bitcoin address is required')
}
const store = await readStore()
const addresses = Array.isArray(store.addresses)
? store.addresses
: []
const filtered = addresses.filter(
(item) => item.address !== normalizedAddress,
)
if (filtered.length === addresses.length) {
throw new Error(
'Bitcoin address is not being watched',
)
}
store.addresses = filtered
await writeStore(store)
return true
}
module.exports = {
listAddresses,
addAddress,
removeAddress,
}