complete basic wallet
This commit is contained in:
@ -1,11 +1,12 @@
|
||||
import "./Invoice.css";
|
||||
import { useState } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { decode as invoiceDecode } from "light-bolt11-decoder";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import SendSats from "Element/SendSats";
|
||||
import ZapCircle from "Icons/ZapCircle";
|
||||
import useWebln from "Hooks/useWebln";
|
||||
import { useWallet } from "Wallet";
|
||||
import { decodeInvoice } from "Util";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -15,38 +16,12 @@ export interface InvoiceProps {
|
||||
|
||||
export default function Invoice(props: InvoiceProps) {
|
||||
const invoice = props.invoice;
|
||||
const webln = useWebln();
|
||||
const [showInvoice, setShowInvoice] = useState(false);
|
||||
const { formatMessage } = useIntl();
|
||||
const [showInvoice, setShowInvoice] = useState(false);
|
||||
const walletState = useWallet();
|
||||
const wallet = walletState.wallet;
|
||||
|
||||
const info = useMemo(() => {
|
||||
try {
|
||||
const parsed = invoiceDecode(invoice);
|
||||
|
||||
const amountSection = parsed.sections.find(a => a.name === "amount");
|
||||
const amount = amountSection ? (amountSection.value as number) : NaN;
|
||||
|
||||
const timestampSection = parsed.sections.find(a => a.name === "timestamp");
|
||||
const timestamp = timestampSection ? (timestampSection.value as number) : NaN;
|
||||
|
||||
const expirySection = parsed.sections.find(a => a.name === "expiry");
|
||||
const expire = expirySection ? (expirySection.value as number) : NaN;
|
||||
const descriptionSection = parsed.sections.find(a => a.name === "description")?.value;
|
||||
const ret = {
|
||||
amount: !isNaN(amount) ? amount / 1000 : 0,
|
||||
expire: !isNaN(timestamp) && !isNaN(expire) ? timestamp + expire : null,
|
||||
description: descriptionSection as string | undefined,
|
||||
expired: false,
|
||||
};
|
||||
if (ret.expire) {
|
||||
ret.expired = ret.expire < new Date().getTime() / 1000;
|
||||
}
|
||||
return ret;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, [invoice]);
|
||||
|
||||
const info = useMemo(() => decodeInvoice(invoice), [invoice]);
|
||||
const [isPaid, setIsPaid] = useState(false);
|
||||
const isExpired = info?.expired;
|
||||
const amount = info?.amount ?? 0;
|
||||
@ -71,9 +46,9 @@ export default function Invoice(props: InvoiceProps) {
|
||||
|
||||
async function payInvoice(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
e.stopPropagation();
|
||||
if (webln?.enabled) {
|
||||
if (wallet?.isReady) {
|
||||
try {
|
||||
await webln.sendPayment(invoice);
|
||||
await wallet.payInvoice(invoice);
|
||||
setIsPaid(true);
|
||||
} catch (error) {
|
||||
setShowInvoice(true);
|
||||
|
@ -3,6 +3,7 @@ import { useSelector, useDispatch } from "react-redux";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||
import { useLongPress } from "use-long-press";
|
||||
import { Event as NEvent, TaggedRawEvent, HexKey } from "@snort/nostr";
|
||||
|
||||
import Bookmark from "Icons/Bookmark";
|
||||
import Pin from "Icons/Pin";
|
||||
@ -19,6 +20,9 @@ import Heart from "Icons/Heart";
|
||||
import Dots from "Icons/Dots";
|
||||
import Zap from "Icons/Zap";
|
||||
import Reply from "Icons/Reply";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import ZapFast from "Icons/ZapFast";
|
||||
|
||||
import { formatShort } from "Number";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { hexToBech32, normalizeReaction, unwrap } from "Util";
|
||||
@ -27,15 +31,12 @@ import Reactions from "Element/Reactions";
|
||||
import SendSats from "Element/SendSats";
|
||||
import { ParsedZap, ZapsSummary } from "Element/Zap";
|
||||
import { useUserProfile } from "Feed/ProfileFeed";
|
||||
import { Event as NEvent, TaggedRawEvent, HexKey } from "@snort/nostr";
|
||||
import { RootState } from "State/Store";
|
||||
import { UserPreferences, setPinned, setBookmarked } from "State/Login";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { TranslateHost } from "Const";
|
||||
import useWebln from "Hooks/useWebln";
|
||||
import { LNURL } from "LNURL";
|
||||
import Spinner from "Icons/Spinner";
|
||||
import ZapFast from "Icons/ZapFast";
|
||||
import { useWallet } from "Wallet";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -69,7 +70,9 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
const [reply, setReply] = useState(false);
|
||||
const [tip, setTip] = useState(false);
|
||||
const [zapping, setZapping] = useState(false);
|
||||
const webln = useWebln();
|
||||
const walletState = useWallet();
|
||||
const wallet = walletState.wallet;
|
||||
|
||||
const isMine = ev.RootPubKey === login;
|
||||
const lang = window.navigator.language;
|
||||
const langNames = new Intl.DisplayNames([...window.navigator.languages], {
|
||||
@ -122,14 +125,14 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
if (zapping || e.isPropagationStopped()) return;
|
||||
|
||||
const lnurl = author?.lud16 || author?.lud06;
|
||||
if (webln?.enabled && lnurl) {
|
||||
if (wallet?.isReady() && lnurl) {
|
||||
setZapping(true);
|
||||
try {
|
||||
const handler = new LNURL(lnurl);
|
||||
await handler.load();
|
||||
const zap = handler.canZap ? await publisher.zap(prefs.defaultZapAmount * 1000, ev.PubKey, ev.Id) : undefined;
|
||||
const invoice = await handler.getInvoice(prefs.defaultZapAmount, undefined, zap);
|
||||
await await webln.sendPayment(unwrap(invoice.pr));
|
||||
await wallet.payInvoice(unwrap(invoice.pr));
|
||||
} catch (e) {
|
||||
console.warn("Fast zap failed", e);
|
||||
if (!(e instanceof Error) || e.message !== "User rejected") {
|
||||
@ -149,7 +152,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
||||
return (
|
||||
<>
|
||||
<div className={`reaction-pill ${didZap ? "reacted" : ""}`} {...longPress()} onClick={e => fastZap(e)}>
|
||||
<div className="reaction-pill-icon">{zapping ? <Spinner /> : webln?.enabled ? <ZapFast /> : <Zap />}</div>
|
||||
<div className="reaction-pill-icon">{zapping ? <Spinner /> : wallet?.isReady ? <ZapFast /> : <Zap />}</div>
|
||||
{zapTotal > 0 && <div className="reaction-pill-number">{formatShort(zapTotal)}</div>}
|
||||
</div>
|
||||
</>
|
||||
|
@ -14,7 +14,6 @@ import ProfileImage from "Element/ProfileImage";
|
||||
import Modal from "Element/Modal";
|
||||
import QrCode from "Element/QrCode";
|
||||
import Copy from "Element/Copy";
|
||||
import useWebln from "Hooks/useWebln";
|
||||
import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } from "LNURL";
|
||||
import { debounce } from "Util";
|
||||
|
||||
@ -77,11 +76,11 @@ export default function SendSats(props: SendSatsProps) {
|
||||
const [zapType, setZapType] = useState(ZapType.PublicZap);
|
||||
const [paying, setPaying] = useState<boolean>(false);
|
||||
|
||||
const webln = useWebln(props.show);
|
||||
const { formatMessage } = useIntl();
|
||||
const publisher = useEventPublisher();
|
||||
const canComment = handler ? (handler.canZap && zapType !== ZapType.NonZap) || handler.maxCommentLength > 0 : false;
|
||||
const wallet = useWallet();
|
||||
const walletState = useWallet();
|
||||
const wallet = walletState.wallet;
|
||||
|
||||
useEffect(() => {
|
||||
if (props.show) {
|
||||
@ -156,7 +155,7 @@ export default function SendSats(props: SendSatsProps) {
|
||||
const rsp = await handler.getInvoice(amount, comment, zap);
|
||||
if (rsp.pr) {
|
||||
setInvoice(rsp.pr);
|
||||
await payWebLNIfEnabled(rsp);
|
||||
await payWithWallet(rsp);
|
||||
}
|
||||
} catch (e) {
|
||||
handleLNURLError(e, formatMessage(messages.InvoiceFail));
|
||||
@ -206,11 +205,11 @@ export default function SendSats(props: SendSatsProps) {
|
||||
);
|
||||
}
|
||||
|
||||
async function payWebLNIfEnabled(invoice: LNURLInvoice) {
|
||||
async function payWithWallet(invoice: LNURLInvoice) {
|
||||
try {
|
||||
if (webln?.enabled) {
|
||||
if (wallet?.isReady) {
|
||||
setPaying(true);
|
||||
const res = await webln.sendPayment(invoice?.pr ?? "");
|
||||
const res = await wallet.payInvoice(invoice?.pr ?? "");
|
||||
console.log(res);
|
||||
setSuccess(invoice?.successAction ?? {});
|
||||
}
|
||||
@ -300,14 +299,6 @@ export default function SendSats(props: SendSatsProps) {
|
||||
);
|
||||
}
|
||||
|
||||
async function payWithWallet(pr: string) {
|
||||
if (wallet) {
|
||||
const rsp = await wallet.payInvoice(pr);
|
||||
console.debug(rsp);
|
||||
setSuccess(rsp as LNURLSuccessAction);
|
||||
}
|
||||
}
|
||||
|
||||
function payInvoice() {
|
||||
if (success || !invoice) return null;
|
||||
return (
|
||||
@ -316,7 +307,12 @@ export default function SendSats(props: SendSatsProps) {
|
||||
{props.notice && <b className="error">{props.notice}</b>}
|
||||
{paying ? (
|
||||
<h4>
|
||||
<FormattedMessage defaultMessage="Paying with WebLN" />
|
||||
<FormattedMessage
|
||||
defaultMessage="Paying with {wallet}"
|
||||
values={{
|
||||
wallet: walletState.config?.info.alias,
|
||||
}}
|
||||
/>
|
||||
...
|
||||
</h4>
|
||||
) : (
|
||||
@ -331,11 +327,6 @@ export default function SendSats(props: SendSatsProps) {
|
||||
<button className="wallet-action" type="button" onClick={() => window.open(`lightning:${invoice}`)}>
|
||||
<FormattedMessage {...messages.OpenWallet} />
|
||||
</button>
|
||||
{wallet && (
|
||||
<button className="wallet-action" type="button" onClick={() => payWithWallet(invoice)}>
|
||||
<FormattedMessage defaultMessage="Pay with Wallet" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -2,12 +2,10 @@ import "./Zap.css";
|
||||
import { useMemo } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useSelector } from "react-redux";
|
||||
import { decode as invoiceDecode } from "light-bolt11-decoder";
|
||||
import { bytesToHex } from "@noble/hashes/utils";
|
||||
import { sha256, unwrap } from "Util";
|
||||
import { Event, HexKey, TaggedRawEvent } from "@snort/nostr";
|
||||
|
||||
import { decodeInvoice, sha256, unwrap } from "Util";
|
||||
import { formatShort } from "Number";
|
||||
import { HexKey, TaggedRawEvent } from "@snort/nostr";
|
||||
import { Event } from "@snort/nostr";
|
||||
import Text from "Element/Text";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import { RootState } from "State/Store";
|
||||
@ -27,15 +25,9 @@ function getInvoice(zap: TaggedRawEvent) {
|
||||
console.debug("Invalid zap: ", zap);
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const decoded = invoiceDecode(bolt11);
|
||||
|
||||
const amount = decoded.sections.find(section => section.name === "amount")?.value;
|
||||
const hash = decoded.sections.find(section => section.name === "description_hash")?.value;
|
||||
|
||||
return { amount, hash: hash ? bytesToHex(hash as Uint8Array) : undefined };
|
||||
} catch {
|
||||
// ignore
|
||||
const decoded = decodeInvoice(bolt11);
|
||||
if (decoded) {
|
||||
return { amount: decoded?.amount, hash: decoded?.descriptionHash };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
Reference in New Issue
Block a user