Add CLN autowithdrawal
This commit is contained in:
		
							parent
							
								
									ebe741dc92
								
							
						
					
					
						commit
						dddbb53792
					
				@ -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' } })
 | 
			
		||||
 | 
			
		||||
@ -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
									
								
							
							
						
						
									
										100
									
								
								components/wallet/cln.js
									
									
									
									
									
										Normal 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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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',
 | 
			
		||||
 | 
			
		||||
@ -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!) {
 | 
			
		||||
 | 
			
		||||
@ -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 })
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user