add redeem route for lnulrwithdrawal

Co-authored-by: tompro <office@protom.eu>
This commit is contained in:
Paul Miller
2024-02-26 16:51:45 +00:00
parent 7b1d293656
commit c486fcdd37
6 changed files with 267 additions and 0 deletions

View File

@@ -53,6 +53,13 @@ export default {
npub: "Nostr Npub",
link_to_nostr_sync: "Import Nostr Contacts"
},
redeem: {
redeem_bitcoin: "Redeem Bitcoin",
lnurl_amount_message:
"Enter withdrawal amount between {{min}} and {{max}} sats",
lnurl_redeem_failed: "Withdrawal Failed",
lnurl_redeem_success: "Payment Received"
},
receive: {
receive_bitcoin: "Receive Bitcoin",
edit: "Edit",

View File

@@ -12,6 +12,7 @@ import {
Main,
NotFound,
Receive,
Redeem,
Scanner,
Search,
Send,
@@ -100,6 +101,7 @@ export function Router() {
<Route path="/feedback" component={Feedback} />
<Route path="/gift" component={GiftReceive} />
<Route path="/receive" component={Receive} />
<Route path="/redeem" component={Redeem} />
<Route path="/scanner" component={Scanner} />
<Route path="/send" component={Send} />
<Route path="/swap" component={Swap} />

248
src/routes/Redeem.tsx Normal file
View File

@@ -0,0 +1,248 @@
import { LnUrlParams } from "@mutinywallet/mutiny-wasm";
import { useNavigate } from "@solidjs/router";
import {
createEffect,
createMemo,
createResource,
createSignal,
Match,
Show,
Suspense,
Switch
} from "solid-js";
import {
AmountEditable,
AmountFiat,
AmountSats,
BackLink,
Button,
DefaultMain,
InfoBox,
LargeHeader,
LoadingShimmer,
MegaCheck,
MutinyWalletGuard,
NavBar,
ReceiveWarnings,
showToast,
SuccessModal,
VStack
} from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import { eify, vibrateSuccess } from "~/utils";
type RedeemState = "edit" | "paid";
export function Redeem() {
const [state, _actions] = useMegaStore();
const navigate = useNavigate();
const i18n = useI18n();
const [amount, setAmount] = createSignal<bigint>(0n);
// const [whatForInput, setWhatForInput] = createSignal("");
const [lnurlData, setLnUrlData] = createSignal<LnUrlParams>();
const [lnurlString, setLnUrlString] = createSignal("");
const [fixedAmount, setFixedAmount] = createSignal(false);
const [redeemState, setRedeemState] = createSignal<RedeemState>("edit");
// loading state for the continue button
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal<string>("");
function mSatsToSats(mSats: bigint) {
return mSats / 1000n;
}
function clearAll() {
setAmount(0n);
setLnUrlData(undefined);
setLnUrlString("");
setFixedAmount(false);
setRedeemState("edit");
setLoading(false);
setError("");
}
const [decodedLnurl] = createResource(async () => {
if (state.scan_result) {
if (state.scan_result.lnurl) {
const decoded = await state.mutiny_wallet?.decode_lnurl(
state.scan_result.lnurl
);
return decoded;
}
}
});
createEffect(() => {
if (decodedLnurl()) {
processLnurl(decodedLnurl()!);
}
});
// A ParsedParams with an lnurl in it
async function processLnurl(decoded: LnUrlParams) {
if (decoded.tag === "withdrawRequest") {
if (decoded.min === decoded.max) {
console.log("fixed amount", decoded.max.toString());
setAmount(mSatsToSats(decoded.max));
setFixedAmount(true);
} else {
setAmount(mSatsToSats(decoded.min));
setFixedAmount(false);
}
setLnUrlData(decoded);
setLnUrlString(state.scan_result?.lnurl || "");
}
}
const lnurlAmountText = createMemo(() => {
if (lnurlData()) {
return i18n.t("redeem.lnurl_amount_message", {
min: mSatsToSats(lnurlData()!.min).toLocaleString(),
max: mSatsToSats(lnurlData()!.max).toLocaleString()
});
}
});
const canSend = createMemo(() => {
const lnurlParams = lnurlData();
if (!lnurlParams) return false;
const min = mSatsToSats(lnurlParams.min);
const max = mSatsToSats(lnurlParams.max);
if (amount() === 0n || amount() < min || amount() > max) return false;
return true;
});
async function handleLnUrlWithdrawal() {
const lnurlParams = lnurlData();
if (!lnurlParams) return;
setError("");
setLoading(true);
try {
const success = await state.mutiny_wallet?.lnurl_withdraw(
lnurlString(),
amount()
);
if (!success) {
setError(i18n.t("redeem.lnurl_redeem_failed"));
} else {
setRedeemState("paid");
await vibrateSuccess();
}
} catch (e) {
console.error("lnurl_withdraw failed", e);
showToast(eify(e));
} finally {
setLoading(false);
}
}
return (
<MutinyWalletGuard>
<DefaultMain>
<BackLink />
<LargeHeader>{i18n.t("redeem.redeem_bitcoin")}</LargeHeader>
<Switch>
<Match when={redeemState() === "edit"}>
<div class="flex-1" />
<VStack>
<Suspense
fallback={
<div class="self-center">
<LoadingShimmer />
</div>
}
>
<Show when={decodedLnurl() && lnurlData()}>
<AmountEditable
initialAmountSats={amount() || "0"}
setAmountSats={setAmount}
onSubmit={handleLnUrlWithdrawal}
frozenAmount={fixedAmount()}
/>
</Show>
</Suspense>
<ReceiveWarnings
amountSats={amount() || "0"}
from_fedi_to_ln={false}
/>
<Show when={lnurlAmountText() && !fixedAmount()}>
<InfoBox accent="white">
<p>{lnurlAmountText()}</p>
</InfoBox>
</Show>
<Show when={error()}>
<InfoBox accent="red">
<p>{error()}</p>
</InfoBox>
</Show>
</VStack>
<div class="flex-1" />
<VStack>
{/* TODO: add tagging to lnurlwithdrawal and all the redeem flows */}
{/* <form onSubmit={handleLnUrlWithdrawal}>
<SimpleInput
type="text"
value={whatForInput()}
placeholder={i18n.t("receive.what_for")}
onInput={(e) =>
setWhatForInput(e.currentTarget.value)
}
/>
</form> */}
<Button
disabled={!amount() || !canSend()}
intent="green"
onClick={handleLnUrlWithdrawal}
loading={loading()}
>
{i18n.t("common.continue")}
</Button>
</VStack>
</Match>
<Match when={redeemState() === "paid"}>
<SuccessModal
open={true}
setOpen={(open: boolean) => {
if (!open) clearAll();
}}
onConfirm={() => {
clearAll();
navigate("/");
}}
>
<MegaCheck />
<h1 class="mb-2 mt-4 w-full text-center text-2xl font-semibold md:text-3xl">
{i18n.t("redeem.lnurl_redeem_success")}
</h1>
<div class="flex flex-col items-center gap-1">
<div class="text-xl">
<AmountSats
amountSats={amount()}
icon="plus"
/>
</div>
<div class="text-white/70">
<AmountFiat
amountSats={amount()}
denominationSize="sm"
/>
</div>
</div>
{/* TODO: add payment details */}
</SuccessModal>
<pre>NICE</pre>
</Match>
</Switch>
</DefaultMain>
<NavBar activeTab="receive" />
</MutinyWalletGuard>
);
}

View File

@@ -447,6 +447,11 @@ export function Send() {
setLnurlp(source.lnurl);
setSource("lightning");
}
// TODO: this is a bit of a hack, ideally we do more nav from the megastore
if (lnurlParams.tag === "withdrawRequest") {
actions.setScanResult(source);
navigate("/redeem");
}
})
.catch((e) => showToast(eify(e)));
}

View File

@@ -9,3 +9,4 @@ export * from "./Send";
export * from "./Swap";
export * from "./SwapLightning";
export * from "./Search";
export * from "./Redeem";