Skip to content

Commit

Permalink
feat: renegotiate chain swap amounts
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Sep 5, 2024
1 parent 4769214 commit 8013ea5
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
generateBitcoinBlock,
generateLiquidBlock,
getBitcoinAddress,
} from "./utils";
} from "../utils";

test.describe("Chain swap", () => {
test.beforeEach(async () => {
Expand Down
84 changes: 84 additions & 0 deletions e2e/chainSwaps/overpayment.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect, test } from "@playwright/test";

import {
elementsSendToAddress,
generateBitcoinBlock,
generateLiquidBlock,
getBitcoinAddress,
getLiquidAddress,
} from "../utils";

test.describe("ChainSwap overpayment", () => {
test.beforeEach(async () => {
await generateBitcoinBlock();
});

test("accept new quote", async ({ page }) => {
await page.goto("/");

await page.locator(".arrow-down").first().click();
await page.getByTestId("select-L-BTC").click();
await page
.locator(
"div:nth-child(3) > .asset-wrap > .asset > .asset-selection > .arrow-down",
)
.click();
await page.getByTestId("select-BTC").click();
await page
.getByTestId("onchainAddress")
.fill(await getBitcoinAddress());

await page.getByTestId("receiveAmount").fill("100 000");
await page.getByTestId("create-swap-button").click();
await page.getByRole("button", { name: "Skip download" }).click();
await page
.getByTestId("pay-onchain-buttons")
.getByText("address")
.click();
const lockupAddress = await page.evaluate(() => {
return navigator.clipboard.readText();
});
await elementsSendToAddress(lockupAddress, 0.01);

await page.getByRole("button", { name: "Accept" }).click();
await generateLiquidBlock();
expect(
page.getByRole("heading", { name: "Congratulations!" }),
).toBeDefined();
});

test("should refund", async ({ page }) => {
await page.goto("/");

await page.locator(".arrow-down").first().click();
await page.getByTestId("select-L-BTC").click();
await page
.locator(
"div:nth-child(3) > .asset-wrap > .asset > .asset-selection > .arrow-down",
)
.click();
await page.getByTestId("select-BTC").click();
await page
.getByTestId("onchainAddress")
.fill(await getBitcoinAddress());

await page.getByTestId("receiveAmount").fill("100 000");
await page.getByTestId("create-swap-button").click();
await page.getByRole("button", { name: "Skip download" }).click();
await page
.getByTestId("pay-onchain-buttons")
.getByText("address")
.click();
const lockupAddress = await page.evaluate(() => {
return navigator.clipboard.readText();
});
await elementsSendToAddress(lockupAddress, 0.01);

await page.getByRole("button", { name: "Refund" }).click();

await page.getByTestId("refundAddress").fill(await getLiquidAddress());

await page.getByTestId("refundButton").click();
expect(page.getByText("Swap has been refunded")).toBeDefined();
});
});
2 changes: 1 addition & 1 deletion e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const bitcoinSendToAddress = async (

export const elementsSendToAddress = async (
address: string,
amount: string,
amount: string | number,
): Promise<string> => {
return execCommand(
`elements-cli-sim-client sendtoaddress "${address}" ${amount}`,
Expand Down
23 changes: 11 additions & 12 deletions src/context/Global.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export type GlobalContextType = {
audio?: boolean,
) => void;
playNotificationSound: () => void;
fetchPairs: () => void;
fetchPairs: () => Promise<void>;

getLogs: () => Promise<Record<string, string[]>>;
clearLogs: () => Promise<void>;
Expand Down Expand Up @@ -191,17 +191,16 @@ const GlobalProvider = (props: { children: any }) => {
audio.play();
};

const fetchPairs = () => {
getPairs()
.then((data) => {
log.debug("getpairs", data);
setOnline(true);
setPairs(data);
})
.catch((error) => {
log.debug(error);
setOnline(false);
});
const fetchPairs = async () => {
try {
const data = await getPairs();
log.debug("getpairs", data);
setOnline(true);
setPairs(data);
} catch (error) {
log.debug(error);
setOnline(false);
}
};

// Use IndexedDB if available; fallback to LocalStorage
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ const dict = {
switch_network: "Switch network",
block: "block",
logs_scan_progress: "Scan progress {{ value }}%",
accept: "Accept",
},
de: {
language: "Deutsch",
Expand Down Expand Up @@ -430,6 +431,7 @@ const dict = {
switch_network: "Netzwerk wechseln",
block: "Block",
logs_scan_progress: "Scan-Fortschritt {{ value }}%",
accept: "Akzeptieren",
},
es: {
language: "Español",
Expand Down Expand Up @@ -649,6 +651,7 @@ const dict = {
switch_network: "Cambiar red",
block: "bloque",
logs_scan_progress: "Progreso del escaneo {{ value }}%",
accept: "Aceptar",
},
zh: {
language: "中文",
Expand Down Expand Up @@ -841,6 +844,7 @@ const dict = {
switch_network: "转换网络",
block: "块",
logs_scan_progress: "扫描进度{{ value }}%",
accept: "接受",
},
};

Expand Down
2 changes: 1 addition & 1 deletion src/pages/RefundEvm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const RefundEvm = () => {
const { signer, getEtherSwap } = useWeb3Signer();

const [refundData] = createResource<LogRefundData>(async () => {
if (signer === undefined) {
if (signer() === undefined) {
return undefined;
}

Expand Down
133 changes: 119 additions & 14 deletions src/status/TransactionLockupFailed.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,133 @@
import { Accessor, Show } from "solid-js";
import BigNumber from "bignumber.js";
import log from "loglevel";
import {
Accessor,
Match,
Show,
Switch,
createResource,
createSignal,
} from "solid-js";

import RefundButton from "../components/RefundButton";
import { SwapType } from "../consts/Enums";
import { useGlobalContext } from "../context/Global";
import { usePayContext } from "../context/Pay";
import NotFound from "../pages/NotFound";
import {
acceptChainSwapNewQuote,
getChainSwapNewQuote,
} from "../utils/boltzClient";
import { formatAmount } from "../utils/denomination";
import { formatError } from "../utils/errors";
import { ChainSwap, SubmarineSwap } from "../utils/swapCreator";

const TransactionLockupFailed = () => {
const { failureReason, swap } = usePayContext();
const { t } = useGlobalContext();
const { t, denomination, separator, fetchPairs, setSwapStorage, pairs } =
useGlobalContext();
const { failureReason, swap, setSwap } = usePayContext();

const [newQuote, newQuoteActions] = createResource<
{ quote: number; receiveAmount: number } | undefined
>(async () => {
if (swap() === null || swap().type !== SwapType.Chain) {
return undefined;
}

try {
const [quote] = await Promise.all([
getChainSwapNewQuote(swap().id),
fetchPairs(),
]);

const claimFee =
pairs()[SwapType.Chain][swap().assetSend][swap().assetReceive]
.fees.minerFees.user.claim + 1;

return {
quote: quote.amount,
receiveAmount: quote.amount - claimFee,
};
} catch (e) {
log.warn(
`Getting new quote for swap ${swap().id} failed: ${formatError(e)}`,
);
}

return undefined;
});

const [quoteRejected, setQuoteRejected] = createSignal<boolean>(false);

return (
<Show when={swap() !== null} fallback={<NotFound />}>
<div>
<h2>{t("lockup_failed")}</h2>
<p>
{t("failure_reason")}: {failureReason()}
</p>
<hr />
<RefundButton
swap={swap as Accessor<SubmarineSwap | ChainSwap>}
/>
<hr />
</div>
<Switch
fallback={
<div>
<h2>{t("lockup_failed")}</h2>
<p>
{t("failure_reason")}: {failureReason()}
</p>
<hr />
<RefundButton
swap={swap as Accessor<SubmarineSwap | ChainSwap>}
/>
<hr />
</div>
}>
<Match
when={
newQuote.state === "ready" &&
newQuote() !== undefined &&
!quoteRejected()
}>
<h2>
New quote:{" "}
{formatAmount(
BigNumber(newQuote().receiveAmount),
denomination(),
separator(),
)}
</h2>
<p>
{t("failure_reason")}: {failureReason()}
</p>
<div class="btns btns-space-between">
<button
class="btn btn-success"
onClick={async () => {
const newSwap = swap() as ChainSwap;

const { quote, receiveAmount } = newQuote();
newSwap.receiveAmount = receiveAmount;
newSwap.claimDetails.amount = quote;

await setSwapStorage(newSwap);
setSwap(newSwap);

try {
await acceptChainSwapNewQuote(
swap().id,
newQuote().quote,
);
} catch (e) {
log.warn(
`Accepting new quote failed: ${formatError(e)}`,
);
await newQuoteActions.refetch();
}
}}>
{t("accept")}
</button>
<button
class="btn btn-danger"
onClick={() => setQuoteRejected(true)}>
{t("refund")}
</button>
</div>
<hr />
</Match>
</Switch>
</Show>
);
};
Expand Down
6 changes: 6 additions & 0 deletions src/utils/boltzClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,12 @@ export const getChainSwapTransactions = (id: string) =>
serverLock: ChainSwapTransaction;
}>(`/v2/swap/chain/${id}/transactions`);

export const getChainSwapNewQuote = (id: string) =>
fetcher<{ amount: number }>(`/v2/swap/chain/${id}/quote`);

export const acceptChainSwapNewQuote = (id: string, amount: number) =>
fetcher<{}>(`/v2/swap/chain/${id}/quote`, { amount });

export {
Pairs,
Contracts,
Expand Down
2 changes: 2 additions & 0 deletions tests/pages/Create.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ describe("Create", () => {
const createButton = (await screen.findByTestId(
"create-swap-button",
)) as HTMLButtonElement;
globalSignals.setOnline(true);

expect(createButton.disabled).toEqual(true);
expect(createButton.innerHTML).toEqual("Invalid BTC address");

Expand Down

0 comments on commit 8013ea5

Please sign in to comment.