diff --git a/package.json b/package.json index a1e5b295c..3155d31b9 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,9 @@ "@cosmjs/proto-signing": "^0.32.1", "@cosmjs/stargate": "^0.32.1", "@cosmjs/tendermint-rpc": "^0.32.1", - "@dydxprotocol/v4-abacus": "^1.4.5", + "@dydxprotocol/v4-abacus": "^1.4.6", "@dydxprotocol/v4-client-js": "^1.0.20", - "@dydxprotocol/v4-localization": "^1.1.30", + "@dydxprotocol/v4-localization": "^1.1.31", "@ethersproject/providers": "^5.7.2", "@js-joda/core": "^5.5.3", "@radix-ui/react-accordion": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 705a63e87..19b1af958 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,14 +26,14 @@ dependencies: specifier: ^0.32.1 version: 0.32.2 '@dydxprotocol/v4-abacus': - specifier: ^1.4.5 - version: 1.4.5 + specifier: ^1.4.6 + version: 1.4.6 '@dydxprotocol/v4-client-js': specifier: ^1.0.20 version: 1.0.20 '@dydxprotocol/v4-localization': - specifier: ^1.1.30 - version: 1.1.30 + specifier: ^1.1.31 + version: 1.1.31 '@ethersproject/providers': specifier: ^5.7.2 version: 5.7.2 @@ -1286,8 +1286,8 @@ packages: resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==} dev: false - /@dydxprotocol/v4-abacus@1.4.5: - resolution: {integrity: sha512-LhJmpIaUkHCsSiHx+jdk+59euvGp2E+gGFelpfmOYpH1+enZgEXDQvWsgHSFEQxYP3mRiGG4uo+ZYNGdvffg7g==} + /@dydxprotocol/v4-abacus@1.4.6: + resolution: {integrity: sha512-qYq+4TizcMMxYVXckn0LCucWBe5N9ZNtD1XnowCAuBUUifHSgMGvao5OeZIKMgNM/udSKOXLss4zLy6dH/G2SA==} dev: false /@dydxprotocol/v4-client-js@1.0.20: @@ -1319,8 +1319,8 @@ packages: - utf-8-validate dev: false - /@dydxprotocol/v4-localization@1.1.30: - resolution: {integrity: sha512-TZfWWRSOxcjLHs972wlJVVHkE7+DVqAUnGZSs24HYHsPtUkPhZiNXMOA2Vk9YddQxumhM79xIRH0cmJSe5DDUg==} + /@dydxprotocol/v4-localization@1.1.31: + resolution: {integrity: sha512-plJVIgFAKq9/hA/gk5GgKgCQFsH3pNtDWfG/yHLDXyiGX0M0mMEi1bTNVs4podFVoHJu1nSL9YPFlpJ00FteGw==} dev: false /@dydxprotocol/v4-proto@4.0.0-dev.0: diff --git a/src/components/SearchSelectMenu.tsx b/src/components/SearchSelectMenu.tsx index fa368ea13..ac6f6201f 100644 --- a/src/components/SearchSelectMenu.tsx +++ b/src/components/SearchSelectMenu.tsx @@ -39,7 +39,7 @@ export const SearchSelectMenu = ({ disabled, label, items, - withSearch, + withSearch = true, withReceiptItems, }: SearchSelectMenuProps) => { const [open, setOpen] = useState(false); @@ -77,6 +77,7 @@ export const SearchSelectMenu = ({ withSearch={withSearch} onItemSelected={() => setOpen(false)} withStickyLayout + $withSearch={withSearch} /> @@ -127,7 +128,7 @@ Styled.Popover = styled(Popover)` box-shadow: none; `; -Styled.ComboboxMenu = styled(ComboboxMenu)` +Styled.ComboboxMenu = styled(ComboboxMenu)<{ $withSearch?: boolean }>` ${layoutMixins.withInnerHorizontalBorders} --comboboxMenu-backgroundColor: var(--color-layer-4); @@ -140,7 +141,8 @@ Styled.ComboboxMenu = styled(ComboboxMenu)` --comboboxMenu-item-checked-textColor: var(--color-text-2); --comboboxMenu-item-highlighted-textColor: var(--color-text-2); - --stickyArea1-topHeight: var(--form-input-height); + --stickyArea1-topHeight: ${({ $withSearch }) => + !$withSearch ? '0' : 'var(--form-input-height)'}; input:focus-visible { outline: none; diff --git a/src/components/TimeoutButton.tsx b/src/components/TimeoutButton.tsx index 4e0c4e63c..1f59548ed 100644 --- a/src/components/TimeoutButton.tsx +++ b/src/components/TimeoutButton.tsx @@ -1,4 +1,4 @@ -import { type ReactNode, useState } from 'react'; +import { type ReactNode, useState, useEffect } from 'react'; import { ButtonAction, ButtonState } from '@/constants/buttons'; import { STRING_KEYS } from '@/constants/localization'; @@ -8,6 +8,7 @@ import { Button, type ButtonStateConfig, type ButtonProps } from '@/components/B type ElementProps = { timeoutInSeconds: number; + onTimeOut?: () => void; slotFinal?: ReactNode; } & ButtonProps; @@ -16,6 +17,7 @@ export type TimeoutButtonProps = ElementProps; export const TimeoutButton = ({ children, timeoutInSeconds, + onTimeOut, slotFinal, ...otherProps }: TimeoutButtonProps) => { @@ -25,6 +27,11 @@ export const TimeoutButton = ({ const secondsLeft = Math.max(0, (timeoutDeadline - now) / 1000); + useEffect(() => { + if (secondsLeft > 0) return; + onTimeOut?.(); + }, [secondsLeft]); + if (slotFinal && secondsLeft <= 0) return slotFinal; return ( diff --git a/src/constants/analytics.ts b/src/constants/analytics.ts index 5fefc73ec..4bb9f50d9 100644 --- a/src/constants/analytics.ts +++ b/src/constants/analytics.ts @@ -144,9 +144,17 @@ export type AnalyticsEventData = validatorUrl: string; } : T extends AnalyticsEvent.TransferDeposit - ? {} + ? { + chainId?: string; + tokenAddress?: string; + tokenSymbol?: string; + } : T extends AnalyticsEvent.TransferWithdraw - ? {} + ? { + chainId?: string; + tokenAddress?: string; + tokenSymbol?: string; + } : // Trading T extends AnalyticsEvent.TradeOrderTypeSelected ? { diff --git a/src/constants/notifications.ts b/src/constants/notifications.ts index d6728d110..b8768151f 100644 --- a/src/constants/notifications.ts +++ b/src/constants/notifications.ts @@ -140,6 +140,7 @@ export type TransferNotifcation = { isCctp?: boolean; errorCount?: number; status?: StatusResponse; + isExchange?: boolean; }; /** diff --git a/src/hooks/useLocalNotifications.tsx b/src/hooks/useLocalNotifications.tsx index 5b2742a90..450e6d5f0 100644 --- a/src/hooks/useLocalNotifications.tsx +++ b/src/hooks/useLocalNotifications.tsx @@ -82,24 +82,30 @@ const useLocalNotificationsContext = () => { isCctp, errorCount, status: currentStatus, + isExchange, } = transferNotification; - // @ts-ignore status.errors is not in the type definition but can be returned - // also error can some time come back as an empty object so we need to ignore for that - const hasErrors = !!currentStatus?.errors || - (currentStatus?.error && Object.keys(currentStatus.error).length !== 0); + const hasErrors = + // @ts-ignore status.errors is not in the type definition but can be returned + // also error can some time come back as an empty object so we need to ignore for that + !!currentStatus?.errors || + (currentStatus?.error && Object.keys(currentStatus.error).length !== 0); if ( + !isExchange && !hasErrors && (!currentStatus?.squidTransactionStatus || currentStatus?.squidTransactionStatus === 'ongoing') ) { try { - const status = await fetchSquidStatus({ - transactionId: txHash, - toChainId, - fromChainId, - }, isCctp); + const status = await fetchSquidStatus( + { + transactionId: txHash, + toChainId, + fromChainId, + }, + isCctp + ); if (status) { transferNotification.status = status; diff --git a/src/hooks/useNotificationTypes.tsx b/src/hooks/useNotificationTypes.tsx index 6a2c16a5f..3063a841b 100644 --- a/src/hooks/useNotificationTypes.tsx +++ b/src/hooks/useNotificationTypes.tsx @@ -179,8 +179,9 @@ export const notificationTypes: NotificationTypeConfig[] = [ useEffect(() => { for (const transfer of transferNotifications) { - const { fromChainId, status, txHash, toAmount, type } = transfer; - const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing'; + const { fromChainId, status, txHash, toAmount, type, isExchange } = transfer; + const isFinished = + (Boolean(status) && status?.squidTransactionStatus !== 'ongoing') || isExchange; const icon = ; const transferType = diff --git a/src/lib/addressUtils.ts b/src/lib/addressUtils.ts index 9763e4d26..1e2f954bc 100644 --- a/src/lib/addressUtils.ts +++ b/src/lib/addressUtils.ts @@ -25,3 +25,22 @@ export function convertBech32Address({ }): string { return toBech32(bech32Prefix, fromHex(toHex(fromBech32(address).data))); } + +/** + * Validates a Cosmos address with a specific prefix. + * @param {string} address The Cosmos address to validate. + * @param {string} prefix The expected prefix for the address. + * @returns {boolean} True if the address is valid and matches the prefix, false otherwise. + */ +export function validateCosmosAddress(address: string, prefix: string) { + try { + // Decode the address to verify its structure and prefix + const { prefix: decodedPrefix } = fromBech32(address); + + // Check if the decoded address has the expected prefix + return decodedPrefix === prefix; + } catch (error) { + // If decoding fails, the address is not valid + return false; + } +} diff --git a/src/lib/testFlags.ts b/src/lib/testFlags.ts index a3e1a622a..74a8cd9c9 100644 --- a/src/lib/testFlags.ts +++ b/src/lib/testFlags.ts @@ -19,13 +19,17 @@ class TestFlags { return !!this.queryParams.displayinitializingmarkets; } - get addressOverride():string { + get addressOverride(): string { return this.queryParams.address; } - + get showTradingRewards() { return !!this.queryParams.tradingrewards; } + + get showCexWithdrawal() { + return !!this.queryParams.cexwithdrawal; + } } export const testFlags = new TestFlags(); diff --git a/src/views/dialogs/DepositDialog.tsx b/src/views/dialogs/DepositDialog.tsx index d9dbba2e2..f46022d06 100644 --- a/src/views/dialogs/DepositDialog.tsx +++ b/src/views/dialogs/DepositDialog.tsx @@ -13,8 +13,6 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => { const stringGetter = useStringGetter(); const { isMobile } = useBreakpoints(); - const closeDialog = () => setIsOpen?.(false); - return ( { title={stringGetter({ key: STRING_KEYS.DEPOSIT })} placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default} > - + ); }; diff --git a/src/views/dialogs/DepositDialog/DepositDialogContent.tsx b/src/views/dialogs/DepositDialog/DepositDialogContent.tsx index ce51e4bf8..bfd9046b2 100644 --- a/src/views/dialogs/DepositDialog/DepositDialogContent.tsx +++ b/src/views/dialogs/DepositDialog/DepositDialogContent.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import styled, { type AnyStyledComponent } from 'styled-components'; import { TransferInputField, TransferType } from '@/constants/abacus'; +import { AnalyticsEvent } from '@/constants/analytics'; import { isMainnet } from '@/constants/networks'; import { layoutMixins } from '@/styles/layoutMixins'; @@ -9,6 +10,7 @@ import { DepositForm } from '@/views/forms/AccountManagementForms/DepositForm'; import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm'; import abacusStateManager from '@/lib/abacus'; +import { track } from '@/lib/analytics'; type ElementProps = { onDeposit?: () => void; @@ -34,9 +36,19 @@ export const DepositDialogContent = ({ onDeposit }: ElementProps) => { return ( {isMainnet || !showFaucet ? ( - + { + track(AnalyticsEvent.TransferDeposit, event); + onDeposit?.(); + }} + /> ) : ( - + { + track(AnalyticsEvent.TransferFaucet); + onDeposit?.(); + }} + /> )} {!isMainnet && ( setShowFaucet(!showFaucet)}> diff --git a/src/views/dialogs/OnboardingDialog.tsx b/src/views/dialogs/OnboardingDialog.tsx index a4449d917..54fb939c2 100644 --- a/src/views/dialogs/OnboardingDialog.tsx +++ b/src/views/dialogs/OnboardingDialog.tsx @@ -105,8 +105,8 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => { {isMainnet ? ( { - track(AnalyticsEvent.TransferDeposit); + onDeposit={(event) => { + track(AnalyticsEvent.TransferDeposit, event); }} /> ) : ( diff --git a/src/views/forms/AccountManagementForms/DepositForm.tsx b/src/views/forms/AccountManagementForms/DepositForm.tsx index a0d80e2c4..0599d6a7b 100644 --- a/src/views/forms/AccountManagementForms/DepositForm.tsx +++ b/src/views/forms/AccountManagementForms/DepositForm.tsx @@ -7,6 +7,7 @@ import { Abi, parseUnits } from 'viem'; import erc20 from '@/abi/erc20.json'; import erc20_usdt from '@/abi/erc20_usdt.json'; import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus'; +import { AnalyticsEvent, AnalyticsEventData } from '@/constants/analytics'; import { AlertType } from '@/constants/alerts'; import { ButtonSize } from '@/constants/buttons'; import { STRING_KEYS } from '@/constants/localization'; @@ -48,7 +49,7 @@ import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt'; import { NobleDeposit } from '../NobleDeposit'; type DepositFormProps = { - onDeposit?: () => void; + onDeposit?: (event?: AnalyticsEventData) => void; onError?: () => void; }; @@ -132,7 +133,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => { if (error) onError?.(); }, [error]); - const onSelectChain = useCallback((name: string, type: 'chain' | 'exchange') => { + const onSelectNetwork = useCallback((name: string, type: 'chain' | 'exchange') => { if (name) { abacusStateManager.clearTransferInputValues(); setFromAmount(''); @@ -258,8 +259,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => { }; const txHash = await signerWagmi.sendTransaction(tx); - onDeposit?.(); - if (txHash) { addTransferNotification({ txHash: txHash, @@ -271,6 +270,12 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => { }); abacusStateManager.clearTransferInputValues(); setFromAmount(''); + + onDeposit?.({ + chainId: chainIdStr || undefined, + tokenAddress: sourceToken?.address || undefined, + tokenSymbol: sourceToken?.symbol || undefined, + }); } } catch (error) { log('DepositForm/onSubmit', error); @@ -279,7 +284,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => { setIsLoading(false); } }, - [requestPayload, signerWagmi, chainId] + [requestPayload, signerWagmi, chainId, sourceToken, sourceChain] ); const amountInputReceipt = [ @@ -378,7 +383,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => { {exchange && nobleAddress ? ( diff --git a/src/views/forms/AccountManagementForms/SourceSelectMenu.tsx b/src/views/forms/AccountManagementForms/SourceSelectMenu.tsx index 8b82e68b3..a11943838 100644 --- a/src/views/forms/AccountManagementForms/SourceSelectMenu.tsx +++ b/src/views/forms/AccountManagementForms/SourceSelectMenu.tsx @@ -14,6 +14,7 @@ import { popoverMixins } from '@/styles/popoverMixins'; import { getTransferInputs } from '@/state/inputsSelectors'; import { isTruthy } from '@/lib/isTruthy'; +import { testFlags } from '@/lib/testFlags'; type ElementProps = { label?: string; @@ -62,11 +63,12 @@ export const SourceSelectMenu = ({ return ( 0 && { - group: 'exchanges', - groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }), - items: exchangeItems, - }, + exchangeItems.length > 0 && + (testFlags.showCexWithdrawal || type === TransferType.deposit) && { + group: 'exchanges', + groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }), + items: exchangeItems, + }, chainItems.length > 0 && { group: 'chains', groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }), diff --git a/src/views/forms/AccountManagementForms/TokenSelectMenu.tsx b/src/views/forms/AccountManagementForms/TokenSelectMenu.tsx index c08fbf07e..96db78f65 100644 --- a/src/views/forms/AccountManagementForms/TokenSelectMenu.tsx +++ b/src/views/forms/AccountManagementForms/TokenSelectMenu.tsx @@ -17,9 +17,10 @@ import { getTransferInputs } from '@/state/inputsSelectors'; type ElementProps = { selectedToken?: TransferInputTokenResource; onSelectToken: (token: TransferInputTokenResource) => void; + isExchange?: boolean; }; -export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps) => { +export const TokenSelectMenu = ({ selectedToken, onSelectToken, isExchange }: ElementProps) => { const stringGetter = useStringGetter(); const { type, depositOptions, withdrawalOptions, resources } = useSelector(getTransferInputs, shallowEqual) || {}; @@ -47,19 +48,24 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps) }, ]} label={stringGetter({ key: STRING_KEYS.ASSET })} - withReceiptItems={[ - { - key: 'swap', - label: stringGetter({ key: STRING_KEYS.SWAP }), - value: selectedToken && ( - <> - {type === TransferType.deposit ? selectedToken?.symbol : 'USDC'} - - {type === TransferType.deposit ? 'USDC' : selectedToken?.symbol} - - ), - }, - ]} + withSearch={!isExchange} + withReceiptItems={ + !isExchange + ? [ + { + key: 'swap', + label: stringGetter({ key: STRING_KEYS.SWAP }), + value: selectedToken && ( + <> + {type === TransferType.deposit ? selectedToken?.symbol : 'USDC'} + + {type === TransferType.deposit ? 'USDC' : selectedToken?.symbol} + + ), + }, + ] + : undefined + } > {selectedToken ? ( diff --git a/src/views/forms/AccountManagementForms/WithdrawForm.tsx b/src/views/forms/AccountManagementForms/WithdrawForm.tsx index e6e0179b5..1fa07fc0e 100644 --- a/src/views/forms/AccountManagementForms/WithdrawForm.tsx +++ b/src/views/forms/AccountManagementForms/WithdrawForm.tsx @@ -7,6 +7,7 @@ import { isAddress } from 'viem'; import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus'; import { AlertType } from '@/constants/alerts'; +import { AnalyticsEvent } from '@/constants/analytics'; import { ButtonSize } from '@/constants/buttons'; import { STRING_KEYS } from '@/constants/localization'; import { isMainnet } from '@/constants/networks'; @@ -55,6 +56,8 @@ import { getNobleChainId } from '@/lib/squid'; import { TokenSelectMenu } from './TokenSelectMenu'; import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt'; +import { validateCosmosAddress } from '@/lib/addressUtils'; +import { track } from '@/lib/analytics'; export const WithdrawForm = () => { const stringGetter = useStringGetter(); @@ -68,6 +71,7 @@ export const WithdrawForm = () => { const { requestPayload, token, + exchange, chain: chainIdStr, address: toAddress, resources, @@ -179,18 +183,27 @@ export const WithdrawForm = () => { requestPayload.data, isCctp ); - if (txHash) { + const nobleChainId = getNobleChainId(); + const toChainId = Boolean(exchange) ? nobleChainId : chainIdStr || undefined; + if (txHash && toChainId) { addTransferNotification({ txHash: txHash, type: TransferNotificationTypes.Withdrawal, - fromChainId: !isCctp ? selectedDydxChainId : getNobleChainId(), - toChainId: chainIdStr || undefined, + fromChainId: !isCctp ? selectedDydxChainId : nobleChainId, + toChainId, toAmount: debouncedAmountBN.toNumber(), triggeredAt: Date.now(), isCctp, + isExchange: Boolean(exchange), }); abacusStateManager.clearTransferInputValues(); setWithdrawAmount(''); + + track(AnalyticsEvent.TransferWithdraw, { + chainId: toChainId, + tokenAddress: toToken?.address || undefined, + tokenSymbol: toToken?.symbol || undefined, + }); } } } catch (error) { @@ -213,7 +226,17 @@ export const WithdrawForm = () => { setIsLoading(false); } }, - [requestPayload, debouncedAmountBN, chainIdStr, toAddress, screenAddresses, stringGetter] + [ + requestPayload, + debouncedAmountBN, + chainIdStr, + toAddress, + selectedDydxChainId, + exchange, + toToken, + screenAddresses, + stringGetter, + ] ); const onChangeAddress = useCallback((e: ChangeEvent) => { @@ -246,13 +269,20 @@ export const WithdrawForm = () => { setWithdrawAmount(freeCollateralBN.toString()); }, [freeCollateralBN, setWithdrawAmount]); - const onSelectChain = useCallback((chain: string) => { - if (chain) { - abacusStateManager.setTransferValue({ - field: TransferInputField.chain, - value: chain, - }); + const onSelectNetwork = useCallback((name: string, type: 'chain' | 'exchange') => { + if (name) { setWithdrawAmount(''); + if (type === 'chain') { + abacusStateManager.setTransferValue({ + field: TransferInputField.chain, + value: name, + }); + } else { + abacusStateManager.setTransferValue({ + field: TransferInputField.exchange, + value: name, + }); + } } }, []); @@ -313,7 +343,7 @@ export const WithdrawForm = () => { }); if (debouncedAmountBN) { - if (!chainIdStr) { + if (!chainIdStr && !exchange) { return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN }); } else if (!toToken) { return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ASSET }); @@ -360,14 +390,19 @@ export const WithdrawForm = () => { summary, ]); + const isInvalidNobleAddress = Boolean( + exchange && toAddress && !validateCosmosAddress(toAddress, 'noble') + ); + const isDisabled = !!errorMessage || !toToken || - !chainIdStr || + (!chainIdStr && !exchange) || !toAddress || debouncedAmountBN.isNaN() || debouncedAmountBN.isZero() || - isLoading; + isLoading || + isInvalidNobleAddress; return ( @@ -385,12 +420,21 @@ export const WithdrawForm = () => { } /> - + {isInvalidNobleAddress && ( + + {stringGetter({ key: STRING_KEYS.NOBLE_ADDRESS_VALIDATION })} + + )} + , subitems: feeSubitems, }, - { + !exchange && { key: 'exchange-rate', label: {stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}, value: withdrawToken && typeof summary?.exchangeRate === 'number' && ( @@ -133,7 +135,9 @@ export const WithdrawButtonAndReceipt = ({ {withdrawToken && {withdrawToken?.symbol}} ), - value: , + value: ( + + ), subitems: [ { key: 'minimum-amount-received', @@ -144,13 +148,17 @@ export const WithdrawButtonAndReceipt = ({ ), value: ( - + ), tooltip: 'minimum-amount-received', }, ], }, - { + !exchange && { key: 'slippage', label: {stringGetter({ key: STRING_KEYS.MAX_SLIPPAGE })}, value: ( @@ -175,7 +183,7 @@ export const WithdrawButtonAndReceipt = ({ /> ), }, - ]; + ].filter(isTruthy); const isFormValid = !isDisabled && !isEditingSlippage; diff --git a/src/views/forms/NobleDeposit.tsx b/src/views/forms/NobleDeposit.tsx index a965c3cb5..f62e2f7d9 100644 --- a/src/views/forms/NobleDeposit.tsx +++ b/src/views/forms/NobleDeposit.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import styled, { type AnyStyledComponent } from 'styled-components'; +import styled, { type AnyStyledComponent, css } from 'styled-components'; import { OpacityToken } from '@/constants/styles/base'; import { STRING_KEYS } from '@/constants/localization'; @@ -10,7 +10,6 @@ import { useAccounts, useStringGetter } from '@/hooks'; import { CopyButton } from '@/components/CopyButton'; import { QrCode } from '@/components/QrCode'; import { Checkbox } from '@/components/Checkbox'; -import { Icon, IconName } from '@/components/Icon'; import { TimeoutButton } from '@/components/TimeoutButton'; import { WithDetailsReceipt } from '@/components/WithDetailsReceipt'; import { WithReceipt } from '@/components/WithReceipt'; @@ -19,6 +18,7 @@ import { generateFadedColorVariant } from '@/lib/styles'; export const NobleDeposit = () => { const [hasAcknowledged, setHasAcknowledged] = useState(false); + const [hasTimedout, setHasTimedout] = useState(false); const stringGetter = useStringGetter(); const { nobleAddress } = useAccounts(); @@ -30,23 +30,21 @@ export const NobleDeposit = () => { { key: 'nobleAddress', label: stringGetter({ key: STRING_KEYS.NOBLE_ADDRESS }), - value: nobleAddress, + value: + hasAcknowledged && hasTimedout + ? nobleAddress + : stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_TO_REVEAL }), }, ]} > - - - + - - - - - -

{stringGetter({ key: STRING_KEYS.NOBLE_WARNING })}

-
- @@ -63,7 +61,12 @@ export const NobleDeposit = () => { > } + onTimeOut={() => setHasTimedout(true)} + slotFinal={ + + {!hasAcknowledged ? stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_RISKS }) : undefined} + + } /> @@ -82,23 +85,14 @@ Styled.WithReceipt = styled(WithReceipt)` --withReceipt-backgroundColor: var(--color-layer-2); `; -Styled.QrCodeContainer = styled.div` - display: flex; - justify-content: center; +Styled.QrCode = styled(QrCode)<{ blurred: boolean }>` + border-radius: 0.5em; - padding: 0.5rem; - - background-color: var(--color-layer-2); - border-radius: 0.5rem; -`; - -Styled.QrCode = styled(QrCode)` - max-height: 20rem; - width: fit-content; - - svg { - max-height: 20rem; - } + ${({ blurred }) => + blurred && + css` + filter: blur(8px); + `} `; Styled.CheckboxContainer = styled.div` diff --git a/src/views/notifications/TransferStatusNotification/index.tsx b/src/views/notifications/TransferStatusNotification/index.tsx index 36a0bbebf..eb8c5abef 100644 --- a/src/views/notifications/TransferStatusNotification/index.tsx +++ b/src/views/notifications/TransferStatusNotification/index.tsx @@ -40,7 +40,7 @@ export const TransferStatusNotification = ({ const stringGetter = useStringGetter(); const [open, setOpen] = useState(false); const [secondsLeft, setSecondsLeft] = useState(0); - const { fromChainId, status, txHash, toAmount } = transfer; + const { fromChainId, status, txHash, toAmount, isExchange } = transfer; // @ts-ignore status.errors is not in the type definition but can be returned const error = status?.errors?.length ? status?.errors[0] : status?.error; @@ -54,6 +54,8 @@ export const TransferStatusNotification = ({ useInterval({ callback: updateSecondsLeft }); + const isComplete = status?.squidTransactionStatus === 'success' || isExchange; + const inProgressStatusString = type === TransferNotificationTypes.Deposit ? secondsLeft > 0 @@ -65,10 +67,10 @@ export const TransferStatusNotification = ({ const statusString = type === TransferNotificationTypes.Deposit - ? status?.squidTransactionStatus === 'success' + ? isComplete ? STRING_KEYS.DEPOSIT_COMPLETE : inProgressStatusString - : status?.squidTransactionStatus === 'success' + : isComplete ? STRING_KEYS.WITHDRAW_COMPLETE : inProgressStatusString; @@ -108,12 +110,12 @@ export const TransferStatusNotification = ({ slotIcon={isToast && slotIcon} slotTitle={slotTitle} slotCustomContent={ - !status ? ( + !status && !isExchange ? ( ) : ( {content} - {!isToast && status?.squidTransactionStatus !== 'success' && !hasError && ( + {!isToast && !isComplete && !hasError && ( )}