@relay-federation/bridge 0.3.0 → 0.3.2

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/cli.js CHANGED
@@ -63,18 +63,19 @@ async function cmdInit () {
63
63
  const config = await initConfig(dir)
64
64
 
65
65
  console.log('Bridge initialized!\n')
66
- console.log(` Config: ${dir}/config.json`)
67
- console.log(` Pubkey: ${config.pubkeyHex}`)
68
- console.log(` Address: ${config.address}`)
69
- console.log(` Secret: ${config.statusSecret}`)
66
+ console.log(` Name: ${config.name}`)
67
+ console.log(` Config: ${dir}/config.json`)
68
+ console.log(` Endpoint: ${config.endpoint}`)
69
+ console.log(` Pubkey: ${config.pubkeyHex}`)
70
+ console.log(` Address: ${config.address}`)
71
+ console.log(` Secret: ${config.statusSecret}`)
70
72
  console.log('')
71
73
  console.log(' Save your operator secret! You need it to log into the dashboard.')
72
74
  console.log('')
73
75
  console.log('Next steps:')
74
- console.log(` 1. Edit config.json set your WSS endpoint`)
75
- console.log(` 2. Fund your bridge: send BSV to ${config.address}`)
76
- console.log(' 3. Import the funding tx: relay-bridge fund <rawTxHex>')
77
- console.log(' 4. Run: relay-bridge register')
76
+ console.log(` 1. Fund your bridge: send BSV to ${config.address}`)
77
+ console.log(' 2. Import the funding tx: relay-bridge fund <rawTxHex>')
78
+ console.log(' 3. Run: relay-bridge register')
78
79
  }
79
80
 
80
81
  async function cmdSecret () {
@@ -319,9 +320,10 @@ async function cmdStart () {
319
320
  })
320
321
 
321
322
  // Wire peer health tracking
