diff --git a/README.md b/README.md index 95943b7..b900c97 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,13 @@ See https://docs.skale.network/metaport/1.1.x/ ## Development +### Storybook preview + +``` +bash prepare_meta.sh && bun install && bun build:lib +bun dev +``` + ### Debug mode To enable debug mode, set `debug` environment variable to `true`: diff --git a/src/components/AmountInput.tsx b/src/components/AmountInput.tsx index ce75cd8..86a8724 100644 --- a/src/components/AmountInput.tsx +++ b/src/components/AmountInput.tsx @@ -12,6 +12,7 @@ import { useCollapseStore } from '../store/Store' export default function AmountInput() { const { address } = useAccount() const transferInProgress = useMetaportStore((state) => state.transferInProgress) + const currentStep = useMetaportStore((state) => state.currentStep) const setAmount = useMetaportStore((state) => state.setAmount) const amount = useMetaportStore((state) => state.amount) const expandedTokens = useCollapseStore((state) => state.expandedTokens) @@ -21,6 +22,13 @@ export default function AmountInput() { setAmount('', address) return } + if (event.target.value.length > 12) { + let initialSize = 22 - event.target.value.length / 3 + initialSize = initialSize <= 12 ? 12 : initialSize + event.target.style.fontSize = initialSize + 'px' + } else { + event.target.style.fontSize = '22px' + } setAmount(event.target.value, address) } @@ -34,7 +42,8 @@ export default function AmountInput() { placeholder="0.00" value={amount} onChange={handleChange} - disabled={transferInProgress} + disabled={transferInProgress || currentStep !== 0} + style={{ width: '100%' }} /> )} diff --git a/src/components/Debug.tsx b/src/components/Debug.tsx index 92e6525..cc2f877 100644 --- a/src/components/Debug.tsx +++ b/src/components/Debug.tsx @@ -21,21 +21,19 @@ * @copyright SKALE Labs 2023-Present */ -import Grid from '@mui/material/Grid'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; +import Grid from '@mui/material/Grid' +import Table from '@mui/material/Table' +import TableBody from '@mui/material/TableBody' +import TableCell from '@mui/material/TableCell' +import TableContainer from '@mui/material/TableContainer' +import TableHead from '@mui/material/TableHead' +import TableRow from '@mui/material/TableRow' +import Paper from '@mui/material/Paper' import { useMetaportStore } from '../store/MetaportStore' import { cls, cmn } from '../core/css' - export default function Debug() { - const debug = useMetaportStore((state) => state.mpc.config.debug) const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) @@ -46,15 +44,14 @@ export default function Debug() { { name: 'chainName1', value: chainName1 }, { name: 'chainName2', value: chainName2 }, { name: 'amount', value: amount }, - { name: 'stepsMetadata', value: JSON.stringify(stepsMetadata) }, + { name: 'stepsMetadata', value: JSON.stringify(stepsMetadata) } ] if (!debug) return return (
- + @@ -80,7 +77,6 @@ export default function Debug() { - ) } diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx index 88b4ac7..722e953 100644 --- a/src/components/ErrorMessage.tsx +++ b/src/components/ErrorMessage.tsx @@ -13,26 +13,26 @@ import LinkOffRoundedIcon from '@mui/icons-material/LinkOffRounded' import PublicOffRoundedIcon from '@mui/icons-material/PublicOffRounded' import SentimentDissatisfiedRoundedIcon from '@mui/icons-material/SentimentDissatisfiedRounded' import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded' -import HourglassTopRoundedIcon from '@mui/icons-material/HourglassTopRounded'; -import CrisisAlertRoundedIcon from '@mui/icons-material/CrisisAlertRounded'; +import HourglassTopRoundedIcon from '@mui/icons-material/HourglassTopRounded' +import CrisisAlertRoundedIcon from '@mui/icons-material/CrisisAlertRounded' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import TextSnippetRoundedIcon from '@mui/icons-material/TextSnippetRounded'; -import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded'; -import AvTimerRoundedIcon from '@mui/icons-material/AvTimerRounded'; - +import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded' +import AvTimerRoundedIcon from '@mui/icons-material/AvTimerRounded' +import RestartAltRoundedIcon from '@mui/icons-material/RestartAltRounded'; +import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded'; +import SortRoundedIcon from '@mui/icons-material/SortRounded'; import { DEFAULT_ERROR_MSG } from '../core/constants' const ERROR_ICONS = { 'link-off': , 'public-off': , sentiment: , - warning: , + warning: , error: , time: } export default function Error(props: { errorMessage: ErrorMessage }) { - const [expanded, setExpanded] = useState(false) const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { @@ -54,28 +54,79 @@ export default function Error(props: { errorMessage: ErrorMessage }) {

Logs are available in your browser's developer console

- -
-
- -
-

- When transferring from SKALE to Ethereum Mainnet, there are frequency limitations. -

-
-
-
- + {props.errorMessage.showTips ? ( +
+
+
+ +
+

+ Transfers might occasionally delay, but all tokens will be sent. +

+
+
+
+ +
+

+ If a transfer is interrupted, you can continue from where you stopped. +

+
+
+
+ +
+

+ Transfers from SKALE to Ethereum Mainnet have frequency limits. +

+
+
+
+ +
+

+ If you still have questions, consult FAQ or contact the support team. +

+
-

- Sometimes transfers may take more time than expected. -

-
- - + ) : null} + } @@ -84,7 +135,7 @@ export default function Error(props: { errorMessage: ErrorMessage }) { >
- +

{expanded === 'panel1' ? 'Hide' : 'Show'} error details @@ -95,13 +146,20 @@ export default function Error(props: { errorMessage: ErrorMessage }) {

{props.errorMessage.text}
- {props.errorMessage.fallback ? ( diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 6198db4..0e47e81 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -74,6 +74,10 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { }, [transactionsHistory]) if (stepsMetadata.length === 0) return
+ + const actionDisabled = + amountErrorMessage || loading || amount == '' || Number(amount) === 0 || !cpData.exitGasOk + return ( @@ -121,9 +125,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { size="medium" className={cls(styles.btnAction, cmn.mtop5)} onClick={() => execute(address, switchNetworkAsync, walletClient)} - disabled={ - !!(amountErrorMessage || loading || amount == '' || !cpData.exitGasOk) - } + disabled={!!actionDisabled} > {step.btnText} diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index d9ad015..b522192 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -89,7 +89,7 @@ export function WidgetUI(props: { config: MetaportConfig }) {
{fabTop ? fabButton : null}
- + diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index 4a72f92..2a2163f 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -64,7 +64,6 @@ export class Action { chainName2: string address: string amount: string - amountWei: bigint tokenId: number token: TokenData @@ -86,9 +85,6 @@ export class Action { constructor( mpc: MetaportCore, - // mainnet: MainnetChain, - // sChain1: SChain, - // sChain2: SChain, chainName1: string, chainName2: string, address: string, @@ -106,7 +102,6 @@ export class Action { this.chainName2 = chainName2 this.address = address this.amount = amount - if (amount) this.amountWei = toWei(amount, token.meta.decimals) this.tokenId = Number(tokenId) this.token = createTokenData(token.keyname, chainName1, token.type, this.mpc.config) @@ -167,6 +162,7 @@ export class Action { updateState(currentState: interfaces.ActionState, transactionHash?: string, timestamp?: number) { log(`actionStateUpd: ${this.constructor.name} - ${currentState} - ${this.token.keyname} \ - ${this.chainName1} -> ${this.chainName2}`) + const amountWei = toWei(this.amount, this.token.meta.decimals) externalEvents.actionStateUpdated({ actionName: this.constructor.name, actionState: currentState, @@ -175,7 +171,7 @@ export class Action { chainName2: this.chainName2, address: this.address, amount: this.amount, - amountWei: this.amountWei, + amountWei: amountWei, tokenId: this.tokenId }, transactionHash, diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index 1470b8b..965ab2d 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -25,7 +25,7 @@ import debug from 'debug' import { Contract } from 'ethers' import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import { fromWei } from '../convertation' +import { fromWei, toWei } from '../convertation' import { TokenData } from '../dataclasses/TokenData' import * as interfaces from '../interfaces' import { addressesEqual } from '../helper' @@ -41,7 +41,17 @@ export async function checkEthBalance( // TODO: optimize balance checks tokenData: TokenData ): Promise { const checkRes: interfaces.CheckRes = { res: false } - + if (!amount || Number(amount) === 0) return checkRes + try { + toWei(amount, tokenData.meta.decimals) + } catch (err) { + if (err.fault && err.fault === 'underflow') { + checkRes.msg = 'The amount is too small' + } else { + checkRes.msg = 'Incorrect amount' + } + return checkRes + } try { const balance = await chain.ethBalance(address) log(`address: ${address}, eth balance: ${balance}, amount: ${amount}`) @@ -73,6 +83,16 @@ export async function checkERC20Balance( ): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes + try { + toWei(amount, tokenData.meta.decimals) + } catch (err) { + if (err.fault && err.fault === 'underflow') { + checkRes.msg = 'The amount is too small' + } else { + checkRes.msg = 'Incorrect amount' + } + return checkRes + } try { const balance = await tokenContract.balanceOf(address) log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`) @@ -97,6 +117,16 @@ export async function checkSFuelBalance( ): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes + try { + toWei(amount, DEFAULT_ERC20_DECIMALS) + } catch (err) { + if (err.fault && err.fault === 'underflow') { + checkRes.msg = 'The amount is too small' + } else { + checkRes.msg = 'Incorrect amount' + } + return checkRes + } try { const balance = await sChain.provider.getBalance(address) log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`) diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index 5985f32..eecd984 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -116,9 +116,10 @@ export class WrapSFuelERC20S extends Action { async execute() { log('WrapSFuelERC20S:execute - starting') this.updateState('wrap') + const amountWei = toWei(this.amount, this.token.meta.decimals) const tx = await this.sChain1.erc20.fundExit(this.token.keyname, { address: this.address, - value: this.amountWei + value: amountWei }) const block = await this.sChain1.provider.getBlock(tx.blockNumber) this.updateState('wrapDone', tx.hash, block.timestamp) diff --git a/src/core/dataclasses/ErrorMessage.ts b/src/core/dataclasses/ErrorMessage.ts index 84157e9..2e46e33 100644 --- a/src/core/dataclasses/ErrorMessage.ts +++ b/src/core/dataclasses/ErrorMessage.ts @@ -21,9 +21,7 @@ * @copyright SKALE Labs 2022-Present */ - -import { TRANSFER_ERROR_MSG } from "../constants" - +import { TRANSFER_ERROR_MSG } from '../constants' export class ErrorMessage { icon: string diff --git a/src/core/metaport.ts b/src/core/metaport.ts index da4e79f..c5a8793 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -99,7 +99,7 @@ export function createWrappedTokensMap( ): TokenDataTypesMap { const wrappedTokens: TokenDataTypesMap = getEmptyTokenDataMap() const tokenType = TokenType.erc20 - if (!chainName1) return wrappedTokens + if (!chainName1 || !config.connections[chainName1][tokenType]) return wrappedTokens Object.keys(config.connections[chainName1][tokenType]).forEach((tokenKeyname) => { const token = config.connections[chainName1][tokenType][tokenKeyname] const wrapperAddress = findFirstWrapperAddress(token) @@ -283,7 +283,8 @@ export default class MetaportCore { destTokenContract, destTokenBalance: null, destChains: Object.keys(token.connections), - amount: '' + amount: '', + currentStep: 0 } } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 76296b2..517de25 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -392,4 +392,4 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { mode: 'dark', vibrant: true } -} \ No newline at end of file +} diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts index f8e05c0..588c692 100644 --- a/src/store/MetaportStore.ts +++ b/src/store/MetaportStore.ts @@ -133,6 +133,9 @@ export const useMetaportStore = create()((set, get) => ({ if (err.info && err.info.error && err.info.error.data && err.info.error.data.message) { headline = err.info.error.data.message } + if (err.shortMessage) { + headline = err.shortMessage + } set({ errorMessage: new dataclasses.TransactionErrorMessage( msg, @@ -191,21 +194,36 @@ export const useMetaportStore = create()((set, get) => ({ loading: true, btnText: 'Checking balance...' }) - const stepMetadata = get().stepsMetadata[get().currentStep] - const actionClass = ACTIONS[stepMetadata.type] - await new actionClass( - get().mpc, - stepMetadata.from, - stepMetadata.to, - address, - amount, - get().tokenId, - get().token, - get().setAmountErrorMessage, - get().setBtnText, - null, - null - ).preAction() + try { + const stepMetadata = get().stepsMetadata[get().currentStep] + const actionClass = ACTIONS[stepMetadata.type] + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + null, + null + ).preAction() + } catch (err) { + console.error(err) + const msg = err.code && err.fault ? `${err.code} - ${err.fault}` : 'Something went wrong' + set({ + errorMessage: new dataclasses.TransactionErrorMessage( + err.message, + get().errorMessageClosedFallback, + msg, + false + ) + }) + } finally { + set({ loading: false }) + } } set({ loading: false }) }, diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index 88fb215..c2f1260 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -384,7 +384,8 @@ button { border-radius: 4px 0 0 4px; padding: 9pt 15pt; font-weight: bold !important; - font-size: 1.3rem !important; + font-size: 22px; + transition: font-size 0.2s ease-in-out; } input::-webkit-outer-spin-button,