server side config saves
This commit is contained in:
parent
4826ae5a7b
commit
ccdf346954
|
@ -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)
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
? {}
|
||||
: {
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 (
|
||||
<div className={styles.grid}>
|
||||
{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
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
|
@ -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])
|
||||
}
|
||||
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue