diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 81173911..7780b28f 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -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') } diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index ce05cde5..437f9804 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -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 { diff --git a/components/logger.js b/components/logger.js index da94fde9..10f03c0a 100644 --- a/components/logger.js +++ b/components/logger.js @@ -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 ( - + {children} @@ -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) } diff --git a/components/nav/common.js b/components/nav/common.js index e5114a65..49881765 100644 --- a/components/nav/common.js +++ b/components/nav/common.js @@ -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 (
@@ -202,16 +204,16 @@ export function MeDropdown ({ me, dropNavKey }) { { - 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 diff --git a/components/wallet-logs.js b/components/wallet-logs.js index f8f95bf5..c5cb678d 100644 --- a/components/wallet-logs.js +++ b/components/wallet-logs.js @@ -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 ( <> -
- - +
+
+ + + { + showModal(onClose => ) + }} + >clear + +
------ start of logs ------
{logs.length === 0 &&
empty
} @@ -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 ( +
+ {prompt} +
+ cancel + +
+
+ ) +} diff --git a/components/webln/lnbits.js b/components/webln/lnbits.js index e433e8e4..c2f3009f 100644 --- a/components/webln/lnbits.js +++ b/components/webln/lnbits.js @@ -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' diff --git a/components/webln/lnc.js b/components/webln/lnc.js index 7b056f42..7f942197 100644 --- a/components/webln/lnc.js +++ b/components/webln/lnc.js @@ -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 ( - + {children} {modal} diff --git a/components/webln/nwc.js b/components/webln/nwc.js index cb403775..a264d665 100644 --- a/components/webln/nwc.js +++ b/components/webln/nwc.js @@ -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' diff --git a/lib/constants.js b/lib/constants.js index f149b9b8..5ac6652a 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -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}`) +} diff --git a/pages/settings/wallets/cln.js b/pages/settings/wallets/cln.js index ce517e1f..ea518320 100644 --- a/pages/settings/wallets/cln.js +++ b/pages/settings/wallets/cln.js @@ -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 }) { />
- +
) diff --git a/pages/settings/wallets/index.js b/pages/settings/wallets/index.js index 25641419..8adbcec5 100644 --- a/pages/settings/wallets/index.js +++ b/pages/settings/wallets/index.js @@ -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 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 ( diff --git a/pages/settings/wallets/lightning-address.js b/pages/settings/wallets/lightning-address.js index 792cbff0..8dbe3c9c 100644 --- a/pages/settings/wallets/lightning-address.js +++ b/pages/settings/wallets/lightning-address.js @@ -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 }) { />
- +
) diff --git a/pages/settings/wallets/lnbits.js b/pages/settings/wallets/lnbits.js index dbdbe46d..b1905c0b 100644 --- a/pages/settings/wallets/lnbits.js +++ b/pages/settings/wallets/lnbits.js @@ -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 () { />
- +
) diff --git a/pages/settings/wallets/lnc.js b/pages/settings/wallets/lnc.js index 508deb20..6f97c191 100644 --- a/pages/settings/wallets/lnc.js +++ b/pages/settings/wallets/lnc.js @@ -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 () { />
- +
) diff --git a/pages/settings/wallets/lnd.js b/pages/settings/wallets/lnd.js index cfa4efd5..1d5d0858 100644 --- a/pages/settings/wallets/lnd.js +++ b/pages/settings/wallets/lnd.js @@ -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 }) { />
- +
) diff --git a/pages/settings/wallets/nwc.js b/pages/settings/wallets/nwc.js index f5c12fe3..81800832 100644 --- a/pages/settings/wallets/nwc.js +++ b/pages/settings/wallets/nwc.js @@ -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 () { />
- +
) diff --git a/prisma/migrations/20240424132802_wallet_log_use_enum/migration.sql b/prisma/migrations/20240424132802_wallet_log_use_enum/migration.sql new file mode 100644 index 00000000..204c49fb --- /dev/null +++ b/prisma/migrations/20240424132802_wallet_log_use_enum/migration.sql @@ -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"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7a3869b4..4c761cc5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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 diff --git a/worker/autowithdraw.js b/worker/autowithdraw.js index c4c4fe54..5f35c96d 100644 --- a/worker/autowithdraw.js +++ b/worker/autowithdraw.js @@ -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