feat: Phase 4 — off-grid Bitcoin relay, block headers, dead man's switch
- Typed message dispatch in listener (BlockHeader, TxRelay, LightningRelay, Alert, TxConfirmation) - Base64 encoding for binary payloads over LoRa (fixes NUL byte truncation) - Compact block header announcements (88 bytes, fits 160-byte LoRa limit) - Block header announcer: internet nodes auto-announce new blocks to Archy peers - TX relay: mesh-only nodes can broadcast transactions via internet-connected peers - Confirmation tracking: relay node monitors 1/3, 2/3, 3/3 confirmations, sends updates back - Dead man's switch background task with configurable interval and signed alert broadcast - 6 new RPC endpoints: relay-tx, block-headers, relay-lightning, deadman-status/configure/checkin - lnd.create-raw-tx: create signed TX without broadcasting (for mesh relay) - Web5 wallet: offline detection + "Send via mesh?" prompt with auto relay + confirmation polling - Mesh.vue: Off-Grid Bitcoin tab, Dead Man tab, Send Bitcoin/Lightning buttons - TX/Lightning relay sends only to Archy peers (not broadcast to all devices) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
eeb3c77e12
commit
70f1348c15
@@ -53,7 +53,8 @@ export interface MeshMessage {
|
||||
delivered: boolean
|
||||
encrypted: boolean
|
||||
message_type?: MeshMessageTypeLabel
|
||||
typed_payload?: InvoiceData | AlertData | CoordinateData | null
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
typed_payload?: Record<string, any> | null
|
||||
}
|
||||
|
||||
export interface InvoiceData {
|
||||
@@ -94,6 +95,14 @@ export interface AlertStatus {
|
||||
emergency_contacts: number
|
||||
}
|
||||
|
||||
export interface BlockHeader {
|
||||
height: number
|
||||
hash: string
|
||||
prev_hash: string
|
||||
timestamp: number
|
||||
announced_by: string
|
||||
}
|
||||
|
||||
export const useMeshStore = defineStore('mesh', () => {
|
||||
const status = ref<MeshStatus | null>(null)
|
||||
const peers = ref<MeshPeer[]>([])
|
||||
@@ -269,8 +278,71 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Phase 4: Off-Grid Bitcoin Operations ────────────────────────────
|
||||
|
||||
const deadmanStatus = ref<AlertStatus | null>(null)
|
||||
const blockHeaders = ref<BlockHeader[]>([])
|
||||
const latestBlockHeight = ref(0)
|
||||
|
||||
async function fetchDeadmanStatus() {
|
||||
try {
|
||||
deadmanStatus.value = await rpcClient.call<AlertStatus>({ method: 'mesh.deadman-status' })
|
||||
} catch {
|
||||
// Dead man switch not available
|
||||
}
|
||||
}
|
||||
|
||||
async function configureDeadman(config: {
|
||||
enabled?: boolean
|
||||
interval_secs?: number
|
||||
lat?: number
|
||||
lng?: number
|
||||
label?: string
|
||||
contacts?: string[]
|
||||
custom_message?: string
|
||||
auto_gps?: boolean
|
||||
}) {
|
||||
return rpcClient.call<AlertStatus>({
|
||||
method: 'mesh.deadman-configure',
|
||||
params: config,
|
||||
})
|
||||
}
|
||||
|
||||
async function deadmanCheckin() {
|
||||
return rpcClient.call<{ checked_in: boolean; time_remaining_secs: number }>({
|
||||
method: 'mesh.deadman-checkin',
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchBlockHeaders(count = 10) {
|
||||
try {
|
||||
const res = await rpcClient.call<{ headers: BlockHeader[]; latest_height: number; count: number }>({
|
||||
method: 'mesh.block-headers',
|
||||
params: { count },
|
||||
})
|
||||
blockHeaders.value = res.headers
|
||||
latestBlockHeight.value = res.latest_height
|
||||
} catch {
|
||||
// Block headers not available
|
||||
}
|
||||
}
|
||||
|
||||
async function relayTransaction(txHex: string) {
|
||||
return rpcClient.call<{ request_id: number; queued: boolean; tx_hex_len: number }>({
|
||||
method: 'mesh.relay-tx',
|
||||
params: { tx_hex: txHex },
|
||||
})
|
||||
}
|
||||
|
||||
async function relayLightning(bolt11: string, amountSats: number) {
|
||||
return rpcClient.call<{ request_id: number; queued: boolean; amount_sats: number }>({
|
||||
method: 'mesh.relay-lightning',
|
||||
params: { bolt11, amount_sats: amountSats },
|
||||
})
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([fetchStatus(), fetchPeers(), fetchMessages()])
|
||||
await Promise.all([fetchStatus(), fetchPeers(), fetchMessages(), fetchDeadmanStatus(), fetchBlockHeaders()])
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -282,6 +354,9 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
sending,
|
||||
unreadCounts,
|
||||
totalUnread,
|
||||
deadmanStatus,
|
||||
blockHeaders,
|
||||
latestBlockHeight,
|
||||
fetchStatus,
|
||||
fetchPeers,
|
||||
fetchMessages,
|
||||
@@ -296,5 +371,11 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
sendAlert,
|
||||
getSessionStatus,
|
||||
rotatePrekeys,
|
||||
fetchDeadmanStatus,
|
||||
configureDeadman,
|
||||
deadmanCheckin,
|
||||
fetchBlockHeaders,
|
||||
relayTransaction,
|
||||
relayLightning,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user