mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
Add support for cashu v4 tokens
This commit is contained in:
parent
1282aee6f4
commit
f2f8186101
5
.changeset/tough-eyes-shake.md
Normal file
5
.changeset/tough-eyes-shake.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add support for cashu v4 tokens
|
@ -16,7 +16,7 @@
|
||||
"build-icons": "node ./scripts/build-icons.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^0.9.0",
|
||||
"@cashu/cashu-ts": "^1.1.0",
|
||||
"@chakra-ui/anatomy": "^2.2.2",
|
||||
"@chakra-ui/breakpoint-utils": "^2.0.8",
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
@ -31,6 +31,7 @@
|
||||
"@codemirror/view": "^6.28.6",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fedimint/core-web": "^0.0.7",
|
||||
"@getalby/bitcoin-connect": "^3.6.2",
|
||||
"@getalby/bitcoin-connect-react": "^3.6.2",
|
||||
"@noble/ciphers": "^1.0.0",
|
||||
|
8662
pnpm-lock.yaml
generated
8662
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,19 @@
|
||||
import { useAsync } from "react-use";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Box, Button, ButtonGroup, Card, CardProps, Heading, IconButton, Link } from "@chakra-ui/react";
|
||||
import { getDecodedToken, Token, CashuMint } from "@cashu/cashu-ts";
|
||||
import { Box, Button, ButtonGroup, Card, CardProps, Heading, IconButton, Link, Text } from "@chakra-ui/react";
|
||||
import { Token, getEncodedToken } from "@cashu/cashu-ts";
|
||||
|
||||
import { CopyIconButton } from "../copy-icon-button";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { ECashIcon, WalletIcon } from "../icons";
|
||||
import { getMint } from "../../services/cashu-mints";
|
||||
import CurrencyDollar from "../icons/currency-dollar";
|
||||
import CurrencyEthereum from "../icons/currency-ethereum";
|
||||
import CurrencyRupeeCircle from "../icons/currency-rupee-circle";
|
||||
import CurrencyEuro from "../icons/currency-euro";
|
||||
import CurrencyYen from "../icons/currency-yen";
|
||||
import CurrencyPound from "../icons/currency-pound";
|
||||
import CurrencyBitcoin from "../icons/currency-bitcoin";
|
||||
|
||||
function RedeemButton({ token }: { token: string }) {
|
||||
const account = useCurrentAccount()!;
|
||||
@ -24,52 +30,85 @@ function RedeemButton({ token }: { token: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function InlineCachuCard({ token, ...props }: Omit<CardProps, "children"> & { token: string }) {
|
||||
export default function InlineCachuCard({
|
||||
token,
|
||||
encoded,
|
||||
...props
|
||||
}: Omit<CardProps, "children"> & { token: Token; encoded?: string }) {
|
||||
const account = useCurrentAccount();
|
||||
|
||||
const [cashu, setCashu] = useState<Token>();
|
||||
encoded = encoded || getEncodedToken(token);
|
||||
const { value: spendable } = useAsync(async () => {
|
||||
if (!cashu) return;
|
||||
for (const token of cashu.token) {
|
||||
const mint = await getMint(token.mint);
|
||||
const spent = await mint.check({ proofs: token.proofs.map((p) => ({ secret: p.secret })) });
|
||||
if (spent.spendable.some((v) => v === false)) return false;
|
||||
if (!token) return;
|
||||
for (const entry of token.token) {
|
||||
const mint = await getMint(entry.mint);
|
||||
const spent = await mint.check({ Ys: entry.proofs.map((p) => p.secret) });
|
||||
if (spent.states.some((v) => v.state === "SPENT")) return false;
|
||||
}
|
||||
return true;
|
||||
}, [cashu]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token.startsWith("cashuA") || token.length < 10) return;
|
||||
try {
|
||||
const cashu = getDecodedToken(token);
|
||||
setCashu(cashu);
|
||||
} catch (e) {}
|
||||
}, [token]);
|
||||
|
||||
if (!cashu) return null;
|
||||
const amount = token?.token[0].proofs.reduce((acc, v) => acc + v.amount, 0);
|
||||
|
||||
let UnitIcon = ECashIcon;
|
||||
let unitColor = "green.500";
|
||||
let denomination = `${amount} tokens`;
|
||||
|
||||
switch (token.unit) {
|
||||
case "usd":
|
||||
UnitIcon = CurrencyDollar;
|
||||
denomination = `$${(amount / 100).toFixed(2)}`;
|
||||
break;
|
||||
|
||||
case "eur":
|
||||
UnitIcon = CurrencyEuro;
|
||||
unitColor = "blue.500";
|
||||
denomination = `€${(amount / 100).toFixed(2)}`;
|
||||
break;
|
||||
|
||||
case "gpb":
|
||||
UnitIcon = CurrencyPound;
|
||||
denomination = `£${(amount / 100).toFixed(2)}`;
|
||||
break;
|
||||
|
||||
case "yen":
|
||||
UnitIcon = CurrencyYen;
|
||||
denomination = `¥${(amount / 100).toFixed(2)}`;
|
||||
break;
|
||||
|
||||
case "eth":
|
||||
UnitIcon = CurrencyEthereum;
|
||||
break;
|
||||
|
||||
case "sat":
|
||||
unitColor = "orange.300";
|
||||
UnitIcon = CurrencyBitcoin;
|
||||
denomination = `${amount} sats`;
|
||||
break;
|
||||
}
|
||||
|
||||
const amount = cashu?.token[0].proofs.reduce((acc, v) => acc + v.amount, 0);
|
||||
return (
|
||||
<Card p="4" flexDirection="row" borderColor="green.500" alignItems="center" gap="4" flexWrap="wrap" {...props}>
|
||||
<ECashIcon boxSize={10} color="green.500" />
|
||||
<Card p="2" flexDirection="column" borderColor="green.500" gap="2" {...props}>
|
||||
<Box>
|
||||
<UnitIcon boxSize={10} color={unitColor} float="left" mr="2" mb="1" />
|
||||
<ButtonGroup float="right">
|
||||
<CopyIconButton value={encoded} title="Copy Token" aria-label="Copy Token" variant="ghost" />
|
||||
<IconButton
|
||||
as={Link}
|
||||
icon={<WalletIcon boxSize={5} />}
|
||||
title="Open Wallet"
|
||||
aria-label="Open Wallet"
|
||||
href={`cashu://` + token}
|
||||
/>
|
||||
{account && <RedeemButton token={encoded} />}
|
||||
</ButtonGroup>
|
||||
<Heading size="md" textDecoration={spendable === false ? "line-through" : undefined}>
|
||||
{amount} Cashu sats{spendable === false ? " (Spent)" : ""}
|
||||
{denomination} {spendable === false ? " (Spent)" : ""}
|
||||
</Heading>
|
||||
{cashu && <small>Mint: {new URL(cashu.token[0].mint).hostname}</small>}
|
||||
{token && <Text fontSize="xs">Mint: {new URL(token.token[0].mint).hostname}</Text>}
|
||||
{token.unit && <Text fontSize="xs">Unit: {token.unit}</Text>}
|
||||
</Box>
|
||||
{cashu.memo && <Box>Memo: {cashu.memo}</Box>}
|
||||
<ButtonGroup ml="auto">
|
||||
<CopyIconButton value={token} title="Copy Token" aria-label="Copy Token" />
|
||||
<IconButton
|
||||
as={Link}
|
||||
icon={<WalletIcon />}
|
||||
title="Open Wallet"
|
||||
aria-label="Open Wallet"
|
||||
href={`cashu://` + token}
|
||||
/>
|
||||
{account && <RedeemButton token={token} />}
|
||||
</ButtonGroup>
|
||||
{token.memo && <Box>{token.memo}</Box>}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
7
src/components/content/cashu.tsx
Normal file
7
src/components/content/cashu.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { CashuToken } from "applesauce-content/nast";
|
||||
|
||||
import InlineCachuCard from "../cashu/inline-cashu-card";
|
||||
|
||||
export default function Cashu({ node }: { node: CashuToken }) {
|
||||
return <InlineCachuCard token={node.token} encoded={node.raw} />;
|
||||
}
|
@ -2,8 +2,10 @@ import { Text } from "@chakra-ui/react";
|
||||
import { ComponentMap } from "applesauce-react";
|
||||
|
||||
import Mention from "./mention";
|
||||
import Cashu from "./cashu";
|
||||
|
||||
export const components: ComponentMap = {
|
||||
text: ({ node }) => <Text as="span">{node.value}</Text>,
|
||||
mention: Mention,
|
||||
cashu: Cashu,
|
||||
};
|
||||
|
@ -25,6 +25,7 @@ import { LightboxProvider } from "../../../components/lightbox-provider";
|
||||
import { renderAudioUrl } from "../../../components/external-embeds/types/audio";
|
||||
import buildLinkComponent from "../../../components/content/links";
|
||||
import { components } from "../../../components/content";
|
||||
import { useKind4Decrypt } from "../../../hooks/use-kind4-decryption";
|
||||
|
||||
export default function DirectMessageContent({
|
||||
event,
|
||||
@ -62,7 +63,8 @@ export default function DirectMessageContent({
|
||||
[LinkComponent],
|
||||
);
|
||||
|
||||
const content = useRenderedContent(event, componentsMap);
|
||||
const { plaintext } = useKind4Decrypt(event);
|
||||
const content = useRenderedContent(event, componentsMap, plaintext);
|
||||
|
||||
return (
|
||||
<TrustProvider event={event}>
|
||||
|
Loading…
x
Reference in New Issue
Block a user