diff --git a/api/resolvers/vault.js b/api/resolvers/vault.js
index 99c0486e..5915899f 100644
--- a/api/resolvers/vault.js
+++ b/api/resolvers/vault.js
@@ -55,8 +55,8 @@ export default {
for (const entry of entries) {
txs.push(models.vaultEntry.update({
- where: { id: entry.id },
- data: { key: entry.key, value: entry.value }
+ where: { userId_key: { userId: me.id, key: entry.key } },
+ data: { value: entry.value }
}))
}
await models.prisma.$transaction(txs)
diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js
index 146c81fe..c6273300 100644
--- a/api/resolvers/wallet.js
+++ b/api/resolvers/wallet.js
@@ -26,12 +26,13 @@ import { getNodeSockets, getOurPubkey } from '../lnd'
function injectResolvers (resolvers) {
console.group('injected GraphQL resolvers:')
- for (const w of walletDefs) {
- const resolverName = generateResolverName(w.walletField)
+ for (const walletDef of walletDefs) {
+ const resolverName = generateResolverName(walletDef.walletField)
console.log(resolverName)
resolvers.Mutation[resolverName] = async (parent, { settings, validateLightning, vaultEntries, ...data }, { me, models }) => {
// allow transformation of the data on validation (this is optional ... won't do anything if not implemented)
- const validData = await walletValidate(w, { ...data, ...settings, vaultEntries })
+ // TODO: our validation should be improved
+ const validData = await walletValidate(walletDef, { ...data, ...settings, vaultEntries })
if (validData) {
Object.keys(validData).filter(key => key in data).forEach(key => { data[key] = validData[key] })
Object.keys(validData).filter(key => key in settings).forEach(key => { settings[key] = validData[key] })
@@ -39,10 +40,12 @@ function injectResolvers (resolvers) {
return await upsertWallet({
wallet: {
- field: w.walletField,
- type: w.walletType
+ field: walletDef.walletField,
+ type: walletDef.walletType
},
- testCreateInvoice: w.testCreateInvoice && validateLightning ? (data) => w.testCreateInvoice(data, { me, models }) : null
+ testCreateInvoice: walletDef.testCreateInvoice && validateLightning
+ ? (data) => walletDef.testCreateInvoice(data, { me, models })
+ : null
}, {
settings,
data,
@@ -643,17 +646,13 @@ export const addWalletLog = async ({ wallet, level, message }, { models }) => {
}
async function upsertWallet (
- { wallet, testCreateInvoice },
- { settings, data, priorityOnly, canSend, canReceive },
- { me, models }
-) {
- if (!me) throw new GqlAuthenticationError()
+ { wallet, testCreateInvoice }, { settings, data, vaultEntries = [] }, { me, models }) {
+ if (!me) {
+ throw new GqlAuthenticationError()
+ }
assertApiKeyNotPermitted({ me })
- const { id, ...walletData } = data
- const { autoWithdrawThreshold, autoWithdrawMaxFeePercent, enabled, priority } = settings
-
- if (testCreateInvoice && !priorityOnly && canReceive && enabled) {
+ if (testCreateInvoice) {
try {
await testCreateInvoice(data)
} catch (err) {
@@ -666,103 +665,111 @@ async function upsertWallet (
}
}
- return await models.$transaction(async (tx) => {
- if (canReceive) {
- await tx.user.update({
- where: { id: me.id },
- data: {
- autoWithdrawMaxFeePercent,
- autoWithdrawThreshold
- }
- })
- }
+ const { id, enabled, priority, ...walletData } = data
+ const {
+ autoWithdrawThreshold,
+ autoWithdrawMaxFeePercent,
+ autoWithdrawMaxFeeTotal
+ } = settings
- let updatedWallet
- if (id) {
- const existingWalletTypeRecord = canReceive
- ? await tx[wallet.field].findUnique({
- where: { walletId: Number(id) }
- })
- : undefined
+ const txs = []
- updatedWallet = await tx.wallet.update({
+ if (id) {
+ const oldVaultEntries = await models.vaultEntry.findMany({ where: { userId: me.id, walletId: Number(id) } })
+
+ // createMany is the set difference of the new - old
+ // deleteMany is the set difference of the old - new
+ // updateMany is the intersection of the old and new
+ const difference = (a = [], b = [], key = 'key') => a.filter(x => !b.find(y => y[key] === x[key]))
+ const intersectionMerge = (a = [], b = [], key = 'key') => a.filter(x => b.find(y => y[key] === x[key]))
+ .map(x => ({ [key]: x[key], ...b.find(y => y[key] === x[key]) }))
+
+ txs.push(
+ models.wallet.update({
where: { id: Number(id), userId: me.id },
data: {
enabled,
priority,
- canSend,
- canReceive,
- // if send-only config or priority only, don't update the wallet type record
- ...(canReceive && !priorityOnly
- ? {
- [wallet.field]: existingWalletTypeRecord
- ? { update: walletData }
- : { create: walletData }
- }
- : {})
+ [wallet.field]: {
+ update: {
+ where: { walletId: Number(id) },
+ data: walletData
+ }
+ },
+ vaultEntries: {
+ deleteMany: difference(oldVaultEntries, vaultEntries, 'key').map(({ key }) => ({
+ userId: me.id, key
+ })),
+ create: difference(vaultEntries, oldVaultEntries, 'key').map(({ key, value }) => ({
+ key, value, userId: me.id
+ })),
+ update: intersectionMerge(oldVaultEntries, vaultEntries, 'key').map(({ key, value }) => ({
+ where: { userId_key: { userId: me.id, key } },
+ data: { value }
+ }))
+ }
},
include: {
- ...(canReceive && !priorityOnly ? { [wallet.field]: true } : {})
+ vaultEntries: true
}
})
- } else {
- updatedWallet = await tx.wallet.create({
+ )
+ } else {
+ txs.push(
+ models.wallet.create({
+ include: {
+ vaultEntries: true
+ },
data: {
enabled,
priority,
- canSend,
- canReceive,
userId: me.id,
type: wallet.type,
- // if send-only config or priority only, don't update the wallet type record
- ...(canReceive && !priorityOnly
- ? {
- [wallet.field]: {
- create: walletData
- }
- }
- : {})
+ [wallet.field]: {
+ create: walletData
+ },
+ vaultEntries: {
+ createMany: {
+ data: vaultEntries.map(({ key, value }) => ({ key, value, userId: me.id }))
+ }
+ }
}
})
- }
+ )
+ }
- const logs = []
- if (canReceive) {
- logs.push({
- userId: me.id,
- wallet: wallet.type,
- level: enabled ? 'SUCCESS' : 'INFO',
- message: id ? 'receive details updated' : 'wallet attached for receives'
- })
- logs.push({
- userId: me.id,
- wallet: wallet.type,
- level: enabled ? 'SUCCESS' : 'INFO',
- message: enabled ? 'receives enabled' : 'receives disabled'
- })
- }
-
- if (canSend) {
- logs.push({
- userId: me.id,
- wallet: wallet.type,
- level: enabled ? 'SUCCESS' : 'INFO',
- message: id ? 'send details updated' : 'wallet attached for sends'
- })
- logs.push({
- userId: me.id,
- wallet: wallet.type,
- level: enabled ? 'SUCCESS' : 'INFO',
- message: enabled ? 'sends enabled' : 'sends disabled'
- })
- }
-
- await tx.walletLog.createMany({
- data: logs
+ txs.push(
+ models.user.update({
+ where: { id: me.id },
+ data: {
+ autoWithdrawMaxFeePercent,
+ autoWithdrawThreshold,
+ autoWithdrawMaxFeeTotal
+ }
})
+ )
- return updatedWallet
- })
+ txs.push(
+ models.walletLog.createMany({
+ data: {
+ userId: me.id,
+ wallet: wallet.type,
+ level: 'SUCCESS',
+ message: id ? 'wallet details updated' : 'wallet attached'
+ }
+ }),
+ models.walletLog.create({
+ data: {
+ userId: me.id,
+ wallet: wallet.type,
+ level: enabled ? 'SUCCESS' : 'INFO',
+ message: enabled ? 'wallet enabled' : 'wallet disabled'
+ }
+ })
+ )
+
+ const [upsertedWallet] = await models.$transaction(txs)
+ return upsertedWallet
}
export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, headers, walletId = null }) {
diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js
index cfbfd98e..daeadf5c 100644
--- a/api/typeDefs/user.js
+++ b/api/typeDefs/user.js
@@ -182,11 +182,9 @@ export default gql`
withdrawMaxFeeDefault: Int!
autoWithdrawThreshold: Int
autoWithdrawMaxFeePercent: Float
-<<<<<<< HEAD
autoWithdrawMaxFeeTotal: Int
-=======
vaultKeyHash: String
->>>>>>> 002b1d19 (user vault and server side client wallets)
+ walletsUpdatedAt: Date
}
type UserOptional {
diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js
index ad697217..c0a88ee4 100644
--- a/api/typeDefs/wallet.js
+++ b/api/typeDefs/wallet.js
@@ -17,7 +17,7 @@ function mutationTypeDefs () {
.filter(isServerField)
.map(fieldToGqlArgOptional)
if (serverFields.length > 0) args += serverFields.join(', ') + ','
- args += 'settings: AutowithdrawSettings!, priorityOnly: Boolean, canSend: Boolean!, canReceive: Boolean!'
+ args += 'enabled: Boolean, priority: Int, vaultEntries: [VaultEntryInput!], settings: AutowithdrawSettings!, validateLightning: Boolean'
const resolverName = generateResolverName(w.walletField)
const typeDef = `${resolverName}(${args}): Wallet`
console.log(typeDef)
@@ -91,8 +91,6 @@ const typeDefs = `
enabled: Boolean!
priority: Int!
wallet: WalletDetails!
- canReceive: Boolean!
- canSend: Boolean!
vaultEntries: [VaultEntry!]!
}
@@ -100,8 +98,6 @@ const typeDefs = `
autoWithdrawThreshold: Int!
autoWithdrawMaxFeePercent: Float!
autoWithdrawMaxFeeTotal: Int!
- priority: Int
- enabled: Boolean
}
type Invoice {
diff --git a/components/fee-button.js b/components/fee-button.js
index 7926dc94..194dd123 100644
--- a/components/fee-button.js
+++ b/components/fee-button.js
@@ -93,7 +93,10 @@ function sortHelper (a, b) {
}
}
-export function FeeButtonProvider ({ baseLineItems = {}, useRemoteLineItems = () => null, children }) {
+const DEFAULT_BASE_LINE_ITEMS = {}
+const DEFAULT_USE_REMOTE_LINE_ITEMS = () => null
+
+export function FeeButtonProvider ({ baseLineItems = DEFAULT_BASE_LINE_ITEMS, useRemoteLineItems = DEFAULT_USE_REMOTE_LINE_ITEMS, children }) {
const [lineItems, setLineItems] = useState({})
const [disabled, setDisabled] = useState(false)
const { me } = useMe()
diff --git a/components/items.js b/components/items.js
index e09c2b06..738ee764 100644
--- a/components/items.js
+++ b/components/items.js
@@ -10,7 +10,10 @@ import { LIMIT } from '@/lib/cursor'
import ItemFull from './item-full'
import { useData } from './use-data'
-export default function Items ({ ssrData, variables = {}, query, destructureData, rank, noMoreText, Footer, filter = () => true }) {
+const DEFAULT_FILTER = () => true
+const DEFAULT_VARIABLES = {}
+
+export default function Items ({ ssrData, variables = DEFAULT_VARIABLES, query, destructureData, rank, noMoreText, Footer, filter = DEFAULT_FILTER }) {
const { data, fetchMore } = useQuery(query || SUB_ITEMS, { variables })
const Foooter = Footer || MoreFooter
const dat = useData(data, ssrData)
diff --git a/components/sub-select.js b/components/sub-select.js
index c4096222..7a682632 100644
--- a/components/sub-select.js
+++ b/components/sub-select.js
@@ -15,7 +15,11 @@ export function SubSelectInitial ({ sub }) {
}
}
-export function useSubs ({ prependSubs = [], sub, filterSubs = () => true, appendSubs = [] }) {
+const DEFAULT_PREPEND_SUBS = []
+const DEFAULT_APPEND_SUBS = []
+const DEFAULT_FILTER_SUBS = () => true
+
+export function useSubs ({ prependSubs = DEFAULT_PREPEND_SUBS, sub, filterSubs = DEFAULT_FILTER_SUBS, appendSubs = DEFAULT_APPEND_SUBS }) {
const { data } = useQuery(SUBS, SSR
? {}
: {
diff --git a/components/use-debounce-callback.js b/components/use-debounce-callback.js
index 3bd4ffc0..7c7f5b24 100644
--- a/components/use-debounce-callback.js
+++ b/components/use-debounce-callback.js
@@ -17,7 +17,9 @@ export function debounce (fn, time) {
}
}
-export default function useDebounceCallback (fn, time, deps = []) {
+const DEFAULT_DEPS = []
+
+export default function useDebounceCallback (fn, time, deps = DEFAULT_DEPS) {
const [args, setArgs] = useState([])
const memoFn = useCallback(fn, deps)
useNoInitialEffect(debounce(() => memoFn(...args), time), [memoFn, time, args])
diff --git a/components/use-indexeddb.js b/components/use-indexeddb.js
index 5086ff69..eac82872 100644
--- a/components/use-indexeddb.js
+++ b/components/use-indexeddb.js
@@ -4,7 +4,11 @@ export function getDbName (userId, name) {
return `app:storage:${userId ?? ''}${name ? `:${name}` : ''}`
}
-function useIndexedDB ({ dbName, storeName, options = { keyPath: 'id', autoIncrement: true }, indices = [], version = 1 }) {
+const DEFAULT_OPTIONS = { keyPath: 'id', autoIncrement: true }
+const DEFAULT_INDICES = []
+const DEFAULT_VERSION = 1
+
+function useIndexedDB ({ dbName, storeName, options = DEFAULT_OPTIONS, indices = DEFAULT_INDICES, version = DEFAULT_VERSION }) {
const [db, setDb] = useState(null)
const [error, setError] = useState(null)
const [notSupported, setNotSupported] = useState(false)
@@ -28,7 +32,7 @@ function useIndexedDB ({ dbName, storeName, options = { keyPath: 'id', autoIncre
} catch (error) {
handleError(error)
}
- }, [storeName, handleError])
+ }, [storeName, handleError, operationQueue])
useEffect(() => {
let isMounted = true
@@ -81,7 +85,7 @@ function useIndexedDB ({ dbName, storeName, options = { keyPath: 'id', autoIncre
db.close()
}
}
- }, [dbName, storeName, version, indices, handleError, processQueue])
+ }, [dbName, storeName, version, indices, options, handleError, processQueue])
const queueOperation = useCallback((operation) => {
if (notSupported) {
diff --git a/components/user-list.js b/components/user-list.js
index c2c2f953..40f77286 100644
--- a/components/user-list.js
+++ b/components/user-list.js
@@ -140,7 +140,9 @@ function UserHidden ({ rank, Embellish }) {
)
}
-export function ListUsers ({ users, rank, statComps = seperate(STAT_COMPONENTS, Seperator), Embellish, nymActionDropdown }) {
+const DEFAULT_STAT_COMPONENTS = seperate(STAT_COMPONENTS, Seperator)
+
+export function ListUsers ({ users, rank, statComps = DEFAULT_STAT_COMPONENTS, Embellish, nymActionDropdown }) {
return (
{users.map((user, i) => (
@@ -155,7 +157,7 @@ export function ListUsers ({ users, rank, statComps = seperate(STAT_COMPONENTS,
export default function UserList ({ ssrData, query, variables, destructureData, rank, footer = true, nymActionDropdown, statCompsProp }) {
const { data, fetchMore } = useQuery(query, { variables })
const dat = useData(data, ssrData)
- const [statComps, setStatComps] = useState(seperate(STAT_COMPONENTS, Seperator))
+ const [statComps, setStatComps] = useState(DEFAULT_STAT_COMPONENTS)
useEffect(() => {
// shift the stat we are sorting by to the front
diff --git a/components/vault/use-vault-configurator.js b/components/vault/use-vault-configurator.js
index 5056208d..f55e7cf1 100644
--- a/components/vault/use-vault-configurator.js
+++ b/components/vault/use-vault-configurator.js
@@ -2,7 +2,7 @@ import { useMutation, useQuery } from '@apollo/client'
import { useMe } from '../me'
import { useToast } from '../toast'
import useIndexedDB, { getDbName } from '../use-indexeddb'
-import { useCallback, useEffect, useState } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import { E_VAULT_KEY_EXISTS } from '@/lib/error'
import { CLEAR_VAULT, GET_VAULT_ENTRIES, UPDATE_VAULT_KEY } from '@/fragments/vault'
import { toHex } from '@/lib/hex'
@@ -21,7 +21,8 @@ const useImperativeQuery = (query) => {
export function useVaultConfigurator () {
const { me } = useMe()
const toaster = useToast()
- const { set, get, remove } = useIndexedDB({ dbName: getDbName(me?.id, 'vault'), storeName: 'vault' })
+ const idbConfig = useMemo(() => ({ dbName: getDbName(me?.id, 'vault'), storeName: 'vault' }), [me?.id])
+ const { set, get, remove } = useIndexedDB(idbConfig)
const [updateVaultKey] = useMutation(UPDATE_VAULT_KEY)
const getVaultEntries = useImperativeQuery(GET_VAULT_ENTRIES)
const [key, setKey] = useState(null)
@@ -46,7 +47,7 @@ export function useVaultConfigurator () {
// toaster?.danger('error loading vault configuration ' + e.message)
}
})()
- }, [me?.privates?.vaultKeyHash, keyHash, get, remove, toaster])
+ }, [me?.privates?.vaultKeyHash, keyHash, get, remove])
// clear vault: remove everything and reset the key
const [clearVault] = useMutation(CLEAR_VAULT, {
diff --git a/components/wallet-logger.js b/components/wallet-logger.js
index ca291c97..11daaade 100644
--- a/components/wallet-logger.js
+++ b/components/wallet-logger.js
@@ -92,11 +92,11 @@ function getWalletLogDbName (userId) {
function useWalletLogDB () {
const { me } = useMe()
- const { add, getPage, clear, error, notSupported } = useIndexedDB({
- dbName: getWalletLogDbName(me?.id),
- storeName: 'wallet_logs',
- indices: INDICES
- })
+ // memoize the idb config to avoid re-creating it on every render
+ const idbConfig = useMemo(() =>
+ ({ dbName: getWalletLogDbName(me?.id), storeName: 'wallet_logs', indices: INDICES }), [me?.id])
+ const { add, getPage, clear, error, notSupported } = useIndexedDB(idbConfig)
+
return { add, getPage, clear, error, notSupported }
}
diff --git a/fragments/users.js b/fragments/users.js
index 768be056..6feac837 100644
--- a/fragments/users.js
+++ b/fragments/users.js
@@ -3,68 +3,12 @@ import { COMMENTS, COMMENTS_ITEM_EXT_FIELDS } from './comments'
import { ITEM_FIELDS, ITEM_FULL_FIELDS } from './items'
import { SUB_FULL_FIELDS } from './subs'
-export const ME = gql`
- {
- me {
- id
- name
- bioId
- photoId
- privates {
- autoDropBolt11s
- diagnostics
- noReferralLinks
- fiatCurrency
- satsFilter
- hideCowboyHat
- hideFromTopUsers
- hideGithub
- hideNostr
- hideTwitter
- hideInvoiceDesc
- hideIsContributor
- hideWalletBalance
- hideWelcomeBanner
- imgproxyOnly
- showImagesAndVideos
- lastCheckedJobs
- nostrCrossposting
- noteAllDescendants
- noteCowboyHat
- noteDeposits
- noteWithdrawals
- noteEarning
- noteForwardedSats
- noteInvites
- noteItemSats
- noteJobIndicator
- noteMentions
- noteItemMentions
- sats
- tipDefault
- tipRandom
- tipRandomMin
- tipRandomMax
- tipPopover
- turboTipping
- zapUndos
- upvotePopover
- wildWestMode
- withdrawMaxFeeDefault
- lnAddr
- autoWithdrawMaxFeePercent
- autoWithdrawThreshold
- disableFreebies
- vaultKeyHash
- }
- optional {
- isContributor
- stacked
- streak
- githubId
- nostrAuthPubkey
- twitterId
- }
+const STREAK_FIELDS = gql`
+ fragment StreakFields on User {
+ optional {
+ streak
+ gunStreak
+ horseStreak
}
}
`
@@ -104,6 +48,8 @@ ${STREAK_FIELDS}
upvotePopover
wildWestMode
disableFreebies
+ vaultKeyHash
+ walletsUpdatedAt
}
optional {
isContributor
diff --git a/fragments/wallet.js b/fragments/wallet.js
index 0c880c5f..3e1a445c 100644
--- a/fragments/wallet.js
+++ b/fragments/wallet.js
@@ -106,86 +106,7 @@ mutation removeWallet($id: ID!) {
removeWallet(id: $id)
}
`
-
// XXX [WALLET] this needs to be updated if another server wallet is added
-export const WALLET = gql`
- query Wallet($id: ID!) {
- wallet(id: $id) {
- id
- createdAt
- priority
- type
- wallet {
- __typename
- ... on WalletLightningAddress {
- address
- }
- ... on WalletLnd {
- socket
- macaroon
- cert
- }
- ... on WalletCln {
- socket
- rune
- cert
- }
- ... on WalletLnbits {
- url
- invoiceKey
- }
- ... on WalletNwc {
- nwcUrlRecv
- }
- ... on WalletPhoenixd {
- url
- secondaryPassword
- }
- }
- }
- }
-`
-
-// XXX [WALLET] this needs to be updated if another server wallet is added
-export const WALLET_BY_TYPE = gql`
- query WalletByType($type: String!) {
- walletByType(type: $type) {
- id
- createdAt
- enabled
- priority
- type
- wallet {
- __typename
- ... on WalletLightningAddress {
- address
- }
- ... on WalletLnd {
- socket
- macaroon
- cert
- }
- ... on WalletCln {
- socket
- rune
- cert
- }
- ... on WalletLnbits {
- url
- invoiceKey
- }
- ... on WalletNwc {
- nwcUrlRecv
- }
- ... on WalletPhoenixd {
- url
- secondaryPassword
- }
- }
- }
- }
-`
-
export const WALLET_FIELDS = gql`
fragment WalletFields on Wallet {
id
@@ -197,12 +118,57 @@ export const WALLET_FIELDS = gql`
key
value
}
+ wallet {
+ __typename
+ ... on WalletLightningAddress {
+ address
+ }
+ ... on WalletLnd {
+ socket
+ macaroon
+ cert
+ }
+ ... on WalletCln {
+ socket
+ rune
+ cert
+ }
+ ... on WalletLnbits {
+ url
+ invoiceKey
+ }
+ ... on WalletNwc {
+ nwcUrlRecv
+ }
+ ... on WalletPhoenixd {
+ url
+ secondaryPassword
+ }
+ }
+ }
+`
+
+export const WALLET = gql`
+ ${WALLET_FIELDS}
+ query Wallet($id: ID!) {
+ wallet(id: $id) {
+ ...WalletFields
+ }
+ }
+`
+
+// XXX [WALLET] this needs to be updated if another server wallet is added
+export const WALLET_BY_TYPE = gql`
+ ${WALLET_FIELDS}
+ query WalletByType($type: String!) {
+ walletByType(type: $type) {
+ ...WalletFields
+ }
}
`
export const WALLETS = gql`
${WALLET_FIELDS}
-
query Wallets {
wallets {
...WalletFields
diff --git a/lib/validate.js b/lib/validate.js
index 3ca29757..96753c94 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -42,11 +42,11 @@ export async function formikValidate (validate, data) {
return result
}
-export async function walletValidate (wallet, data) {
- if (typeof wallet.def.fieldValidation === 'function') {
- return await formikValidate(wallet.def.fieldValidation, data)
+export async function walletValidate (walletDef, data) {
+ if (typeof walletDef.fieldValidation === 'function') {
+ return await formikValidate(walletDef.fieldValidation, data)
} else {
- return await ssValidate(wallet.def.fieldValidation, data)
+ return await ssValidate(walletDef.fieldValidation, data)
}
}
diff --git a/package-lock.json b/package-lock.json
index 9d391222..d4dd4c9f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -51,7 +51,7 @@
"mdast-util-gfm": "^3.0.0",
"mdast-util-to-string": "^4.0.0",
"micromark-extension-gfm": "^3.0.0",
- "next": "^14.2.15",
+ "next": "^14.2.16",
"next-auth": "^4.24.8",
"next-plausible": "^3.12.2",
"next-seo": "^6.6.0",
@@ -4124,9 +4124,9 @@
}
},
"node_modules/@next/env": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.15.tgz",
- "integrity": "sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ=="
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.16.tgz",
+ "integrity": "sha512-fLrX5TfJzHCbnZ9YUSnGW63tMV3L4nSfhgOQ0iCcX21Pt+VSTDuaLsSuL8J/2XAiVA5AnzvXDpf6pMs60QxOag=="
},
"node_modules/@next/eslint-plugin-next": {
"version": "14.2.15",
@@ -4184,9 +4184,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz",
- "integrity": "sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.16.tgz",
+ "integrity": "sha512-uFT34QojYkf0+nn6MEZ4gIWQ5aqGF11uIZ1HSxG+cSbj+Mg3+tYm8qXYd3dKN5jqKUm5rBVvf1PBRO/MeQ6rxw==",
"cpu": [
"arm64"
],
@@ -4199,9 +4199,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz",
- "integrity": "sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.16.tgz",
+ "integrity": "sha512-mCecsFkYezem0QiZlg2bau3Xul77VxUD38b/auAjohMA22G9KTJneUYMv78vWoCCFkleFAhY1NIvbyjj1ncG9g==",
"cpu": [
"x64"
],
@@ -4214,9 +4214,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz",
- "integrity": "sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.16.tgz",
+ "integrity": "sha512-yhkNA36+ECTC91KSyZcgWgKrYIyDnXZj8PqtJ+c2pMvj45xf7y/HrgI17hLdrcYamLfVt7pBaJUMxADtPaczHA==",
"cpu": [
"arm64"
],
@@ -4229,9 +4229,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz",
- "integrity": "sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.16.tgz",
+ "integrity": "sha512-X2YSyu5RMys8R2lA0yLMCOCtqFOoLxrq2YbazFvcPOE4i/isubYjkh+JCpRmqYfEuCVltvlo+oGfj/b5T2pKUA==",
"cpu": [
"arm64"
],
@@ -4244,9 +4244,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz",
- "integrity": "sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.16.tgz",
+ "integrity": "sha512-9AGcX7VAkGbc5zTSa+bjQ757tkjr6C/pKS7OK8cX7QEiK6MHIIezBLcQ7gQqbDW2k5yaqba2aDtaBeyyZh1i6Q==",
"cpu": [
"x64"
],
@@ -4259,9 +4259,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz",
- "integrity": "sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.16.tgz",
+ "integrity": "sha512-Klgeagrdun4WWDaOizdbtIIm8khUDQJ/5cRzdpXHfkbY91LxBXeejL4kbZBrpR/nmgRrQvmz4l3OtttNVkz2Sg==",
"cpu": [
"x64"
],
@@ -4274,9 +4274,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz",
- "integrity": "sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.16.tgz",
+ "integrity": "sha512-PwW8A1UC1Y0xIm83G3yFGPiOBftJK4zukTmk7DI1CebyMOoaVpd8aSy7K6GhobzhkjYvqS/QmzcfsWG2Dwizdg==",
"cpu": [
"arm64"
],
@@ -4289,9 +4289,9 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.15.tgz",
- "integrity": "sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.16.tgz",
+ "integrity": "sha512-jhPl3nN0oKEshJBNDAo0etGMzv0j3q3VYorTSFqH1o3rwv1MQRdor27u1zhkgsHPNeY1jxcgyx1ZsCkDD1IHgg==",
"cpu": [
"ia32"
],
@@ -4304,9 +4304,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz",
- "integrity": "sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.16.tgz",
+ "integrity": "sha512-OA7NtfxgirCjfqt+02BqxC3MIgM/JaGjw9tOe4fyZgPsqfseNiMPnCRP44Pfs+Gpo9zPN+SXaFsgP6vk8d571A==",
"cpu": [
"x64"
],
@@ -15494,11 +15494,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"node_modules/next": {
- "version": "14.2.15",
- "resolved": "https://registry.npmjs.org/next/-/next-14.2.15.tgz",
- "integrity": "sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.16.tgz",
+ "integrity": "sha512-LcO7WnFu6lYSvCzZoo1dB+IO0xXz5uEv52HF1IUN0IqVTUIZGHuuR10I5efiLadGt+4oZqTcNZyVVEem/TM5nA==",
"dependencies": {
- "@next/env": "14.2.15",
+ "@next/env": "14.2.16",
"@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
@@ -15513,15 +15513,15 @@
"node": ">=18.17.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "14.2.15",
- "@next/swc-darwin-x64": "14.2.15",
- "@next/swc-linux-arm64-gnu": "14.2.15",
- "@next/swc-linux-arm64-musl": "14.2.15",
- "@next/swc-linux-x64-gnu": "14.2.15",
- "@next/swc-linux-x64-musl": "14.2.15",
- "@next/swc-win32-arm64-msvc": "14.2.15",
- "@next/swc-win32-ia32-msvc": "14.2.15",
- "@next/swc-win32-x64-msvc": "14.2.15"
+ "@next/swc-darwin-arm64": "14.2.16",
+ "@next/swc-darwin-x64": "14.2.16",
+ "@next/swc-linux-arm64-gnu": "14.2.16",
+ "@next/swc-linux-arm64-musl": "14.2.16",
+ "@next/swc-linux-x64-gnu": "14.2.16",
+ "@next/swc-linux-x64-musl": "14.2.16",
+ "@next/swc-win32-arm64-msvc": "14.2.16",
+ "@next/swc-win32-ia32-msvc": "14.2.16",
+ "@next/swc-win32-x64-msvc": "14.2.16"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
diff --git a/package.json b/package.json
index 6fb481e5..9ea6c48f 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"mdast-util-gfm": "^3.0.0",
"mdast-util-to-string": "^4.0.0",
"micromark-extension-gfm": "^3.0.0",
- "next": "^14.2.15",
+ "next": "^14.2.16",
"next-auth": "^4.24.8",
"next-plausible": "^3.12.2",
"next-seo": "^6.6.0",
diff --git a/prisma/migrations/20241021224248_vault/migration.sql b/prisma/migrations/20241024175439_vault/migration.sql
similarity index 63%
rename from prisma/migrations/20241021224248_vault/migration.sql
rename to prisma/migrations/20241024175439_vault/migration.sql
index dd318e1f..ca5c3ea4 100644
--- a/prisma/migrations/20241021224248_vault/migration.sql
+++ b/prisma/migrations/20241024175439_vault/migration.sql
@@ -11,16 +11,14 @@ ALTER TYPE "WalletType" ADD VALUE 'LNC';
ALTER TYPE "WalletType" ADD VALUE 'WEBLN';
-- AlterTable
-ALTER TABLE "Wallet" ADD COLUMN "canReceive" BOOLEAN NOT NULL DEFAULT true,
-ADD COLUMN "canSend" BOOLEAN NOT NULL DEFAULT false;
-
--- AlterTable
-ALTER TABLE "users" ADD COLUMN "vaultKeyHash" TEXT NOT NULL DEFAULT '';
+ALTER TABLE "users" ADD COLUMN "vaultKeyHash" TEXT NOT NULL DEFAULT '',
+ADD COLUMN "walletsUpdatedAt" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "VaultEntry" (
"id" SERIAL NOT NULL,
- "key" VARCHAR(64) NOT NULL,
+ "key" TEXT NOT NULL,
+ "iv" TEXT NOT NULL,
"value" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
"walletId" INTEGER,
@@ -30,17 +28,32 @@ CREATE TABLE "VaultEntry" (
CONSTRAINT "VaultEntry_pkey" PRIMARY KEY ("id")
);
--- CreateIndex
-CREATE INDEX "VaultEntry_userId_idx" ON "VaultEntry"("userId");
-
-- CreateIndex
CREATE INDEX "VaultEntry_walletId_idx" ON "VaultEntry"("walletId");
-- CreateIndex
-CREATE UNIQUE INDEX "VaultEntry_userId_key_walletId_key" ON "VaultEntry"("userId", "key", "walletId");
+CREATE UNIQUE INDEX "VaultEntry_userId_key_key" ON "VaultEntry"("userId", "key");
+
+-- CreateIndex
+CREATE INDEX "Wallet_priority_idx" ON "Wallet"("priority");
-- AddForeignKey
ALTER TABLE "VaultEntry" ADD CONSTRAINT "VaultEntry_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VaultEntry" ADD CONSTRAINT "VaultEntry_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE FUNCTION wallet_updated_at_trigger() RETURNS TRIGGER AS $$
+BEGIN
+ UPDATE "users" SET "walletsUpdatedAt" = NOW() WHERE "id" = NEW."userId";
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER wallet_updated_at_trigger
+AFTER INSERT OR UPDATE ON "Wallet"
+FOR EACH ROW EXECUTE PROCEDURE wallet_updated_at_trigger();
+
+CREATE TRIGGER vault_entry_updated_at_trigger
+AFTER INSERT OR UPDATE ON "VaultEntry"
+FOR EACH ROW EXECUTE PROCEDURE wallet_updated_at_trigger();
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e9496a97..cd643e67 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -138,6 +138,7 @@ model User {
oneDayReferrals OneDayReferral[] @relation("OneDayReferral_referrer")
oneDayReferrees OneDayReferral[] @relation("OneDayReferral_referrees")
vaultKeyHash String @default("")
+ walletsUpdatedAt DateTime?
vaultEntries VaultEntry[] @relation("VaultEntries")
@@index([photoId])
@@ -187,16 +188,14 @@ enum WalletType {
}
model Wallet {
- id Int @id @default(autoincrement())
- createdAt DateTime @default(now()) @map("created_at")
- updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
- userId Int
- label String?
- enabled Boolean @default(true)
- priority Int @default(0)
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
- canReceive Boolean @default(true)
- canSend Boolean @default(false)
+ id Int @id @default(autoincrement())
+ createdAt DateTime @default(now()) @map("created_at")
+ updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
+ userId Int
+ label String?
+ enabled Boolean @default(true)
+ priority Int @default(0)
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
// NOTE: this denormalized json field exists to make polymorphic joins efficient
// when reading wallets ... it is populated by a trigger when wallet descendants update
@@ -218,11 +217,13 @@ model Wallet {
InvoiceForward InvoiceForward[]
@@index([userId])
+ @@index([priority])
}
model VaultEntry {
id Int @id @default(autoincrement())
- key String @db.VarChar(64)
+ key String @db.Text
+ iv String @db.Text
value String @db.Text
userId Int
walletId Int?
@@ -231,8 +232,7 @@ model VaultEntry {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
- @@unique([userId, key, walletId])
- @@index([userId])
+ @@unique([userId, key])
@@index([walletId])
}
diff --git a/wallets/common.js b/wallets/common.js
index 877f49ed..76d33aa9 100644
--- a/wallets/common.js
+++ b/wallets/common.js
@@ -74,24 +74,24 @@ function checkFields ({ fields, config }) {
return val
}
-export function isConfigured (wallet) {
- return isSendConfigured(wallet) || isReceiveConfigured(wallet)
+export function isConfigured ({ def, config }) {
+ return isSendConfigured({ def, config }) || isReceiveConfigured({ def, config })
}
-function isSendConfigured (wallet) {
- const fields = wallet.def.fields.filter(isClientField)
- return checkFields({ fields, config: wallet.config })
+function isSendConfigured ({ def, config }) {
+ const fields = def.fields.filter(isClientField)
+ return checkFields({ fields, config })
}
-function isReceiveConfigured (wallet) {
- const fields = wallet.def.fields.filter(isServerField)
- return checkFields({ fields, config: wallet.config })
+function isReceiveConfigured ({ def, config }) {
+ const fields = def.fields.filter(isServerField)
+ return checkFields({ fields, config })
}
-export function canSend (wallet) {
- return !!wallet.def.sendPayment && isSendConfigured(wallet)
+export function canSend ({ def, config }) {
+ return !!def.sendPayment && isSendConfigured({ def, config })
}
-export function canReceive (wallet) {
- return !wallet.def.clientOnly && isReceiveConfigured(wallet)
+export function canReceive ({ def, config }) {
+ return !def.clientOnly && isReceiveConfigured({ def, config })
}
diff --git a/wallets/config.js b/wallets/config.js
index c7d6a5b9..d4312ed9 100644
--- a/wallets/config.js
+++ b/wallets/config.js
@@ -35,13 +35,12 @@ export function useWalletConfigurator (wallet) {
const _validate = useCallback(async (config, validateLightning = true) => {
const { serverWithShared, clientWithShared } = siftConfig(wallet.def.fields, config)
- console.log('sifted', siftConfig(wallet.def.fields, config))
let clientConfig = clientWithShared
let serverConfig = serverWithShared
- if (canSend(wallet)) {
- let transformedConfig = await walletValidate(wallet, clientWithShared)
+ if (canSend({ def: wallet.def, config: clientConfig })) {
+ let transformedConfig = await walletValidate(wallet.def, clientWithShared)
if (transformedConfig) {
clientConfig = Object.assign(clientConfig, transformedConfig)
}
@@ -51,29 +50,29 @@ export function useWalletConfigurator (wallet) {
clientConfig = Object.assign(clientConfig, transformedConfig)
}
}
- }
-
- if (canReceive(wallet)) {
- const transformedConfig = await walletValidate(wallet, serverConfig)
+ } else if (canReceive({ def: wallet.def, config: serverConfig })) {
+ const transformedConfig = await walletValidate(wallet.def, serverConfig)
if (transformedConfig) {
serverConfig = Object.assign(serverConfig, transformedConfig)
}
+ } else {
+ throw new Error('configuration must be able to send or receive')
}
return { clientConfig, serverConfig }
}, [wallet])
const save = useCallback(async (newConfig, validateLightning = true) => {
- const { clientConfig, serverConfig } = _validate(newConfig, validateLightning)
+ const { clientConfig, serverConfig } = await _validate(newConfig, validateLightning)
// if vault is active, encrypt and send to server regardless of wallet type
if (isActive) {
await _saveToServer(serverConfig, clientConfig, validateLightning)
} else {
- if (canSend(wallet)) {
+ if (canSend({ def: wallet.def, config: clientConfig })) {
await _saveToLocal(clientConfig)
}
- if (canReceive(wallet)) {
+ if (canReceive({ def: wallet.def, config: serverConfig })) {
await _saveToServer(serverConfig, clientConfig, validateLightning)
}
}
@@ -84,18 +83,19 @@ export function useWalletConfigurator (wallet) {
}, [wallet.config?.id])
const _detachFromLocal = useCallback(async () => {
- // if vault is not active and has a client config, delete from local storage
window.localStorage.removeItem(getStorageKey(wallet.def.name, me?.id))
}, [me?.id, wallet.def.name])
const detach = useCallback(async () => {
if (isActive) {
+ // if vault is active, detach all wallets from server
await _detachFromServer()
} else {
if (wallet.config.id) {
await _detachFromServer()
}
+ // if vault is not active and has a client config, delete from local storage
await _detachFromLocal()
}
}, [isActive, _detachFromServer, _detachFromLocal])
diff --git a/wallets/graphql.js b/wallets/graphql.js
index cc399c32..0fbd055d 100644
--- a/wallets/graphql.js
+++ b/wallets/graphql.js
@@ -33,17 +33,18 @@ export function generateMutation (wallet) {
.filter(isServerField)
.map(f => `$${f.name}: String`)
.join(', ')
- headerArgs += ', $settings: AutowithdrawSettings!, $validateLightning: Boolean'
+ headerArgs += ', $enabled: Boolean, $priority: Int, $vaultEntries: [VaultEntryInput!], $settings: AutowithdrawSettings!, $validateLightning: Boolean'
let inputArgs = 'id: $id, '
inputArgs += wallet.fields
.filter(isServerField)
.map(f => `${f.name}: $${f.name}`).join(', ')
- inputArgs += ', settings: $settings, validateLightning: $validateLightning,'
+ inputArgs += ', enabled: $enabled, priority: $priority, vaultEntries: $vaultEntries, settings: $settings, validateLightning: $validateLightning'
- return gql`mutation ${resolverName}(${headerArgs}) {
+ return gql`
${WALLET_FIELDS}
- ${resolverName}(${inputArgs}) {
+ mutation ${resolverName}(${headerArgs}) {
+ ${resolverName}(${inputArgs}) {
...WalletFields
}
}`
diff --git a/wallets/index.js b/wallets/index.js
index 8b1ff6b2..d5b65434 100644
--- a/wallets/index.js
+++ b/wallets/index.js
@@ -1,6 +1,6 @@
import { useMe } from '@/components/me'
import { WALLETS } from '@/fragments/wallet'
-import { LONG_POLL_INTERVAL, SSR } from '@/lib/constants'
+import { SSR } from '@/lib/constants'
import { useQuery } from '@apollo/client'
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { getStorageKey, getWalletByType, Status, walletPrioritySort, canSend } from './common'
@@ -41,15 +41,17 @@ const walletDefsOnly = walletDefs.map(w => ({ def: w, config: {} }))
export function WalletsProvider ({ children }) {
const { decrypt } = useVault()
+ const { me } = useMe()
const { wallets: localWallets, reloadLocalWallets } = useLocalWallets()
- // TODO: instead of polling, this should only be called when the vault key is updated
- // or a denormalized field on the user 'vaultUpdatedAt' is changed
- const { data } = useQuery(WALLETS, {
- pollInterval: LONG_POLL_INTERVAL,
- nextFetchPolicy: 'cache-and-network',
- skip: SSR
- })
+ const { data, refetch } = useQuery(WALLETS,
+ SSR ? {} : { nextFetchPolicy: 'cache-and-network' })
+
+ useEffect(() => {
+ if (me?.privates?.walletsUpdatedAt) {
+ refetch()
+ }
+ }, [me?.privates?.walletsUpdatedAt, me?.privates?.vaultKeyHash, refetch])
const wallets = useMemo(() => {
// form wallets into a list of { config, def }
@@ -60,7 +62,9 @@ export function WalletsProvider ({ children }) {
config[key] = decrypt(value)
}
- return { config, def }
+ // the specific wallet config on the server is stored in wallet.wallet
+ // on the client, it's stored in unnested
+ return { config: { ...config, ...w.wallet }, def }
}) ?? []
// merge wallets on name
diff --git a/wallets/server.js b/wallets/server.js
index 09624ecb..7e004aa6 100644
--- a/wallets/server.js
+++ b/wallets/server.js
@@ -17,6 +17,7 @@ import { parsePaymentRequest } from 'ln-service'
import { toPositiveNumber } from '@/lib/validate'
import { PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
import { withTimeout } from '@/lib/time'
+import { canReceive } from './common'
export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln]
@@ -25,7 +26,7 @@ const MAX_PENDING_INVOICES_PER_WALLET = 25
export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360 }, { models }) {
// get the wallets in order of priority
const wallets = await models.wallet.findMany({
- where: { userId, enabled: true, canReceive: true },
+ where: { userId, enabled: true },
include: {
user: true
},
@@ -42,11 +43,14 @@ export async function createInvoice (userId, { msats, description, descriptionHa
const w = walletDefs.find(w => w.walletType === wallet.def.walletType)
try {
const { walletType, walletField, createInvoice } = w
+ if (!canReceive({ def: w, config: wallet })) {
+ continue
+ }
const walletFull = await models.wallet.findFirst({
where: {
userId,
- type: wallet.def.walletType
+ type: walletType
},
include: {
[walletField]: true