stacker.news/wallets/common.js

164 lines
5.0 KiB
JavaScript
Raw Normal View History

2024-10-23 00:53:56 +00:00
import walletDefs from 'wallets/client'
export const Status = {
Enabled: 'Enabled',
2024-10-23 17:42:34 +00:00
Disabled: 'Disabled'
2024-10-23 00:53:56 +00:00
}
export function getWalletByName (name) {
return walletDefs.find(def => def.name === name)
}
export function getWalletByType (type) {
return walletDefs.find(def => def.walletType === type)
}
2024-10-23 22:17:35 +00:00
export function getStorageKey (name, userId) {
2024-10-23 00:53:56 +00:00
let storageKey = `wallet:${name}`
// WebLN has no credentials we need to scope to users
// so we can use the same storage key for all users
2024-10-23 22:17:35 +00:00
if (userId && name !== 'webln') {
storageKey = `${storageKey}:${userId}`
2024-10-23 00:53:56 +00:00
}
return storageKey
}
export function walletPrioritySort (w1, w2) {
2024-10-23 17:42:34 +00:00
// enabled/configured wallets always come before disabled/unconfigured wallets
if ((w1.config?.enabled && !w2.config?.enabled) || (isConfigured(w1) && !isConfigured(w2))) {
return -1
} else if ((w2.config?.enabled && !w1.config?.enabled) || (isConfigured(w2) && !isConfigured(w1))) {
return 1
}
const delta = w1.config?.priority - w2.config?.priority
2024-10-23 00:53:56 +00:00
// delta is NaN if either priority is undefined
if (!Number.isNaN(delta) && delta !== 0) return delta
// if one wallet has a priority but the other one doesn't, the one with the priority comes first
2024-10-23 17:42:34 +00:00
if (w1.config?.priority !== undefined && w2.config?.priority === undefined) return -1
if (w1.config?.priority === undefined && w2.config?.priority !== undefined) return 1
2024-10-23 00:53:56 +00:00
// both wallets have no priority set, falling back to other methods
// if both wallets have an id, use that as tie breaker
// since that's the order in which autowithdrawals are attempted
if (w1.config?.id && w2.config?.id) return Number(w1.config.id) - Number(w2.config.id)
// else we will use the card title as tie breaker
2024-10-23 17:42:34 +00:00
return w1.def.card.title < w2.def.card.title ? -1 : 1
}
export function isServerField (f) {
return f.serverOnly || !f.clientOnly
}
export function isClientField (f) {
return f.clientOnly || !f.serverOnly
}
function checkFields ({ fields, config }) {
// a wallet is configured if all of its required fields are set
let val = fields.every(f => {
2024-10-31 03:26:45 +00:00
if ((f.optional || f.generated) && !f.requiredWithout) return true
2024-10-25 19:10:37 +00:00
return !!config?.[f.name]
2024-10-23 17:42:34 +00:00
})
// however, a wallet is not configured if all fields are optional and none are set
// since that usually means that one of them is required
if (val && fields.length > 0) {
2024-10-31 03:26:45 +00:00
val = !(fields.every(f => f.optional || f.generated) && fields.every(f => !config?.[f.name]))
2024-10-23 17:42:34 +00:00
}
return val
}
2024-10-24 20:30:56 +00:00
export function isConfigured ({ def, config }) {
return isSendConfigured({ def, config }) || isReceiveConfigured({ def, config })
2024-10-23 17:42:34 +00:00
}
2024-10-24 20:30:56 +00:00
function isSendConfigured ({ def, config }) {
const fields = def.fields.filter(isClientField)
2024-10-31 03:26:45 +00:00
return (fields.length > 0 || def.isAvailable?.()) && checkFields({ fields, config })
2024-10-23 17:42:34 +00:00
}
2024-10-24 20:30:56 +00:00
function isReceiveConfigured ({ def, config }) {
const fields = def.fields.filter(isServerField)
2024-10-31 03:26:45 +00:00
return fields.length > 0 && checkFields({ fields, config })
2024-10-23 17:42:34 +00:00
}
2024-10-24 20:30:56 +00:00
export function canSend ({ def, config }) {
return !!def.sendPayment && isSendConfigured({ def, config })
2024-10-23 17:42:34 +00:00
}
2024-10-24 20:30:56 +00:00
export function canReceive ({ def, config }) {
2024-10-25 19:10:37 +00:00
return def.fields.some(f => f.serverOnly) && isReceiveConfigured({ def, config })
2024-10-23 00:53:56 +00:00
}
export function siftConfig (fields, config) {
const sifted = {
clientOnly: {},
serverOnly: {},
shared: {},
serverWithShared: {},
clientWithShared: {},
settings: null
}
for (const [key, value] of Object.entries(config)) {
if (['id'].includes(key)) {
sifted.serverOnly[key] = value
continue
}
if (['autoWithdrawMaxFeePercent', 'autoWithdrawThreshold', 'autoWithdrawMaxFeeTotal'].includes(key)) {
sifted.serverOnly[key] = value
sifted.settings = { ...sifted.settings, [key]: value }
continue
}
const field = fields.find(({ name }) => name === key)
if (field) {
if (field.serverOnly) {
sifted.serverOnly[key] = value
} else if (field.clientOnly) {
sifted.clientOnly[key] = value
} else {
sifted.shared[key] = value
}
} else if (['enabled', 'priority'].includes(key)) {
sifted.shared[key] = value
}
}
sifted.serverWithShared = { ...sifted.shared, ...sifted.serverOnly }
sifted.clientWithShared = { ...sifted.shared, ...sifted.clientOnly }
return sifted
}
export async function upsertWalletVariables ({ def, config }, encrypt, append = {}) {
const { serverWithShared, settings, clientOnly } = siftConfig(def.fields, config)
// if we are disconnected from the vault, we leave vaultEntries undefined so we don't
// delete entries from connected devices
let vaultEntries
if (clientOnly && encrypt) {
vaultEntries = []
for (const [key, value] of Object.entries(clientOnly)) {
if (value) {
vaultEntries.push({ key, ...await encrypt(value) })
}
}
}
return { ...serverWithShared, settings, vaultEntries, ...append }
}
export async function saveWalletLocally (name, config, userId) {
const storageKey = getStorageKey(name, userId)
window.localStorage.setItem(storageKey, JSON.stringify(config))
}