Compare commits

..
Author SHA1 Message Date
jaredvu 5a91e56ac3 🚧 WAGMI 2 2024-01-09 13:28:21 -08:00
27 changed files with 4532 additions and 876 deletions
+5 -4
View File
@@ -39,7 +39,7 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.2.3",
"@dydxprotocol/v4-abacus": "^1.1.32",
"@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.6",
"@ethersproject/providers": "^5.7.2",
@@ -72,6 +72,7 @@
"@reduxjs/toolkit": "^1.9.5",
"@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0",
"@tanstack/react-query": "^5.17.9",
"@types/lodash": "^4.14.195",
"@types/styled-components": "^5.1.26",
"@visx/axis": "^3.1.0",
@@ -104,15 +105,15 @@
"react-aria": "^3.25.0",
"react-dom": "^18.2.0",
"react-number-format": "^5.2.2",
"react-query": "^3.39.3",
"react-redux": "^8.1.1",
"react-router-dom": "^6.14.0",
"react-stately": "^3.23.0",
"reselect": "^4.1.8",
"stream-browserify": "^3.0.0",
"styled-components": "^5.3.11",
"use-latest": "^1.2.1",
"viem": "^1.20.0",
"wagmi": "^1.4.12"
"viem": "^2.0.0",
"wagmi": "^2.0.3"
},
"devDependencies": {
"@babel/core": "^7.22.5",
+4194 -547
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,8 +1,8 @@
import { lazy, Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { WagmiConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from 'react-query';
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { GrazProvider } from 'graz';
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
@@ -120,9 +120,9 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
};
const providers = [
wrapProvider(WagmiProvider, { config }),
wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider),
-26
View File
@@ -1,26 +0,0 @@
import type { Story } from '@ladle/react';
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
import { StoryWrapper } from '.ladle/components';
export const Accordion: Story<AccordionProps> = (args) => {
return (
<StoryWrapper>
<AccordionComponent {...args} />
</StoryWrapper>
);
};
Accordion.args = {
items: [
{
header: 'Question 1?',
content: 'Answer 1.',
},
{
header: 'Question 2?',
content: 'Answer 2.',
},
],
};
+3 -13
View File
@@ -3,8 +3,6 @@ import styled, { keyframes, type AnyStyledComponent } from 'styled-components';
import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
import { PlusIcon } from '@/icons';
export type AccordionItem = {
@@ -12,7 +10,7 @@ export type AccordionItem = {
content: React.ReactNode;
};
export type AccordionProps = {
type AccordionProps = {
items: AccordionItem[];
className?: string;
};
@@ -38,13 +36,6 @@ export const Accordion = ({ items, className }: AccordionProps) => (
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Root = styled(Root)`
--accordion-paddingY: 1rem;
--accordion-paddingX: 1rem;
@media ${breakpoints.notTablet} {
--accordion-paddingX: 1.5rem;
}
> *:not(:last-child) {
border-bottom: var(--border-width) solid var(--border-color);
}
@@ -76,8 +67,7 @@ Styled.Icon = styled.div`
Styled.Trigger = styled(Trigger)`
${layoutMixins.spacedRow}
width: 100%;
padding: var(--accordion-paddingY) var(--accordion-paddingX);
gap: 0.5rem;
padding: 1rem 0.75rem;
color: var(--color-text-1);
text-align: start;
@@ -101,7 +91,7 @@ Styled.Trigger = styled(Trigger)`
Styled.Content = styled(Content)`
overflow: hidden;
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
margin: 0 0.75rem 1rem;
color: var(--color-text-0);
-7
View File
@@ -4,7 +4,6 @@ import { Link } from 'react-router-dom';
import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
type PanelProps = {
slotHeaderContent?: string;
@@ -68,12 +67,6 @@ Styled.Panel = styled.section<{ onClick?: () => void }>`
--panel-content-paddingY: var(--panel-paddingY);
--panel-content-paddingX: var(--panel-paddingX);
@media ${breakpoints.notTablet} {
--panel-paddingX: 1.5rem;
--panel-paddingY: 1.25rem;
--panel-content-paddingY: 1rem;
}
${layoutMixins.row}
background-color: var(--color-layer-3);
+3 -3
View File
@@ -142,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
// ------ Transfer Items ------ //
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
const transferInputFields = [...TransferInputField.values()] as const;
export type TransferInputFields = (typeof transferInputFields)[number];
@@ -151,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
export type TransferTypes = (typeof transferTypes)[number];
// ------ Trade Items ------ //
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
const tradeInputFields = [...TradeInputField.values()] as const;
export type TradeInputFields = (typeof tradeInputFields)[number];
@@ -162,7 +162,7 @@ export type TradeState<T> = {
};
export const ClosePositionInputField =
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
+5
View File
@@ -0,0 +1,5 @@
export enum QueryKeys {
ACCOUNT_BALANCE = 'ACCOUNT_BALANCE',
LAUNCH_INCENTIVES = 'LAUNCH_INCENTIVES',
TRANSACTION_STATUS = 'TRANSACTION_STATUS',
}
+13 -2
View File
@@ -52,7 +52,7 @@ type WalletConnectionTypeConfig = {
wagmiConnectorId?: string;
};
export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectionTypeConfig> = {
export const WALLET_CONNECTION_TYPES: Record<WalletConnectionType, WalletConnectionTypeConfig> = {
[WalletConnectionType.CoinbaseWalletSdk]: {
name: 'Coinbase Wallet SDK',
wagmiConnectorId: 'coinbaseWallet',
@@ -110,7 +110,7 @@ export const WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS = Object.values(
WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS
);
type WalletConfig = {
export type WalletConfig = {
type: WalletType;
stringKey: string;
icon: string;
@@ -274,6 +274,17 @@ export type WalletConnection = {
provider?: ExternalProvider;
};
export type WalletConnectConfig = {
client: {
name: string;
description: string;
iconUrl: string;
};
v2: {
projectId: string;
};
};
// dYdX Chain wallets
export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
+27 -11
View File
@@ -1,11 +1,12 @@
import { useCallback } from 'react';
import { useCallback, useEffect } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { useBalance } from 'wagmi';
import { useBalance, useBlockNumber } from 'wagmi';
import { StargateClient } from '@cosmjs/stargate';
import { useQuery } from 'react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { formatUnits } from 'viem';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { QueryKeys } from '@/constants/queries';
import { EvmAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils';
@@ -52,15 +53,27 @@ export const useAccountBalance = ({
const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const stakingBalances = useSelector(getStakingBalances, shallowEqual);
const evmQuery = useBalance({
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
const queryClient = useQueryClient();
const { data: blockNumber } = useBlockNumber({ watch: true });
const {
data: evmQuery,
status: evmBalanceStatus,
fetchStatus: evmBalanceFetchStatus,
queryKey,
} = useBalance({
query: {
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
},
address: evmAddress,
chainId: typeof chainId === 'number' ? chainId : Number(evmChainId),
token:
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS ? undefined : (addressOrDenom as EvmAddress),
watch: true,
});
useEffect(() => {
queryClient.invalidateQueries({ queryKey });
}, [blockNumber]);
const cosmosQueryFn = useCallback(async () => {
if (dydxAddress && bech32AddrPrefix && rpc && addressOrDenom) {
const address = convertBech32Address({
@@ -78,7 +91,7 @@ export const useAccountBalance = ({
const cosmosQuery = useQuery({
enabled: Boolean(isCosmosChain && dydxAddress && bech32AddrPrefix && rpc && addressOrDenom),
queryKey: `accountBalances_${chainId}_${addressOrDenom}`,
queryKey: [QueryKeys.ACCOUNT_BALANCE, chainId, addressOrDenom],
queryFn: cosmosQueryFn,
refetchOnWindowFocus: false,
refetchOnMount: false,
@@ -87,8 +100,11 @@ export const useAccountBalance = ({
staleTime: 10_000,
});
const { formatted: evmBalance } = evmQuery.data || {};
const balance = isCosmosChain ? cosmosQuery.data : evmBalance;
const balance = isCosmosChain
? cosmosQuery.data
: evmQuery
? formatUnits(evmQuery?.value, evmQuery?.decimals)
: undefined;
const nativeTokenCoinBalance = balances?.[chainTokenDenom];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
@@ -104,7 +120,7 @@ export const useAccountBalance = ({
nativeTokenBalance,
nativeStakingBalance,
usdcBalance,
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
queryStatus: isCosmosChain ? cosmosQuery.status : evmBalanceStatus,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmBalanceFetchStatus === 'fetching',
};
};
-3
View File
@@ -39,7 +39,6 @@ const useAccountsContext = () => {
walletConnectionType,
selectWalletType,
selectedWalletType,
selectedWalletError,
evmAddress,
signerWagmi,
publicClientWagmi,
@@ -300,9 +299,7 @@ const useAccountsContext = () => {
walletConnectionType,
// Wallet selection
selectWalletType,
selectedWalletType,
selectedWalletError,
// Wallet connection (EVM)
evmAddress,
+24 -13
View File
@@ -1,8 +1,9 @@
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
import { useQuery } from 'react-query';
import { useQuery } from '@tanstack/react-query';
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import { type TransferNotifcation } from '@/constants/notifications';
import { QueryKeys } from '@/constants/queries';
import { useAccounts } from '@/hooks/useAccounts';
import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid';
@@ -68,8 +69,8 @@ const useLocalNotificationsContext = () => {
[transferNotifications]
);
useQuery({
queryKey: 'getTransactionStatus',
const { data: newTransferNotifications } = useQuery({
queryKey: [QueryKeys.TRANSACTION_STATUS],
queryFn: async () => {
const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
const newTransferNotifications = await Promise.all(
@@ -84,10 +85,11 @@ const useLocalNotificationsContext = () => {
status: currentStatus,
} = 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 (
!hasErrors &&
@@ -95,11 +97,14 @@ const useLocalNotificationsContext = () => {
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;
@@ -121,12 +126,18 @@ const useLocalNotificationsContext = () => {
return newTransferNotifications;
};
const newTransferNotifications = await processTransferNotifications(transferNotifications);
setTransferNotifications(newTransferNotifications);
return newTransferNotifications;
},
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
});
useEffect(() => {
if (!newTransferNotifications) return;
setTransferNotifications(newTransferNotifications);
}, [newTransferNotifications]);
return {
transferNotifications,
addTransferNotification,
+13 -6
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useNetwork, useSwitchNetwork } from 'wagmi';
import { UseSwitchChainReturnType, useAccount, useSwitchChain } from 'wagmi';
export const useMatchingEvmNetwork = ({
chainId,
@@ -8,10 +8,10 @@ export const useMatchingEvmNetwork = ({
}: {
chainId?: string | number;
switchAutomatically?: boolean;
onError?: (error: Error) => void;
onError?: Parameters<UseSwitchChainReturnType['switchChainAsync']>[1]['onError'];
}) => {
const { chain } = useNetwork();
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
const { chain } = useAccount();
const { switchChainAsync, isPending } = useSwitchChain();
// If chainId is not a number, we can assume it is a non EVM compatible chain
const isMatchingNetwork = useMemo(
@@ -21,7 +21,14 @@ export const useMatchingEvmNetwork = ({
const matchNetwork = useCallback(async () => {
if (!isMatchingNetwork) {
await switchNetworkAsync?.(Number(chainId));
await switchChainAsync?.(
{
chainId: Number(chainId),
},
{
onError,
}
);
}
}, [chainId, chain]);
@@ -34,6 +41,6 @@ export const useMatchingEvmNetwork = ({
return {
isMatchingNetwork,
matchNetwork,
isSwitchingNetwork: isLoading,
isSwitchingNetwork: isPending,
};
};
+14 -10
View File
@@ -104,12 +104,13 @@ export const useWalletConnection = () => {
[walletConnectConfig, walletType, walletConnectionType]
);
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
const { connectAsync: connectWagmi } = useConnectWagmi();
const { suggestAndConnect: connectGraz } = useConnectGraz();
const connectWallet = useCallback(
async ({ walletType }: { walletType: WalletType }) => {
const walletConnection = getWalletConnection({ walletType });
console.log({ walletConnection });
try {
if (!walletConnection) {
@@ -133,13 +134,17 @@ export const useWalletConnection = () => {
}
} else {
if (!isConnectedWagmi) {
await connectWagmi({
connector: resolveWagmiConnector({
walletType,
walletConnection,
walletConnectConfig,
}),
const connector = resolveWagmiConnector({
walletType,
walletConnection,
walletConnectConfig,
});
if (connector) {
await connectWagmi({
connector,
});
}
}
}
} catch (error) {
@@ -156,7 +161,7 @@ export const useWalletConnection = () => {
walletConnectionType: walletConnection.type,
};
},
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi]
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, walletConnectConfig]
);
const disconnectWallet = useCallback(async () => {
@@ -204,10 +209,9 @@ export const useWalletConnection = () => {
})();
}, [selectedWalletType, signerWagmi, signerGraz]);
const selectWalletType = async (walletType: WalletType | undefined) => {
const selectWalletType = (walletType: WalletType | undefined) => {
if (selectedWalletType) {
setSelectedWalletType(undefined);
await new Promise(requestAnimationFrame);
}
setSelectedWalletType(walletType);
+3 -1
View File
@@ -1 +1,3 @@
<svg fill="none" height="10" viewBox="0 0 15 10" width="15" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m.321829 1.09389c.200562-.192959.469529-.298401.747791-.29315.27827.00525.54307.120763.73621.32115l5.194 5.5132 5.19397-5.5132c.0944-.10408.2088-.188152.3363-.247234.1275-.059081.2655-.091968.406-.096713.1404-.004746.2804.018748.4116.069088s.2509.126504.3522.223979c.1012.09747.1818.21427.2371.34347.0552.12921.084.26819.0845.40871.0006.14052-.0271.27972-.0813.40936s-.1339.24707-.2344.34534l-5.94997 6.3c-.09795.10163-.21538.18245-.34527.23766-.1299.05521-.26959.08367-.41073.08367s-.28083-.02846-.41073-.08367c-.12989-.05521-.24732-.13603-.34527-.23766l-5.950001-6.3c-.192962-.20056-.29840394-.46953-.29315364-.74779.00525031-.27827.12076264-.54307.32115364-.73621z" fill="currentColor" fill-rule="evenodd"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 6L7.83335 10.5L2 6" stroke="currentColor" stroke-width="2.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 205 B

+110 -121
View File
@@ -1,7 +1,6 @@
import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
import { goerli } from 'wagmi/chains';
import { createConfig, createConnector, http, usePublicClient } from 'wagmi';
import {
type Chain,
arbitrum,
arbitrumGoerli,
avalanche,
@@ -26,34 +25,30 @@ import {
fantomTestnet,
celo,
celoAlfajores,
scroll,
kava,
mainnet,
goerli,
sepolia,
} from 'viem/chains';
} from 'wagmi/chains';
import { alchemyProvider } from 'wagmi/providers/alchemy';
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc';
import { publicProvider } from 'wagmi/providers/public';
import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { MetaMaskConnector } from 'wagmi/connectors/metaMask';
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect';
import { coinbaseWallet, injected, metaMask, walletConnect } from 'wagmi/connectors';
import type { ExternalProvider } from '@ethersproject/providers';
import {
type WalletConnectConfig,
type WalletConnection,
WalletConnectionType,
type WalletType,
walletConnectionTypes,
WalletType,
WALLET_CONNECTION_TYPES,
wallets,
WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
WalletConfig,
} from '@/constants/wallets';
import { isTruthy } from './isTruthy';
import { log } from './telemetry';
// Config
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
const WAGMI_SUPPORTED_CHAINS: Parameters<typeof createConfig>[0]['chains'] = [
mainnet,
goerli,
sepolia,
@@ -81,122 +76,106 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
fantomTestnet,
celo,
celoAlfajores,
scroll,
kava,
];
const { chains, publicClient, webSocketPublicClient } = configureChains(
WAGMI_SUPPORTED_CHAINS,
[
import.meta.env.VITE_ALCHEMY_API_KEY &&
alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }),
jsonRpcProvider({
rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] }),
}),
publicProvider(),
].filter(isTruthy)
);
export const config = createConfig({
chains: WAGMI_SUPPORTED_CHAINS,
transports: Object.fromEntries(
Object.values(WAGMI_SUPPORTED_CHAINS).map(({ id }) => [id, http()])
),
});
const injectedConnectorOptions = {
chains,
options: {
name: 'Injected',
const createdInjectedConnectorWithProvider = ({ provider }: { provider: ExternalProvider }) => {
console.log(provider);
return injected({
target() {
return {
id: 'windowProvider',
name: 'Injected',
provider,
};
},
shimDisconnect: true,
shimChainChangedDisconnect: false,
},
});
};
type WalletConnectConfig = {
client: {
name: string;
description: string;
iconUrl: string;
};
v2: {
projectId: string;
};
const getConnector = ({
walletConnectionType,
walletConnectConfig,
}: {
walletConnectionType: WalletConnectionType;
walletConnectConfig: WalletConnectConfig;
}) => {
console.log(walletConnectionType);
switch (walletConnectionType) {
case WalletConnectionType.WalletConnect2:
return [
walletConnect(
getConnectorInfoForWc2({
walletConnectConfig,
})
),
];
case WalletConnectionType.CoinbaseWalletSdk:
return [
coinbaseWallet({
appName: 'dYdX',
appLogoUrl: walletConnectConfig.client.iconUrl,
reloadOnDisconnect: false,
darkMode: true,
}),
];
case WalletConnectionType.InjectedEip1193:
return injected({
target() {
return {
id: 'windowProvider',
name: 'Injected',
provider: window.ethereum,
};
},
shimDisconnect: true,
});
case WalletConnectionType.CosmosSigner:
default: {
return undefined;
}
}
};
const getWalletconnect2ConnectorOptions = (
config: WalletConnectConfig
): ConstructorParameters<typeof WalletConnectConnector>[0] => ({
chains,
options: {
projectId: config.v2.projectId,
const getConnectorInfoForWc2 = ({
walletConnectId,
walletConnectConfig,
}: {
walletConnectId?: string;
walletConnectConfig: WalletConnectConfig;
}) => {
const explorerRecommendedWalletIds = walletConnectId
? [walletConnectId]
: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS;
return {
projectId: walletConnectConfig.v2.projectId,
metadata: {
name: config.client.name,
description: config.client.description,
name: walletConnectConfig.client.name,
description: walletConnectConfig.client.description,
url: import.meta.env.VITE_BASE_URL,
icons: [config.client.iconUrl],
icons: [walletConnectConfig.client.iconUrl],
},
showQrModal: true,
qrModalOptions: {
themeMode: 'dark' as const,
themeVariables: {
'--wcm-accent-color': '#5973fe',
'--wcm-accent-color': 'var(--color-accent)',
'--wcm-font-family': 'var(--fontFamily-base)',
'--wcm-background-color': 'var(--color-accent)',
},
explorerRecommendedWalletIds: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
explorerRecommendedWalletIds,
},
},
});
const getConnectors = (walletConnectConfig: WalletConnectConfig) => [
new MetaMaskConnector({
chains,
options: {
shimDisconnect: true,
},
}),
new CoinbaseWalletConnector({
chains,
options: {
appName: 'dYdX',
reloadOnDisconnect: false,
},
}),
new WalletConnectConnector(getWalletconnect2ConnectorOptions(walletConnectConfig)),
new InjectedConnector(injectedConnectorOptions),
];
export const config = createConfig({
autoConnect: true,
// connectors,
publicClient,
webSocketPublicClient,
});
// Custom connectors
import type { ExternalProvider } from '@ethersproject/providers';
// Create a custom wagmi InjectedConnector using a specific injected EIP-1193 provider (instead of wagmi's default detection logic)
const createInjectedConnectorWithProvider = (provider: ExternalProvider) =>
new (class extends InjectedConnector {
getProvider = async () =>
provider as unknown as Awaited<ReturnType<InjectedConnector['getProvider']>>;
})(injectedConnectorOptions) as InjectedConnector;
const createWalletConnect2ConnectorWithId = (
walletconnectId: string,
walletConnectConfig: WalletConnectConfig
) => {
const walletconnect2ConnectorOptions = getWalletconnect2ConnectorOptions(walletConnectConfig);
return new WalletConnectConnector({
...walletconnect2ConnectorOptions,
options: {
...walletconnect2ConnectorOptions.options,
qrModalOptions: {
...walletconnect2ConnectorOptions.options.qrModalOptions,
explorerRecommendedWalletIds: [walletconnectId],
explorerExcludedWalletIds: 'ALL',
},
},
});
};
};
// Custom connector from wallet selection
export const resolveWagmiConnector = ({
walletType,
walletConnection,
@@ -207,13 +186,23 @@ export const resolveWagmiConnector = ({
walletConnectConfig: WalletConnectConfig;
}) => {
const walletConfig = wallets[walletType];
const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider
? createInjectedConnectorWithProvider(walletConnection.provider)
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id, walletConnectConfig)
: getConnectors(walletConnectConfig).find(
({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId
);
if (walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider) {
if (walletType === WalletType.MetaMask) {
return metaMask();
}
return createdInjectedConnectorWithProvider({ provider: walletConnection.provider });
} else if (
walletConnection.type === WalletConnectionType.WalletConnect2 &&
walletConfig.walletconnect2Id
) {
return walletConnect(
getConnectorInfoForWc2({
walletConnectId: walletConfig.walletconnect2Id,
walletConnectConfig,
})
);
} else {
return getConnector({ walletConnectionType: walletConnection.type, walletConnectConfig });
}
};
+13 -29
View File
@@ -30,9 +30,6 @@ import abacusStateManager from '@/lib/abacus';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
import { MigratePanel } from './rewards/MigratePanel';
const ENS_CHAIN_ID = 1; // Ethereum
const Profile = () => {
@@ -178,27 +175,8 @@ const Profile = () => {
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
/>
</Styled.EqualGrid>
<MigratePanel />
<DYDXBalancePanel />
<Styled.EqualGrid>
<Styled.RewardsPanel
slotHeaderContent="Trading Rewards"
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{
key: 'week-rewards',
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
value: '-',
},
]}
layout="grid"
/>
</Styled.RewardsPanel>
<Panel
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
@@ -213,6 +191,19 @@ const Profile = () => {
layout="grid"
/>
</Panel>
<Styled.RewardsPanel
slotHeaderContent={stringGetter({ key: STRING_KEYS.REWARDS })}
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.YOU_WILL_EARN }), value: '-' },
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.EPOCH_ENDS_IN }), value: '-' },
]}
layout="grid"
/>
</Styled.RewardsPanel>
</Styled.EqualGrid>
<Styled.TablePanel
@@ -336,13 +327,6 @@ Styled.Details = styled(Details)`
`;
Styled.RewardsPanel = styled(Panel)`
align-self: flex-start;
&,
> * {
height: 100%;
}
dl {
--details-grid-numColumns: 1;
}
+12 -2
View File
@@ -2,6 +2,7 @@ import type { ElementType } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins';
import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
@@ -32,7 +33,7 @@ export const DYDXBalancePanel = () => {
const { chainTokenLabel } = useTokenConfigs();
return (
<Panel
<Styled.Panel
slotHeader={
<Styled.Header>
<Styled.Title>
@@ -108,12 +109,21 @@ export const DYDXBalancePanel = () => {
]}
/>
</Styled.Content>
</Panel>
</Styled.Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingX: 1.5rem;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1rem;
+18 -5
View File
@@ -1,11 +1,12 @@
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useQuery } from 'react-query';
import { useQuery } from '@tanstack/react-query';
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { QueryKeys } from '@/constants/queries';
import breakpoints from '@/styles/breakpoints';
import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks';
@@ -68,9 +69,9 @@ const EstimatedRewards = () => {
const stringGetter = useStringGetter();
const { dydxAddress } = useAccounts();
const { data, isLoading } = useQuery({
const { data, isLoading, status, error } = useQuery({
enabled: !!dydxAddress,
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}_${SEASON_NUMBER}`,
queryKey: [QueryKeys.LAUNCH_INCENTIVES, dydxAddress, SEASON_NUMBER],
queryFn: async () => {
if (!dydxAddress) return undefined;
const resp = await fetch(
@@ -78,9 +79,14 @@ const EstimatedRewards = () => {
);
return (await resp.json())?.incentivePoints;
},
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
});
useEffect(() => {
if (status === 'error') {
log('LaunchIncentives/fetchPoints', error);
}
}, [status]);
return (
<Styled.EstimatedRewardsCard>
<Styled.EstimatedRewardsCardContent>
@@ -153,8 +159,15 @@ const LaunchIncentivesContent = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingY: 1rem;
--panel-paddingX: 1.5rem;
background-color: var(--color-layer-4);
width: 100%;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
}
`;
Styled.ForV4 = styled.span`
@@ -218,7 +231,7 @@ Styled.Column = styled.div`
Styled.EstimatedRewardsCard = styled.div`
${layoutMixins.spacedRow}
padding: 1rem 1.25rem;
min-width: 19rem;
min-width: 21.25rem;
height: calc(100% - calc(1.5rem * 2));
margin: 1.5rem;
+11 -5
View File
@@ -7,6 +7,8 @@ import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details';
@@ -26,7 +28,7 @@ import { MustBigNumber } from '@/lib/numbers';
const TOKEN_MIGRATION_LEARN_MORE_LINK =
'https://www.dydx.foundation/blog/update-on-exploring-the-future-of-dydx';
export const MigratePanel = ({ className }: { className?: string }) => {
export const MigratePanel = () => {
const { isNotTablet } = useBreakpoints();
const stringGetter = useStringGetter();
@@ -43,7 +45,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
return isNotTablet ? (
<Styled.MigratePanel
className={className}
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>}
slotRight={
<Styled.MigrateAction>
@@ -73,7 +74,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
</Styled.MigratePanel>
) : (
<Styled.MigratePanel
className={className}
slotHeader={
<Styled.MobileMigrateHeader>
<h3>
@@ -134,11 +134,17 @@ export const MigratePanel = ({ className }: { className?: string }) => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.MigratePanel = styled(Panel)`
--panel-paddingX: 1.5rem;
width: 100%;
background-image: url('/dots-background.svg');
background-position: right;
background-repeat: no-repeat;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Title = styled.h3`
@@ -151,7 +157,7 @@ Styled.Title = styled.h3`
Styled.MigrateAction = styled.div`
${layoutMixins.flexEqualColumns}
align-items: center;
margin: 1rem;
margin-right: 1rem;
gap: 1rem;
padding: 1rem;
width: 100%;
@@ -191,7 +197,7 @@ Styled.MobileMigrateHeader = styled.div`
font: var(--font-small-book);
color: var(--color-text-0);
padding: 1rem 1rem 0;
padding: 1rem 1.5rem 0;
h3 {
${layoutMixins.inlineRow}
+1 -7
View File
@@ -1,6 +1,5 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { breakpoints } from '@/styles';
import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter } from '@/hooks';
@@ -52,7 +51,6 @@ export const RewardsHelpPanel = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.HelpCard = styled(Panel)`
--panel-content-paddingX: 0;
--panel-content-paddingY: 0;
width: 100%;
height: max-content;
@@ -66,15 +64,11 @@ Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1ch;
padding: 1rem 1rem;
padding: 1.25rem 1.5rem;
border-bottom: var(--border-width) solid var(--border-color);
font: var(--font-small-book);
@media ${breakpoints.notTablet} {
padding: 1.5rem 1.25rem;
}
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
+17 -39
View File
@@ -1,18 +1,15 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { AppRoute } from '@/constants/routes';
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { BackButton } from '@/components/BackButton';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
@@ -23,14 +20,12 @@ import { openDialog } from '@/state/dialogs';
import { DYDXBalancePanel } from './DYDXBalancePanel';
import { MigratePanel } from './MigratePanel';
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
import { RewardsHelpPanel } from './RewardsHelpPanel';
const RewardsPage = () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
const { isTablet, isNotTablet } = useBreakpoints();
const navigate = useNavigate();
const panelArrow = (
<Styled.Arrow>
@@ -44,16 +39,13 @@ const RewardsPage = () => {
return (
<Styled.Page>
{isTablet && (
<Styled.MobileHeader>
<BackButton onClick={() => navigate(AppRoute.Profile)} />
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
</Styled.MobileHeader>
)}
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <MigratePanel />}
{import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
{isTablet ? (
<LaunchIncentivesPanel />
<>
<LaunchIncentivesPanel />
<DYDXBalancePanel />
</>
) : (
<Styled.PanelRowIncentivesAndBalance>
<LaunchIncentivesPanel />
@@ -63,9 +55,7 @@ const RewardsPage = () => {
<Styled.PanelRow>
<Styled.Panel
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
}
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
@@ -78,9 +68,7 @@ const RewardsPage = () => {
</Styled.Panel>
<Styled.Panel
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>
}
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
@@ -92,8 +80,6 @@ const RewardsPage = () => {
</Styled.Description>
</Styled.Panel>
</Styled.PanelRow>
<RewardsHelpPanel />
</Styled.Page>
);
};
@@ -109,41 +95,33 @@ Styled.Page = styled.div`
align-items: center;
> * {
--content-max-width: 80rem;
--content-max-width: 70rem;
max-width: min(calc(100vw - 4rem), var(--content-max-width));
}
@media ${breakpoints.tablet} {
--stickyArea-topHeight: var(--page-header-height-mobile);
padding: 0 1rem 1rem;
padding: 1.25rem;
> * {
max-width: calc(100vw - 2rem);
max-width: calc(100vw - 2.5rem);
width: 100%;
}
}
`;
Styled.MobileHeader = styled.header`
${layoutMixins.contentSectionDetachedScrollable}
${layoutMixins.stickyHeader}
z-index: 2;
padding: 1.25rem 0;
margin-bottom: -1.5rem;
font: var(--font-large-medium);
color: var(--color-text-2);
background-color: var(--color-layer-2);
`;
Styled.Panel = styled(Panel)`
height: fit-content;
--panel-paddingX: 1.5rem;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
padding: 1rem 1.5rem 0;
`;
Styled.Description = styled.div`
+6
View File
@@ -119,6 +119,12 @@ export const MarketDetails: React.FC = () => {
tooltip: 'initial-margin-fraction',
value: <Output useGrouping value={initialMarginFraction} type={OutputType.SmallPercent} />,
},
{
key: 'base-position-notional',
label: stringGetter({ key: STRING_KEYS.BASE_POSITION_NOTIONAL }),
tooltip: 'base-position-notional',
value: <Output useGrouping value={basePositionNotional} type={OutputType.Number} />,
},
];
return (
@@ -3,19 +3,18 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { AlertType } from '@/constants/alerts';
import { STRING_KEYS } from '@/constants/localization';
import { WalletType, wallets } from '@/constants/wallets';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { WalletType, wallets } from '@/constants/wallets';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { AlertMessage } from '@/components/AlertMessage';
import { Button } from '@/components/Button';
import { Icon } from '@/components/Icon';
import { Link } from '@/components/Link';
import { useAccounts, useStringGetter, useURLConfigs } from '@/hooks';
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -25,7 +24,7 @@ export const ChooseWallet = () => {
const displayedWallets = useDisplayedWallets();
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts();
const { selectWalletType, selectedWalletType, selectedWalletError } = useWalletConnection();
return (
<>
@@ -86,13 +86,7 @@ export const GenerateKeys = ({
].includes(status);
const signTypedData = getSignTypedData(selectedNetwork);
const { signTypedDataAsync } = useSignTypedData({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const { signTypedDataAsync } = useSignTypedData();
const staticEncryptionKey = import.meta.env.VITE_PK_ENCRYPTION_KEY;
@@ -103,7 +97,13 @@ export const GenerateKeys = ({
// 1. First signature
setStatus(EvmDerivedAccountStatus.Deriving);
const signature = await signTypedDataAsync();
const signature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const { wallet: dydxWallet } = await getWalletFromEvmSignature({ signature });
// 2. Ensure signature is deterministic
@@ -121,7 +121,13 @@ export const GenerateKeys = ({
setStatus(EvmDerivedAccountStatus.EnsuringDeterminism);
// Second signature
const additionalSignature = await signTypedDataAsync();
const additionalSignature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
if (signature !== additionalSignature) {
throw new Error(
@@ -108,6 +108,20 @@ export const DepositButtonAndReceipt = ({
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
const { toAmount, toAmountMin } = useMemo(() => {
if (isCctp) {
return {
toAmount: summary?.toAmount,
toAmountMin: summary?.toAmountMin,
};
} else {
return {
toAmount: summary?.toAmount && formatUnits(BigInt(summary.toAmount), usdcDecimals),
toAmountMin: summary?.toAmountMin && formatUnits(BigInt(summary.toAmountMin), usdcDecimals),
};
}
}, [isCctp, summary]);
const submitButtonReceipt = [
{
key: 'expected-deposit-amount',
@@ -116,7 +130,7 @@ export const DepositButtonAndReceipt = ({
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
</span>
),
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />,
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmount} />,
subitems: [
{
key: 'minimum-deposit-amount',
@@ -126,7 +140,7 @@ export const DepositButtonAndReceipt = ({
</span>
),
value: (
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmountMin} />
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmountMin} />
),
tooltip: 'minimum-deposit-amount',
},
@@ -34,7 +34,6 @@ export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps)
selectedToken && onSelectToken(selectedToken);
},
slotBefore: <Styled.Img src={token.iconUrl} alt="" />,
tag: resources?.tokenResources?.get(token.type)?.symbol,
}));
return (