handle lsp errors

This commit is contained in:
Paul Miller
2023-07-11 16:25:50 -05:00
committed by Tony Giorgio
parent 3f36d5a475
commit 3d2195e6cf
4 changed files with 203 additions and 40 deletions

35
scripts/errorsToTs.cjs Normal file
View File

@@ -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.");
}
})();

View File

@@ -68,12 +68,20 @@ export function IntegratedQr(props: {
<p class="text-xl font-bold">Copied</p> <p class="text-xl font-bold">Copied</p>
</div> </div>
</Show> </Show>
<div class="w-full flex justify-between items-center py-4 max-w-[256px]"> <div
<Amount class="w-full flex items-center py-4 max-w-[256px]"
amountSats={Number(props.amountSats)} classList={{
showFiat "justify-between": props.kind !== "onchain",
whiteBg "justify-end": props.kind === "onchain"
/> }}
>
<Show when={props.kind !== "onchain"}>
<Amount
amountSats={Number(props.amountSats)}
showFiat
whiteBg
/>
</Show>
<KindIndicator kind={props.kind} /> <KindIndicator kind={props.kind} />
</div> </div>

View File

@@ -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);
}

View File

@@ -45,6 +45,8 @@ import { FeesModal } from "~/components/MoreInfoModal";
import { IntegratedQr } from "~/components/IntegratedQR"; import { IntegratedQr } from "~/components/IntegratedQR";
import side2side from "~/assets/icons/side-to-side.svg"; import side2side from "~/assets/icons/side-to-side.svg";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import eify from "~/utils/eify";
import { matchError } from "~/logic/errorDispatch";
type OnChainTx = { type OnChainTx = {
transaction: { transaction: {
@@ -167,6 +169,9 @@ export default function Receive() {
// The flavor of the receive // The flavor of the receive
const [flavor, setFlavor] = createSignal<ReceiveFlavor>("unified"); const [flavor, setFlavor] = createSignal<ReceiveFlavor>("unified");
// loading state for the continue button
const [loading, setLoading] = createSignal(false);
const receiveString = createMemo(() => { const receiveString = createMemo(() => {
if (unified() && receiveState() === "show") { if (unified() && receiveState() === "show") {
if (flavor() === "unified") { if (flavor() === "unified") {
@@ -227,8 +232,23 @@ export default function Receive() {
async function getUnifiedQr(amount: string) { async function getUnifiedQr(amount: string) {
const bigAmount = BigInt(amount); 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 { try {
const tags = await processContacts(selectedValues());
const raw = await state.mutiny_wallet?.create_bip21( const raw = await state.mutiny_wallet?.create_bip21(
bigAmount, bigAmount,
tags tags
@@ -241,11 +261,32 @@ export default function Receive() {
lightning: raw?.invoice lightning: raw?.invoice
}); });
setLoading(false);
return `bitcoin:${raw?.address}?${params}`; return `bitcoin:${raw?.address}?${params}`;
} catch (e) { } catch (e) {
showToast(new Error("Failed to create invoice. Please try again.")); showToast(matchError(e));
console.error(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) { async function onSubmit(e: Event) {
@@ -267,19 +308,22 @@ export default function Receive() {
const address = bip21.address; const address = bip21.address;
try { try {
const invoice = await state.mutiny_wallet?.get_invoice( // Lightning invoice might be blank
lightning 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 the invoice has a fees amount that's probably the LSP fee
if (invoice?.fees_paid) { if (invoice?.fees_paid) {
setLspFee(invoice.fees_paid); setLspFee(invoice.fees_paid);
} }
if (invoice && invoice.paid) { if (invoice && invoice.paid) {
setReceiveState("paid"); setReceiveState("paid");
setPaymentInvoice(invoice); setPaymentInvoice(invoice);
return "lightning_paid"; return "lightning_paid";
}
} }
const tx = (await state.mutiny_wallet?.check_address( const tx = (await state.mutiny_wallet?.check_address(
@@ -350,7 +394,9 @@ export default function Receive() {
<TagEditor <TagEditor
selectedValues={selectedValues()} selectedValues={selectedValues()}
setSelectedValues={setSelectedValues} setSelectedValues={setSelectedValues}
placeholder={i18n.t("receive_add_the_sender")} placeholder={i18n.t(
"receive_add_the_sender"
)}
/> />
</Card> </Card>
@@ -360,6 +406,7 @@ export default function Receive() {
disabled={!amount()} disabled={!amount()}
intent="green" intent="green"
onClick={onSubmit} onClick={onSubmit}
loading={loading()}
> >
{i18n.t("continue")} {i18n.t("continue")}
</Button> </Button>
@@ -375,26 +422,31 @@ export default function Receive() {
<p class="text-neutral-400 text-center"> <p class="text-neutral-400 text-center">
{i18n.t("keep_mutiny_open")} {i18n.t("keep_mutiny_open")}
</p> </p>
<button {/* Only show method chooser when we have an invoice */}
class="font-bold text-m-grey-400 flex gap-2 p-2 items-center mx-auto" <Show when={bip21Raw()?.invoice}>
onClick={() => setMethodChooserOpen(true)} <button
> class="font-bold text-m-grey-400 flex gap-2 p-2 items-center mx-auto"
<span>Choose format</span> onClick={() => setMethodChooserOpen(true)}
<img class="w-4 h-4" src={side2side} /> >
</button> <span>Choose format</span>
<SimpleDialog <img class="w-4 h-4" src={side2side} />
title="Choose payment format" </button>
open={methodChooserOpen()} <SimpleDialog
setOpen={(open) => setMethodChooserOpen(open)} title="Choose payment format"
> open={methodChooserOpen()}
<StyledRadioGroup setOpen={(open) =>
value={flavor()} setMethodChooserOpen(open)
onValueChange={setFlavor} }
choices={RECEIVE_FLAVORS} >
accent="white" <StyledRadioGroup
vertical value={flavor()}
/> onValueChange={setFlavor}
</SimpleDialog> choices={RECEIVE_FLAVORS}
accent="white"
vertical
/>
</SimpleDialog>
</Show>
</Match> </Match>
<Match <Match
when={ when={