mirror of
https://github.com/lumehq/lume.git
synced 2025-03-28 18:52:33 +01:00
Add bitcoin connect (#215)
* feat: add bitcoin connect * feat: improve zap screen
This commit is contained in:
parent
1283432632
commit
3fbd66dece
@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@getalby/bitcoin-connect-react": "^3.5.3",
|
||||||
"@lume/icons": "workspace:^",
|
"@lume/icons": "workspace:^",
|
||||||
"@lume/system": "workspace:^",
|
"@lume/system": "workspace:^",
|
||||||
"@lume/ui": "workspace:^",
|
"@lume/ui": "workspace:^",
|
||||||
|
@ -19,42 +19,33 @@ function Screen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container withDrag>
|
<Container withDrag>
|
||||||
<div className="h-full w-full flex-1 px-5">
|
<div className="flex-1 w-full h-full px-5">
|
||||||
{!isDone ? (
|
<div className="flex flex-col gap-2">
|
||||||
<>
|
<div>
|
||||||
<div className="flex flex-col gap-2">
|
<h3 className="text-2xl font-light">
|
||||||
<div className="inline-flex size-14 items-center justify-center rounded-xl bg-black text-white shadow-md">
|
Connect <span className="font-semibold">bitcoin wallet</span> to
|
||||||
<ZapIcon className="size-5" />
|
start zapping to your favorite content and creator.
|
||||||
</div>
|
</h3>
|
||||||
<div>
|
</div>
|
||||||
<h3 className="text-2xl font-light">
|
</div>
|
||||||
Connect <span className="font-semibold">bitcoin wallet</span>{" "}
|
<div className="flex flex-col gap-2 mt-10">
|
||||||
to start zapping to your favorite content and creator.
|
<div className="flex flex-col gap-1.5">
|
||||||
</h3>
|
<label>Paste a Nostr Wallet Connect connection string</label>
|
||||||
</div>
|
<textarea
|
||||||
</div>
|
value={uri}
|
||||||
<div className="mt-10 flex flex-col gap-2">
|
onChange={(e) => setUri(e.target.value)}
|
||||||
<div className="flex flex-col gap-1.5">
|
placeholder="nostrconnect://"
|
||||||
<label>Paste a Nostr Wallet Connect connection string</label>
|
className="w-full h-24 px-3 bg-transparent rounded-lg border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
||||||
<textarea
|
/>
|
||||||
value={uri}
|
</div>
|
||||||
onChange={(e) => setUri(e.target.value)}
|
<button
|
||||||
placeholder="nostrconnect://"
|
type="button"
|
||||||
className="h-24 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
onClick={save}
|
||||||
/>
|
className="inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
|
||||||
</div>
|
>
|
||||||
<button
|
Save & Connect
|
||||||
type="button"
|
</button>
|
||||||
onClick={save}
|
</div>
|
||||||
className="inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Save & Connect
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div>Done</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -79,7 +79,7 @@ function Screen() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/settings/zap">
|
<Link to="/settings/wallet">
|
||||||
{({ isActive }) => {
|
{({ isActive }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -91,9 +91,7 @@ function Screen() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ZapIcon className="size-5 shrink-0" />
|
<ZapIcon className="size-5 shrink-0" />
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">Wallet</p>
|
||||||
{t("settings.zap.title")}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
39
apps/desktop2/src/routes/settings/bitcoin-connect.tsx
Normal file
39
apps/desktop2/src/routes/settings/bitcoin-connect.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Button, init } from "@getalby/bitcoin-connect-react";
|
||||||
|
import { NostrAccount } from "@lume/system";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { getCurrent } from "@tauri-apps/api/webviewWindow";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/settings/bitcoin-connect")({
|
||||||
|
beforeLoad: () => {
|
||||||
|
init({
|
||||||
|
appName: "Lume",
|
||||||
|
filters: ["nwc"],
|
||||||
|
showBalance: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const setNwcUri = async (uri: string) => {
|
||||||
|
const cmd = await NostrAccount.setWallet(uri);
|
||||||
|
if (cmd) getCurrent().close();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center size-full">
|
||||||
|
<div className="flex flex-col items-center justify-center gap-3 text-center">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-black/70 dark:text-white/70">
|
||||||
|
Click to the button below to connect with your Bitcoin wallet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onConnected={(provider) =>
|
||||||
|
setNwcUri(provider.client.nostrWalletConnectUrl)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -51,7 +51,7 @@ function Screen() {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full max-w-xl mx-auto">
|
<div className="w-full max-w-xl mx-auto">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="flex items-center w-full h-12 px-3 text-sm rounded-xl bg-black/5 dark:bg-white/5">
|
<div className="flex items-center w-full px-3 text-sm rounded-lg h-11 bg-black/5 dark:bg-white/5">
|
||||||
* Setting changes require restarting the app to take effect.
|
* Setting changes require restarting the app to take effect.
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
59
apps/desktop2/src/routes/settings/wallet.tsx
Normal file
59
apps/desktop2/src/routes/settings/wallet.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { NostrAccount } from "@lume/system";
|
||||||
|
import { getBitcoinDisplayValues } from "@lume/utils";
|
||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/settings/wallet")({
|
||||||
|
beforeLoad: async () => {
|
||||||
|
const wallet = await NostrAccount.loadWallet();
|
||||||
|
if (!wallet) {
|
||||||
|
throw redirect({ to: "/settings/bitcoin-connect" });
|
||||||
|
}
|
||||||
|
const balance = getBitcoinDisplayValues(wallet);
|
||||||
|
return { balance };
|
||||||
|
},
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { balance } = Route.useRouteContext();
|
||||||
|
|
||||||
|
const disconnect = async () => {
|
||||||
|
window.localStorage.removeItem("bc:config");
|
||||||
|
await NostrAccount.removeWallet();
|
||||||
|
|
||||||
|
return redirect({ to: "/settings/bitcoin-connect" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-xl mx-auto">
|
||||||
|
<div className="flex flex-col w-full gap-3">
|
||||||
|
<div className="flex flex-col w-full px-3 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
|
<div className="flex items-center justify-between w-full gap-4 py-3">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-medium">Connection</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end w-36 shrink-0">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => disconnect()}
|
||||||
|
className="h-8 w-max px-2.5 text-sm rounded-lg inline-flex items-center justify-center bg-black/10 dark:bg-white/10 hover:bg-black/20 dark:hover:bg-white/20"
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col w-full px-3 bg-black/5 dark:bg-white/5 rounded-xl">
|
||||||
|
<div className="flex items-center justify-between w-full gap-4 py-3">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-medium">Current Balance</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end w-36 shrink-0">
|
||||||
|
₿ {balance.bitcoinFormatted}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,102 +0,0 @@
|
|||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/settings/zap")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-xl mx-auto">
|
|
||||||
<div className="flex flex-col gap-3 divide-y divide-neutral-300 dark:divide-neutral-700">
|
|
||||||
<div className="flex flex-col gap-6 py-3">
|
|
||||||
<Connection />
|
|
||||||
<DefaultAmount />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Connection() {
|
|
||||||
const [uri, setUri] = useState("");
|
|
||||||
|
|
||||||
const connect = async () => {
|
|
||||||
try {
|
|
||||||
await invoke("set_nwc", { uri });
|
|
||||||
} catch (e) {
|
|
||||||
await message(String(e), { title: "Zap", kind: "info" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="text-sm font-medium w-36 shrink-0 text-end">
|
|
||||||
Connection
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex flex-col w-full gap-1">
|
|
||||||
<label
|
|
||||||
htmlFor="nwc"
|
|
||||||
className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
|
||||||
>
|
|
||||||
Nostr Wallet Connect
|
|
||||||
</label>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
name="nwc"
|
|
||||||
type="text"
|
|
||||||
value={uri}
|
|
||||||
onChange={(e) => setUri(e.target.value)}
|
|
||||||
placeholder="nostrconnect://"
|
|
||||||
className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => connect()}
|
|
||||||
className="inline-flex items-center justify-center w-24 text-sm font-medium rounded-lg h-9 bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-900 dark:hover:bg-neutral-700"
|
|
||||||
>
|
|
||||||
Connect
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DefaultAmount() {
|
|
||||||
return (
|
|
||||||
<div className="flex items-start gap-6">
|
|
||||||
<div className="text-sm font-medium w-36 shrink-0 text-end">
|
|
||||||
Default amount
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex flex-col w-full gap-1">
|
|
||||||
<label
|
|
||||||
htmlFor="amount"
|
|
||||||
className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
|
||||||
>
|
|
||||||
Set default amount for quick zapping
|
|
||||||
</label>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
name="amount"
|
|
||||||
type="number"
|
|
||||||
value={21}
|
|
||||||
className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="inline-flex items-center justify-center w-24 text-sm font-medium rounded-lg h-9 bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-900 dark:hover:bg-neutral-700"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
import { Balance } from "@/components/balance";
|
|
||||||
import { User } from "@/components/user";
|
|
||||||
import { LumeEvent } from "@lume/system";
|
|
||||||
import { Box, Container } from "@lume/ui";
|
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { getCurrent } from "@tauri-apps/api/webviewWindow";
|
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { useState } from "react";
|
|
||||||
import CurrencyInput from "react-currency-input-field";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const DEFAULT_VALUES = [69, 100, 200, 500];
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/zap/$id")({
|
|
||||||
component: Screen,
|
|
||||||
});
|
|
||||||
|
|
||||||
function Screen() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { id } = Route.useParams();
|
|
||||||
// @ts-ignore, magic !!!
|
|
||||||
const { pubkey, account } = Route.useSearch();
|
|
||||||
|
|
||||||
const [amount, setAmount] = useState(21);
|
|
||||||
const [content, setContent] = useState("");
|
|
||||||
const [isCompleted, setIsCompleted] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
const submit = async () => {
|
|
||||||
try {
|
|
||||||
// start loading
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const val = await LumeEvent.zap(id, amount, content);
|
|
||||||
|
|
||||||
if (val) {
|
|
||||||
setIsCompleted(true);
|
|
||||||
const window = getCurrent();
|
|
||||||
// close current window
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setIsLoading(false);
|
|
||||||
await message(String(e), {
|
|
||||||
title: "Zap",
|
|
||||||
kind: "error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<Balance account={account} />
|
|
||||||
<Box className="flex flex-col gap-3">
|
|
||||||
<div className="flex flex-col justify-between h-full py-5">
|
|
||||||
<div className="flex items-center justify-center gap-2 h-11 shrink-0">
|
|
||||||
{t("note.zap.modalTitle")}{" "}
|
|
||||||
<User.Provider pubkey={pubkey}>
|
|
||||||
<User.Root className="inline-flex items-center gap-2 p-1 rounded-full bg-neutral-100 dark:bg-neutral-900">
|
|
||||||
<User.Avatar className="rounded-full size-6" />
|
|
||||||
<User.Name className="pr-2 text-sm font-medium" />
|
|
||||||
</User.Root>
|
|
||||||
</User.Provider>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col justify-between flex-1 px-5">
|
|
||||||
<div className="relative flex flex-col flex-1 pb-8">
|
|
||||||
<div className="inline-flex items-center justify-center flex-1 h-full gap-1">
|
|
||||||
<CurrencyInput
|
|
||||||
placeholder="0"
|
|
||||||
defaultValue={21}
|
|
||||||
value={amount}
|
|
||||||
decimalsLimit={2}
|
|
||||||
min={0} // 0 sats
|
|
||||||
max={10000} // 1M sats
|
|
||||||
maxLength={10000} // 1M sats
|
|
||||||
onValueChange={(value) => setAmount(Number(value))}
|
|
||||||
className="flex-1 w-full text-4xl font-semibold text-right bg-transparent border-none placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
|
|
||||||
/>
|
|
||||||
<span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-500 dark:text-neutral-400">
|
|
||||||
sats
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="inline-flex items-center justify-center gap-2">
|
|
||||||
{DEFAULT_VALUES.map((value) => (
|
|
||||||
<button
|
|
||||||
key={value}
|
|
||||||
type="button"
|
|
||||||
onClick={() => setAmount(value)}
|
|
||||||
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
|
||||||
>
|
|
||||||
{value} sats
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col w-full gap-2">
|
|
||||||
<input
|
|
||||||
name="message"
|
|
||||||
value={content}
|
|
||||||
onChange={(e) => setContent(e.target.value)}
|
|
||||||
spellCheck={false}
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
placeholder={t("note.zap.messagePlaceholder")}
|
|
||||||
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:text-neutral-400"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => submit()}
|
|
||||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg border-t border-neutral-900 bg-neutral-950 pb-[2px] font-semibold text-neutral-50 hover:bg-neutral-900 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
|
||||||
>
|
|
||||||
{isCompleted
|
|
||||||
? t("note.zap.buttonFinish")
|
|
||||||
: isLoading
|
|
||||||
? t("note.zap.buttonLoading")
|
|
||||||
: t("note.zap.zap")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
119
apps/desktop2/src/routes/zap.$id.tsx
Normal file
119
apps/desktop2/src/routes/zap.$id.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { User } from "@/components/user";
|
||||||
|
import { NostrQuery } from "@lume/system";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { getCurrent } from "@tauri-apps/api/webviewWindow";
|
||||||
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
|
import { useState } from "react";
|
||||||
|
import CurrencyInput from "react-currency-input-field";
|
||||||
|
|
||||||
|
const DEFAULT_VALUES = [21, 50, 100, 200];
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/zap/$id")({
|
||||||
|
beforeLoad: async ({ params }) => {
|
||||||
|
const event = await NostrQuery.getEvent(params.id);
|
||||||
|
return { event };
|
||||||
|
},
|
||||||
|
component: Screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Screen() {
|
||||||
|
const { event } = Route.useRouteContext();
|
||||||
|
|
||||||
|
const [amount, setAmount] = useState(21);
|
||||||
|
const [content, setContent] = useState("");
|
||||||
|
const [isCompleted, setIsCompleted] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
try {
|
||||||
|
// start loading
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Zap
|
||||||
|
const val = await event.zap(amount, content);
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
setIsCompleted(true);
|
||||||
|
// close current window
|
||||||
|
await getCurrent().close();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setIsLoading(false);
|
||||||
|
await message(String(e), {
|
||||||
|
title: "Zap",
|
||||||
|
kind: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-tauri-drag-region className="flex flex-col pb-5 size-full">
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="flex items-center justify-center h-24 gap-2 shrink-0"
|
||||||
|
>
|
||||||
|
<p className="text-sm">Send zap to </p>
|
||||||
|
<User.Provider pubkey={event.pubkey}>
|
||||||
|
<User.Root className="inline-flex items-center gap-2 p-1 rounded-full bg-black/5 dark:bg-white/5">
|
||||||
|
<User.Avatar className="rounded-full size-6" />
|
||||||
|
<User.Name className="pr-2 text-sm font-medium" />
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-between h-full">
|
||||||
|
<div className="flex flex-col justify-between flex-1 px-5">
|
||||||
|
<div className="relative flex flex-col flex-1 pb-8">
|
||||||
|
<div className="inline-flex items-center justify-center flex-1 h-full gap-1">
|
||||||
|
<CurrencyInput
|
||||||
|
placeholder="0"
|
||||||
|
defaultValue={21}
|
||||||
|
value={amount}
|
||||||
|
decimalsLimit={2}
|
||||||
|
min={0} // 0 sats
|
||||||
|
max={10000} // 1M sats
|
||||||
|
maxLength={10000} // 1M sats
|
||||||
|
onValueChange={(value) => setAmount(Number(value))}
|
||||||
|
className="flex-1 w-full text-4xl font-semibold text-right bg-transparent border-none placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
|
||||||
|
/>
|
||||||
|
<span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-500 dark:text-neutral-400">
|
||||||
|
sats
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex items-center justify-center gap-2">
|
||||||
|
{DEFAULT_VALUES.map((value) => (
|
||||||
|
<button
|
||||||
|
key={value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setAmount(value)}
|
||||||
|
className="w-max rounded-full bg-black/10 px-2.5 py-1 text-xs font-medium hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
|
||||||
|
>
|
||||||
|
{value} sats
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col w-full gap-2">
|
||||||
|
<input
|
||||||
|
name="message"
|
||||||
|
value={content}
|
||||||
|
onChange={(e) => setContent(e.target.value)}
|
||||||
|
spellCheck={false}
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
placeholder="Enter message (optional)"
|
||||||
|
className="h-11 w-full resize-none rounded-xl border-transparent bg-black/5 px-3 !outline-none placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:bg-white/5"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => submit()}
|
||||||
|
className="inline-flex items-center justify-center w-full h-10 font-medium rounded-xl bg-neutral-950 text-neutral-50 hover:bg-neutral-900 dark:bg-white/20 dark:hover:bg-white/30"
|
||||||
|
>
|
||||||
|
{isCompleted ? "Zapped" : isLoading ? "Processing..." : "Zap"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from "@lume/types";
|
import type { Metadata } from "@lume/types";
|
||||||
import { type Result, commands } from "./commands";
|
|
||||||
import { Window } from "@tauri-apps/api/window";
|
import { Window } from "@tauri-apps/api/window";
|
||||||
|
import { type Result, commands } from "./commands";
|
||||||
|
|
||||||
export class NostrAccount {
|
export class NostrAccount {
|
||||||
static async getAccounts() {
|
static async getAccounts() {
|
||||||
@ -99,8 +99,28 @@ export class NostrAccount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async loadWallet() {
|
||||||
|
const query = await commands.loadWallet();
|
||||||
|
|
||||||
|
if (query.status === "ok") {
|
||||||
|
return Number.parseInt(query.data);
|
||||||
|
} else {
|
||||||
|
throw new Error(query.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async setWallet(uri: string) {
|
static async setWallet(uri: string) {
|
||||||
const query = await commands.setNwc(uri);
|
const query = await commands.setWallet(uri);
|
||||||
|
|
||||||
|
if (query.status === "ok") {
|
||||||
|
return query.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(query.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async removeWallet() {
|
||||||
|
const query = await commands.removeWallet();
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return query.data;
|
return query.data;
|
||||||
@ -110,7 +130,7 @@ export class NostrAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getProfile() {
|
static async getProfile() {
|
||||||
const query = await commands.getCurrentUserProfile();
|
const query = await commands.getCurrentProfile();
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return JSON.parse(query.data) as Metadata;
|
return JSON.parse(query.data) as Metadata;
|
||||||
@ -119,16 +139,6 @@ export class NostrAccount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getBalance() {
|
|
||||||
const query = await commands.getBalance();
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
|
||||||
return Number.parseInt(query.data);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getContactList() {
|
static async getContactList() {
|
||||||
const query = await commands.getContactList();
|
const query = await commands.getContactList();
|
||||||
|
|
||||||
|
@ -180,25 +180,25 @@ try {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setNwc(uri: string) : Promise<Result<boolean, string>> {
|
async setWallet(uri: string) : Promise<Result<boolean, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("set_nwc", { uri }) };
|
return { status: "ok", data: await TAURI_INVOKE("set_wallet", { uri }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadNwc() : Promise<Result<boolean, string>> {
|
async loadWallet() : Promise<Result<string, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("load_nwc") };
|
return { status: "ok", data: await TAURI_INVOKE("load_wallet") };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getBalance() : Promise<Result<string, string>> {
|
async removeWallet() : Promise<Result<null, null>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("get_balance") };
|
return { status: "ok", data: await TAURI_INVOKE("remove_wallet") };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
@ -407,9 +407,9 @@ try {
|
|||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openWindow(label: string, title: string, url: string, width: number, height: number) : Promise<Result<null, string>> {
|
async openWindow(window: Window) : Promise<Result<null, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("open_window", { label, title, url, width, height }) };
|
return { status: "ok", data: await TAURI_INVOKE("open_window", { window }) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
@ -438,6 +438,7 @@ export type Meta = { content: string; images: string[]; videos: string[]; events
|
|||||||
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
|
export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null }
|
||||||
export type RichEvent = { raw: string; parsed: Meta | null }
|
export type RichEvent = { raw: string; parsed: Meta | null }
|
||||||
export type Settings = { proxy: string | null; image_resize_service: string | null; use_relay_hint: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean }
|
export type Settings = { proxy: string | null; image_resize_service: string | null; use_relay_hint: boolean; content_warning: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean }
|
||||||
|
export type Window = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean }
|
||||||
|
|
||||||
/** tauri-specta globals **/
|
/** tauri-specta globals **/
|
||||||
|
|
||||||
|
@ -18,7 +18,15 @@ export class LumeWindow {
|
|||||||
const label = `event-${event.id}`;
|
const label = `event-${event.id}`;
|
||||||
const url = `/events/${root ?? reply ?? event.id}`;
|
const url = `/events/${root ?? reply ?? event.id}`;
|
||||||
|
|
||||||
const query = await commands.openWindow(label, "Thread", url, 500, 800);
|
const query = await commands.openWindow({
|
||||||
|
label,
|
||||||
|
url,
|
||||||
|
title: "Thread",
|
||||||
|
width: 500,
|
||||||
|
height: 800,
|
||||||
|
maximizable: true,
|
||||||
|
minimizable: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return query.data;
|
return query.data;
|
||||||
@ -29,13 +37,15 @@ export class LumeWindow {
|
|||||||
|
|
||||||
static async openProfile(pubkey: string) {
|
static async openProfile(pubkey: string) {
|
||||||
const label = `user-${pubkey}`;
|
const label = `user-${pubkey}`;
|
||||||
const query = await commands.openWindow(
|
const query = await commands.openWindow({
|
||||||
label,
|
label,
|
||||||
"Profile",
|
url: `/users/${pubkey}`,
|
||||||
`/users/${pubkey}`,
|
title: "Profile",
|
||||||
500,
|
width: 500,
|
||||||
800,
|
height: 800,
|
||||||
);
|
maximizable: true,
|
||||||
|
minimizable: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return query.data;
|
return query.data;
|
||||||
@ -60,7 +70,15 @@ export class LumeWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const label = `editor-${reply_to ? reply_to : 0}`;
|
const label = `editor-${reply_to ? reply_to : 0}`;
|
||||||
const query = await commands.openWindow(label, "Editor", url, 560, 340);
|
const query = await commands.openWindow({
|
||||||
|
label,
|
||||||
|
url,
|
||||||
|
title: "Editor",
|
||||||
|
width: 560,
|
||||||
|
height: 340,
|
||||||
|
maximizable: true,
|
||||||
|
minimizable: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return query.data;
|
return query.data;
|
||||||
@ -69,45 +87,35 @@ export class LumeWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async openZap(id: string, pubkey: string) {
|
static async openZap(id: string) {
|
||||||
const nwc = await commands.loadNwc();
|
const wallet = await commands.loadWallet();
|
||||||
|
|
||||||
if (nwc.status === "ok") {
|
if (wallet.status === "ok") {
|
||||||
const status = nwc.data;
|
await commands.openWindow({
|
||||||
|
label: `zap-${id}`,
|
||||||
if (!status) {
|
url: `/zap/${id}`,
|
||||||
const label = "nwc";
|
title: "Zap",
|
||||||
await commands.openWindow(
|
width: 360,
|
||||||
label,
|
height: 460,
|
||||||
"Nostr Wallet Connect",
|
maximizable: false,
|
||||||
"/nwc",
|
minimizable: false,
|
||||||
400,
|
});
|
||||||
600,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const label = `zap-${id}`;
|
|
||||||
await commands.openWindow(
|
|
||||||
label,
|
|
||||||
"Zap",
|
|
||||||
`/zap/${id}?pubkey=${pubkey}`,
|
|
||||||
400,
|
|
||||||
500,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(nwc.error);
|
await LumeWindow.openSettings("bitcoin-connect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async openSettings() {
|
static async openSettings(path?: string) {
|
||||||
const label = "settings";
|
const label = "settings";
|
||||||
const query = await commands.openWindow(
|
const query = await commands.openWindow({
|
||||||
label,
|
label,
|
||||||
"Settings",
|
url: path ? `/settings/${path}` : "/settings/general",
|
||||||
"/settings/general",
|
title: "Settings",
|
||||||
800,
|
width: 800,
|
||||||
500,
|
height: 500,
|
||||||
);
|
maximizable: false,
|
||||||
|
minimizable: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return query.data;
|
return query.data;
|
||||||
@ -118,30 +126,15 @@ export class LumeWindow {
|
|||||||
|
|
||||||
static async openSearch() {
|
static async openSearch() {
|
||||||
const label = "search";
|
const label = "search";
|
||||||
const query = await commands.openWindow(
|
const query = await commands.openWindow({
|
||||||
label,
|
label,
|
||||||
"Search",
|
url: "/search",
|
||||||
"/search",
|
title: "Search",
|
||||||
400,
|
width: 400,
|
||||||
600,
|
height: 600,
|
||||||
);
|
maximizable: false,
|
||||||
|
minimizable: false,
|
||||||
if (query.status === "ok") {
|
});
|
||||||
return query.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(query.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async openActivity(account: string) {
|
|
||||||
const label = "activity";
|
|
||||||
const query = await commands.openWindow(
|
|
||||||
label,
|
|
||||||
"Activity",
|
|
||||||
`/activity/${account}/texts`,
|
|
||||||
400,
|
|
||||||
600,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (query.status === "ok") {
|
if (query.status === "ok") {
|
||||||
return query.data;
|
return query.data;
|
||||||
|
@ -81,7 +81,7 @@ export function displayLongHandle(str: string) {
|
|||||||
const handle = split[0];
|
const handle = split[0];
|
||||||
const service = split[1];
|
const service = split[1];
|
||||||
|
|
||||||
return handle.substring(0, 16) + "..." + "@" + service;
|
return `${handle.substring(0, 16)}...@${service}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert number to K, M, B, T, etc.
|
// convert number to K, M, B, T, etc.
|
||||||
@ -127,7 +127,7 @@ export function getBitcoinDisplayValues(satoshis: number) {
|
|||||||
.reverse()
|
.reverse()
|
||||||
.forEach((c, index) => {
|
.forEach((c, index) => {
|
||||||
if (index > 0 && index % 3 === 0) {
|
if (index > 0 && index % 3 === 0) {
|
||||||
res = " " + res;
|
res = ` ${res}`;
|
||||||
}
|
}
|
||||||
res = c + res;
|
res = c + res;
|
||||||
});
|
});
|
||||||
|
114
pnpm-lock.yaml
generated
114
pnpm-lock.yaml
generated
@ -54,6 +54,9 @@ importers:
|
|||||||
|
|
||||||
apps/desktop2:
|
apps/desktop2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@getalby/bitcoin-connect-react':
|
||||||
|
specifier: ^3.5.3
|
||||||
|
version: 3.5.3(@types/react@18.3.3)(react@18.3.1)(typescript@5.4.5)
|
||||||
'@lume/icons':
|
'@lume/icons':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../packages/icons
|
version: link:../../packages/icons
|
||||||
@ -1196,6 +1199,49 @@ packages:
|
|||||||
resolution: {integrity: sha512-7ncjjSpRSRKvjJEoru092iFiEoC89lz4oG4+SGg9hh7DI/5SXf+kE+dg+6Fv/bwiK/WJCo4Q2gvPZGRlCE5mcA==}
|
resolution: {integrity: sha512-7ncjjSpRSRKvjJEoru092iFiEoC89lz4oG4+SGg9hh7DI/5SXf+kE+dg+6Fv/bwiK/WJCo4Q2gvPZGRlCE5mcA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@getalby/bitcoin-connect-react@3.5.3(@types/react@18.3.3)(react@18.3.1)(typescript@5.4.5):
|
||||||
|
resolution: {integrity: sha512-/oAPFFva/T946JzuNv6X/AuCGb46co2rLxfiINy4am/jFN+mAZ1HNGjOycTodpsTXnNpr3Ih37wV+YuI/03ueQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.2.0
|
||||||
|
dependencies:
|
||||||
|
'@getalby/bitcoin-connect': 3.5.3(@types/react@18.3.3)(react@18.3.1)(typescript@5.4.5)
|
||||||
|
react: 18.3.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- immer
|
||||||
|
- typescript
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@getalby/bitcoin-connect@3.5.3(@types/react@18.3.3)(react@18.3.1)(typescript@5.4.5):
|
||||||
|
resolution: {integrity: sha512-csVNT4gXzuJtXP3ZyXnNnSGpt4JEQJvynLhc/aG3VNZVqPhNgot5Npj1J4XIewrsn4sWVIr9WkAtCxGfQ6XmSQ==}
|
||||||
|
dependencies:
|
||||||
|
'@getalby/lightning-tools': 5.0.3
|
||||||
|
'@getalby/sdk': 3.5.1(typescript@5.4.5)
|
||||||
|
'@lightninglabs/lnc-web': 0.3.1-alpha
|
||||||
|
qrcode-generator: 1.4.4
|
||||||
|
zustand: 4.5.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- immer
|
||||||
|
- react
|
||||||
|
- typescript
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@getalby/lightning-tools@5.0.3:
|
||||||
|
resolution: {integrity: sha512-QG3/SBI5n2py5IgsjP3K+c8eq55eiI3PQB12yo9Pot0b5hcN7TNNoTKn0fgLJjO1iEVCUkF513kDOpjjXwK0hQ==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@getalby/sdk@3.5.1(typescript@5.4.5):
|
||||||
|
resolution: {integrity: sha512-Qz9GgXMoVpupDLqbzA2CHpru+9yqijQrxeRN7CDfV6l39js/BGwin93MFTh7eFj2TsMo+i8JeM3BVn+SJn/iRg==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 5.0.1
|
||||||
|
nostr-tools: 1.17.0(typescript@5.4.5)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- typescript
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@img/sharp-darwin-arm64@0.33.4:
|
/@img/sharp-darwin-arm64@0.33.4:
|
||||||
resolution: {integrity: sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==}
|
resolution: {integrity: sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==}
|
||||||
engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
|
engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
|
||||||
@ -1433,6 +1479,21 @@ packages:
|
|||||||
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@lightninglabs/lnc-core@0.3.1-alpha:
|
||||||
|
resolution: {integrity: sha512-I/hThdItLWJ6RU8Z27ZIXhpBS2JJuD3+TjtaQXX2CabaUYXlcN4sk+Kx8N/zG/fk8qZvjlRWum4vHu4ZX554Fg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@lightninglabs/lnc-web@0.3.1-alpha:
|
||||||
|
resolution: {integrity: sha512-yL5SgBkl6kd6ISzJHGlSN7TXbiDoo1pfGvTOIdVWYVyXtEeW8PT+x6YGOmyQXGFT2OOf7fC7PfP9VnskDPuFaA==}
|
||||||
|
dependencies:
|
||||||
|
'@lightninglabs/lnc-core': 0.3.1-alpha
|
||||||
|
crypto-js: 4.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@noble/ciphers@0.2.0:
|
||||||
|
resolution: {integrity: sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@noble/ciphers@0.5.3:
|
/@noble/ciphers@0.5.3:
|
||||||
resolution: {integrity: sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==}
|
resolution: {integrity: sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3434,6 +3495,10 @@ packages:
|
|||||||
shebang-command: 2.0.0
|
shebang-command: 2.0.0
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
|
|
||||||
|
/crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cssesc@3.0.0:
|
/cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -4720,6 +4785,23 @@ packages:
|
|||||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
/nostr-tools@1.17.0(typescript@5.4.5):
|
||||||
|
resolution: {integrity: sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=5.0.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@noble/ciphers': 0.2.0
|
||||||
|
'@noble/curves': 1.1.0
|
||||||
|
'@noble/hashes': 1.3.1
|
||||||
|
'@scure/base': 1.1.1
|
||||||
|
'@scure/bip32': 1.3.1
|
||||||
|
'@scure/bip39': 1.2.1
|
||||||
|
typescript: 5.4.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/nostr-tools@2.7.0(typescript@5.4.5):
|
/nostr-tools@2.7.0(typescript@5.4.5):
|
||||||
resolution: {integrity: sha512-jJoL2J1CBiKDxaXZww27nY/Wsuxzx7AULxmGKFce4sskDu1tohNyfnzYQ8BvDyvkstU8kNZUAXPL32tre33uig==}
|
resolution: {integrity: sha512-jJoL2J1CBiKDxaXZww27nY/Wsuxzx7AULxmGKFce4sskDu1tohNyfnzYQ8BvDyvkstU8kNZUAXPL32tre33uig==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5019,6 +5101,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/qrcode-generator@1.4.4:
|
||||||
|
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/queue-microtask@1.2.3:
|
/queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
@ -5921,6 +6007,14 @@ packages:
|
|||||||
tslib: 2.6.3
|
tslib: 2.6.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/use-sync-external-store@1.2.0(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/use-sync-external-store@1.2.2(react@18.3.1):
|
/use-sync-external-store@1.2.2(react@18.3.1):
|
||||||
resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==}
|
resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -6327,6 +6421,26 @@ packages:
|
|||||||
/zod@3.23.8:
|
/zod@3.23.8:
|
||||||
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
||||||
|
|
||||||
|
/zustand@4.5.2(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=16.8'
|
||||||
|
immer: '>=9.0.6'
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
use-sync-external-store: 1.2.0(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/zwitch@2.0.4:
|
/zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -3,6 +3,8 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use cocoa::{appkit::NSApp, base::nil, foundation::NSString};
|
use cocoa::{appkit::NSApp, base::nil, foundation::NSString};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use tauri::{LogicalPosition, LogicalSize, Manager, State, WebviewUrl};
|
use tauri::{LogicalPosition, LogicalSize, Manager, State, WebviewUrl};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tauri::TitleBarStyle;
|
use tauri::TitleBarStyle;
|
||||||
@ -14,6 +16,17 @@ use url::Url;
|
|||||||
|
|
||||||
use crate::Nostr;
|
use crate::Nostr;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Type)]
|
||||||
|
pub struct Window {
|
||||||
|
label: String,
|
||||||
|
title: String,
|
||||||
|
url: String,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
maximizable: bool,
|
||||||
|
minimizable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn create_column(
|
pub fn create_column(
|
||||||
@ -121,15 +134,8 @@ pub fn resize_column(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn open_window(
|
pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||||
label: &str,
|
if let Some(window) = app_handle.get_window(&window.label) {
|
||||||
title: &str,
|
|
||||||
url: &str,
|
|
||||||
width: f64,
|
|
||||||
height: f64,
|
|
||||||
app_handle: tauri::AppHandle,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
if let Some(window) = app_handle.get_window(label) {
|
|
||||||
if window.is_visible().unwrap_or_default() {
|
if window.is_visible().unwrap_or_default() {
|
||||||
let _ = window.set_focus();
|
let _ = window.set_focus();
|
||||||
} else {
|
} else {
|
||||||
@ -138,21 +144,27 @@ pub fn open_window(
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let window = WebviewWindowBuilder::new(&app_handle, label, WebviewUrl::App(PathBuf::from(url)))
|
let window = WebviewWindowBuilder::new(
|
||||||
.title(title)
|
&app_handle,
|
||||||
.min_inner_size(width, height)
|
&window.label,
|
||||||
.inner_size(width, height)
|
WebviewUrl::App(PathBuf::from(window.url)),
|
||||||
.hidden_title(true)
|
)
|
||||||
.title_bar_style(TitleBarStyle::Overlay)
|
.title(&window.title)
|
||||||
.transparent(true)
|
.min_inner_size(window.width, window.height)
|
||||||
.effects(WindowEffectsConfig {
|
.inner_size(window.width, window.height)
|
||||||
state: None,
|
.hidden_title(true)
|
||||||
effects: vec![Effect::WindowBackground],
|
.title_bar_style(TitleBarStyle::Overlay)
|
||||||
radius: None,
|
.transparent(true)
|
||||||
color: None,
|
.minimizable(window.minimizable)
|
||||||
})
|
.maximizable(window.maximizable)
|
||||||
.build()
|
.effects(WindowEffectsConfig {
|
||||||
.unwrap();
|
state: None,
|
||||||
|
effects: vec![Effect::WindowBackground],
|
||||||
|
radius: None,
|
||||||
|
color: None,
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let window = WebviewWindowBuilder::new(&app_handle, label, WebviewUrl::App(PathBuf::from(url)))
|
let window = WebviewWindowBuilder::new(&app_handle, label, WebviewUrl::App(PathBuf::from(url)))
|
||||||
@ -180,10 +192,6 @@ pub fn open_window(
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
// Create a custom titlebar for Windows
|
// Create a custom titlebar for Windows
|
||||||
window.create_overlay_titlebar().unwrap();
|
window.create_overlay_titlebar().unwrap();
|
||||||
|
|
||||||
// Set a custom inset to the traffic lights
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
window.set_traffic_lights_inset(8.0, 16.0).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -9,20 +9,20 @@ extern crate cocoa;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate objc;
|
extern crate objc;
|
||||||
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
io::{self, BufRead},
|
io::{self, BufRead},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
use tauri::{Manager, path::BaseDirectory};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tauri::tray::{MouseButtonState, TrayIconEvent};
|
use tauri::tray::{MouseButtonState, TrayIconEvent};
|
||||||
use tauri::{path::BaseDirectory, Manager};
|
|
||||||
use tauri_nspanel::ManagerExt;
|
use tauri_nspanel::ManagerExt;
|
||||||
use tauri_plugin_decorum::WebviewWindowExt;
|
use tauri_plugin_decorum::WebviewWindowExt;
|
||||||
|
|
||||||
@ -98,9 +98,9 @@ fn main() {
|
|||||||
nostr::metadata::toggle_contact,
|
nostr::metadata::toggle_contact,
|
||||||
nostr::metadata::get_nstore,
|
nostr::metadata::get_nstore,
|
||||||
nostr::metadata::set_nstore,
|
nostr::metadata::set_nstore,
|
||||||
nostr::metadata::set_nwc,
|
nostr::metadata::set_wallet,
|
||||||
nostr::metadata::load_nwc,
|
nostr::metadata::load_wallet,
|
||||||
nostr::metadata::get_balance,
|
nostr::metadata::remove_wallet,
|
||||||
nostr::metadata::zap_profile,
|
nostr::metadata::zap_profile,
|
||||||
nostr::metadata::zap_event,
|
nostr::metadata::zap_event,
|
||||||
nostr::metadata::friend_to_friend,
|
nostr::metadata::friend_to_friend,
|
||||||
|
@ -310,12 +310,12 @@ pub async fn get_nstore(key: &str, state: State<'_, Nostr>) -> Result<String, St
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn set_nwc(uri: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
if let Ok(nwc_uri) = NostrWalletConnectURI::from_str(uri) {
|
if let Ok(nwc_uri) = NostrWalletConnectURI::from_str(uri) {
|
||||||
let nwc = NWC::new(nwc_uri);
|
let nwc = NWC::new(nwc_uri);
|
||||||
let keyring = Entry::new("Lume Secret Storage", "NWC").map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Secret", "Bitcoin Connect").map_err(|e| e.to_string())?;
|
||||||
keyring.set_password(uri).map_err(|e| e.to_string())?;
|
keyring.set_password(uri).map_err(|e| e.to_string())?;
|
||||||
client.set_zapper(nwc).await;
|
client.set_zapper(nwc).await;
|
||||||
|
|
||||||
@ -327,38 +327,42 @@ pub async fn set_nwc(uri: &str, state: State<'_, Nostr>) -> Result<bool, String>
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn load_nwc(state: State<'_, Nostr>) -> Result<bool, String> {
|
pub async fn load_wallet(state: State<'_, Nostr>) -> Result<String, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let keyring = Entry::new("Lume Secret Storage", "NWC").map_err(|e| e.to_string())?;
|
let keyring = Entry::new("Lume Secret", "Bitcoin Connect").unwrap();
|
||||||
|
|
||||||
match keyring.get_password() {
|
match keyring.get_password() {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
let uri = NostrWalletConnectURI::from_str(&val).map_err(|e| e.to_string())?;
|
let uri = NostrWalletConnectURI::from_str(&val).unwrap();
|
||||||
let nwc = NWC::new(uri);
|
let nwc = NWC::new(uri);
|
||||||
|
|
||||||
|
// Get current balance
|
||||||
|
let balance = nwc.get_balance().await;
|
||||||
|
|
||||||
|
// Update zapper
|
||||||
client.set_zapper(nwc).await;
|
client.set_zapper(nwc).await;
|
||||||
|
|
||||||
Ok(true)
|
match balance {
|
||||||
|
Ok(val) => Ok(val.to_string()),
|
||||||
|
Err(_) => Err("Get balance failed.".into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => Ok(false),
|
Err(_) => Err("NWC not found.".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_balance() -> Result<String, String> {
|
pub async fn remove_wallet(state: State<'_, Nostr>) -> Result<(), ()> {
|
||||||
let keyring = Entry::new("Lume Secret Storage", "NWC").map_err(|e| e.to_string())?;
|
let client = &state.client;
|
||||||
|
let keyring = Entry::new("Lume Secret", "Bitcoin Connect").unwrap();
|
||||||
|
|
||||||
match keyring.get_password() {
|
match keyring.delete_password() {
|
||||||
Ok(val) => {
|
Ok(_) => {
|
||||||
let uri = NostrWalletConnectURI::from_str(&val).map_err(|e| e.to_string())?;
|
client.unset_zapper().await;
|
||||||
let nwc = NWC::new(uri);
|
Ok(())
|
||||||
nwc
|
|
||||||
.get_balance()
|
|
||||||
.await
|
|
||||||
.map(|balance| balance.to_string())
|
|
||||||
.map_err(|_| "Get balance failed".into())
|
|
||||||
}
|
}
|
||||||
Err(_) => Err("Something wrong".into()),
|
Err(_) => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,32 +375,28 @@ pub async fn zap_profile(
|
|||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_key: Option<PublicKey> = match Nip19::from_bech32(id) {
|
let public_key = match Nip19::from_bech32(id) {
|
||||||
Ok(val) => match val {
|
Ok(val) => match val {
|
||||||
Nip19::Pubkey(key) => Some(key),
|
Nip19::Pubkey(key) => key,
|
||||||
Nip19::Profile(profile) => Some(profile.public_key),
|
Nip19::Profile(profile) => profile.public_key,
|
||||||
_ => None,
|
_ => return Err("Public Key is not valid.".into()),
|
||||||
},
|
},
|
||||||
Err(_) => match PublicKey::from_str(id) {
|
Err(_) => match PublicKey::from_str(id) {
|
||||||
Ok(val) => Some(val),
|
Ok(val) => val,
|
||||||
Err(_) => None,
|
Err(_) => return Err("Public Key is not valid.".into()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(recipient) = public_key {
|
let details = ZapDetails::new(ZapType::Private).message(message);
|
||||||
let details = ZapDetails::new(ZapType::Public).message(message);
|
let num = match amount.parse::<u64>() {
|
||||||
let num = match amount.parse::<u64>() {
|
Ok(val) => val,
|
||||||
Ok(val) => val,
|
Err(_) => return Err("Invalid amount.".into()),
|
||||||
Err(_) => return Err("Invalid amount.".into()),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if client.zap(recipient, num, Some(details)).await.is_ok() {
|
if client.zap(public_key, num, Some(details)).await.is_ok() {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else {
|
|
||||||
Err("Zap profile failed".into())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Err("Parse public key failed".into())
|
Err("Zap profile failed".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,32 +409,28 @@ pub async fn zap_event(
|
|||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let event_id: Option<EventId> = match Nip19::from_bech32(id) {
|
let event_id = match Nip19::from_bech32(id) {
|
||||||
Ok(val) => match val {
|
Ok(val) => match val {
|
||||||
Nip19::EventId(id) => Some(id),
|
Nip19::EventId(id) => id,
|
||||||
Nip19::Event(event) => Some(event.event_id),
|
Nip19::Event(event) => event.event_id,
|
||||||
_ => None,
|
_ => return Err("Event ID is invalid.".into()),
|
||||||
},
|
},
|
||||||
Err(_) => match EventId::from_hex(id) {
|
Err(_) => match EventId::from_hex(id) {
|
||||||
Ok(val) => Some(val),
|
Ok(val) => val,
|
||||||
Err(_) => None,
|
Err(_) => return Err("Event ID is invalid.".into()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(recipient) = event_id {
|
let details = ZapDetails::new(ZapType::Private).message(message);
|
||||||
let details = ZapDetails::new(ZapType::Public).message(message);
|
let num = match amount.parse::<u64>() {
|
||||||
let num = match amount.parse::<u64>() {
|
Ok(val) => val,
|
||||||
Ok(val) => val,
|
Err(_) => return Err("Invalid amount.".into()),
|
||||||
Err(_) => return Err("Invalid amount.".into()),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if client.zap(recipient, num, Some(details)).await.is_ok() {
|
if client.zap(event_id, num, Some(details)).await.is_ok() {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else {
|
|
||||||
Err("Zap event failed".into())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Err("Parse event ID failed".into())
|
Err("Zap event failed".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user