Compare commits

...
Author SHA1 Message Date
aforaleka 071cb25c04 pessimistic collat check in non-dev/staging envs
Signed-off-by: aforaleka <aforaleka@gmail.com>
2024-02-22 15:11:19 -05:00
moo-onthelawn e1d2a5eda4 Update env config to enable reduce only in testnet (#321)
* update flag

* update new env.json
2024-02-22 13:07:06 -05:00
moo-onthelawn 56be9ba9cc traced back to innerWindow error (#320) 2024-02-21 19:17:33 -05:00
Bill 859e7dde80 Add notification for season 3 incentives (#322)
* Add notification for season 3 incentives

* add type

* address comments
2024-02-21 16:04:56 -08:00
John Huang d9daf9f583 Feature/mob 193 install app dlg (#308)
* getOS and get meta from header

* Clean up

* Update localization

* Comments

* Rename URL variable

* MOB-193 PR

* PR
2024-02-21 12:52:11 -08:00
John Huang 5be9d2b1b2 TRCL-3553 Having the launch incentive api in env.json (#317) 2024-02-21 12:51:36 -08:00
Bill d2c5b7945b Remove CEX withdraw testflag (#316) 2024-02-21 09:50:11 -08:00
aleka fc5d8538fe bump abacus for new env config path usage (#314)
* bump abacus for new env config path usage

* bump abacus + localization for good til date
2024-02-20 22:42:18 -05:00
yujin512 fdc262f59f Update otherMarketExchangeConfig.json (#315)
* Update otherMarketExchangeConfig.json

add strk

* Update otherMarketExchangeConfig.json

remove huobi, add kraken

* Update otherMarketParameters.json

add strk to parameters

* add strk icon and metadata
2024-02-20 16:08:11 -05:00
Bill 7f947804bd Enable CCTP onboarding for avalanche, optimism (#310)
* Enable CCTP for arbitrum avalanche optimism

* Enable CCTP onboarding for arbitrum, avalanche, optimism

* remove arb
2024-02-19 20:18:57 -08:00
aleka 6620a70a5d fix parsing error and surface tx broadcast error (#313)
* fix parsing error and surface tx broadcast error

* fix network as chain id
2024-02-19 22:06:13 -05:00
Bill bff779631b Cex withdraw (#301)
* Coinbase withdrawal and deposit UI

* analytics

* packages bumps

* address comments

* address comments

* add test flag
2024-02-16 17:56:06 -08:00
36 changed files with 562 additions and 185 deletions
+2 -2
View File
@@ -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.12",
"@dydxprotocol/v4-client-js": "^1.0.20",
"@dydxprotocol/v4-localization": "^1.1.30",
"@dydxprotocol/v4-localization": "^1.1.35",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
+12 -12
View File
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
follow-redirects: 1.15.3
@@ -26,14 +30,14 @@ dependencies:
specifier: ^0.32.1
version: 0.32.2
'@dydxprotocol/v4-abacus':
specifier: ^1.4.5
version: 1.4.5
specifier: ^1.4.12
version: 1.4.12
'@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.35
version: 1.1.35
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@@ -1286,8 +1290,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.12:
resolution: {integrity: sha512-dNQFaNrLTujyViMH2JEtf9Vn2ew2le+qTWexktK5h7AeADBbS9uxQknMIFhfwzGkbS1QqqszBT1plrDmQ+E6nQ==}
dev: false
/@dydxprotocol/v4-client-js@1.0.20:
@@ -1319,8 +1323,8 @@ packages:
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@1.1.30:
resolution: {integrity: sha512-TZfWWRSOxcjLHs972wlJVVHkE7+DVqAUnGZSs24HYHsPtUkPhZiNXMOA2Vk9YddQxumhM79xIRH0cmJSe5DDUg==}
/@dydxprotocol/v4-localization@1.1.35:
resolution: {integrity: sha512-q5JFYoL/QanHXOtFqRa2owBZJibi1sMpSm3dAcxs9x0/xe8mo6fWcnbQfhl8k7g0/tv7PsBc+e3rbWD0EfvGiA==}
dev: false
/@dydxprotocol/v4-proto@4.0.0-dev.0:
@@ -16049,7 +16053,3 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
+21 -11
View File
@@ -1,12 +1,22 @@
[
{
"chainId": "1",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Ethereum"
},
{
"chainId": "5",
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
"name": "Ethereum Goerli"
}
]
{
"chainId": "1",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Ethereum"
},
{
"chainId": "5",
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
"name": "Ethereum Goerli"
},
{
"chainId": "43114",
"tokenAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"name": "Avalanche"
},
{
"chainId": "10",
"tokenAddress": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"name": "optimism"
}
]
+7 -7
View File
@@ -725,7 +725,7 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet-dydx": {
@@ -812,7 +812,7 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet-nodefleet": {
@@ -899,7 +899,7 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet-kingnodes": {
@@ -986,7 +986,7 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet-liquify": {
@@ -1073,7 +1073,7 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet-polkachu": {
@@ -1151,7 +1151,7 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet-bware": {
@@ -1238,7 +1238,7 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true
}
},
"dydxprotocol-mainnet": {
+7
View File
@@ -428,6 +428,13 @@
"whitepaperLink": "https://solana.com/solana-whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/solana/"
},
"STRK-USD": {
"name": "Starknet",
"tags": ["Layer 2"],
"websiteLink": "https://www.starknet.io/en",
"whitepaperLink": "https://docs.starknet.io/documentation/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/starknet-token/"
},
"STX-USD": {
"name": "Stacks",
"tags": ["Layer 2"],
@@ -513,6 +513,14 @@
{ "exchangeName": "Okx", "ticker": "SOL-USDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Mexc", "ticker": "SOL_USDT", "adjustByMarket": "USDT-USD" }
],
"STRK": [
{ "exchangeName": "Binance", "ticker": "STRKUSDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Bybit", "ticker": "STRKUSDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Kraken", "ticker": "STRKUSD" },
{ "exchangeName": "Kucoin", "ticker": "STRK-USDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Okx", "ticker": "STRK-USDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Gate", "ticker": "STRK_USDT", "adjustByMarket": "USDT-USD" }
],
"STX": [
{ "exchangeName": "Binance", "ticker": "STXUSDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Bybit", "ticker": "STXUSDT", "adjustByMarket": "USDT-USD" },
+17
View File
@@ -968,6 +968,23 @@
"minOrderSize": 1000000,
"quantumConversionExponent": -9
},
{
"baseAsset": "STRK",
"referencePrice": 2.05,
"numOracles": 6,
"liquidityTier": 2,
"assetName": "Starknet",
"p": 0.0,
"atomicResolution": -6.0,
"minExchanges": 3,
"minPriceChangePpm": 4000,
"priceExponent": -9.0,
"stepBaseQuantum": 1000000,
"ticksizeExponent": -3,
"subticksPerTick": 1000000,
"minOrderSize": 1000000,
"quantumConversionExponent": -9
},
{
"baseAsset": "STX",
"referencePrice": 1.5144048703611412,
+36 -18
View File
@@ -71,7 +71,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"launchIncentive": "https://cloud.chaoslabs.co/"
},
"dydx-testnet-4": {
"tos": "https://dydx.exchange/v4-terms",
@@ -92,7 +93,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"launchIncentive": "https://cloud.chaoslabs.co/"
},
"[mainnet chain id]": {
"tos": "[HTTP link to TOS]",
@@ -113,7 +115,8 @@
"keplrDashboard": "[HTTP link to keplr dashboard, can be null]",
"strideZoneApp": "[HTTP link to stride zone app, can be null]",
"accountExportLearnMore": "[HTTP link to account export learn more, can be null]",
"walletLearnMore": "[HTTP link to wallet learn more, can be null]"
"walletLearnMore": "[HTTP link to wallet learn more, can be null]",
"launchIncentive": "[HTTP link to launch incentive host, can be null]"
}
},
"wallets": {
@@ -258,7 +261,8 @@
"faucet": "https://faucet.v4dev.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
}
},
"dydxprotocol-dev-2": {
@@ -283,7 +287,8 @@
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
}
},
"dydxprotocol-dev-4": {
@@ -309,7 +314,8 @@
"faucet": "https://faucet.v4dev4.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
}
},
"dydxprotocol-dev-5": {
@@ -334,7 +340,8 @@
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
}
},
"dydxprotocol-staging": {
@@ -360,7 +367,8 @@
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
}
},
"dydxprotocol-staging-forced-update": {
@@ -393,7 +401,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
}
},
"dydxprotocol-staging-west": {
@@ -419,7 +428,8 @@
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
}
},
"dydxprotocol-testnet": {
@@ -449,7 +459,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
}
},
"dydxprotocol-testnet-dydx": {
@@ -475,7 +486,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
}
},
"dydxprotocol-testnet-nodefleet": {
@@ -501,7 +513,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
}
},
"dydxprotocol-testnet-kingnodes": {
@@ -527,7 +540,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
}
},
"dydxprotocol-testnet-liquify": {
@@ -553,7 +567,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
}
},
"dydxprotocol-testnet-polkachu": {
@@ -579,7 +594,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
}
},
"dydxprotocol-testnet-bware": {
@@ -605,7 +621,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
}
},
"dydxprotocol-mainnet": {
@@ -631,7 +648,8 @@
"nobleValidator": "[noble validator endpoint for mainnet]"
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": false,
"usePessimisticCollateralCheck": true
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+5 -3
View File
@@ -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}
/>
</Styled.Popover>
</Styled.WithDetailsReceipt>
@@ -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;
+8 -1
View File
@@ -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 (
+19 -2
View File
@@ -81,6 +81,9 @@ export enum AnalyticsEvent {
TradePlaceOrderConfirmed = 'TradePlaceOrderConfirmed',
TradeCancelOrder = 'TradeCancelOrder',
TradeCancelOrderConfirmed = 'TradeCancelOrderConfirmed',
// Notification
NotificationAction = 'NotificationAction',
}
export type AnalyticsEventData<T extends AnalyticsEvent> =
@@ -144,9 +147,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
? {
@@ -172,6 +183,12 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
/** URL/IP of node the order was sent to */
validatorUrl: string;
}
: // Notifcation
T extends AnalyticsEvent.NotificationAction
? {
type: string;
id: string;
}
: never;
export const DEFAULT_TRANSACTION_MEMO = 'dYdX Frontend (web)';
+1
View File
@@ -11,6 +11,7 @@ export enum DialogTypes {
ExternalNavKeplr = 'ExternalNavKeplr',
MnemonicExport = 'MnemonicExport',
MobileSignIn = 'MobileSignIn',
MobileDownload = 'MobileDownload',
Onboarding = 'Onboarding',
OrderDetails = 'OrderDetails',
Preferences = 'Preferences',
+6
View File
@@ -140,8 +140,14 @@ export type TransferNotifcation = {
isCctp?: boolean;
errorCount?: number;
status?: StatusResponse;
isExchange?: boolean;
};
export enum ReleaseUpdateNotificationIds {
RewardsAndFullTradingLive = 'rewards-and-full-trading-live',
IncentivesS3 = 'incentives-s3',
}
/**
* @description Struct to store whether a NotificationType should be triggered
*/
+9 -7
View File
@@ -57,13 +57,15 @@ export const useChartLines = ({
useEffect(() => {
if (tvWidget && isChartReady) {
tvWidget.onChartReady(() => {
tvWidget.chart().dataReady(() => {
if (showOrderLines) {
drawOrderLines();
drawPositionLine();
} else {
deleteChartLines();
}
tvWidget.headerReady().then(() => {
tvWidget.chart().dataReady(() => {
if (showOrderLines) {
drawOrderLines();
drawPositionLine();
} else {
deleteChartLines();
}
});
});
});
}
+15 -9
View File
@@ -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;
+40 -8
View File
@@ -5,7 +5,7 @@ import { isEqual, groupBy } from 'lodash';
import { useNavigate } from 'react-router-dom';
import { DialogTypes } from '@/constants/dialogs';
import { AppRoute } from '@/constants/routes';
import { AppRoute, TokenRoute } from '@/constants/routes';
import { DydxChainAsset } from '@/constants/wallets';
import {
@@ -20,9 +20,10 @@ import {
NotificationType,
DEFAULT_TOAST_AUTO_CLOSE_MS,
TransferNotificationTypes,
ReleaseUpdateNotificationIds,
} from '@/constants/notifications';
import { useStringGetter } from '@/hooks';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
@@ -179,8 +180,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 =
@@ -237,13 +239,16 @@ export const notificationTypes: NotificationTypeConfig[] = [
{
type: NotificationType.ReleaseUpdates,
useTrigger: ({ trigger }) => {
const { chainTokenLabel } = useTokenConfigs();
const stringGetter = useStringGetter();
const expirationDate = new Date('2024-03-08T23:59:59');
const currentDate = new Date();
useEffect(() => {
trigger(
'rewards-and-full-trading-live',
ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
{
icon: <AssetIcon symbol="DYDX" />,
icon: <AssetIcon symbol={chainTokenLabel} />,
title: stringGetter({ key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.TITLE' }),
body: stringGetter({
key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.BODY',
@@ -269,14 +274,41 @@ export const notificationTypes: NotificationTypeConfig[] = [
},
}),
toastSensitivity: 'foreground',
groupKey: NotificationType.ReleaseUpdates,
groupKey: ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
},
[]
);
if (currentDate <= expirationDate) {
trigger(
ReleaseUpdateNotificationIds.IncentivesS3,
{
icon: <AssetIcon symbol={chainTokenLabel} />,
title: stringGetter({ key: 'NOTIFICATIONS.INCENTIVES_SEASON_BEGUN.TITLE' }),
body: stringGetter({
key: 'NOTIFICATIONS.INCENTIVES_SEASON_BEGUN.BODY',
params: {
SEASON_NUMBER: '3',
PREV_SEASON_NUMBER: '1',
DYDX_AMOUNT: '34',
USDC_AMOUNT: '100',
},
}),
toastSensitivity: 'foreground',
groupKey: ReleaseUpdateNotificationIds.IncentivesS3,
},
[]
);
}
}, [stringGetter]);
},
useNotificationAction: () => {
return () => {};
const { chainTokenLabel } = useTokenConfigs();
const navigate = useNavigate();
return (notificationId: string) => {
if (notificationId === ReleaseUpdateNotificationIds.IncentivesS3) {
navigate(`${chainTokenLabel}/${TokenRoute.TradingRewards}`);
}
};
},
},
];
+8 -3
View File
@@ -1,5 +1,6 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { AnalyticsEvent } from '@/constants/analytics';
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import {
type Notification,
@@ -13,7 +14,8 @@ import {
import { useLocalStorage } from './useLocalStorage';
import { notificationTypes } from './useNotificationTypes';
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
import { track } from '@/lib/analytics';
import { renderSvgToDataUrl } from '@/lib/renderSvgToDataUrl';
type NotificationsContextType = ReturnType<typeof useNotificationsContext>;
@@ -48,6 +50,7 @@ const useNotificationsContext = () => {
defaultValue: {
[NotificationType.AbacusGenerated]: true,
[NotificationType.SquidTransfer]: true,
[NotificationType.ReleaseUpdates]: true,
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
},
});
@@ -176,8 +179,10 @@ const useNotificationsContext = () => {
)
);
const onNotificationAction = async (notification: Notification) =>
await actions[notification.type]?.(notification.id);
const onNotificationAction = async (notification: Notification) => {
track(AnalyticsEvent.NotificationAction, { type: notification.type, id: notification.id });
return await actions[notification.type]?.(notification.id);
};
// Push notifications
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
+2
View File
@@ -31,6 +31,7 @@ import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDial
import { NewMarketMessageDetailsDialog } from '@/views/dialogs/NewMarketMessageDetailsDialog';
import { NewMarketAgreementDialog } from '@/views/dialogs/NewMarketAgreementDialog';
import { ExternalNavStrideDialog } from '@/views/dialogs/ExternalNavStrideDialog';
import { MobileDownloadDialog } from '@/views/dialogs/MobileDownloadDialog';
export const DialogManager = () => {
const dispatch = useDispatch();
@@ -63,6 +64,7 @@ export const DialogManager = () => {
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
[DialogTypes.MobileDownload]: <MobileDownloadDialog {...modalProps} />,
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
+6 -7
View File
@@ -34,7 +34,7 @@ import {
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
import { DialogTypes } from '@/constants/dialogs';
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 { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
@@ -44,7 +44,10 @@ import { StatefulOrderError } from '../errors';
import { bytesToBigInt } from '../numbers';
import { log } from '../telemetry';
import { hashFromTx, getMintscanTxLink } from '../txUtils';
import { getDydxChainIdFromNetwork } from '../network';
(BigInt.prototype as any).toJSON = function () {
return this.toString();
};
class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
private compositeClient: CompositeClient | undefined;
@@ -239,10 +242,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
if (isTestnet) {
console.log(
getMintscanTxLink(
getDydxChainIdFromNetwork(this.compositeClient.network.getString() as DydxNetwork),
hash
)
getMintscanTxLink(this.compositeClient.network.getString() as DydxChainId, hash)
);
} else console.log(`txHash: ${hash}`);
@@ -538,7 +538,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
const result = await this.cctpWithdraw(params);
callback(result);
break;
break;
}
default: {
break;
+19
View File
@@ -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;
}
}
+1 -5
View File
@@ -1,8 +1,4 @@
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP, type DydxChainId } from '@/constants/networks';
export const validateAgainstAvailableEnvironments = (value: DydxNetwork) =>
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
export const getDydxChainIdFromNetwork = (network: DydxNetwork) => {
return ENVIRONMENT_CONFIG_MAP[network].dydxChainId as DydxChainId;
};
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
+3 -3
View File
@@ -31,13 +31,13 @@ export const fetchSquidStatus = async (
const response = await fetch(url, {
headers: {
"x-integrator-id": integratorId || 'dYdX-api'
'x-integrator-id': integratorId || 'dYdX-api',
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(error);
throw error;
}
return response.json();
@@ -45,4 +45,4 @@ export const fetchSquidStatus = async (
export const getNobleChainId = () => {
return isMainnet ? 'noble-1' : 'grand-1';
}
};
+2 -2
View File
@@ -19,10 +19,10 @@ class TestFlags {
return !!this.queryParams.displayinitializingmarkets;
}
get addressOverride():string {
get addressOverride(): string {
return this.queryParams.address;
}
get showTradingRewards() {
return !!this.queryParams.tradingrewards;
}
+1 -3
View File
@@ -13,8 +13,6 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const { isMobile } = useBreakpoints();
const closeDialog = () => setIsOpen?.(false);
return (
<Dialog
isOpen
@@ -22,7 +20,7 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
>
<DepositDialogContent onDeposit={closeDialog} />
<DepositDialogContent />
</Dialog>
);
};
@@ -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 (
<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)}>
+140
View File
@@ -0,0 +1,140 @@
import styled, { AnyStyledComponent, css } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { QrCode } from '@/components/QrCode';
import { useStringGetter } from '@/hooks';
import { STRING_KEYS } from '@/constants/localization';
type ElementProps = {
setIsOpen: (open: boolean) => void;
};
/*
When/if deployer deploys the web app with smartbanner, "smartbanner:button-url-apple" and/or
"smartbanner:button-url-google" <meta> are set.
This implementation assumes "smartbanner:button-url-apple" and "smartbanner:button-url-google"
are set to the same value with onelink or other redirect URL.
Since there is no way for the desktop web app to know what mobile device the user is using,
we should give a onelink URL which redirects to either iOS or Android app store depending on
the mobile device used to scan the link.
*/
// for testing only
// export const mobileAppUrl = "http://example.com";
let mobileAppUrl: string | undefined | null = undefined;
export const getMobileAppUrl = () => {
if (!mobileAppUrl) {
mobileAppUrl =
// for testing to verify <meta> is retrieved by name, QR code should show "@dYdX" as value
// document.querySelector('meta[name="twitter:creator"]')?.getAttribute('content') ??
document.querySelector('meta[name="smartbanner:button-url-apple"]')?.getAttribute('content') ??
document.querySelector('meta[name="smartbanner:button-url-google"]')?.getAttribute('content');
}
return mobileAppUrl;
}
const MobileQrCode = ({
url,
}: {
url: string;
}) => {
return (
<Styled.QrCodeContainer isShowing={true}>
<QrCode hasLogo size={432} value={url} />
</Styled.QrCodeContainer>
);
};
/*
MobileDownloadDialog should only been shown on desktop when mobileAppUrl has value. That's controlled by AccountMenu.tsx.
*/
export const MobileDownloadDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const content = (
<MobileQrCode url={mobileAppUrl!} />
);
return (
<Dialog isOpen setIsOpen={setIsOpen} title={
stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP })
}>
<Styled.Content>{content}</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
strong {
font-weight: 900;
color: var(--color-text-2);
}
footer {
${layoutMixins.row}
justify-content: space-between;
svg {
width: auto;
}
}
`;
Styled.WaitingSpan = styled.span`
strong {
color: var(--color-warning);
}
`;
Styled.QrCodeContainer = styled.figure<{ isShowing: boolean }>`
${layoutMixins.stack}
overflow: hidden;
border-radius: 0.75em;
cursor: pointer;
transition: 0.2s;
&:hover {
filter: brightness(var(--hover-filter-base));
}
> * {
position: relative;
transition: 0.16s;
}
> :first-child {
pointer-events: none;
${({ isShowing }) =>
!isShowing &&
css`
filter: blur(1rem) brightness(1.4);
will-change: filter;
`}
}
> span {
place-self: center;
font-size: 1.4em;
color: var(--color-text-2);
${({ isShowing }) =>
isShowing &&
css`
opacity: 0;
`}
}
`;
+2 -2
View File
@@ -105,8 +105,8 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
<Styled.Content>
{isMainnet ? (
<DepositForm
onDeposit={() => {
track(AnalyticsEvent.TransferDeposit);
onDeposit={(event) => {
track(AnalyticsEvent.TransferDeposit, event);
}}
/>
) : (
@@ -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<AnalyticsEvent.TransferDeposit>) => 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) => {
<SourceSelectMenu
selectedChain={chainIdStr || undefined}
selectedExchange={exchange || undefined}
onSelect={onSelectChain}
onSelect={onSelectNetwork}
/>
{exchange && nobleAddress ? (
<NobleDeposit />
@@ -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;
@@ -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 && (
<>
<Tag>{type === TransferType.deposit ? selectedToken?.symbol : 'USDC'}</Tag>
<DiffArrow />
<Tag>{type === TransferType.deposit ? 'USDC' : selectedToken?.symbol}</Tag>
</>
),
},
]}
withSearch={!isExchange}
withReceiptItems={
!isExchange
? [
{
key: 'swap',
label: stringGetter({ key: STRING_KEYS.SWAP }),
value: selectedToken && (
<>
<Tag>{type === TransferType.deposit ? selectedToken?.symbol : 'USDC'}</Tag>
<DiffArrow />
<Tag>{type === TransferType.deposit ? 'USDC' : selectedToken?.symbol}</Tag>
</>
),
},
]
: undefined
}
>
<Styled.AssetRow>
{selectedToken ? (
@@ -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<HTMLInputElement>) => {
@@ -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 (
<Styled.Form onSubmit={onSubmit}>
@@ -385,12 +420,21 @@ export const WithdrawForm = () => {
}
/>
<SourceSelectMenu
label={stringGetter({ key: STRING_KEYS.NETWORK })}
selectedExchange={exchange || undefined}
selectedChain={chainIdStr || undefined}
onSelect={onSelectChain}
onSelect={onSelectNetwork}
/>
</Styled.DestinationRow>
<TokenSelectMenu selectedToken={toToken || undefined} onSelectToken={onSelectToken} />
{isInvalidNobleAddress && (
<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}>
<FormInput
type={InputType.Number}
@@ -27,6 +27,8 @@ import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { getSubaccount } from '@/state/accountSelectors';
import { getTransferInputs } from '@/state/inputsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { SlippageEditor } from '../SlippageEditor';
type ElementProps = {
@@ -55,7 +57,7 @@ export const WithdrawButtonAndReceipt = ({
const stringGetter = useStringGetter();
const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const { summary, requestPayload, exchange } = useSelector(getTransferInputs, shallowEqual) || {};
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { usdcLabel } = useTokenConfigs();
@@ -92,7 +94,7 @@ export const WithdrawButtonAndReceipt = ({
value: <Output type={OutputType.Fiat} value={totalFees} />,
subitems: feeSubitems,
},
{
!exchange && {
key: 'exchange-rate',
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
value: withdrawToken && typeof summary?.exchangeRate === 'number' && (
@@ -133,7 +135,9 @@ export const WithdrawButtonAndReceipt = ({
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
</span>
),
value: <Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />,
value: (
<Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />
),
subitems: [
{
key: 'minimum-amount-received',
@@ -144,13 +148,17 @@ export const WithdrawButtonAndReceipt = ({
</span>
),
value: (
<Output type={OutputType.Asset} value={summary?.toAmountMin} fractionDigits={TOKEN_DECIMALS} />
<Output
type={OutputType.Asset}
value={summary?.toAmountMin}
fractionDigits={TOKEN_DECIMALS}
/>
),
tooltip: 'minimum-amount-received',
},
],
},
{
!exchange && {
key: 'slippage',
label: <span>{stringGetter({ key: STRING_KEYS.MAX_SLIPPAGE })}</span>,
value: (
@@ -175,7 +183,7 @@ export const WithdrawButtonAndReceipt = ({
/>
),
},
];
].filter(isTruthy);
const isFormValid = !isDisabled && !isEditingSlippage;
+25 -31
View File
@@ -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 }),
},
]}
>
<Styled.QrCodeContainer>
<Styled.QrCode size={432} value={nobleAddress || ''} />
</Styled.QrCodeContainer>
<Styled.QrCode
hasLogo
size={432}
value={nobleAddress || ''}
blurred={!hasAcknowledged || !hasTimedout}
/>
</WithDetailsReceipt>
<Styled.WaitingSpan>
<Styled.CautionIconContainer>
<Icon iconName={IconName.CautionCircleStroked} />
</Styled.CautionIconContainer>
<p>{stringGetter({ key: STRING_KEYS.NOBLE_WARNING })}</p>
</Styled.WaitingSpan>
<Styled.WithReceipt
slotReceipt={
<Styled.CheckboxContainer>
@@ -63,7 +61,12 @@ export const NobleDeposit = () => {
>
<TimeoutButton
timeoutInSeconds={8}
slotFinal={<CopyButton state={{ isDisabled: !hasAcknowledged }} value={nobleAddress} />}
onTimeOut={() => setHasTimedout(true)}
slotFinal={
<CopyButton state={{ isDisabled: !hasAcknowledged }} value={nobleAddress}>
{!hasAcknowledged ? stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_RISKS }) : undefined}
</CopyButton>
}
/>
</Styled.WithReceipt>
</>
@@ -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`
+13
View File
@@ -40,6 +40,7 @@ import { getAppTheme } from '@/state/configsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { MustBigNumber } from '@/lib/numbers';
import { getMobileAppUrl } from '../dialogs/MobileDownloadDialog';
export const AccountMenu = () => {
const stringGetter = useStringGetter();
@@ -189,6 +190,18 @@ export const AccountMenu = () => {
label: stringGetter({ key: STRING_KEYS.DISPLAY_SETTINGS }),
onSelect: () => dispatch(openDialog({ type: DialogTypes.DisplaySettings })),
},
...(getMobileAppUrl()
? [
{
value: 'MobileDownload',
icon: <Icon iconName={IconName.Qr} />,
label: stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP }),
onSelect: () => {
dispatch(openDialog({ type: DialogTypes.MobileDownload }));
},
},
]
: []),
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
{
@@ -40,7 +40,7 @@ export const TransferStatusNotification = ({
const stringGetter = useStringGetter();
const [open, setOpen] = useState<boolean>(false);
const [secondsLeft, setSecondsLeft] = useState<number>(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 ? (
<LoadingDots size={3} />
) : (
<Styled.BridgingStatus>
{content}
{!isToast && status?.squidTransactionStatus !== 'success' && !hasError && (
{!isToast && !isComplete && !hasError && (
<Styled.TransferStatusSteps status={status} type={type} />
)}
</Styled.BridgingStatus>