diff --git a/README.md b/README.md
index 5afa7a3f..19eb6091 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,7 @@ just i18n $lang
### Adding new languages or keys
1. In `src/i18n/` locate your desired language folder or create one if one does not exist
+
- When creating a new language dir ensure it follows the ISO 639 2-letter standard
2. In this folder create a file called `translations.ts`, this is where the translation keys for your desired language will be located
@@ -139,6 +140,7 @@ just i18n $lang
3. Populate your translation file with a translation object where all of the keys will be located
If you want to add Japanese you will create a file `/src/i18n/jp/translations.ts` and populate it with keys like so:
+
```
export default {
Common: {
@@ -147,6 +149,7 @@ export default {
}
}
```
+
(You should compare your translations against the English language as all other languages are not the master and are likely deprecated)
4. Add your new translation file to the `/src/i18n/config.ts` so you can begin to see them in the app
@@ -163,6 +166,7 @@ export const resources: {
```
5. Add your language to the `Language` object in `/src/utils/languages.ts`. This will allow you to select the language via the language selector in the UI. If your desired language is set as your primary language in your browser it will be selected automatically
+
```
export const LANGUAGE_OPTIONS: Language[] = [
{
diff --git a/src/i18n/en/translations.ts b/src/i18n/en/translations.ts
index 314db496..f93b0fd8 100644
--- a/src/i18n/en/translations.ts
+++ b/src/i18n/en/translations.ts
@@ -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",
diff --git a/src/router.tsx b/src/router.tsx
index bb8407c6..713016f4 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -12,6 +12,7 @@ import {
Main,
NotFound,
Receive,
+ Redeem,
Scanner,
Search,
Send,
@@ -100,6 +101,7 @@ export function Router() {
+
diff --git a/src/routes/Redeem.tsx b/src/routes/Redeem.tsx
new file mode 100644
index 00000000..65c88725
--- /dev/null
+++ b/src/routes/Redeem.tsx
@@ -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(0n);
+ // const [whatForInput, setWhatForInput] = createSignal("");
+ const [lnurlData, setLnUrlData] = createSignal();
+ const [lnurlString, setLnUrlString] = createSignal("");
+ const [fixedAmount, setFixedAmount] = createSignal(false);
+
+ const [redeemState, setRedeemState] = createSignal("edit");
+
+ // loading state for the continue button
+ const [loading, setLoading] = createSignal(false);
+ const [error, setError] = createSignal("");
+
+ 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 (
+
+
+
+ {i18n.t("redeem.redeem_bitcoin")}
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+
+
{lnurlAmountText()}
+
+
+
+
+
{error()}
+
+
+
+
+
+ {/* TODO: add tagging to lnurlwithdrawal and all the redeem flows */}
+ {/* */}
+
+
+
+
+ {
+ if (!open) clearAll();
+ }}
+ onConfirm={() => {
+ clearAll();
+ navigate("/");
+ }}
+ >
+
+
+ {i18n.t("redeem.lnurl_redeem_success")}
+
+
+
+
+
+
+
+
+
+ {/* TODO: add payment details */}
+
+
NICE
+
+
+
+
+
+ );
+}
diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx
index 6e856f8d..908db888 100644
--- a/src/routes/Send.tsx
+++ b/src/routes/Send.tsx
@@ -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)));
}
diff --git a/src/routes/index.ts b/src/routes/index.ts
index a47f92d4..d70096cf 100644
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -9,3 +9,4 @@ export * from "./Send";
export * from "./Swap";
export * from "./SwapLightning";
export * from "./Search";
+export * from "./Redeem";