cleanup lnurl code

This commit is contained in:
hzrd149 2024-11-22 11:12:38 -06:00
parent a4d691a6cf
commit 01d7db3232
31 changed files with 623 additions and 432 deletions

View File

@ -28,9 +28,9 @@
"@codemirror/autocomplete": "^6.18.3",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.10.3",
"@codemirror/view": "^6.34.3",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@codemirror/view": "^6.35.0",
"@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5",
"@getalby/bitcoin-connect": "^3.6.2",
"@getalby/bitcoin-connect-react": "^3.6.2",
"@noble/ciphers": "^1.0.0",
@ -56,7 +56,8 @@
"chart.js": "^4.4.6",
"cheerio": "^1.0.0",
"chroma-js": "^2.6.0",
"codemirror-json-schema": "^0.6.1",
"codemirror": "^6.0.1",
"codemirror-json-schema": "^0.7.9",
"dayjs": "^1.11.13",
"debug": "^4.3.7",
"easymde": "^2.18.0",
@ -117,7 +118,7 @@
"zen-observable": "^0.10.0"
},
"devDependencies": {
"@changesets/cli": "^2.27.9",
"@changesets/cli": "^2.27.10",
"@types/chroma-js": "^2.4.4",
"@types/debug": "^4.1.12",
"@types/dom-serial": "^1.0.6",

814
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ import Timestamp from "../../timestamp";
import TextNoteContents from "../../note/timeline-note/text-note-contents";
import UserAvatar from "../../user/user-avatar";
import { LightningIcon } from "../../icons";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import ZapReceiptMenu from "../../zap/zap-receipt-menu";
import { EmbedEventPointer } from "../index";
@ -48,7 +48,7 @@ export default function EmbeddedZapRecept({ zap, ...props }: Omit<CardProps, "ch
{payment.amount && (
<>
<LightningIcon color="yellow.500" boxSize={5} />
<Text>{readablizeSats(payment.amount / 1000)}</Text>
<Text>{humanReadableSats(payment.amount / 1000)}</Text>
</>
)}

View File

@ -24,7 +24,7 @@ import lnurlMetadataService from "../../services/lnurl-metadata";
import signingService from "../../services/signing";
import accountService from "../../services/account";
import PayStep from "./pay-step";
import { getInvoiceFromCallbackUrl } from "../../helpers/lnurl";
import { getInvoice } from "../../../../applesauce/packages/core/src/helpers/lnurl";
import UserLink from "../user/user-link";
import { getEventRelayHints } from "../../services/event-relay-hint";
import { eventStore, queryStore } from "../../services/event-store";
@ -57,7 +57,7 @@ async function getPayRequestForPubkey(
callback.searchParams.append("amount", String(amount));
if (comment) callback.searchParams.append("comment", comment);
const invoice = await getInvoiceFromCallbackUrl(callback);
const invoice = await getInvoice(callback);
return { invoice, pubkey };
}
@ -101,7 +101,7 @@ async function getPayRequestForPubkey(
callback.searchParams.append("amount", String(amount));
callback.searchParams.append("nostr", JSON.stringify(signed));
const invoice = await getInvoiceFromCallbackUrl(callback);
const invoice = await getInvoice(callback);
return { invoice, pubkey };
}

View File

@ -2,7 +2,7 @@ import { Box, Button, Flex, Input, Text } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { NostrEvent } from "../../types/nostr-event";
import { readablizeSats } from "../../helpers/bolt11";
import { humanReadableSats } from "../../helpers/lightning";
import { LightningIcon } from "../icons";
import useUserLNURLMetadata from "../../hooks/use-user-lnurl-metadata";
import { getZapSplits } from "../../helpers/nostr/zaps";
@ -117,7 +117,7 @@ export default function InputStep({
size="md"
autoFocus
>
{actionName} {readablizeSats(watch("amount"))} sats
{actionName} {humanReadableSats(watch("amount"))} sats
</Button>
</Flex>
</Flex>

View File

@ -16,7 +16,7 @@ import {
} from "@chakra-ui/react";
import { parseBolt11 } from "applesauce-core/helpers";
import { readablizeSats } from "../../helpers/bolt11";
import { humanReadableSats } from "../../helpers/lightning";
import { CopyIconButton } from "../copy-icon-button";
import QrCode02 from "../icons/qr-code-02";
import QrCodeSvg from "../qr-code/qr-code-svg";
@ -94,7 +94,7 @@ export default function InlineInvoiceCard({
<CopyIconButton value={invoice.paymentRequest} aria-label="Copy Invoice" />
<IconButton icon={<QrCode02 boxSize={6} />} onClick={more.onToggle} aria-label="Show QrCode" />
<Button as="a" onClick={handleClick} isLoading={loading} href={`lightning:${paymentRequest}`}>
Pay {invoice.amount ? readablizeSats(invoice.amount / 1000) + " sats" : ""}
Pay {invoice.amount ? humanReadableSats(invoice.amount / 1000) + " sats" : ""}
</Button>
</ButtonGroup>
</Flex>

View File

@ -1,7 +1,7 @@
import { Button, ButtonProps, IconButton, useDisclosure } from "@chakra-ui/react";
import { getZapSender } from "applesauce-core/helpers";
import { readablizeSats } from "../../helpers/bolt11";
import { humanReadableSats } from "../../helpers/lightning";
import { totalZaps } from "../../helpers/nostr/zaps";
import useCurrentAccount from "../../hooks/use-current-account";
import useEventZaps from "../../hooks/use-event-zaps";
@ -48,7 +48,7 @@ export default function NoteZapButton({ event, allowComment, showEventPreview, .
onClick={onOpen}
isDisabled={!canZap}
>
{readablizeSats(total / 1000)}
{humanReadableSats(total / 1000)}
</Button>
) : (
<IconButton

View File

@ -6,7 +6,7 @@ import { getZapPayment, getZapRequest } from "applesauce-core/helpers";
import useEventZaps from "../../../../hooks/use-event-zaps";
import UserAvatar from "../../../user/user-avatar";
import { readablizeSats } from "../../../../helpers/bolt11";
import { humanReadableSats } from "../../../../helpers/lightning";
import { LightningIcon } from "../../../icons";
function ZapBubble({ zap }: { zap: NostrEvent }) {
@ -18,7 +18,7 @@ function ZapBubble({ zap }: { zap: NostrEvent }) {
return (
<Tag key={zap.id} borderRadius="full" py="1" flexShrink={0} variant="outline">
<LightningIcon mr="1" color="yellow.400" />
<TagLabel fontWeight="bold">{readablizeSats((payment.amount ?? 0) / 1000)}</TagLabel>
<TagLabel fontWeight="bold">{humanReadableSats((payment.amount ?? 0) / 1000)}</TagLabel>
<UserAvatar pubkey={request.pubkey} size="xs" square={false} ml="2" />
</Tag>
);

View File

@ -1,11 +0,0 @@
import { bech32 } from "bech32";
/** @deprecated */
export function decodeText(encoded: string) {
const decoded = bech32.decode(encoded, Infinity);
const text = new TextDecoder().decode(new Uint8Array(bech32.fromWords(decoded.words)));
return {
text,
prefix: decoded.prefix,
};
}

View File

@ -1,41 +0,0 @@
import { decode, Section, AmountSection, DescriptionSection, TimestampSection } from "light-bolt11-decoder";
import dayjs from "dayjs";
export type ParsedInvoice = {
paymentRequest: string;
description: string;
amount?: number;
timestamp: Date;
expiry: Date;
};
function isDescription(section: Section): section is DescriptionSection {
return section.name === "description";
}
function isAmount(section: Section): section is AmountSection {
return section.name === "amount";
}
function isTimestamp(section: Section): section is TimestampSection {
return section.name === "timestamp";
}
export function parsePaymentRequest(paymentRequest: string): ParsedInvoice {
const decoded = decode(paymentRequest);
const timestamp = decoded.sections.find(isTimestamp)?.value ?? 0;
return {
paymentRequest: decoded.paymentRequest,
description: decoded.sections.find(isDescription)?.value ?? "",
amount: parseInt(decoded.sections.find(isAmount)?.value ?? "0"),
timestamp: dayjs.unix(timestamp).toDate(),
expiry: dayjs.unix(timestamp + decoded.expiry).toDate(),
};
}
// based on https://stackoverflow.com/a/10469752
export function readablizeSats(sats: number) {
if (sats === 0) return "0";
var s = ["", "K", "M"];
var e = Math.floor(Math.log(sats) / Math.log(1000));
return Math.round((sats / Math.pow(1000, e)) * 100) / 100 + s[e];
}

7
src/helpers/lightning.ts Normal file
View File

@ -0,0 +1,7 @@
// based on https://stackoverflow.com/a/10469752
export function humanReadableSats(sats: number) {
if (sats === 0) return "0";
var s = ["", "K", "M"];
var e = Math.floor(Math.log(sats) / Math.log(1000));
return Math.round((sats / Math.pow(1000, e)) * 100) / 100 + s[e];
}

View File

@ -1,46 +1,9 @@
import { decodeText } from "./bech32";
import { parsePaymentRequest } from "./bolt11";
import { decodeLNURL } from "applesauce-core/helpers";
export function isLNURL(lnurl: string) {
try {
const parsed = decodeText(lnurl);
return parsed.prefix.toLowerCase() === "lnurl";
return !!decodeLNURL(lnurl);
} catch (e) {
return false;
}
}
export function parseLub16Address(address: string) {
let [name, domain] = address.split("@");
if (!name || !domain) return;
return `https://${domain}/.well-known/lnurlp/${name}`;
}
export function parseLNURL(lnurl: string) {
const { text, prefix } = decodeText(lnurl);
return prefix === "lnurl" ? text : undefined;
}
export function getLudEndpoint(addressOrLNURL: string) {
if (addressOrLNURL.includes("@")) {
return parseLub16Address(addressOrLNURL);
}
try {
return parseLNURL(addressOrLNURL);
} catch (e) {}
}
export async function getInvoiceFromCallbackUrl(callback: URL) {
const amount = callback.searchParams.get("amount");
if (!amount) throw new Error("Missing amount");
const { pr: payRequest } = await fetch(callback).then((res) => res.json());
if (payRequest as string) {
const parsed = parsePaymentRequest(payRequest);
if (parsed.amount !== parseInt(amount)) throw new Error("Incorrect amount");
return payRequest as string;
} else throw new Error("Failed to get invoice");
}

View File

@ -1,5 +1,5 @@
import { parseLNURLOrAddress } from "applesauce-core/helpers/lnurl";
import { fetchWithProxy } from "../helpers/request";
import { getLudEndpoint } from "../helpers/lnurl";
type LNURLPMetadata = {
callback: string;
@ -21,7 +21,7 @@ class LNURLMetadataService {
private pending = new Map<string, Promise<LNURLPMetadata | undefined>>();
private async fetchMetadata(addressOrLNURL: string) {
const url = getLudEndpoint(addressOrLNURL);
const url = parseLNURLOrAddress(addressOrLNURL);
if (!url) return;
try {
const metadata = await fetchWithProxy(url).then((res) => res.json() as Promise<LNURLError | LNURLPMetadata>);

View File

@ -20,7 +20,7 @@ import { getCommunityImage, getCommunityName } from "../../../helpers/nostr/comm
import UserAvatarLink from "../../../components/user/user-avatar-link";
import UserLink from "../../../components/user/user-link";
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import User01 from "../../../components/icons/user-01";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import { AddressPointer } from "nostr-tools/nip19";
@ -67,7 +67,7 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
{countMembers !== undefined && countMembers > 0 && (
<Tag variant="solid" ml="auto" alignSelf="flex-end" textShadow="none">
<TagLeftIcon as={User01} boxSize={4} />
<TagLabel>{readablizeSats(countMembers)}</TagLabel>
<TagLabel>{humanReadableSats(countMembers)}</TagLabel>
</Tag>
)}

View File

@ -28,7 +28,7 @@ import { NostrEvent } from "../../../types/nostr-event";
import CommunityJoinButton from "../../communities/components/community-join-button";
import CommunityMenu from "./community-menu";
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import CommunityMembersModal from "./community-members-modal";
import { RelayFavicon } from "../../../components/relay-favicon";
@ -83,7 +83,7 @@ export default function HorizontalCommunityDetails({
<Heading size="sm" mb="1">
Members
</Heading>
<Text>{countMembers ? readablizeSats(countMembers) : "unknown"}</Text>
<Text>{countMembers ? humanReadableSats(countMembers) : "unknown"}</Text>
</Box>
{rules && (
<Box>

View File

@ -16,7 +16,7 @@ import CommunityJoinButton from "../../communities/components/community-join-but
import CommunityMenu from "./community-menu";
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
import CommunityMembersModal from "./community-members-modal";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import { RelayFavicon } from "../../../components/relay-favicon";
export default function VerticalCommunityDetails({
@ -65,7 +65,7 @@ export default function VerticalCommunityDetails({
<Heading size="sm" mb="1">
Members
</Heading>
<Text>{countMembers ? readablizeSats(countMembers) : "unknown"}</Text>
<Text>{countMembers ? humanReadableSats(countMembers) : "unknown"}</Text>
</Box>
{rules && (
<Box>

View File

@ -6,7 +6,7 @@ import { LightningIcon } from "../../../components/icons";
import useEventZaps from "../../../hooks/use-event-zaps";
import { getEventUID } from "../../../helpers/nostr/event";
import { totalZaps } from "../../../helpers/nostr/zaps";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
export default function GoalProgress({ goal }: { goal: NostrEvent }) {
const amount = getGoalAmount(goal);
@ -18,7 +18,8 @@ export default function GoalProgress({ goal }: { goal: NostrEvent }) {
<LightningIcon />
<Progress value={(raised / amount) * 100} colorScheme="yellow" flex={1} />
<Text>
{readablizeSats(raised / 1000)} / {readablizeSats(amount / 1000)} ({Math.round((raised / amount) * 1000) / 10}%)
{humanReadableSats(raised / 1000)} / {humanReadableSats(amount / 1000)} (
{Math.round((raised / amount) * 1000) / 10}%)
</Text>
</Flex>
);

View File

@ -6,7 +6,7 @@ import useEventZaps from "../../../hooks/use-event-zaps";
import { NostrEvent } from "../../../types/nostr-event";
import UserAvatarLink from "../../../components/user/user-avatar-link";
import UserLink from "../../../components/user/user-link";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import { LightningIcon } from "../../../components/icons";
export default function GoalTopZappers({
@ -35,7 +35,7 @@ export default function GoalTopZappers({
<Box whiteSpace="pre" isTruncated>
<UserLink fontSize="lg" fontWeight="bold" pubkey={pubkey} mr="2" />
<br />
<LightningIcon /> {readablizeSats(amount / 1000)}
<LightningIcon /> {humanReadableSats(amount / 1000)}
</Box>
</Flex>
))}

View File

@ -6,7 +6,7 @@ import { getGoalRelays } from "../../../helpers/nostr/goal";
import useEventZaps from "../../../hooks/use-event-zaps";
import UserAvatarLink from "../../../components/user/user-avatar-link";
import UserLink from "../../../components/user/user-link";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import { LightningIcon } from "../../../components/icons";
import Timestamp from "../../../components/timestamp";
import TextNoteContents from "../../../components/note/timeline-note/text-note-contents";
@ -29,7 +29,7 @@ function GoalZap({ zap }: { zap: NostrEvent }) {
</Box>
<Spacer />
<Text>
<LightningIcon /> {readablizeSats(payment.amount / 1000)}
<LightningIcon /> {humanReadableSats(payment.amount / 1000)}
</Text>
</Flex>
);

View File

@ -10,7 +10,7 @@ import UserAvatar from "../../components/user/user-avatar";
import UserLink from "../../components/user/user-link";
import GoalContents from "./components/goal-contents";
import GoalZapList from "./components/goal-zap-list";
import { readablizeSats } from "../../helpers/bolt11";
import { humanReadableSats } from "../../helpers/lightning";
import GoalZapButton from "./components/goal-zap-button";
import VerticalPageLayout from "../../components/vertical-page-layout";
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
@ -30,7 +30,7 @@ export default function GoalDetailsView() {
</Button>
<Heading size="md" isTruncated>
{getGoalName(goal)} ({readablizeSats(getGoalAmount(goal) / 1000)})
{getGoalName(goal)} ({humanReadableSats(getGoalAmount(goal) / 1000)})
</Heading>
<Spacer />

View File

@ -9,7 +9,7 @@ import {
} from "applesauce-core/helpers";
import { NostrEvent } from "nostr-tools";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import { EmbedEventPointer } from "../../../components/embed-event";
import UserAvatarLink from "../../../components/user/user-avatar-link";
import { LightningIcon } from "../../../components/icons";
@ -50,7 +50,7 @@ const ZapNotification = forwardRef<HTMLDivElement, { zap: NostrEvent; onClick?:
timestamp={request.created_at}
summary={
<>
{readablizeSats(payment.amount / 1000)} {request.content}
{humanReadableSats(payment.amount / 1000)} {request.content}
</>
}
onClick={onClick}
@ -59,7 +59,7 @@ const ZapNotification = forwardRef<HTMLDivElement, { zap: NostrEvent; onClick?:
<AvatarGroup size="sm">
<UserAvatarLink pubkey={sender} />
</AvatarGroup>
<Text>zapped {readablizeSats(payment.amount / 1000)} sats</Text>
<Text>zapped {humanReadableSats(payment.amount / 1000)} sats</Text>
<ButtonGroup size="sm" variant="ghost" ml="auto">
<ZapReceiptMenu zap={zap} aria-label="More Options" />
</ButtonGroup>

View File

@ -7,7 +7,7 @@ import { Link as RouterLink } from "react-router-dom";
import UserAvatar from "../../../components/user/user-avatar";
import UserDnsIdentity from "../../../components/user/user-dns-identity";
import trustedUserStatsService from "../../../services/trusted-user-stats";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import replaceableEventsService from "../../../services/replaceable-events";
import UserAboutContent from "../../../components/user/user-about";
import UserName from "../../../components/user/user-name";
@ -42,7 +42,7 @@ function ProfileResult({ profile }: { profile: NostrEvent }) {
</Flex>
<UserAboutContent pubkey={profile.pubkey} noOfLines={3} isTruncated />
{stats && (
<>{stats.followers_pubkey_count && <Text>Followers: {readablizeSats(stats.followers_pubkey_count)}</Text>}</>
<>{stats.followers_pubkey_count && <Text>Followers: {humanReadableSats(stats.followers_pubkey_count)}</Text>}</>
)}
</Flex>
);

View File

@ -19,9 +19,9 @@ import {
Text,
} from "@chakra-ui/react";
import { useInterval } from "react-use";
import { parseBolt11 } from "applesauce-core/helpers";
import useUserLNURLMetadata from "../../../hooks/use-user-lnurl-metadata";
import { parsePaymentRequest } from "../../../helpers/bolt11";
import { V4VStreamIcon, V4VStopIcon } from "../../../components/icons";
export default function StreamSatsPerMinute({ pubkey, ...props }: { pubkey: string } & FlexProps) {
@ -49,7 +49,7 @@ export default function StreamSatsPerMinute({ pubkey, ...props }: { pubkey: stri
const { pr: payRequest } = await fetch(callbackUrl).then((res) => res.json());
if (payRequest as string) {
const parsed = parsePaymentRequest(payRequest);
const parsed = parseBolt11(payRequest);
if (parsed.amount !== amountMsats) throw new Error("incorrect amount");
} else throw new Error("Failed to get invoice");

View File

@ -4,7 +4,7 @@ import { getZapPayment, getZapSender } from "applesauce-core/helpers";
import UserLink from "../../../components/user/user-link";
import { LightningIcon } from "../../../components/icons";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import useStreamChatTimeline from "../stream/stream-chat/use-stream-chat-timeline";
import { ParsedStream } from "../../../helpers/nostr/stream";
import UserAvatarLink from "../../../components/user/user-avatar-link";
@ -30,7 +30,7 @@ export default function TopZappers({ stream, ...props }: FlexProps & { stream: P
<UserLink pubkey={pubkey} fontWeight="bold" />
<br />
<LightningIcon />
{readablizeSats(total / 1000)}
{humanReadableSats(total / 1000)}
</Text>
</Flex>
))}

View File

@ -6,7 +6,7 @@ import UserAvatar from "../../../../components/user/user-avatar";
import UserLink from "../../../../components/user/user-link";
import { NostrEvent } from "../../../../types/nostr-event";
import { LightningIcon } from "../../../../components/icons";
import { readablizeSats } from "../../../../helpers/bolt11";
import { humanReadableSats } from "../../../../helpers/lightning";
import { TrustProvider } from "../../../../providers/local/trust-provider";
import ChatMessageContent from "./chat-message-content";
import useClientSideMuteFilter from "../../../../hooks/use-client-side-mute-filter";
@ -31,7 +31,7 @@ function ZapMessage({ zap, stream }: { zap: NostrEvent; stream: ParsedStream })
<LightningIcon color="yellow.400" />
<UserAvatar pubkey={sender} size="xs" />
<UserLink pubkey={sender} fontWeight="bold" color="yellow.400" />
<Text>zapped {readablizeSats(payment.amount / 1000)} sats</Text>
<Text>zapped {humanReadableSats(payment.amount / 1000)} sats</Text>
</Flex>
<Box>
<ChatMessageContent event={request} />

View File

@ -8,7 +8,7 @@ import UserAvatarLink from "../../../../components/user/user-avatar-link";
import UserLink from "../../../../components/user/user-link";
import Timestamp from "../../../../components/timestamp";
import { LightningIcon } from "../../../../components/icons";
import { readablizeSats } from "../../../../helpers/bolt11";
import { humanReadableSats } from "../../../../helpers/lightning";
import TextNoteContents from "../../../../components/note/timeline-note/text-note-contents";
import { TrustProvider } from "../../../../providers/local/trust-provider";
import ZapReceiptMenu from "../../../../components/zap/zap-receipt-menu";
@ -24,7 +24,7 @@ const ZapEvent = memo(({ zap }: { zap: NostrEvent }) => {
<Flex gap="2">
<Flex direction="column" alignItems="center" minW="10">
<LightningIcon color="yellow.500" boxSize={5} />
<Text>{readablizeSats(payment.amount / 1000)}</Text>
<Text>{humanReadableSats(payment.amount / 1000)}</Text>
</Flex>
<UserAvatarLink pubkey={sender} size="sm" ml="2" />

View File

@ -5,7 +5,7 @@ import { NostrEvent } from "../../../../types/nostr-event";
import UserAvatarLink from "../../../../components/user/user-avatar-link";
import UserLink from "../../../../components/user/user-link";
import { LightningIcon } from "../../../../components/icons";
import { readablizeSats } from "../../../../helpers/bolt11";
import { humanReadableSats } from "../../../../helpers/lightning";
export default function TextToSpeechStatus({ status }: { status: NostrEvent }) {
const toast = useToast();
@ -48,7 +48,7 @@ export default function TextToSpeechStatus({ status }: { status: NostrEvent }) {
isLoading={paying || paid}
isDisabled={!window.webln}
>
Pay {readablizeSats(amountMsat / 1000)} sats
Pay {humanReadableSats(amountMsat / 1000)} sats
</Button>
)}
</Flex>

View File

@ -5,7 +5,7 @@ import { NostrEvent } from "../../../../types/nostr-event";
import UserAvatarLink from "../../../../components/user/user-avatar-link";
import UserLink from "../../../../components/user/user-link";
import { LightningIcon } from "../../../../components/icons";
import { readablizeSats } from "../../../../helpers/bolt11";
import { humanReadableSats } from "../../../../helpers/lightning";
export default function TranslationStatus({ status }: { status: NostrEvent }) {
const toast = useToast();
@ -48,7 +48,7 @@ export default function TranslationStatus({ status }: { status: NostrEvent }) {
isLoading={paying || paid}
isDisabled={!window.webln}
>
Pay {readablizeSats(amountMsat / 1000)} sats
Pay {humanReadableSats(amountMsat / 1000)} sats
</Button>
)}
</Flex>

View File

@ -17,10 +17,10 @@ import {
Text,
useDisclosure,
} from "@chakra-ui/react";
import { nip19, NostrEvent } from "nostr-tools";
import { nip19 } from "nostr-tools";
import { ChatIcon } from "@chakra-ui/icons";
import { getLudEndpoint } from "../../../helpers/lnurl";
import { parseLNURLOrAddress } from "../../../../../applesauce/packages/core/src/helpers/lnurl";
import { truncatedId } from "../../../helpers/nostr/event";
import { parseAddress } from "../../../services/dns-identity";
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
@ -49,10 +49,7 @@ import UserJoinedChanneled from "./user-joined-channels";
import { getTextColor } from "../../../helpers/color";
import UserName from "../../../components/user/user-name";
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
import { renderGenericUrl } from "../../../components/content/links/common";
import UserAboutContent from "../../../components/user/user-about";
import { useStoreQuery } from "applesauce-react/hooks";
import { TimelineQuery } from "applesauce-core/queries";
import UserRecentEvents from "./user-recent-events";
function DNSIdentityWarning({ pubkey }: { pubkey: string }) {
@ -186,7 +183,7 @@ export default function UserAboutTab() {
{metadata?.lud16 && (
<Flex gap="2">
<LightningIcon boxSize="1.2em" />
<Link href={getLudEndpoint(metadata.lud16)} isExternal>
<Link href={parseLNURLOrAddress(metadata.lud16)?.toString()} isExternal>
{metadata.lud16}
</Link>
</Flex>

View File

@ -16,7 +16,7 @@ import {
import { useAsync } from "react-use";
import { kinds } from "nostr-tools";
import { readablizeSats } from "../../../helpers/bolt11";
import { humanReadableSats } from "../../../helpers/lightning";
import trustedUserStatsService from "../../../services/trusted-user-stats";
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
import useUserContactList from "../../../hooks/use-user-contact-list";
@ -46,7 +46,7 @@ export default function UserStatsAccordion({ pubkey }: { pubkey: string }) {
<StatGroup gap="4" whiteSpace="pre">
<Stat>
<StatLabel>Following</StatLabel>
<StatNumber>{contacts ? readablizeSats(getPubkeysFromList(contacts).length) : "Unknown"}</StatNumber>
<StatNumber>{contacts ? humanReadableSats(getPubkeysFromList(contacts).length) : "Unknown"}</StatNumber>
{contacts && (
<StatHelpText>
Updated <Timestamp timestamp={contacts.created_at} />
@ -58,17 +58,17 @@ export default function UserStatsAccordion({ pubkey }: { pubkey: string }) {
<>
<Stat>
<StatLabel>Followers</StatLabel>
<StatNumber>{readablizeSats(followerCount ?? 0) || 0}</StatNumber>
<StatNumber>{humanReadableSats(followerCount ?? 0) || 0}</StatNumber>
</Stat>
<Stat>
<StatLabel>Notes & replies</StatLabel>
<StatNumber>{readablizeSats(stats.pub_note_count) || 0}</StatNumber>
<StatNumber>{humanReadableSats(stats.pub_note_count) || 0}</StatNumber>
</Stat>
<Stat>
<StatLabel>Reactions</StatLabel>
<StatNumber>{readablizeSats(stats.pub_reaction_count) || 0}</StatNumber>
<StatNumber>{humanReadableSats(stats.pub_reaction_count) || 0}</StatNumber>
</Stat>
</>
)}
@ -96,15 +96,15 @@ export default function UserStatsAccordion({ pubkey }: { pubkey: string }) {
</Stat>
<Stat>
<StatLabel>Total Sats Sent</StatLabel>
<StatNumber>{readablizeSats(stats.zaps_sent.msats / 1000)}</StatNumber>
<StatNumber>{humanReadableSats(stats.zaps_sent.msats / 1000)}</StatNumber>
</Stat>
<Stat>
<StatLabel>Avg Zap Sent</StatLabel>
<StatNumber>{readablizeSats(stats.zaps_sent.avg_msats / 1000)}</StatNumber>
<StatNumber>{humanReadableSats(stats.zaps_sent.avg_msats / 1000)}</StatNumber>
</Stat>
<Stat>
<StatLabel>Biggest Zap Sent</StatLabel>
<StatNumber>{readablizeSats(stats.zaps_sent.max_msats / 1000)}</StatNumber>
<StatNumber>{humanReadableSats(stats.zaps_sent.max_msats / 1000)}</StatNumber>
</Stat>
</>
)}
@ -117,15 +117,15 @@ export default function UserStatsAccordion({ pubkey }: { pubkey: string }) {
</Stat>
<Stat>
<StatLabel>Total Sats Received</StatLabel>
<StatNumber>{readablizeSats(stats.zaps_received.msats / 1000)}</StatNumber>
<StatNumber>{humanReadableSats(stats.zaps_received.msats / 1000)}</StatNumber>
</Stat>
<Stat>
<StatLabel>Avg Zap Received</StatLabel>
<StatNumber>{readablizeSats(stats.zaps_received.avg_msats / 1000)}</StatNumber>
<StatNumber>{humanReadableSats(stats.zaps_received.avg_msats / 1000)}</StatNumber>
</Stat>
<Stat>
<StatLabel>Biggest Zap Received</StatLabel>
<StatNumber>{readablizeSats(stats.zaps_received.max_msats / 1000)}</StatNumber>
<StatNumber>{humanReadableSats(stats.zaps_received.max_msats / 1000)}</StatNumber>
</Stat>
</>
)}

View File

@ -9,7 +9,7 @@ import { ErrorBoundary } from "../../components/error-boundary";
import { LightningIcon } from "../../components/icons";
import UserAvatarLink from "../../components/user/user-avatar-link";
import UserLink from "../../components/user/user-link";
import { readablizeSats } from "../../helpers/bolt11";
import { humanReadableSats } from "../../helpers/lightning";
import { isProfileZap, isNoteZap, totalZaps } from "../../helpers/nostr/zaps";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { NostrEvent, isATag, isETag } from "../../types/nostr-event";
@ -68,7 +68,7 @@ const Zap = ({ zap }: { zap: NostrEvent }) => {
{payment?.amount && (
<Flex gap="2">
<LightningIcon color="yellow.400" />
<Text>{readablizeSats(payment.amount / 1000)} sats</Text>
<Text>{humanReadableSats(payment.amount / 1000)} sats</Text>
</Flex>
)}
<Timestamp ml="auto" timestamp={request.created_at} />
@ -120,7 +120,7 @@ const UserZapsTab = () => {
<Flex gap="2">
<LightningIcon color="yellow.400" />
<Text>
{readablizeSats(totalZaps(zaps) / 1000)} sats in the last{" "}
{humanReadableSats(totalZaps(zaps) / 1000)} sats in the last{" "}
{dayjs.unix(zaps[zaps.length - 1].created_at).fromNow(true)}
</Text>
</Flex>