This commit is contained in:
ekzyis 2024-06-20 21:52:07 +02:00
parent 7c287ae58b
commit 6d3f7d4230
5 changed files with 108 additions and 28 deletions

View File

@ -7,7 +7,8 @@ import { bolt11Tags } from '@/lib/bolt11'
// wallet definitions
export const WALLET_DEFS = [
await import('@/components/wallet/lnbits')
await import('@/components/wallet/lnbits'),
await import('@/components/wallet/nwc')
]
export const Status = {
@ -41,6 +42,7 @@ export function useWallet (name) {
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?.()

View File

@ -18,6 +18,7 @@ export const fields = [
export const card = {
title: 'LNbits',
subtitle: 'use LNbits for payments',
badges: ['send only', 'non-custodialish']
}

103
components/wallet/nwc.js Normal file
View File

@ -0,0 +1,103 @@
import { NOSTR_PUBKEY_HEX } from '@/lib/nostr'
import { parseNwcUrl } from '@/lib/url'
import { Relay } from 'nostr-tools'
import { object, string } from 'yup'
export const name = 'nwc'
export const fields = [
{
name: 'nwcUrl',
label: 'connection',
type: 'password'
}
]
export const card = {
title: 'NWC',
subtitle: 'use Nostr Wallet Connect for payments',
badges: ['send only', 'non-custodialish']
}
export const schema = object({
nwcUrl: string()
.required('required')
.test(async (nwcUrl, context) => {
// run validation in sequence to control order of errors
// inspired by https://github.com/jquense/yup/issues/851#issuecomment-1049705180
try {
await string().required('required').validate(nwcUrl)
await string().matches(/^nostr\+?walletconnect:\/\//, { message: 'must start with nostr+walletconnect://' }).validate(nwcUrl)
let relayUrl, walletPubkey, secret
try {
({ relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl))
} catch {
// invalid URL error. handle as if pubkey validation failed to not confuse user.
throw new Error('pubkey must be 64 hex chars')
}
await string().required('pubkey required').trim().matches(NOSTR_PUBKEY_HEX, 'pubkey must be 64 hex chars').validate(walletPubkey)
await string().required('relay url required').trim().wss('relay must use wss://').validate(relayUrl)
await string().required('secret required').trim().matches(/^[0-9a-fA-F]{64}$/, 'secret must be 64 hex chars').validate(secret)
} catch (err) {
return context.createError({ message: err.message })
}
return true
})
})
export async function validate ({ logger, ...config }) {
return await getInfo({ logger, ...config })
}
async function getInfo ({ logger, nwcUrl }) {
const { relayUrl, walletPubkey } = parseNwcUrl(nwcUrl)
logger.info(`requesting info event from ${relayUrl}`)
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 {
return await new Promise((resolve, reject) => {
let found = false
const sub = relay.subscribe([
{
kinds: [13194],
authors: [walletPubkey]
}
], {
onevent (event) {
found = true
logger.ok(`received info event from ${relayUrl}`)
resolve(event)
},
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))
}
},
oneose () {
if (!found) {
const msg = 'EOSE received without info event'
logger.error(msg)
reject(new Error(msg))
}
sub?.close()
}
})
})
} finally {
// For some reason, websocket is already in CLOSING or CLOSED state.
// relay?.close()
if (relay) logger.info(`closed connection to ${relayUrl}`)
}
}

View File

@ -600,32 +600,6 @@ export const lnAddrSchema = ({ payerData, min, max, commentAllowed } = {}) =>
return accum
}, {})))
export const nwcSchema = object({
nwcUrl: string()
.required('required')
.test(async (nwcUrl, context) => {
// run validation in sequence to control order of errors
// inspired by https://github.com/jquense/yup/issues/851#issuecomment-1049705180
try {
await string().required('required').validate(nwcUrl)
await string().matches(/^nostr\+?walletconnect:\/\//, { message: 'must start with nostr+walletconnect://' }).validate(nwcUrl)
let relayUrl, walletPubkey, secret
try {
({ relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl))
} catch {
// invalid URL error. handle as if pubkey validation failed to not confuse user.
throw new Error('pubkey must be 64 hex chars')
}
await string().required('pubkey required').trim().matches(NOSTR_PUBKEY_HEX, 'pubkey must be 64 hex chars').validate(walletPubkey)
await string().required('relay url required').trim().wss('relay must use wss://').validate(relayUrl)
await string().required('secret required').trim().matches(/^[0-9a-fA-F]{64}$/, 'secret must be 64 hex chars').validate(secret)
} catch (err) {
return context.createError({ message: err.message })
}
return true
})
})
export const lncSchema = object({
pairingPhrase: array()
.transform(function (value, originalValue) {

View File

@ -28,7 +28,7 @@ export default function WalletSettings () {
return (
<CenterLayout>
<h2 className='pb-2'>{wallet.card.title}</h2>
<h6 className='text-muted text-center pb-3'>use {wallet.card.title} for payments</h6>
<h6 className='text-muted text-center pb-3'>{wallet.card.subtitle}</h6>
<WalletSecurityBanner />
<Form
initial={initial}