@relay-federation/bridge 0.3.11 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/address-scanner.js +4 -3
- package/lib/status-server.js +174 -3
- package/package.json +1 -1
package/lib/address-scanner.js
CHANGED
|
@@ -25,14 +25,15 @@ export async function scanAddress (address, store, onProgress = () => {}) {
|
|
|
25
25
|
// Phase 1: Fetch tx history from WhatsOnChain
|
|
26
26
|
onProgress({ phase: 'discovery', current: 0, total: 0, message: 'Fetching transaction history...' })
|
|
27
27
|
|
|
28
|
-
const historyUrl = `${WOC_BASE}/address/${address}/history`
|
|
28
|
+
const historyUrl = `${WOC_BASE}/address/${address}/confirmed/history`
|
|
29
29
|
const histRes = await fetchWithRetry(historyUrl)
|
|
30
30
|
if (!histRes.ok) {
|
|
31
31
|
throw new Error(`WhatsOnChain returned ${histRes.status} for address history`)
|
|
32
32
|
}
|
|
33
|
-
const
|
|
33
|
+
const data = await histRes.json()
|
|
34
|
+
const history = Array.isArray(data) ? data : (data.result || [])
|
|
34
35
|
|
|
35
|
-
if (
|
|
36
|
+
if (history.length === 0) {
|
|
36
37
|
onProgress({ phase: 'done', current: 0, total: 0, message: 'No transactions found for this address' })
|
|
37
38
|
return { address, txsScanned: 0, inscriptionsFound: 0, errors: 0 }
|
|
38
39
|
}
|
package/lib/status-server.js
CHANGED
|
@@ -415,7 +415,7 @@ export class StatusServer {
|
|
|
415
415
|
isP2PKH: o.isP2PKH,
|
|
416
416
|
hash160: o.hash160,
|
|
417
417
|
type: o.type,
|
|
418
|
-
data: o.data,
|
|
418
|
+
data: o.data ? o.data.map(d => d.length > 128 ? d.slice(0, 128) + '...' : d) : o.data,
|
|
419
419
|
protocol: o.protocol,
|
|
420
420
|
parsed: o.parsed
|
|
421
421
|
}))
|
|
@@ -804,9 +804,10 @@ export class StatusServer {
|
|
|
804
804
|
return
|
|
805
805
|
}
|
|
806
806
|
try {
|
|
807
|
-
const resp = await fetch('https://api.whatsonchain.com/v1/bsv/main/address/' + addr + '/history', { signal: AbortSignal.timeout(10000) })
|
|
807
|
+
const resp = await fetch('https://api.whatsonchain.com/v1/bsv/main/address/' + addr + '/confirmed/history', { signal: AbortSignal.timeout(10000) })
|
|
808
808
|
if (!resp.ok) throw new Error('WoC returned ' + resp.status)
|
|
809
|
-
const
|
|
809
|
+
const data = await resp.json()
|
|
810
|
+
const history = Array.isArray(data) ? data : (data.result || [])
|
|
810
811
|
this._addressCache.set(addr, { data: history, time: Date.now() })
|
|
811
812
|
// Prune cache if it grows too large
|
|
812
813
|
if (this._addressCache.size > 100) {
|
|
@@ -1092,6 +1093,176 @@ export class StatusServer {
|
|
|
1092
1093
|
return
|
|
1093
1094
|
}
|
|
1094
1095
|
|
|
1096
|
+
// GET /health — MCP/CLI compatibility
|
|
1097
|
+
if (req.method === 'GET' && path === '/health') {
|
|
1098
|
+
const status = await this.getStatus()
|
|
1099
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1100
|
+
res.end(JSON.stringify({
|
|
1101
|
+
status: 'ok',
|
|
1102
|
+
headerHeight: status.headers.bestHeight,
|
|
1103
|
+
connectedPeers: status.bsvNode.peers,
|
|
1104
|
+
synced: status.headers.bestHeight > 0
|
|
1105
|
+
}))
|
|
1106
|
+
return
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// GET /api/address/:addr/unspent — UTXO lookup via GorillaPool ordinals
|
|
1110
|
+
const unspentMatch = path.match(/^\/api\/address\/([13][a-km-zA-HJ-NP-Z1-9]{24,33})\/unspent$/)
|
|
1111
|
+
if (req.method === 'GET' && unspentMatch) {
|
|
1112
|
+
const addr = unspentMatch[1]
|
|
1113
|
+
try {
|
|
1114
|
+
const resp = await fetch(
|
|
1115
|
+
`https://ordinals.gorillapool.io/api/txos/address/${addr}/unspent`,
|
|
1116
|
+
{ signal: AbortSignal.timeout(10000) }
|
|
1117
|
+
)
|
|
1118
|
+
if (!resp.ok) throw new Error(`GorillaPool ${resp.status}`)
|
|
1119
|
+
const data = await resp.json()
|
|
1120
|
+
// Transform GorillaPool format → WoC format
|
|
1121
|
+
const utxos = data.map(u => ({
|
|
1122
|
+
tx_hash: u.txid,
|
|
1123
|
+
tx_pos: u.vout,
|
|
1124
|
+
value: u.satoshis
|
|
1125
|
+
}))
|
|
1126
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1127
|
+
res.end(JSON.stringify(utxos))
|
|
1128
|
+
} catch (err) {
|
|
1129
|
+
res.writeHead(502, { 'Content-Type': 'application/json' })
|
|
1130
|
+
res.end(JSON.stringify({ error: 'UTXO fetch failed: ' + err.message }))
|
|
1131
|
+
}
|
|
1132
|
+
return
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// GET /api/tx/:txid/hex — raw transaction hex
|
|
1136
|
+
const hexMatch = path.match(/^\/api\/tx\/([0-9a-f]{64})\/hex$/)
|
|
1137
|
+
if (req.method === 'GET' && hexMatch) {
|
|
1138
|
+
const txid = hexMatch[1]
|
|
1139
|
+
let rawHex = null
|
|
1140
|
+
// Mempool first
|
|
1141
|
+
if (this._txRelay && this._txRelay.mempool.has(txid)) {
|
|
1142
|
+
rawHex = this._txRelay.mempool.get(txid)
|
|
1143
|
+
}
|
|
1144
|
+
// P2P second
|
|
1145
|
+
if (!rawHex && this._bsvNodeClient) {
|
|
1146
|
+
try {
|
|
1147
|
+
const result = await this._bsvNodeClient.getTx(txid, 5000)
|
|
1148
|
+
rawHex = result.rawHex
|
|
1149
|
+
} catch {}
|
|
1150
|
+
}
|
|
1151
|
+
// WoC fallback
|
|
1152
|
+
if (!rawHex) {
|
|
1153
|
+
try {
|
|
1154
|
+
const resp = await fetch(`https://api.whatsonchain.com/v1/bsv/main/tx/${txid}/hex`)
|
|
1155
|
+
if (resp.ok) rawHex = await resp.text()
|
|
1156
|
+
} catch {}
|
|
1157
|
+
}
|
|
1158
|
+
if (rawHex) {
|
|
1159
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' })
|
|
1160
|
+
res.end(rawHex)
|
|
1161
|
+
} else {
|
|
1162
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
1163
|
+
res.end(JSON.stringify({ error: 'tx not found' }))
|
|
1164
|
+
}
|
|
1165
|
+
return
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// POST /api/broadcast — MCP/CLI compatibility (accepts { rawTx } key)
|
|
1169
|
+
if (req.method === 'POST' && path === '/api/broadcast') {
|
|
1170
|
+
const body = await this._readBody(req)
|
|
1171
|
+
const rawHex = body.rawTx || body.rawHex
|
|
1172
|
+
if (!rawHex || typeof rawHex !== 'string') {
|
|
1173
|
+
res.writeHead(400, { 'Content-Type': 'application/json' })
|
|
1174
|
+
res.end(JSON.stringify({ error: 'rawTx or rawHex required' }))
|
|
1175
|
+
return
|
|
1176
|
+
}
|
|
1177
|
+
if (!/^[0-9a-fA-F]+$/.test(rawHex) || rawHex.length % 2 !== 0) {
|
|
1178
|
+
res.writeHead(400, { 'Content-Type': 'application/json' })
|
|
1179
|
+
res.end(JSON.stringify({ error: 'Invalid hex string' }))
|
|
1180
|
+
return
|
|
1181
|
+
}
|
|
1182
|
+
const buf = Buffer.from(rawHex, 'hex')
|
|
1183
|
+
const hash = createHash('sha256').update(createHash('sha256').update(buf).digest()).digest()
|
|
1184
|
+
const txid = Buffer.from(hash).reverse().toString('hex')
|
|
1185
|
+
const sent = this._txRelay ? this._txRelay.broadcastTx(txid, rawHex) : 0
|
|
1186
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1187
|
+
res.end(JSON.stringify({ txid, peers: sent }))
|
|
1188
|
+
return
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// GET /api/address/:addr/history — proxy to WoC confirmed/history (web app compat)
|
|
1192
|
+
const apiHistMatch = path.match(/^\/api\/address\/([13][a-km-zA-HJ-NP-Z1-9]{24,33})\/history$/)
|
|
1193
|
+
if (req.method === 'GET' && apiHistMatch) {
|
|
1194
|
+
const addr = apiHistMatch[1]
|
|
1195
|
+
const cached = this._addressCache.get(addr)
|
|
1196
|
+
if (cached && Date.now() - cached.time < 60000) {
|
|
1197
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1198
|
+
res.end(JSON.stringify(cached.data))
|
|
1199
|
+
return
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
const resp = await fetch('https://api.whatsonchain.com/v1/bsv/main/address/' + addr + '/confirmed/history', { signal: AbortSignal.timeout(10000) })
|
|
1203
|
+
if (!resp.ok) throw new Error('WoC returned ' + resp.status)
|
|
1204
|
+
const data = await resp.json()
|
|
1205
|
+
const history = Array.isArray(data) ? data : (data.result || [])
|
|
1206
|
+
this._addressCache.set(addr, { data: history, time: Date.now() })
|
|
1207
|
+
if (this._addressCache.size > 100) {
|
|
1208
|
+
const oldest = this._addressCache.keys().next().value
|
|
1209
|
+
this._addressCache.delete(oldest)
|
|
1210
|
+
}
|
|
1211
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1212
|
+
res.end(JSON.stringify(history))
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
res.writeHead(502, { 'Content-Type': 'application/json' })
|
|
1215
|
+
res.end(JSON.stringify({ error: 'Failed to fetch address history: ' + err.message }))
|
|
1216
|
+
}
|
|
1217
|
+
return
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// GET /api/address/:addr/balance — sum UTXOs from GorillaPool (web app compat)
|
|
1221
|
+
const apiBalMatch = path.match(/^\/api\/address\/([13][a-km-zA-HJ-NP-Z1-9]{24,33})\/balance$/)
|
|
1222
|
+
if (req.method === 'GET' && apiBalMatch) {
|
|
1223
|
+
const addr = apiBalMatch[1]
|
|
1224
|
+
try {
|
|
1225
|
+
const resp = await fetch(
|
|
1226
|
+
`https://ordinals.gorillapool.io/api/txos/address/${addr}/unspent`,
|
|
1227
|
+
{ signal: AbortSignal.timeout(10000) }
|
|
1228
|
+
)
|
|
1229
|
+
if (!resp.ok) throw new Error(`GorillaPool ${resp.status}`)
|
|
1230
|
+
const data = await resp.json()
|
|
1231
|
+
const confirmed = data.reduce((sum, u) => sum + (u.satoshis || 0), 0)
|
|
1232
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1233
|
+
res.end(JSON.stringify({ confirmed, unconfirmed: 0 }))
|
|
1234
|
+
} catch (err) {
|
|
1235
|
+
res.writeHead(502, { 'Content-Type': 'application/json' })
|
|
1236
|
+
res.end(JSON.stringify({ error: 'Balance fetch failed: ' + err.message }))
|
|
1237
|
+
}
|
|
1238
|
+
return
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// GET /api/tx/:txid — full tx JSON via WoC (web app compat)
|
|
1242
|
+
const apiTxMatch = path.match(/^\/api\/tx\/([0-9a-f]{64})$/)
|
|
1243
|
+
if (req.method === 'GET' && apiTxMatch) {
|
|
1244
|
+
const txid = apiTxMatch[1]
|
|
1245
|
+
try {
|
|
1246
|
+
const resp = await fetch(`https://api.whatsonchain.com/v1/bsv/main/tx/${txid}`, { signal: AbortSignal.timeout(10000) })
|
|
1247
|
+
if (!resp.ok) throw new Error('WoC returned ' + resp.status)
|
|
1248
|
+
const data = await resp.json()
|
|
1249
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1250
|
+
res.end(JSON.stringify(data))
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
res.writeHead(502, { 'Content-Type': 'application/json' })
|
|
1253
|
+
res.end(JSON.stringify({ error: 'tx fetch failed: ' + err.message }))
|
|
1254
|
+
}
|
|
1255
|
+
return
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// GET /api/mesh/status — alias for /status (web app compat)
|
|
1259
|
+
if (req.method === 'GET' && path === '/api/mesh/status') {
|
|
1260
|
+
const status = await this.getStatus({ authenticated })
|
|
1261
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
1262
|
+
res.end(JSON.stringify(status))
|
|
1263
|
+
return
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1095
1266
|
res.writeHead(404)
|
|
1096
1267
|
res.end('Not Found')
|
|
1097
1268
|
}
|