Compare commits

..

6 Commits

Author SHA1 Message Date
ekzyis
ef700e188a Pass config with spread operator 2024-06-21 22:54:03 +02:00
ekzyis
e907c07d5f Use INFO level for 'wallet disabled' message 2024-06-21 22:52:45 +02:00
ekzyis
5f0e653efe Run validation during save 2024-06-21 22:52:45 +02:00
ekzyis
811946d0c6 Fix enableWallet
* wrong storage key was used
* broke if wallets with no configs existed
2024-06-21 22:52:45 +02:00
ekzyis
da37d7a00c Fix unused isDefault saved in config 2024-06-21 22:52:45 +02:00
ekzyis
748152b5c1 Add NWC wallet 2024-06-21 22:52:37 +02:00
5 changed files with 84 additions and 59 deletions

View File

@ -30,7 +30,7 @@ export function useWallet (name) {
const hash = bolt11Tags(bolt11).payment_hash
logger.info('sending payment:', `payment_hash=${hash}`)
try {
const { preimage } = await wallet.sendPayment({ bolt11, config })
const { preimage } = await wallet.sendPayment({ bolt11, ...config, logger })
logger.ok('payment successful:', `payment_hash=${hash}`, `preimage=${preimage}`)
} catch (err) {
const message = err.message || err.toString?.()
@ -39,18 +39,6 @@ export function useWallet (name) {
}
}, [wallet, config, logger])
const validate = useCallback(async (values) => {
try {
// validate should log custom INFO and OK message
// TODO: add timeout
return await wallet.validate({ logger, ...values })
} catch (err) {
const message = err.message || err.toString?.()
logger.error(message)
throw err
}
}, [wallet, logger])
const enable = useCallback(() => {
enableWallet(name, me)
logger.ok('wallet enabled')
@ -58,15 +46,18 @@ export function useWallet (name) {
const disable = useCallback(() => {
disableWallet(name, me)
logger.ok('wallet disabled')
logger.info('wallet disabled')
}, [name, me, logger])
const save = useCallback((values) => {
const save = useCallback(async (config) => {
try {
saveConfig(values)
// validate should log custom INFO and OK message
// TODO: add timeout
await wallet.validate({ logger, ...config })
saveConfig(config)
logger.ok('wallet attached')
} catch (err) {
const message = 'failed to attach: ' + err.message || err.toString?.()
const message = err.message || err.toString?.()
logger.error(message)
throw err
}
@ -78,7 +69,7 @@ export function useWallet (name) {
clearConfig()
logger.ok('wallet detached')
} catch (err) {
const message = 'failed to detach: ' + err.message || err.toString?.()
const message = err.message || err.toString?.()
logger.error(message)
throw err
}
@ -87,7 +78,6 @@ export function useWallet (name) {
return {
...wallet,
sendPayment,
validate,
config,
save,
delete: delete_,
@ -127,11 +117,11 @@ function getStorageKey (name, me) {
function enableWallet (name, me) {
// mark all wallets as disabled except the one to enable
for (const walletDef of WALLET_DEFS) {
const key = getStorageKey(walletDef.name, me)
let config = JSON.parse(window.localStorage.getItem(key))
const toEnable = walletDef.name === name
const key = getStorageKey(name, me)
const config = JSON.parse(window.localStorage.getItem(key))
if (config.enabled || toEnable) {
config.enabled = toEnable
if (config || toEnable) {
config = { ...config, enabled: toEnable }
window.localStorage.setItem(key, JSON.stringify(config))
}
}

View File

@ -22,8 +22,10 @@ export const card = {
badges: ['send only', 'non-custodialish']
}
export async function validate ({ logger, ...config }) {
return await getInfo({ logger, ...config })
export async function validate ({ logger, url, adminKey }) {
logger.info('trying to fetch wallet')
await getWallet(url, adminKey)
logger.ok('wallet found')
}
export const schema = object({
@ -51,28 +53,7 @@ export const schema = object({
adminKey: string().length(32)
})
async function getInfo ({ logger, ...config }) {
logger.info('trying to fetch wallet')
const response = await getWallet(config.url, config.adminKey)
logger.ok('wallet found')
return {
node: {
alias: response.name,
pubkey: ''
},
methods: [
'getInfo',
'getBalance',
'sendPayment'
],
version: '1.0',
supports: ['lightning']
}
}
export async function sendPayment ({ bolt11, config }) {
const { url, adminKey } = config
export async function sendPayment ({ bolt11, url, adminKey }) {
const response = await postPayment(url, adminKey, bolt11)
const checkResponse = await getPayment(url, adminKey, response.payment_hash)

View File

@ -1,6 +1,6 @@
import { NOSTR_PUBKEY_HEX } from '@/lib/nostr'
import { parseNwcUrl } from '@/lib/url'
import { Relay } from 'nostr-tools'
import { Relay, finalizeEvent, nip04 } from 'nostr-tools'
import { object, string } from 'yup'
export const name = 'nwc'
@ -45,11 +45,7 @@ export const schema = object({
})
})
export async function validate ({ logger, ...config }) {
return await getInfo({ logger, ...config })
}
async function getInfo ({ logger, nwcUrl }) {
export async function validate ({ logger, nwcUrl }) {
const { relayUrl, walletPubkey } = parseNwcUrl(nwcUrl)
logger.info(`requesting info event from ${relayUrl}`)
@ -101,3 +97,65 @@ async function getInfo ({ logger, nwcUrl }) {
if (relay) logger.info(`closed connection to ${relayUrl}`)
}
}
export async function sendPayment ({ bolt11, nwcUrl, logger }) {
const { relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl)
const relay = await Relay.connect(relayUrl).catch(() => {
// NOTE: passed error is undefined for some reason
const msg = `failed to connect to ${relayUrl}`
logger.error(msg)
throw new Error(msg)
})
logger.ok(`connected to ${relayUrl}`)
try {
const ret = await new Promise(function (resolve, reject) {
(async function () {
const payload = {
method: 'pay_invoice',
params: { invoice: bolt11 }
}
const content = await nip04.encrypt(secret, walletPubkey, JSON.stringify(payload))
const request = finalizeEvent({
kind: 23194,
created_at: Math.floor(Date.now() / 1000),
tags: [['p', walletPubkey]],
content
}, secret)
await relay.publish(request)
const filter = {
kinds: [23195],
authors: [walletPubkey],
'#e': [request.id]
}
relay.subscribe([filter], {
async onevent (response) {
try {
const content = JSON.parse(await nip04.decrypt(secret, walletPubkey, response.content))
if (content.error) return reject(new Error(content.error.message))
if (content.result) return resolve({ preimage: content.result.preimage })
} catch (err) {
return reject(err)
}
},
onclose (reason) {
if (!['closed by caller', 'relay connection closed by us'].includes(reason)) {
// only log if not closed by us (caller)
const msg = 'connection closed: ' + (reason || 'unknown reason')
logger.error(msg)
reject(new Error(msg))
}
}
})
})().catch(reject)
})
return ret
} finally {
// For some reason, websocket is already in CLOSING or CLOSED state.
// relay?.close()
if (relay) logger.info(`closed connection to ${relayUrl}`)
}
}

View File

@ -10,7 +10,6 @@ import { msatsToSats, numWithUnits, abbrNum, ensureB64, B64_URL_REGEX } from './
import * as usersFragments from '@/fragments/users'
import * as subsFragments from '@/fragments/subs'
import { isInvoicableMacaroon, isInvoiceMacaroon } from './macaroon'
import { parseNwcUrl } from './url'
import { datePivot } from './time'
import { decodeRune } from '@/lib/cln'
import bip39Words from './bip39-words'

View File

@ -21,9 +21,7 @@ export default function WalletSettings () {
...acc,
[field.name]: wallet.config?.[field.name] || ''
}
}, {
isDefault: wallet.isDefault || false
})
}, {})
return (
<CenterLayout>
@ -36,8 +34,7 @@ export default function WalletSettings () {
onSubmit={async ({ enabled, ...values }) => {
try {
const newConfig = !wallet.isConfigured
await wallet.validate(values)
wallet.save(values)
await wallet.save(values)
// enable wallet if checkbox was set or if wallet was just configured
if (enabled || newConfig) wallet.enable()
else wallet.disable()