Skip to content

Commit

Permalink
feat: warning for high fees [OTE-621] (#856)
Browse files Browse the repository at this point in the history
  • Loading branch information
yogurtandjam authored Jul 30, 2024
1 parent fdac42a commit a26bb23
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 59 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "1.8.54",
"@dydxprotocol/v4-abacus": "1.8.63",
"@dydxprotocol/v4-client-js": "^1.1.27",
"@dydxprotocol/v4-localization": "^1.1.163",
"@dydxprotocol/v4-localization": "^1.1.164",
"@ethersproject/providers": "^5.7.2",
"@hugocxl/react-to-image": "^0.0.9",
"@js-joda/core": "^5.5.3",
Expand Down
17 changes: 8 additions & 9 deletions pnpm-lock.yaml

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

24 changes: 24 additions & 0 deletions src/constants/__test__/cctp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';

import {
getLowestFeeChainNames,
getMapOfHighestFeeTokensByChainId,
getMapOfLowestFeeTokensByChainId,
getMapOfLowestFeeTokensByDenom,
} from '@/constants/cctp';
Expand Down Expand Up @@ -288,3 +289,26 @@ describe('getMapOfLowestFeeTokensByChainId', () => {
});
});
});

describe('getMapOfHighestFeeTokensByChainId', () => {
it('deposits squid - should be an empty map', () => {
expect(getMapOfHighestFeeTokensByChainId(TransferType.deposit, false)).toEqual({});
});
it('deposits skip - should be an empty map', () => {
expect(getMapOfHighestFeeTokensByChainId(TransferType.deposit, true)).toEqual({});
});
it('withdrawals squid - should be an empty map', () => {
expect(getMapOfHighestFeeTokensByChainId(TransferType.withdrawal, false)).toEqual({});
});
it('withdrawals skip - should be a map with one property: ethereum chain id', () => {
expect(getMapOfHighestFeeTokensByChainId(TransferType.withdrawal, true)).toEqual({
1: [
{
chainId: '1',
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
name: 'Ethereum',
},
],
});
});
});
41 changes: 22 additions & 19 deletions src/constants/abacus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,36 +116,39 @@ export const ErrorType = Abacus.exchange.dydx.abacus.output.input.ErrorType;

// ------ Wallet ------ //
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance;
export type StakingDelegation = Abacus.exchange.dydx.abacus.output.StakingDelegation;
export type UnbondingDelegation = Abacus.exchange.dydx.abacus.output.UnbondingDelegation;
export type StakingRewards = Abacus.exchange.dydx.abacus.output.StakingRewards;
export type TradingRewards = Abacus.exchange.dydx.abacus.output.TradingRewards;
export type HistoricalTradingReward = Abacus.exchange.dydx.abacus.output.HistoricalTradingReward;
export type AccountBalance = Abacus.exchange.dydx.abacus.output.account.AccountBalance;
export type StakingDelegation = Abacus.exchange.dydx.abacus.output.account.StakingDelegation;
export type UnbondingDelegation = Abacus.exchange.dydx.abacus.output.account.UnbondingDelegation;
export type StakingRewards = Abacus.exchange.dydx.abacus.output.account.StakingRewards;
export type TradingRewards = Abacus.exchange.dydx.abacus.output.account.TradingRewards;
export type HistoricalTradingReward =
Abacus.exchange.dydx.abacus.output.account.HistoricalTradingReward;
export const HistoricalTradingRewardsPeriod =
Abacus.exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod;
const historicalTradingRewardsPeriod = [...HistoricalTradingRewardsPeriod.values()] as const;
export type HistoricalTradingRewardsPeriods = (typeof historicalTradingRewardsPeriod)[number];

export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount;
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition;
export type Subaccount = Abacus.exchange.dydx.abacus.output.account.Subaccount;
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.account.SubaccountPosition;
export type SubaccountPendingPosition =
Abacus.exchange.dydx.abacus.output.SubaccountPendingPosition;
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
Abacus.exchange.dydx.abacus.output.account.SubaccountPendingPosition;
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.account.SubaccountOrder;
export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
const abacusOrderStatuses = [...AbacusOrderStatus.values()] as const;
export type AbacusOrderStatuses = (typeof abacusOrderStatuses)[number];
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[];
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.account.SubaccountFill[];
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.account.SubaccountFill;
export type SubaccountFundingPayment =
Abacus.exchange.dydx.abacus.output.account.SubaccountFundingPayment;
export type SubaccountFundingPayments =
Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment[];
export type SubaccountTransfer = Abacus.exchange.dydx.abacus.output.SubaccountTransfer;
export type SubaccountTransfers = Abacus.exchange.dydx.abacus.output.SubaccountTransfer[];
Abacus.exchange.dydx.abacus.output.account.SubaccountFundingPayment[];
export type SubaccountTransfer = Abacus.exchange.dydx.abacus.output.account.SubaccountTransfer;
export type SubaccountTransfers = Abacus.exchange.dydx.abacus.output.account.SubaccountTransfer[];

