From a9ddde78173cae16327b51db445fef1eb3f77a8f Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Fri, 3 Jan 2025 17:57:53 +0100 Subject: [PATCH 01/11] refactor: split up refund page --- e2e/refund/refundFile.spec.ts | 3 + src/components/RefundButton.tsx | 7 +- src/components/SwapList.tsx | 3 +- src/i18n/i18n.ts | 13 ++ src/index.tsx | 3 + src/pages/History.tsx | 1 + src/pages/Refund.tsx | 222 +++---------------------- src/pages/RefundExternal.tsx | 257 +++++++++++++++++++++++++++++ src/style/index.scss | 4 + src/style/tabs.scss | 28 ++++ tests/components/SwapList.spec.tsx | 2 +- tests/pages/Refund.spec.tsx | 2 +- 12 files changed, 336 insertions(+), 209 deletions(-) create mode 100644 src/pages/RefundExternal.tsx create mode 100644 src/style/tabs.scss diff --git a/e2e/refund/refundFile.spec.ts b/e2e/refund/refundFile.spec.ts index a16006de..61e2a4ef 100644 --- a/e2e/refund/refundFile.spec.ts +++ b/e2e/refund/refundFile.spec.ts @@ -10,6 +10,9 @@ test.describe("Refund files", () => { await page.goto("/"); await page.getByRole("link", { name: "Refund" }).click(); + await page + .getByRole("button", { name: "Refund external swap" }) + .click(); await page.getByTestId("refundUpload").click(); await page diff --git a/src/components/RefundButton.tsx b/src/components/RefundButton.tsx index d6499802..90d2bbf4 100644 --- a/src/components/RefundButton.tsx +++ b/src/components/RefundButton.tsx @@ -178,10 +178,9 @@ const RefundButton = (props: { currentSwap.refundTx = res.refundTx; await setSwapStorage(currentSwap); setSwap(currentSwap); - } else { - if (props.setRefundTxId) { - props.setRefundTxId(res.refundTx); - } + } + if (props.setRefundTxId) { + props.setRefundTxId(res.refundTx); } } catch (error) { log.warn("refund failed", error); diff --git a/src/components/SwapList.tsx b/src/components/SwapList.tsx index 66999e19..7534f6d1 100644 --- a/src/components/SwapList.tsx +++ b/src/components/SwapList.tsx @@ -9,6 +9,7 @@ import { SwapIcons } from "./SwapIcons"; const SwapList = (props: { swapsSignal: Accessor; + action: string; onDelete?: () => Promise; }) => { const navigate = useNavigate(); @@ -51,7 +52,7 @@ const SwapList = (props: { class="swaplist-item" onClick={() => navigate(`/swap/${swap.id}`)}> - {t("view")} + {props.action} diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index b9bf78f9..eaa80d01 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -103,6 +103,19 @@ const dict = { refund_past_swaps: "Past swaps", refund_past_swaps_subline: "Swaps that got saved into your browsers storage", + no_refundable_swaps: + "No refundable swaps found in your browser history", + cant_find_swap: "Can't find your swap?", + refund_external_explainer: + "Try refunding an external swap via refund file and other emergency methods", + refund_external_explainer_rsk: + "Connect your Rootstock Wallet to scan for refundable swaps that are not saved in this browser’s swap history.", + refund_external_scanning_rsk: + "Scanning for refundable swaps in your Rootstock Wallet...", + connected_wallet_no_swaps: + "The connected Rootstock Wallet does NOT contain any refundable swaps.", + rsk_log_endpoint_not_available: "Log endpoint not available", + refund_external_swap: "Refund External Swap", history_no_swaps: "Looks like you didn't do any swaps yet.", refund_address_header: "Enter address of your {{ asset }} wallet to refund", diff --git a/src/index.tsx b/src/index.tsx index 2adb8d15..688fc0d0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -25,6 +25,7 @@ import NotFound from "./pages/NotFound"; import Pay from "./pages/Pay"; import Refund from "./pages/Refund"; import RefundEvm from "./pages/RefundEvm"; +import RefundExternal from "./pages/RefundExternal"; import RefundStep from "./pages/RefundStep"; import "./style/index.scss"; import "./utils/patches"; @@ -105,6 +106,8 @@ const cleanup = render( } /> + + diff --git a/src/pages/History.tsx b/src/pages/History.tsx index 1535f442..cd7d431c 100644 --- a/src/pages/History.tsx +++ b/src/pages/History.tsx @@ -153,6 +153,7 @@ const History = () => { onDelete={async () => { setSwaps(await getSwaps()); }} + action={t("view")} /> 0}> diff --git a/src/pages/Refund.tsx b/src/pages/Refund.tsx index c7c3d5bb..c8c2cdcc 100644 --- a/src/pages/Refund.tsx +++ b/src/pages/Refund.tsx @@ -1,158 +1,26 @@ import { useNavigate } from "@solidjs/router"; import log from "loglevel"; -import QrScanner from "qr-scanner"; -import { Show, createEffect, createSignal, onCleanup, onMount } from "solid-js"; +import { Show, createSignal, onMount } from "solid-js"; -import BlockExplorer from "../components/BlockExplorer"; -import ConnectWallet from "../components/ConnectWallet"; -import RefundButton from "../components/RefundButton"; import SwapList from "../components/SwapList"; -import SwapListLogs from "../components/SwapListLogs"; import SettingsCog from "../components/settings/SettingsCog"; import SettingsMenu from "../components/settings/SettingsMenu"; -import { RBTC } from "../consts/Assets"; import { SwapType } from "../consts/Enums"; import { swapStatusFailed, swapStatusSuccess } from "../consts/SwapStatus"; import { useGlobalContext } from "../context/Global"; -import { useWeb3Signer } from "../context/Web3"; +import "../style/tabs.scss"; import { getLockupTransaction, getSwapStatus } from "../utils/boltzClient"; -import { - LogRefundData, - scanLogsForPossibleRefunds, -} from "../utils/contractLogs"; -import { validateRefundFile } from "../utils/refundFile"; import { SomeSwap } from "../utils/swapCreator"; import ErrorWasm from "./ErrorWasm"; -enum RefundError { - InvalidData, -} - const Refund = () => { const navigate = useNavigate(); - const { getSwap, getSwaps, updateSwapStatus, wasmSupported, t } = - useGlobalContext(); - const { signer, providers, getEtherSwap } = useWeb3Signer(); - - const [swapFound, setSwapFound] = createSignal(null); - const [refundInvalid, setRefundInvalid] = createSignal< - RefundError | undefined - >(undefined); - const [refundJson, setRefundJson] = createSignal(null); - const [refundTxId, setRefundTxId] = createSignal(""); - - const checkRefundJsonKeys = async ( - input: HTMLInputElement, - json: Record, - ) => { - log.debug("checking refund json", json); - - try { - const data = validateRefundFile(json); - - // When the swap id is found in the local storage, show a redirect to it - const localStorageSwap = await getSwap(data.id); - if (localStorageSwap !== null) { - setSwapFound(data.id); - return; - } - - setRefundJson(data); - setRefundInvalid(undefined); - } catch (e) { - log.warn("Refund json validation failed", e); - setRefundInvalid(RefundError.InvalidData); - input.setCustomValidity(t("invalid_refund_file")); - } - }; - - const uploadChange = async (e: Event) => { - const input = e.currentTarget as HTMLInputElement; - const inputFile = input.files[0]; - input.setCustomValidity(""); - setRefundJson(null); - setSwapFound(null); - setRefundInvalid(undefined); - - if (["image/png", "image/jpg", "image/jpeg"].includes(inputFile.type)) { - try { - const res = await QrScanner.scanImage(inputFile, { - returnDetailedScanResult: true, - }); - await checkRefundJsonKeys(input, JSON.parse(res.data)); - } catch (e) { - log.error("invalid QR code upload", e); - setRefundInvalid(RefundError.InvalidData); - input.setCustomValidity(t("invalid_refund_file")); - } - } else { - try { - const data = await inputFile.text(); - await checkRefundJsonKeys(input, JSON.parse(data)); - } catch (e) { - log.error("invalid file upload", e); - setRefundInvalid(RefundError.InvalidData); - input.setCustomValidity(t("invalid_refund_file")); - } - } - }; + const { getSwaps, updateSwapStatus, wasmSupported, t } = useGlobalContext(); const refundSwapsSanityFilter = (swap: SomeSwap) => swap.type !== SwapType.Reverse && swap.refundTx === undefined; const [refundableSwaps, setRefundableSwaps] = createSignal([]); - const [logRefundableSwaps, setLogRefundableSwaps] = createSignal< - LogRefundData[] - >([]); - const [refundScanProgress, setRefundScanProgress] = createSignal< - string | undefined - >(undefined); - - let refundScanAbort: AbortController | undefined = undefined; - - onCleanup(() => { - if (refundScanAbort) { - refundScanAbort.abort(); - } - }); - - // eslint-disable-next-line solid/reactivity - createEffect(async () => { - setLogRefundableSwaps([]); - - if (refundScanAbort !== undefined) { - refundScanAbort.abort("signer changed"); - } - - if (signer() === undefined) { - return; - } - - setRefundScanProgress( - t("logs_scan_progress", { - value: Number(0).toFixed(2), - }), - ); - - refundScanAbort = new AbortController(); - - const generator = scanLogsForPossibleRefunds( - refundScanAbort.signal, - signer(), - getEtherSwap(), - ); - - for await (const value of generator) { - setRefundScanProgress( - t("logs_scan_progress", { - value: (value.progress * 100).toFixed(2), - }), - ); - setLogRefundableSwaps(logRefundableSwaps().concat(value.events)); - } - - setRefundScanProgress(undefined); - }); onMount(async () => { const addToRefundableSwaps = (swap: SomeSwap) => { @@ -204,80 +72,30 @@ const Refund = () => { }>
- +
+ +

{t("refund_a_swap")}

+
0} fallback={ <> -

{t("refund_a_swap")}

-

{t("refund_a_swap_subline")}

+

{t("no_refundable_swaps")}

+
}> -

{t("refund_swap", { id: refundJson().id })}

-
- - 0}> - - - 0}> - - - uploadChange(e)} - /> - 0 && - (refundJson() === null || - refundJson().assetSend === RBTC) - }> -
- -
- -
-

{t("swap_in_history")}

- -
- -
- -
- -
- -
- -
-

{t("refunded")}

-
-
+

{t("cant_find_swap")}

+

{t("refund_external_explainer")}

+
diff --git a/src/pages/RefundExternal.tsx b/src/pages/RefundExternal.tsx new file mode 100644 index 00000000..b2ae5460 --- /dev/null +++ b/src/pages/RefundExternal.tsx @@ -0,0 +1,257 @@ +import { useNavigate, useParams } from "@solidjs/router"; +import log from "loglevel"; +import QrScanner from "qr-scanner"; +import { + For, + Match, + Show, + Switch, + createEffect, + createSignal, + onCleanup, +} from "solid-js"; + +import BlockExplorer from "../components/BlockExplorer"; +import ConnectWallet from "../components/ConnectWallet"; +import RefundButton from "../components/RefundButton"; +import SwapListLogs from "../components/SwapListLogs"; +import SettingsCog from "../components/settings/SettingsCog"; +import SettingsMenu from "../components/settings/SettingsMenu"; +import { useGlobalContext } from "../context/Global"; +import { useWeb3Signer } from "../context/Web3"; +import "../style/tabs.scss"; +import { + LogRefundData, + scanLogsForPossibleRefunds, +} from "../utils/contractLogs"; +import { validateRefundFile } from "../utils/refundFile"; +import ErrorWasm from "./ErrorWasm"; + +enum RefundError { + InvalidData, +} + +const RefundBtcLike = () => { + const { t } = useGlobalContext(); + + const [refundInvalid, setRefundInvalid] = createSignal< + RefundError | undefined + >(undefined); + const [refundJson, setRefundJson] = createSignal(null); + const [refundTxId, setRefundTxId] = createSignal(""); + + const checkRefundJsonKeys = ( + input: HTMLInputElement, + json: Record, + ) => { + log.debug("checking refund json", json); + + try { + const data = validateRefundFile(json); + + setRefundJson(data); + setRefundInvalid(undefined); + } catch (e) { + log.warn("Refund json validation failed", e); + setRefundInvalid(RefundError.InvalidData); + input.setCustomValidity(t("invalid_refund_file")); + } + }; + + const uploadChange = async (e: Event) => { + const input = e.currentTarget as HTMLInputElement; + const inputFile = input.files[0]; + input.setCustomValidity(""); + setRefundJson(null); + setRefundInvalid(undefined); + + if (["image/png", "image/jpg", "image/jpeg"].includes(inputFile.type)) { + try { + const res = await QrScanner.scanImage(inputFile, { + returnDetailedScanResult: true, + }); + checkRefundJsonKeys(input, JSON.parse(res.data)); + } catch (e) { + log.error("invalid QR code upload", e); + setRefundInvalid(RefundError.InvalidData); + input.setCustomValidity(t("invalid_refund_file")); + } + } else { + try { + const data = await inputFile.text(); + checkRefundJsonKeys(input, JSON.parse(data)); + } catch (e) { + log.error("invalid file upload", e); + setRefundInvalid(RefundError.InvalidData); + input.setCustomValidity(t("invalid_refund_file")); + } + } + }; + + return ( + <> +

{t("refund_a_swap_subline")}

+ uploadChange(e)} + /> + +
+ +
+ +
+ +
+ +
+

{t("refunded")}

+
+ +
+ + + ); +}; + +const RefundRsk = () => { + const { t } = useGlobalContext(); + const { signer, getEtherSwap } = useWeb3Signer(); + + const [logRefundableSwaps, setLogRefundableSwaps] = createSignal< + LogRefundData[] + >([]); + const [refundScanProgress, setRefundScanProgress] = createSignal< + string | undefined + >(undefined); + + let refundScanAbort: AbortController | undefined = undefined; + + onCleanup(() => { + if (refundScanAbort) { + refundScanAbort.abort(); + } + }); + + // eslint-disable-next-line solid/reactivity + createEffect(async () => { + setLogRefundableSwaps([]); + + if (refundScanAbort !== undefined) { + refundScanAbort.abort("signer changed"); + } + + if (signer() === undefined) { + return; + } + + setRefundScanProgress( + t("logs_scan_progress", { + value: Number(0).toFixed(2), + }), + ); + + refundScanAbort = new AbortController(); + + const generator = scanLogsForPossibleRefunds( + refundScanAbort.signal, + signer(), + getEtherSwap(), + ); + + for await (const value of generator) { + setRefundScanProgress( + t("logs_scan_progress", { + value: (value.progress * 100).toFixed(2), + }), + ); + setLogRefundableSwaps(logRefundableSwaps().concat(value.events)); + } + + setRefundScanProgress(undefined); + }); + + return ( + {t("rsk_log_endpoint_not_available")}

}> + {t("refund_external_explainer_rsk")}

}> + 0}> + + + +

{t("refund_external_scanning_rsk")}

+
+ +

{t("connected_wallet_no_swaps")}

+
+
+
+ +
+ ); +}; + +const RefundExternal = () => { + const { wasmSupported, t } = useGlobalContext(); + + const params = useParams(); + const navigate = useNavigate(); + + const tabBtc = { name: "Bitcoin / Liquid", value: "btc" }; + const tabRsk = { name: "Rootstock", value: "rsk" }; + + const selected = () => params.type ?? tabBtc.value; + + return ( + }> +
+
+
+ +

{t("refund_external_swap")}

+
+
+ + {(tab) => ( +
+ navigate( + `/refund/external/${tab.value}`, + ) + }> + {tab.name} +
+ )} +
+
+ + + + + + + +
+
+
+ ); +}; + +export default RefundExternal; diff --git a/src/style/index.scss b/src/style/index.scss index 23360141..27f68940 100644 --- a/src/style/index.scss +++ b/src/style/index.scss @@ -651,3 +651,7 @@ textarea { font-weight: 600; text-align: center; } + +header { + margin-bottom: 1rem; +} diff --git a/src/style/tabs.scss b/src/style/tabs.scss new file mode 100644 index 00000000..4834b094 --- /dev/null +++ b/src/style/tabs.scss @@ -0,0 +1,28 @@ +@use "sass:color"; +@use "vars"; + +.tabs { + display: flex; + gap: 0.5rem; + align-items: center; + justify-content: center; + + .tab { + padding: 0.5rem 1rem; + background-color: vars.$background; + color: vars.$color; + border-radius: 20px; + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background-color: color.adjust(vars.$background, $lightness: -10%); + } + + &.active { + background-color: vars.$primary; + color: vars.$background; + } + } +} diff --git a/tests/components/SwapList.spec.tsx b/tests/components/SwapList.spec.tsx index 819afd0d..0cd65858 100644 --- a/tests/components/SwapList.spec.tsx +++ b/tests/components/SwapList.spec.tsx @@ -19,7 +19,7 @@ describe("SwapList", () => { const { container: { firstChild: firstChild }, - } = render(() => , { + } = render(() => , { wrapper: contextWrapper, }); diff --git a/tests/pages/Refund.spec.tsx b/tests/pages/Refund.spec.tsx index 2ba5c23e..0f3ed6ed 100644 --- a/tests/pages/Refund.spec.tsx +++ b/tests/pages/Refund.spec.tsx @@ -54,7 +54,7 @@ describe("Refund", () => { const refundFrame = (await screen.findByTestId( "refundFrame", )) as HTMLDivElement; - expect(refundFrame.children.length).toEqual(5); + expect(refundFrame.children.length).toEqual(7); const uploadInput = await screen.findByTestId("refundUpload"); const swapFile = new File(["{}"], "swap.json", { From bd71ccba865dd99f6b864072400b5333e8993195 Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Wed, 8 Jan 2025 11:35:48 +0100 Subject: [PATCH 02/11] chore: fix wording --- src/i18n/i18n.ts | 6 ++---- src/pages/Refund.tsx | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index eaa80d01..e243f52a 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -96,8 +96,7 @@ const dict = { backup_refund_list_clear_history: "clear your browsing history", backup_refund_skip: "If any of the above applies to you, we strongly recommend downloading this refund file!", - refund_a_swap: "Refund a swap", - refund_swap: "Refund Swap {{ id }}", + refund_swap: "Refund Swap", refund_a_swap_subline: "Upload your refund file and reclaim your locked funds", refund_past_swaps: "Past swaps", @@ -341,8 +340,7 @@ const dict = { backup_refund_list_clear_history: "die Browser Daten gelöscht werden", backup_refund_skip: "Falls einer dieser Punkte zutrifft, empfehlen wir ausdrücklich die Rückerstattungsdatei herunterzuladen!", - refund_a_swap: "Einen Swap erstatten", - refund_swap: "Erstatte Swap {{ id }}", + refund_swap: "Swap erstatten", refund_a_swap_subline: "Lade deine Rückerstattungsdatei hoch und hole dir deine Bitcoin aus einem fehlgeschlagenen Swap zurück", refund_past_swaps: "Historische Swaps", diff --git a/src/pages/Refund.tsx b/src/pages/Refund.tsx index c8c2cdcc..e2008f88 100644 --- a/src/pages/Refund.tsx +++ b/src/pages/Refund.tsx @@ -74,7 +74,7 @@ const Refund = () => {
-

{t("refund_a_swap")}

+

{t("refund_swap")}

0} From c7bb7d36c938cc114d4dab1628969441b2952834 Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Wed, 8 Jan 2025 12:02:19 +0100 Subject: [PATCH 03/11] fix: always show `RefundButton` in external btc refund --- src/pages/RefundExternal.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/RefundExternal.tsx b/src/pages/RefundExternal.tsx index b2ae5460..43fd8bd7 100644 --- a/src/pages/RefundExternal.tsx +++ b/src/pages/RefundExternal.tsx @@ -110,10 +110,8 @@ const RefundBtcLike = () => { })()} - -
- -
+
+

{t("refunded")}

From 4b43d3c5dd19c720747964bdedf15a4ee5ebd600 Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Wed, 8 Jan 2025 13:32:31 +0100 Subject: [PATCH 04/11] refactor: dont show tabs at all if rsk log endpoint isnt available --- src/i18n/i18n.ts | 1 - src/pages/RefundExternal.tsx | 44 ++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index e243f52a..d59cebc6 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -113,7 +113,6 @@ const dict = { "Scanning for refundable swaps in your Rootstock Wallet...", connected_wallet_no_swaps: "The connected Rootstock Wallet does NOT contain any refundable swaps.", - rsk_log_endpoint_not_available: "Log endpoint not available", refund_external_swap: "Refund External Swap", history_no_swaps: "Looks like you didn't do any swaps yet.", refund_address_header: diff --git a/src/pages/RefundExternal.tsx b/src/pages/RefundExternal.tsx index 43fd8bd7..5675e135 100644 --- a/src/pages/RefundExternal.tsx +++ b/src/pages/RefundExternal.tsx @@ -185,9 +185,7 @@ const RefundRsk = () => { }); return ( - {t("rsk_log_endpoint_not_available")}

}> + <> {t("refund_external_explainer_rsk")}

}> 0}> @@ -201,7 +199,7 @@ const RefundRsk = () => {

-
+ ); }; @@ -216,6 +214,12 @@ const RefundExternal = () => { const selected = () => params.type ?? tabBtc.value; + const rskAvailable = + import.meta.env.VITE_RSK_LOG_SCAN_ENDPOINT !== undefined; + if (!rskAvailable) { + log.warn("RSK log scan endpoint not available"); + } + return ( }>
@@ -224,21 +228,23 @@ const RefundExternal = () => {

{t("refund_external_swap")}

-
- - {(tab) => ( -
- navigate( - `/refund/external/${tab.value}`, - ) - }> - {tab.name} -
- )} -
-
+ +
+ + {(tab) => ( +
+ navigate( + `/refund/external/${tab.value}`, + ) + }> + {tab.name} +
+ )} +
+
+
From a40bd5dcc06d057910e6e453b6f7d32264009727 Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Wed, 8 Jan 2025 18:37:41 +0100 Subject: [PATCH 05/11] fix: make `lockupTransaction` reactive to swap --- src/components/RefundButton.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/RefundButton.tsx b/src/components/RefundButton.tsx index 90d2bbf4..3aa75b30 100644 --- a/src/components/RefundButton.tsx +++ b/src/components/RefundButton.tsx @@ -10,7 +10,7 @@ import { Show, Switch, createResource, - createSignal, + createSignal, createEffect } from "solid-js"; import { ChainSwap, SubmarineSwap } from "src/utils/swapCreator"; @@ -213,7 +213,7 @@ const RefundButton = (props: { setRefundRunning(false); }; - const [lockupTransaction] = createResource(async () => { + const [lockupTransaction, {refetch: refetchLockup}] = createResource(async () => { if (!props.swap()) { return undefined; } @@ -232,6 +232,12 @@ const RefundButton = (props: { return transactionToRefund; }); + createEffect(() => { + if(props.swap() !== undefined) { + refetchLockup(null); + } + }) + const [preimageHash] = createResource(async () => { return (await decodeInvoice((props.swap() as SubmarineSwap).invoice)) .preimageHash; From 362c3915d4fae08e6cbc1fe42e1682bd59eb4c58 Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Wed, 8 Jan 2025 18:38:37 +0100 Subject: [PATCH 06/11] chore: move invalid json message to refund button --- src/components/RefundButton.tsx | 3 ++- src/i18n/i18n.ts | 6 +++--- src/pages/RefundExternal.tsx | 17 +++++------------ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/components/RefundButton.tsx b/src/components/RefundButton.tsx index 3aa75b30..b3a0a1ff 100644 --- a/src/components/RefundButton.tsx +++ b/src/components/RefundButton.tsx @@ -112,6 +112,7 @@ export const RefundEvm = (props: { const RefundButton = (props: { swap: Accessor; setRefundTxId?: Setter; + buttonOverride?: string; }) => { const { getSwap, @@ -338,7 +339,7 @@ const RefundButton = (props: { class="btn" disabled={!valid() || refundRunning()} onClick={() => refundAction()}> - {t("refund")} + {props.buttonOverride ?? t("refund")} diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index d59cebc6..d7645207 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -98,7 +98,7 @@ const dict = { "If any of the above applies to you, we strongly recommend downloading this refund file!", refund_swap: "Refund Swap", refund_a_swap_subline: - "Upload your refund file and reclaim your locked funds", + "Upload your Swap’s refund file, to refund a swap that is not available in this browser’s swap history.", refund_past_swaps: "Past swaps", refund_past_swaps_subline: "Swaps that got saved into your browsers storage", @@ -116,9 +116,9 @@ const dict = { refund_external_swap: "Refund External Swap", history_no_swaps: "Looks like you didn't do any swaps yet.", refund_address_header: - "Enter address of your {{ asset }} wallet to refund", + "Enter a {{ asset }} address to receive your refund on:", refund_address_header_no_asset: - "Enter address of your wallet to refund", + "Enter an address to receive your refund on:", refund_backup: "Backup", refund_import: "Import Backup", refund_clear: "Delete storage", diff --git a/src/pages/RefundExternal.tsx b/src/pages/RefundExternal.tsx index 5675e135..f6169945 100644 --- a/src/pages/RefundExternal.tsx +++ b/src/pages/RefundExternal.tsx @@ -99,19 +99,12 @@ const RefundBtcLike = () => { accept="application/json,image/png,imagine/jpg,image/jpeg" onChange={(e) => uploadChange(e)} /> - -
- -

- +

{t("refunded")}

From 2b95b9f39ac97850fd5a18bd8170fb04f610beee Mon Sep 17 00:00:00 2001 From: Kilian <19181985+kilrau@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:58:49 +0100 Subject: [PATCH 07/11] chore: add missing strings + prettier --- src/components/RefundButton.tsx | 39 ++++++++++---------- src/i18n/i18n.ts | 64 +++++++++++++++++++++++++++------ src/pages/RefundExternal.tsx | 6 +++- 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/components/RefundButton.tsx b/src/components/RefundButton.tsx index b3a0a1ff..a2c0da0f 100644 --- a/src/components/RefundButton.tsx +++ b/src/components/RefundButton.tsx @@ -9,8 +9,9 @@ import { Setter, Show, Switch, + createEffect, createResource, - createSignal, createEffect + createSignal, } from "solid-js"; import { ChainSwap, SubmarineSwap } from "src/utils/swapCreator"; @@ -214,30 +215,32 @@ const RefundButton = (props: { setRefundRunning(false); }; - const [lockupTransaction, {refetch: refetchLockup}] = createResource(async () => { - if (!props.swap()) { - return undefined; - } + const [lockupTransaction, { refetch: refetchLockup }] = createResource( + async () => { + if (!props.swap()) { + return undefined; + } - const transactionToRefund = await getLockupTransaction( - props.swap().id, - props.swap().type, - ); + const transactionToRefund = await getLockupTransaction( + props.swap().id, + props.swap().type, + ); - // show refund ETA for legacy swaps - if (props.swap().version !== OutputType.Taproot) { - setTimeoutEta(transactionToRefund.timeoutEta); - setTimeoutBlockheight(transactionToRefund.timeoutBlockHeight); - } + // show refund ETA for legacy swaps + if (props.swap().version !== OutputType.Taproot) { + setTimeoutEta(transactionToRefund.timeoutEta); + setTimeoutBlockheight(transactionToRefund.timeoutBlockHeight); + } - return transactionToRefund; - }); + return transactionToRefund; + }, + ); createEffect(() => { - if(props.swap() !== undefined) { + if (props.swap() !== undefined) { refetchLockup(null); } - }) + }); const [preimageHash] = createResource(async () => { return (await decodeInvoice((props.swap() as SubmarineSwap).invoice)) diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index d7645207..ffc72505 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -112,7 +112,7 @@ const dict = { refund_external_scanning_rsk: "Scanning for refundable swaps in your Rootstock Wallet...", connected_wallet_no_swaps: - "The connected Rootstock Wallet does NOT contain any refundable swaps.", + "The connected Rootstock Wallet does not contain any refundable swaps.", refund_external_swap: "Refund External Swap", history_no_swaps: "Looks like you didn't do any swaps yet.", refund_address_header: @@ -339,12 +339,24 @@ const dict = { backup_refund_list_clear_history: "die Browser Daten gelöscht werden", backup_refund_skip: "Falls einer dieser Punkte zutrifft, empfehlen wir ausdrücklich die Rückerstattungsdatei herunterzuladen!", - refund_swap: "Swap erstatten", + refund_swap: "Swap Erstatten", refund_a_swap_subline: - "Lade deine Rückerstattungsdatei hoch und hole dir deine Bitcoin aus einem fehlgeschlagenen Swap zurück", + "Lade deine Rückerstattungsdatei hoch und hole dir deine Bitcoin aus einem fehlgeschlagenen Swap zurück.", refund_past_swaps: "Historische Swaps", refund_past_swaps_subline: "Swaps, die in deinem Browser gespeichert wurden", + no_refundable_swaps: + "Keine erstattungsfähigen Swaps in Browserverlauf gefunden", + cant_find_swap: "Swap nicht gefunden?", + refund_external_explainer: + "Versuchen Sie, einen externen Swap über eine Rückerstattungsdatei und andere Notfallmethoden zu erstatten", + refund_external_explainer_rsk: + "Verbinden Sie Ihr Rootstock Wallet, um nach erstattungsfähigen Swaps zu suchen, die nicht im Swap-Verlauf dieses Browsers gespeichert sind..", + refund_external_scanning_rsk: + "Scanne nach erstattungsfähigen Swaps in Rootstock-Wallet...", + connected_wallet_no_swaps: + "Das angeschlossene Rootstock Wallet enthält keine erstattungsfähigen Swaps.", + refund_external_swap: "Externen Swap rückerstatten", history_no_swaps: "Es sieht so aus, als hättest du noch nicht geswappt.", refund_address_header: "Adresse deines {{ asset }} Wallets eingeben", @@ -575,13 +587,24 @@ const dict = { backup_refund_list_clear_history: "borra tu historial de navegación", backup_refund_skip: "Si alguno de los puntos anteriores le afecta, le recomendamos que descargue el archivo de reembolso.", - refund_a_swap: "Reembolsar un intercambio", - refund_swap: "Reembolsar intercambio {{ id }}", + refund_swap: "Reembolsar Intercambio", refund_a_swap_subline: - "Cargue su archivo de reembolso y recupere sus fondos bloqueados", + "Cargue su archivo de reembolso y recupere sus fondos bloqueados.", refund_past_swaps: "Intercambios anteriores", refund_past_swaps_subline: "Intercambios que se guardaron en el almacenamiento del navegador", + no_refundable_swaps: + "No se han encontrado swaps reembolsables en el historial de tu navegador", + cant_find_swap: "¿No encuentra su intercambio?", + refund_external_explainer: + "Intenta reembolsar un swap externo mediante el archivo de reembolso y otros métodos de emergencia.", + refund_external_explainer_rsk: + "Conecta tu monedero Rootstock para buscar swaps reembolsables que no estén guardados en el historial de swaps de este navegador.", + refund_external_scanning_rsk: + "Escaneando en busca de swaps reembolsables en su monedero Rootstock...", + connected_wallet_no_swaps: + "El monedero Rootstock conectada no contiene ningún swap reembolsable.", + refund_external_swap: "Reembolsar Swap Externo", history_no_swaps: "Parece que aún no has realizado ningún intercambio.", refund_address_header: "Introduzca la dirección de tu monedero {{ asset }} para reembolsar", @@ -807,11 +830,21 @@ const dict = { backup_refund_list_tor: "正在使用Tor浏览器", backup_refund_list_clear_history: "清除您的浏览历史记录", backup_refund_skip: "如果您符合以上任何条件,强烈建议下载此退款文件!", - refund_a_swap: "退还交换", - refund_swap: "退交换{{ id }}", + refund_swap: "退还交换", refund_a_swap_subline: "上传您的退款文件,取回被锁定的资金", refund_past_swaps: "过去的交换", refund_past_swaps_subline: "保存在浏览器存储中的交换", + no_refundable_swaps: "在您的浏览器历史中未发现可退款的交换。", + cant_find_swap: "找不到您的交换?", + refund_external_explainer: + "尝试通过退款文件和其他紧急方法退款外部交换。", + refund_external_explainer_rsk: + "连接您的 Rootstock 钱包,扫描未保存在此浏览器交换历史记录中的可退款交换。", + refund_external_scanning_rsk: + "正在扫描您的 Rootstock 钱包中的可退款掉期。。。", + connected_wallet_no_swaps: + "已连接的 Rootstock 钱包不包含任何可退款掉期。", + refund_external_swap: "退款外部交换", history_no_swaps: "看起来您还没有进行任何交换。", refund_address_header: "输入要退款的 {{ asset }} 钱包地址", refund_address_header_no_asset: "输入要退款的钱包地址", @@ -1026,12 +1059,23 @@ const dict = { backup_refund_list_clear_history: "ブラウザ履歴を消した場合", backup_refund_skip: "もし以上にあてはまる場合、返金ファイルをダウンロードすることを強くお勧めします!", - refund_a_swap: "スワップを返金する", - refund_swap: "スワップの返金 {{ id }}", + refund_swap: "スワップを返金する", refund_a_swap_subline: "返金ファイルをアップロードし、ロックされた資金を回収します", refund_past_swaps: "過去のスワップ", refund_past_swaps_subline: "ブラウザのストレージに保存されたスワップ", + no_refundable_swaps: + "ブラウザの履歴に返金可能なスワップが見つかりません。", + cant_find_swap: "スワップが見つからない?", + refund_external_explainer: + "払い戻しファイルやその他の緊急手段を使って外部スワップを払い戻してみる。", + refund_external_explainer_rsk: + "Rootstockウォレットを接続して、このブラウザのスワップ履歴に保存されていない払い戻し可能なスワップをスキャンします。", + refund_external_scanning_rsk: + "Rootstockウォレットにある払い戻し可能なスワップをスキャンしています...", + connected_wallet_no_swaps: + "接続されているRootstockウォレットには返金可能なスワップが含まれていません。", + refund_external_swap: "払い戻し外部スワップ", history_no_swaps: "まだスワップを行っていないようです。", refund_address_header: "返金用の {{ asset }} ウォレットのアドレスを入力", diff --git a/src/pages/RefundExternal.tsx b/src/pages/RefundExternal.tsx index f6169945..f57a1fdc 100644 --- a/src/pages/RefundExternal.tsx +++ b/src/pages/RefundExternal.tsx @@ -103,7 +103,11 @@ const RefundBtcLike = () => {
From 52b90970f3aaee6bfc7ac574207233189545f981 Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Thu, 9 Jan 2025 13:21:17 +0100 Subject: [PATCH 08/11] fix: dont show refund button after swap has been refunded --- src/pages/RefundExternal.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/pages/RefundExternal.tsx b/src/pages/RefundExternal.tsx index f57a1fdc..a0bb2678 100644 --- a/src/pages/RefundExternal.tsx +++ b/src/pages/RefundExternal.tsx @@ -99,16 +99,18 @@ const RefundBtcLike = () => { accept="application/json,image/png,imagine/jpg,image/jpeg" onChange={(e) => uploadChange(e)} /> -
- + +
+ +

{t("refunded")}

From a57f849b66cc414862115b1ad09764a8a23401cd Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Thu, 9 Jan 2025 13:29:46 +0100 Subject: [PATCH 09/11] refactor: proper resource implementation for lockupTransaction --- src/components/RefundButton.tsx | 40 +++++++++++++-------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/components/RefundButton.tsx b/src/components/RefundButton.tsx index a2c0da0f..2ba64904 100644 --- a/src/components/RefundButton.tsx +++ b/src/components/RefundButton.tsx @@ -9,7 +9,6 @@ import { Setter, Show, Switch, - createEffect, createResource, createSignal, } from "solid-js"; @@ -215,31 +214,24 @@ const RefundButton = (props: { setRefundRunning(false); }; - const [lockupTransaction, { refetch: refetchLockup }] = createResource( - async () => { - if (!props.swap()) { - return undefined; - } - - const transactionToRefund = await getLockupTransaction( - props.swap().id, - props.swap().type, - ); - - // show refund ETA for legacy swaps - if (props.swap().version !== OutputType.Taproot) { - setTimeoutEta(transactionToRefund.timeoutEta); - setTimeoutBlockheight(transactionToRefund.timeoutBlockHeight); - } + // eslint-disable-next-line solid/reactivity + const [lockupTransaction] = createResource(props.swap, async (swap) => { + if (!swap) { + return undefined; + } - return transactionToRefund; - }, - ); + const transactionToRefund = await getLockupTransaction( + swap.id, + swap.type, + ); - createEffect(() => { - if (props.swap() !== undefined) { - refetchLockup(null); + // show refund ETA for legacy swaps + if (swap.version !== OutputType.Taproot) { + setTimeoutEta(transactionToRefund.timeoutEta); + setTimeoutBlockheight(transactionToRefund.timeoutBlockHeight); } + + return transactionToRefund; }); const [preimageHash] = createResource(async () => { @@ -304,7 +296,7 @@ const RefundButton = (props: {
}> - + 0 || timeoutBlockheight() > 0}> Date: Thu, 9 Jan 2025 17:21:32 +0100 Subject: [PATCH 10/11] chore: fix tests --- src/pages/RefundExternal.tsx | 4 +- tests/pages/Refund.spec.tsx | 142 ---------------------------- tests/pages/RefundExternal.spec.tsx | 99 +++++++++++++++++++ 3 files changed, 101 insertions(+), 144 deletions(-) delete mode 100644 tests/pages/Refund.spec.tsx create mode 100644 tests/pages/RefundExternal.spec.tsx diff --git a/src/pages/RefundExternal.tsx b/src/pages/RefundExternal.tsx index a0bb2678..9075403e 100644 --- a/src/pages/RefundExternal.tsx +++ b/src/pages/RefundExternal.tsx @@ -31,7 +31,7 @@ enum RefundError { InvalidData, } -const RefundBtcLike = () => { +export const RefundBtcLike = () => { const { t } = useGlobalContext(); const [refundInvalid, setRefundInvalid] = createSignal< @@ -126,7 +126,7 @@ const RefundBtcLike = () => { ); }; -const RefundRsk = () => { +export const RefundRsk = () => { const { t } = useGlobalContext(); const { signer, getEtherSwap } = useWeb3Signer(); diff --git a/tests/pages/Refund.spec.tsx b/tests/pages/Refund.spec.tsx deleted file mode 100644 index 0f3ed6ed..00000000 --- a/tests/pages/Refund.spec.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { render, screen } from "@solidjs/testing-library"; -import { userEvent } from "@testing-library/user-event"; - -import i18n from "../../src/i18n/i18n"; -import Refund from "../../src/pages/Refund"; -import { TestComponent, contextWrapper, globalSignals } from "../helper"; - -/* eslint-disable require-await,@typescript-eslint/require-await,@typescript-eslint/no-explicit-any */ - -jest.mock("../../src/utils/boltzClient", () => { - const originalModule = jest.requireActual("../../src/utils/boltzClient"); - - return { - __esModule: true, - ...originalModule, - getLockupTransaction: jest.fn(() => { - return { timeoutBlockHeight: 10, timeoutEta: 10 }; - }), - } as unknown; -}); - -describe("Refund", () => { - test("should render WASM error", async () => { - render( - () => ( - <> - - - - ), - { - wrapper: contextWrapper, - }, - ); - globalSignals.setWasmSupported(false); - expect( - await screen.findAllByText(i18n.en.error_wasm), - ).not.toBeUndefined(); - }); - - test("should show refund button when file was uploaded", async () => { - const user = userEvent.setup(); - render( - () => ( - <> - - - - ), - { - wrapper: contextWrapper, - }, - ); - const refundFrame = (await screen.findByTestId( - "refundFrame", - )) as HTMLDivElement; - expect(refundFrame.children.length).toEqual(7); - - const uploadInput = await screen.findByTestId("refundUpload"); - const swapFile = new File(["{}"], "swap.json", { - type: "application/json", - }); - (swapFile as any).text = async () => - JSON.stringify({ - asset: "BTC", - id: "", - privateKey: "", - }); - await user.upload(uploadInput, swapFile); - - expect(await screen.findAllByText(i18n.en.refund)).not.toBeUndefined(); - }); - - test("should show invalid refund button when invalid file was uploaded", async () => { - const user = userEvent.setup(); - render( - () => ( - <> - - - - ), - { - wrapper: contextWrapper, - }, - ); - const refundFrame = (await screen.findByTestId( - "refundFrame", - )) as HTMLDivElement; - expect(refundFrame.children.length).toEqual(5); - - const uploadInput = await screen.findByTestId("refundUpload"); - const swapFile = new File(["{}"], "swap.json", { - type: "application/json", - }); - (swapFile as any).text = async () => - JSON.stringify({ - asset: "BTC", - }); - await user.upload(uploadInput, swapFile); - - expect( - await screen.findAllByText(i18n.en.invalid_refund_file), - ).not.toBeUndefined(); - }); - - test("should show open swap button when swap is in `swaps()`", async () => { - const user = userEvent.setup(); - render( - () => ( - <> - - - - ), - { - wrapper: contextWrapper, - }, - ); - const swap = { - asset: "BTC", - id: "123", - privateKey: "", - }; - await globalSignals.setSwapStorage(swap as any); - const refundFrame = (await screen.findByTestId( - "refundFrame", - )) as HTMLDivElement; - expect(refundFrame.children.length).toEqual(5); - - const uploadInput = await screen.findByTestId("refundUpload"); - const swapFile = new File(["{}"], "swap.json", { - type: "application/json", - }); - (swapFile as any).text = async () => JSON.stringify(swap); - await user.upload(uploadInput, swapFile); - - expect( - await screen.findAllByText(i18n.en.open_swap), - ).not.toBeUndefined(); - }); -}); diff --git a/tests/pages/RefundExternal.spec.tsx b/tests/pages/RefundExternal.spec.tsx new file mode 100644 index 00000000..4d11230d --- /dev/null +++ b/tests/pages/RefundExternal.spec.tsx @@ -0,0 +1,99 @@ +import { render, screen } from "@solidjs/testing-library"; +import { userEvent } from "@testing-library/user-event"; + +import i18n from "../../src/i18n/i18n"; +import { TestComponent, contextWrapper, globalSignals } from "../helper"; +import RefundExternal, { RefundBtcLike } from "../../src/pages/RefundExternal"; + +/* eslint-disable require-await,@typescript-eslint/require-await,@typescript-eslint/no-explicit-any */ + +jest.mock("../../src/utils/boltzClient", () => { + const originalModule = jest.requireActual("../../src/utils/boltzClient"); + + return { + __esModule: true, + ...originalModule, + getLockupTransaction: jest.fn(() => { + return { timeoutBlockHeight: 10, timeoutEta: 10 }; + }), + } as unknown; +}); + +describe("RefundExternal", () => { + test("should render WASM error", async () => { + render( + () => ( + <> + + + + ), + { + wrapper: contextWrapper, + }, + ); + globalSignals.setWasmSupported(false); + expect( + await screen.findAllByText(i18n.en.error_wasm), + ).not.toBeUndefined(); + }); + + describe("BtcLike", () => { + test("should show refund button when file was uploaded", async () => { + const user = userEvent.setup(); + render( + () => ( + <> + + + + ), + { + wrapper: contextWrapper, + }, + ); + const uploadInput = await screen.findByTestId("refundUpload"); + const swapFile = new File(["{}"], "swap.json", { + type: "application/json", + }); + (swapFile as any).text = async () => + JSON.stringify({ + asset: "BTC", + id: "", + privateKey: "", + }); + await user.upload(uploadInput, swapFile); + + expect(await screen.findAllByText(i18n.en.refund)).not.toBeUndefined(); + }); + + test("should show invalid refund button when invalid file was uploaded", async () => { + const user = userEvent.setup(); + render( + () => ( + <> + + + + ), + { + wrapper: contextWrapper, + }, + ); + const uploadInput = await screen.findByTestId("refundUpload"); + const swapFile = new File(["{}"], "swap.json", { + type: "application/json", + }); + (swapFile as any).text = async () => + JSON.stringify({ + asset: "BTC", + }); + await user.upload(uploadInput, swapFile); + + expect( + await screen.findAllByText(i18n.en.invalid_refund_file), + ).not.toBeUndefined(); + }); + }) + +}); From 06cd04cf8787c2f3223db16daf24bc98ab0b7b2b Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Thu, 9 Jan 2025 17:24:34 +0100 Subject: [PATCH 11/11] chore: prettier --- src/components/RefundButton.tsx | 6 +++++- tests/pages/RefundExternal.spec.tsx | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/RefundButton.tsx b/src/components/RefundButton.tsx index 2ba64904..996455f0 100644 --- a/src/components/RefundButton.tsx +++ b/src/components/RefundButton.tsx @@ -296,7 +296,11 @@ const RefundButton = (props: { }> - + 0 || timeoutBlockheight() > 0}> { }); await user.upload(uploadInput, swapFile); - expect(await screen.findAllByText(i18n.en.refund)).not.toBeUndefined(); + expect( + await screen.findAllByText(i18n.en.refund), + ).not.toBeUndefined(); }); test("should show invalid refund button when invalid file was uploaded", async () => { @@ -94,6 +96,5 @@ describe("RefundExternal", () => { await screen.findAllByText(i18n.en.invalid_refund_file), ).not.toBeUndefined(); }); - }) - + }); });