diff options
| author | Adam Soltys <[email protected]> | 2026-10-16 13:05:15 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-16 13:05:15 -0700 |
| commit | 73bffd93caefa53af3a5d28e70059a3cfbe68939 (patch) | |
| tree | 1f27599810674431205bb53e62416684261c2783 /bridge/ln.js | |
| parent | 294a49598e43e3e6f9e34f0246bbea41837359f9 (diff) | |
| download | coinosv3-73bffd93caefa53af3a5d28e70059a3cfbe68939.tar.gz coinosv3-73bffd93caefa53af3a5d28e70059a3cfbe68939.zip | |
The server-side services moved out: names, nwcpush, blossom and bridge now live in coinosv3-server. The coinos-server agent on desk extracted them (2026-10-16, after the names.json truncation of 10-15: the registrar is on SQLite there, the drift that had only ever existed on cs is committed there, and names/nwcpush/blossom on cs run from a clone of that repo, deployed by git push through its own hook). Removed here with the tests that went with them (registry-ownership-test, deepl-translate.test); the README points at the new repository and AGENTS.md says how service changes deploy now (git push from ~/coinosv3-server, never scp). feed/ and vmedia/ stay for the moment — they are in coinosv3-server too but still deploy from ~/public-feed and ~/vmedia the old way — with public-feed-cache-test. sync-blob-test spawns blossom from the sibling coinosv3-server checkout (COINOSV3_SERVER overrides) and says so when it isn't there; it passes.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J1Kon8bxJoHRpJdSzshCkp
Diffstat (limited to 'bridge/ln.js')
| -rw-r--r-- | bridge/ln.js | 116 |
1 files changed, 0 insertions, 116 deletions
diff --git a/bridge/ln.js b/bridge/ln.js deleted file mode 100644 index cf2eab4..0000000 --- a/bridge/ln.js +++ /dev/null @@ -1,116 +0,0 @@ -// Lightning backend for the swap bridge: pay a bolt11 and report the -// preimage. Deliberately tiny — the bridge only ever needs to *send* over -// Lightning (an ark->LN swap), because an ASP's LN *receive* is already free. -// -// Two adapters, both CLN, both using named parameters: -// 'socket' — lightning-rpc unix socket (production: mount the socket) -// 'docker' — `docker exec <container> lightning-cli` (regtest convenience) - -import { execFile } from 'node:child_process'; -import { createConnection } from 'node:net'; - -const run = (cmd, args) => new Promise((resolve, reject) => { - execFile(cmd, args, { maxBuffer: 8 << 20 }, (err, stdout, stderr) => { - if (err) return reject(new Error((stderr || err.message).trim().slice(0, 400))); - resolve(stdout); - }); -}); - -// CLN prints progress lines before the JSON body on some commands. -const parseJson = (s) => { - const i = s.indexOf('{'); - if (i < 0) throw new Error('no JSON in CLN response: ' + s.slice(0, 200)); - return JSON.parse(s.slice(i)); -}; - -function dockerAdapter({ container, network }) { - return { - async call(method, params = {}) { - const args = ['exec', container, 'lightning-cli', `--${network}`, method, - ...Object.entries(params).map(([k, v]) => `${k}=${v}`)]; - return parseJson(await run('docker', args)); - }, - }; -} - -function socketAdapter({ path }) { - let id = 0; - return { - call(method, params = {}) { - return new Promise((resolve, reject) => { - const sock = createConnection(path); - let buf = ''; - sock.on('error', reject); - sock.on('connect', () => sock.write(JSON.stringify({ - jsonrpc: '2.0', id: ++id, method, params, - }) + '\n')); - sock.on('data', (d) => { - buf += d; - let nl; - while ((nl = buf.indexOf('\n')) >= 0) { - const line = buf.slice(0, nl).trim(); - buf = buf.slice(nl + 1); - if (!line) continue; - let msg; - try { msg = JSON.parse(line); } catch { continue; } - sock.end(); - if (msg.error) return reject(new Error(JSON.stringify(msg.error).slice(0, 400))); - return resolve(msg.result); - } - }); - }); - }, - }; -} - -export function lnBackend(cfg) { - const rpc = cfg.kind === 'docker' ? dockerAdapter(cfg) : socketAdapter(cfg); - - return { - // Raw RPC passthrough — the names registrar drives offers/waitanyinvoice. - call: (method, params) => rpc.call(method, params), - - async info() { - const r = await rpc.call('getinfo'); - return { id: r.id, alias: r.alias, blockheight: r.blockheight, network: r.network }; - }, - - // Spendable outbound capacity, so the bridge can refuse a swap it can't - // route rather than taking an HTLC it will only have to refund. - async outboundSat() { - const r = await rpc.call('listpeerchannels'); - return (r.channels || []) - .filter((c) => c.state === 'CHANNELD_NORMAL' && c.peer_connected) - .reduce((n, c) => n + Math.floor((c.spendable_msat ?? 0) / 1000), 0); - }, - - // Pay a bolt11. Resolves { preimage, feeSat }; throws otherwise. - // maxfee caps routing spend so a swap can't cost more than we charged. - async pay(bolt11, { maxfeeSat, retryFor = 60 } = {}) { - const params = { bolt11, retry_for: retryFor }; - if (maxfeeSat != null) params.maxfee = `${Math.max(0, Math.floor(maxfeeSat))}sat`; - const r = await rpc.call('pay', params); - if (r.status && r.status !== 'complete') throw new Error(`payment not complete: ${r.status}`); - if (!r.payment_preimage) throw new Error('payment returned no preimage'); - return { - preimage: r.payment_preimage, - feeSat: Math.ceil((((r.amount_sent_msat ?? 0) - (r.amount_msat ?? 0)) || 0) / 1000), - }; - }, - - // Has this payment hash already been paid by us? Checked before paying so - // a crashed-and-resumed bridge never double-pays. - async paidPreimage(paymentHash) { - const r = await rpc.call('listpays', { payment_hash: paymentHash }).catch(() => null); - const done = (r?.pays || []).find((p) => p.status === 'complete'); - return done?.preimage || null; - }, - - // Is a payment still in flight? A pending pay must not be retried and - // must not be refunded — its preimage may still arrive. - async isPending(paymentHash) { - const r = await rpc.call('listpays', { payment_hash: paymentHash }).catch(() => null); - return (r?.pays || []).some((p) => p.status === 'pending'); - }, - }; -} |