// ------ Historical PnL ------ //
export type SubAccountHistoricalPNLs = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL[];
export type SubAccountHistoricalPNLs =
Abacus.exchange.dydx.abacus.output.account.SubaccountHistoricalPNL[];
export const HistoricalPnlPeriod = Abacus.exchange.dydx.abacus.state.manager.HistoricalPnlPeriod;
const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
Expand Down Expand Up @@ -196,8 +199,8 @@ export type AbacusOrderSides = Abacus.exchange.dydx.abacus.output.input.OrderSid
export const AbacusOrderType = Abacus.exchange.dydx.abacus.output.input.OrderType;
export const AbacusOrderSide = Abacus.exchange.dydx.abacus.output.input.OrderSide;

export const AbacusPositionSide = Abacus.exchange.dydx.abacus.output.PositionSide;
export type AbacusPositionSides = Abacus.exchange.dydx.abacus.output.PositionSide;
export const AbacusPositionSide = Abacus.exchange.dydx.abacus.output.account.PositionSide;
export type AbacusPositionSides = Abacus.exchange.dydx.abacus.output.account.PositionSide;

export const AbacusMarginMode = Abacus.exchange.dydx.abacus.output.input.MarginMode;

Expand Down
23 changes: 18 additions & 5 deletions src/constants/cctp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type CctpTokenInfo = {
chainId: string;
tokenAddress: string;
name: string;
isTestnet?: boolean;
};

type NullableTransferType = TransferTypeType | undefined | null;
Expand All @@ -17,6 +18,11 @@ const getLowestFeeChains = (type: NullableTransferType, skipEnabled: boolean) =>
? mainnetChains
: mainnetChains.filter(({ chainId }) => (skipEnabled ? chainId !== '1' : true));

const getHighestFeeChains = (type: NullableTransferType, skipEnabled: boolean) =>
type === TransferType.withdrawal
? mainnetChains.filter(({ chainId }) => (skipEnabled ? chainId === '1' : false))
: [];

// move this out if we need it in another module
const capitalizeNames = (str: string) => str[0].toUpperCase() + str.slice(1);

Expand All @@ -35,11 +41,8 @@ export const getMapOfLowestFeeTokensByDenom = (type: NullableTransferType, skipE
{} as Record<string, CctpTokenInfo[]>
);

