Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use bitcoin-connect for self-custodial zaps #715

Closed
wants to merge 12 commits into from
11 changes: 8 additions & 3 deletions components/qr.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import QRCode from 'qrcode.react'
import { CopyInput, InputSkeleton } from './form'
import InvoiceStatus from './invoice-status'
import { requestProvider } from 'webln'
import { useEffect } from 'react'
import { useWebLN } from './webln'

export default function Qr ({ asIs, value, webLn, statusVariant, description, status }) {
const qrValue = asIs ? value : 'lightning:' + value.toUpperCase()
const { provider } = useWebLN()
ekzyis marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
async function effect () {
if (webLn) {
try {
const provider = await requestProvider()
await provider.sendPayment(value)
if (provider) {
// TODO in strict mode, this tries to pay the invoice twice
// so an error is logged to the console for the second attempt.
// but the payment still succeeds. (only tested with LNbits so far)
await provider.sendPayment(value)
}
} catch (e) {
console.log(e.message)
}
Expand Down
59 changes: 59 additions & 0 deletions components/webln.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { createContext, useContext, useEffect, useState } from 'react'

const WebLNContext = createContext({})

export function WebLNProvider ({ children }) {
const [provider, setProvider] = useState(null)
const [info, setInfo] = useState(null)
// NOTE these functions are undefined initially - can this be a problem?
const [launchModal, setLaunchModal] = useState()
const [launchPaymentModal, setLaunchPaymentModal] = useState()
const [closeModal, setCloseModal] = useState()
const [isConnected, setIsConnected] = useState()

useEffect(() => {
const unsub = []
async function effect () {
const [isConnected, onConnected, onDisconnected, requestProvider, launchModal, launchPaymentModal, closeModal] = await import('@getalby/bitcoin-connect-react').then(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could import init here too and then call it to set Stacker News as the name of the connecting app (this will work for at least NWC and maybe other connectors in the future)

(mod) => [mod.isConnected, mod.onConnected, mod.onDisconnected, mod.requestProvider, mod.launchModal, mod.launchPaymentModal, mod.closeModal]
)

// if you want to store a function, you need to wrap it with another function because of updater functions
// see https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state
setLaunchModal(() => launchModal)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are imported and then passed as state, could they just be imported where they are actually used? actually I don't think they are even used yet, are they?

Copy link
Member Author

@ekzyis ekzyis Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are imported and then passed as state, could they just be imported where they are actually used?

I am importing all of them here so I can simply use what I need where I need it by calling useWebLN. If I would import them where they are actually used, I would spread useEffect with an async wrapper inside around our code base which is what I wanted to avoid. Simply calling useContext is a nicer syntax:

pages/wallet.js:

const { provider, info, launchModal } = useWebLN()

actually I don't think they are even used yet, are they?

You're right, isConnected isn't used outside of this function and launchPaymentModal isn't used because of this but the other functions are. I anticipated that I would use these two functions at some point, too.

setLaunchPaymentModal(() => launchPaymentModal)
setCloseModal(() => closeModal)
setIsConnected(() => isConnected)

if (isConnected()) {
// requestProvider will not launch a modal because a provider is already available.
// TODO but it might for wallets that must be unlocked?
const provider = await requestProvider()
setProvider(provider)
}
unsub.push(onConnected(async (provider) => {
setProvider(provider)
const info = await provider.getInfo()
setInfo(info)
}))
unsub.push(onDisconnected(() => {
setProvider(null)
setInfo(null)
}))
}
effect()

return () => unsub.forEach(fn => fn())
}, [setProvider])

const value = { provider, info, launchModal, launchPaymentModal, closeModal, isConnected }
return (
<WebLNContext.Provider value={value}>
{children}
</WebLNContext.Provider>
)
}

export function useWebLN () {
return useContext(WebLNContext)
}
Loading