diff --git a/scripts/errorsToTs.cjs b/scripts/errorsToTs.cjs
new file mode 100644
index 00000000..62b46f5c
--- /dev/null
+++ b/scripts/errorsToTs.cjs
@@ -0,0 +1,35 @@
+const fs = require("fs").promises;
+
+(async () => {
+ const filePath = process.argv[2]; // grab the file path from the command-line arguments
+
+ if (!filePath) {
+ console.error("Please provide a file path.");
+ process.exit(1);
+ }
+
+ let file;
+
+ try {
+ file = await fs.readFile(filePath, "utf-8");
+ } catch (error) {
+ console.error(`Failed to read file at path: ${filePath}`);
+ console.error(error);
+ process.exit(1);
+ }
+
+ const regex = /#\s*\[error\(\s*"([^"]*)"\s*\)\]/g;
+
+ let matches = file.match(regex);
+
+ if (matches) {
+ let errors = matches.map((match) => match.match(/"(.*?)"/)[1]); // capture text within ""
+ let typeScriptTypeString = `type MutinyError = "${errors.join(
+ '"\n\t| "'
+ )}";`;
+
+ console.log(typeScriptTypeString);
+ } else {
+ console.error("No matches found in the provided file.");
+ }
+})();
diff --git a/src/components/IntegratedQR.tsx b/src/components/IntegratedQR.tsx
index b055c486..0687a839 100644
--- a/src/components/IntegratedQR.tsx
+++ b/src/components/IntegratedQR.tsx
@@ -68,12 +68,20 @@ export function IntegratedQr(props: {
Copied
-
-
+
+
+
+
diff --git a/src/logic/errorDispatch.ts b/src/logic/errorDispatch.ts
new file mode 100644
index 00000000..c6f08200
--- /dev/null
+++ b/src/logic/errorDispatch.ts
@@ -0,0 +1,68 @@
+// IMPORTANT: this should match 1:1 with the MutinyJsError enum in mutiny-wasm
+// If we can handle all of these, we can handle all the errors that Mutiny can throw
+
+// WARNING: autogenerated code, generated by errorsToTs.cjs
+type MutinyError =
+ | "Mutiny is already running."
+ | "Mutiny is not running."
+ | "Resource Not found."
+ | "Funding transaction could not be created."
+ | "Network connection closed."
+ | "The invoice or address is on a different network."
+ | "An invoice must not get payed twice."
+ | "Payment timed out."
+ | "The given invoice is invalid."
+ | "Failed to create invoice."
+ | "Channel reserve amount is too high."
+ | "We do not have enough balance to pay the given amount."
+ | "Failed to call on the given LNURL."
+ | "Failed to make a request to the LSP."
+ | "Failed to request channel from LSP due to funding error."
+ | "Failed to have a connection to the LSP node."
+ | "Subscription Client Not Configured"
+ | "Invalid Parameter"
+ | "Called incorrect lnurl function."
+ | "Failed to find route."
+ | "Failed to parse the given peer information."
+ | "Failed to create channel."
+ | "Failed to close channel."
+ | "Failed to persist data."
+ | "Failed to read data from storage."
+ | "Failed to decode lightning data."
+ | "Failed to generate seed"
+ | "Invalid mnemonic"
+ | "Failed to conduct wallet operation."
+ | "Failed to sign given transaction."
+ | "Failed to conduct chain access operation."
+ | "Failed to to sync on-chain wallet."
+ | "Failed to execute a rapid gossip sync function"
+ | "Failed to read or write json from the front end"
+ | "The given node pubkey is invalid."
+ | "Failed to get the bitcoin price."
+ | "Satoshi amount is invalid"
+ | "Failed to execute a dlc function"
+ | "Failed to execute a wasm_bindgen function"
+ | "Invalid Arguments were given"
+ | "Incorrect password entered."
+ | "Unknown Error";
+
+export function matchError(e: unknown): Error {
+ let errorString;
+
+ if (e instanceof Error) {
+ errorString = e.message;
+ } else if (typeof e === "string") {
+ errorString = e;
+ } else {
+ errorString = "Unknown error";
+ }
+
+ switch (errorString as MutinyError) {
+ case "Failed to make a request to the LSP.":
+ case "Failed to request channel from LSP due to funding error.":
+ case "Failed to have a connection to the LSP node.":
+ return new Error("LSP error, only on-chain is available for now.");
+ }
+
+ return new Error(errorString);
+}
diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx
index 3288b84c..11a10ef1 100644
--- a/src/routes/Receive.tsx
+++ b/src/routes/Receive.tsx
@@ -45,6 +45,8 @@ import { FeesModal } from "~/components/MoreInfoModal";
import { IntegratedQr } from "~/components/IntegratedQR";
import side2side from "~/assets/icons/side-to-side.svg";
import { useI18n } from "~/i18n/context";
+import eify from "~/utils/eify";
+import { matchError } from "~/logic/errorDispatch";
type OnChainTx = {
transaction: {
@@ -167,6 +169,9 @@ export default function Receive() {
// The flavor of the receive
const [flavor, setFlavor] = createSignal("unified");
+ // loading state for the continue button
+ const [loading, setLoading] = createSignal(false);
+
const receiveString = createMemo(() => {
if (unified() && receiveState() === "show") {
if (flavor() === "unified") {
@@ -227,8 +232,23 @@ export default function Receive() {
async function getUnifiedQr(amount: string) {
const bigAmount = BigInt(amount);
+ setLoading(true);
+
+ // Both paths use tags so we'll do this once
+ let tags;
+
+ try {
+ tags = await processContacts(selectedValues());
+ } catch (e) {
+ showToast(eify(e));
+ console.error(e);
+ setLoading(false);
+ return;
+ }
+
+ // Happy path
+ // First we try to get both an invoice and an address
try {
- const tags = await processContacts(selectedValues());
const raw = await state.mutiny_wallet?.create_bip21(
bigAmount,
tags
@@ -241,11 +261,32 @@ export default function Receive() {
lightning: raw?.invoice
});
+ setLoading(false);
return `bitcoin:${raw?.address}?${params}`;
} catch (e) {
- showToast(new Error("Failed to create invoice. Please try again."));
+ showToast(matchError(e));
console.error(e);
}
+
+ // If we didn't return before this, that means create_bip21 failed
+ // So now we'll just try and get an address without the invoice
+ try {
+ const raw = await state.mutiny_wallet?.get_new_address(tags);
+
+ // Save the raw info so we can watch the address
+ setBip21Raw(raw);
+
+ setFlavor("onchain");
+
+ // We won't meddle with a "unified" QR here
+ return raw?.address;
+ } catch (e) {
+ // If THAT failed we're really screwed
+ showToast(matchError(e));
+ console.error(e);
+ } finally {
+ setLoading(false);
+ }
}
async function onSubmit(e: Event) {
@@ -267,19 +308,22 @@ export default function Receive() {
const address = bip21.address;
try {
- const invoice = await state.mutiny_wallet?.get_invoice(
- lightning
- );
-
- // If the invoice has a fees amount that's probably the LSP fee
- if (invoice?.fees_paid) {
- setLspFee(invoice.fees_paid);
- }
+ // Lightning invoice might be blank
+ if (lightning) {
+ const invoice = await state.mutiny_wallet?.get_invoice(
+ lightning
+ );
+
+ // If the invoice has a fees amount that's probably the LSP fee
+ if (invoice?.fees_paid) {
+ setLspFee(invoice.fees_paid);
+ }
- if (invoice && invoice.paid) {
- setReceiveState("paid");
- setPaymentInvoice(invoice);
- return "lightning_paid";
+ if (invoice && invoice.paid) {
+ setReceiveState("paid");
+ setPaymentInvoice(invoice);
+ return "lightning_paid";
+ }
}
const tx = (await state.mutiny_wallet?.check_address(
@@ -350,7 +394,9 @@ export default function Receive() {
@@ -360,6 +406,7 @@ export default function Receive() {
disabled={!amount()}
intent="green"
onClick={onSubmit}
+ loading={loading()}
>
{i18n.t("continue")}
@@ -375,26 +422,31 @@ export default function Receive() {
{i18n.t("keep_mutiny_open")}
-
- setMethodChooserOpen(open)}
- >
-
-
+ {/* Only show method chooser when we have an invoice */}
+
+
+
+ setMethodChooserOpen(open)
+ }
+ >
+
+
+