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 { lnAddrOptions } from '@/lib/lnurl'
|
||||||
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '@/lib/format'
|
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '@/lib/format'
|
||||||
import { CLNAutowithdrawSchema, LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '@/lib/validate'
|
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 { datePivot } from '@/lib/time'
|
||||||
import assertGofacYourself from './ofac'
|
import assertGofacYourself from './ofac'
|
||||||
import assertApiKeyNotPermitted from './apiKey'
|
import assertApiKeyNotPermitted from './apiKey'
|
||||||
|
@ -434,11 +434,11 @@ export default {
|
||||||
data.macaroon = ensureB64(data.macaroon)
|
data.macaroon = ensureB64(data.macaroon)
|
||||||
data.cert = ensureB64(data.cert)
|
data.cert = ensureB64(data.cert)
|
||||||
|
|
||||||
const walletType = 'LND'
|
const wallet = Wallet.LND
|
||||||
return await upsertWallet(
|
return await upsertWallet(
|
||||||
{
|
{
|
||||||
schema: LNDAutowithdrawSchema,
|
schema: LNDAutowithdrawSchema,
|
||||||
walletType,
|
wallet,
|
||||||
testConnect: async ({ cert, macaroon, socket }) => {
|
testConnect: async ({ cert, macaroon, socket }) => {
|
||||||
try {
|
try {
|
||||||
const { lnd } = await authenticatedLndGrpc({
|
const { lnd } = await authenticatedLndGrpc({
|
||||||
|
@ -453,12 +453,12 @@ export default {
|
||||||
expires_at: new Date()
|
expires_at: new Date()
|
||||||
})
|
})
|
||||||
// we wrap both calls in one try/catch since connection attempts happen on RPC calls
|
// 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
|
return inv
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
||||||
const details = err[2]?.err?.details || err.message || err.toString?.()
|
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
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,11 +468,11 @@ export default {
|
||||||
upsertWalletCLN: async (parent, { settings, ...data }, { me, models }) => {
|
upsertWalletCLN: async (parent, { settings, ...data }, { me, models }) => {
|
||||||
data.cert = ensureB64(data.cert)
|
data.cert = ensureB64(data.cert)
|
||||||
|
|
||||||
const walletType = 'CLN'
|
const wallet = Wallet.CLN
|
||||||
return await upsertWallet(
|
return await upsertWallet(
|
||||||
{
|
{
|
||||||
schema: CLNAutowithdrawSchema,
|
schema: CLNAutowithdrawSchema,
|
||||||
walletType,
|
wallet,
|
||||||
testConnect: async ({ socket, rune, cert }) => {
|
testConnect: async ({ socket, rune, cert }) => {
|
||||||
try {
|
try {
|
||||||
const inv = await createInvoiceCLN({
|
const inv = await createInvoiceCLN({
|
||||||
|
@ -483,11 +483,11 @@ export default {
|
||||||
msats: 'any',
|
msats: 'any',
|
||||||
expiry: 0
|
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
|
return inv
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const details = err.details || err.message || err.toString?.()
|
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
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -495,14 +495,14 @@ export default {
|
||||||
{ settings, data }, { me, models })
|
{ settings, data }, { me, models })
|
||||||
},
|
},
|
||||||
upsertWalletLNAddr: async (parent, { settings, ...data }, { me, models }) => {
|
upsertWalletLNAddr: async (parent, { settings, ...data }, { me, models }) => {
|
||||||
const walletType = 'LIGHTNING_ADDRESS'
|
const wallet = Wallet.LnAddr
|
||||||
return await upsertWallet(
|
return await upsertWallet(
|
||||||
{
|
{
|
||||||
schema: lnAddrAutowithdrawSchema,
|
schema: lnAddrAutowithdrawSchema,
|
||||||
walletType,
|
wallet,
|
||||||
testConnect: async ({ address }) => {
|
testConnect: async ({ address }) => {
|
||||||
const options = await lnAddrOptions(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
|
return options
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -523,6 +523,15 @@ export default {
|
||||||
models.walletLog.create({ data: { userId: me.id, wallet: wallet.type, level: 'SUCCESS', message: 'wallet deleted' } })
|
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
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -557,14 +566,14 @@ export default {
|
||||||
|
|
||||||
export const addWalletLog = async ({ wallet, level, message }, { me, models }) => {
|
export const addWalletLog = async ({ wallet, level, message }, { me, models }) => {
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
console.error('error creating wallet log:', err)
|
console.error('error creating wallet log:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upsertWallet (
|
async function upsertWallet (
|
||||||
{ schema, walletType, testConnect }, { settings, data }, { me, models }) {
|
{ schema, wallet, testConnect }, { settings, data }, { me, models }) {
|
||||||
if (!me) {
|
if (!me) {
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
||||||
}
|
}
|
||||||
|
@ -577,7 +586,7 @@ async function upsertWallet (
|
||||||
await testConnect(data)
|
await testConnect(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(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' } })
|
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) {
|
if (id) {
|
||||||
txs.push(
|
txs.push(
|
||||||
models.wallet.update({
|
models.wallet.update({
|
||||||
where: { id: Number(id), userId: me.id },
|
where: { id: Number(id), userId: me.id },
|
||||||
data: {
|
data: {
|
||||||
priority: priority ? 1 : 0,
|
priority: priority ? 1 : 0,
|
||||||
[walletName]: {
|
[wallet.field]: {
|
||||||
update: {
|
update: {
|
||||||
where: { walletId: Number(id) },
|
where: { walletId: Number(id) },
|
||||||
data: walletData
|
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 {
|
} else {
|
||||||
txs.push(
|
txs.push(
|
||||||
|
@ -632,13 +638,13 @@ async function upsertWallet (
|
||||||
data: {
|
data: {
|
||||||
priority: Number(priority),
|
priority: Number(priority),
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
type: walletType,
|
type: wallet.type,
|
||||||
[walletName]: {
|
[wallet.field]: {
|
||||||
create: walletData
|
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') {
|
if (autoWithdraw && decoded.destination === ourPubkey && process.env.NODE_ENV === 'production') {
|
||||||
// unset lnaddr so we don't trigger another withdrawal with same destination
|
// unset lnaddr so we don't trigger another withdrawal with same destination
|
||||||
await models.wallet.deleteMany({
|
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')
|
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
|
upsertWalletCLN(id: ID, socket: String!, rune: String!, cert: String, settings: AutowithdrawSettings!): Boolean
|
||||||
upsertWalletLNAddr(id: ID, address: String!, settings: AutowithdrawSettings!): Boolean
|
upsertWalletLNAddr(id: ID, address: String!, settings: AutowithdrawSettings!): Boolean
|
||||||
removeWallet(id: ID!): Boolean
|
removeWallet(id: ID!): Boolean
|
||||||
|
deleteWalletLogs(wallet: String): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Wallet {
|
type Wallet {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import fancyNames from '@/lib/fancy-names.json'
|
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 { WALLET_LOGS } from '@/fragments/wallet'
|
||||||
|
import { getWalletBy } from '@/lib/constants'
|
||||||
|
|
||||||
const generateFancyName = () => {
|
const generateFancyName = () => {
|
||||||
// 100 adjectives * 100 nouns * 10000 = 100M possible names
|
// 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 WalletLoggerProvider = ({ children }) => {
|
||||||
const [logs, setLogs] = useState([])
|
const [logs, setLogs] = useState([])
|
||||||
const idbStoreName = 'wallet_logs'
|
const idbStoreName = 'wallet_logs'
|
||||||
|
@ -187,12 +173,33 @@ const WalletLoggerProvider = ({ children }) => {
|
||||||
const existingIds = prevLogs.map(({ id }) => id)
|
const existingIds = prevLogs.map(({ id }) => id)
|
||||||
const logs = walletLogs
|
const logs = walletLogs
|
||||||
.filter(({ id }) => !existingIds.includes(id))
|
.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)
|
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) => {
|
const saveLog = useCallback((log) => {
|
||||||
if (!idb.current) {
|
if (!idb.current) {
|
||||||
// IDB may not be ready yet
|
// IDB may not be ready yet
|
||||||
|
@ -234,14 +241,36 @@ const WalletLoggerProvider = ({ children }) => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const appendLog = useCallback((wallet, level, message) => {
|
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)
|
saveLog(log)
|
||||||
setLogs((prevLogs) => [...prevLogs, log])
|
setLogs((prevLogs) => [...prevLogs, log])
|
||||||
}, [saveLog])
|
}, [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 (
|
return (
|
||||||
<WalletLogsContext.Provider value={logs}>
|
<WalletLogsContext.Provider value={logs}>
|
||||||
<WalletLoggerContext.Provider value={appendLog}>
|
<WalletLoggerContext.Provider value={{ appendLog, deleteLogs }}>
|
||||||
{children}
|
{children}
|
||||||
</WalletLoggerContext.Provider>
|
</WalletLoggerContext.Provider>
|
||||||
</WalletLogsContext.Provider>
|
</WalletLogsContext.Provider>
|
||||||
|
@ -249,14 +278,14 @@ const WalletLoggerProvider = ({ children }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWalletLogger (wallet) {
|
export function useWalletLogger (wallet) {
|
||||||
const appendLog = useContext(WalletLoggerContext)
|
const { appendLog, deleteLogs: innerDeleteLogs } = useContext(WalletLoggerContext)
|
||||||
|
|
||||||
const log = useCallback(level => message => {
|
const log = useCallback(level => message => {
|
||||||
// TODO:
|
// TODO:
|
||||||
// also send this to us if diagnostics was enabled,
|
// also send this to us if diagnostics was enabled,
|
||||||
// very similar to how the service worker logger works.
|
// very similar to how the service worker logger works.
|
||||||
appendLog(wallet, level, message)
|
appendLog(wallet, level, message)
|
||||||
console[level !== 'error' ? 'info' : 'error'](`[${wallet}]`, message)
|
console[level !== 'error' ? 'info' : 'error'](`[${wallet.logTag}]`, message)
|
||||||
}, [appendLog, wallet])
|
}, [appendLog, wallet])
|
||||||
|
|
||||||
const logger = useMemo(() => ({
|
const logger = useMemo(() => ({
|
||||||
|
@ -265,10 +294,12 @@ export function useWalletLogger (wallet) {
|
||||||
error: (...message) => log('error')(message.join(' '))
|
error: (...message) => log('error')(message.join(' '))
|
||||||
}), [log, wallet])
|
}), [log, wallet])
|
||||||
|
|
||||||
return logger
|
const deleteLogs = useCallback((w) => innerDeleteLogs(w || wallet), [innerDeleteLogs, wallet])
|
||||||
|
|
||||||
|
return { logger, deleteLogs }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWalletLogs (wallet) {
|
export function useWalletLogs (wallet) {
|
||||||
const logs = useContext(WalletLogsContext)
|
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 { useCallback, useEffect, useState } from 'react'
|
||||||
import Price from '../price'
|
import Price from '../price'
|
||||||
import SubSelect from '../sub-select'
|
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 Head from 'next/head'
|
||||||
import NoteIcon from '../../svgs/notification-4-fill.svg'
|
import NoteIcon from '../../svgs/notification-4-fill.svg'
|
||||||
import { useMe } from '../me'
|
import { useMe } from '../me'
|
||||||
|
@ -22,6 +22,7 @@ import SearchIcon from '../../svgs/search-line.svg'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import SnIcon from '@/svgs/sn.svg'
|
import SnIcon from '@/svgs/sn.svg'
|
||||||
import { useHasNewNotes } from '../use-has-new-notes'
|
import { useHasNewNotes } from '../use-has-new-notes'
|
||||||
|
import { useWalletLogger } from '../logger'
|
||||||
|
|
||||||
export function Brand ({ className }) {
|
export function Brand ({ className }) {
|
||||||
return (
|
return (
|
||||||
|
@ -162,6 +163,7 @@ export function NavWalletSummary ({ className }) {
|
||||||
export function MeDropdown ({ me, dropNavKey }) {
|
export function MeDropdown ({ me, dropNavKey }) {
|
||||||
if (!me) return null
|
if (!me) return null
|
||||||
const { registration: swRegistration, togglePushSubscription } = useServiceWorker()
|
const { registration: swRegistration, togglePushSubscription } = useServiceWorker()
|
||||||
|
const { deleteLogs } = useWalletLogger()
|
||||||
return (
|
return (
|
||||||
<div className='position-relative'>
|
<div className='position-relative'>
|
||||||
<Dropdown className={styles.dropdown} align='end'>
|
<Dropdown className={styles.dropdown} align='end'>
|
||||||
|
@ -202,16 +204,16 @@ export function MeDropdown ({ me, dropNavKey }) {
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
// order is important because we need to be logged in to delete push subscription on server
|
||||||
// order is important because we need to be logged in to delete push subscription on server
|
const pushSubscription = await swRegistration?.pushManager.getSubscription()
|
||||||
const pushSubscription = await swRegistration?.pushManager.getSubscription()
|
if (pushSubscription) {
|
||||||
if (pushSubscription) {
|
|
||||||
await togglePushSubscription()
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// don't prevent signout because of an unsubscription error
|
// 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: '/' })
|
await signOut({ callbackUrl: '/' })
|
||||||
}}
|
}}
|
||||||
>logout
|
>logout
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import LogMessage from './log-message'
|
import LogMessage from './log-message'
|
||||||
import { useWalletLogs } from './logger'
|
import { useWalletLogger, useWalletLogs } from './logger'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { Checkbox, Form } from './form'
|
import { Checkbox, Form } from './form'
|
||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import styles from '@/styles/log.module.css'
|
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 FollowCheckbox = ({ value, ...props }) => {
|
||||||
const [,, helpers] = useField(props.name)
|
const [,, helpers] = useField(props.name)
|
||||||
|
@ -26,6 +29,7 @@ export default function WalletLogs ({ wallet, embedded }) {
|
||||||
const [follow, setFollow] = useState(defaultFollow ?? true)
|
const [follow, setFollow] = useState(defaultFollow ?? true)
|
||||||
const tableRef = useRef()
|
const tableRef = useRef()
|
||||||
const scrollY = useRef()
|
const scrollY = useRef()
|
||||||
|
const showModal = useShowModal()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (follow) {
|
if (follow) {
|
||||||
|
@ -57,12 +61,21 @@ export default function WalletLogs ({ wallet, embedded }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form initial={{ follow: true }}>
|
<div className='d-flex w-100 align-items-center mb-3'>
|
||||||
<FollowCheckbox
|
<Form initial={{ follow: true }}>
|
||||||
label='follow logs' name='follow' value={follow}
|
<FollowCheckbox
|
||||||
handleChange={setFollow}
|
label='follow logs' name='follow' value={follow}
|
||||||
/>
|
handleChange={setFollow} groupClassName='mb-0'
|
||||||
</Form>
|
/>
|
||||||
|
</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 ref={tableRef} className={`${styles.logTable} ${embedded ? styles.embedded : ''}`}>
|
||||||
<div className='w-100 text-center'>------ start of logs ------</div>
|
<div className='w-100 text-center'>------ start of logs ------</div>
|
||||||
{logs.length === 0 && <div className='w-100 text-center'>empty</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 { useWalletLogger } from '../logger'
|
||||||
import { Status } from '.'
|
import { Status } from '.'
|
||||||
import { bolt11Tags } from '@/lib/bolt11'
|
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
|
// 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 [url, setUrl] = useState('')
|
||||||
const [adminKey, setAdminKey] = useState('')
|
const [adminKey, setAdminKey] = useState('')
|
||||||
const [status, setStatus] = useState()
|
const [status, setStatus] = useState()
|
||||||
const logger = useWalletLogger('lnbits')
|
const { logger } = useWalletLogger(Wallet.LNbits)
|
||||||
|
|
||||||
const name = 'LNbits'
|
const name = 'LNbits'
|
||||||
const storageKey = 'webln:provider:lnbits'
|
const storageKey = 'webln:provider:lnbits'
|
||||||
|
|
|
@ -7,6 +7,7 @@ import useModal from '../modal'
|
||||||
import { Form, PasswordInput, SubmitButton } from '../form'
|
import { Form, PasswordInput, SubmitButton } from '../form'
|
||||||
import CancelButton from '../cancel-button'
|
import CancelButton from '../cancel-button'
|
||||||
import { Mutex } from 'async-mutex'
|
import { Mutex } from 'async-mutex'
|
||||||
|
import { Wallet } from '@/lib/constants'
|
||||||
|
|
||||||
const LNCContext = createContext()
|
const LNCContext = createContext()
|
||||||
const mutex = new Mutex()
|
const mutex = new Mutex()
|
||||||
|
@ -32,8 +33,7 @@ function validateNarrowPerms (lnc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LNCProvider ({ children }) {
|
export function LNCProvider ({ children }) {
|
||||||
const name = 'lnc'
|
const logger = useWalletLogger(Wallet.LNC)
|
||||||
const logger = useWalletLogger(name)
|
|
||||||
const [config, setConfig] = useState({})
|
const [config, setConfig] = useState({})
|
||||||
const [lnc, setLNC] = useState()
|
const [lnc, setLNC] = useState()
|
||||||
const [status, setStatus] = useState()
|
const [status, setStatus] = useState()
|
||||||
|
@ -188,7 +188,7 @@ export function LNCProvider ({ children }) {
|
||||||
}, [setStatus, setConfig, logger])
|
}, [setStatus, setConfig, logger])
|
||||||
|
|
||||||
return (
|
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}
|
{children}
|
||||||
{modal}
|
{modal}
|
||||||
</LNCContext.Provider>
|
</LNCContext.Provider>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { parseNwcUrl } from '@/lib/url'
|
||||||
import { useWalletLogger } from '../logger'
|
import { useWalletLogger } from '../logger'
|
||||||
import { Status } from '.'
|
import { Status } from '.'
|
||||||
import { bolt11Tags } from '@/lib/bolt11'
|
import { bolt11Tags } from '@/lib/bolt11'
|
||||||
|
import { Wallet } from '@/lib/constants'
|
||||||
|
|
||||||
const NWCContext = createContext()
|
const NWCContext = createContext()
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ export function NWCProvider ({ children }) {
|
||||||
const [relayUrl, setRelayUrl] = useState()
|
const [relayUrl, setRelayUrl] = useState()
|
||||||
const [secret, setSecret] = useState()
|
const [secret, setSecret] = useState()
|
||||||
const [status, setStatus] = useState()
|
const [status, setStatus] = useState()
|
||||||
const logger = useWalletLogger('nwc')
|
const { logger } = useWalletLogger(Wallet.NWC)
|
||||||
|
|
||||||
const name = 'NWC'
|
const name = 'NWC'
|
||||||
const storageKey = 'webln:provider: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 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 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)
|
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 WalletLogs from '@/components/wallet-logs'
|
||||||
import Info from '@/components/info'
|
import Info from '@/components/info'
|
||||||
import Text from '@/components/text'
|
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 const getServerSideProps = getGetServerSideProps({ query: WALLET_BY_TYPE, variables, authRequired: true })
|
||||||
|
|
||||||
export default function CLN ({ ssrData }) {
|
export default function CLN ({ ssrData }) {
|
||||||
|
@ -118,7 +119,7 @@ export default function CLN ({ ssrData }) {
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
<div className='mt-3 w-100'>
|
<div className='mt-3 w-100'>
|
||||||
<WalletLogs wallet='cln' embedded />
|
<WalletLogs wallet={Wallet.CLN} embedded />
|
||||||
</div>
|
</div>
|
||||||
</CenterLayout>
|
</CenterLayout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { useQuery } from '@apollo/client'
|
||||||
import PageLoading from '@/components/page-loading'
|
import PageLoading from '@/components/page-loading'
|
||||||
import { LNCCard } from './lnc'
|
import { LNCCard } from './lnc'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { Wallet as W } from '@/lib/constants'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps({ query: WALLETS, authRequired: true })
|
export const getServerSideProps = getGetServerSideProps({ query: WALLETS, authRequired: true })
|
||||||
|
|
||||||
|
@ -20,9 +21,9 @@ export default function Wallet ({ ssrData }) {
|
||||||
|
|
||||||
if (!data && !ssrData) return <PageLoading />
|
if (!data && !ssrData) return <PageLoading />
|
||||||
const { wallets } = data || ssrData
|
const { wallets } = data || ssrData
|
||||||
const lnd = wallets.find(w => w.type === 'LND')
|
const lnd = wallets.find(w => w.type === W.LND.type)
|
||||||
const lnaddr = wallets.find(w => w.type === 'LIGHTNING_ADDRESS')
|
const lnaddr = wallets.find(w => w.type === W.LnAddr.type)
|
||||||
const cln = wallets.find(w => w.type === 'CLN')
|
const cln = wallets.find(w => w.type === W.CLN.type)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|
|
@ -10,8 +10,9 @@ import { useRouter } from 'next/router'
|
||||||
import { AutowithdrawSettings, autowithdrawInitial } from '@/components/autowithdraw-shared'
|
import { AutowithdrawSettings, autowithdrawInitial } from '@/components/autowithdraw-shared'
|
||||||
import { REMOVE_WALLET, UPSERT_WALLET_LNADDR, WALLET_BY_TYPE } from '@/fragments/wallet'
|
import { REMOVE_WALLET, UPSERT_WALLET_LNADDR, WALLET_BY_TYPE } from '@/fragments/wallet'
|
||||||
import WalletLogs from '@/components/wallet-logs'
|
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 const getServerSideProps = getGetServerSideProps({ query: WALLET_BY_TYPE, variables, authRequired: true })
|
||||||
|
|
||||||
export default function LightningAddress ({ ssrData }) {
|
export default function LightningAddress ({ ssrData }) {
|
||||||
|
@ -87,7 +88,7 @@ export default function LightningAddress ({ ssrData }) {
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
<div className='mt-3 w-100'>
|
<div className='mt-3 w-100'>
|
||||||
<WalletLogs wallet='lnAddr' embedded />
|
<WalletLogs wallet={Wallet.LnAddr} embedded />
|
||||||
</div>
|
</div>
|
||||||
</CenterLayout>
|
</CenterLayout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { useLNbits } from '@/components/webln/lnbits'
|
||||||
import { WalletSecurityBanner } from '@/components/banners'
|
import { WalletSecurityBanner } from '@/components/banners'
|
||||||
import { useWebLNConfigurator } from '@/components/webln'
|
import { useWebLNConfigurator } from '@/components/webln'
|
||||||
import WalletLogs from '@/components/wallet-logs'
|
import WalletLogs from '@/components/wallet-logs'
|
||||||
|
import { Wallet } from '@/lib/constants'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ export default function LNbits () {
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
<div className='mt-3 w-100'>
|
<div className='mt-3 w-100'>
|
||||||
<WalletLogs wallet='lnbits' embedded />
|
<WalletLogs wallet={Wallet.LNbits} embedded />
|
||||||
</div>
|
</div>
|
||||||
</CenterLayout>
|
</CenterLayout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { XXX_DEFAULT_PASSWORD, useLNC } from '@/components/webln/lnc'
|
||||||
import { lncSchema } from '@/lib/validate'
|
import { lncSchema } from '@/lib/validate'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { Wallet } from '@/lib/constants'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ export default function LNC () {
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
<div className='mt-3 w-100'>
|
<div className='mt-3 w-100'>
|
||||||
<WalletLogs wallet='lnc' embedded />
|
<WalletLogs wallet={Wallet.LNC} embedded />
|
||||||
</div>
|
</div>
|
||||||
</CenterLayout>
|
</CenterLayout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,9 @@ import { REMOVE_WALLET, UPSERT_WALLET_LND, WALLET_BY_TYPE } from '@/fragments/wa
|
||||||
import Info from '@/components/info'
|
import Info from '@/components/info'
|
||||||
import Text from '@/components/text'
|
import Text from '@/components/text'
|
||||||
import WalletLogs from '@/components/wallet-logs'
|
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 const getServerSideProps = getGetServerSideProps({ query: WALLET_BY_TYPE, variables, authRequired: true })
|
||||||
|
|
||||||
export default function LND ({ ssrData }) {
|
export default function LND ({ ssrData }) {
|
||||||
|
@ -118,7 +119,7 @@ export default function LND ({ ssrData }) {
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
<div className='mt-3 w-100'>
|
<div className='mt-3 w-100'>
|
||||||
<WalletLogs wallet='lnd' embedded />
|
<WalletLogs wallet={Wallet.LND} embedded />
|
||||||
</div>
|
</div>
|
||||||
</CenterLayout>
|
</CenterLayout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { useNWC } from '@/components/webln/nwc'
|
||||||
import { WalletSecurityBanner } from '@/components/banners'
|
import { WalletSecurityBanner } from '@/components/banners'
|
||||||
import { useWebLNConfigurator } from '@/components/webln'
|
import { useWebLNConfigurator } from '@/components/webln'
|
||||||
import WalletLogs from '@/components/wallet-logs'
|
import WalletLogs from '@/components/wallet-logs'
|
||||||
|
import { Wallet } from '@/lib/constants'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ export default function NWC () {
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
<div className='mt-3 w-100'>
|
<div className='mt-3 w-100'>
|
||||||
<WalletLogs wallet='nwc' embedded />
|
<WalletLogs wallet={Wallet.NWC} embedded />
|
||||||
</div>
|
</div>
|
||||||
</CenterLayout>
|
</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")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
userId Int
|
userId Int
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
wallet String
|
wallet WalletType
|
||||||
level LogLevel
|
level LogLevel
|
||||||
message String
|
message String
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { msatsToSats, satsToMsats } from '@/lib/format'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
import { createWithdrawal, sendToLnAddr, addWalletLog } from '@/api/resolvers/wallet'
|
import { createWithdrawal, sendToLnAddr, addWalletLog } from '@/api/resolvers/wallet'
|
||||||
import { createInvoice as createInvoiceCLN } from '@/lib/cln'
|
import { createInvoice as createInvoiceCLN } from '@/lib/cln'
|
||||||
|
import { Wallet } from '@/lib/constants'
|
||||||
|
|
||||||
export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
export async function autoWithdraw ({ data: { id }, models, lnd }) {
|
||||||
const user = await models.user.findUnique({ where: { id } })
|
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) {
|
for (const wallet of wallets) {
|
||||||
try {
|
try {
|
||||||
if (wallet.type === 'LND') {
|
if (wallet.type === Wallet.LND.type) {
|
||||||
await autowithdrawLND(
|
await autowithdrawLND(
|
||||||
{ amount, maxFee },
|
{ amount, maxFee },
|
||||||
{ models, me: user, lnd })
|
{ models, me: user, lnd })
|
||||||
} else if (wallet.type === 'CLN') {
|
} else if (wallet.type === Wallet.CLN.type) {
|
||||||
await autowithdrawCLN(
|
await autowithdrawCLN(
|
||||||
{ amount, maxFee },
|
{ amount, maxFee },
|
||||||
{ models, me: user, lnd })
|
{ models, me: user, lnd })
|
||||||
} else if (wallet.type === 'LIGHTNING_ADDRESS') {
|
} else if (wallet.type === Wallet.LnAddr.type) {
|
||||||
await autowithdrawLNAddr(
|
await autowithdrawLNAddr(
|
||||||
{ amount, maxFee },
|
{ amount, maxFee },
|
||||||
{ models, me: user, lnd })
|
{ 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 } }]
|
// LND errors are in this shape: [code, type, { err: { code, details, metadata } }]
|
||||||
const details = error[2]?.err?.details || error.message || error.toString?.()
|
const details = error[2]?.err?.details || error.message || error.toString?.()
|
||||||
await addWalletLog({
|
await addWalletLog({
|
||||||
wallet: wallet.type,
|
wallet,
|
||||||
level: 'ERROR',
|
level: 'ERROR',
|
||||||
message: 'autowithdrawal failed: ' + details
|
message: 'autowithdrawal failed: ' + details
|
||||||
}, { me: user, models })
|
}, { me: user, models })
|
||||||
|
@ -86,7 +87,7 @@ async function autowithdrawLNAddr (
|
||||||
const wallet = await models.wallet.findFirst({
|
const wallet = await models.wallet.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
type: 'LIGHTNING_ADDRESS'
|
type: Wallet.LnAddr.type
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
walletLightningAddress: true
|
walletLightningAddress: true
|
||||||
|
@ -109,7 +110,7 @@ async function autowithdrawLND ({ amount, maxFee }, { me, models, lnd }) {
|
||||||
const wallet = await models.wallet.findFirst({
|
const wallet = await models.wallet.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
type: 'LND'
|
type: Wallet.LND.type
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
walletLND: true
|
walletLND: true
|
||||||
|
@ -145,7 +146,7 @@ async function autowithdrawCLN ({ amount, maxFee }, { me, models, lnd }) {
|
||||||
const wallet = await models.wallet.findFirst({
|
const wallet = await models.wallet.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
type: 'CLN'
|
type: Wallet.CLN.type
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
walletCLN: true
|
walletCLN: true
|
||||||
|
|
Loading…
Reference in New Issue