Merge remote-tracking branch 'origin/main' into ark-merge

This commit is contained in:
Dorian
2026-07-14 22:08:55 +01:00
142 changed files with 7738 additions and 1336 deletions
+210 -10
View File
@@ -228,6 +228,25 @@ function seedMockState() {
return {
analyticsEnabled: false,
nodeVisibility: 'discoverable',
nostrDiscovery: true,
pendingPeerRequests: [
{
id: 'preq-demo-1',
from_nostr_pubkey: '7e2a9c4b1d8f3a6c5e0b9d2f7a4c1e8b3d6f9a2c5e8b1d4f7a0c3e6b9d2f5a8c',
from_nostr_npub: 'npub1demo0inbound0request0xyzq',
from_did: 'did:key:z6MkwPn5xQc8vT2sLb6eRu9dYf3gHa7mJzK1oDiW4nXvE8tq',
from_name: 'basement-node',
message: 'Hey — saw your node on the relay, mind if we peer?',
received_at: new Date(Date.now() - 45 * 60000).toISOString(),
state: 'pending',
outbound: false,
},
],
tollgateEnabled: true,
tollgatePrice: 21,
tollgateStepMs: 60000,
tollgateMinSteps: 5,
tollgateMint: 'https://mint.archy.demo',
cashuMints: [
'https://mint.minibits.cash/Bitcoin',
'https://stablenut.umint.cash',
@@ -1236,6 +1255,29 @@ for (const [prefixes, dir] of [
for (const p of prefixes) app.use(p, express.static(path.join(DOCKER_UI, dir)))
}
// Shells that reference /app/<id>/assets/... resolve to the frontend's shared assets.
// Demo placeholder for mempool: the real explorer needs a synced chain +
// electrs, which the public demo doesn't run. Show a branded "not available
// in demo" page instead of a 502. Self-contained (inline styles/logo) so it
// renders identically from the mock origin in dev and in the demo stack.
app.get(/^\/app\/mempool(\/.*)?$/, (_req, res) => {
res.type('html').send(`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Mempool</title></head>
<body style="margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#11131f;font-family:-apple-system,'Segoe UI',Roboto,sans-serif;">
<div style="text-align:center;padding:2rem;">
<div style="display:flex;gap:6px;justify-content:center;margin-bottom:1.25rem;">
<div style="width:34px;height:34px;border-radius:7px;background:#9339f4;"></div>
<div style="width:34px;height:34px;border-radius:7px;background:#105fb0;"></div>
<div style="width:34px;height:34px;border-radius:7px;background:#00a0dc;"></div>
<div style="width:34px;height:34px;border-radius:7px;background:#00c896;"></div>
</div>
<div style="font-size:1.6rem;font-weight:700;color:#fff;letter-spacing:.02em;">mempool</div>
<p style="color:rgba(255,255,255,.55);font-size:.95rem;margin-top:.75rem;">Not available in the demo</p>
<p style="color:rgba(255,255,255,.35);font-size:.8rem;max-width:340px;margin:0.5rem auto 0;">
The block explorer runs against your node's own synced chain — install Archipelago to explore mempool.space-style data privately.
</p>
</div>
</body></html>`)
})
app.get(/^\/app\/[^/]+\/assets\/(.*)$/, (req, res) => res.redirect(302, '/assets/' + req.params[0]))
// Dummy status for both the electrs-ui shell and the in-app ElectrumX sync screen.
@@ -2509,6 +2551,9 @@ app.post('/rpc/v1', (req, res) => {
nodes: [
{ did: 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2ReMkBe4bR6XBIDNq9', onion: 'disc1abc2def3ghi4jkl5mno6pqr7stu8vwx9yz.onion', pubkey: 'disc1pub', node_address: '192.168.1.50' },
{ did: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', onion: 'disc2xyz9wvu8tsr7qpo6nml5kji4hgf3edc2ba.onion', pubkey: 'disc2pub', node_address: '192.168.1.51' },
{ did: 'did:key:z6MkfV2sQpXm4d8YtR1nWc7uHb3eKj9gLa5xPzD6oTiN8rEw', onion: 'disc3mn04pq15rs26tu37vw48xy59za60bc71de.onion', pubkey: 'disc3pub', node_address: '192.168.1.72' },
{ did: 'did:key:z6MkrJ8pWx2yNc5vT9qLb4eHu7dKf1gMa3sPzE6oXiQ8nRvw', onion: 'disc4fg82hi93jk04lm15no26pq37rs48tu59vw.onion', pubkey: 'disc4pub', node_address: '100.72.19.44' },
{ did: 'did:key:z6MkhT4wQn8xPc2vL6sRb9eYu3dJf7gKa1mNzD5oWiE8tXvq', onion: 'disc5xy60za71bc82de93fg04hi15jk26lm37no.onion', pubkey: 'disc5pub', node_address: '100.101.7.23' },
],
},
})
@@ -2583,17 +2628,22 @@ app.post('/rpc/v1', (req, res) => {
}
case 'federation.invite': {
// trust level threads through the invite (backend parity):
// "Invite a Peer" sends observer, "Link Your Nodes" trusted.
const trust = params?.trust_level === 'observer' ? 'observer' : 'trusted'
const mockCode = 'fed1:' + Buffer.from(JSON.stringify({
did: 'did:key:z6MkTest228NodeInvite',
onion: 'self228abc2def3ghi4jkl5mno6pqr7stu8vwx.onion',
pubkey: 'aabbccdd',
token: 'mock-invite-token-' + Date.now(),
trust,
})).toString('base64url')
return res.json({
result: {
code: mockCode,
did: 'did:key:z6MkTest228NodeInvite',
onion: 'self228abc2def3ghi4jkl5mno6pqr7stu8vwx.onion',
trust_level: trust,
},
})
}
@@ -2672,20 +2722,32 @@ app.post('/rpc/v1', (req, res) => {
// =====================================================================
case 'mesh.status': {
globalThis.__meshHeaders ||= { announce_block_headers: false, receive_block_headers: true }
// Stateful enable/disable so the dev UI can demo the global
// "mesh device detected" setup modal: disable mesh on the Mesh page
// and the modal fires (device present but not connected).
globalThis.__meshCfg ||= { enabled: true, lora_region: 'EU_868', device_kind: null, channel_name: 'archipelago', advert_name: 'archy-228' }
const mc = globalThis.__meshCfg
return res.json({
result: {
enabled: true,
device_type: 'Meshcore',
enabled: mc.enabled,
device_type: mc.enabled ? 'Meshcore' : 'unknown',
device_path: '/dev/ttyUSB0',
device_connected: true,
firmware_version: '2.3.1',
self_node_id: 42,
self_advert_name: 'archy-228',
peer_count: 4,
channel_name: 'archipelago',
device_connected: mc.enabled,
firmware_version: mc.enabled ? '2.3.1' : null,
self_node_id: mc.enabled ? 42 : null,
self_advert_name: mc.advert_name,
peer_count: mc.enabled ? 4 : 0,
channel_name: mc.channel_name,
messages_sent: 23,
messages_received: 47,
detected_devices: ['/dev/ttyUSB0'],
device_present: true,
detected_device_info: [
{ path: '/dev/ttyUSB0', vid: '10c4', pid: 'ea60', product: 'HELTEC LoRa 32 V3', manufacturer: 'Heltec' },
],
lora_region: mc.lora_region,
device_kind: mc.device_kind,
region: mc.enabled ? mc.lora_region : null,
announce_block_headers: globalThis.__meshHeaders.announce_block_headers,
receive_block_headers: globalThis.__meshHeaders.receive_block_headers,
},
@@ -2796,7 +2858,16 @@ app.post('/rpc/v1', (req, res) => {
globalThis.__meshHeaders ||= { announce_block_headers: false, receive_block_headers: true }
if (params && typeof params.announce_block_headers === 'boolean') globalThis.__meshHeaders.announce_block_headers = params.announce_block_headers
if (params && typeof params.receive_block_headers === 'boolean') globalThis.__meshHeaders.receive_block_headers = params.receive_block_headers
return res.json({ result: { configured: true, ...globalThis.__meshHeaders } })
// Stateful radio settings (see mesh.status) — lets the dev UI demo
// the detected-device modal + the Device panel settings round-trip.
globalThis.__meshCfg ||= { enabled: true, lora_region: 'EU_868', device_kind: null, channel_name: 'archipelago', advert_name: 'archy-228' }
const cfg = globalThis.__meshCfg
if (params && typeof params.enabled === 'boolean') cfg.enabled = params.enabled
if (params && typeof params.lora_region === 'string') cfg.lora_region = params.lora_region || null
if (params && typeof params.device_kind === 'string') cfg.device_kind = params.device_kind === 'auto' ? null : params.device_kind
if (params && typeof params.channel_name === 'string') cfg.channel_name = params.channel_name
if (params && typeof params.advert_name === 'string') cfg.advert_name = params.advert_name
return res.json({ result: { configured: true, enabled: cfg.enabled, lora_region: cfg.lora_region, device_kind: cfg.device_kind, ...globalThis.__meshHeaders } })
}
case 'mesh.send-invoice': {
@@ -3554,7 +3625,8 @@ app.post('/rpc/v1', (req, res) => {
case 'update.list-mirrors': {
globalThis.__mockMirrors ||= [
{ url: 'http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/manifest.json', label: 'Origin (vps2)' },
// Neutral placeholder — the public demo must not reveal the real release-server address.
{ url: 'https://updates.archipelago.demo/releases/manifest.json', label: 'Origin' },
]
return res.json({ result: { mirrors: globalThis.__mockMirrors } })
}
@@ -3729,6 +3801,134 @@ app.post('/rpc/v1', (req, res) => {
return res.json({ result: { node_id: nodeId, history } })
}
// ── OpenWRT / TollGate gateway (demo: a thoroughly-used gateway) ──
case 'openwrt.get-status': {
return res.json({
result: {
host: '192.168.8.1',
hostname: 'archy-tollgate',
uptime_secs: 19 * 86400 + 7 * 3600 + 42 * 60,
release: {
openwrt_release: 'OpenWrt 24.10.1',
openwrt_version: 'r28597-0425664679',
openwrt_board_name: 'glinet,gl-mt3000',
openwrt_arch: 'aarch64_cortex-a53',
openwrt_target: 'mediatek/filogic',
},
tollgate: {
installed: true,
enabled: mockState.tollgateEnabled,
metric: 'milliseconds',
step_size_ms: mockState.tollgateStepMs,
price_per_step: mockState.tollgatePrice,
min_steps: mockState.tollgateMinSteps,
currency: 'sat',
mint_url: mockState.tollgateMint,
},
wifi_interfaces: [
{ section: 'default_radio0', ssid: 'TollGate-⚡-2.4G', device: 'radio0', encryption: 'none', network: 'lan', disabled: false },
{ section: 'default_radio1', ssid: 'TollGate-⚡-5G', device: 'radio1', encryption: 'none', network: 'lan', disabled: false },
{ section: 'wifinet2', ssid: 'Archy-Private', device: 'radio1', encryption: 'sae', network: 'lan', disabled: false },
],
wan: {
configured: true,
ssid: 'CasaDelSol-5G',
assoc_ssid: 'CasaDelSol-5G',
encryption: 'psk2',
ip: '192.168.1.187',
internet: true,
radio0_disabled: false,
sta_iface: 'wifinet1',
sta_state: 'COMPLETED',
wifi_log: 'wlan1: associated -> COMPLETED (RSSI -52 dBm)',
lan_ip: '172.16.0.1',
lan_netmask: '255.255.255.0',
dhcp_start: '100',
dhcp_limit: '150',
masq: true,
},
},
})
}
case 'openwrt.scan': {
return res.json({ result: { routers: ['192.168.8.1'] } })
}
case 'openwrt.scan-wifi': {
return res.json({
result: {
networks: [
{ ssid: 'CasaDelSol-5G', bssid: 'a4:3e:51:0b:77:21', signal: -52, channel: 44, encryption: 'psk2' },
{ ssid: 'CasaDelSol', bssid: 'a4:3e:51:0b:77:20', signal: -49, channel: 6, encryption: 'psk2' },
{ ssid: 'Vodafone-B221', bssid: '5c:35:3b:9a:12:f0', signal: -71, channel: 1, encryption: 'psk2' },
{ ssid: 'cafe-guest', bssid: '10:27:f5:c4:88:3a', signal: -78, channel: 11, encryption: 'none' },
],
},
})
}
case 'openwrt.provision-tollgate': {
if (params?.price_per_step != null) mockState.tollgatePrice = params.price_per_step
if (params?.step_size_ms != null) mockState.tollgateStepMs = params.step_size_ms
if (params?.min_steps != null) mockState.tollgateMinSteps = params.min_steps
if (params?.mint_url) mockState.tollgateMint = params.mint_url
if (params?.enabled != null) mockState.tollgateEnabled = !!params.enabled
return res.json({ result: { ok: true } })
}
case 'openwrt.configure-wan': {
return res.json({ result: { ok: true } })
}
// ── Nostr peer discovery (demo) ──────────────────────────────────
case 'nostr.discovery-status': {
return res.json({ result: { enabled: mockState.nostrDiscovery } })
}
case 'nostr.set-discovery': {
mockState.nostrDiscovery = !!params?.enabled
return res.json({ result: { enabled: mockState.nostrDiscovery } })
}
case 'handshake.discover': {
return res.json({
result: {
nodes: [
{ nostr_pubkey: '8f1c9a2b7d4e6f0a3c5b8d1e4f7a0c3b6e9d2f5a8b1c4e7d0a3f6c9b2e5d8a1f', nostr_npub: 'npub13uwf52m04893s79hxc7fmz57jd60jz4kd3nk28h5t2rr3l2xkq0qte0mfz', did: 'did:key:z6MkfV2sQpXm4d8YtR1nWc7uHb3eKj9gLa5xPzD6oTiN8rEw', version: '1.7.99' },
{ nostr_pubkey: '2a4c6e8f0b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a', nostr_npub: 'npub1z5jvknq0k8f7am7wp0946fmdxu2yjkzr8mfw2n0lqv4tksz3xy8s5c9m2d', did: 'did:key:z6MkrJ8pWx2yNc5vT9qLb4eHu7dKf1gMa3sPzE6oXiQ8nRvw', version: '1.7.99' },
{ nostr_pubkey: '5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d', nostr_npub: 'npub1t9k2vfmq7x8n3a5wj0c4bz6y1d8s2e7r5m9p0l3q6u4i8o2h7g1f5x9c3vk', did: 'did:key:z6MkhT4wQn8xPc2vL6sRb9eYu3dJf7gKa1mNzD5oWiE8tXvq', version: '1.7.98' },
{ nostr_pubkey: '9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e', nostr_npub: 'npub1w3e8rkmq2x7n9a4vj5c0bz1y6d3s8e2r7m4p9l0q5u8i3o6h2g7f1x4c9vs', did: 'did:key:z6MkgS9wPm3xNc7vK2sQb5eTu8dHf4gJa6mLzD1oXiR8nEvw', version: '1.7.99' },
],
},
})
}
case 'handshake.connect': {
const reqId = `preq-${Date.now()}`
mockState.pendingPeerRequests.push({
id: reqId,
from_nostr_pubkey: params?.recipient_nostr_pubkey || 'unknown',
from_nostr_npub: 'npub1outbounddemo',
from_did: 'did:key:zOutboundDemo',
from_name: params?.name || null,
message: params?.message || null,
received_at: new Date().toISOString(),
state: 'pending',
outbound: true,
})
return res.json({ result: { ok: true, sent_to: params?.recipient_nostr_pubkey || '', id: reqId } })
}
case 'handshake.poll': {
return res.json({ result: { polled: 4, new_requests: [], applied_invites: [], rejected_outbound: [], skipped: [] } })
}
case 'federation.list-pending-requests': {
return res.json({ result: { requests: mockState.pendingPeerRequests } })
}
case 'federation.approve-request': {
const r = mockState.pendingPeerRequests.find((x) => x.id === params?.id)
if (r) r.state = 'approved'
return res.json({ result: { approved: true, id: params?.id || '' } })
}
case 'federation.reject-request': {
const r = mockState.pendingPeerRequests.find((x) => x.id === params?.id)
if (r) r.state = 'rejected'
return res.json({ result: { rejected: true, id: params?.id || '' } })
}
default: {
console.log(`[RPC] Unknown method: ${method}`)
return res.json({