diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js
index 766bab1a..79765993 100644
--- a/api/resolvers/wallet.js
+++ b/api/resolvers/wallet.js
@@ -678,9 +678,12 @@ export const walletLogger = ({ wallet, models }) => {
payment_hash: decoded.id,
created_at: decoded.created_at,
expires_at: decoded.expires_at,
- description: decoded.description
+ description: decoded.description,
+ // payments should affect wallet status
+ status: true
}
}
+ context.recv = true
await models.walletLog.create({
data: {
diff --git a/components/log-message.js b/components/log-message.js
index b1593027..73f76263 100644
--- a/components/log-message.js
+++ b/components/log-message.js
@@ -19,7 +19,16 @@ export default function LogMessage ({ showWallet, wallet, level, message, contex
className = 'text-info'
}
- const hasContext = context && Object.keys(context).length > 0
+ const filtered = context
+ ? Object.keys(context)
+ .filter(key => !['send', 'recv', 'status'].includes(key))
+ .reduce((obj, key) => {
+ obj[key] = context[key]
+ return obj
+ }, {})
+ : {}
+
+ const hasContext = context && Object.keys(filtered).length > 0
const handleClick = () => {
if (hasContext) { setShow(show => !show) }
@@ -37,16 +46,17 @@ export default function LogMessage ({ showWallet, wallet, level, message, contex
{message} |
{indicator} |
- {show && hasContext && Object.entries(context).map(([key, value], i) => {
- const last = i === Object.keys(context).length - 1
- return (
-
- |
- {key} |
- {value} |
-
- )
- })}
+ {show && hasContext && Object.entries(filtered)
+ .map(([key, value], i) => {
+ const last = i === Object.keys(filtered).length - 1
+ return (
+
+ |
+ {key} |
+ {value} |
+
+ )
+ })}
>
)
}
diff --git a/components/wallet-buttonbar.js b/components/wallet-buttonbar.js
index 76b60e92..46572919 100644
--- a/components/wallet-buttonbar.js
+++ b/components/wallet-buttonbar.js
@@ -11,7 +11,7 @@ export default function WalletButtonBar ({
return (
- {isConfigured(wallet) &&
+ {isConfigured(wallet) && wallet.def.requiresConfig &&
}
{children}
diff --git a/components/wallet-card.js b/components/wallet-card.js
index eff71d06..a964f8d6 100644
--- a/components/wallet-card.js
+++ b/components/wallet-card.js
@@ -1,23 +1,34 @@
-import { Badge, Card } from 'react-bootstrap'
+import { Card } from 'react-bootstrap'
import styles from '@/styles/wallet.module.css'
import Plug from '@/svgs/plug.svg'
import Gear from '@/svgs/settings-5-fill.svg'
import Link from 'next/link'
import { Status, isConfigured } from '@/wallets/common'
import DraggableIcon from '@/svgs/draggable.svg'
+import RecvIcon from '@/svgs/arrow-left-down-line.svg'
+import SendIcon from '@/svgs/arrow-right-up-line.svg'
+import useDarkMode from './dark-mode'
+import { useEffect, useState } from 'react'
+
+const statusToClass = status => {
+ switch (status) {
+ case Status.Enabled: return styles.success
+ case Status.Disabled: return styles.disabled
+ case Status.Error: return styles.error
+ case Status.Warning: return styles.warning
+ }
+}
export default function WalletCard ({ wallet, draggable, onDragStart, onDragEnter, onDragEnd, onTouchStart, sourceIndex, targetIndex, index }) {
- const { card: { title, badges } } = wallet.def
+ const [dark] = useDarkMode()
+ const { card: { title, image } } = wallet.def
+ const [imgSrc, setImgSrc] = useState(image?.src)
- let indicator = styles.disabled
- switch (wallet.status) {
- case Status.Enabled:
- indicator = styles.success
- break
- default:
- indicator = styles.disabled
- break
- }
+ useEffect(() => {
+ if (!imgSrc) return
+ // wallet.png <-> wallet-dark.png
+ setImgSrc(dark ? image?.src.replace(/\.([a-z]{3})$/, '-dark.$1') : image?.src)
+ }, [dark])
return (
- {wallet.status === Status.Enabled &&
}
-
+
+ {wallet.status.any !== Status.Disabled && }
+ {wallet.support.recv && }
+ {wallet.support.send && }
+
- {title}
-
- {badges?.map(
- badge => {
- let style = ''
- switch (badge) {
- case 'receive': style = styles.receive; break
- case 'send': style = styles.send; break
- }
- return (
-
- {badge}
-
- )
- })}
-
+
+ {image
+ ?
+ :
{title}}
+
diff --git a/components/wallet-logger.js b/components/wallet-logger.js
index e709443c..7beadeb0 100644
--- a/components/wallet-logger.js
+++ b/components/wallet-logger.js
@@ -174,9 +174,12 @@ export function useWalletLogger (wallet, setLogs) {
payment_hash: decoded.tagsObject.payment_hash,
description: decoded.tagsObject.description,
created_at: new Date(decoded.timestamp * 1000).toISOString(),
- expires_at: new Date(decoded.timeExpireDate * 1000).toISOString()
+ expires_at: new Date(decoded.timeExpireDate * 1000).toISOString(),
+ // payments should affect wallet status
+ status: true
}
}
+ context.send = true
appendLog(wallet, level, message, context)
console[level !== 'error' ? 'info' : 'error'](`[${tag(wallet)}]`, message)
diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js
index 891307ce..cbaa0173 100644
--- a/pages/settings/wallets/[wallet].js
+++ b/pages/settings/wallets/[wallet].js
@@ -67,10 +67,16 @@ export default function WalletSettings () {
}
}, [wallet.def])
+ const { card: { image, title, subtitle } } = wallet?.def || { card: {} }
+
return (
- {wallet?.def.card.title}
- {wallet?.def.card.subtitle}
+ {image
+ ? typeof image === 'object'
+ ?
+ :
+ : {title}
}
+ {subtitle}
}
diff --git a/public/wallets/blink-dark.svg b/public/wallets/blink-dark.svg
new file mode 100644
index 00000000..b62d73f3
--- /dev/null
+++ b/public/wallets/blink-dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/wallets/blink.svg b/public/wallets/blink.svg
new file mode 100644
index 00000000..01dadddd
--- /dev/null
+++ b/public/wallets/blink.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/wallets/cln-dark.svg b/public/wallets/cln-dark.svg
new file mode 100644
index 00000000..4a51b36b
--- /dev/null
+++ b/public/wallets/cln-dark.svg
@@ -0,0 +1,65 @@
+
+
+
diff --git a/public/wallets/cln.svg b/public/wallets/cln.svg
new file mode 100644
index 00000000..0e92b7e4
--- /dev/null
+++ b/public/wallets/cln.svg
@@ -0,0 +1,65 @@
+
+
+
diff --git a/public/wallets/lnbits-dark.svg b/public/wallets/lnbits-dark.svg
new file mode 100644
index 00000000..53120791
--- /dev/null
+++ b/public/wallets/lnbits-dark.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/public/wallets/lnbits.svg b/public/wallets/lnbits.svg
new file mode 100644
index 00000000..97a2ec17
--- /dev/null
+++ b/public/wallets/lnbits.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/public/wallets/lnd-dark.png b/public/wallets/lnd-dark.png
new file mode 100644
index 00000000..169d2772
Binary files /dev/null and b/public/wallets/lnd-dark.png differ
diff --git a/public/wallets/lnd.png b/public/wallets/lnd.png
new file mode 100644
index 00000000..169d2772
Binary files /dev/null and b/public/wallets/lnd.png differ
diff --git a/public/wallets/phoenixd-dark.png b/public/wallets/phoenixd-dark.png
new file mode 100644
index 00000000..93aa9336
Binary files /dev/null and b/public/wallets/phoenixd-dark.png differ
diff --git a/public/wallets/phoenixd.png b/public/wallets/phoenixd.png
new file mode 100644
index 00000000..59d1c7cc
Binary files /dev/null and b/public/wallets/phoenixd.png differ
diff --git a/scripts/genwallet.sh b/scripts/genwallet.sh
index 5d0556c5..e14609ec 100644
--- a/scripts/genwallet.sh
+++ b/scripts/genwallet.sh
@@ -58,7 +58,6 @@ $(todo)
export const card = {
title: '$wallet',
subtitle: '',
- badges: []
}
$(todo)
diff --git a/styles/wallet.module.css b/styles/wallet.module.css
index 133734d4..f968297f 100644
--- a/styles/wallet.module.css
+++ b/styles/wallet.module.css
@@ -20,7 +20,7 @@
height: 180px;
}
-.cardMeta {
+.indicators {
position: absolute;
width: 100%;
display: grid;
@@ -59,23 +59,20 @@
}
.indicator {
- width: 10px;
- height: 10px;
- border-radius: 50%;
+ width: 14px;
+ height: 14px;
}
.indicator.success {
color: var(--bs-green) !important;
background-color: var(--bs-green) !important;
border: 1px solid var(--bs-success);
- filter: drop-shadow(0 0 2px #20c997);
}
.indicator.error {
color: var(--bs-red) !important;
background-color: var(--bs-red) !important;
border: 1px solid var(--bs-danger);
- filter: drop-shadow(0 0 2px #c92020);
}
.indicator.warning {
diff --git a/svgs/arrow-left-down-line.svg b/svgs/arrow-left-down-line.svg
new file mode 100644
index 00000000..81d43660
--- /dev/null
+++ b/svgs/arrow-left-down-line.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/svgs/arrow-right-up-line.svg b/svgs/arrow-right-up-line.svg
new file mode 100644
index 00000000..93d2f337
--- /dev/null
+++ b/svgs/arrow-right-up-line.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wallets/README.md b/wallets/README.md
index 166900f4..9ad23f8a 100644
--- a/wallets/README.md
+++ b/wallets/README.md
@@ -152,9 +152,9 @@ The card title.
The subtitle that is shown below the title if you enter the configuration form of a wallet.
-- `badges: string[]`
+- `image: { src: string, ... }`
-The badges that are shown inside the card.
+The image props that will be used to show an image inside the card. Should contain at least the `src` property.
### client.js
diff --git a/wallets/blink/index.js b/wallets/blink/index.js
index 65304369..d870592c 100644
--- a/wallets/blink/index.js
+++ b/wallets/blink/index.js
@@ -67,5 +67,5 @@ export const fields = [
export const card = {
title: 'Blink',
subtitle: 'use [Blink](https://blink.sv/) for payments',
- badges: ['send', 'receive']
+ image: { src: '/wallets/blink.svg' }
}
diff --git a/wallets/cln/index.js b/wallets/cln/index.js
index 14525f97..dc8523d1 100644
--- a/wallets/cln/index.js
+++ b/wallets/cln/index.js
@@ -63,5 +63,5 @@ export const fields = [
export const card = {
title: 'CLN',
subtitle: 'autowithdraw to your Core Lightning node via [CLNRest](https://docs.corelightning.org/docs/rest)',
- badges: ['receive']
+ image: { src: '/wallets/cln.svg' }
}
diff --git a/wallets/common.js b/wallets/common.js
index c88b4655..c36d70aa 100644
--- a/wallets/common.js
+++ b/wallets/common.js
@@ -2,7 +2,9 @@ import walletDefs from '@/wallets/client'
export const Status = {
Enabled: 'Enabled',
- Disabled: 'Disabled'
+ Disabled: 'Disabled',
+ Error: 'Error',
+ Warning: 'Warning'
}
export function getWalletByName (name) {
@@ -89,12 +91,24 @@ function isReceiveConfigured ({ def, config }) {
return fields.length > 0 && checkFields({ fields, config })
}
+export function supportsSend ({ def, config }) {
+ return !!def.sendPayment
+}
+
+export function supportsReceive ({ def, config }) {
+ return def.fields.some(f => f.serverOnly)
+}
+
export function canSend ({ def, config }) {
- return !!def.sendPayment && isSendConfigured({ def, config })
+ return (
+ supportsSend({ def, config }) &&
+ isSendConfigured({ def, config }) &&
+ (def.requiresConfig || config?.enabled)
+ )
}
export function canReceive ({ def, config }) {
- return def.fields.some(f => f.serverOnly) && isReceiveConfigured({ def, config })
+ return supportsReceive({ def, config }) && isReceiveConfigured({ def, config })
}
export function siftConfig (fields, config) {
@@ -161,3 +175,31 @@ export async function saveWalletLocally (name, config, userId) {
const storageKey = getStorageKey(name, userId)
window.localStorage.setItem(storageKey, JSON.stringify(config))
}
+
+export const statusFromLog = (wallet, logs) => {
+ if (wallet.status.any === Status.Disabled) return wallet
+
+ // override status depending on if there have been warnings or errors in the logs recently
+ // find first log from which we can derive status (logs are sorted by recent first)
+ const walletLogs = logs.filter(l => l.wallet === wallet.def.name)
+ const sendLevel = walletLogs.find(l => l.context?.status && l.context?.send)?.level
+ const recvLevel = walletLogs.find(l => l.context?.status && l.context?.recv)?.level
+
+ const levelToStatus = (level) => {
+ switch (level?.toLowerCase()) {
+ case 'ok':
+ case 'success': return Status.Enabled
+ case 'error': return Status.Error
+ case 'warn': return Status.Warning
+ }
+ }
+
+ return {
+ ...wallet,
+ status: {
+ ...wallet.status,
+ send: levelToStatus(sendLevel) || wallet.status.send,
+ recv: levelToStatus(recvLevel) || wallet.status.recv
+ }
+ }
+}
diff --git a/wallets/config.js b/wallets/config.js
index 13011613..6227605c 100644
--- a/wallets/config.js
+++ b/wallets/config.js
@@ -54,7 +54,7 @@ export function useWalletConfigurator (wallet) {
if (transformedConfig) {
serverConfig = Object.assign(serverConfig, transformedConfig)
}
- } else {
+ } else if (wallet.def.requiresConfig) {
throw new Error('configuration must be able to send or receive')
}
diff --git a/wallets/index.js b/wallets/index.js
index 719e230f..c2e83eed 100644
--- a/wallets/index.js
+++ b/wallets/index.js
@@ -3,9 +3,9 @@ import { SET_WALLET_PRIORITY, WALLETS } from '@/fragments/wallet'
import { SSR } from '@/lib/constants'
import { useApolloClient, useMutation, useQuery } from '@apollo/client'
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
-import { getStorageKey, getWalletByType, Status, walletPrioritySort, canSend, isConfigured, upsertWalletVariables, siftConfig, saveWalletLocally } from './common'
+import { getStorageKey, getWalletByType, Status, walletPrioritySort, canSend, isConfigured, upsertWalletVariables, siftConfig, saveWalletLocally, canReceive, supportsReceive, supportsSend, statusFromLog } from './common'
import useVault from '@/components/vault/use-vault'
-import { useWalletLogger } from '@/components/wallet-logger'
+import { useWalletLogger, useWalletLogs } from '@/components/wallet-logger'
import { decode as bolt11Decode } from 'bolt11'
import walletDefs from '@/wallets/client'
import { generateMutation } from './graphql'
@@ -67,6 +67,7 @@ export function WalletsProvider ({ children }) {
const [setWalletPriority] = useMutation(SET_WALLET_PRIORITY)
const [serverWallets, setServerWallets] = useState([])
const client = useApolloClient()
+ const { logs } = useWalletLogs()
const { data, refetch } = useQuery(WALLETS,
SSR ? {} : { nextFetchPolicy: 'cache-and-network' })
@@ -111,7 +112,10 @@ export function WalletsProvider ({ children }) {
const merged = {}
for (const wallet of [...walletDefsOnly, ...localWallets, ...serverWallets]) {
merged[wallet.def.name] = {
- def: wallet.def,
+ def: {
+ ...wallet.def,
+ requiresConfig: wallet.def.fields.length > 0
+ },
config: {
...merged[wallet.def.name]?.config,
...Object.fromEntries(
@@ -128,8 +132,21 @@ export function WalletsProvider ({ children }) {
// sort by priority, then add status field
return Object.values(merged)
.sort(walletPrioritySort)
- .map(w => ({ ...w, status: w.config?.enabled ? Status.Enabled : Status.Disabled }))
- }, [serverWallets, localWallets])
+ .map(w => {
+ return {
+ ...w,
+ support: {
+ recv: supportsReceive(w),
+ send: supportsSend(w)
+ },
+ status: {
+ any: w.config?.enabled && isConfigured(w) ? Status.Enabled : Status.Disabled,
+ send: w.config?.enabled && canSend(w) ? Status.Enabled : Status.Disabled,
+ recv: w.config?.enabled && canReceive(w) ? Status.Enabled : Status.Disabled
+ }
+ }
+ }).map(w => statusFromLog(w, logs))
+ }, [serverWallets, localWallets, logs])
const settings = useMemo(() => {
return {
diff --git a/wallets/lightning-address/index.js b/wallets/lightning-address/index.js
index e4c16bc1..e2bb7e52 100644
--- a/wallets/lightning-address/index.js
+++ b/wallets/lightning-address/index.js
@@ -22,6 +22,5 @@ export const fields = [
export const card = {
title: 'lightning address',
- subtitle: 'autowithdraw to a lightning address',
- badges: ['receive']
+ subtitle: 'autowithdraw to a lightning address'
}
diff --git a/wallets/lnbits/index.js b/wallets/lnbits/index.js
index edcadead..492086f7 100644
--- a/wallets/lnbits/index.js
+++ b/wallets/lnbits/index.js
@@ -55,5 +55,5 @@ export const fields = [
export const card = {
title: 'LNbits',
subtitle: 'use [LNbits](https://lnbits.com/) for payments',
- badges: ['send', 'receive']
+ image: { src: '/wallets/lnbits.svg' }
}
diff --git a/wallets/lnc/client.js b/wallets/lnc/client.js
index 72eab585..03f04152 100644
--- a/wallets/lnc/client.js
+++ b/wallets/lnc/client.js
@@ -38,11 +38,11 @@ export async function testSendPayment (credentials, { logger }) {
logger.info('connecting ...')
await lnc.connect()
- logger.ok('connected')
+ logger.info('connected')
logger.info('validating permissions ...')
await validateNarrowPerms(lnc)
- logger.ok('permissions ok')
+ logger.info('permissions ok')
return lnc.credentials.credentials
} finally {
diff --git a/wallets/lnc/index.js b/wallets/lnc/index.js
index 395762bf..c039678b 100644
--- a/wallets/lnc/index.js
+++ b/wallets/lnc/index.js
@@ -60,6 +60,5 @@ export const fields = [
export const card = {
title: 'LNC',
- subtitle: 'use Lightning Node Connect for LND payments',
- badges: ['send', 'budgetable']
+ subtitle: 'use Lightning Node Connect for LND payments'
}
diff --git a/wallets/lnd/index.js b/wallets/lnd/index.js
index e1d6395c..dc55aa84 100644
--- a/wallets/lnd/index.js
+++ b/wallets/lnd/index.js
@@ -50,5 +50,5 @@ export const fields = [
export const card = {
title: 'LND',
subtitle: 'autowithdraw to your Lightning Labs node',
- badges: ['receive']
+ image: { src: '/wallets/lnd.png' }
}
diff --git a/wallets/nwc/index.js b/wallets/nwc/index.js
index 49794d92..5bab2300 100644
--- a/wallets/nwc/index.js
+++ b/wallets/nwc/index.js
@@ -30,8 +30,7 @@ export const fields = [
export const card = {
title: 'NWC',
- subtitle: 'use Nostr Wallet Connect for payments',
- badges: ['send', 'receive', 'budgetable']
+ subtitle: 'use Nostr Wallet Connect for payments'
}
export async function nwcCall ({ nwcUrl, method, params }, { logger, timeout } = {}) {
diff --git a/wallets/phoenixd/index.js b/wallets/phoenixd/index.js
index 7bbec9eb..ad7f19ee 100644
--- a/wallets/phoenixd/index.js
+++ b/wallets/phoenixd/index.js
@@ -38,5 +38,5 @@ export const fields = [
export const card = {
title: 'phoenixd',
subtitle: 'use [phoenixd](https://phoenix.acinq.co/server) for payments',
- badges: ['send', 'receive']
+ image: { src: '/wallets/phoenixd.png' }
}
diff --git a/wallets/webln/index.js b/wallets/webln/index.js
index e59f51bc..cce91750 100644
--- a/wallets/webln/index.js
+++ b/wallets/webln/index.js
@@ -12,6 +12,5 @@ export const fields = []
export const card = {
title: 'WebLN',
- subtitle: 'use a [WebLN provider](https://www.webln.guide/ressources/webln-providers) for payments',
- badges: ['send']
+ subtitle: 'use a [WebLN provider](https://www.webln.guide/ressources/webln-providers) for payments'
}