export const getMapOfLowestFeeTokensByChainId = (
type: NullableTransferType,
skipEnabled: boolean
) =>
getLowestFeeChains(type, skipEnabled).reduce(
const getMapOfChainsByChainId = (chains: CctpTokenInfo[]) =>
chains.reduce(
(acc, token) => {
if (!acc[token.chainId]) {
acc[token.chainId] = [];
Expand All @@ -50,6 +53,16 @@ export const getMapOfLowestFeeTokensByChainId = (
{} as Record<string, CctpTokenInfo[]>
);

export const getMapOfLowestFeeTokensByChainId = (
type: NullableTransferType,
skipEnabled: boolean
) => getMapOfChainsByChainId(getLowestFeeChains(type, skipEnabled));

export const getMapOfHighestFeeTokensByChainId = (
type: NullableTransferType,
skipEnabled: boolean
) => getMapOfChainsByChainId(getHighestFeeChains(type, skipEnabled));

export const cctpTokensByDenom = cctpTokens.reduce(
(acc, token) => {
if (!acc[token.tokenAddress]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { getTransferInputs } from '@/state/inputsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { MustBigNumber } from '@/lib/numbers';

import { RouteWarningMessage } from '../RouteWarningMessage';
import { SlippageEditor } from '../SlippageEditor';

type ElementProps = {
Expand Down Expand Up @@ -105,7 +106,9 @@ export const DepositButtonAndReceipt = ({
requestPayload,
depositOptions,
chain: chainIdStr,
warning: routeWarning,
} = useAppSelector(getTransferInputs, shallowEqual) ?? {};

const { usdcLabel } = useTokenConfigs();
const isSkipEnabled = useStatsigGateValue(StatSigFlags.ffSkipMigration);

Expand Down Expand Up @@ -250,11 +253,22 @@ export const DepositButtonAndReceipt = ({
},
].filter(isTruthy);

const [hasAcknowledged, setHasAcknowledged] = useState(false);
const requiresAcknowledgement = Boolean(routeWarning && !hasAcknowledged);

const isFormValid =
!isDisabled && !isEditingSlippage && connectionError !== ConnectionErrorType.CHAIN_DISRUPTION;
!isDisabled &&
!isEditingSlippage &&
connectionError !== ConnectionErrorType.CHAIN_DISRUPTION &&
!requiresAcknowledgement;

return (
<$WithReceipt slotReceipt={<$Details items={submitButtonReceipt} />}>
<RouteWarningMessage
hasAcknowledged={hasAcknowledged}
setHasAcknowledged={setHasAcknowledged}
routeWarningJSON={routeWarning}
/>
{!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Base} />
) : !isConnectedWagmi ? (
Expand Down
32 changes: 32 additions & 0 deletions src/views/forms/AccountManagementForms/HighestFeesText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import styled from 'styled-components';

import { STRING_KEYS } from '@/constants/localization';

import { useStringGetter } from '@/hooks/useStringGetter';

export const HighestFeesDecoratorText = () => {
const stringGetter = useStringGetter();
return (
<$Text>
{stringGetter({
key: STRING_KEYS.HIGH_FEES_IN_GENERAL,
params: {
HIGH_FEES_IN_GENERAL_HIGHLIGHT_TEXT: (
<$RedHighlight>
{stringGetter({ key: STRING_KEYS.HIGH_FEES_IN_GENERAL_HIGHLIGHT_TEXT })}
</$RedHighlight>
),
},
})}
</$Text>
);
};

const $Text = styled.div`
font: var(--font-small-regular);
color: var(--color-text-0);
`;

const $RedHighlight = styled.span`
color: var(--color-red);
`;
76 changes: 76 additions & 0 deletions src/views/forms/AccountManagementForms/RouteWarningMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Dispatch, SetStateAction, useMemo } from 'react';

import styled from 'styled-components';

import { AlertType } from '@/constants/alerts';
import { STRING_KEYS } from '@/constants/localization';

import { useStringGetter } from '@/hooks/useStringGetter';

import { AlertMessage } from '@/components/AlertMessage';
import { Checkbox } from '@/components/Checkbox';

import { log } from '@/lib/telemetry';

type RouteWarningMessageProps = {
hasAcknowledged: boolean;
setHasAcknowledged: Dispatch<SetStateAction<boolean>>;
routeWarningJSON: string | undefined | null;
};
/*
Warning message example:
{
"type": "BAD_PRICE_WARNING",
"message": "Difference in USD value of route input and output is large. Input USD value: 52.98 Output USD value: 28.05"
}
*/

const getStringKeyFromWarningType = (warningType: string) => {
if (warningType === 'BAD_PRICE_WARNING') return STRING_KEYS.ACKNOWLEDGE_WARNING_MESSAGE_HIGH_FEES;
return STRING_KEYS.ACKNOWLEDGE_WARNING_MESSAGE_FALLBACK;
};

export const RouteWarningMessage = ({
hasAcknowledged,
setHasAcknowledged,
routeWarningJSON,
}: RouteWarningMessageProps) => {
const stringGetter = useStringGetter();
const { warningMessage, acknowledgementMessageStringKey } = useMemo(() => {
if (!routeWarningJSON) return {};
try {
const warningObject = JSON.parse(routeWarningJSON);
return {
warningMessage: warningObject.message,
acknowledgementMessageStringKey: getStringKeyFromWarningType(warningObject.type),
};
} catch (err) {
log('RouteWarningMessage/warningObject', err);
return {};
}
}, [routeWarningJSON]);
if (!warningMessage || !acknowledgementMessageStringKey) {
setHasAcknowledged(false);
return null;
}

return (
<$AlertMessage type={AlertType.Warning}>
<Checkbox
checked={hasAcknowledged}
onCheckedChange={setHasAcknowledged}
id="acknowledge-route-warning"
label={stringGetter({
key: acknowledgementMessageStringKey,
params: {
WARNING_MESSAGE: warningMessage,
},
})}
/>
</$AlertMessage>
);
};

const $AlertMessage = styled(AlertMessage)`
margin: var(--form-input-paddingY) var(--form-input-paddingX);
`;
Loading

0 comments on commit a26bb23

Please sign in to comment.