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:
benthecarman
2024-03-26 10:53:18 -05:00
committed by Paul Miller
parent bdb0611e81
commit c313debd34
18 changed files with 533 additions and 100 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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
View File

@@ -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:

View File

@@ -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": {

View File

@@ -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>

View 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>
);
}

View File

@@ -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];

View File

@@ -56,3 +56,4 @@ export * from "./GenericItem";
export * from "./HomeBalance";
export * from "./EditProfileForm";
export * from "./ImportNsecForm";
export * from "./LightningAddressShower";

View File

@@ -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());

View File

@@ -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>

View File

@@ -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

View File

@@ -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")
}

View 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>
);
}

View File

@@ -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

View File

@@ -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>

View File

@@ -14,3 +14,4 @@ export * from "./Servers";
export * from "./ManageFederations";
export * from "./NostrKeys";
export * from "./ImportProfile";
export * from "./LightningAddress";