From dddbb5379218dd08ff8de12dadafa77df5c85d41 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sun, 7 Jul 2024 10:41:44 +0200 Subject: [PATCH] Add CLN autowithdrawal --- api/resolvers/wallet.js | 48 +++++------------- api/typeDefs/wallet.js | 1 - components/wallet/cln.js | 100 +++++++++++++++++++++++++++++++++++++ components/wallet/index.js | 3 +- fragments/wallet.js | 7 --- worker/autowithdraw.js | 41 ++------------- 6 files changed, 120 insertions(+), 80 deletions(-) create mode 100644 components/wallet/cln.js diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 9ea1270c..d5517b4d 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -4,20 +4,21 @@ import crypto, { timingSafeEqual } from 'crypto' import serialize from './serial' import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor' import { SELECT, itemQueryWithMeta } from './item' -import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '@/lib/format' -import { CLNAutowithdrawSchema, amountSchema, ssValidate, withdrawlSchema } from '@/lib/validate' -import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT, Wallet } from '@/lib/constants' +import { msatsToSats, msatsToSatsDecimal } from '@/lib/format' +import { amountSchema, ssValidate, withdrawlSchema } from '@/lib/validate' +import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '@/lib/constants' import { datePivot } from '@/lib/time' import assertGofacYourself from './ofac' import assertApiKeyNotPermitted from './apiKey' -import { createInvoice as createInvoiceCLN } from '@/lib/cln' +import { createInvoice as clnCreateInvoice } from '@/lib/cln' import { bolt11Tags } from '@/lib/bolt11' import { checkInvoice } from 'worker/wallet' import * as lnd from '@/components/wallet/lnd' import * as lnAddr from '@/components/wallet/lightning-address' +import * as cln from '@/components/wallet/cln' import { fetchLnAddrInvoice } from '@/lib/wallet' -export const SERVER_WALLET_DEFS = [lnd, lnAddr] +export const SERVER_WALLET_DEFS = [lnd, lnAddr, cln] function walletResolvers () { const resolvers = {} @@ -35,7 +36,13 @@ function walletResolvers () { testConnect: (data) => testConnect( data, - { me, models, addWalletLog, lnService: { authenticatedLndGrpc, createInvoice } } + { + me, + models, + addWalletLog, + lnService: { authenticatedLndGrpc, createInvoice }, + cln: { createInvoice: clnCreateInvoice } + } ) }, { settings, data }, { me, models }) } @@ -453,35 +460,6 @@ export default { return { id } }, ...walletResolvers(), - upsertWalletCLN: async (parent, { settings, ...data }, { me, models }) => { - data.cert = ensureB64(data.cert) - - const wallet = Wallet.CLN - return await upsertWallet( - { - schema: CLNAutowithdrawSchema, - wallet, - testConnect: async ({ socket, rune, cert }) => { - try { - const inv = await createInvoiceCLN({ - socket, - rune, - cert, - description: 'SN connection test', - msats: 'any', - expiry: 0 - }) - await addWalletLog({ wallet, level: 'SUCCESS', message: 'connected to CLN' }, { me, models }) - return inv - } catch (err) { - const details = err.details || err.message || err.toString?.() - await addWalletLog({ wallet, level: 'ERROR', message: `could not connect to CLN: ${details}` }, { me, models }) - throw err - } - } - }, - { settings, data }, { me, models }) - }, removeWallet: async (parent, { id }, { me, models }) => { if (!me) { throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } }) diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index 456c5339..db861c08 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -38,7 +38,6 @@ export default gql` cancelInvoice(hash: String!, hmac: String!): Invoice! dropBolt11(id: ID): Withdrawl ${walletTypeDefs()} - upsertWalletCLN(id: ID, socket: String!, rune: String!, cert: String, settings: AutowithdrawSettings!): Boolean removeWallet(id: ID!): Boolean deleteWalletLogs(wallet: String): Boolean } diff --git a/components/wallet/cln.js b/components/wallet/cln.js new file mode 100644 index 00000000..27eb4009 --- /dev/null +++ b/components/wallet/cln.js @@ -0,0 +1,100 @@ +import React from 'react' +import { CLNAutowithdrawSchema } from '@/lib/validate' +import { ensureB64 } from '@/lib/format' + +export const name = 'cln' + +export const fields = [ + { + name: 'socket', + label: 'rest host and port', + type: 'text', + placeholder: '55.5.555.55:3010', + hint: 'tor or clearnet', + clear: true + }, + { + name: 'rune', + label: 'invoice only rune', + help: { + text: 'We only accept runes that *only* allow `method=invoice`.\nRun this to generate one:\n\n```lightning-cli createrune restrictions=\'["method=invoice"]\'```' + }, + type: 'text', + placeholder: 'S34KtUW-6gqS_hD_9cc_PNhfF-NinZyBOCgr1aIrark9NCZtZXRob2Q9aW52b2ljZQ==', + hint: 'must be restricted to method=invoice', + clear: true + }, + { + name: 'cert', + label: 'cert', + type: 'text', + placeholder: 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNNVENDQWRpZ0F3SUJBZ0lRSHBFdFdrcGJwZHV4RVF2eVBPc3NWVEFLQmdncWhrak9QUVFEQWpBdk1SOHcKSFFZRFZRUUtFeFpzYm1RZ1lYVjBiMmRsYm1WeVlYUmxaQ0JqWlhKME1Rd3dDZ1lEVlFRREV3TmliMkl3SGhjTgpNalF3TVRBM01qQXhORE0wV2hjTk1qVXdNekF6TWpBeE5ETTBXakF2TVI4d0hRWURWUVFLRXhac2JtUWdZWFYwCmIyZGxibVZ5WVhSbFpDQmpaWEowTVF3d0NnWURWUVFERXdOaWIySXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak8KUFFNQkJ3TkNBQVJUS3NMVk5oZnhqb1FLVDlkVVdDbzUzSmQwTnBuL1BtYi9LUE02M1JxbU52dFYvdFk4NjJJZwpSbE41cmNHRnBEajhUeFc2OVhIK0pTcHpjWDdlN3N0Um80SFZNSUhTTUE0R0ExVWREd0VCL3dRRUF3SUNwREFUCkJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCVDAKMnh3V25GeHRUNzI0MWxwZlNoNm9FWi9UMWpCN0JnTlZIUkVFZERCeWdnTmliMktDQ1d4dlkyRnNhRzl6ZElJRApZbTlpZ2d4d2IyeGhjaTF1TVMxaWIyS0NGR2h2YzNRdVpHOWphMlZ5TG1sdWRHVnlibUZzZ2dSMWJtbDRnZ3AxCmJtbDRjR0ZqYTJWMGdnZGlkV1pqYjI1dWh3Ui9BQUFCaHhBQUFBQUFBQUFBQUFBQUFBQUFBQUFCaHdTc0VnQUQKTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUEwUTlkRXdoNXpPRnpwL3hYeHNpemh5SkxNVG5yazU1VWx1NHJPRwo4WW52QWlBVGt4U3p3Y3hZZnFscGx0UlNIbmd0NUJFcDBzcXlHL05nenBzb2pmMGNqQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K', + optional: React.createElement( + React.Fragment, + {}, + 'optional if from ', + React.createElement('a', { href: 'https://en.wikipedia.org/wiki/Certificate_authority', target: '_blank', rel: 'noreferrer' }, 'CA'), + ' (e.g. voltage)'), + hint: 'hex or base64 encoded', + clear: true + } +] + +export const card = { + title: 'CLN', + subtitle: React.createElement( + React.Fragment, + {}, + 'autowithdraw to your Core Lightning node via ', + React.createElement('a', { href: 'https://docs.corelightning.org/docs/rest', target: '_blank', rel: 'noreferrer' }, 'CLNRest') + ), + badges: ['receive only', 'non-custodialish'] +} + +export const schema = CLNAutowithdrawSchema + +export const server = { + walletType: 'CLN', + walletField: 'walletCLN', + resolverName: 'upsertWalletCLN', + testConnect: async ( + { socket, rune, cert }, + { me, models, addWalletLog, cln: { createInvoice } } + ) => { + try { + cert = ensureB64(cert) + + const inv = await createInvoice({ + socket, + rune, + cert, + description: 'SN connection test', + msats: 'any', + expiry: 0 + }) + await addWalletLog({ wallet: { type: 'CLN' }, level: 'SUCCESS', message: 'connected to CLN' }, { me, models }) + return inv + } catch (err) { + const details = err.details || err.message || err.toString?.() + await addWalletLog({ wallet: { type: 'CLN' }, level: 'ERROR', message: `could not connect to CLN: ${details}` }, { me, models }) + throw err + } + }, + createInvoice: async ( + { amount }, + { socket, rune, cert }, + { me, models, lnd, cln: { createInvoice } } + ) => { + cert = ensureB64(cert) + + const inv = await createInvoice({ + socket, + rune, + cert, + description: me.hideInvoiceDesc ? undefined : 'autowithdraw to CLN from SN', + msats: amount + 'sat', + expiry: 360 + }) + return inv.bolt11 + } +} diff --git a/components/wallet/index.js b/components/wallet/index.js index 8b49c0fa..ad5b26a7 100644 --- a/components/wallet/index.js +++ b/components/wallet/index.js @@ -10,12 +10,13 @@ import * as nwc from '@/components/wallet/nwc' import * as lnc from '@/components/wallet/lnc' import * as lnd from '@/components/wallet/lnd' import * as lnAddr from '@/components/wallet/lightning-address' +import * as cln from '@/components/wallet/cln' import { gql, useApolloClient, useQuery } from '@apollo/client' import { REMOVE_WALLET, WALLET_BY_TYPE } from '@/fragments/wallet' import { autowithdrawInitial } from '../autowithdraw-shared' // wallet definitions -export const WALLET_DEFS = [lnbits, nwc, lnc, lnd, lnAddr] +export const WALLET_DEFS = [lnbits, nwc, lnc, lnd, lnAddr, cln] export const Status = { Initialized: 'Initialized', diff --git a/fragments/wallet.js b/fragments/wallet.js index c33e1660..b1515f88 100644 --- a/fragments/wallet.js +++ b/fragments/wallet.js @@ -100,13 +100,6 @@ export const SEND_TO_LNADDR = gql` } }` -export const UPSERT_WALLET_CLN = -gql` -mutation upsertWalletCLN($id: ID, $socket: String!, $rune: String!, $cert: String, $settings: AutowithdrawSettings!) { - upsertWalletCLN(id: $id, socket: $socket, rune: $rune, cert: $cert, settings: $settings) -} -` - export const REMOVE_WALLET = gql` mutation removeWallet($id: ID!) { diff --git a/worker/autowithdraw.js b/worker/autowithdraw.js index 02e6c94b..981f0ea2 100644 --- a/worker/autowithdraw.js +++ b/worker/autowithdraw.js @@ -1,8 +1,7 @@ import { authenticatedLndGrpc, createInvoice as lndCreateInvoice, getIdentity, decodePaymentRequest } from 'ln-service' import { msatsToSats, satsToMsats } from '@/lib/format' -// import { datePivot } from '@/lib/time' -import { createWithdrawal, /* sendToLnAddr, */ addWalletLog, SERVER_WALLET_DEFS } from '@/api/resolvers/wallet' -// import { createInvoice as createInvoiceCLN } from '@/lib/cln' +import { createWithdrawal, addWalletLog, SERVER_WALLET_DEFS } from '@/api/resolvers/wallet' +import { createInvoice as clnCreateInvoice } from '@/lib/cln' export async function autoWithdraw ({ data: { id }, models, lnd }) { const user = await models.user.findUnique({ where: { id } }) @@ -106,41 +105,11 @@ async function autowithdraw ( createInvoice: lndCreateInvoice, getIdentity, decodePaymentRequest + }, + cln: { + createInvoice: clnCreateInvoice } }) return await createWithdrawal(null, { invoice: bolt11, maxFee }, { me, models, lnd, walletId: wallet.id }) } - -// async function autowithdrawCLN ({ amount, maxFee }, { me, models, lnd }) { -// if (!me) { -// throw new Error('me not specified') -// } -// -// const wallet = await models.wallet.findFirst({ -// where: { -// userId: me.id, -// type: Wallet.CLN.type -// }, -// include: { -// walletCLN: true -// } -// }) -// -// if (!wallet || !wallet.walletCLN) { -// throw new Error('no cln wallet found') -// } -// -// const { walletCLN: { cert, rune, socket } } = wallet -// -// const inv = await createInvoiceCLN({ -// socket, -// rune, -// cert, -// description: me.hideInvoiceDesc ? undefined : 'autowithdraw to CLN from SN', -// msats: amount + 'sat', -// expiry: 360 -// }) -// -// return await createWithdrawal(null, { invoice: inv.bolt11, maxFee }, { me, models, lnd, walletId: wallet.id }) -// }