From 7219b82f9171130cfcbc38a1e764e7c589c42f0d Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Tue, 18 Mar 2025 11:09:45 +0000 Subject: [PATCH] Add CLN send via CLNRest --- wallets/cln/client.js | 38 ++++++++++++++++++++++++++++++++++++++ wallets/cln/index.js | 25 ++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/wallets/cln/client.js b/wallets/cln/client.js index 97b542b38..875d48d89 100644 --- a/wallets/cln/client.js +++ b/wallets/cln/client.js @@ -1 +1,39 @@ +import { fetchWithTimeout } from '@/lib/fetch' +import { assertContentTypeJson, assertResponseOk } from '@/lib/url' + export * from '@/wallets/cln' + +export async function testSendPayment ({ socket, runePay }, { logger, signal }) { + // TODO: check rune +} + +export async function sendPayment (bolt11, { socket, runePay }, { logger, signal }) { + const url = `https://${socket}/v1/pay` + + const headers = new Headers() + headers.set('Content-Type', 'application/json') + headers.set('Rune', runePay) + // see nodeId in lib/cln.js + headers.set('nodeId', '02cb2e2d5a6c5b17fa67b1a883e2973c82e328fb9bd08b2b156a9e23820c87a490') + + const body = new URLSearchParams() + body.append('bolt11', bolt11) + + const res = await fetchWithTimeout(url, { + method: 'POST', + headers, + body, + signal + }) + + assertResponseOk(res) + assertContentTypeJson(res) + + const payment = await res.json() + const preimage = payment.data.payment_preimage + if (!preimage) { + throw new Error(payment.reason) + } + + return preimage +} diff --git a/wallets/cln/index.js b/wallets/cln/index.js index 2e9eec71e..58749dbb0 100644 --- a/wallets/cln/index.js +++ b/wallets/cln/index.js @@ -14,7 +14,6 @@ export const fields = [ placeholder: '55.5.555.55:3010', hint: 'tor or clearnet', clear: true, - serverOnly: true, validate: string().socket() }, { @@ -33,10 +32,13 @@ export const fields = [ hint: 'must be restricted to method=invoice', clear: true, serverOnly: true, + optional: 'for receiving', + requiredWithout: 'runePay', validate: string().matches(B64_URL_REGEX, { message: 'invalid rune' }) .test({ name: 'rune', test: (v, context) => { + if (!v) return true const decoded = decodeRune(v) if (!decoded) return context.createError({ message: 'invalid rune' }) if (decoded.restrictions.length === 0) { @@ -52,6 +54,27 @@ export const fields = [ } }) }, + { + name: 'runePay', + label: 'pay rune', + type: 'text', + placeholder: 'S34KtUW-6gqS_hD_9cc_PNhfF-NinZyBOCgr1aIrark9NCZtZXRob2Q9aW52b2ljZQ==', + hint: 'must allow method=pay', + clear: true, + clientOnly: true, + optional: 'for sending', + requiredWithout: 'rune', + validate: string().matches(B64_URL_REGEX, { message: 'invalid rune' }) + .test({ + name: 'runePay', + test: (v, context) => { + if (!v) return true + const decoded = decodeRune(v) + if (!decoded) return context.createError({ message: 'invalid rune' }) + return true + } + }) + }, { name: 'cert', label: 'cert',