Allow deletion of wallet logs (#1101)
* Allow deletion of wallet logs * Refactor wallet logs client<>server glue code * Use variant='link' and className='text-muted fw-bold nav-link' for clear & cancel There is a bug though: 'clear' stays highlighted after modal is closed * Include wallet in toast * Delete logs on logout * Fix ugly wallet name in confirm dialog * Fix clear still highlighted after modal closed * Only delete client wallet logs * Fix ugly wallet name in toast * Fix bad search and replace * Use Wallet object as constant * Also delete LNC logs on logout --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
e5f8c4e8e8
commit
4961cc045b
|
@ -7,7 +7,7 @@ import { SELECT } from './item'
|
|||
import { lnAddrOptions } from '@/lib/lnurl'
|
||||
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '@/lib/format'
|
||||
import { CLNAutowithdrawSchema, LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '@/lib/validate'
|
||||
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '@/lib/constants'
|
||||
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT, Wallet } from '@/lib/constants'
|
||||
import { datePivot } from '@/lib/time'
|
||||
import assertGofacYourself from './ofac'
|
||||
import assertApiKeyNotPermitted from './apiKey'
|
||||
|
@ -434,11 +434,11 @@ export default {
|
|||
data.macaroon = ensureB64(data.macaroon)
|
||||
data.cert = ensureB64(data.cert)
|
||||
|
||||
const walletType = 'LND'
|
||||
const wallet = Wallet.LND
|
||||
return await upsertWallet(
|
||||
{
|
||||
schema: LNDAutowithdrawSchema,
|
||||
walletType,
|
||||
wallet,
|
||||
testConnect: async ({ cert, macaroon, socket }) => {
|
||||
try {
|
||||
const { lnd } = await authenticatedLndGrpc({
|
||||
|
@ -453,12 +453,12 @@ export default {
|
|||
expires_at: new Date()
|
||||
})
|
||||
// we wrap both calls in one try/catch since connection attempts happen on RPC calls
|
||||
await addWalletLog({ wallet: walletType, level: 'SUCCESS', message: 'connected to LND' }, { me, models })
|
||||
await addWalletLog({ wallet, level: 'SUCCESS', message: 'connected to LND' }, { me, models })
|
||||
return inv
|
||||
} catch (err) {
|
||||
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
||||
const details = err[2]?.err?.details || err.message || err.toString?.()
|
||||
await addWalletLog({ wallet: walletType, level: 'ERROR', message: `could not connect to LND: ${details}` }, { me, models })
|
||||
await addWalletLog({ wallet, level: 'ERROR', message: `could not connect to LND: ${details}` }, { me, models })
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
@ -468,11 +468,11 @@ export default {
|
|||
upsertWalletCLN: async (parent, { settings, ...data }, { me, models }) => {
|
||||
data.cert = ensureB64(data.cert)
|
||||
|
||||
const walletType = 'CLN'
|
||||
const wallet = Wallet.CLN
|
||||
return await upsertWallet(
|
||||
{
|
||||
schema: CLNAutowithdrawSchema,
|
||||
walletType,
|
||||
wallet,
|
||||
testConnect: async ({ socket, rune, cert }) => {
|
||||
try {
|
||||
const inv = await createInvoiceCLN({
|
||||
|
@ -483,11 +483,11 @@ export default {
|
|||
msats: 'any',
|
||||
expiry: 0
|
||||
})
|
||||
await addWalletLog({ wallet: walletType, level: 'SUCCESS', message: 'connected to CLN' }, { me, models })
|
||||
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: walletType, level: 'ERROR', message: `could not connect to CLN: ${details}` }, { me, models })
|
||||
await addWalletLog({ wallet, level: 'ERROR', message: `could not connect to CLN: ${details}` }, { me, models })
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
@ -495,14 +495,14 @@ export default {
|
|||
{ settings, data }, { me, models })
|
||||
},
|
||||
upsertWalletLNAddr: async (parent, { settings, ...data }, { me, models }) => {
|
||||
const walletType = 'LIGHTNING_ADDRESS'
|
||||
const wallet = Wallet.LnAddr
|
||||
return await upsertWallet(
|
||||
{
|
||||
schema: lnAddrAutowithdrawSchema,
|
||||
walletType,
|
||||
wallet,
|
||||
testConnect: async ({ address }) => {
|
||||
const options = await lnAddrOptions(address)
|
||||
await addWalletLog({ wallet: walletType, level: 'SUCCESS', message: 'fetched payment details' }, { me, models })
|
||||
await addWalletLog({ wallet, level: 'SUCCESS', message: 'fetched payment details' }, { me, models })
|
||||
return options
|
||||
}
|
||||
},
|
||||
|
@ -523,6 +523,15 @@ export default {
|
|||
models.walletLog.create({ data: { userId: me.id, wallet: wallet.type, level: 'SUCCESS', message: 'wallet deleted' } })
|
||||
])
|
||||
|
||||
return true
|
||||
},
|
||||
deleteWalletLogs: async (parent, { wallet }, { me, models }) => {
|
||||
if (!me) {
|
||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
||||
}
|
||||
|
||||
await models.walletLog.deleteMany({ where: { userId: me.id, wallet } })
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
@ -557,14 +566,14 @@ export default {
|
|||
|
||||
export const addWalletLog = async ({ wallet, level, message }, { me, models }) => {
|
||||
try {
|
||||
await models.walletLog.create({ data: { userId: me.id, wallet, level, message } })
|
||||
await models.walletLog.create({ data: { userId: me.id, wallet: wallet.type, level, message } })
|
||||
} catch (err) {
|
||||
console.error('error creating wallet log:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function upsertWallet (
|
||||
{ schema, walletType, testConnect }, { settings, data }, { me, models }) {
|
||||
{ schema, wallet, testConnect }, { settings, data }, { me, models }) {
|
||||
if (!me) {
|
||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
||||
}
|
||||
|
@ -577,7 +586,7 @@ async function upsertWallet (
|
|||
await testConnect(data)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
await addWalletLog({ wallet: walletType, level: 'ERROR', message: 'failed to attach wallet' }, { me, models })
|
||||
await addWalletLog({ wallet, level: 'ERROR', message: 'failed to attach wallet' }, { me, models })
|
||||
throw new GraphQLError('failed to connect to wallet', { extensions: { code: 'BAD_INPUT' } })
|
||||
}
|
||||
}
|
||||
|
@ -607,16 +616,13 @@ async function upsertWallet (
|
|||
}))
|
||||
}
|
||||
|
||||
const walletName = walletType === 'LND'
|
||||
? 'walletLND'
|
||||
: walletType === 'CLN' ? 'walletCLN' : 'walletLightningAddress'
|
||||
if (id) {
|
||||
txs.push(
|
||||
models.wallet.update({
|
||||
where: { id: Number(id), userId: me.id },
|
||||
data: {
|
||||
priority: priority ? 1 : 0,
|
||||
[walletName]: {
|
||||
[wallet.field]: {
|
||||
update: {
|
||||
where: { walletId: Number(id) },
|
||||
data: walletData
|
||||
|
@ -624,7 +630,7 @@ async function upsertWallet (
|
|||
}
|
||||
}
|
||||
}),
|
||||
models.walletLog.create({ data: { userId: me.id, wallet: walletType, level: 'SUCCESS', message: 'wallet updated' } })
|
||||
models.walletLog.create({ data: { userId: me.id, wallet: wallet.type, level: 'SUCCESS', message: 'wallet updated' } })
|
||||
)
|
||||
} else {
|
||||
txs.push(
|
||||
|
@ -632,13 +638,13 @@ async function upsertWallet (
|
|||
data: {
|
||||
priority: Number(priority),
|
||||
userId: me.id,
|
||||
type: walletType,
|
||||
[walletName]: {
|
||||
type: wallet.type,
|
||||
[wallet.field]: {
|
||||
create: walletData
|
||||
}
|
||||
}
|
||||
}),
|
||||
models.walletLog.create({ data: { userId: me.id, wallet: walletType, level: 'SUCCESS', message: 'wallet created' } })
|
||||
models.walletLog.create({ data: { userId: me.id, wallet: wallet.type, level: 'SUCCESS', message: 'wallet created' } })
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -751,7 +757,7 @@ export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ...
|
|||
if (autoWithdraw && decoded.destination === ourPubkey && process.env.NODE_ENV === 'production') {
|
||||
// unset lnaddr so we don't trigger another withdrawal with same destination
|
||||
await models.wallet.deleteMany({
|
||||
where: { userId: me.id, type: 'LIGHTNING_ADDRESS' }
|
||||
where: { userId: me.id, type: Wallet.LnAddr.type }
|
||||
})
|
||||
throw new Error('automated withdrawals to other stackers are not allowed')
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export default gql`
|
|||
upsertWalletCLN(id: ID, socket: String!, rune: String!, cert: String, settings: AutowithdrawSettings!): Boolean
|
||||
upsertWalletLNAddr(id: ID, address: String!, settings: AutowithdrawSettings!): Boolean
|
||||
removeWallet(id: ID!): Boolean
|
||||
deleteWalletLogs(wallet: String): Boolean
|
||||
}
|
||||
|
||||
type Wallet {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useMe } from './me'
|
||||
import fancyNames from '@/lib/fancy-names.json'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||
import { WALLET_LOGS } from '@/fragments/wallet'
|
||||
import { getWalletBy } from '@/lib/constants'
|
||||
|
||||
const generateFancyName = () => {
|
||||
// 100 adjectives * 100 nouns * 10000 = 100M possible names
|
||||
|
@ -157,21 +158,6 @@ const initIndexedDB = async (storeName) => {
|
|||
})
|
||||
}
|
||||
|
||||
const renameWallet = (wallet) => {
|
||||
switch (wallet) {
|
||||
case 'walletLightningAddress':
|
||||
case 'LIGHTNING_ADDRESS':
|
||||
return 'lnAddr'
|
||||
case 'walletLND':
|
||||
case 'LND':
|
||||
return 'lnd'
|
||||
case 'walletCLN':
|
||||
case 'CLN':
|
||||
return 'cln'
|
||||
}
|
||||
return wallet
|
||||
}
|
||||
|
||||
const WalletLoggerProvider = ({ children }) => {
|
||||
const [logs, setLogs] = useState([])
|
||||
const idbStoreName = 'wallet_logs'
|
||||
|
@ -187,12 +173,33 @@ const WalletLoggerProvider = ({ children }) => {
|
|||
const existingIds = prevLogs.map(({ id }) => id)
|
||||
const logs = walletLogs
|
||||
.filter(({ id }) => !existingIds.includes(id))
|
||||
.map(({ createdAt, wallet, ...log }) => ({ ts: +new Date(createdAt), wallet: renameWallet(wallet), ...log }))
|
||||
.map(({ createdAt, wallet: walletType, ...log }) => {
|
||||
return {
|
||||
ts: +new Date(createdAt),
|
||||
wallet: getWalletBy('type', walletType).logTag,
|
||||
...log
|
||||
}
|
||||
})
|
||||
return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const [deleteServerWalletLogs] = useMutation(
|
||||
gql`
|
||||
mutation deleteWalletLogs($wallet: String) {
|
||||
deleteWalletLogs(wallet: $wallet)
|
||||
}
|
||||
`,
|
||||
{
|
||||
onCompleted: (_, { variables: { wallet: walletType } }) => {
|
||||
setLogs((logs) => {
|
||||
return logs.filter(l => walletType ? l.wallet !== getWalletBy('type', walletType).logTag : false)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const saveLog = useCallback((log) => {
|
||||
if (!idb.current) {
|
||||
// IDB may not be ready yet
|
||||
|
@ -234,14 +241,36 @@ const WalletLoggerProvider = ({ children }) => {
|
|||
}, [])
|
||||
|
||||
const appendLog = useCallback((wallet, level, message) => {
|
||||
const log = { wallet, level, message, ts: +new Date() }
|
||||
const log = { wallet: wallet.logTag, level, message, ts: +new Date() }
|
||||
saveLog(log)
|
||||
setLogs((prevLogs) => [...prevLogs, log])
|
||||
}, [saveLog])
|
||||
|
||||
const deleteLogs = useCallback(async (wallet) => {
|
||||
if (!wallet || wallet.server) {
|
||||
await deleteServerWalletLogs({ variables: { wallet: wallet?.type } })
|
||||
}
|
||||
if (!wallet || !wallet.server) {
|
||||
const tx = idb.current.transaction(idbStoreName, 'readwrite')
|
||||
const objectStore = tx.objectStore(idbStoreName)
|
||||
const idx = objectStore.index('wallet_ts')
|
||||
const request = wallet ? idx.openCursor(window.IDBKeyRange.bound([wallet.logTag, -Infinity], [wallet.logTag, Infinity])) : idx.openCursor()
|
||||
request.onsuccess = function (event) {
|
||||
const cursor = event.target.result
|
||||
if (cursor) {
|
||||
cursor.delete()
|
||||
cursor.continue()
|
||||
} else {
|
||||
// finished
|
||||
setLogs((logs) => logs.filter(l => wallet ? l.wallet !== wallet.logTag : false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [setLogs])
|
||||
|
||||
return (
|
||||
<WalletLogsContext.Provider value={logs}>
|
||||
<WalletLoggerContext.Provider value={appendLog}>
|
||||
<WalletLoggerContext.Provider value={{ appendLog, deleteLogs }}>
|
||||
{children}
|
||||
</WalletLoggerContext.Provider>
|
||||
</WalletLogsContext.Provider>
|
||||
|
@ -249,14 +278,14 @@ const WalletLoggerProvider = ({ children }) => {
|
|||
}
|
||||
|
||||
export function useWalletLogger (wallet) {
|
||||
const appendLog = useContext(WalletLoggerContext)
|
||||
const { appendLog, deleteLogs: innerDeleteLogs } = useContext(WalletLoggerContext)
|
||||
|
||||
const log = useCallback(level => message => {
|
||||
// TODO:
|
||||
// also send this to us if diagnostics was enabled,
|
||||
// very similar to how the service worker logger works.
|
||||
appendLog(wallet, level, message)
|
||||
console[level !== 'error' ? 'info' : 'error'](`[${wallet}]`, message)
|
||||
console[level !== 'error' ? 'info' : 'error'](`[${wallet.logTag}]`, message)
|
||||
}, [appendLog, wallet])
|
||||
|
||||
const logger = useMemo(() => ({
|
||||
|
@ -265,10 +294,12 @@ export function useWalletLogger (wallet) {
|
|||
error: (...message) => log('error')(message.join(' '))
|
||||
}), [log, wallet])
|
||||
|
||||
return logger
|
||||
const deleteLogs = useCallback((w) => innerDeleteLogs(w || wallet), [innerDeleteLogs, wallet])
|
||||
|
||||
return { logger, deleteLogs }
|
||||
}
|
||||
|
||||
export function useWalletLogs (wallet) {
|
||||
const logs = useContext(WalletLogsContext)
|
||||
return logs.filter(l => !wallet || l.wallet === wallet)
|
||||
return logs.filter(l => !wallet || l.wallet === wallet.logTag)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import BackArrow from '../../svgs/arrow-left-line.svg'
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Price from '../price'
|
||||
import SubSelect from '../sub-select'
|
||||
import { ANON_USER_ID, BALANCE_LIMIT_MSATS } from '../../lib/constants'
|
||||
import { ANON_USER_ID, BALANCE_LIMIT_MSATS, Wallet } from '../../lib/constants'
|
||||
import Head from 'next/head'
|
||||
import NoteIcon from '../../svgs/notification-4-fill.svg'
|
||||
import { useMe } from '../me'
|
||||
|
@ -22,6 +22,7 @@ import SearchIcon from '../../svgs/search-line.svg'
|
|||
import classNames from 'classnames'
|
||||
import SnIcon from '@/svgs/sn.svg'
|
||||
import { useHasNewNotes } from '../use-has-new-notes'
|
||||
import { useWalletLogger } from '../logger'
|
||||
|
||||
export function Brand ({ className }) {
|
||||
return (
|
||||
|
@ -162,6 +163,7 @@ export function NavWalletSummary ({ className }) {
|
|||
export function MeDropdown ({ me, dropNavKey }) {
|
||||
if (!me) return null
|
||||
const { registration: swRegistration, togglePushSubscription } = useServiceWorker()
|
||||
const { deleteLogs } = useWalletLogger()
|
||||
return (
|
||||
<div className='position-relative'>
|
||||
<Dropdown className={styles.dropdown} align='end'>
|
||||
|
@ -202,16 +204,16 @@ export function MeDropdown ({ me, dropNavKey }) {
|
|||
<Dropdown.Divider />
|
||||
<Dropdown.Item
|
||||
onClick={async () => {
|
||||
try {
|
||||
// order is important because we need to be logged in to delete push subscription on server
|
||||
const pushSubscription = await swRegistration?.pushManager.getSubscription()
|
||||
if (pushSubscription) {
|
||||
await togglePushSubscription()
|
||||
}
|
||||
} catch (err) {
|
||||
// order is important because we need to be logged in to delete push subscription on server
|
||||
const pushSubscription = await swRegistration?.pushManager.getSubscription()
|
||||
if (pushSubscription) {
|
||||
// don't prevent signout because of an unsubscription error
|
||||
console.error(err)
|
||||
await togglePushSubscription().catch(console.error)
|
||||
}
|
||||
// delete client wallet logs to prevent leak of private data if a shared device was used
|
||||
await deleteLogs(Wallet.NWC).catch(console.error)
|
||||
await deleteLogs(Wallet.LNbits).catch(console.error)
|
||||
await deleteLogs(Wallet.LNC).catch(console.error)
|
||||
await signOut({ callbackUrl: '/' })
|
||||
}}
|
||||
>logout
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import LogMessage from './log-message'
|
||||
import { useWalletLogs } from './logger'
|
||||
import { useWalletLogger, useWalletLogs } from './logger'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Checkbox, Form } from './form'
|
||||
import { useField } from 'formik'
|
||||
import styles from '@/styles/log.module.css'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useToast } from './toast'
|
||||
import { useShowModal } from './modal'
|
||||
|
||||
const FollowCheckbox = ({ value, ...props }) => {
|
||||
const [,, helpers] = useField(props.name)
|
||||
|
@ -26,6 +29,7 @@ export default function WalletLogs ({ wallet, embedded }) {
|
|||
const [follow, setFollow] = useState(defaultFollow ?? true)
|
||||
const tableRef = useRef()
|
||||
const scrollY = useRef()
|
||||
const showModal = useShowModal()
|
||||
|
||||
useEffect(() => {
|
||||
if (follow) {
|
||||
|
@ -57,12 +61,21 @@ export default function WalletLogs ({ wallet, embedded }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Form initial={{ follow: true }}>
|
||||
<FollowCheckbox
|
||||
label='follow logs' name='follow' value={follow}
|
||||
handleChange={setFollow}
|
||||
/>
|
||||
</Form>
|
||||
<div className='d-flex w-100 align-items-center mb-3'>
|
||||
<Form initial={{ follow: true }}>
|
||||
<FollowCheckbox
|
||||
label='follow logs' name='follow' value={follow}
|
||||
handleChange={setFollow} groupClassName='mb-0'
|
||||
/>
|
||||
</Form>
|
||||
<span
|
||||
style={{ cursor: 'pointer' }}
|
||||
className='text-muted fw-bold nav-link' onClick={() => {
|
||||
showModal(onClose => <DeleteWalletLogsObstacle wallet={wallet} onClose={onClose} />)
|
||||
}}
|
||||
>clear
|
||||
</span>
|
||||
</div>
|
||||
<div ref={tableRef} className={`${styles.logTable} ${embedded ? styles.embedded : ''}`}>
|
||||
<div className='w-100 text-center'>------ start of logs ------</div>
|
||||
{logs.length === 0 && <div className='w-100 text-center'>empty</div>}
|
||||
|
@ -75,3 +88,34 @@ export default function WalletLogs ({ wallet, embedded }) {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function DeleteWalletLogsObstacle ({ wallet, onClose }) {
|
||||
const toaster = useToast()
|
||||
const { deleteLogs } = useWalletLogger(wallet)
|
||||
|
||||
const prompt = `Do you really want to delete all ${wallet ? '' : 'wallet'} logs ${wallet ? 'of this wallet' : ''}?`
|
||||
return (
|
||||
<div className='text-center'>
|
||||
{prompt}
|
||||
<div className='d-flex justify-center align-items-center mt-3 mx-auto'>
|
||||
<span style={{ cursor: 'pointer' }} className='d-flex ms-auto text-muted fw-bold nav-link mx-3' onClick={onClose}>cancel</span>
|
||||
<Button
|
||||
className='d-flex me-auto mx-3' variant='danger'
|
||||
onClick={
|
||||
async () => {
|
||||
try {
|
||||
await deleteLogs()
|
||||
onClose()
|
||||
toaster.success('deleted wallet logs')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toaster.danger('failed to delete wallet logs')
|
||||
}
|
||||
}
|
||||
}
|
||||
>delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
|
|||
import { useWalletLogger } from '../logger'
|
||||
import { Status } from '.'
|
||||
import { bolt11Tags } from '@/lib/bolt11'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
// Reference: https://github.com/getAlby/bitcoin-connect/blob/v3.2.0-alpha/src/connectors/LnbitsConnector.ts
|
||||
|
||||
|
@ -67,7 +68,7 @@ export function LNbitsProvider ({ children }) {
|
|||
const [url, setUrl] = useState('')
|
||||
const [adminKey, setAdminKey] = useState('')
|
||||
const [status, setStatus] = useState()
|
||||
const logger = useWalletLogger('lnbits')
|
||||
const { logger } = useWalletLogger(Wallet.LNbits)
|
||||
|
||||
const name = 'LNbits'
|
||||
const storageKey = 'webln:provider:lnbits'
|
||||
|
|
|
@ -7,6 +7,7 @@ import useModal from '../modal'
|
|||
import { Form, PasswordInput, SubmitButton } from '../form'
|
||||
import CancelButton from '../cancel-button'
|
||||
import { Mutex } from 'async-mutex'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
const LNCContext = createContext()
|
||||
const mutex = new Mutex()
|
||||
|
@ -32,8 +33,7 @@ function validateNarrowPerms (lnc) {
|
|||
}
|
||||
|
||||
export function LNCProvider ({ children }) {
|
||||
const name = 'lnc'
|
||||
const logger = useWalletLogger(name)
|
||||
const logger = useWalletLogger(Wallet.LNC)
|
||||
const [config, setConfig] = useState({})
|
||||
const [lnc, setLNC] = useState()
|
||||
const [status, setStatus] = useState()
|
||||
|
@ -188,7 +188,7 @@ export function LNCProvider ({ children }) {
|
|||
}, [setStatus, setConfig, logger])
|
||||
|
||||
return (
|
||||
<LNCContext.Provider value={{ name, status, unlock, getInfo, sendPayment, config, saveConfig, clearConfig }}>
|
||||
<LNCContext.Provider value={{ name: 'lnc', status, unlock, getInfo, sendPayment, config, saveConfig, clearConfig }}>
|
||||
{children}
|
||||
{modal}
|
||||
</LNCContext.Provider>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { parseNwcUrl } from '@/lib/url'
|
|||
import { useWalletLogger } from '../logger'
|
||||
import { Status } from '.'
|
||||
import { bolt11Tags } from '@/lib/bolt11'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
const NWCContext = createContext()
|
||||
|
||||
|
@ -15,7 +16,7 @@ export function NWCProvider ({ children }) {
|
|||
const [relayUrl, setRelayUrl] = useState()
|
||||
const [secret, setSecret] = useState()
|
||||
const [status, setStatus] = useState()
|
||||
const logger = useWalletLogger('nwc')
|
||||
const { logger } = useWalletLogger(Wallet.NWC)
|
||||
|
||||
const name = 'NWC'
|
||||
const storageKey = 'webln:provider:nwc'
|
||||
|
|
|
@ -131,3 +131,20 @@ export const FAST_POLL_INTERVAL = Number(process.env.NEXT_PUBLIC_FAST_POLL_INTER
|
|||
export const NORMAL_POLL_INTERVAL = Number(process.env.NEXT_PUBLIC_NORMAL_POLL_INTERVAL)
|
||||
export const LONG_POLL_INTERVAL = Number(process.env.NEXT_PUBLIC_LONG_POLL_INTERVAL)
|
||||
export const EXTRA_LONG_POLL_INTERVAL = Number(process.env.NEXT_PUBLIC_EXTRA_LONG_POLL_INTERVAL)
|
||||
|
||||
// attached wallets
|
||||
export const Wallet = {
|
||||
LND: { logTag: 'lnd', server: true, type: 'LND', field: 'walletLND' },
|
||||
CLN: { logTag: 'cln', server: true, type: 'CLN', field: 'walletCLN' },
|
||||
LnAddr: { logTag: 'lnAddr', server: true, type: 'LIGHTNING_ADDRESS', field: 'walletLightningAddress' },
|
||||
NWC: { logTag: 'nwc', server: false },
|
||||
LNbits: { logTag: 'lnbits', server: false },
|
||||
LNC: { logTag: 'lnc', server: false }
|
||||
}
|
||||
|
||||
export const getWalletBy = (key, value) => {
|
||||
for (const w of Object.values(Wallet)) {
|
||||
if (w[key] === value) return w
|
||||
}
|
||||
throw new Error(`wallet not found: ${key}=${value}`)
|
||||
}
|
||||
|
|
|
@ -12,8 +12,9 @@ import { REMOVE_WALLET, UPSERT_WALLET_CLN, WALLET_BY_TYPE } from '@/fragments/wa
|
|||
import WalletLogs from '@/components/wallet-logs'
|
||||
import Info from '@/components/info'
|
||||
import Text from '@/components/text'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
const variables = { type: 'CLN' }
|
||||
const variables = { type: Wallet.CLN.type }
|
||||
export const getServerSideProps = getGetServerSideProps({ query: WALLET_BY_TYPE, variables, authRequired: true })
|
||||
|
||||
export default function CLN ({ ssrData }) {
|
||||
|
@ -118,7 +119,7 @@ export default function CLN ({ ssrData }) {
|
|||
/>
|
||||
</Form>
|
||||
<div className='mt-3 w-100'>
|
||||
<WalletLogs wallet='cln' embedded />
|
||||
<WalletLogs wallet={Wallet.CLN} embedded />
|
||||
</div>
|
||||
</CenterLayout>
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useQuery } from '@apollo/client'
|
|||
import PageLoading from '@/components/page-loading'
|
||||
import { LNCCard } from './lnc'
|
||||
import Link from 'next/link'
|
||||
import { Wallet as W } from '@/lib/constants'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ query: WALLETS, authRequired: true })
|
||||
|
||||
|
@ -20,9 +21,9 @@ export default function Wallet ({ ssrData }) {
|
|||
|
||||
if (!data && !ssrData) return <PageLoading />
|
||||
const { wallets } = data || ssrData
|
||||
const lnd = wallets.find(w => w.type === 'LND')
|
||||
const lnaddr = wallets.find(w => w.type === 'LIGHTNING_ADDRESS')
|
||||
const cln = wallets.find(w => w.type === 'CLN')
|
||||
const lnd = wallets.find(w => w.type === W.LND.type)
|
||||
const lnaddr = wallets.find(w => w.type === W.LnAddr.type)
|
||||
const cln = wallets.find(w => w.type === W.CLN.type)
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
|
|
@ -10,8 +10,9 @@ import { useRouter } from 'next/router'
|
|||
import { AutowithdrawSettings, autowithdrawInitial } from '@/components/autowithdraw-shared'
|
||||
import { REMOVE_WALLET, UPSERT_WALLET_LNADDR, WALLET_BY_TYPE } from '@/fragments/wallet'
|
||||
import WalletLogs from '@/components/wallet-logs'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
const variables = { type: 'LIGHTNING_ADDRESS' }
|
||||
const variables = { type: Wallet.LnAddr.type }
|
||||
export const getServerSideProps = getGetServerSideProps({ query: WALLET_BY_TYPE, variables, authRequired: true })
|
||||
|
||||
export default function LightningAddress ({ ssrData }) {
|
||||
|
@ -87,7 +88,7 @@ export default function LightningAddress ({ ssrData }) {
|
|||
/>
|
||||
</Form>
|
||||
<div className='mt-3 w-100'>
|
||||
<WalletLogs wallet='lnAddr' embedded />
|
||||
<WalletLogs wallet={Wallet.LnAddr} embedded />
|
||||
</div>
|
||||
</CenterLayout>
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ import { useLNbits } from '@/components/webln/lnbits'
|
|||
import { WalletSecurityBanner } from '@/components/banners'
|
||||
import { useWebLNConfigurator } from '@/components/webln'
|
||||
import WalletLogs from '@/components/wallet-logs'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||
|
||||
|
@ -79,7 +80,7 @@ export default function LNbits () {
|
|||
/>
|
||||
</Form>
|
||||
<div className='mt-3 w-100'>
|
||||
<WalletLogs wallet='lnbits' embedded />
|
||||
<WalletLogs wallet={Wallet.LNbits} embedded />
|
||||
</div>
|
||||
</CenterLayout>
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ import { XXX_DEFAULT_PASSWORD, useLNC } from '@/components/webln/lnc'
|
|||
import { lncSchema } from '@/lib/validate'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||
|
||||
|
@ -102,7 +103,7 @@ export default function LNC () {
|
|||
/>
|
||||
</Form>
|
||||
<div className='mt-3 w-100'>
|
||||
<WalletLogs wallet='lnc' embedded />
|
||||
<WalletLogs wallet={Wallet.LNC} embedded />
|
||||
</div>
|
||||
</CenterLayout>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,9 @@ import { REMOVE_WALLET, UPSERT_WALLET_LND, WALLET_BY_TYPE } from '@/fragments/wa
|
|||
import Info from '@/components/info'
|
||||
import Text from '@/components/text'
|
||||
import WalletLogs from '@/components/wallet-logs'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
const variables = { type: 'LND' }
|
||||
const variables = { type: Wallet.LND.type }
|
||||
export const getServerSideProps = getGetServerSideProps({ query: WALLET_BY_TYPE, variables, authRequired: true })
|
||||
|
||||
export default function LND ({ ssrData }) {
|
||||
|
@ -118,7 +119,7 @@ export default function LND ({ ssrData }) {
|
|||
/>
|
||||
</Form>
|
||||
<div className='mt-3 w-100'>
|
||||
<WalletLogs wallet='lnd' embedded />
|
||||
<WalletLogs wallet={Wallet.LND} embedded />
|
||||
</div>
|
||||
</CenterLayout>
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ import { useNWC } from '@/components/webln/nwc'
|
|||
import { WalletSecurityBanner } from '@/components/banners'
|
||||
import { useWebLNConfigurator } from '@/components/webln'
|
||||
import WalletLogs from '@/components/wallet-logs'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||
|
||||
|
@ -72,7 +73,7 @@ export default function NWC () {
|
|||
/>
|
||||
</Form>
|
||||
<div className='mt-3 w-100'>
|
||||
<WalletLogs wallet='nwc' embedded />
|
||||
<WalletLogs wallet={Wallet.NWC} embedded />
|
||||
</div>
|
||||
</CenterLayout>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- Changed the type of `wallet` on the `WalletLog` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
|
||||
*/
|
||||
|
||||
UPDATE "WalletLog"
|
||||
SET wallet = CASE
|
||||
WHEN wallet = 'walletLND' THEN 'LND'
|
||||
WHEN wallet = 'walletCLN' THEN 'CLN'
|
||||
WHEN wallet = 'walletLightningAddress' THEN 'LIGHTNING_ADDRESS'
|
||||
ELSE wallet
|
||||
END;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "WalletLog" ALTER COLUMN "wallet" TYPE "WalletType" USING "wallet"::"WalletType";
|
|
@ -166,7 +166,7 @@ model WalletLog {
|
|||
createdAt DateTime @default(now()) @map("created_at")
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
wallet String
|
||||
wallet WalletType
|
||||
level LogLevel
|
||||
message String
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { msatsToSats, satsToMsats } from '@/lib/format'
|
|||
import { datePivot } from '@/lib/time'
|
||||
import { createWithdrawal, sendToLnAddr, addWalletLog } from '@/api/resolvers/wallet'
|
||||
import { createInvoice as createInvoiceCLN } from '@/lib/cln'
|
||||
import { Wallet } from '@/lib/constants'
|
||||
|
||||
export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
||||
const user = await models.user.findUnique({ where: { id } })
|
||||
|
@ -46,15 +47,15 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
|||
|
||||
for (const wallet of wallets) {
|
||||
try {
|
||||
if (wallet.type === 'LND') {
|
||||
if (wallet.type === Wallet.LND.type) {
|
||||
await autowithdrawLND(
|
||||
{ amount, maxFee },
|
||||
{ models, me: user, lnd })
|
||||
} else if (wallet.type === 'CLN') {
|
||||
} else if (wallet.type === Wallet.CLN.type) {
|
||||
await autowithdrawCLN(
|
||||
{ amount, maxFee },
|
||||
{ models, me: user, lnd })
|
||||
} else if (wallet.type === 'LIGHTNING_ADDRESS') {
|
||||
} else if (wallet.type === Wallet.LnAddr.type) {
|
||||
await autowithdrawLNAddr(
|
||||
{ amount, maxFee },
|
||||
{ models, me: user, lnd })
|
||||
|
@ -66,7 +67,7 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
|||
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
||||
const details = error[2]?.err?.details || error.message || error.toString?.()
|
||||
await addWalletLog({
|
||||
wallet: wallet.type,
|
||||
wallet,
|
||||
level: 'ERROR',
|
||||
message: 'autowithdrawal failed: ' + details
|
||||
}, { me: user, models })
|
||||
|
@ -86,7 +87,7 @@ async function autowithdrawLNAddr (
|
|||
const wallet = await models.wallet.findFirst({
|
||||
where: {
|
||||
userId: me.id,
|
||||
type: 'LIGHTNING_ADDRESS'
|
||||
type: Wallet.LnAddr.type
|
||||
},
|
||||
include: {
|
||||
walletLightningAddress: true
|
||||
|
@ -109,7 +110,7 @@ async function autowithdrawLND ({ amount, maxFee }, { me, models, lnd }) {
|
|||
const wallet = await models.wallet.findFirst({
|
||||
where: {
|
||||
userId: me.id,
|
||||
type: 'LND'
|
||||
type: Wallet.LND.type
|
||||
},
|
||||
include: {
|
||||
walletLND: true
|
||||
|
@ -145,7 +146,7 @@ async function autowithdrawCLN ({ amount, maxFee }, { me, models, lnd }) {
|
|||
const wallet = await models.wallet.findFirst({
|
||||
where: {
|
||||
userId: me.id,
|
||||
type: 'CLN'
|
||||
type: Wallet.CLN.type
|
||||
},
|
||||
include: {
|
||||
walletCLN: true
|
||||
|
|
Loading…
Reference in New Issue