Skip to content

Commit

Permalink
Cex withdraw (#301)
Browse files Browse the repository at this point in the history
* Coinbase withdrawal and deposit UI

* analytics

* packages bumps

* address comments

* address comments

* add test flag
  • Loading branch information
rosepuppy authored Feb 17, 2024
1 parent b115bf3 commit bff7796
Show file tree
Hide file tree
Showing 20 changed files with 238 additions and 119 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 8 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions src/components/SearchSelectMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const SearchSelectMenu = ({
disabled,
label,
items,
withSearch,
withSearch = true,
withReceiptItems,
}: SearchSelectMenuProps) => {
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -77,6 +77,7 @@ export const SearchSelectMenu = ({
withSearch={withSearch}
onItemSelected={() => setOpen(false)}
withStickyLayout
$withSearch={withSearch}
/>
</Styled.Popover>
</Styled.WithDetailsReceipt>
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
9 changes: 8 additions & 1 deletion src/components/TimeoutButton.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,6 +8,7 @@ import { Button, type ButtonStateConfig, type ButtonProps } from '@/components/B

type ElementProps = {
timeoutInSeconds: number;
onTimeOut?: () => void;
slotFinal?: ReactNode;
} & ButtonProps;

Expand All @@ -16,6 +17,7 @@ export type TimeoutButtonProps = ElementProps;
export const TimeoutButton = ({
children,
timeoutInSeconds,
onTimeOut,
slotFinal,
...otherProps
}: TimeoutButtonProps) => {
Expand All @@ -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 (
Expand Down
12 changes: 10 additions & 2 deletions src/constants/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,17 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
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
? {
Expand Down
1 change: 1 addition & 0 deletions src/constants/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export type TransferNotifcation = {
isCctp?: boolean;
errorCount?: number;
status?: StatusResponse;
isExchange?: boolean;
};

/**
Expand Down
24 changes: 15 additions & 9 deletions src/hooks/useLocalNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/hooks/useNotificationTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;

const transferType =
Expand Down
19 changes: 19 additions & 0 deletions src/lib/addressUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
8 changes: 6 additions & 2 deletions src/lib/testFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
4 changes: 1 addition & 3 deletions src/views/dialogs/DepositDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const { isMobile } = useBreakpoints();

const closeDialog = () => setIsOpen?.(false);

return (
<Dialog
isOpen
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
>
<DepositDialogContent onDeposit={closeDialog} />
<DepositDialogContent />
</Dialog>
);
};
16 changes: 14 additions & 2 deletions src/views/dialogs/DepositDialog/DepositDialogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ 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';

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;
Expand All @@ -34,9 +36,19 @@ export const DepositDialogContent = ({ onDeposit }: ElementProps) => {
return (
<Styled.Content>
{isMainnet || !showFaucet ? (
<DepositForm onDeposit={onDeposit} />
<DepositForm
onDeposit={(event) => {
track(AnalyticsEvent.TransferDeposit, event);
onDeposit?.();
}}
/>
) : (
<TestnetDepositForm onDeposit={onDeposit} />
<TestnetDepositForm
onDeposit={() => {
track(AnalyticsEvent.TransferFaucet);
onDeposit?.();
}}
/>
)}
{!isMainnet && (
<Styled.TextToggle onClick={() => setShowFaucet(!showFaucet)}>
Expand Down
4 changes: 2 additions & 2 deletions src/views/dialogs/OnboardingDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
<Styled.Content>
{isMainnet ? (
<DepositForm
onDeposit={() => {
track(AnalyticsEvent.TransferDeposit);
onDeposit={(event) => {
track(AnalyticsEvent.TransferDeposit, event);
}}
/>
) : (
Expand Down
17 changes: 11 additions & 6 deletions src/views/forms/AccountManagementForms/DepositForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -48,7 +49,7 @@ import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
import { NobleDeposit } from '../NobleDeposit';

type DepositFormProps = {
onDeposit?: () => void;
onDeposit?: (event?: AnalyticsEventData<AnalyticsEvent.TransferDeposit>) => void;
onError?: () => void;
};

Expand Down Expand Up @@ -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('');
Expand Down Expand Up @@ -258,8 +259,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
};
const txHash = await signerWagmi.sendTransaction(tx);

onDeposit?.();

if (txHash) {
addTransferNotification({
txHash: txHash,
Expand All @@ -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);
Expand All @@ -279,7 +284,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
setIsLoading(false);
}
},
[requestPayload, signerWagmi, chainId]
[requestPayload, signerWagmi, chainId, sourceToken, sourceChain]
);

const amountInputReceipt = [
Expand Down Expand Up @@ -378,7 +383,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
<SourceSelectMenu
selectedChain={chainIdStr || undefined}
selectedExchange={exchange || undefined}
onSelect={onSelectChain}
onSelect={onSelectNetwork}
/>
{exchange && nobleAddress ? (
<NobleDeposit />
Expand Down
Loading

0 comments on commit bff7796

Please sign in to comment.