mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-17 06:14:21 +01:00
Mutiny Address
use check_lnurl_name update to rc1 use env vars set nip05 if not set update to rc2 public zaps use privacy_level in activity item get profile from nostr if not a contact rc3, update env vars and use them for domain async nostr things
This commit is contained in:
committed by
Paul Miller
parent
bdb0611e81
commit
c313debd34
@@ -18,3 +18,5 @@ VITE_STORAGE="https://storage-staging.mutinywallet.com/v2"
|
||||
VITE_FEEDBACK="https://feedback-staging.mutinywallet.com"
|
||||
VITE_SCORER="https://scorer-staging.mutinywallet.com"
|
||||
VITE_PRIMAL="https://primal-cache.mutinywallet.com/api"
|
||||
VITE_BLIND_AUTH="https://blind-auth-staging.mutinywallet.com"
|
||||
VITE_HERMES="https://hermes-blinded-staging.fly.dev"
|
||||
|
||||
@@ -12,3 +12,5 @@ VITE_STORAGE="https://storage.mutinywallet.com/v2"
|
||||
VITE_FEEDBACK="https://feedback.mutinywallet.com"
|
||||
VITE_SCORER="https://scorer.mutinywallet.com"
|
||||
VITE_PRIMAL="https://primal-cache.mutinywallet.com/api"
|
||||
VITE_BLIND_AUTH="https://blind-auth.mutinywallet.com"
|
||||
VITE_HERMES="https://mutiny.plus"
|
||||
@@ -12,3 +12,5 @@ VITE_STORAGE="https://storage-staging.mutinywallet.com/v2"
|
||||
VITE_FEEDBACK="https://feedback-staging.mutinywallet.com"
|
||||
VITE_SCORER="https://scorer-staging.mutinywallet.com"
|
||||
VITE_PRIMAL="https://primal-cache.mutinywallet.com/api"
|
||||
VITE_BLIND_AUTH="https://blind-auth-staging.mutinywallet.com"
|
||||
VITE_HERMES="https://signet.mutiny.plus"
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"@capacitor/toast": "^5.0.6",
|
||||
"@kobalte/core": "^0.12.6",
|
||||
"@kobalte/tailwindcss": "^0.9.0",
|
||||
"@mutinywallet/mutiny-wasm": "0.6.1",
|
||||
"@mutinywallet/mutiny-wasm": "0.6.2-rc3",
|
||||
"@modular-forms/solid": "^0.20.0",
|
||||
"@solid-primitives/upload": "^0.0.117",
|
||||
"@solidjs/meta": "^0.29.3",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -51,8 +51,8 @@ dependencies:
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.0(solid-js@1.8.16)
|
||||
'@mutinywallet/mutiny-wasm':
|
||||
specifier: 0.6.1
|
||||
version: 0.6.1
|
||||
specifier: 0.6.2-rc3
|
||||
version: 0.6.2-rc3
|
||||
'@solid-primitives/upload':
|
||||
specifier: ^0.0.117
|
||||
version: 0.0.117(solid-js@1.8.16)
|
||||
@@ -2085,8 +2085,8 @@ packages:
|
||||
solid-js: 1.8.16
|
||||
dev: false
|
||||
|
||||
/@mutinywallet/mutiny-wasm@0.6.1:
|
||||
resolution: {integrity: sha512-z4v7kxqxPwa34lji2HgfzZF3xLwl/bAMy18klXb+uBhEI2/G3LHm+chphNuMxHq+YYsKEbFD+1QGfbpskj1z7Q==}
|
||||
/@mutinywallet/mutiny-wasm@0.6.2-rc3:
|
||||
resolution: {integrity: sha512-/FxSLHuMxIuXYs2C6KbMNzBuGeSHri+BkIrtuJFI1JWG3+XcSiJF4HU2mqAwaZQH9jg25x+LvY71aZ7pM3tM/w==}
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir@2.1.5:
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
"edit": {
|
||||
"nym": "Nym"
|
||||
},
|
||||
"deleted": "Your nostr profile has been deleted."
|
||||
"deleted": "Your nostr profile has been deleted.",
|
||||
"no_lightning_address": "No lightning address set"
|
||||
},
|
||||
"chat": {
|
||||
"prompt": "This is a new conversation. Try asking for money!",
|
||||
@@ -189,7 +190,8 @@
|
||||
"payment_pending_description": "It's taking a while, but it's possible this payment may still go through. Please check 'Activity' for the current status.",
|
||||
"hodl_invoice_warning": "This is a hodl invoice. Payments to hodl invoices can cause channel force closes, which results in high on-chain fees. Pay at your own risk!",
|
||||
"private": "Private",
|
||||
"anonzap": "Anon Zap"
|
||||
"publiczap": "Public Zap",
|
||||
"privatezap": "Private Zap"
|
||||
},
|
||||
"feedback": {
|
||||
"header": "Give us feedback!",
|
||||
@@ -600,6 +602,11 @@
|
||||
"unlink_account": "Unlink nostr profile",
|
||||
"unlink_account_confirm": "Unlinking your nostr profile will reset your wallet to its default nostr profile.",
|
||||
"delete_account_confirm_scary": "THIS PROFILE WILL BE DELETED ACROSS ALL NOSTR APPS."
|
||||
},
|
||||
"lightning_address": {
|
||||
"title": "Mutiny Address",
|
||||
"description": "Get zaps and payments to your wallet with your own lightning address.",
|
||||
"create": "Create Mutiny Address"
|
||||
}
|
||||
},
|
||||
"swap": {
|
||||
|
||||
@@ -5,8 +5,14 @@ import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
|
||||
|
||||
import { ActivityDetailsModal, ButtonCard, NiceP } from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { PrivacyLevel } from "~/routes";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { timeAgo } from "~/utils";
|
||||
import {
|
||||
actuallyFetchNostrProfile,
|
||||
hexpubFromNpub,
|
||||
profileToPseudoContact,
|
||||
timeAgo
|
||||
} from "~/utils";
|
||||
|
||||
import { GenericItem } from "./GenericItem";
|
||||
|
||||
@@ -24,6 +30,7 @@ export interface IActivityItem {
|
||||
labels: string[];
|
||||
contacts: TagItem[];
|
||||
last_updated: number;
|
||||
privacy_level: PrivacyLevel;
|
||||
}
|
||||
|
||||
export function UnifiedActivityItem(props: {
|
||||
@@ -46,10 +53,40 @@ export function UnifiedActivityItem(props: {
|
||||
return props.item.contacts[0];
|
||||
};
|
||||
|
||||
const profileFromNostr = createAsync(async () => {
|
||||
if (props.item.contacts.length === 0) {
|
||||
if (props.item.labels) {
|
||||
const npub = props.item.labels.find((l) =>
|
||||
l.startsWith("npub")
|
||||
);
|
||||
if (npub) {
|
||||
try {
|
||||
const hexpub = await hexpubFromNpub(npub);
|
||||
if (!hexpub) {
|
||||
return undefined;
|
||||
}
|
||||
const profile = await actuallyFetchNostrProfile(hexpub);
|
||||
if (!profile) {
|
||||
return undefined;
|
||||
}
|
||||
const pseudoContact = profileToPseudoContact(profile);
|
||||
return pseudoContact;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// TODO: figure out what other shit we should filter out
|
||||
const message = () => {
|
||||
const filtered = props.item.labels.filter(
|
||||
(l) => l !== "SWAP" && !l.startsWith("LN Channel:")
|
||||
(l) =>
|
||||
l !== "SWAP" &&
|
||||
!l.startsWith("LN Channel:") &&
|
||||
!l.startsWith("npub")
|
||||
);
|
||||
if (filtered.length === 0) {
|
||||
return undefined;
|
||||
@@ -119,17 +156,23 @@ export function UnifiedActivityItem(props: {
|
||||
return (
|
||||
<div class="pt-3 first-of-type:pt-0">
|
||||
<GenericItem
|
||||
primaryAvatarUrl={primaryContact()?.image_url || ""}
|
||||
primaryAvatarUrl={
|
||||
primaryContact()?.image_url
|
||||
? primaryContact()?.image_url
|
||||
: profileFromNostr()?.primal_image_url || ""
|
||||
}
|
||||
icon={shouldShowShuffle() ? <Shuffle /> : undefined}
|
||||
primaryOnClick={() =>
|
||||
primaryName() !== "You" && primaryContact()?.id
|
||||
primaryContact()?.id
|
||||
? navigate(`/chat/${primaryContact()?.id}`)
|
||||
: undefined
|
||||
}
|
||||
amountOnClick={click}
|
||||
primaryName={
|
||||
props.item.inbound
|
||||
? primaryContact()?.name || "Unknown"
|
||||
? primaryContact()?.name
|
||||
? primaryContact()!.name
|
||||
: profileFromNostr()?.name || "Unknown"
|
||||
: "You"
|
||||
}
|
||||
genericAvatar={shouldShowGeneric()}
|
||||
@@ -144,7 +187,7 @@ export function UnifiedActivityItem(props: {
|
||||
date={timeAgo(props.item.last_updated)}
|
||||
accent={props.item.inbound ? "green" : undefined}
|
||||
visibility={
|
||||
props.item.kind === "Lightning" ? "private" : undefined
|
||||
props.item.privacy_level === "Public" ? "public" : "private"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
59
src/components/LightningAddressShower.tsx
Normal file
59
src/components/LightningAddressShower.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Copy, QrCode } from "lucide-solid";
|
||||
import { createSignal, Match, Switch } from "solid-js";
|
||||
import { QRCodeSVG } from "solid-qr-code";
|
||||
|
||||
import { FancyCard, SimpleDialog } from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { useCopy } from "~/utils";
|
||||
|
||||
export function LightningAddressShower(props: { lud16: string }) {
|
||||
const i18n = useI18n();
|
||||
const [showQr, setShowQr] = createSignal(false);
|
||||
|
||||
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||
|
||||
return (
|
||||
<FancyCard>
|
||||
<Switch>
|
||||
<Match when={props.lud16}>
|
||||
<p class="break-all text-center font-system-mono text-base ">
|
||||
{props.lud16}
|
||||
</p>
|
||||
<div class="flex w-full justify-center gap-8">
|
||||
<button onClick={() => setShowQr(true)}>
|
||||
<QrCode class="inline-block" />
|
||||
</button>
|
||||
<button
|
||||
class="p-1"
|
||||
classList={{
|
||||
"bg-m-red rounded": copied()
|
||||
}}
|
||||
onClick={() => copy(props.lud16)}
|
||||
>
|
||||
<Copy class="inline-block" />
|
||||
</button>
|
||||
</div>{" "}
|
||||
<SimpleDialog
|
||||
open={showQr()}
|
||||
setOpen={(open) => {
|
||||
setShowQr(open);
|
||||
}}
|
||||
title={"Lightning Address"}
|
||||
>
|
||||
<div class="w-[10rem] self-center rounded bg-white p-[1rem]">
|
||||
<QRCodeSVG
|
||||
value={"lightning:" + props.lud16 || ""}
|
||||
class="h-full max-h-[256px] w-full"
|
||||
/>
|
||||
</div>
|
||||
</SimpleDialog>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<p class="text-center text-base italic text-m-grey-350">
|
||||
{i18n.t("profile.no_lightning_address")}
|
||||
</p>
|
||||
</Match>
|
||||
</Switch>
|
||||
</FancyCard>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { createAsync, useNavigate } from "@solidjs/router";
|
||||
import { Search } from "lucide-solid";
|
||||
import {
|
||||
createEffect,
|
||||
@@ -40,10 +40,9 @@ export function NostrActivity() {
|
||||
const i18n = useI18n();
|
||||
const [state, _actions] = useMegaStore();
|
||||
|
||||
const [data, { refetch }] = createResource(
|
||||
state.mutiny_wallet?.get_npub(),
|
||||
fetchZaps
|
||||
);
|
||||
const npub = createAsync(async () => state.mutiny_wallet?.get_npub());
|
||||
|
||||
const [data, { refetch }] = createResource(npub, fetchZaps);
|
||||
|
||||
function nameFromHexpub(hexpub: string): string {
|
||||
const profile = data.latest?.profiles[hexpub];
|
||||
|
||||
@@ -56,3 +56,4 @@ export * from "./GenericItem";
|
||||
export * from "./HomeBalance";
|
||||
export * from "./EditProfileForm";
|
||||
export * from "./ImportNsecForm";
|
||||
export * from "./LightningAddressShower";
|
||||
|
||||
@@ -17,6 +17,8 @@ export type MutinyWalletSettingStrings = {
|
||||
scorer?: string;
|
||||
selfhosted?: string;
|
||||
primal_api?: string;
|
||||
blind_auth?: string;
|
||||
hermes?: string;
|
||||
};
|
||||
|
||||
const SETTINGS_KEYS = [
|
||||
@@ -84,6 +86,16 @@ const SETTINGS_KEYS = [
|
||||
name: "primal_api",
|
||||
storageKey: "USER_SETTINGS_primal_api",
|
||||
default: import.meta.env.VITE_PRIMAL
|
||||
},
|
||||
{
|
||||
name: "blind_auth",
|
||||
storageKey: "USER_SETTINGS_blind_auth",
|
||||
default: import.meta.env.VITE_BLIND_AUTH
|
||||
},
|
||||
{
|
||||
name: "hermes",
|
||||
storageKey: "USER_SETTINGS_hermes",
|
||||
default: import.meta.env.VITE_HERMES
|
||||
}
|
||||
];
|
||||
|
||||
@@ -252,7 +264,9 @@ export async function setupMutinyWallet(
|
||||
subscriptions,
|
||||
storage,
|
||||
scorer,
|
||||
primal_api
|
||||
primal_api,
|
||||
blind_auth,
|
||||
hermes
|
||||
} = settings;
|
||||
|
||||
let nsec;
|
||||
@@ -290,6 +304,8 @@ export async function setupMutinyWallet(
|
||||
console.log("Using storage address", storage);
|
||||
console.log("Using scorer address", scorer);
|
||||
console.log("Using primal api", primal_api);
|
||||
console.log("Using blind auth", blind_auth);
|
||||
console.log("Using hermes", hermes);
|
||||
console.log(safeMode ? "Safe mode enabled" : "Safe mode disabled");
|
||||
console.log(shouldZapHodl ? "Hodl zaps enabled" : "Hodl zaps disabled");
|
||||
|
||||
@@ -325,7 +341,11 @@ export async function setupMutinyWallet(
|
||||
// Nip7
|
||||
extension_key ? extension_key : undefined,
|
||||
// primal URL
|
||||
primal_api || "https://primal-cache.mutinywallet.com/api"
|
||||
primal_api || "https://primal-cache.mutinywallet.com/api",
|
||||
/// blind auth url
|
||||
blind_auth,
|
||||
/// hermes url
|
||||
hermes
|
||||
);
|
||||
|
||||
sessionStorage.setItem("MUTINY_WALLET_INITIALIZED", Date.now().toString());
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
Gift,
|
||||
ImportProfileSettings,
|
||||
Language,
|
||||
LightningAddress,
|
||||
ManageFederations,
|
||||
NostrKeys,
|
||||
Plus,
|
||||
@@ -193,6 +194,7 @@ export function Router() {
|
||||
component={ImportProfileSettings}
|
||||
/>
|
||||
<Route path="/federations" component={ManageFederations} />
|
||||
<Route path="/lightningaddress" component={LightningAddress} />
|
||||
</Route>
|
||||
<Route path="/*all" component={NotFound} />
|
||||
</SolidRouter>
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { Copy, Edit, Import, QrCode } from "lucide-solid";
|
||||
import { createMemo, createSignal, Match, Show, Switch } from "solid-js";
|
||||
import { QRCodeSVG } from "solid-qr-code";
|
||||
import { AtSign, Edit, Import } from "lucide-solid";
|
||||
import { createMemo, Show } from "solid-js";
|
||||
|
||||
import {
|
||||
BackLink,
|
||||
BalanceBox,
|
||||
ButtonCard,
|
||||
DefaultMain,
|
||||
FancyCard,
|
||||
LabelCircle,
|
||||
LightningAddressShower,
|
||||
MutinyWalletGuard,
|
||||
NavBar,
|
||||
NiceP,
|
||||
SimpleDialog
|
||||
NiceP
|
||||
} from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { DEFAULT_NOSTR_NAME, useCopy } from "~/utils";
|
||||
import { DEFAULT_NOSTR_NAME } from "~/utils";
|
||||
|
||||
export function Profile() {
|
||||
const [state, _actions] = useMegaStore();
|
||||
@@ -28,8 +26,6 @@ export function Profile() {
|
||||
const profile = createMemo(() => {
|
||||
const profile = state.mutiny_wallet?.get_nostr_profile();
|
||||
|
||||
console.log("profile", profile);
|
||||
|
||||
return {
|
||||
name: profile?.display_name || profile?.name || DEFAULT_NOSTR_NAME,
|
||||
picture: profile?.picture || undefined,
|
||||
@@ -38,14 +34,25 @@ export function Profile() {
|
||||
};
|
||||
});
|
||||
|
||||
const [showQr, setShowQr] = createSignal(false);
|
||||
|
||||
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||
|
||||
const profileDeleted = createMemo(() => {
|
||||
return profile().deleted === true || profile().deleted === "true";
|
||||
});
|
||||
|
||||
const hasMutinyAddress = createMemo(() => {
|
||||
if (profile().lud16) {
|
||||
const hermes = import.meta.env.VITE_HERMES;
|
||||
if (!hermes) {
|
||||
return false;
|
||||
}
|
||||
const hermesDoman = new URL(hermes).hostname;
|
||||
const afterAt = profile().lud16.split("@")[1];
|
||||
if (afterAt && afterAt.includes(hermesDoman)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return (
|
||||
<MutinyWalletGuard>
|
||||
<DefaultMain>
|
||||
@@ -62,53 +69,7 @@ export function Profile() {
|
||||
<Show when={profile().name}>{profile().name}</Show>
|
||||
</h1>
|
||||
|
||||
<FancyCard>
|
||||
<Switch>
|
||||
<Match when={profile().lud16}>
|
||||
<p class="break-all text-center font-system-mono text-base ">
|
||||
{profile().lud16}
|
||||
</p>
|
||||
<div class="flex w-full justify-center gap-8">
|
||||
<button onClick={() => setShowQr(true)}>
|
||||
<QrCode class="inline-block" />
|
||||
</button>
|
||||
<button
|
||||
class="p-1"
|
||||
classList={{
|
||||
"bg-m-red rounded": copied()
|
||||
}}
|
||||
onClick={() =>
|
||||
copy(profile().lud16)
|
||||
}
|
||||
>
|
||||
<Copy class="inline-block" />
|
||||
</button>
|
||||
</div>{" "}
|
||||
<SimpleDialog
|
||||
open={showQr()}
|
||||
setOpen={(open) => {
|
||||
setShowQr(open);
|
||||
}}
|
||||
title={"Lightning Address"}
|
||||
>
|
||||
<div class="w-[10rem] self-center rounded bg-white p-[1rem]">
|
||||
<QRCodeSVG
|
||||
value={
|
||||
"lightning:" +
|
||||
profile().lud16 || ""
|
||||
}
|
||||
class="h-full max-h-[256px] w-full"
|
||||
/>
|
||||
</div>
|
||||
</SimpleDialog>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<p class="text-center text-base italic text-m-grey-350">
|
||||
No Lightning Address set
|
||||
</p>
|
||||
</Match>
|
||||
</Switch>
|
||||
</FancyCard>
|
||||
<LightningAddressShower lud16={profile().lud16} />
|
||||
</div>
|
||||
<ButtonCard onClick={() => navigate("/editprofile")}>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -117,6 +78,24 @@ export function Profile() {
|
||||
<NiceP>{i18n.t("profile.edit_profile")}</NiceP>
|
||||
</div>
|
||||
</ButtonCard>
|
||||
<Show
|
||||
when={state.federations?.length && !hasMutinyAddress()}
|
||||
>
|
||||
<ButtonCard
|
||||
onClick={() =>
|
||||
navigate("/settings/lightningaddress")
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<AtSign class="inline-block text-m-red" />
|
||||
<NiceP>
|
||||
{i18n.t(
|
||||
"settings.lightning_address.create"
|
||||
)}
|
||||
</NiceP>
|
||||
</div>
|
||||
</ButtonCard>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={profile() && profile().deleted}>
|
||||
<ButtonCard
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MutinyInvoice, TagItem } from "@mutinywallet/mutiny-wasm";
|
||||
import { useLocation, useNavigate, useSearchParams } from "@solidjs/router";
|
||||
import { EyeOff, Link, X, Zap } from "lucide-solid";
|
||||
import { Eye, EyeOff, Link, X, Zap } from "lucide-solid";
|
||||
import {
|
||||
createEffect,
|
||||
createMemo,
|
||||
@@ -48,6 +48,7 @@ import { useMegaStore } from "~/state/megaStore";
|
||||
import { eify, vibrateSuccess } from "~/utils";
|
||||
|
||||
export type SendSource = "lightning" | "onchain";
|
||||
export type PrivacyLevel = "Public" | "Private" | "Anonymous" | "Not Available";
|
||||
|
||||
// const TEST_DEST = "bitcoin:tb1pdh43en28jmhnsrhxkusja46aufdlae5qnfrhucw5jvefw9flce3sdxfcwe?amount=0.00001&label=heyo&lightning=lntbs10u1pjrwrdedq8dpjhjmcnp4qd60w268ve0jencwzhz048ruprkxefhj0va2uspgj4q42azdg89uupp5gngy2pqte5q5uvnwcxwl2t8fsdlla5s6xl8aar4xcsvxeus2w2pqsp5n5jp3pz3vpu92p3uswttxmw79a5lc566herwh3f2amwz2sp6f9tq9qyysgqcqpcxqrpwugv5m534ww5ukcf6sdw2m75f2ntjfh3gzeqay649256yvtecgnhjyugf74zakaf56sdh66ec9fqep2kvu6xv09gcwkv36rrkm38ylqsgpw3yfjl"
|
||||
// const TEST_DEST_ADDRESS = "tb1pdh43en28jmhnsrhxkusja46aufdlae5qnfrhucw5jvefw9flce3sdxfcwe"
|
||||
@@ -505,10 +506,9 @@ export function Send() {
|
||||
}
|
||||
} else if (source() === "lightning" && lnurlp()) {
|
||||
const zapNpub =
|
||||
visibility() === "anonzap" && contact()?.npub
|
||||
visibility() !== "Not Available" && contact()?.npub
|
||||
? contact()?.npub
|
||||
: undefined;
|
||||
console.log("zapnpub", zapNpub);
|
||||
const comment = zapNpub ? whatForInput() : undefined;
|
||||
const payment = await state.mutiny_wallet?.lnurl_pay(
|
||||
lnurlp()!,
|
||||
@@ -516,7 +516,7 @@ export function Send() {
|
||||
zapNpub, // zap_npub
|
||||
tags,
|
||||
comment, // comment
|
||||
zapNpub ? "Anonymous" : undefined
|
||||
visibility()
|
||||
);
|
||||
sentDetails.payment_hash = payment?.payment_hash;
|
||||
|
||||
@@ -645,15 +645,16 @@ export function Send() {
|
||||
}
|
||||
});
|
||||
|
||||
const [visibility, setVisibility] = createSignal<"private" | "anonzap">(
|
||||
"private"
|
||||
);
|
||||
const [visibility, setVisibility] =
|
||||
createSignal<PrivacyLevel>("Not Available");
|
||||
|
||||
function toggleVisibility() {
|
||||
if (visibility() === "private") {
|
||||
setVisibility("anonzap");
|
||||
if (visibility() === "Not Available") {
|
||||
setVisibility("Private");
|
||||
} else if (visibility() === "Private") {
|
||||
setVisibility("Public");
|
||||
} else {
|
||||
setVisibility("private");
|
||||
setVisibility("Not Available");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +828,8 @@ export function Send() {
|
||||
<Switch>
|
||||
<Match
|
||||
when={
|
||||
visibility() === "private"
|
||||
visibility() ===
|
||||
"Not Available"
|
||||
}
|
||||
>
|
||||
<EyeOff class="h-4 w-4" />
|
||||
@@ -837,12 +839,22 @@ export function Send() {
|
||||
</Match>
|
||||
<Match
|
||||
when={
|
||||
visibility() === "anonzap"
|
||||
visibility() === "Private"
|
||||
}
|
||||
>
|
||||
<Zap class="h-4 w-4" />
|
||||
<EyeOff class="h-4 w-4" />
|
||||
<span>
|
||||
{i18n.t("send.anonzap")}
|
||||
{i18n.t("send.privatezap")}
|
||||
</span>
|
||||
</Match>
|
||||
<Match
|
||||
when={visibility() === "Public"}
|
||||
>
|
||||
<Zap class="h-4 w-4" />
|
||||
<Eye class="h-4 w-4" />
|
||||
<span>
|
||||
{i18n.t("send.publiczap")}
|
||||
</span>
|
||||
</Match>
|
||||
</Switch>
|
||||
@@ -861,7 +873,7 @@ export function Send() {
|
||||
<SimpleInput
|
||||
type="text"
|
||||
placeholder={
|
||||
visibility() === "private"
|
||||
visibility() === "Not Available"
|
||||
? i18n.t("send.what_for")
|
||||
: i18n.t("send.zap_note")
|
||||
}
|
||||
|
||||
282
src/routes/settings/LightningAddress.tsx
Normal file
282
src/routes/settings/LightningAddress.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import {
|
||||
createForm,
|
||||
required,
|
||||
reset,
|
||||
SubmitHandler
|
||||
} from "@modular-forms/solid";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { Users } from "lucide-solid";
|
||||
import {
|
||||
createMemo,
|
||||
createResource,
|
||||
createSignal,
|
||||
Match,
|
||||
Show,
|
||||
Suspense,
|
||||
Switch
|
||||
} from "solid-js";
|
||||
|
||||
import {
|
||||
BackPop,
|
||||
Button,
|
||||
ButtonCard,
|
||||
DefaultMain,
|
||||
InfoBox,
|
||||
LargeHeader,
|
||||
LightningAddressShower,
|
||||
MutinyPlusCta,
|
||||
MutinyWalletGuard,
|
||||
NavBar,
|
||||
NiceP,
|
||||
TextField,
|
||||
VStack
|
||||
} from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { eify } from "~/utils";
|
||||
|
||||
type HermesForm = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
// todo(paul) put this somewhere else
|
||||
function HermesForm(props: { onSubmit: (name: string) => void }) {
|
||||
const [state, _] = useMegaStore();
|
||||
const [error, setError] = createSignal<Error>();
|
||||
const [success, setSuccess] = createSignal("");
|
||||
|
||||
const [nameForm, { Form, Field }] = createForm<HermesForm>({
|
||||
initialValues: {
|
||||
name: ""
|
||||
}
|
||||
});
|
||||
|
||||
const network = state.mutiny_wallet?.get_network() || "signet";
|
||||
|
||||
const handleSubmit: SubmitHandler<HermesForm> = async (f: HermesForm) => {
|
||||
setSuccess("");
|
||||
setError(undefined);
|
||||
try {
|
||||
const name = f.name.trim();
|
||||
const available =
|
||||
await state.mutiny_wallet?.check_available_lnurl_name(name);
|
||||
if (!available) {
|
||||
throw new Error("Name already taken");
|
||||
}
|
||||
await state.mutiny_wallet?.reserve_lnurl_name(name);
|
||||
console.log("lnurl name reserved:", name);
|
||||
|
||||
const hermes = import.meta.env.VITE_HERMES;
|
||||
if (!hermes) {
|
||||
throw new Error("Hermes not configured");
|
||||
}
|
||||
const hermesDoman = new URL(hermes).hostname;
|
||||
|
||||
const formattedName = `${name}${hermesDoman}`;
|
||||
|
||||
const existingProfile = state.mutiny_wallet?.get_nostr_profile();
|
||||
|
||||
const _ = await state.mutiny_wallet?.edit_nostr_profile(
|
||||
undefined,
|
||||
undefined,
|
||||
// lnurl
|
||||
formattedName,
|
||||
// nip05 if they don't have one
|
||||
existingProfile?.nip05 ? undefined : formattedName
|
||||
);
|
||||
reset(nameForm);
|
||||
props.onSubmit(name);
|
||||
} catch (e) {
|
||||
console.error("Error reserving name:", e);
|
||||
setError(eify(e));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<VStack>
|
||||
<Field name="name" validate={[required("Must not be empty")]}>
|
||||
{(field, props) => (
|
||||
<div class="flex w-full flex-1 gap-2">
|
||||
<div class="flex-1">
|
||||
<TextField
|
||||
{...props}
|
||||
{...field}
|
||||
error={field.error}
|
||||
label={"Nym"}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-0 self-end pb-2 text-2xl text-m-grey-350">
|
||||
<Show
|
||||
when={network === "signet"}
|
||||
fallback={"@mutiny.plus"}
|
||||
>
|
||||
@signet.mutiny.plus
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Button
|
||||
loading={nameForm.submitting}
|
||||
disabled={nameForm.invalid}
|
||||
intent="blue"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Show when={error()}>
|
||||
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||
</Show>
|
||||
<Show when={success()}>
|
||||
<InfoBox accent="green">{success()}</InfoBox>
|
||||
</Show>
|
||||
</VStack>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export function LightningAddress() {
|
||||
const i18n = useI18n();
|
||||
const [state, _actions] = useMegaStore();
|
||||
const navigate = useNavigate();
|
||||
const [error, setError] = createSignal<Error>();
|
||||
const [settingLnAddress, setSettingLnAddress] = createSignal(false);
|
||||
|
||||
// TODO: should be able to ask mutiny-node for this
|
||||
const [lnurlName, { refetch }] = createResource(async () => {
|
||||
try {
|
||||
const name = await state.mutiny_wallet?.check_lnurl_name();
|
||||
return name;
|
||||
} catch (e) {
|
||||
setError(eify(e));
|
||||
}
|
||||
});
|
||||
const ios = Capacitor.getPlatform() === "ios";
|
||||
// const ios = true;
|
||||
|
||||
const network = state.mutiny_wallet?.get_network() || "signet";
|
||||
|
||||
const formattedLnAddress = createMemo(() => {
|
||||
const name = lnurlName();
|
||||
if (name) {
|
||||
const suffix =
|
||||
network === "signet" ? "@signet.mutiny.plus" : "@mutiny.plus";
|
||||
return `${lnurlName()}${suffix}`;
|
||||
}
|
||||
});
|
||||
|
||||
const profileLnAddress = createMemo(() => {
|
||||
if (lnurlName()) {
|
||||
const profile = state.mutiny_wallet?.get_nostr_profile();
|
||||
if (profile?.lud16) {
|
||||
return profile.lud16;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function setLnAddress(newAddress: string) {
|
||||
if (!newAddress) return;
|
||||
try {
|
||||
setSettingLnAddress(true);
|
||||
setError(undefined);
|
||||
|
||||
const _ = await state.mutiny_wallet?.edit_nostr_profile(
|
||||
undefined,
|
||||
undefined,
|
||||
newAddress,
|
||||
undefined
|
||||
);
|
||||
navigate("/profile");
|
||||
} catch (e) {
|
||||
console.error("Error setting ln address:", e);
|
||||
setError(eify(e));
|
||||
}
|
||||
|
||||
setSettingLnAddress(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<MutinyWalletGuard>
|
||||
<DefaultMain>
|
||||
<BackPop default="/profile" />
|
||||
<LargeHeader>
|
||||
{i18n.t("settings.lightning_address.title")}
|
||||
</LargeHeader>
|
||||
<NiceP>
|
||||
{i18n.t("settings.lightning_address.description")}
|
||||
</NiceP>
|
||||
<Show when={error()}>
|
||||
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||
</Show>
|
||||
<Suspense>
|
||||
<Switch>
|
||||
<Match when={!state.mutiny_plus && ios}>
|
||||
<InfoBox accent="white">
|
||||
Mutiny Address requires Mutiny+ which cannot be
|
||||
purchased with this app.
|
||||
</InfoBox>
|
||||
</Match>
|
||||
<Match when={!state.mutiny_plus}>
|
||||
<NiceP>
|
||||
Join <strong>Mutiny+</strong> to get a lightning
|
||||
address.
|
||||
</NiceP>
|
||||
<MutinyPlusCta />
|
||||
</Match>
|
||||
<Match when={!lnurlName() && state.federations?.length}>
|
||||
<HermesForm onSubmit={() => refetch()} />
|
||||
</Match>
|
||||
<Match
|
||||
when={
|
||||
formattedLnAddress() &&
|
||||
state.federations?.length
|
||||
}
|
||||
>
|
||||
<VStack>
|
||||
<LightningAddressShower
|
||||
lud16={formattedLnAddress()!}
|
||||
/>
|
||||
<Show
|
||||
when={
|
||||
profileLnAddress() !==
|
||||
formattedLnAddress()
|
||||
}
|
||||
>
|
||||
<Button
|
||||
loading={settingLnAddress()}
|
||||
onClick={() =>
|
||||
setLnAddress(formattedLnAddress()!)
|
||||
}
|
||||
>
|
||||
Use this address
|
||||
</Button>
|
||||
</Show>
|
||||
</VStack>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<NiceP>
|
||||
Add a federation to reserve lightning address.
|
||||
</NiceP>
|
||||
<ButtonCard
|
||||
onClick={() =>
|
||||
navigate("/settings/federations")
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Users class="inline-block text-m-red" />
|
||||
<NiceP>
|
||||
{i18n.t("profile.join_federation")}
|
||||
</NiceP>
|
||||
</div>
|
||||
</ButtonCard>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="settings" />
|
||||
</MutinyWalletGuard>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { A } from "@solidjs/router";
|
||||
import { A, createAsync } from "@solidjs/router";
|
||||
import { SecureStoragePlugin } from "capacitor-secure-storage-plugin";
|
||||
import { Import, Trash, Unlink } from "lucide-solid";
|
||||
import { createResource, createSignal, Match, Show, Switch } from "solid-js";
|
||||
@@ -115,8 +115,8 @@ export function NostrKeys() {
|
||||
const i18n = useI18n();
|
||||
const [state, _actions] = useMegaStore();
|
||||
|
||||
const npub = () => state.mutiny_wallet?.get_npub();
|
||||
const nsec = () => state.mutiny_wallet?.export_nsec();
|
||||
const npub = createAsync(async () => state.mutiny_wallet?.get_npub());
|
||||
const nsec = createAsync(async () => state.mutiny_wallet?.export_nsec());
|
||||
const profile = () => state.mutiny_wallet?.get_nostr_profile();
|
||||
|
||||
// @ts-expect-error we're checking for an extension
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { A } from "@solidjs/router";
|
||||
import { A, useNavigate } from "@solidjs/router";
|
||||
import { AtSign } from "lucide-solid";
|
||||
import {
|
||||
createResource,
|
||||
createSignal,
|
||||
@@ -12,6 +13,7 @@ import party from "~/assets/party.gif";
|
||||
import {
|
||||
BackLink,
|
||||
Button,
|
||||
ButtonCard,
|
||||
ConfirmDialog,
|
||||
DefaultMain,
|
||||
ExternalLink,
|
||||
@@ -173,6 +175,8 @@ export function Plus() {
|
||||
const i18n = useI18n();
|
||||
const [state, _actions] = useMegaStore();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<MutinyWalletGuard>
|
||||
<DefaultMain>
|
||||
@@ -181,8 +185,10 @@ export function Plus() {
|
||||
<Switch>
|
||||
<Match when={state.mutiny_plus}>
|
||||
<img src={party} class="mx-auto w-1/2" />
|
||||
<NiceP>{i18n.t("settings.plus.thanks")}</NiceP>
|
||||
<Perks alreadySubbed />
|
||||
{/* <NiceP>{i18n.t("settings.plus.thanks")}</NiceP> */}
|
||||
<NiceP>You're part of the Mutiny!</NiceP>
|
||||
|
||||
{/* <Perks alreadySubbed /> */}
|
||||
<NiceP>
|
||||
{i18n.t("settings.plus.renewal_time")}{" "}
|
||||
<strong class="text-white">
|
||||
@@ -198,6 +204,22 @@ export function Plus() {
|
||||
{i18n.t("settings.plus.wallet_connection")}
|
||||
</A>
|
||||
</NiceP>
|
||||
<Show when={state.federations?.length}>
|
||||
<ButtonCard
|
||||
onClick={() =>
|
||||
navigate("/settings/lightningaddress")
|
||||
}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<AtSign class="inline-block text-m-red" />
|
||||
<NiceP>
|
||||
{i18n.t(
|
||||
"settings.lightning_address.create"
|
||||
)}
|
||||
</NiceP>
|
||||
</div>
|
||||
</ButtonCard>
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={!state.mutiny_plus}>
|
||||
<NiceP>
|
||||
|
||||
@@ -14,3 +14,4 @@ export * from "./Servers";
|
||||
export * from "./ManageFederations";
|
||||
export * from "./NostrKeys";
|
||||
export * from "./ImportProfile";
|
||||
export * from "./LightningAddress";
|
||||
|
||||
Reference in New Issue
Block a user