From 78789e0b2eaf91e2de6b4e488324fbec0fcefb9d Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 31 Jul 2026 00:49:21 -0700 Subject: Add Ark <-> Lightning via the ASP (captaind-native submarine swaps) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ASP is the HTLC counterparty (second.tech's protocol), so no external swap provider is needed: paying a bolt11 converts pubkey vtxos into ServerHtlcSend vtxos the server can only claim by revealing the invoice preimage; receiving grants ServerHtlcRecv vtxos claimed by revealing ours. Failure paths cosign the HTLC back (revocation) — proven end-to-end against regtest captaind, which independently derives the HTLC taproots. - ark/lightning.js: bolt11 decode, lightning fee schedules, all pay/receive gRPC-web wrappers (cosign, initiate, status, revocation, hold-invoice receive flow, anti-DoS attestation) - ark/send.js: arkoor builder generalized to policy outputs (pubkey / serverHtlcSend / serverHtlcRecv), two-leaf taproot trees, HTLC clause scripts, reusable per-part cosign helpers for package RPCs - ark/manager.js: ln-pay and ln-recv crash-safe action state machines (deterministic receive preimages from the seed, chain 5), mailbox lightning notifications, unpayable-invoice -> instant revocation - proto.js: lightning ArkInfo fields + mailbox lightning message decode - validate/refresh: policy-aware vtxo validation and reconstruction - features: bolt11 routes via ark when the balance covers it (Boltz stays the fallback), lightning-invoice receive on the Ark pane - tools/ark/ln-test.js: harness proving HTLC cosign + revocation against captaind; manager-test.js updated for the consolidated bc-container chain Co-Authored-By: Claude Fable 5 --- src/ark/proto.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'src/ark/proto.js') diff --git a/src/ark/proto.js b/src/ark/proto.js index ae2342e..bb8918d 100644 --- a/src/ark/proto.js +++ b/src/ark/proto.js @@ -368,6 +368,8 @@ export async function getArkInfo(ark) { boardFees: {}, refreshFees: { baseFeeSat: 0, ppmExpiryTable: [] }, offboardFees: { baseFeeSat: 0, fixedAdditionalVb: 0, ppmExpiryTable: [] }, + lnReceiveFees: { baseFeeSat: 0, ppm: 0 }, + lnSendFees: { minFeeSat: 0, baseFeeSat: 0, ppmExpiryTable: [] }, }; for (const { field, value } of pbFields(data)) { if (field === 1) info.network = td.decode(value); @@ -375,8 +377,12 @@ export async function getArkInfo(ark) { if (field === 3) info.roundIntervalSecs = Number(value); if (field === 5) info.vtxoExitDelta = Number(value); if (field === 6) info.vtxoExpiryDelta = Number(value); + if (field === 7) info.htlcSendExpiryDelta = Number(value); + if (field === 8) info.htlcExpiryDelta = Number(value); if (field === 10) info.requiredBoardConfirmations = Number(value); + if (field === 11) info.maxUserInvoiceCltvDelta = Number(value); if (field === 13) info.minBoardAmountSat = Number(value); + if (field === 15) info.lnReceiveAntiDosRequired = Number(value) !== 0; if (field === 16) info.mailboxPubkey = hex.encode(value); if (field === 20) info.maxOffboardInputs = Number(value); if (field === 18) { // FeeSchedule @@ -421,6 +427,26 @@ export async function getArkInfo(ark) { } info.refreshFees = rf; } + if (f.field === 4) { // lightning receive + for (const g of pbFields(f.value)) { + if (g.field === 1) info.lnReceiveFees.baseFeeSat = Number(g.value); + if (g.field === 2) info.lnReceiveFees.ppm = Number(g.value); + } + } + if (f.field === 5) { // lightning send + for (const g of pbFields(f.value)) { + if (g.field === 1) info.lnSendFees.minFeeSat = Number(g.value); + if (g.field === 2) info.lnSendFees.baseFeeSat = Number(g.value); + if (g.field === 3) { + const e = { thresholdBlocks: 0, ppm: 0 }; + for (const h of pbFields(g.value)) { + if (h.field === 1) e.thresholdBlocks = Number(h.value); + if (h.field === 2) e.ppm = Number(h.value); + } + info.lnSendFees.ppmExpiryTable.push(e); + } + } + } } } } @@ -464,6 +490,20 @@ export function decodeMailboxMessage(value) { if (a.field === 1) msg.vtxos.push(decodeVtxo(a.value)); } } + if (f.field === 5) { // IncomingLightningPaymentMessage + msg.kind = 'lnIncoming'; + for (const a of pbFields(f.value)) { + if (a.field === 1) msg.paymentHash = hex.encode(a.value); + if (a.field === 2) msg.amountMsat = Number(a.value); + } + } + if (f.field === 7) { // LightningSendFinishedMessage + msg.kind = 'lnSendFinished'; + for (const a of pbFields(f.value)) { + if (a.field === 1) msg.paymentHash = hex.encode(a.value); + if (a.field === 2) msg.preimage = hex.encode(a.value); + } + } } return msg; } -- cgit v1.2.3