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) + } + > + + +