Add CLN autowithdrawal

This commit is contained in:
ekzyis 2024-07-07 10:41:44 +02:00
parent ebe741dc92
commit dddbb53792
6 changed files with 120 additions and 80 deletions

View File

@ -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' } })

View File

@ -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
}

100
components/wallet/cln.js Normal file
View File

@ -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
}
}

View File

@ -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',

View File

@ -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!) {

View File

@ -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 })
// }