Compare commits
2 Commits
3d3dc52cec
...
34bfa89e74
Author | SHA1 | Date | |
---|---|---|---|
|
34bfa89e74 | ||
|
bb8c1ccffc |
@ -9,7 +9,7 @@ import { Status } from './webln'
|
||||
|
||||
export const isConfigured = status => [Status.Enabled, Status.Locked, Status.Error, true].includes(status)
|
||||
|
||||
export function WalletCard ({ title, badges, provider, status }) {
|
||||
export function WalletCard ({ title, badges, provider, status, href }) {
|
||||
const configured = isConfigured(status)
|
||||
let indicator = styles.disabled
|
||||
switch (status) {
|
||||
@ -42,14 +42,13 @@ export function WalletCard ({ title, badges, provider, status }) {
|
||||
</Badge>)}
|
||||
</Card.Subtitle>
|
||||
</Card.Body>
|
||||
{provider &&
|
||||
<Link href={`/settings/wallets/${provider}`}>
|
||||
<Card.Footer className={styles.attach}>
|
||||
{configured
|
||||
? <>configure<Gear width={14} height={14} /></>
|
||||
: <>attach<Plug width={14} height={14} /></>}
|
||||
</Card.Footer>
|
||||
</Link>}
|
||||
<Link href={href}>
|
||||
<Card.Footer className={styles.attach}>
|
||||
{configured
|
||||
? <>configure<Gear width={14} height={14} /></>
|
||||
: <>attach<Plug width={14} height={14} /></>}
|
||||
</Card.Footer>
|
||||
</Link>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
70
components/wallet-configurator.js
Normal file
70
components/wallet-configurator.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { WalletSecurityBanner } from './banners'
|
||||
import { Form } from './form'
|
||||
import { CenterLayout } from './layout'
|
||||
|
||||
export default function WalletConfigurator ({ config }) {
|
||||
const initial = config.provider
|
||||
|
||||
return (
|
||||
<CenterLayout>
|
||||
<h2 className='pb-2'>{config.title}</h2>
|
||||
<h6 className='text-muted text-center pb-3'>use {config.title} for payments</h6>
|
||||
<WalletSecurityBanner />
|
||||
<Form
|
||||
initial={{
|
||||
url: url || '',
|
||||
adminKey: adminKey || '',
|
||||
isDefault: isDefault || false
|
||||
}}
|
||||
schema={lnbitsSchema}
|
||||
onSubmit={async ({ isDefault, ...values }) => {
|
||||
try {
|
||||
await saveConfig(values)
|
||||
if (isDefault) setProvider(lnbits)
|
||||
toaster.success('saved settings')
|
||||
router.push('/settings/wallets')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toaster.danger('failed to attach: ' + err.message || err.toString?.())
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ClientInput
|
||||
initialValue={url}
|
||||
label='lnbits url'
|
||||
name='url'
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
<PasswordInput
|
||||
initialValue={adminKey}
|
||||
label='admin key'
|
||||
name='adminKey'
|
||||
newPass
|
||||
required
|
||||
/>
|
||||
<ClientCheckbox
|
||||
disabled={!configured || isDefault || enabledProviders.length === 1}
|
||||
initialValue={isDefault}
|
||||
label='default payment method'
|
||||
name='isDefault'
|
||||
/>
|
||||
<WalletButtonBar
|
||||
status={status} onDelete={async () => {
|
||||
try {
|
||||
await clearConfig()
|
||||
toaster.success('saved settings')
|
||||
router.push('/settings/wallets')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toaster.danger('failed to detach: ' + err.message || err.toString?.())
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form>
|
||||
<div className='mt-3 w-100'>
|
||||
<WalletLogs wallet={Wallet.LNbits} embedded />
|
||||
</div>
|
||||
</CenterLayout>
|
||||
)
|
||||
}
|
@ -2,6 +2,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState }
|
||||
import { LNbitsProvider, useLNbits } from './lnbits'
|
||||
import { NWCProvider, useNWC } from './nwc'
|
||||
import { LNCProvider, useLNC } from './lnc'
|
||||
import lnbits from './lnbits2'
|
||||
|
||||
const WebLNContext = createContext({})
|
||||
|
||||
|
22
components/webln/index2.js
Normal file
22
components/webln/index2.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { createContext, useContext, useMemo, useState } from 'react'
|
||||
import lnbits from './lnbits2'
|
||||
|
||||
const storageKey = 'webln:providers'
|
||||
|
||||
const WebLNContext = createContext({})
|
||||
|
||||
export function useWebLN () {
|
||||
const { provider } = useContext(WebLNContext)
|
||||
return provider
|
||||
}
|
||||
|
||||
export function RawWebLNProvider ({ children }) {
|
||||
const [provider, setProvider] = useState()
|
||||
|
||||
const value = useMemo(() => ({ provider, setProvider }), [])
|
||||
return (
|
||||
<WebLNContext.Provider value={value}>
|
||||
{children}
|
||||
</WebLNContext.Provider>
|
||||
)
|
||||
}
|
@ -67,8 +67,7 @@ const getPayment = async (baseUrl, adminKey, paymentHash) => {
|
||||
|
||||
export function LNbitsProvider ({ children }) {
|
||||
const me = useMe()
|
||||
const [url, setUrl] = useState('')
|
||||
const [adminKey, setAdminKey] = useState('')
|
||||
const [config, setConfig] = useState({})
|
||||
const [status, setStatus] = useState()
|
||||
const { logger } = useWalletLogger(Wallet.LNbits)
|
||||
|
||||
@ -78,7 +77,7 @@ export function LNbitsProvider ({ children }) {
|
||||
}
|
||||
|
||||
const getInfo = useCallback(async () => {
|
||||
const response = await getWallet(url, adminKey)
|
||||
const response = await getWallet(config.url, config.adminKey)
|
||||
return {
|
||||
node: {
|
||||
alias: response.name,
|
||||
@ -92,9 +91,11 @@ export function LNbitsProvider ({ children }) {
|
||||
version: '1.0',
|
||||
supports: ['lightning']
|
||||
}
|
||||
}, [url, adminKey])
|
||||
}, [config])
|
||||
|
||||
const sendPayment = useCallback(async (bolt11) => {
|
||||
const { url, adminKey } = config
|
||||
|
||||
const hash = bolt11Tags(bolt11).payment_hash
|
||||
logger.info('sending payment:', `payment_hash=${hash}`)
|
||||
|
||||
@ -111,7 +112,7 @@ export function LNbitsProvider ({ children }) {
|
||||
logger.error('payment failed:', `payment_hash=${hash}`, err.message || err.toString?.())
|
||||
throw err
|
||||
}
|
||||
}, [logger, url, adminKey])
|
||||
}, [logger, config])
|
||||
|
||||
const loadConfig = useCallback(async () => {
|
||||
let configStr = window.localStorage.getItem(storageKey)
|
||||
@ -129,20 +130,17 @@ export function LNbitsProvider ({ children }) {
|
||||
}
|
||||
|
||||
const config = JSON.parse(configStr)
|
||||
|
||||
const { url, adminKey } = config
|
||||
setUrl(url)
|
||||
setAdminKey(adminKey)
|
||||
setConfig(config)
|
||||
|
||||
logger.info(
|
||||
'loaded wallet config: ' +
|
||||
'adminKey=****** ' +
|
||||
`url=${url}`)
|
||||
`url=${config.url}`)
|
||||
|
||||
try {
|
||||
// validate config by trying to fetch wallet
|
||||
logger.info('trying to fetch wallet')
|
||||
await getWallet(url, adminKey)
|
||||
await getWallet(config.url, config.adminKey)
|
||||
logger.ok('wallet found')
|
||||
setStatus(Status.Enabled)
|
||||
logger.ok('wallet enabled')
|
||||
@ -156,8 +154,7 @@ export function LNbitsProvider ({ children }) {
|
||||
|
||||
const saveConfig = useCallback(async (config) => {
|
||||
// immediately store config so it's not lost even if config is invalid
|
||||
setUrl(config.url)
|
||||
setAdminKey(config.adminKey)
|
||||
setConfig(config)
|
||||
|
||||
// XXX This is insecure, XSS vulns could lead to loss of funds!
|
||||
// -> check how mutiny encrypts their wallet and/or check if we can leverage web workers
|
||||
@ -186,8 +183,7 @@ export function LNbitsProvider ({ children }) {
|
||||
|
||||
const clearConfig = useCallback(() => {
|
||||
window.localStorage.removeItem(storageKey)
|
||||
setUrl('')
|
||||
setAdminKey('')
|
||||
setConfig({})
|
||||
setStatus(undefined)
|
||||
}, [])
|
||||
|
||||
@ -196,8 +192,8 @@ export function LNbitsProvider ({ children }) {
|
||||
}, [])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({ name: 'LNbits', url, adminKey, status, saveConfig, clearConfig, getInfo, sendPayment }),
|
||||
[url, adminKey, status, saveConfig, clearConfig, getInfo, sendPayment])
|
||||
() => ({ name: 'LNbits', ...config, status, saveConfig, clearConfig, getInfo, sendPayment }),
|
||||
[config, status, saveConfig, clearConfig, getInfo, sendPayment])
|
||||
return (
|
||||
<LNbitsContext.Provider value={value}>
|
||||
{children}
|
||||
|
124
components/webln/lnbits2.js
Normal file
124
components/webln/lnbits2.js
Normal file
@ -0,0 +1,124 @@
|
||||
import { bolt11Tags } from '@/lib/bolt11'
|
||||
|
||||
export const name = 'LNbits'
|
||||
|
||||
export const config = {
|
||||
provider: {
|
||||
url: {
|
||||
label: 'lnbits url',
|
||||
type: 'text'
|
||||
},
|
||||
adminKey: {
|
||||
label: 'admin key',
|
||||
type: 'password'
|
||||
}
|
||||
},
|
||||
card: {
|
||||
title: 'LNbits',
|
||||
badges: ['send only', 'non-custodialish'],
|
||||
href: '/settings/wallets/lnbits'
|
||||
}
|
||||
}
|
||||
|
||||
export function getInfo ({ config, logger }) {
|
||||
return async function () {
|
||||
const response = await getWallet(config.url, config.adminKey)
|
||||
return {
|
||||
node: {
|
||||
alias: response.name,
|
||||
pubkey: ''
|
||||
},
|
||||
methods: [
|
||||
'getInfo',
|
||||
'getBalance',
|
||||
'sendPayment'
|
||||
],
|
||||
version: '1.0',
|
||||
supports: ['lightning']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function sendPayment ({ config, logger }) {
|
||||
return async function (bolt11) {
|
||||
const { url, adminKey } = config
|
||||
|
||||
const hash = bolt11Tags(bolt11).payment_hash
|
||||
logger.info('sending payment:', `payment_hash=${hash}`)
|
||||
|
||||
try {
|
||||
const response = await postPayment(url, adminKey, bolt11)
|
||||
|
||||
const checkResponse = await getPayment(url, adminKey, response.payment_hash)
|
||||
if (!checkResponse.preimage) {
|
||||
throw new Error('No preimage')
|
||||
}
|
||||
|
||||
const preimage = checkResponse.preimage
|
||||
logger.ok('payment successful:', `payment_hash=${hash}`, `preimage=${preimage}`)
|
||||
return { preimage }
|
||||
} catch (err) {
|
||||
logger.error('payment failed:', `payment_hash=${hash}`, err.message || err.toString?.())
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getWallet (baseUrl, adminKey) {
|
||||
const url = baseUrl.replace(/\/+$/, '')
|
||||
const path = '/api/v1/wallet'
|
||||
|
||||
const headers = new Headers()
|
||||
headers.append('Accept', 'application/json')
|
||||
headers.append('Content-Type', 'application/json')
|
||||
headers.append('X-Api-Key', adminKey)
|
||||
|
||||
const res = await fetch(url + path, { method: 'GET', headers })
|
||||
if (!res.ok) {
|
||||
const errBody = await res.json()
|
||||
throw new Error(errBody.detail)
|
||||
}
|
||||
|
||||
const wallet = await res.json()
|
||||
return wallet
|
||||
}
|
||||
|
||||
async function postPayment (baseUrl, adminKey, bolt11) {
|
||||
const url = baseUrl.replace(/\/+$/, '')
|
||||
const path = '/api/v1/payments'
|
||||
|
||||
const headers = new Headers()
|
||||
headers.append('Accept', 'application/json')
|
||||
headers.append('Content-Type', 'application/json')
|
||||
headers.append('X-Api-Key', adminKey)
|
||||
|
||||
const body = JSON.stringify({ bolt11, out: true })
|
||||
|
||||
const res = await fetch(url + path, { method: 'POST', headers, body })
|
||||
if (!res.ok) {
|
||||
const errBody = await res.json()
|
||||
throw new Error(errBody.detail)
|
||||
}
|
||||
|
||||
const payment = await res.json()
|
||||
return payment
|
||||
}
|
||||
|
||||
async function getPayment (baseUrl, adminKey, paymentHash) {
|
||||
const url = baseUrl.replace(/\/+$/, '')
|
||||
const path = `/api/v1/payments/${paymentHash}`
|
||||
|
||||
const headers = new Headers()
|
||||
headers.append('Accept', 'application/json')
|
||||
headers.append('Content-Type', 'application/json')
|
||||
headers.append('X-Api-Key', adminKey)
|
||||
|
||||
const res = await fetch(url + path, { method: 'GET', headers })
|
||||
if (!res.ok) {
|
||||
const errBody = await res.json()
|
||||
throw new Error(errBody.detail)
|
||||
}
|
||||
|
||||
const payment = await res.json()
|
||||
return payment
|
||||
}
|
@ -2,28 +2,32 @@ import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||
import Layout from '@/components/layout'
|
||||
import styles from '@/styles/wallet.module.css'
|
||||
import { WalletCard } from '@/components/wallet-card'
|
||||
import { LightningAddressWalletCard } from './lightning-address'
|
||||
import { LNbitsCard } from './lnbits'
|
||||
import { NWCCard } from './nwc'
|
||||
import { LNDCard } from './lnd'
|
||||
import { CLNCard } from './cln'
|
||||
// import { LightningAddressWalletCard } from './lightning-address'
|
||||
// import { LNbitsCard } from './lnbits'
|
||||
// import { NWCCard } from './nwc'
|
||||
// import { LNDCard } from './lnd'
|
||||
// import { CLNCard } from './cln'
|
||||
import { WALLETS } from '@/fragments/wallet'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import PageLoading from '@/components/page-loading'
|
||||
import { LNCCard } from './lnc'
|
||||
// import { useQuery } from '@apollo/client'
|
||||
// import PageLoading from '@/components/page-loading'
|
||||
// import { LNCCard } from './lnc'
|
||||
import Link from 'next/link'
|
||||
import { Wallet as W } from '@/lib/constants'
|
||||
// import { Wallet as W } from '@/lib/constants'
|
||||
import { config as lnbitsConfig } from '@/components/webln/lnbits2'
|
||||
|
||||
// TODO: load configs without individual imports?
|
||||
const walletConfigs = [lnbitsConfig]
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ query: WALLETS, authRequired: true })
|
||||
|
||||
export default function Wallet ({ ssrData }) {
|
||||
const { data } = useQuery(WALLETS)
|
||||
|
||||
if (!data && !ssrData) return <PageLoading />
|
||||
const { wallets } = data || ssrData
|
||||
const lnd = wallets.find(w => w.type === W.LND.type)
|
||||
const lnaddr = wallets.find(w => w.type === W.LnAddr.type)
|
||||
const cln = wallets.find(w => w.type === W.CLN.type)
|
||||
// const { data } = useQuery(WALLETS)
|
||||
//
|
||||
// if (!data && !ssrData) return <PageLoading />
|
||||
// const { wallets } = data || ssrData
|
||||
// const lnd = wallets.find(w => w.type === W.LND.type)
|
||||
// const lnaddr = wallets.find(w => w.type === W.LnAddr.type)
|
||||
// const cln = wallets.find(w => w.type === W.CLN.type)
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
@ -36,15 +40,9 @@ export default function Wallet ({ ssrData }) {
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.walletGrid}>
|
||||
<LightningAddressWalletCard wallet={lnaddr} />
|
||||
<LNDCard wallet={lnd} />
|
||||
<CLNCard wallet={cln} />
|
||||
<LNbitsCard />
|
||||
<NWCCard />
|
||||
<LNCCard />
|
||||
<WalletCard title='coming soon' badges={['probably']} />
|
||||
<WalletCard title='coming soon' badges={['we hope']} />
|
||||
<WalletCard title='coming soon' badges={['tm']} />
|
||||
{walletConfigs.map((config, i) => (
|
||||
<WalletCard key={i} {...config.card} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user