Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(bridge-ui-v2): processing fee and amount input validation #14220

Merged
merged 42 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
bec2657
minor change
jscriptcoder Jul 17, 2023
20b7d90
Merge branch 'main' into bridge_eth
jscriptcoder Jul 18, 2023
3a84b4f
wip
jscriptcoder Jul 18, 2023
82d6d88
wip
jscriptcoder Jul 19, 2023
8adfaea
wip
jscriptcoder Jul 19, 2023
ce37706
wip
jscriptcoder Jul 19, 2023
614a564
wip
jscriptcoder Jul 19, 2023
ccd044b
max button
jscriptcoder Jul 19, 2023
0c49995
wip
jscriptcoder Jul 19, 2023
2908484
wip
jscriptcoder Jul 19, 2023
a222c3f
wip
jscriptcoder Jul 19, 2023
626b928
wip
jscriptcoder Jul 19, 2023
93ceb18
wip
jscriptcoder Jul 19, 2023
3d9bf59
amount input validation
jscriptcoder Jul 19, 2023
2ab59ae
Merge branch 'main' into bridge_eth
jscriptcoder Jul 19, 2023
1be9386
minor change
jscriptcoder Jul 19, 2023
b1831d7
Merge branch 'bridge_eth' of https://github.com/taikoxyz/taiko-mono i…
jscriptcoder Jul 19, 2023
8d5fbec
minor comment
jscriptcoder Jul 19, 2023
5354874
minor TODO
jscriptcoder Jul 19, 2023
0be1db3
fix test
jscriptcoder Jul 19, 2023
8e51d03
remove unnecessary file
jscriptcoder Jul 19, 2023
5780ea5
fix error
jscriptcoder Jul 19, 2023
3ba05aa
fix ts error
jscriptcoder Jul 19, 2023
9e49964
fix lint
jscriptcoder Jul 19, 2023
c1f27b9
minor change
jscriptcoder Jul 19, 2023
a3b7074
minor change
jscriptcoder Jul 19, 2023
893b37e
fix lint
jscriptcoder Jul 19, 2023
b459d39
align to new design
jscriptcoder Jul 19, 2023
d67cc8e
minor change
jscriptcoder Jul 19, 2023
86189c6
wip
jscriptcoder Jul 19, 2023
a07fd21
wip
jscriptcoder Jul 20, 2023
9621dda
merge main
jscriptcoder Jul 20, 2023
46dbaa3
wip
jscriptcoder Jul 20, 2023
1f64023
wip
jscriptcoder Jul 20, 2023
0691e81
wip
jscriptcoder Jul 20, 2023
9c34ede
wip
jscriptcoder Jul 20, 2023
3b76b8c
wip
jscriptcoder Jul 20, 2023
7587459
wip
jscriptcoder Jul 20, 2023
3c62249
wip
jscriptcoder Jul 20, 2023
4948994
inssuficient balance and allowance
jscriptcoder Jul 20, 2023
348be82
fix ts
jscriptcoder Jul 20, 2023
c46764c
minor change
jscriptcoder Jul 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script lang="ts">
import type { FetchBalanceResult } from '@wagmi/core';
import { t } from 'svelte-i18n';
import { formatEther, parseUnits } from 'viem';
import { formatUnits, parseUnits } from 'viem';

import Icon from '$components/Icon/Icon.svelte';
import { InputBox } from '$components/InputBox';
import { warningToast } from '$components/NotificationToast';
import { getMaxToBridge } from '$libs/bridge/getMaxToBridge';
import { checkBalanceToBridge, getMaxAmountToBridge } from '$libs/bridge';
import { InsufficientAllowanceError, InsufficientBalanceError } from '$libs/error';
import { debounce } from '$libs/util/debounce';
import { uid } from '$libs/util/uid';
import { account } from '$stores/account';
Expand All @@ -20,44 +21,49 @@
let inputBox: InputBox;

let computingMaxAmount = false;
let errorAmount = false;

// Let's get the max amount to bridge and see if it's less
// than what the user has entered. For ETH, will actually get an error
// when trying to get that max amount, if the user has entered too much ETH
// There are two possible errors that can happen when the user
// enters an amount:
// 1. Insufficient balance
// 2. Insufficient allowance
// The first one is an error and the user cannot proceed. The second one
// is a warning but the user must approve allowance before bridging.
let insufficientBalance = false;
let insufficientAllowance = false;

async function checkEnteredAmount() {
insufficientBalance = false;
insufficientAllowance = false;

if (
!$selectedToken ||
!$network ||
!$destNetwork ||
!$account?.address ||
$enteredAmount === BigInt(0) // why to even bother, right?
) {
errorAmount = false;
$enteredAmount === BigInt(0) // no need to check if the amount is 0
)
return;
}

