@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.
@@ -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 history = await histRes.json()
33
+ const data = await histRes.json()
34
+ const history = Array.isArray(data) ? data : (data.result || [])
34
35
 
35
- if (!Array.isArray(history) || history.length === 0) {
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
  }
@@ -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 history = await resp.json()
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relay-federation/bridge",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Bridge server — WebSocket peering, header sync, tx relay, CLI",
5
5
  "type": "module",
6
6
  "bin": {