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
4b7c765cd1
commit
d1ac098edb
@@ -408,6 +408,78 @@ pub fn parse_channel_msg_v1(data: &[u8]) -> Result<(u8, String)> {
|
||||
Ok((channel_idx, text))
|
||||
}
|
||||
|
||||
// ─── Raw-bytes variants for typed message detection ────────────────────
|
||||
|
||||
/// Parse RESP_CONTACT_MSG_V3 returning raw payload bytes (not UTF-8 lossy).
|
||||
/// Returns (sender_pubkey_prefix_hex, raw_payload_bytes, snr).
|
||||
pub fn parse_contact_msg_v3_raw(data: &[u8]) -> Result<(String, Vec<u8>, i8)> {
|
||||
if data.len() < 15 {
|
||||
anyhow::bail!("Contact message too short: {} bytes", data.len());
|
||||
}
|
||||
let snr = data[0] as i8;
|
||||
let pubkey_prefix = hex::encode(&data[3..9]);
|
||||
let txt_type = data[10];
|
||||
let text_start = if txt_type == 2 { 19 } else { 15 };
|
||||
let payload = if data.len() > text_start {
|
||||
data[text_start..].to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
Ok((pubkey_prefix, payload, snr))
|
||||
}
|
||||
|
||||
/// Parse RESP_CONTACT_MSG returning raw payload bytes.
|
||||
/// Returns (sender_pubkey_prefix_hex, raw_payload_bytes).
|
||||
pub fn parse_contact_msg_v1_raw(data: &[u8]) -> Result<(String, Vec<u8>)> {
|
||||
if data.len() < 12 {
|
||||
anyhow::bail!("Contact message v1 too short: {} bytes", data.len());
|
||||
}
|
||||
let pubkey_prefix = hex::encode(&data[0..6]);
|
||||
let txt_type = data[7];
|
||||
let text_start = if txt_type == 2 { 16 } else { 12 };
|
||||
let payload = if data.len() > text_start {
|
||||
data[text_start..].to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
Ok((pubkey_prefix, payload))
|
||||
}
|
||||
|
||||
/// Parse RESP_CHANNEL_MSG_V3 returning raw payload bytes.
|
||||
/// Returns (channel_idx, raw_payload_bytes).
|
||||
pub fn parse_channel_msg_v3_raw(data: &[u8]) -> Result<(u8, Vec<u8>)> {
|
||||
if data.len() < 7 {
|
||||
anyhow::bail!("Channel message too short: {} bytes", data.len());
|
||||
}
|
||||
let channel_idx = data[0];
|
||||
let payload = if data.len() > 7 {
|
||||
let mut p = data[7..].to_vec();
|
||||
// Strip trailing NUL bytes
|
||||
while p.last() == Some(&0) { p.pop(); }
|
||||
p
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
Ok((channel_idx, payload))
|
||||
}
|
||||
|
||||
/// Parse RESP_CHANNEL_MSG returning raw payload bytes.
|
||||
/// Returns (channel_idx, raw_payload_bytes).
|
||||
pub fn parse_channel_msg_v1_raw(data: &[u8]) -> Result<(u8, Vec<u8>)> {
|
||||
if data.len() < 7 {
|
||||
anyhow::bail!("Channel message v1 too short: {} bytes", data.len());
|
||||
}
|
||||
let channel_idx = data[0];
|
||||
let payload = if data.len() > 7 {
|
||||
let mut p = data[7..].to_vec();
|
||||
while p.last() == Some(&0) { p.pop(); }
|
||||
p
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
Ok((channel_idx, payload))
|
||||
}
|
||||
|
||||
/// Parse RESP_ERR (0x01). Returns descriptive error string.
|
||||
pub fn parse_error(data: &[u8]) -> String {
|
||||
if data.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user