Compare commits

..
Author SHA1 Message Date
aforaleka ed7db77a34 fix network as chain id 2024-02-16 23:31:47 -05:00
aforaleka 854f12fcf1 fix parsing error and surface tx broadcast error 2024-02-16 17:33:50 -05:00
22 changed files with 126 additions and 250 deletions
+2 -2
View File
@@ -40,9 +40,9 @@
"@cosmjs/proto-signing": "^0.32.1", "@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1", "@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1", "@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "^1.4.6", "@dydxprotocol/v4-abacus": "^1.4.5",
"@dydxprotocol/v4-client-js": "^1.0.20", "@dydxprotocol/v4-client-js": "^1.0.20",
"@dydxprotocol/v4-localization": "^1.1.31", "@dydxprotocol/v4-localization": "^1.1.30",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3", "@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
+8 -8
View File
@@ -26,14 +26,14 @@ dependencies:
specifier: ^0.32.1 specifier: ^0.32.1
version: 0.32.2 version: 0.32.2
'@dydxprotocol/v4-abacus': '@dydxprotocol/v4-abacus':
specifier: ^1.4.6 specifier: ^1.4.5
version: 1.4.6 version: 1.4.5
'@dydxprotocol/v4-client-js': '@dydxprotocol/v4-client-js':
specifier: ^1.0.20 specifier: ^1.0.20
version: 1.0.20 version: 1.0.20
'@dydxprotocol/v4-localization': '@dydxprotocol/v4-localization':
specifier: ^1.1.31 specifier: ^1.1.30
version: 1.1.31 version: 1.1.30
'@ethersproject/providers': '@ethersproject/providers':
specifier: ^5.7.2 specifier: ^5.7.2
version: 5.7.2 version: 5.7.2
@@ -1286,8 +1286,8 @@ packages:
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==} resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
dev: false dev: false
/@dydxprotocol/v4-abacus@1.4.6: /@dydxprotocol/v4-abacus@1.4.5:
resolution: {integrity: sha512-qYq+4TizcMMxYVXckn0LCucWBe5N9ZNtD1XnowCAuBUUifHSgMGvao5OeZIKMgNM/udSKOXLss4zLy6dH/G2SA==} resolution: {integrity: sha512-LhJmpIaUkHCsSiHx+jdk+59euvGp2E+gGFelpfmOYpH1+enZgEXDQvWsgHSFEQxYP3mRiGG4uo+ZYNGdvffg7g==}
dev: false dev: false
/@dydxprotocol/v4-client-js@1.0.20: /@dydxprotocol/v4-client-js@1.0.20:
@@ -1319,8 +1319,8 @@ packages:
- utf-8-validate - utf-8-validate
dev: false dev: false
/@dydxprotocol/v4-localization@1.1.31: /@dydxprotocol/v4-localization@1.1.30:
resolution: {integrity: sha512-plJVIgFAKq9/hA/gk5GgKgCQFsH3pNtDWfG/yHLDXyiGX0M0mMEi1bTNVs4podFVoHJu1nSL9YPFlpJ00FteGw==} resolution: {integrity: sha512-TZfWWRSOxcjLHs972wlJVVHkE7+DVqAUnGZSs24HYHsPtUkPhZiNXMOA2Vk9YddQxumhM79xIRH0cmJSe5DDUg==}
dev: false dev: false
/@dydxprotocol/v4-proto@4.0.0-dev.0: /@dydxprotocol/v4-proto@4.0.0-dev.0:
+3 -5
View File
@@ -39,7 +39,7 @@ export const SearchSelectMenu = ({
disabled, disabled,
label, label,
items, items,
withSearch = true, withSearch,
withReceiptItems, withReceiptItems,
}: SearchSelectMenuProps) => { }: SearchSelectMenuProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -77,7 +77,6 @@ export const SearchSelectMenu = ({
withSearch={withSearch} withSearch={withSearch}
onItemSelected={() => setOpen(false)} onItemSelected={() => setOpen(false)}
withStickyLayout withStickyLayout
$withSearch={withSearch}
/> />
</Styled.Popover> </Styled.Popover>
</Styled.WithDetailsReceipt> </Styled.WithDetailsReceipt>
@@ -128,7 +127,7 @@ Styled.Popover = styled(Popover)`
box-shadow: none; box-shadow: none;
`; `;
Styled.ComboboxMenu = styled(ComboboxMenu)<{ $withSearch?: boolean }>` Styled.ComboboxMenu = styled(ComboboxMenu)`
${layoutMixins.withInnerHorizontalBorders} ${layoutMixins.withInnerHorizontalBorders}
--comboboxMenu-backgroundColor: var(--color-layer-4); --comboboxMenu-backgroundColor: var(--color-layer-4);
@@ -141,8 +140,7 @@ Styled.ComboboxMenu = styled(ComboboxMenu)<{ $withSearch?: boolean }>`
--comboboxMenu-item-checked-textColor: var(--color-text-2); --comboboxMenu-item-checked-textColor: var(--color-text-2);
--comboboxMenu-item-highlighted-textColor: var(--color-text-2); --comboboxMenu-item-highlighted-textColor: var(--color-text-2);
--stickyArea1-topHeight: ${({ $withSearch }) => --stickyArea1-topHeight: var(--form-input-height);
!$withSearch ? '0' : 'var(--form-input-height)'};
input:focus-visible { input:focus-visible {
outline: none; outline: none;
+1 -8
View File
@@ -1,4 +1,4 @@
import { type ReactNode, useState, useEffect } from 'react'; import { type ReactNode, useState } from 'react';
import { ButtonAction, ButtonState } from '@/constants/buttons'; import { ButtonAction, ButtonState } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
@@ -8,7 +8,6 @@ import { Button, type ButtonStateConfig, type ButtonProps } from '@/components/B
type ElementProps = { type ElementProps = {
timeoutInSeconds: number; timeoutInSeconds: number;
onTimeOut?: () => void;
slotFinal?: ReactNode; slotFinal?: ReactNode;
} & ButtonProps; } & ButtonProps;
@@ -17,7 +16,6 @@ export type TimeoutButtonProps = ElementProps;
export const TimeoutButton = ({ export const TimeoutButton = ({
children, children,
timeoutInSeconds, timeoutInSeconds,
onTimeOut,
slotFinal, slotFinal,
...otherProps ...otherProps
}: TimeoutButtonProps) => { }: TimeoutButtonProps) => {
@@ -27,11 +25,6 @@ export const TimeoutButton = ({
const secondsLeft = Math.max(0, (timeoutDeadline - now) / 1000); const secondsLeft = Math.max(0, (timeoutDeadline - now) / 1000);
useEffect(() => {
if (secondsLeft > 0) return;
onTimeOut?.();
}, [secondsLeft]);
if (slotFinal && secondsLeft <= 0) return slotFinal; if (slotFinal && secondsLeft <= 0) return slotFinal;
return ( return (
+2 -10
View File
@@ -144,17 +144,9 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
validatorUrl: string; validatorUrl: string;
} }
: T extends AnalyticsEvent.TransferDeposit : T extends AnalyticsEvent.TransferDeposit
? { ? {}
chainId?: string;
tokenAddress?: string;
tokenSymbol?: string;
}
: T extends AnalyticsEvent.TransferWithdraw : T extends AnalyticsEvent.TransferWithdraw
? { ? {}
chainId?: string;
tokenAddress?: string;
tokenSymbol?: string;
}
: // Trading : // Trading
T extends AnalyticsEvent.TradeOrderTypeSelected T extends AnalyticsEvent.TradeOrderTypeSelected
? { ? {
-1
View File
@@ -140,7 +140,6 @@ export type TransferNotifcation = {
isCctp?: boolean; isCctp?: boolean;
errorCount?: number; errorCount?: number;
status?: StatusResponse; status?: StatusResponse;
isExchange?: boolean;
}; };
/** /**
+9 -15
View File
@@ -82,30 +82,24 @@ const useLocalNotificationsContext = () => {
isCctp, isCctp,
errorCount, errorCount,
status: currentStatus, status: currentStatus,
isExchange,
} = transferNotification; } = transferNotification;
const hasErrors = // @ts-ignore status.errors is not in the type definition but can be returned
// @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
// also error can some time come back as an empty object so we need to ignore for that const hasErrors = !!currentStatus?.errors ||
!!currentStatus?.errors || (currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
if ( if (
!isExchange &&
!hasErrors && !hasErrors &&
(!currentStatus?.squidTransactionStatus || (!currentStatus?.squidTransactionStatus ||
currentStatus?.squidTransactionStatus === 'ongoing') currentStatus?.squidTransactionStatus === 'ongoing')
) { ) {
try { try {
const status = await fetchSquidStatus( const status = await fetchSquidStatus({
{ transactionId: txHash,
transactionId: txHash, toChainId,
toChainId, fromChainId,
fromChainId, }, isCctp);
},
isCctp
);
if (status) { if (status) {
transferNotification.status = status; transferNotification.status = status;
+2 -3
View File
@@ -179,9 +179,8 @@ export const notificationTypes: NotificationTypeConfig[] = [
useEffect(() => { useEffect(() => {
for (const transfer of transferNotifications) { for (const transfer of transferNotifications) {
const { fromChainId, status, txHash, toAmount, type, isExchange } = transfer; const { fromChainId, status, txHash, toAmount, type } = transfer;
const isFinished = const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
(Boolean(status) && status?.squidTransactionStatus !== 'ongoing') || isExchange;
const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />; const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;
const transferType = const transferType =
+6 -7
View File
@@ -34,7 +34,7 @@ import {
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics'; import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade'; import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
import { DydxNetwork, isTestnet } from '@/constants/networks'; import { DydxChainId, isTestnet } from '@/constants/networks';
import { RootStore } from '@/state/_store'; import { RootStore } from '@/state/_store';
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account'; import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
@@ -44,7 +44,10 @@ import { StatefulOrderError } from '../errors';
import { bytesToBigInt } from '../numbers'; import { bytesToBigInt } from '../numbers';
import { log } from '../telemetry'; import { log } from '../telemetry';
import { hashFromTx, getMintscanTxLink } from '../txUtils'; import { hashFromTx, getMintscanTxLink } from '../txUtils';
import { getDydxChainIdFromNetwork } from '../network';
(BigInt.prototype as any).toJSON = function () {
return this.toString();
};
class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol { class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
private compositeClient: CompositeClient | undefined; private compositeClient: CompositeClient | undefined;
@@ -239,10 +242,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
if (isTestnet) { if (isTestnet) {
console.log( console.log(
getMintscanTxLink( getMintscanTxLink(this.compositeClient.network.getString() as DydxChainId, hash)
getDydxChainIdFromNetwork(this.compositeClient.network.getString() as DydxNetwork),
hash
)
); );
} else console.log(`txHash: ${hash}`); } else console.log(`txHash: ${hash}`);
@@ -538,7 +538,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
const result = await this.cctpWithdraw(params); const result = await this.cctpWithdraw(params);
callback(result); callback(result);
break; break;
break;
} }
default: { default: {
break; break;
-19
View File
@@ -25,22 +25,3 @@ export function convertBech32Address({
}): string { }): string {
return toBech32(bech32Prefix, fromHex(toHex(fromBech32(address).data))); 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;
}
}
+1 -5
View File
@@ -1,8 +1,4 @@
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP, type DydxChainId } from '@/constants/networks'; import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP, type DydxChainId } from '@/constants/networks';
export const validateAgainstAvailableEnvironments = (value: DydxNetwork) => export const validateAgainstAvailableEnvironments = (value: DydxNetwork) =>
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value); Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
export const getDydxChainIdFromNetwork = (network: DydxNetwork) => {
return ENVIRONMENT_CONFIG_MAP[network].dydxChainId as DydxChainId;
};
+2 -6
View File
@@ -19,17 +19,13 @@ class TestFlags {
return !!this.queryParams.displayinitializingmarkets; return !!this.queryParams.displayinitializingmarkets;
} }
get addressOverride(): string { get addressOverride():string {
return this.queryParams.address; return this.queryParams.address;
} }
get showTradingRewards() { get showTradingRewards() {
return !!this.queryParams.tradingrewards; return !!this.queryParams.tradingrewards;
} }
get showCexWithdrawal() {
return !!this.queryParams.cexwithdrawal;
}
} }
export const testFlags = new TestFlags(); export const testFlags = new TestFlags();
+3 -1
View File
@@ -13,6 +13,8 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { isMobile } = useBreakpoints(); const { isMobile } = useBreakpoints();
const closeDialog = () => setIsOpen?.(false);
return ( return (
<Dialog <Dialog
isOpen isOpen
@@ -20,7 +22,7 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
title={stringGetter({ key: STRING_KEYS.DEPOSIT })} title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default} placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
> >
<DepositDialogContent /> <DepositDialogContent onDeposit={closeDialog} />
</Dialog> </Dialog>
); );
}; };
@@ -2,7 +2,6 @@ import { useEffect, useState } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components'; import styled, { type AnyStyledComponent } from 'styled-components';
import { TransferInputField, TransferType } from '@/constants/abacus'; import { TransferInputField, TransferType } from '@/constants/abacus';
import { AnalyticsEvent } from '@/constants/analytics';
import { isMainnet } from '@/constants/networks'; import { isMainnet } from '@/constants/networks';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -10,7 +9,6 @@ import { DepositForm } from '@/views/forms/AccountManagementForms/DepositForm';
import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm'; import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm';
import abacusStateManager from '@/lib/abacus'; import abacusStateManager from '@/lib/abacus';
import { track } from '@/lib/analytics';
type ElementProps = { type ElementProps = {
onDeposit?: () => void; onDeposit?: () => void;
@@ -36,19 +34,9 @@ export const DepositDialogContent = ({ onDeposit }: ElementProps) => {
return ( return (
<Styled.Content> <Styled.Content>
{isMainnet || !showFaucet ? ( {isMainnet || !showFaucet ? (
<DepositForm <DepositForm onDeposit={onDeposit} />
onDeposit={(event) => {
track(AnalyticsEvent.TransferDeposit, event);
onDeposit?.();
}}
/>
) : ( ) : (
<TestnetDepositForm <TestnetDepositForm onDeposit={onDeposit} />
onDeposit={() => {
track(AnalyticsEvent.TransferFaucet);
onDeposit?.();
}}
/>
)} )}
{!isMainnet && ( {!isMainnet && (
<Styled.TextToggle onClick={() => setShowFaucet(!showFaucet)}> <Styled.TextToggle onClick={() => setShowFaucet(!showFaucet)}>
+2 -2
View File
@@ -105,8 +105,8 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
<Styled.Content> <Styled.Content>
{isMainnet ? ( {isMainnet ? (
<DepositForm <DepositForm
onDeposit={(event) => { onDeposit={() => {
track(AnalyticsEvent.TransferDeposit, event); track(AnalyticsEvent.TransferDeposit);
}} }}
/> />
) : ( ) : (
@@ -7,7 +7,6 @@ import { Abi, parseUnits } from 'viem';
import erc20 from '@/abi/erc20.json'; import erc20 from '@/abi/erc20.json';
import erc20_usdt from '@/abi/erc20_usdt.json'; import erc20_usdt from '@/abi/erc20_usdt.json';
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus'; import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
import { AnalyticsEvent, AnalyticsEventData } from '@/constants/analytics';
import { AlertType } from '@/constants/alerts'; import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons'; import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
@@ -49,7 +48,7 @@ import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
import { NobleDeposit } from '../NobleDeposit'; import { NobleDeposit } from '../NobleDeposit';
type DepositFormProps = { type DepositFormProps = {
onDeposit?: (event?: AnalyticsEventData<AnalyticsEvent.TransferDeposit>) => void; onDeposit?: () => void;
onError?: () => void; onError?: () => void;
}; };
@@ -133,7 +132,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
if (error) onError?.(); if (error) onError?.();
}, [error]); }, [error]);
const onSelectNetwork = useCallback((name: string, type: 'chain' | 'exchange') => { const onSelectChain = useCallback((name: string, type: 'chain' | 'exchange') => {
if (name) { if (name) {
abacusStateManager.clearTransferInputValues(); abacusStateManager.clearTransferInputValues();
setFromAmount(''); setFromAmount('');
@@ -259,6 +258,8 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
}; };
const txHash = await signerWagmi.sendTransaction(tx); const txHash = await signerWagmi.sendTransaction(tx);
onDeposit?.();
if (txHash) { if (txHash) {
addTransferNotification({ addTransferNotification({
txHash: txHash, txHash: txHash,
@@ -270,12 +271,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
}); });
abacusStateManager.clearTransferInputValues(); abacusStateManager.clearTransferInputValues();
setFromAmount(''); setFromAmount('');
onDeposit?.({
chainId: chainIdStr || undefined,
tokenAddress: sourceToken?.address || undefined,
tokenSymbol: sourceToken?.symbol || undefined,
});
} }
} catch (error) { } catch (error) {
log('DepositForm/onSubmit', error); log('DepositForm/onSubmit', error);
@@ -284,7 +279,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
setIsLoading(false); setIsLoading(false);
} }
}, },
[requestPayload, signerWagmi, chainId, sourceToken, sourceChain] [requestPayload, signerWagmi, chainId]
); );
const amountInputReceipt = [ const amountInputReceipt = [
@@ -383,7 +378,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
<SourceSelectMenu <SourceSelectMenu
selectedChain={chainIdStr || undefined} selectedChain={chainIdStr || undefined}
selectedExchange={exchange || undefined} selectedExchange={exchange || undefined}
onSelect={onSelectNetwork} onSelect={onSelectChain}
/> />
{exchange && nobleAddress ? ( {exchange && nobleAddress ? (
<NobleDeposit /> <NobleDeposit />
@@ -14,7 +14,6 @@ import { popoverMixins } from '@/styles/popoverMixins';
import { getTransferInputs } from '@/state/inputsSelectors'; import { getTransferInputs } from '@/state/inputsSelectors';
import { isTruthy } from '@/lib/isTruthy'; import { isTruthy } from '@/lib/isTruthy';
import { testFlags } from '@/lib/testFlags';
type ElementProps = { type ElementProps = {
label?: string; label?: string;
@@ -63,12 +62,11 @@ export const SourceSelectMenu = ({
return ( return (
<SearchSelectMenu <SearchSelectMenu
items={[ items={[
exchangeItems.length > 0 && exchangeItems.length > 0 && {
(testFlags.showCexWithdrawal || type === TransferType.deposit) && { group: 'exchanges',
group: 'exchanges', groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }),
groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }), items: exchangeItems,
items: exchangeItems, },
},
chainItems.length > 0 && { chainItems.length > 0 && {
group: 'chains', group: 'chains',
groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }), groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }),
@@ -17,10 +17,9 @@ import { getTransferInputs } from '@/state/inputsSelectors';
type ElementProps = { type ElementProps = {
selectedToken?: TransferInputTokenResource; selectedToken?: TransferInputTokenResource;
onSelectToken: (token: TransferInputTokenResource) => void; onSelectToken: (token: TransferInputTokenResource) => void;
isExchange?: boolean;
}; };
export const TokenSelectMenu = ({ selectedToken, onSelectToken, isExchange }: ElementProps) => { export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { type, depositOptions, withdrawalOptions, resources } = const { type, depositOptions, withdrawalOptions, resources } =
useSelector(getTransferInputs, shallowEqual) || {}; useSelector(getTransferInputs, shallowEqual) || {};
@@ -48,24 +47,19 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken, isExchange }: El
}, },
]} ]}
label={stringGetter({ key: STRING_KEYS.ASSET })} label={stringGetter({ key: STRING_KEYS.ASSET })}
withSearch={!isExchange} withReceiptItems={[
withReceiptItems={ {
!isExchange key: 'swap',
? [ label: stringGetter({ key: STRING_KEYS.SWAP }),
{ value: selectedToken && (
key: 'swap', <>
label: stringGetter({ key: STRING_KEYS.SWAP }), <Tag>{type === TransferType.deposit ? selectedToken?.symbol : 'USDC'}</Tag>
value: selectedToken && ( <DiffArrow />
<> <Tag>{type === TransferType.deposit ? 'USDC' : selectedToken?.symbol}</Tag>
<Tag>{type === TransferType.deposit ? selectedToken?.symbol : 'USDC'}</Tag> </>
<DiffArrow /> ),
<Tag>{type === TransferType.deposit ? 'USDC' : selectedToken?.symbol}</Tag> },
</> ]}
),
},
]
: undefined
}
> >
<Styled.AssetRow> <Styled.AssetRow>
{selectedToken ? ( {selectedToken ? (
@@ -7,7 +7,6 @@ import { isAddress } from 'viem';
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus'; import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
import { AlertType } from '@/constants/alerts'; import { AlertType } from '@/constants/alerts';
import { AnalyticsEvent } from '@/constants/analytics';
import { ButtonSize } from '@/constants/buttons'; import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { isMainnet } from '@/constants/networks'; import { isMainnet } from '@/constants/networks';
@@ -56,8 +55,6 @@ import { getNobleChainId } from '@/lib/squid';
import { TokenSelectMenu } from './TokenSelectMenu'; import { TokenSelectMenu } from './TokenSelectMenu';
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt'; import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
import { validateCosmosAddress } from '@/lib/addressUtils';
import { track } from '@/lib/analytics';
export const WithdrawForm = () => { export const WithdrawForm = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
@@ -71,7 +68,6 @@ export const WithdrawForm = () => {
const { const {
requestPayload, requestPayload,
token, token,
exchange,
chain: chainIdStr, chain: chainIdStr,
address: toAddress, address: toAddress,
resources, resources,
@@ -183,27 +179,18 @@ export const WithdrawForm = () => {
requestPayload.data, requestPayload.data,
isCctp isCctp
); );
const nobleChainId = getNobleChainId(); if (txHash) {
const toChainId = Boolean(exchange) ? nobleChainId : chainIdStr || undefined;
if (txHash && toChainId) {
addTransferNotification({ addTransferNotification({
txHash: txHash, txHash: txHash,
type: TransferNotificationTypes.Withdrawal, type: TransferNotificationTypes.Withdrawal,
fromChainId: !isCctp ? selectedDydxChainId : nobleChainId, fromChainId: !isCctp ? selectedDydxChainId : getNobleChainId(),
toChainId, toChainId: chainIdStr || undefined,
toAmount: debouncedAmountBN.toNumber(), toAmount: debouncedAmountBN.toNumber(),
triggeredAt: Date.now(), triggeredAt: Date.now(),
isCctp, isCctp,
isExchange: Boolean(exchange),
}); });
abacusStateManager.clearTransferInputValues(); abacusStateManager.clearTransferInputValues();
setWithdrawAmount(''); setWithdrawAmount('');
track(AnalyticsEvent.TransferWithdraw, {
chainId: toChainId,
tokenAddress: toToken?.address || undefined,
tokenSymbol: toToken?.symbol || undefined,
});
} }
} }
} catch (error) { } catch (error) {
@@ -226,17 +213,7 @@ export const WithdrawForm = () => {
setIsLoading(false); setIsLoading(false);
} }
}, },
[ [requestPayload, debouncedAmountBN, chainIdStr, toAddress, screenAddresses, stringGetter]
requestPayload,
debouncedAmountBN,
chainIdStr,
toAddress,
selectedDydxChainId,
exchange,
toToken,
screenAddresses,
stringGetter,
]
); );
const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => { const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => {
@@ -269,20 +246,13 @@ export const WithdrawForm = () => {
setWithdrawAmount(freeCollateralBN.toString()); setWithdrawAmount(freeCollateralBN.toString());
}, [freeCollateralBN, setWithdrawAmount]); }, [freeCollateralBN, setWithdrawAmount]);
const onSelectNetwork = useCallback((name: string, type: 'chain' | 'exchange') => { const onSelectChain = useCallback((chain: string) => {
if (name) { if (chain) {
abacusStateManager.setTransferValue({
field: TransferInputField.chain,
value: chain,
});
setWithdrawAmount(''); setWithdrawAmount('');
if (type === 'chain') {
abacusStateManager.setTransferValue({
field: TransferInputField.chain,
value: name,
});
} else {
abacusStateManager.setTransferValue({
field: TransferInputField.exchange,
value: name,
});
}
} }
}, []); }, []);
@@ -343,7 +313,7 @@ export const WithdrawForm = () => {
}); });
if (debouncedAmountBN) { if (debouncedAmountBN) {
if (!chainIdStr && !exchange) { if (!chainIdStr) {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN }); return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN });
} else if (!toToken) { } else if (!toToken) {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ASSET }); return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ASSET });
@@ -390,19 +360,14 @@ export const WithdrawForm = () => {
summary, summary,
]); ]);
const isInvalidNobleAddress = Boolean(
exchange && toAddress && !validateCosmosAddress(toAddress, 'noble')
);
const isDisabled = const isDisabled =
!!errorMessage || !!errorMessage ||
!toToken || !toToken ||
(!chainIdStr && !exchange) || !chainIdStr ||
!toAddress || !toAddress ||
debouncedAmountBN.isNaN() || debouncedAmountBN.isNaN() ||
debouncedAmountBN.isZero() || debouncedAmountBN.isZero() ||
isLoading || isLoading;
isInvalidNobleAddress;
return ( return (
<Styled.Form onSubmit={onSubmit}> <Styled.Form onSubmit={onSubmit}>
@@ -420,21 +385,12 @@ export const WithdrawForm = () => {
} }
/> />
<SourceSelectMenu <SourceSelectMenu
selectedExchange={exchange || undefined} label={stringGetter({ key: STRING_KEYS.NETWORK })}
selectedChain={chainIdStr || undefined} selectedChain={chainIdStr || undefined}
onSelect={onSelectNetwork} onSelect={onSelectChain}
/> />
</Styled.DestinationRow> </Styled.DestinationRow>
{isInvalidNobleAddress && ( <TokenSelectMenu selectedToken={toToken || undefined} onSelectToken={onSelectToken} />
<AlertMessage type={AlertType.Error}>
{stringGetter({ key: STRING_KEYS.NOBLE_ADDRESS_VALIDATION })}
</AlertMessage>
)}
<TokenSelectMenu
selectedToken={toToken || undefined}
onSelectToken={onSelectToken}
isExchange={Boolean(exchange)}
/>
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}> <Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
<FormInput <FormInput
type={InputType.Number} type={InputType.Number}
@@ -27,8 +27,6 @@ import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { getSubaccount } from '@/state/accountSelectors'; import { getSubaccount } from '@/state/accountSelectors';
import { getTransferInputs } from '@/state/inputsSelectors'; import { getTransferInputs } from '@/state/inputsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { SlippageEditor } from '../SlippageEditor'; import { SlippageEditor } from '../SlippageEditor';
type ElementProps = { type ElementProps = {
@@ -57,7 +55,7 @@ export const WithdrawButtonAndReceipt = ({
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { leverage } = useSelector(getSubaccount, shallowEqual) || {}; const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
const { summary, requestPayload, exchange } = useSelector(getTransferInputs, shallowEqual) || {}; const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual); const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { usdcLabel } = useTokenConfigs(); const { usdcLabel } = useTokenConfigs();
@@ -94,7 +92,7 @@ export const WithdrawButtonAndReceipt = ({
value: <Output type={OutputType.Fiat} value={totalFees} />, value: <Output type={OutputType.Fiat} value={totalFees} />,
subitems: feeSubitems, subitems: feeSubitems,
}, },
!exchange && { {
key: 'exchange-rate', key: 'exchange-rate',
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>, label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
value: withdrawToken && typeof summary?.exchangeRate === 'number' && ( value: withdrawToken && typeof summary?.exchangeRate === 'number' && (
@@ -135,9 +133,7 @@ export const WithdrawButtonAndReceipt = ({
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>} {withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
</span> </span>
), ),
value: ( value: <Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />,
<Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />
),
subitems: [ subitems: [
{ {
key: 'minimum-amount-received', key: 'minimum-amount-received',
@@ -148,17 +144,13 @@ export const WithdrawButtonAndReceipt = ({
</span> </span>
), ),
value: ( value: (
<Output <Output type={OutputType.Asset} value={summary?.toAmountMin} fractionDigits={TOKEN_DECIMALS} />
type={OutputType.Asset}
value={summary?.toAmountMin}
fractionDigits={TOKEN_DECIMALS}
/>
), ),
tooltip: 'minimum-amount-received', tooltip: 'minimum-amount-received',
}, },
], ],
}, },
!exchange && { {
key: 'slippage', key: 'slippage',
label: <span>{stringGetter({ key: STRING_KEYS.MAX_SLIPPAGE })}</span>, label: <span>{stringGetter({ key: STRING_KEYS.MAX_SLIPPAGE })}</span>,
value: ( value: (
@@ -183,7 +175,7 @@ export const WithdrawButtonAndReceipt = ({
/> />
), ),
}, },
].filter(isTruthy); ];
const isFormValid = !isDisabled && !isEditingSlippage; const isFormValid = !isDisabled && !isEditingSlippage;
+31 -25
View File
@@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import styled, { type AnyStyledComponent, css } from 'styled-components'; import styled, { type AnyStyledComponent } from 'styled-components';
import { OpacityToken } from '@/constants/styles/base'; import { OpacityToken } from '@/constants/styles/base';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
@@ -10,6 +10,7 @@ import { useAccounts, useStringGetter } from '@/hooks';
import { CopyButton } from '@/components/CopyButton'; import { CopyButton } from '@/components/CopyButton';
import { QrCode } from '@/components/QrCode'; import { QrCode } from '@/components/QrCode';
import { Checkbox } from '@/components/Checkbox'; import { Checkbox } from '@/components/Checkbox';
import { Icon, IconName } from '@/components/Icon';
import { TimeoutButton } from '@/components/TimeoutButton'; import { TimeoutButton } from '@/components/TimeoutButton';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt'; import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { WithReceipt } from '@/components/WithReceipt'; import { WithReceipt } from '@/components/WithReceipt';
@@ -18,7 +19,6 @@ import { generateFadedColorVariant } from '@/lib/styles';
export const NobleDeposit = () => { export const NobleDeposit = () => {
const [hasAcknowledged, setHasAcknowledged] = useState(false); const [hasAcknowledged, setHasAcknowledged] = useState(false);
const [hasTimedout, setHasTimedout] = useState(false);
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { nobleAddress } = useAccounts(); const { nobleAddress } = useAccounts();
@@ -30,21 +30,23 @@ export const NobleDeposit = () => {
{ {
key: 'nobleAddress', key: 'nobleAddress',
label: stringGetter({ key: STRING_KEYS.NOBLE_ADDRESS }), label: stringGetter({ key: STRING_KEYS.NOBLE_ADDRESS }),
value: value: nobleAddress,
hasAcknowledged && hasTimedout
? nobleAddress
: stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_TO_REVEAL }),
}, },
]} ]}
> >
<Styled.QrCode <Styled.QrCodeContainer>
hasLogo <Styled.QrCode size={432} value={nobleAddress || ''} />
size={432} </Styled.QrCodeContainer>
value={nobleAddress || ''}
blurred={!hasAcknowledged || !hasTimedout}
/>
</WithDetailsReceipt> </WithDetailsReceipt>
<Styled.WaitingSpan>
<Styled.CautionIconContainer>
<Icon iconName={IconName.CautionCircleStroked} />
</Styled.CautionIconContainer>
<p>{stringGetter({ key: STRING_KEYS.NOBLE_WARNING })}</p>
</Styled.WaitingSpan>
<Styled.WithReceipt <Styled.WithReceipt
slotReceipt={ slotReceipt={
<Styled.CheckboxContainer> <Styled.CheckboxContainer>
@@ -61,12 +63,7 @@ export const NobleDeposit = () => {
> >
<TimeoutButton <TimeoutButton
timeoutInSeconds={8} timeoutInSeconds={8}
onTimeOut={() => setHasTimedout(true)} slotFinal={<CopyButton state={{ isDisabled: !hasAcknowledged }} value={nobleAddress} />}
slotFinal={
<CopyButton state={{ isDisabled: !hasAcknowledged }} value={nobleAddress}>
{!hasAcknowledged ? stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_RISKS }) : undefined}
</CopyButton>
}
/> />
</Styled.WithReceipt> </Styled.WithReceipt>
</> </>
@@ -85,14 +82,23 @@ Styled.WithReceipt = styled(WithReceipt)`
--withReceipt-backgroundColor: var(--color-layer-2); --withReceipt-backgroundColor: var(--color-layer-2);
`; `;
Styled.QrCode = styled(QrCode)<{ blurred: boolean }>` Styled.QrCodeContainer = styled.div`
border-radius: 0.5em; display: flex;
justify-content: center;
${({ blurred }) => padding: 0.5rem;
blurred &&
css` background-color: var(--color-layer-2);
filter: blur(8px); border-radius: 0.5rem;
`} `;
Styled.QrCode = styled(QrCode)`
max-height: 20rem;
width: fit-content;
svg {
max-height: 20rem;
}
`; `;
Styled.CheckboxContainer = styled.div` Styled.CheckboxContainer = styled.div`
@@ -40,7 +40,7 @@ export const TransferStatusNotification = ({
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const [secondsLeft, setSecondsLeft] = useState<number>(0); const [secondsLeft, setSecondsLeft] = useState<number>(0);
const { fromChainId, status, txHash, toAmount, isExchange } = transfer; const { fromChainId, status, txHash, toAmount } = transfer;
// @ts-ignore status.errors is not in the type definition but can be returned // @ts-ignore status.errors is not in the type definition but can be returned
const error = status?.errors?.length ? status?.errors[0] : status?.error; const error = status?.errors?.length ? status?.errors[0] : status?.error;
@@ -54,8 +54,6 @@ export const TransferStatusNotification = ({
useInterval({ callback: updateSecondsLeft }); useInterval({ callback: updateSecondsLeft });
const isComplete = status?.squidTransactionStatus === 'success' || isExchange;
const inProgressStatusString = const inProgressStatusString =
type === TransferNotificationTypes.Deposit type === TransferNotificationTypes.Deposit
? secondsLeft > 0 ? secondsLeft > 0
@@ -67,10 +65,10 @@ export const TransferStatusNotification = ({
const statusString = const statusString =
type === TransferNotificationTypes.Deposit type === TransferNotificationTypes.Deposit
? isComplete ? status?.squidTransactionStatus === 'success'
? STRING_KEYS.DEPOSIT_COMPLETE ? STRING_KEYS.DEPOSIT_COMPLETE
: inProgressStatusString : inProgressStatusString
: isComplete : status?.squidTransactionStatus === 'success'
? STRING_KEYS.WITHDRAW_COMPLETE ? STRING_KEYS.WITHDRAW_COMPLETE
: inProgressStatusString; : inProgressStatusString;
@@ -110,12 +108,12 @@ export const TransferStatusNotification = ({
slotIcon={isToast && slotIcon} slotIcon={isToast && slotIcon}
slotTitle={slotTitle} slotTitle={slotTitle}
slotCustomContent={ slotCustomContent={
!status && !isExchange ? ( !status ? (
<LoadingDots size={3} /> <LoadingDots size={3} />
) : ( ) : (
<Styled.BridgingStatus> <Styled.BridgingStatus>
{content} {content}
{!isToast && !isComplete && !hasError && ( {!isToast && status?.squidTransactionStatus !== 'success' && !hasError && (
<Styled.TransferStatusSteps status={status} type={type} /> <Styled.TransferStatusSteps status={status} type={type} />
)} )}
</Styled.BridgingStatus> </Styled.BridgingStatus>