Skip to content

Commit

Permalink
add redeem route for lnulrwithdrawal
Browse files Browse the repository at this point in the history
Co-authored-by: tompro <[email protected]>
  • Loading branch information
futurepaul and tompro committed Feb 26, 2024
1 parent 7b1d293 commit c486fcd
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,15 @@ 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

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: {
Expand All @@ -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
Expand All @@ -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[] = [
{
Expand Down
7 changes: 7 additions & 0 deletions src/i18n/en/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Main,
NotFound,
Receive,
Redeem,
Scanner,
Search,
Send,
Expand Down Expand Up @@ -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} />
Expand Down
248 changes: 248 additions & 0 deletions src/routes/Redeem.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
5 changes: 5 additions & 0 deletions src/routes/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
Expand Down
1 change: 1 addition & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./Send";
export * from "./Swap";
export * from "./SwapLightning";
export * from "./Search";
export * from "./Redeem";

0 comments on commit c486fcd

Please sign in to comment.