mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 13:21:44 +01:00
add more wallet features
This commit is contained in:
parent
dc138f6037
commit
5fb58609e5
@ -19,7 +19,7 @@
|
||||
"cap-sync-version": "pnpm dlx capacitor-set-version . -v $(jq -r .version package.json) -b 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^2.2.1",
|
||||
"@cashu/cashu-ts": "^2.2.2",
|
||||
"@chakra-ui/anatomy": "^2.3.4",
|
||||
"@chakra-ui/breakpoint-utils": "^2.0.8",
|
||||
"@chakra-ui/icons": "^2.2.4",
|
||||
|
108
pnpm-lock.yaml
generated
108
pnpm-lock.yaml
generated
@ -13,8 +13,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@cashu/cashu-ts':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@chakra-ui/anatomy':
|
||||
specifier: ^2.3.4
|
||||
version: 2.3.4
|
||||
@ -104,31 +104,31 @@ importers:
|
||||
version: 0.7.2
|
||||
applesauce-accounts:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-content:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-core:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-factory:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-loaders:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-react:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2)
|
||||
applesauce-relay:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-signers:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-wallet:
|
||||
specifier: next
|
||||
version: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
version: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
bech32:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
@ -1038,8 +1038,8 @@ packages:
|
||||
'@cashu/cashu-ts@2.0.0-rc1':
|
||||
resolution: {integrity: sha512-39459l7x/fUMEgOsCdGLLl6rMekO4nbv+wEuavmyElh8hgN8t66wcb29AJvdFTb6K3lPACKF2rs/jAlPYrN7Ng==}
|
||||
|
||||
'@cashu/cashu-ts@2.2.1':
|
||||
resolution: {integrity: sha512-/A8Lfkf7nexldcAcTbqrITXxwgiCYTTnrthB8DoipLVeDfyUXer48FJdUmXpRp87Aijn2BNklo8qA0yO0kHXaA==}
|
||||
'@cashu/cashu-ts@2.2.2':
|
||||
resolution: {integrity: sha512-s4DRaIZOh2MC0qi0G1Te4KfOzzw91EZiIVupssKDmaPSUbT1ggLonj5qyzB4OCpI7uZ4lDpnxh43xkRZyAqotw==}
|
||||
|
||||
'@cashu/crypto@0.2.7':
|
||||
resolution: {integrity: sha512-1aaDfUjiHNXoJqg8nW+341TLWV9W28DsVNXJUKcHL0yAmwLs5+56SSnb8LLDJzPamLVoYL0U0bda91klAzptig==}
|
||||
@ -2195,32 +2195,32 @@ packages:
|
||||
engines: {node: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
applesauce-accounts@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-Q1vEjYznvkxRDM2C/gN+UJpjva2QvFLrIlfMTyrfm7s1nDEIKP6wJ4Oj72PsnyZREP0/UnH7fP5olvJMakBOBg==}
|
||||
applesauce-accounts@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-Gn+PJ3RgSOJGqKLcC9LRm7KgrwL64D0ZQBIZ7XORk/dF61kaF88gW3TnL72aUByjp8jJHxOTFxaR+HTR22aHZA==}
|
||||
|
||||
applesauce-content@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-oxMgVy1hS0ZWGL+0Skrt25vcMUwY8T3A1p45vc1q17oXWbIKBqZPtenoHarluEBdY9iUt0QBVVIFWSQtphudxw==}
|
||||
applesauce-content@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-Bc5GS+taB29zewyH/t8Cfq3CdCF47xE20sVurNplrUyJ6jS99im+BFsgKQY1OX41xSsK6h3TDVjBYRUSvSwRtA==}
|
||||
|
||||
applesauce-core@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-u6YNDyPy2v+B8Iv6WFkTTnilLJfNgFVeDs+aGy4KpItq0tdEQcnwTIFRcTovSaCNn2YYNJ1WHw30/a622SmshA==}
|
||||
applesauce-core@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-8E1PAfH4UPbS/GMNrKyrWO2DDtx+PlzMVnL5UskUX5YcEy3ziPCjXY10U4V32p+DKH+aGzFsWkaSVN4OR/TXaw==}
|
||||
|
||||
applesauce-factory@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-rJNnwwxIdX1s4vdmAmIDvappQT/0J0XH+xIpMDnuf1yvMmwK/lH+DvGWPdC2h/TNI4Lbd3c3774jUkgBZoKMrQ==}
|
||||
applesauce-factory@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-avUTztNHvuZJe44OCoeI5aA8vcrokuqXPjsux+8xkrFOpLJ3LWcXOWQEhQJJ0sj9idiWoHa7P6kZVk7mQ+lhTw==}
|
||||
|
||||
applesauce-loaders@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-KiqVGqzIEOuGfLU6TGGjRFru7/0e1HATSyWsXyvyPJd0Pt5Ud9tkJidI+bAY+jZthDcBlFdEPT4HTPjKJ63AOg==}
|
||||
applesauce-loaders@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-lwJvrruMeeNo25WK87GSDHb3QkxEmgM90rLrLSAdNVxhNW99ZhnHWmJr3bwMnRgRQoOTqNHAHhBV8Uk2TGsWCA==}
|
||||
|
||||
applesauce-react@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-MrWVYJUuKGAuW0ZjUTBgNP/o4AVth1vPlmAra0xYsH5y8puQvU0APqXpH60yrOGqMvtWiZbpMpnhPKjmjytxfw==}
|
||||
applesauce-react@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-ZOewc/bvC2EiO5lOhNyiOBPzh1NsNA1fEDdHLI5oyUs76n4Pl/2sIz2Rhm9i+wMyxvrpqd26itglILibnMh+tw==}
|
||||
|
||||
applesauce-relay@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-5QyyRQc5vEHGUWJgHRw6hM5zSkvkqt+UYrwkL9yGcP1rQ/IyM0kcoog1VobKrSGjlQOyXxhw0cShSKoTzGWreA==}
|
||||
applesauce-relay@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-JOq2SktlRGr6EIBJS3aZXxo+ySDcdySFdjlrNtSAMRfH6jGKHEkjbqgaMPdNQ0QGAkIYnnLTWwFSQJEYpH4H0w==}
|
||||
|
||||
applesauce-signers@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-dfppzzFIT3FtLF9JmBNAQToD8euOsWqt940itYMvesP5gV7BEHCSPiBzHdaXPNfK5/ay/kN9WHleBdwVcKTW5A==}
|
||||
applesauce-signers@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-phmk143NUPEwu7h4bEL9Fpofx12C9lawhoC7+47CGYjgOdfAT2xjH+Xicot7qqUYjlE0QzYikS5rLzUXA/m93w==}
|
||||
|
||||
applesauce-wallet@0.0.0-next-20250310121307:
|
||||
resolution: {integrity: sha512-9rwmR4RsJ1hv5nM7uOo/HWKHzhkhuueEtL99ZJleOGnQWm6fTz0LlMRDCT8OLlX7ZCC9Oho7p38YBqISQzJHew==}
|
||||
applesauce-wallet@0.0.0-next-20250310162525:
|
||||
resolution: {integrity: sha512-Bcp/HUyGyW3057wtK0y/eWLeEmV2WfuwpMNFWAHjs/Ro50FFNLt9YA7tEjiMQe42KQTwn+YjBXOpm2FFxDxAfg==}
|
||||
|
||||
arg@4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
@ -6961,7 +6961,7 @@ snapshots:
|
||||
'@scure/bip32': 1.6.2
|
||||
buffer: 6.0.3
|
||||
|
||||
'@cashu/cashu-ts@2.2.1':
|
||||
'@cashu/cashu-ts@2.2.2':
|
||||
dependencies:
|
||||
'@cashu/crypto': 0.3.4
|
||||
'@noble/curves': 1.8.1
|
||||
@ -8435,10 +8435,10 @@ snapshots:
|
||||
dependencies:
|
||||
entities: 2.2.0
|
||||
|
||||
applesauce-accounts@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-accounts@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
'@noble/hashes': 1.7.1
|
||||
applesauce-signers: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-signers: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
nanoid: 5.1.3
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
rxjs: 7.8.2
|
||||
@ -8446,13 +8446,13 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-content@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-content@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
'@cashu/cashu-ts': 2.0.0-rc1
|
||||
'@types/hast': 3.0.4
|
||||
'@types/mdast': 4.0.4
|
||||
'@types/unist': 3.0.3
|
||||
applesauce-core: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
mdast-util-find-and-replace: 3.0.2
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
remark: 15.0.1
|
||||
@ -8463,7 +8463,7 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-core@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-core@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
'@noble/hashes': 1.7.1
|
||||
'@scure/base': 1.2.4
|
||||
@ -8478,19 +8478,19 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-factory@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-factory@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
applesauce-content: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-content: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
nanoid: 5.1.3
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-loaders@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-loaders@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
applesauce-core: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
nanoid: 5.1.3
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
rx-nostr: 3.5.0
|
||||
@ -8499,12 +8499,12 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-react@0.0.0-next-20250310121307(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2):
|
||||
applesauce-react@0.0.0-next-20250310162525(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2):
|
||||
dependencies:
|
||||
applesauce-accounts: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-content: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-factory: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-accounts: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-content: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
applesauce-factory: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
observable-hooks: 4.2.4(react-dom@19.0.0(react@19.0.0))(react@18.3.1)(rxjs@7.8.2)
|
||||
react: 18.3.1
|
||||
@ -8514,9 +8514,9 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-relay@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-relay@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
applesauce-core: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
nanoid: 5.1.3
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
rxjs: 7.8.2
|
||||
@ -8524,12 +8524,12 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-signers@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-signers@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
'@noble/hashes': 1.7.1
|
||||
'@noble/secp256k1': 1.7.1
|
||||
'@scure/base': 1.2.4
|
||||
applesauce-core: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
debug: 4.4.0
|
||||
nanoid: 5.1.3
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
@ -8537,9 +8537,9 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
applesauce-wallet@0.0.0-next-20250310121307(typescript@5.8.2):
|
||||
applesauce-wallet@0.0.0-next-20250310162525(typescript@5.8.2):
|
||||
dependencies:
|
||||
applesauce-core: 0.0.0-next-20250310121307(typescript@5.8.2)
|
||||
applesauce-core: 0.0.0-next-20250310162525(typescript@5.8.2)
|
||||
nostr-tools: 2.10.4(typescript@5.8.2)
|
||||
rxjs: 7.8.2
|
||||
transitivePeerDependencies:
|
||||
@ -8700,7 +8700,7 @@ snapshots:
|
||||
|
||||
blossom-client-sdk@3.0.1:
|
||||
dependencies:
|
||||
'@cashu/cashu-ts': 2.2.1
|
||||
'@cashu/cashu-ts': 2.2.2
|
||||
'@noble/hashes': 1.7.1
|
||||
|
||||
blossom-server-sdk@0.4.0:
|
||||
|
20
src/components/cashu/cashu-mint-favicon.tsx
Normal file
20
src/components/cashu/cashu-mint-favicon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useMemo } from "react";
|
||||
import { useAsync } from "react-use";
|
||||
import { Avatar, AvatarProps } from "@chakra-ui/react";
|
||||
|
||||
import { MediaServerIcon } from "../icons";
|
||||
import { getCashuMint } from "../../services/cashu-mints";
|
||||
|
||||
export default function CashuMintFavicon({ mint, ...props }: { mint: string } & Omit<AvatarProps, "src">) {
|
||||
const { value: cashuMint } = useAsync(() => getCashuMint(mint), [mint]);
|
||||
const { value: info } = useAsync(async () => cashuMint?.getInfo(), [cashuMint]);
|
||||
|
||||
const url = useMemo(() => {
|
||||
const url = new URL(mint);
|
||||
url.protocol = "https:";
|
||||
url.pathname = "/favicon.ico";
|
||||
return url.toString();
|
||||
}, [mint]);
|
||||
|
||||
return <Avatar src={url} icon={<MediaServerIcon />} overflow="hidden" {...props} />;
|
||||
}
|
14
src/components/cashu/cashu-mint-name.tsx
Normal file
14
src/components/cashu/cashu-mint-name.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { AvatarProps, Text } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { cashuMintInfo } from "../../services/cashu-mints";
|
||||
|
||||
export default function CashuMintName({ mint, ...props }: { mint: string } & Omit<AvatarProps, "src">) {
|
||||
const info = useObservable(cashuMintInfo(mint));
|
||||
|
||||
return (
|
||||
<Text as="span" {...props}>
|
||||
{info?.name || mint}
|
||||
</Text>
|
||||
);
|
||||
}
|
@ -10,7 +10,7 @@ import CurrencyEuro from "../icons/currency-euro";
|
||||
import CurrencyYen from "../icons/currency-yen";
|
||||
import CurrencyPound from "../icons/currency-pound";
|
||||
import CurrencyBitcoin from "../icons/currency-bitcoin";
|
||||
import { getMintWallet } from "../../services/cashu-mints";
|
||||
import { getCashuWallet } from "../../services/cashu-mints";
|
||||
|
||||
export default function InlineCachuCard({
|
||||
token,
|
||||
@ -20,7 +20,7 @@ export default function InlineCachuCard({
|
||||
encoded = encoded || getEncodedToken(token);
|
||||
const { value: spendable, loading } = useAsync(async () => {
|
||||
if (!token) return;
|
||||
const wallet = await getMintWallet(token.mint);
|
||||
const wallet = await getCashuWallet(token.mint);
|
||||
const status = await wallet.checkProofsStates(token.proofs);
|
||||
return status.some((s) => s.state !== CheckStateEnum.UNSPENT);
|
||||
}, [token]);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Suspense, lazy, useCallback } from "react";
|
||||
import { IconButton, useDisclosure, useToast } from "@chakra-ui/react";
|
||||
import { IconButton, IconButtonProps, useDisclosure, useToast } from "@chakra-ui/react";
|
||||
|
||||
import { type QrScannerModalProps } from "./qr-scanner-modal";
|
||||
import { CAP_IS_NATIVE } from "../../env";
|
||||
@ -71,7 +71,10 @@ async function scanWithNative() {
|
||||
}
|
||||
}
|
||||
|
||||
export default function QRCodeScannerButton({ onData }: { onData: QrScannerModalProps["onData"] }) {
|
||||
export default function QRCodeScannerButton({
|
||||
onData,
|
||||
...props
|
||||
}: { onData: QrScannerModalProps["onData"] } & Omit<IconButtonProps, "icon" | "aria-label">) {
|
||||
const toast = useToast();
|
||||
const modal = useDisclosure();
|
||||
|
||||
@ -91,7 +94,7 @@ export default function QRCodeScannerButton({ onData }: { onData: QrScannerModal
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton onClick={handleClick} icon={<QrCodeIcon boxSize={6} />} aria-label="Qr Scanner" />
|
||||
<IconButton onClick={handleClick} icon={<QrCodeIcon boxSize={6} />} aria-label="Qr Scanner" {...props} />
|
||||
{modal.isOpen && (
|
||||
<Suspense fallback={null}>
|
||||
<QrScannerModal isOpen={modal.isOpen} onClose={modal.onClose} onData={onData} />
|
||||
|
@ -1,13 +1,46 @@
|
||||
import { CashuMint, CashuWallet } from "@cashu/cashu-ts";
|
||||
import { CashuMint, CashuWallet, GetInfoResponse } from "@cashu/cashu-ts";
|
||||
import { normalizeURL } from "applesauce-core/helpers";
|
||||
import { from, Observable, ReplaySubject, share, switchMap } from "rxjs";
|
||||
|
||||
const mints = new Map<string, CashuMint>();
|
||||
const wallets = new Map<string, CashuWallet>();
|
||||
|
||||
export async function getMintWallet(url: string) {
|
||||
export async function getCashuMint(url: string) {
|
||||
const formatted = new URL(url).toString();
|
||||
if (!mints.has(formatted)) {
|
||||
const mint = new CashuMint(formatted);
|
||||
mints.set(formatted, mint);
|
||||
}
|
||||
return mints.get(formatted)!;
|
||||
}
|
||||
|
||||
export async function getCashuWallet(url: string) {
|
||||
const formatted = new URL(url).toString();
|
||||
if (!wallets.has(formatted)) {
|
||||
const mint = new CashuMint(formatted);
|
||||
const mint = await getCashuMint(url);
|
||||
const wallet = new CashuWallet(mint);
|
||||
wallets.set(formatted, wallet);
|
||||
}
|
||||
return wallets.get(formatted)!;
|
||||
}
|
||||
|
||||
const mintInfo = new Map<string, Observable<GetInfoResponse>>();
|
||||
export function cashuMintInfo(mint: string): Observable<GetInfoResponse> {
|
||||
mint = normalizeURL(mint);
|
||||
const existing = mintInfo.get(mint);
|
||||
if (existing) return existing;
|
||||
|
||||
const observable = from(getCashuMint(mint)).pipe(
|
||||
// fetch mint info
|
||||
switchMap((m) => from(m.getInfo())),
|
||||
// share value and keep warm for 2 minutes
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnRefCountZero: false,
|
||||
resetOnComplete: false,
|
||||
resetOnError: false,
|
||||
}),
|
||||
);
|
||||
mintInfo.set(mint, observable);
|
||||
return observable;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import RequireActiveAccount from "../../../components/router/require-active-acco
|
||||
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
|
||||
import { useActiveAccount } from "applesauce-react/hooks";
|
||||
import { InboxIcon, OutboxIcon } from "../../../components/icons";
|
||||
import MediaServerFavicon from "../../../components/media-server/media-server-favicon";
|
||||
import MediaServerFavicon from "../../../components/favicon/media-server-favicon";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
|
@ -19,7 +19,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import RequireActiveAccount from "../../../components/router/require-active-account";
|
||||
import { useActiveAccount } from "applesauce-react/hooks";
|
||||
import MediaServerFavicon from "../../../components/media-server/media-server-favicon";
|
||||
import MediaServerFavicon from "../../../components/favicon/media-server-favicon";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
import useUsersMediaServers from "../../../hooks/use-user-media-servers";
|
||||
import DebugEventButton from "../../../components/debug-modal/debug-event-button";
|
||||
|
@ -1,44 +1,37 @@
|
||||
import { Button, Card, CardBody, CardFooter, CardHeader, CardProps, Text } from "@chakra-ui/react";
|
||||
import { useActiveAccount, useEventStore, useStoreQuery } from "applesauce-react/hooks";
|
||||
import { Button, Card, CardBody, CardHeader, CardProps, Flex, Text } from "@chakra-ui/react";
|
||||
import { useStoreQuery } from "applesauce-react/hooks";
|
||||
import { WalletBalanceQuery } from "applesauce-wallet/queries";
|
||||
import { ECashIcon } from "../../components/icons";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { isWalletLocked, unlockWallet, WALLET_KIND } from "applesauce-wallet/helpers";
|
||||
import useAsyncErrorHandler from "../../hooks/use-async-error-handler";
|
||||
import { WALLET_KIND } from "applesauce-wallet/helpers";
|
||||
import useEventUpdate from "../../hooks/use-event-update";
|
||||
import QRCodeScannerButton from "../../components/qr-code/qr-code-scanner-button";
|
||||
|
||||
export default function WalletBalanceCard({ pubkey, ...props }: { pubkey: string } & Omit<CardProps, "children">) {
|
||||
const account = useActiveAccount();
|
||||
const eventStore = useEventStore();
|
||||
const wallet = useReplaceableEvent({ kind: WALLET_KIND, pubkey });
|
||||
useEventUpdate(wallet?.id);
|
||||
|
||||
const locked = !wallet || isWalletLocked(wallet);
|
||||
const balance = useStoreQuery(WalletBalanceQuery, [pubkey]);
|
||||
|
||||
const unlock = useAsyncErrorHandler(async () => {
|
||||
if (!account) throw new Error("Missing account");
|
||||
if (!wallet) throw new Error("Missing wallet");
|
||||
await unlockWallet(wallet, account);
|
||||
eventStore.update(wallet);
|
||||
}, [wallet, account]);
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader gap="2" display="flex" justifyContent="center" alignItems="center" pt="10">
|
||||
<ECashIcon color="green.400" boxSize={6} />
|
||||
<Text fontWeight="bold" fontSize="lg">
|
||||
<CardHeader gap="4" display="flex" justifyContent="center" alignItems="center" pt="10">
|
||||
<ECashIcon color="green.400" boxSize={12} />
|
||||
<Text fontWeight="bold" fontSize="4xl">
|
||||
{balance ? Object.values(balance).reduce((t, v) => t + v, 0) : "--Locked--"}
|
||||
</Text>
|
||||
</CardHeader>
|
||||
<CardBody></CardBody>
|
||||
{locked && (
|
||||
<CardFooter display="flex">
|
||||
<Button colorScheme="primary" onClick={unlock} mx="auto">
|
||||
Unlock
|
||||
<CardBody>
|
||||
<Flex gap="2" w="full">
|
||||
<Button isDisabled w="full" size="lg">
|
||||
Send
|
||||
</Button>
|
||||
</CardFooter>
|
||||
)}
|
||||
<QRCodeScannerButton onData={() => {}} isDisabled size="lg" />
|
||||
<Button isDisabled w="full" size="lg">
|
||||
Receive
|
||||
</Button>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { Button, ButtonGroup, Card, CardBody, CardFooter, Flex, Text } from "@chakra-ui/react";
|
||||
import { kinds, NostrEvent } from "nostr-tools";
|
||||
import { WalletQuery } from "applesauce-wallet/queries";
|
||||
import { Button, Card, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { WalletBalanceQuery, WalletQuery } from "applesauce-wallet/queries";
|
||||
import {
|
||||
getTokenDetails,
|
||||
isHistoryDetailsLocked,
|
||||
isTokenDetailsLocked,
|
||||
unlockHistoryDetails,
|
||||
unlockTokenDetails,
|
||||
unlockWallet,
|
||||
WALLET_HISTORY_KIND,
|
||||
WALLET_KIND,
|
||||
WALLET_TOKEN_KIND,
|
||||
} from "applesauce-wallet/helpers";
|
||||
|
||||
import { useActiveAccount, useEventStore, useStoreQuery } from "applesauce-react/hooks";
|
||||
import { useActiveAccount, useStoreQuery } from "applesauce-react/hooks";
|
||||
import useAsyncErrorHandler from "../../hooks/use-async-error-handler";
|
||||
import { eventStore } from "../../services/event-store";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
@ -20,53 +22,10 @@ import useUserMailboxes from "../../hooks/use-user-mailboxes";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import useEventIntersectionRef from "../../hooks/use-event-intersection-ref";
|
||||
import useEventUpdate from "../../hooks/use-event-update";
|
||||
import DebugEventButton from "../../components/debug-modal/debug-event-button";
|
||||
import { ECashIcon } from "../../components/icons";
|
||||
import WalletBalanceCard from "./balance-card";
|
||||
import { useMemo } from "react";
|
||||
|
||||
function TokenEvent({ token }: { token: NostrEvent }) {
|
||||
const account = useActiveAccount();
|
||||
const eventStore = useEventStore();
|
||||
useEventUpdate(token.id);
|
||||
const ref = useEventIntersectionRef(token);
|
||||
|
||||
const locked = isTokenDetailsLocked(token);
|
||||
const details = !locked ? getTokenDetails(token) : undefined;
|
||||
const amount = details?.proofs.reduce((t, p) => t + p.amount, 0);
|
||||
|
||||
const unlock = useAsyncErrorHandler(async () => {
|
||||
if (!account) return;
|
||||
await unlockTokenDetails(token, account);
|
||||
eventStore.update(token);
|
||||
}, [token, account, eventStore]);
|
||||
|
||||
return (
|
||||
<Card ref={ref} w="full">
|
||||
<CardBody p="2" alignItems="center" flexDirection="row" display="flex" gap="2">
|
||||
<ECashIcon color="green.400" boxSize={6} />
|
||||
{amount && <Text>{amount}</Text>}
|
||||
<ButtonGroup size="sm" ms="auto">
|
||||
{locked && (
|
||||
<Button onClick={unlock} variant="link" p="2">
|
||||
Unlock
|
||||
</Button>
|
||||
)}
|
||||
<DebugEventButton variant="ghost" event={token} />
|
||||
</ButtonGroup>
|
||||
</CardBody>
|
||||
{details && (
|
||||
<CardFooter px="2" pt="0" pb="0">
|
||||
<Text fontSize="sm" fontStyle="italic">
|
||||
{details.mint}
|
||||
</Text>
|
||||
</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
import WalletTokensTab from "./tabs/tokens";
|
||||
import WalletHistoryTab from "./tabs/history";
|
||||
import WalletMintsTab from "./tabs/mints";
|
||||
|
||||
export default function WalletHomeView() {
|
||||
const account = useActiveAccount()!;
|
||||
@ -76,13 +35,12 @@ export default function WalletHomeView() {
|
||||
const readRelays = useReadRelays(mailboxes?.outboxes);
|
||||
const { timeline: events, loader } = useTimelineLoader(`${account.pubkey}-wallet-tokens`, readRelays, [
|
||||
{
|
||||
kinds: [WALLET_TOKEN_KIND],
|
||||
kinds: [WALLET_TOKEN_KIND, WALLET_HISTORY_KIND],
|
||||
authors: [account.pubkey],
|
||||
},
|
||||
{ kinds: [kinds.EventDeletion], "#k": [String(WALLET_TOKEN_KIND)], authors: [account.pubkey] },
|
||||
]);
|
||||
|
||||
const tokens = useMemo(() => events.filter((e) => e.kind === WALLET_TOKEN_KIND), [events]);
|
||||
const balance = useStoreQuery(WalletBalanceQuery, [account.pubkey]);
|
||||
|
||||
const unlock = useAsyncErrorHandler(async () => {
|
||||
if (!wallet) throw new Error("Missing wallet");
|
||||
@ -90,11 +48,18 @@ export default function WalletHomeView() {
|
||||
eventStore.update(wallet);
|
||||
|
||||
// attempt to unlock all tokens
|
||||
for (const token of tokens) {
|
||||
await unlockTokenDetails(token, account);
|
||||
eventStore.update(token);
|
||||
for (const event of events) {
|
||||
if (event.kind === WALLET_TOKEN_KIND) {
|
||||
if (!isTokenDetailsLocked(event)) continue;
|
||||
await unlockTokenDetails(event, account);
|
||||
eventStore.update(event);
|
||||
} else if (event.kind === WALLET_HISTORY_KIND) {
|
||||
if (!isHistoryDetailsLocked(event)) continue;
|
||||
await unlockHistoryDetails(event, account);
|
||||
eventStore.update(event);
|
||||
}
|
||||
}
|
||||
}, [wallet, account, tokens]);
|
||||
}, [wallet, account, events]);
|
||||
|
||||
const walletInfo = useStoreQuery(WalletQuery, [account.pubkey]);
|
||||
|
||||
@ -113,19 +78,30 @@ export default function WalletHomeView() {
|
||||
}
|
||||
>
|
||||
<WalletBalanceCard pubkey={account.pubkey} w="full" maxW="2xl" mx="auto" />
|
||||
{walletInfo?.locked === false && (
|
||||
<Card p="2" whiteSpace="pre-line">
|
||||
Key: {walletInfo.privateKey}
|
||||
<br />
|
||||
Mints: {walletInfo.mints.join(", ")}
|
||||
</Card>
|
||||
{walletInfo?.locked && (
|
||||
<Button onClick={unlock} colorScheme="primary" mx="auto" size="lg" w="sm">
|
||||
Unlock
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Flex direction="column" gap="2" w="full" maxW="lg" mx="auto">
|
||||
{tokens.map((token) => (
|
||||
<TokenEvent key={token.id} token={token} />
|
||||
))}
|
||||
</Flex>
|
||||
<Tabs isFitted maxW="2xl" mx="auto" w="full" isLazy>
|
||||
<TabList mb="1em">
|
||||
<Tab>History ({events.filter((e) => e.kind === WALLET_HISTORY_KIND).length})</Tab>
|
||||
<Tab>Tokens ({events.filter((e) => e.kind === WALLET_TOKEN_KIND).length})</Tab>
|
||||
<Tab>Mints ({balance ? Object.keys(balance).length : 0})</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel p="0">
|
||||
<WalletHistoryTab />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<WalletTokensTab />
|
||||
</TabPanel>
|
||||
<TabPanel p="0">
|
||||
<WalletMintsTab />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</SimpleView>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
122
src/views/wallet/tabs/history.tsx
Normal file
122
src/views/wallet/tabs/history.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import {
|
||||
AvatarGroup,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
Flex,
|
||||
IconButton,
|
||||
Spacer,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { useActiveAccount, useEventStore, useStoreQuery } from "applesauce-react/hooks";
|
||||
import {
|
||||
getHistoryDetails,
|
||||
getHistoryRedeemed,
|
||||
isHistoryDetailsLocked,
|
||||
unlockHistoryDetails,
|
||||
} from "applesauce-wallet/helpers";
|
||||
import { WalletHistoryQuery } from "applesauce-wallet/queries";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
|
||||
import Lock01 from "../../../components/icons/lock-01";
|
||||
import DebugEventButton from "../../../components/debug-modal/debug-event-button";
|
||||
import ArrowBlockUp from "../../../components/icons/arrow-block-up";
|
||||
import ArrowBlockDown from "../../../components/icons/arrow-block-down";
|
||||
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
|
||||
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
|
||||
import { useDeleteEventContext } from "../../../providers/route/delete-event-provider";
|
||||
import { TrashIcon } from "../../../components/icons";
|
||||
import useEventUpdate from "../../../hooks/use-event-update";
|
||||
import Timestamp from "../../../components/timestamp";
|
||||
import useSingleEvents from "../../../hooks/use-single-events";
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
|
||||
function HistoryEntry({ entry }: { entry: NostrEvent }) {
|
||||
const account = useActiveAccount()!;
|
||||
const eventStore = useEventStore();
|
||||
const locked = isHistoryDetailsLocked(entry);
|
||||
const details = !locked ? getHistoryDetails(entry) : undefined;
|
||||
useEventUpdate(entry.id);
|
||||
|
||||
const ref = useEventIntersectionRef(entry);
|
||||
const { deleteEvent } = useDeleteEventContext();
|
||||
|
||||
const redeemedIds = getHistoryRedeemed(entry);
|
||||
const redeemed = useSingleEvents(redeemedIds);
|
||||
|
||||
const unlock = useAsyncErrorHandler(async () => {
|
||||
await unlockHistoryDetails(entry, account);
|
||||
eventStore.update(entry);
|
||||
}, [entry, account, eventStore]);
|
||||
|
||||
return (
|
||||
<Card ref={ref}>
|
||||
<CardBody p="2" display="flex" flexDirection="row" gap="2">
|
||||
{locked ? (
|
||||
<Lock01 boxSize={8} />
|
||||
) : details?.direction === "in" ? (
|
||||
<ArrowBlockDown boxSize={8} color="green.500" />
|
||||
) : (
|
||||
<ArrowBlockUp boxSize={8} color="orange.500" />
|
||||
)}
|
||||
<Text fontSize="xl">{details?.amount}</Text>
|
||||
<Spacer />
|
||||
<ButtonGroup size="sm" alignItems="center">
|
||||
{locked && (
|
||||
<Button onClick={unlock} variant="link" p="2">
|
||||
Unlock
|
||||
</Button>
|
||||
)}
|
||||
<Timestamp timestamp={entry.created_at} />
|
||||
<DebugEventButton variant="ghost" event={entry} />
|
||||
<IconButton
|
||||
icon={<TrashIcon boxSize={5} />}
|
||||
aria-label="Delete entry"
|
||||
onClick={() => deleteEvent(entry)}
|
||||
colorScheme="red"
|
||||
variant="ghost"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</CardBody>
|
||||
{redeemed.length > 0 && (
|
||||
<CardFooter p="2">
|
||||
<Text mr="2">Redeemed zaps from:</Text>
|
||||
<AvatarGroup size="sm">
|
||||
{redeemed.map((event) => (
|
||||
<UserAvatarLink pubkey={event.pubkey} />
|
||||
))}
|
||||
</AvatarGroup>
|
||||
</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WalletHistoryTab() {
|
||||
const account = useActiveAccount()!;
|
||||
const eventStore = useEventStore();
|
||||
|
||||
const history = useStoreQuery(WalletHistoryQuery, [account.pubkey]) ?? [];
|
||||
const locked = useStoreQuery(WalletHistoryQuery, [account.pubkey, true]) ?? [];
|
||||
|
||||
const unlock = useAsyncErrorHandler(async () => {
|
||||
for (const entry of locked) {
|
||||
if (!isHistoryDetailsLocked(entry)) continue;
|
||||
await unlockHistoryDetails(entry, account);
|
||||
eventStore.update(entry);
|
||||
}
|
||||
}, [locked, account, eventStore]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" w="full">
|
||||
{locked && locked.length > 0 && (
|
||||
<Button onClick={unlock} size="sm" variant="link" p="2" ms="auto">
|
||||
Unlock all ({locked?.length})
|
||||
</Button>
|
||||
)}
|
||||
{history?.map((entry) => <HistoryEntry key={entry.id} entry={entry} />)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
30
src/views/wallet/tabs/mints.tsx
Normal file
30
src/views/wallet/tabs/mints.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { Box, Card, Flex, Link, Text } from "@chakra-ui/react";
|
||||
import { useActiveAccount, useStoreQuery } from "applesauce-react/hooks";
|
||||
import { WalletBalanceQuery } from "applesauce-wallet/queries";
|
||||
import CashuMintFavicon from "../../../components/cashu/cashu-mint-favicon";
|
||||
import CashuMintName from "../../../components/cashu/cashu-mint-name";
|
||||
|
||||
export default function WalletMintsTab() {
|
||||
const account = useActiveAccount()!;
|
||||
const balance = useStoreQuery(WalletBalanceQuery, [account.pubkey]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
{balance &&
|
||||
Object.entries(balance).map(([mint, total]) => (
|
||||
<Card key={mint} gap="2" p="2" display="flex" direction="row">
|
||||
<CashuMintFavicon mint={mint} size="sm" />
|
||||
<Flex direction="column" w="full">
|
||||
<Flex w="full" justifyContent="space-between">
|
||||
<CashuMintName mint={mint} fontWeight="bold" />
|
||||
<Link href={mint} isExternal fontStyle="italic">
|
||||
{mint}
|
||||
</Link>
|
||||
</Flex>
|
||||
<Text>Amount: {total}</Text>
|
||||
</Flex>
|
||||
</Card>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
124
src/views/wallet/tabs/tokens.tsx
Normal file
124
src/views/wallet/tabs/tokens.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Card,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
Flex,
|
||||
FlexProps,
|
||||
IconButton,
|
||||
Spacer,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { useActiveAccount, useEventStore, useStoreQuery } from "applesauce-react/hooks";
|
||||
import { WalletTokensQuery } from "applesauce-wallet/queries";
|
||||
import { getTokenDetails, isTokenDetailsLocked, unlockTokenDetails } from "applesauce-wallet/helpers";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
|
||||
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
|
||||
import useEventUpdate from "../../../hooks/use-event-update";
|
||||
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
|
||||
import { ECashIcon, TrashIcon } from "../../../components/icons";
|
||||
import DebugEventButton from "../../../components/debug-modal/debug-event-button";
|
||||
import { useDeleteEventContext } from "../../../providers/route/delete-event-provider";
|
||||
import Timestamp from "../../../components/timestamp";
|
||||
import { useState } from "react";
|
||||
import { getCashuWallet } from "../../../services/cashu-mints";
|
||||
|
||||
function TokenEvent({ token }: { token: NostrEvent }) {
|
||||
const account = useActiveAccount();
|
||||
const eventStore = useEventStore();
|
||||
useEventUpdate(token.id);
|
||||
const ref = useEventIntersectionRef(token);
|
||||
|
||||
const locked = isTokenDetailsLocked(token);
|
||||
const details = !locked ? getTokenDetails(token) : undefined;
|
||||
const amount = details?.proofs.reduce((t, p) => t + p.amount, 0);
|
||||
|
||||
const [spent, setSpent] = useState<boolean>();
|
||||
const check = useAsyncErrorHandler(async () => {
|
||||
if (!details) return;
|
||||
const wallet = await getCashuWallet(details.mint);
|
||||
const state = await wallet.checkProofsStates(details.proofs);
|
||||
|
||||
setSpent(!state.some((t) => t.state === "UNSPENT"));
|
||||
}, [details, setSpent]);
|
||||
|
||||
const { deleteEvent } = useDeleteEventContext();
|
||||
|
||||
const unlock = useAsyncErrorHandler(async () => {
|
||||
if (!account) return;
|
||||
await unlockTokenDetails(token, account);
|
||||
eventStore.update(token);
|
||||
}, [token, account, eventStore]);
|
||||
|
||||
return (
|
||||
<Card ref={ref} w="full">
|
||||
<CardHeader p="2" alignItems="center" flexDirection="row" display="flex" gap="2">
|
||||
<ECashIcon color="green.400" boxSize={8} />
|
||||
{amount && <Text fontSize="xl">{amount}</Text>}
|
||||
<ButtonGroup size="sm" ms="auto" alignItems="center">
|
||||
{locked && (
|
||||
<Button onClick={unlock} variant="link" p="2">
|
||||
Unlock
|
||||
</Button>
|
||||
)}
|
||||
<Timestamp timestamp={token.created_at} />
|
||||
<DebugEventButton variant="ghost" event={token} />
|
||||
<IconButton
|
||||
icon={<TrashIcon boxSize={5} />}
|
||||
aria-label="Delete entry"
|
||||
onClick={() => deleteEvent(token)}
|
||||
colorScheme="red"
|
||||
variant="ghost"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</CardHeader>
|
||||
{details && (
|
||||
<CardFooter px="2" pt="0" pb="0" gap="2" display="flex">
|
||||
<Button
|
||||
variant="link"
|
||||
colorScheme={spent === undefined ? undefined : spent ? "red" : "green"}
|
||||
onClick={check}
|
||||
>
|
||||
{spent === undefined ? "Check" : spent ? "Spent" : "Unspent"}
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Text fontSize="sm" fontStyle="italic">
|
||||
{details.mint}
|
||||
</Text>
|
||||
</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WalletTokensTab({ ...props }: Omit<FlexProps, "children">) {
|
||||
const account = useActiveAccount()!;
|
||||
const eventStore = useEventStore();
|
||||
|
||||
const tokens = useStoreQuery(WalletTokensQuery, [account.pubkey]) ?? [];
|
||||
const locked = useStoreQuery(WalletTokensQuery, [account.pubkey, true]) ?? [];
|
||||
|
||||
const unlock = useAsyncErrorHandler(async () => {
|
||||
if (!locked) return;
|
||||
for (const token of locked) {
|
||||
await unlockTokenDetails(token, account);
|
||||
eventStore.update(token);
|
||||
}
|
||||
}, [locked, account, eventStore]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" {...props}>
|
||||
{locked && locked.length > 0 && (
|
||||
<Button onClick={unlock} size="sm" variant="link" p="2" ms="auto">
|
||||
Unlock all ({locked?.length})
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{tokens.map((token) => (
|
||||
<TokenEvent key={token.id} token={token} />
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user