322
- peerManager.on('peer:connect', ({ pubkeyHex }) => {
323
+ peerManager.on('peer:connect', ({ pubkeyHex, endpoint }) => {
323
324
  peerHealth.recordSeen(pubkeyHex)
324
325
  scorer.setStakeAge(pubkeyHex, 7)
326
+ if (endpoint) gossipManager.addSeed({ pubkeyHex, endpoint, meshId: config.meshId })
325
327
  })
326
328
 
327
329
  peerManager.on('peer:disconnect', ({ pubkeyHex }) => {
@@ -429,6 +431,8 @@ async function cmdStart () {
429
431
  const { extractOpReturnData, decodePayload, PROTOCOL_PREFIX } = await import('../registry/lib/cbor.js')
430
432
  const { Transaction: BsvTx } = await import('@bsv/sdk')
431
433
 
434
+ // Registry bootstrapped via discoverNewPeers() after server start (no WoC dependency)
435
+
432
436
  watcher.on('utxo:received', async ({ txid, hash160 }) => {
433
437
  if (hash160 !== beaconHash160) return
434
438
 
@@ -640,14 +644,17 @@ async function cmdStart () {
640
644
  let gossipStarted = false
641
645
 
642
646
  // Start gossip after first peer connection completes.
643
- // With crypto handshake, peer:connect fires AFTER handshake verification,
644
- // so gossip won't race the handshake anymore.
647
+ // Delay 5s so all seed handshakes finish before gossip broadcasts
648
+ // (immediate broadcast would send announce/getpeers through connections
649
+ // whose inbound side is still waiting for verify — breaking the handshake).
645
650
  peerManager.on('peer:connect', () => {
646
651
  if (!gossipStarted) {
647
652
  gossipStarted = true
648
- gossipManager.start()
649
- gossipManager.requestPeersFromAll()
650
- console.log('Gossip started')
653
+ setTimeout(() => {
654
+ gossipManager.start()
655
+ gossipManager.requestPeersFromAll()
656
+ console.log('Gossip started')
657
+ }, 5000)
651
658
 
652
659
  // Periodic peer refresh — re-request peer lists every 10 minutes
653
660
  // Catches registrations missed during downtime or initial gossip
@@ -702,6 +709,7 @@ async function cmdStart () {
702
709
  // Connect to seed peers (accept both string URLs and {pubkeyHex, endpoint} objects)
703
710
  console.log(`Connecting to ${seedPeers.length} seed peer(s)...`)
704
711
  for (let i = 0; i < seedPeers.length; i++) {
712
+ if (i > 0) await new Promise(r => setTimeout(r, 2000)) // stagger to avoid handshake races
705
713
  const seed = seedPeers[i]
706
714
  const endpoint = typeof seed === 'string' ? seed : seed.endpoint
707
715
  const pubkey = typeof seed === 'string' ? `seed_${i}` : seed.pubkeyHex
@@ -769,6 +777,47 @@ async function cmdStart () {
769
777
  statusServer.startAppMonitoring()
770
778
  console.log(` Status: http://127.0.0.1:${statusPort}/status`)
771
779
 
780
+ // ── Peer discovery — bootstrap registry from seed peers, then periodic refresh ──
781
+ const knownEndpoints = new Set()
782
+ for (const sp of (config.seedPeers || [])) knownEndpoints.add(sp.endpoint)
783
+ knownEndpoints.add(config.endpoint)
784
+
785
+ async function discoverNewPeers () {
786
+ const peersToQuery = [...(config.seedPeers || [])]
787
+ for (const [, conn] of peerManager.peers) {
788
+ if (conn.endpoint && conn.readyState === 1) peersToQuery.push({ endpoint: conn.endpoint })
789
+ }
790
+ for (const peer of peersToQuery) {
791
+ try {
792
+ const ep = peer.endpoint || ''
793
+ const u = new URL(ep)
794
+ const statusUrl = 'http://' + u.hostname + ':' + (parseInt(u.port, 10) + 1000) + '/discover'
795
+ const res = await fetch(statusUrl, { signal: AbortSignal.timeout(5000) })
796
+ if (!res.ok) continue
797
+ const data = await res.json()
798
+ if (!data.bridges) continue
799
+ for (const b of data.bridges) {
800
+ if (!b.endpoint) continue
801
+ if (b.pubkeyHex) registeredPubkeys.add(b.pubkeyHex)
802
+ seedEndpoints.add(b.endpoint)
803
+ if (knownEndpoints.has(b.endpoint)) continue
804
+ knownEndpoints.add(b.endpoint)
805
+ const conn = peerManager.connectToPeer({ endpoint: b.endpoint, pubkeyHex: b.pubkeyHex })
806
+ if (conn) {
807
+ conn.on('open', () => performOutboundHandshake(conn))
808
+ const msg = `Discovered new peer: ${b.name || b.pubkeyHex?.slice(0, 16) || b.endpoint}`
809
+ console.log(msg)
810
+ statusServer.addLog(msg)
811
+ }
812
+ }
813
+ } catch {}
814
+ }
815
+ }
816
+ await discoverNewPeers()
817
+ console.log(` Registry: ${registeredPubkeys.size} trusted pubkeys after peer discovery`)
818
+ setTimeout(discoverNewPeers, 5000)
819
+ setInterval(discoverNewPeers, 300000)
820
+
772
821
  // ── 9. Log events (dual: console + status server ring buffer) ──
773
822
  peerManager.on('peer:connect', ({ pubkeyHex }) => {
774
823
  const msg = `Peer connected: ${pubkeyHex.slice(0, 16)}...`
@@ -978,7 +978,7 @@ async function fetchBridge(bridge) {
978
978
  const r = await fetch(bridge.url + '/status' + getAuthParam(bridge.url), { signal: AbortSignal.timeout(5000) });
979
979
  if (!r.ok) throw new Error('HTTP ' + r.status);
980
980
  const data = await r.json();
981
- data._name = bridge.name; data._url = bridge.url; data._error = null; data._lastSeen = Date.now();
981
+ data._name = (data.bridge && data.bridge.name) || bridge.name; data._url = bridge.url; data._error = null; data._lastSeen = Date.now();
982
982
  return data;
983
983
  } catch (e) {
984
984
  return { _name: bridge.name, _url: bridge.url, _error: e.message,
@@ -1009,7 +1009,7 @@ async function discoverBridges() {
1009
1009
  if (!b.statusUrl) continue;
1010
1010
  const base = b.statusUrl.replace(/\/status$/, '');
1011
1011
  if (!known.has(base)) {
1012
- const name = 'bridge-' + (b.pubkeyHex ? b.pubkeyHex.slice(0, 8) : known.size);
1012
+ const name = b.name || 'bridge-' + (b.pubkeyHex ? b.pubkeyHex.slice(0, 8) : known.size);
1013
1013
  known.set(base, name);
1014
1014
  }
1015
1015
  }
package/lib/config.js CHANGED
@@ -21,16 +21,29 @@ export function defaultConfigDir () {
21
21
  * @param {string} [dir] — Config directory (default: ~/.relay-bridge)
22
22
  * @returns {Promise<object>} The generated config
23
23
  */
24
- export async function initConfig (dir = DEFAULT_DIR) {
24
+ export async function initConfig (dir = DEFAULT_DIR, opts = {}) {
25
25
  const privKey = PrivateKey.fromRandom()
26
26
 
27
27
  const address = privKey.toPublicKey().toAddress()
28
28
 
29
+ // Auto-detect public IP
30
+ let publicIp = opts.ip || null
31
+ if (!publicIp) {
32
+ try {
33
+ const res = await fetch('https://api.ipify.org?format=json', { signal: AbortSignal.timeout(5000) })
34
+ if (res.ok) publicIp = (await res.json()).ip
35
+ } catch {}
36
+ }
37
+
38
+ const name = opts.name || (publicIp ? 'bridge-' + publicIp.split('.').pop() : 'bridge-' + privKey.toPublicKey().toString().slice(0, 8))
39
+ const endpoint = publicIp ? 'ws://' + publicIp + ':8333' : 'ws://your-bridge-ip:8333'
40
+
29
41
  const config = {
42
+ name,
30
43
  wif: privKey.toWif(),
31
44
  pubkeyHex: privKey.toPublicKey().toString(),
32
45
  address,
33
- endpoint: 'wss://your-bridge.example.com:8333',
46
+ endpoint,
34
47
  meshId: '70016',
35
48
  capabilities: ['tx_relay', 'header_sync', 'broadcast', 'address_history'],
36
49
  spvEndpoint: 'https://relay.indelible.one',
@@ -169,7 +169,7 @@ export class PeerManager extends EventEmitter {
169
169
  // If cryptographic handshake is available, use it
170
170
  if (opts.handshake && msg.nonce && Array.isArray(msg.versions)) {
171
171
  const isSeed = opts.seedEndpoints && opts.seedEndpoints.has(msg.endpoint)
172
- const result = opts.handshake.handleHello(msg, isSeed ? null : (opts.registeredPubkeys || null))
172
+ const result = opts.handshake.handleHello(msg, null) // cryptographic handshake is the security layer — no whitelist gate
173
173
  if (result.error) {
174
174
  ws.send(JSON.stringify({ type: 'error', error: result.error }))
175
175
  ws.close()
@@ -112,6 +112,7 @@ export class StatusServer {
112
112
 
113
113
  const status = {
114
114
  bridge: {
115
+ name: this._config.name || null,
115
116
  pubkeyHex: this._config.pubkeyHex || null,
116
117
  meshId: this._config.meshId || null,
117
118
  uptimeSeconds: Math.floor((Date.now() - this._startedAt) / 1000)
@@ -425,6 +426,7 @@ export class StatusServer {
425
426
  const bridges = []
426
427
  // Add self
427
428
  bridges.push({
429
+ name: this._config.name || null,
428
430
  pubkeyHex: this._config.pubkeyHex || null,
429
431
  endpoint: this._config.endpoint || null,
430
432
  meshId: this._config.meshId || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relay-federation/bridge",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Bridge server — WebSocket peering, header sync, tx relay, CLI",
5
5
  "type": "module",
6
6
  "bin": {