try {
const maxAmount = await getMaxToBridge({
await checkBalanceToBridge({
to: $account.address,
token: $selectedToken,
amount: $enteredAmount,
balance: tokenBalance.value,
processingFee: $processingFee,
srcChainId: $network.id,
destChainId: $destNetwork?.id,
userAddress: $account.address,
amount: $enteredAmount,
destChainId: $destNetwork.id,
processingFee: $processingFee,
});

if ($enteredAmount > maxAmount) {
errorAmount = true;
}
} catch (err) {
console.error(err);

// Viem will throw an error that contains the following message, indicating
// that the user won't have enough to pay the transaction
// TODO: better way to handle this. Error codes?
if (`${err}`.toLocaleLowerCase().match('transaction exceeds the balance')) {
errorAmount = true;
switch (true) {
case err instanceof InsufficientBalanceError:
insufficientBalance = true;
break;
case err instanceof InsufficientAllowanceError:
insufficientAllowance = true;
break;
}
}
}
Expand All @@ -68,45 +74,51 @@
// Will trigger on input events. We update the entered amount
// and check it's validity
function updateAmount(event: Event) {
errorAmount = false;
insufficientBalance = false;
insufficientAllowance = false;

if (!$selectedToken) return;

const target = event.target as HTMLInputElement;

try {
$enteredAmount = parseUnits(target.value, $selectedToken?.decimals);
$enteredAmount = parseUnits(target.value, $selectedToken.decimals);

debouncedCheckEnteredAmount();
} catch (err) {
$enteredAmount = BigInt(0);
}
}

function setETHAmount(amount: bigint) {
inputBox.setValue(formatEther(amount));
$enteredAmount = amount;
}

// Will trigger when the user clicks on the "Max" button
// "MAX" button handler
async function useMaxAmount() {
errorAmount = false;
insufficientBalance = false;
insufficientAllowance = false;

if (!$selectedToken || !$network || !$account?.address) return;
// We cannot calculate the max amount without these guys
if (!$selectedToken || !$network || !$destNetwork || !$account?.address) return;

computingMaxAmount = true;

try {
const maxAmount = await getMaxToBridge({
const maxAmount = await getMaxAmountToBridge({
to: $account.address,
token: $selectedToken,
balance: tokenBalance.value,
processingFee: $processingFee,
srcChainId: $network.id,
destChainId: $destNetwork?.id,
userAddress: $account.address,
destChainId: $destNetwork.id,
amount: BigInt(1), // whatever amount to estimate the cost
});

setETHAmount(maxAmount);
// Update UI
inputBox.setValue(formatUnits(maxAmount, $selectedToken.decimals));

// Update state
$enteredAmount = maxAmount;

// Check validity
checkEnteredAmount();
} catch (err) {
console.error(err);
warningToast($t('amount_input.button.failed_max'));
Expand Down Expand Up @@ -134,19 +146,20 @@
placeholder="0.01"
min="0"
loading={computingMaxAmount}
error={errorAmount}
error={insufficientBalance}
on:input={updateAmount}
bind:this={inputBox}
class="w-full input-box outline-none py-6 pr-16 px-[26px] title-subsection-bold placeholder:text-tertiary-content" />
<!-- TODO: talk to Jane about the MAX button and its styling -->
<button
class="absolute right-6 uppercase"
class="absolute right-6 uppercase hover:font-bold"
disabled={!$selectedToken || !$network || computingMaxAmount}
on:click={useMaxAmount}>
{$t('amount_input.button.max')}
</button>
</div>

{#if errorAmount}
{#if insufficientBalance}
<!-- TODO: should we make another component for flat error messages? -->
<div class="f-items-center space-x-1 mt-3">
<Icon type="exclamation-circle" fillClass="fill-negative-sentiment" />
Expand All @@ -155,4 +168,13 @@
</div>
</div>
{/if}

{#if insufficientAllowance}
<div class="f-items-center space-x-1 mt-3">
<Icon type="exclamation-circle" fillClass="fill-warning-sentiment" />
<div class="body-small-regular text-warning-sentiment">
{$t('amount_input.error.insufficient_allowance')}
</div>
</div>
{/if}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
let errorComputingTokenBalance = false;

async function updateTokenBalance(token: Maybe<Token>, account?: Account, srcChainId?: number, destChainId?: number) {
if (!token || !account || !account.address) return;
if (!token || !srcChainId || !account?.address) return;

computingTokenBalance = true;
errorComputingTokenBalance = false;

try {
value = await getTokenBalance({
token,
srcChainId,
destChainId,
userAddress: account.address,
chainId: srcChainId,
});
} catch (err) {
console.error(err);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Amount } from './Amount.svelte';

This file was deleted.

10 changes: 5 additions & 5 deletions packages/bridge-ui-v2/src/components/Bridge/Bridge.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import type { Account } from '$stores/account';
import { type Network, network } from '$stores/network';

import { AmountInput } from './AmountInput';
import { Amount } from './Amount';
import { ProcessingFee } from './ProcessingFee';
import { RecipientInput } from './RecipientInput';
import { Recipient } from './Recipient';
import { destNetwork, selectedToken } from './state';
import SwitchChainsButton from './SwitchChainsButton.svelte';

Expand Down Expand Up @@ -45,23 +45,23 @@
<TokenDropdown {tokens} bind:value={$selectedToken} />
</div>

<AmountInput />
<Amount />

<div class="f-justify-center">
<SwitchChainsButton />
</div>

<div class="space-y-2">
<ChainSelector label={$t('chain.to')} value={$destNetwork} readOnly />
<RecipientInput />
<!-- <RecipientInput /> -->
</div>
</div>

<ProcessingFee />

<div class="h-sep" />

<Button type="primary" class="px-[28px] py-[14px]">
<Button type="primary" class="px-[28px] py-[14px] rounded-full w-full">
<span class="body-bold">{$t('bridge.button.bridge')}</span>
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@
try {
// Get the balance of the user on the destination chain
const destBalance = await getBalance({
token,
userAddress,
chainId: destChainId,
srcChainId: destChainId,
});

// Calculate the recommended amount of ETH needed for processMessage call
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { formatEther } from 'viem';

import { Alert } from '$components/Alert';
import { Button } from '$components/Button';
import { Icon } from '$components/Icon';
import { InputBox } from '$components/InputBox';
import { LoadingText } from '$components/LoadingText';
Expand All @@ -19,6 +20,7 @@

let dialogId = `dialog-${uid()}`;
let selectedFeeMethod = ProcessingFeeMethod.RECOMMENDED;
let prevOptionSelected = ProcessingFeeMethod.RECOMMENDED;

let recommendedAmount = BigInt(0);
let calculatingRecommendedAmount = false;
Expand All @@ -42,10 +44,20 @@
}

function openModal() {
// Keep track of the selected method before opening the modal
// so if we cancel we can go back to the previous method
prevOptionSelected = selectedFeeMethod;

modalOpen = true;
}

function closeOnOptionClick() {
function cancelModal() {
inputBox.clear();
selectedFeeMethod = prevOptionSelected;
closeModal();
}

function closeModalWithDelay() {
// By adding delay there is enough time to see the selected option
// before closing the modal. Better experience for the user.
setTimeout(closeModal, processingFeeComponent.closingDelayOptionClick);
Expand Down Expand Up @@ -122,15 +134,15 @@
{/if}
</span>

<dialog id={dialogId} class="modal modal-bottom md:absolute md:px-4 md:pb-4" class:modal-open={modalOpen}>
<dialog id={dialogId} class="modal" class:modal-open={modalOpen}>
<div class="modal-box relative px-6 py-[35px] md:rounded-[20px] bg-neutral-background">
<button class="absolute right-6 top-[35px]" on:click={closeModal}>
<Icon type="x-close" fillClass="fill-primary-icon" size={24} />
</button>

<h3 class="title-body-bold mb-7">{$t('processing_fee.title')}</h3>

<ul class="space-y-7 mb-[20px]">
<ul class="space-y-7">
<!-- RECOMMENDED -->
<li class="f-between-center">
<div class="f-col">
Expand All @@ -155,7 +167,7 @@
value={ProcessingFeeMethod.RECOMMENDED}
name="processingFeeMethod"
bind:group={selectedFeeMethod}
on:click={closeOnOptionClick} />
on:click={closeModalWithDelay} />
</li>

<!-- NONE -->
Expand All @@ -177,7 +189,7 @@
value={ProcessingFeeMethod.NONE}
name="processingFeeMethod"
bind:group={selectedFeeMethod}
on:click={closeOnOptionClick} />
on:click={closeModalWithDelay} />
</div>

{#if !hasEnoughEth}
Expand Down Expand Up @@ -207,7 +219,7 @@
</li>
</ul>

<div class="relative f-items-center">
<div class="relative f-items-center my-[20px]">
<InputBox
type="number"
min="0"
Expand All @@ -218,6 +230,18 @@
bind:this={inputBox} />
<span class="absolute right-6 uppercase body-bold text-secondary-content">ETH</span>
</div>

<div class="grid grid-cols-2 gap-[20px]">
<Button
on:click={cancelModal}
type="neutral"
class="px-[28px] py-[10px] rounded-full w-auto bg-transparent !border border-primary-brand hover:border-primary-interactive-hover">
<span class="body-bold">{$t('processing_fee.button.cancel')}</span>
</Button>
<Button type="primary" class="px-[28px] py-[10px] rounded-full w-auto" on:click={closeModal}>
<span class="body-bold">{$t('processing_fee.button.confirm')}</span>
</Button>
</div>
</div>
</dialog>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
let interval: ReturnType<typeof setInterval>;

async function compute(token: Maybe<Token>, srcChainId?: number, destChainId?: number) {
if (!token) return;
// Without token nor destination chain we cannot compute this fee
if (!token || !destChainId) return;

calculating = true;
error = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Recipient } from './Recipient.svelte';

This file was deleted.

Loading