Skip to content

Commit

Permalink
Show swap progress
Browse files Browse the repository at this point in the history
  • Loading branch information
megrogan committed Dec 5, 2023
1 parent ba858ab commit 492a05b
Show file tree
Hide file tree
Showing 19 changed files with 327 additions and 84 deletions.
9 changes: 6 additions & 3 deletions frontend/app/src/components/home/BalanceWithRefresh.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
export let bold = false;
export let toppingUp = false;
export let showTopUp = false;
export let showRefresh = true;
export let refreshing = false;
$: cryptoLookup = client.cryptoLookup;
Expand Down Expand Up @@ -58,9 +59,11 @@
<div class="amount" class:bold>
{client.formatTokens(value, minDecimals, tokenDetails.decimals)}
</div>
<div class="refresh" class:refreshing on:click={refresh}>
<Refresh size={"1em"} color={"var(--icon-txt)"} />
</div>
{#if showRefresh}
<div class="refresh" class:refreshing on:click={refresh}>
<Refresh size={"1em"} color={"var(--icon-txt)"} />
</div>
{/if}
{#if showTopUp}
<div class="top-up" on:click={topUp} title={$_("cryptoAccount.topUp")}>
<Plus size={"1em"} color={toppingUp ? "var(--icon-selected)" : "var(--icon-txt)"} />
Expand Down
14 changes: 7 additions & 7 deletions frontend/app/src/components/home/profile/Accounts.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ChevronDown from "svelte-material-icons/ChevronDown.svelte";
import ArrowRightBoldCircle from "svelte-material-icons/ArrowRightBoldCircle.svelte";
import ArrowLeftBoldCircle from "svelte-material-icons/ArrowLeftBoldCircle.svelte";
//import SwapIcon from "svelte-material-icons/SwapHorizontal.svelte";
import SwapIcon from "svelte-material-icons/SwapHorizontal.svelte";
import ViewList from "svelte-material-icons/ViewList.svelte";
import { _ } from "svelte-i18n";
import ErrorMessage from "../../ErrorMessage.svelte";
Expand Down Expand Up @@ -65,10 +65,10 @@
manageMode = "send";
}
// function showSwap(ledger: string) {
// selectedLedger = ledger;
// manageMode = "swap";
// }
function showSwap(ledger: string) {
selectedLedger = ledger;
manageMode = "swap";
}
</script>

{#if manageMode !== "none" && selectedLedger !== undefined}
Expand Down Expand Up @@ -126,7 +126,7 @@
slot="icon" />
<div slot="text">{$_("cryptoAccount.receive")}</div>
</MenuItem>
<!-- {#await client.getTokenSwaps(token.ledger) then swaps}
{#await client.getTokenSwaps(token.ledger) then swaps}
{#if Object.keys(swaps).length > 0}
<MenuItem on:click={() => showSwap(token.ledger)}>
<SwapIcon
Expand All @@ -136,7 +136,7 @@
<div slot="text">{$_("cryptoAccount.swap")}</div>
</MenuItem>
{/if}
{/await} -->
{/await}
{#if snsLedgers.has(token.ledger)}
<MenuItem on:click={() => (transactionsFor = token)}>
<ViewList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
$: howToBuyUrl = tokenDetails.howToBuyUrl;
$: title = $_(`cryptoAccount.${mode}Token`, { values: { symbol } });
$: cryptoBalance = client.cryptoBalance;
$: swapping = mode === "swap" && swapStep === "swap" && busy;
$: secondaryButtonText = $_(
capturingAccount
? "noThanks"
Expand All @@ -55,7 +56,7 @@
);
$: remainingBalance =
amountToSend > BigInt(0)
amountToSend > BigInt(0) && swapStep !== "swapped"
? $cryptoBalance[ledger] - amountToSend - transferFees
: $cryptoBalance[ledger];
Expand Down Expand Up @@ -97,7 +98,7 @@
}
</script>

<Overlay on:close dismissible>
<Overlay on:close>
<ModalContent>
<span class="header" slot="header">
<div class="main-title">{title}</div>
Expand All @@ -107,6 +108,7 @@
value={remainingBalance}
label={$_("cryptoAccount.shortBalanceLabel")}
minDecimals={2}
showRefresh={!swapping}
bold
on:refreshed={onBalanceRefreshed}
on:error={onBalanceRefreshError} />
Expand Down Expand Up @@ -145,10 +147,12 @@
</form>
<span slot="footer">
<ButtonGroup>
<Button
secondary={mode !== "receive"}
tiny={$mobileWidth}
on:click={onSecondaryClick}>{secondaryButtonText}</Button>
{#if !swapping}
<Button
secondary={mode !== "receive"}
tiny={$mobileWidth}
on:click={onSecondaryClick}>{secondaryButtonText}</Button>
{/if}
{#if mode !== "receive" && swapStep !== "swapped"}
<Button
disabled={busy || !valid}
Expand Down
90 changes: 48 additions & 42 deletions frontend/app/src/components/home/profile/SwapCrypto.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { Record } from "@dfinity/candid/lib/cjs/idl";
import CryptoSelector from "../CryptoSelector.svelte";
import Legend from "../../Legend.svelte";
import SwapProgress from "./SwapProgress.svelte";
export let ledgerIn: string;
export let amountIn: bigint;
Expand All @@ -29,7 +30,12 @@
$: detailsIn = $cryptoLookup[ledgerIn];
$: detailsOut = ledgerOut !== undefined ? $cryptoLookup[ledgerOut] : undefined;
$: anySwapsAvailable = Object.keys(swaps).length > 0 && detailsOut !== undefined;
//$: swapping = swapStep === "swap" && busy;
$: swapping = swapStep === "swap" && busy;
$: amountInText = client.formatTokens(amountIn, 0, detailsIn.decimals);
$: minAmountOut =
bestQuote !== undefined
? (bestQuote[1] * BigInt(detailsIn.symbol === "CHAT" ? 102 : 98)) / BigInt(100)
: BigInt(0);
$: {
valid =
Expand Down Expand Up @@ -60,7 +66,6 @@
const [dexId, quote] = bestQuote!;
const amountOutText = client.formatTokens(quote, 0, detailsOut!.decimals);
const amountInText = client.formatTokens(amountIn, 0, detailsIn.decimals);
const rate = (Number(amountOutText) / Number(amountInText)).toPrecision(3);
const dex = dexName(dexId);
const swapText = $_("tokenSwap.title");
Expand Down Expand Up @@ -91,49 +96,11 @@
if (!valid) return;
busy = true;
message = "";
dispatch("error", undefined);
swapId = random128();
const [dex, quote] = bestQuote!;
const minAmountOut = (quote * BigInt(98)) / BigInt(100);
const amountInText = client.formatTokens(amountIn, 0, detailsIn.decimals);
const minAmountOutText = client.formatTokens(minAmountOut, 0, detailsOut!.decimals);
const values = {
tokenIn: detailsIn.symbol,
tokenOut: detailsOut!.symbol,
amountIn: amountInText,
minAmountOut: minAmountOutText,
dex: dexName(dex),
};
client
.swapTokens(swapId, ledgerIn, ledgerOut!, amountIn, minAmountOut, dex)
.then((response) => {
if (response.kind === "success") {
swapStep = "swapped";
const amountOutText = client.formatTokens(
response.amountOut,
0,
detailsOut!.decimals,
);
message = $_("tokenSwap.swapSucceeded", {
values: { ...values, amountOut: amountOutText },
});
} else {
dispatch("error", { error: "tokenSwap.swapFailed", values });
}
})
.catch((err) => {
client.logError(
`Failed to swap ${detailsIn.symbol} to ${detailsOut!.symbol} on ${dexName(
dex,
)}`,
err,
);
dispatch("error", { error: "tokenSwap.swapFailed", values });
})
.finally(() => (busy = false));
client.swapTokens(swapId, ledgerIn, ledgerOut!, amountIn, minAmountOut, bestQuote![0]);
}
function dexName(dex: DexId): string {
Expand All @@ -153,6 +120,34 @@
function onLedgerInSelected(ev: CustomEvent<{ ledger: string; urlFormat: string }>): void {
loadSwaps(ev.detail.ledger);
}
function onSwapComplete(
ev: CustomEvent<{ status: "success" | "failure" | "error"; amountOut: string }>,
): void {
busy = false;
swapStep = "swapped";
const minAmountOutText = client.formatTokens(minAmountOut, 0, detailsOut!.decimals);
const values = {
tokenIn: detailsIn.symbol,
tokenOut: detailsOut!.symbol,
amountIn: amountInText,
amountOut: ev.detail.amountOut,
minAmountOut: minAmountOutText,
dex: dexName(bestQuote![0]),
};
if (ev.detail.status === "success") {
message = $_("tokenSwap.swapSucceeded", { values });
} else if (ev.detail.status === "failure") {
message = $_("tokenSwap.swapFailed", { values });
} else {
message = $_("tokenSwap.swapError");
}
client.refreshAccountBalance(ledgerIn);
client.refreshAccountBalance(ledgerOut!);
}
</script>

{#if swapStep === "quote"}
Expand Down Expand Up @@ -187,6 +182,17 @@
{/await}
{/if}

{#if swapping && swapId !== undefined && detailsOut !== undefined && bestQuote !== undefined}
<SwapProgress
{swapId}
tokenIn={detailsIn.symbol}
tokenOut={detailsOut.symbol}
amountIn={amountInText}
decimalsOut={detailsOut.decimals}
dex={dexName(bestQuote[0])}
on:complete={onSwapComplete} />
{/if}

{#if message !== undefined}
<Markdown inline={false} text={message} />
{/if}
Expand Down
109 changes: 109 additions & 0 deletions frontend/app/src/components/home/profile/SwapProgress.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script lang="ts">
import type { OpenChat } from "openchat-client";
import { Poller } from "openchat-client";
import { createEventDispatcher, getContext, onMount } from "svelte";
import { _ } from "svelte-i18n";
export let swapId: bigint;
export let tokenIn: string;
export let tokenOut: string;
export let amountIn: string;
export let decimalsOut: number;
export let dex: string;
const client = getContext<OpenChat>("client");
const dispatch = createEventDispatcher();
const POLL_INTERVAL = 1000;
let step = 0;
let amountOut = "";
let swapProgressSteps = ["get", "deposit", "notify", "swap", "withdraw"];
let swapProgressResults: (boolean | undefined)[] = [
undefined,
undefined,
undefined,
undefined,
undefined,
];
$: values = {
tokenIn,
tokenOut,
amountIn,
amountOut,
dex,
};
onMount(() => {
const poller = new Poller(querySwapProgress, POLL_INTERVAL, POLL_INTERVAL, true);
return () => poller.stop();
});
async function querySwapProgress() {
let response = await client.tokenSwapStatus(swapId);
if (response.kind === "success") {
if (response.withdrawnFromDex?.kind === "ok" && step <= 4) {
const status =
response.amountSwapped?.kind === "ok" &&
response.amountSwapped.value.kind === "ok"
? "success"
: "failure";
swapProgressResults = [true, true, true, status === "success", true];
step = 5;
onComplete(status);
} else if (response.amountSwapped?.kind === "ok" && step <= 3) {
if (response.amountSwapped.value.kind === "ok") {
amountOut = client.formatTokens(
response.amountSwapped.value.value,
0,
decimalsOut,
);
swapProgressResults = [true, true, true, true, undefined];
} else {
swapProgressSteps[4] = "refund";
swapProgressResults = [true, true, true, false, undefined];
}
step = 4;
} else if (response.notifyDex?.kind == "ok" && step <= 2) {
swapProgressResults = [true, true, true, undefined, undefined];
step = 3;
} else if (response.transfer?.kind == "ok" && step <= 1) {
swapProgressResults = [true, true, undefined, undefined, undefined];
step = 2;
} else if (response.transfer?.kind == "error" && step <= 1) {
swapProgressResults = [true, false, undefined, undefined, undefined];
onComplete("error");
} else if (response.depositAccount?.kind == "ok" && step === 0) {
swapProgressResults = [true, undefined, undefined, undefined, undefined];
step = 1;
} else if (response.depositAccount?.kind == "error" && step === 0) {
swapProgressResults = [false, undefined, undefined, undefined, undefined];
onComplete("error");
}
}
}
function onComplete(status: "success" | "failure" | "error") {
dispatch("complete", { status, amountOut });
}
</script>

<ol>
{#each swapProgressSteps as label, i}
{#if step >= i}
<li>
{$_(`tokenSwap.progress.${label}`, {
values,
})}... {#if swapProgressResults[i] === true}✅️{:else if swapProgressResults[i] === false}❌️{/if}
</li>
{/if}
{/each}
</ol>

<style lang="scss">
ol {
margin-left: $sp4;
}
</style>
13 changes: 11 additions & 2 deletions frontend/app/src/i18n/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -1188,8 +1188,17 @@
"getTokenSwapsError": "获取 {tokenIn} 的交换时出错",
"quoteError": "获取交换 {tokenIn} 的报价时出错",
"findingAvailableTokens": "正在寻找可用的代币进行交换...",
"swapFailed": "无法使用 {amountIn} 将 **{tokenIn}** {minAmountOut} 至少交换为 **{tokenOut}** {dex}",
"swapSucceeded": "已使用 {amountIn} 成功将 **{tokenIn}** {amountOut} 交换为 **{tokenOut}** {dex}",
"swapInfo": "最优惠的报价是来自 {rate} 的 **{dex}**。这为 **{amountOut}** {tokenOut} 提供了 **{amountIn}** {tokenIn}。仅当收到的 {tokenOut} 金额至少为报价金额的 98% 时,交换才会继续进行。您想继续尝试“{swap}”吗?"
"swapFailed": "{dex} 无法将 **{amountIn}** {tokenIn} 交换为至少 **{minAmountOut}** {tokenOut}。",
"progress": {
"get": "获取存款账户",
"deposit": "将 {amountIn} {tokenIn} 转移到 {dex}",
"notify": "通知{dex}存款",
"swap": "指示 {dex} 执行交换",
"withdraw": "从 {amountOut} 中撤回 {tokenOut} {dex}",
"refund": "从 {amountIn} 退款 {tokenIn} {dex}"
},
"swapInfo": "所报的最佳费率是 {rate} 每 {tokenOut} 的 **{tokenIn}** {dex}。这为 **{amountOut}** {tokenOut} 提供了 **{amountIn}** {tokenIn}。仅当收到的 {tokenOut} 金额至少为报价金额的 98% 时,交换才会进行。您想继续尝试“{swap}”吗?",
"swapError": "无法处理交换。没有资金损失。"
}
}
Loading

0 comments on commit 492a05b

Please sign in to comment.