Compare commits

...

1 Commits

Author SHA1 Message Date
jaredvu
5a91e56ac3
🚧 WAGMI 2 2024-01-09 13:28:21 -08:00
15 changed files with 4558 additions and 793 deletions

View File

@ -72,6 +72,7 @@
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@scure/bip32": "^1.3.0", "@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0", "@scure/bip39": "^1.2.0",
"@tanstack/react-query": "^5.17.9",
"@types/lodash": "^4.14.195", "@types/lodash": "^4.14.195",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@visx/axis": "^3.1.0", "@visx/axis": "^3.1.0",
@ -104,15 +105,15 @@
"react-aria": "^3.25.0", "react-aria": "^3.25.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-number-format": "^5.2.2", "react-number-format": "^5.2.2",
"react-query": "^3.39.3",
"react-redux": "^8.1.1", "react-redux": "^8.1.1",
"react-router-dom": "^6.14.0", "react-router-dom": "^6.14.0",
"react-stately": "^3.23.0", "react-stately": "^3.23.0",
"reselect": "^4.1.8", "reselect": "^4.1.8",
"stream-browserify": "^3.0.0",
"styled-components": "^5.3.11", "styled-components": "^5.3.11",
"use-latest": "^1.2.1", "use-latest": "^1.2.1",
"viem": "^1.12.2", "viem": "^2.0.0",
"wagmi": "^1.4.12" "wagmi": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.5", "@babel/core": "^7.22.5",

4884
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,7 @@
"environments": { "environments": {
"dydxprotocol-dev": { "dydxprotocol-dev": {
"name": "v4 Dev", "name": "v4 Dev",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -113,7 +113,7 @@
}, },
"dydxprotocol-dev-2": { "dydxprotocol-dev-2": {
"name": "v4 Dev 2", "name": "v4 Dev 2",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -186,7 +186,7 @@
}, },
"dydxprotocol-dev-4": { "dydxprotocol-dev-4": {
"name": "v4 Dev 4", "name": "v4 Dev 4",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -259,7 +259,7 @@
}, },
"dydxprotocol-dev-5": { "dydxprotocol-dev-5": {
"name": "v4 Dev 5", "name": "v4 Dev 5",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -332,7 +332,7 @@
}, },
"dydxprotocol-staging": { "dydxprotocol-staging": {
"name": "v4 Staging", "name": "v4 Staging",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -407,7 +407,7 @@
}, },
"dydxprotocol-staging-forced-update": { "dydxprotocol-staging-forced-update": {
"name": "v4 Staging Forced Update", "name": "v4 Staging Forced Update",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -480,7 +480,7 @@
}, },
"dydxprotocol-staging-west": { "dydxprotocol-staging-west": {
"name": "v4 Staging West", "name": "v4 Staging West",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -555,7 +555,7 @@
}, },
"dydxprotocol-testnet": { "dydxprotocol-testnet": {
"name": "v4 Public Testnet", "name": "v4 Public Testnet",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -634,7 +634,7 @@
}, },
"dydxprotocol-testnet-dydx": { "dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX", "name": "v4 Public Testnet/dYdX",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -710,7 +710,7 @@
}, },
"dydxprotocol-testnet-nodefleet": { "dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet", "name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -786,7 +786,7 @@
}, },
"dydxprotocol-testnet-kingnodes": { "dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes", "name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -862,7 +862,7 @@
}, },
"dydxprotocol-testnet-liquify": { "dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify", "name": "v4 Public Testnet/Liquify",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -938,7 +938,7 @@
}, },
"dydxprotocol-testnet-polkachu": { "dydxprotocol-testnet-polkachu": {
"name": "v4 Public Testnet/Polkahcu", "name": "v4 Public Testnet/Polkahcu",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",
@ -1005,7 +1005,7 @@
}, },
"dydxprotocol-testnet-bware": { "dydxprotocol-testnet-bware": {
"name": "v4 Public Testnet/BWare", "name": "v4 Public Testnet/BWare",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png", "chainLogo": "/dydx-chain.png",

View File

@ -1,8 +1,8 @@
import { lazy, Suspense } from 'react'; import { lazy, Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent, css } from 'styled-components'; import styled, { AnyStyledComponent, css } from 'styled-components';
import { WagmiConfig } from 'wagmi'; import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { GrazProvider } from 'graz'; import { GrazProvider } from 'graz';
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes'; import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
@ -120,9 +120,9 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
}; };
const providers = [ const providers = [
wrapProvider(WagmiProvider, { config }),
wrapProvider(QueryClientProvider, { client: queryClient }), wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider), wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider), wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider), wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider), wrapProvider(DydxProvider),

5
src/constants/queries.ts Normal file
View File

@ -0,0 +1,5 @@
export enum QueryKeys {
ACCOUNT_BALANCE = 'ACCOUNT_BALANCE',
LAUNCH_INCENTIVES = 'LAUNCH_INCENTIVES',
TRANSACTION_STATUS = 'TRANSACTION_STATUS',
}

View File

@ -52,7 +52,7 @@ type WalletConnectionTypeConfig = {
wagmiConnectorId?: string; wagmiConnectorId?: string;
}; };
export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectionTypeConfig> = { export const WALLET_CONNECTION_TYPES: Record<WalletConnectionType, WalletConnectionTypeConfig> = {
[WalletConnectionType.CoinbaseWalletSdk]: { [WalletConnectionType.CoinbaseWalletSdk]: {
name: 'Coinbase Wallet SDK', name: 'Coinbase Wallet SDK',
wagmiConnectorId: 'coinbaseWallet', wagmiConnectorId: 'coinbaseWallet',
@ -110,7 +110,7 @@ export const WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS = Object.values(
WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS
); );
type WalletConfig = { export type WalletConfig = {
type: WalletType; type: WalletType;
stringKey: string; stringKey: string;
icon: string; icon: string;
@ -274,6 +274,17 @@ export type WalletConnection = {
provider?: ExternalProvider; provider?: ExternalProvider;
}; };
export type WalletConnectConfig = {
client: {
name: string;
description: string;
iconUrl: string;
};
v2: {
projectId: string;
};
};
// dYdX Chain wallets // dYdX Chain wallets
export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0"; export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";

View File

@ -1,11 +1,12 @@
import { useCallback } from 'react'; import { useCallback, useEffect } from 'react';
import { shallowEqual, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import { useBalance } from 'wagmi'; import { useBalance, useBlockNumber } from 'wagmi';
import { StargateClient } from '@cosmjs/stargate'; import { StargateClient } from '@cosmjs/stargate';
import { useQuery } from 'react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { formatUnits } from 'viem'; import { formatUnits } from 'viem';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { QueryKeys } from '@/constants/queries';
import { EvmAddress } from '@/constants/wallets'; import { EvmAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils'; import { convertBech32Address } from '@/lib/addressUtils';
@ -52,15 +53,27 @@ export const useAccountBalance = ({
const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId); const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const stakingBalances = useSelector(getStakingBalances, shallowEqual); const stakingBalances = useSelector(getStakingBalances, shallowEqual);
const evmQuery = useBalance({ const queryClient = useQueryClient();
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')), const { data: blockNumber } = useBlockNumber({ watch: true });
const {
data: evmQuery,
status: evmBalanceStatus,
fetchStatus: evmBalanceFetchStatus,
queryKey,
} = useBalance({
query: {
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
},
address: evmAddress, address: evmAddress,
chainId: typeof chainId === 'number' ? chainId : Number(evmChainId), chainId: typeof chainId === 'number' ? chainId : Number(evmChainId),
token: token:
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS ? undefined : (addressOrDenom as EvmAddress), addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS ? undefined : (addressOrDenom as EvmAddress),
watch: true,
}); });
useEffect(() => {
queryClient.invalidateQueries({ queryKey });
}, [blockNumber]);
const cosmosQueryFn = useCallback(async () => { const cosmosQueryFn = useCallback(async () => {
if (dydxAddress && bech32AddrPrefix && rpc && addressOrDenom) { if (dydxAddress && bech32AddrPrefix && rpc && addressOrDenom) {
const address = convertBech32Address({ const address = convertBech32Address({
@ -78,7 +91,7 @@ export const useAccountBalance = ({
const cosmosQuery = useQuery({ const cosmosQuery = useQuery({
enabled: Boolean(isCosmosChain && dydxAddress && bech32AddrPrefix && rpc && addressOrDenom), enabled: Boolean(isCosmosChain && dydxAddress && bech32AddrPrefix && rpc && addressOrDenom),
queryKey: `accountBalances_${chainId}_${addressOrDenom}`, queryKey: [QueryKeys.ACCOUNT_BALANCE, chainId, addressOrDenom],
queryFn: cosmosQueryFn, queryFn: cosmosQueryFn,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchOnMount: false, refetchOnMount: false,
@ -87,8 +100,11 @@ export const useAccountBalance = ({
staleTime: 10_000, staleTime: 10_000,
}); });
const { formatted: evmBalance } = evmQuery.data || {}; const balance = isCosmosChain
const balance = isCosmosChain ? cosmosQuery.data : evmBalance; ? cosmosQuery.data
: evmQuery
? formatUnits(evmQuery?.value, evmQuery?.decimals)
: undefined;
const nativeTokenCoinBalance = balances?.[chainTokenDenom]; const nativeTokenCoinBalance = balances?.[chainTokenDenom];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount); const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
@ -104,7 +120,7 @@ export const useAccountBalance = ({
nativeTokenBalance, nativeTokenBalance,
nativeStakingBalance, nativeStakingBalance,
usdcBalance, usdcBalance,
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status, queryStatus: isCosmosChain ? cosmosQuery.status : evmBalanceStatus,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching', isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmBalanceFetchStatus === 'fetching',
}; };
}; };

View File

@ -39,7 +39,6 @@ const useAccountsContext = () => {
walletConnectionType, walletConnectionType,
selectWalletType, selectWalletType,
selectedWalletType, selectedWalletType,
selectedWalletError,
evmAddress, evmAddress,
signerWagmi, signerWagmi,
publicClientWagmi, publicClientWagmi,
@ -300,9 +299,7 @@ const useAccountsContext = () => {
walletConnectionType, walletConnectionType,
// Wallet selection // Wallet selection
selectWalletType,
selectedWalletType, selectedWalletType,
selectedWalletError,
// Wallet connection (EVM) // Wallet connection (EVM)
evmAddress, evmAddress,

View File

@ -1,8 +1,9 @@
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react'; 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 { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import { type TransferNotifcation } from '@/constants/notifications'; import { type TransferNotifcation } from '@/constants/notifications';
import { QueryKeys } from '@/constants/queries';
import { useAccounts } from '@/hooks/useAccounts'; import { useAccounts } from '@/hooks/useAccounts';
import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid'; import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid';
@ -68,8 +69,8 @@ const useLocalNotificationsContext = () => {
[transferNotifications] [transferNotifications]
); );
useQuery({ const { data: newTransferNotifications } = useQuery({
queryKey: 'getTransactionStatus', queryKey: [QueryKeys.TRANSACTION_STATUS],
queryFn: async () => { queryFn: async () => {
const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => { const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
const newTransferNotifications = await Promise.all( const newTransferNotifications = await Promise.all(
@ -84,10 +85,11 @@ const useLocalNotificationsContext = () => {
status: currentStatus, status: currentStatus,
} = transferNotification; } = transferNotification;
// @ts-ignore status.errors is not in the type definition but can be returned const hasErrors =
// also error can some time come back as an empty object so we need to ignore for that // @ts-ignore status.errors is not in the type definition but can be returned
const hasErrors = !!currentStatus?.errors || // also error can some time come back as an empty object so we need to ignore for that
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0); !!currentStatus?.errors ||
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
if ( if (
!hasErrors && !hasErrors &&
@ -95,11 +97,14 @@ const useLocalNotificationsContext = () => {
currentStatus?.squidTransactionStatus === 'ongoing') currentStatus?.squidTransactionStatus === 'ongoing')
) { ) {
try { try {
const status = await fetchSquidStatus({ const status = await fetchSquidStatus(
transactionId: txHash, {
toChainId, transactionId: txHash,
fromChainId, toChainId,
}, isCctp); fromChainId,
},
isCctp
);
if (status) { if (status) {
transferNotification.status = status; transferNotification.status = status;
@ -121,12 +126,18 @@ const useLocalNotificationsContext = () => {
return newTransferNotifications; return newTransferNotifications;
}; };
const newTransferNotifications = await processTransferNotifications(transferNotifications); const newTransferNotifications = await processTransferNotifications(transferNotifications);
setTransferNotifications(newTransferNotifications); return newTransferNotifications;
}, },
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL, refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
}); });
useEffect(() => {
if (!newTransferNotifications) return;
setTransferNotifications(newTransferNotifications);
}, [newTransferNotifications]);
return { return {
transferNotifications, transferNotifications,
addTransferNotification, addTransferNotification,

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useNetwork, useSwitchNetwork } from 'wagmi'; import { UseSwitchChainReturnType, useAccount, useSwitchChain } from 'wagmi';
export const useMatchingEvmNetwork = ({ export const useMatchingEvmNetwork = ({
chainId, chainId,
@ -8,10 +8,10 @@ export const useMatchingEvmNetwork = ({
}: { }: {
chainId?: string | number; chainId?: string | number;
switchAutomatically?: boolean; switchAutomatically?: boolean;
onError?: (error: Error) => void; onError?: Parameters<UseSwitchChainReturnType['switchChainAsync']>[1]['onError'];
}) => { }) => {
const { chain } = useNetwork(); const { chain } = useAccount();
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError }); const { switchChainAsync, isPending } = useSwitchChain();
// If chainId is not a number, we can assume it is a non EVM compatible chain // If chainId is not a number, we can assume it is a non EVM compatible chain
const isMatchingNetwork = useMemo( const isMatchingNetwork = useMemo(
@ -21,7 +21,14 @@ export const useMatchingEvmNetwork = ({
const matchNetwork = useCallback(async () => { const matchNetwork = useCallback(async () => {
if (!isMatchingNetwork) { if (!isMatchingNetwork) {
await switchNetworkAsync?.(Number(chainId)); await switchChainAsync?.(
{
chainId: Number(chainId),
},
{
onError,
}
);
} }
}, [chainId, chain]); }, [chainId, chain]);
@ -34,6 +41,6 @@ export const useMatchingEvmNetwork = ({
return { return {
isMatchingNetwork, isMatchingNetwork,
matchNetwork, matchNetwork,
isSwitchingNetwork: isLoading, isSwitchingNetwork: isPending,
}; };
}; };

View File

@ -104,12 +104,13 @@ export const useWalletConnection = () => {
[walletConnectConfig, walletType, walletConnectionType] [walletConnectConfig, walletType, walletConnectionType]
); );
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector }) const { connectAsync: connectWagmi } = useConnectWagmi();
const { suggestAndConnect: connectGraz } = useConnectGraz(); const { suggestAndConnect: connectGraz } = useConnectGraz();
const connectWallet = useCallback( const connectWallet = useCallback(
async ({ walletType }: { walletType: WalletType }) => { async ({ walletType }: { walletType: WalletType }) => {
const walletConnection = getWalletConnection({ walletType }); const walletConnection = getWalletConnection({ walletType });
console.log({ walletConnection });
try { try {
if (!walletConnection) { if (!walletConnection) {
@ -133,13 +134,17 @@ export const useWalletConnection = () => {
} }
} else { } else {
if (!isConnectedWagmi) { if (!isConnectedWagmi) {
await connectWagmi({ const connector = resolveWagmiConnector({
connector: resolveWagmiConnector({ walletType,
walletType, walletConnection,
walletConnection, walletConnectConfig,
walletConnectConfig,
}),
}); });
if (connector) {
await connectWagmi({
connector,
});
}
} }
} }
} catch (error) { } catch (error) {
@ -156,7 +161,7 @@ export const useWalletConnection = () => {
walletConnectionType: walletConnection.type, walletConnectionType: walletConnection.type,
}; };
}, },
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi] [isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi, walletConnectConfig]
); );
const disconnectWallet = useCallback(async () => { const disconnectWallet = useCallback(async () => {
@ -204,10 +209,9 @@ export const useWalletConnection = () => {
})(); })();
}, [selectedWalletType, signerWagmi, signerGraz]); }, [selectedWalletType, signerWagmi, signerGraz]);
const selectWalletType = async (walletType: WalletType | undefined) => { const selectWalletType = (walletType: WalletType | undefined) => {
if (selectedWalletType) { if (selectedWalletType) {
setSelectedWalletType(undefined); setSelectedWalletType(undefined);
await new Promise(requestAnimationFrame);
} }
setSelectedWalletType(walletType); setSelectedWalletType(walletType);

View File

@ -1,7 +1,6 @@
import { createConfig, configureChains, mainnet, Chain, Connector, usePublicClient } from 'wagmi'; import { createConfig, createConnector, http, usePublicClient } from 'wagmi';
import { goerli } from 'wagmi/chains';
import { import {
type Chain,
arbitrum, arbitrum,
arbitrumGoerli, arbitrumGoerli,
avalanche, avalanche,
@ -26,34 +25,33 @@ import {
fantomTestnet, fantomTestnet,
celo, celo,
celoAlfajores, celoAlfajores,
} from 'viem/chains'; mainnet,
goerli,
sepolia,
} from 'wagmi/chains';
import { alchemyProvider } from 'wagmi/providers/alchemy'; import { coinbaseWallet, injected, metaMask, walletConnect } from 'wagmi/connectors';
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'; import type { ExternalProvider } from '@ethersproject/providers';
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 { import {
type WalletConnectConfig,
type WalletConnection, type WalletConnection,
WalletConnectionType, WalletConnectionType,
type WalletType, WalletType,
walletConnectionTypes, WALLET_CONNECTION_TYPES,
wallets, wallets,
WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS, WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
WalletConfig,
} from '@/constants/wallets'; } from '@/constants/wallets';
import { isTruthy } from './isTruthy'; import { isTruthy } from './isTruthy';
import { log } from './telemetry';
// Config // Config
const WAGMI_SUPPORTED_CHAINS: Parameters<typeof createConfig>[0]['chains'] = [
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
mainnet, mainnet,
goerli, goerli,
sepolia,
arbitrum, arbitrum,
arbitrumGoerli, arbitrumGoerli,
avalanche, avalanche,
@ -80,118 +78,104 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
celoAlfajores, celoAlfajores,
]; ];
const { chains, publicClient, webSocketPublicClient } = configureChains( export const config = createConfig({
WAGMI_SUPPORTED_CHAINS, chains: WAGMI_SUPPORTED_CHAINS,
[ transports: Object.fromEntries(
import.meta.env.VITE_ALCHEMY_API_KEY && Object.values(WAGMI_SUPPORTED_CHAINS).map(({ id }) => [id, http()])
alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }), ),
jsonRpcProvider({ });
rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] }),
}),
publicProvider(),
].filter(isTruthy)
);
const injectedConnectorOptions = { const createdInjectedConnectorWithProvider = ({ provider }: { provider: ExternalProvider }) => {
chains, console.log(provider);
options: { return injected({
name: 'Injected', target() {
return {
id: 'windowProvider',
name: 'Injected',
provider,
};
},
shimDisconnect: true, shimDisconnect: true,
shimChainChangedDisconnect: false, });
},
}; };
type WalletConnectConfig = { const getConnector = ({
client: { walletConnectionType,
name: string; walletConnectConfig,
description: string; }: {
iconUrl: string; walletConnectionType: WalletConnectionType;
}; walletConnectConfig: WalletConnectConfig;
v2: { }) => {
projectId: string; 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 = ( const getConnectorInfoForWc2 = ({
config: WalletConnectConfig walletConnectId,
): ConstructorParameters<typeof WalletConnectConnector>[0] => ({ walletConnectConfig,
chains, }: {
options: { walletConnectId?: string;
projectId: config.v2.projectId, walletConnectConfig: WalletConnectConfig;
}) => {
const explorerRecommendedWalletIds = walletConnectId
? [walletConnectId]
: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS;
return {
projectId: walletConnectConfig.v2.projectId,
metadata: { metadata: {
name: config.client.name, name: walletConnectConfig.client.name,
description: config.client.description, description: walletConnectConfig.client.description,
url: import.meta.env.VITE_BASE_URL, url: import.meta.env.VITE_BASE_URL,
icons: [config.client.iconUrl], icons: [walletConnectConfig.client.iconUrl],
}, },
showQrModal: true, showQrModal: true,
qrModalOptions: { qrModalOptions: {
themeMode: 'dark' as const, themeMode: 'dark' as const,
themeVariables: { themeVariables: {
'--wcm-accent-color': '#5973fe', '--wcm-accent-color': 'var(--color-accent)',
'--wcm-font-family': 'var(--fontFamily-base)', '--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 // Custom connector from wallet selection
export const resolveWagmiConnector = ({ export const resolveWagmiConnector = ({
walletType, walletType,
walletConnection, walletConnection,
@ -202,13 +186,23 @@ export const resolveWagmiConnector = ({
walletConnectConfig: WalletConnectConfig; walletConnectConfig: WalletConnectConfig;
}) => { }) => {
const walletConfig = wallets[walletType]; const walletConfig = wallets[walletType];
const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider if (walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider) {
? createInjectedConnectorWithProvider(walletConnection.provider) if (walletType === WalletType.MetaMask) {
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id return metaMask();
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id, walletConnectConfig) }
: getConnectors(walletConnectConfig).find( return createdInjectedConnectorWithProvider({ provider: walletConnection.provider });
({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId } else if (
); walletConnection.type === WalletConnectionType.WalletConnect2 &&
walletConfig.walletconnect2Id
) {
return walletConnect(
getConnectorInfoForWc2({
walletConnectId: walletConfig.walletconnect2Id,
walletConnectConfig,
})
);
} else {
return getConnector({ walletConnectionType: walletConnection.type, walletConnectConfig });
}
}; };

View File

@ -1,11 +1,12 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useQuery } from 'react-query'; import { useQuery } from '@tanstack/react-query';
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons'; import { ButtonAction } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { QueryKeys } from '@/constants/queries';
import breakpoints from '@/styles/breakpoints'; import breakpoints from '@/styles/breakpoints';
import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks'; import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks';
@ -68,17 +69,24 @@ const EstimatedRewards = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { dydxAddress } = useAccounts(); const { dydxAddress } = useAccounts();
const { data, isLoading } = useQuery({ const { data, isLoading, status, error } = useQuery({
enabled: !!dydxAddress, enabled: !!dydxAddress,
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}`, queryKey: [QueryKeys.LAUNCH_INCENTIVES, dydxAddress, SEASON_NUMBER],
queryFn: async () => { queryFn: async () => {
if (!dydxAddress) return undefined; if (!dydxAddress) return undefined;
const resp = await fetch(`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}?n=${SEASON_NUMBER}`); const resp = await fetch(
`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}?n=${SEASON_NUMBER}`
);
return (await resp.json())?.incentivePoints; return (await resp.json())?.incentivePoints;
}, },
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
}); });
useEffect(() => {
if (status === 'error') {
log('LaunchIncentives/fetchPoints', error);
}
}, [status]);
return ( return (
<Styled.EstimatedRewardsCard> <Styled.EstimatedRewardsCard>
<Styled.EstimatedRewardsCardContent> <Styled.EstimatedRewardsCardContent>

View File

@ -3,19 +3,18 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { AlertType } from '@/constants/alerts'; import { AlertType } from '@/constants/alerts';
import { STRING_KEYS } from '@/constants/localization';
import { WalletType, wallets } from '@/constants/wallets';
import { ButtonAction, ButtonSize } from '@/constants/buttons'; 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 { AlertMessage } from '@/components/AlertMessage';
import { Button } from '@/components/Button'; import { Button } from '@/components/Button';
import { Icon } from '@/components/Icon'; import { Icon } from '@/components/Icon';
import { Link } from '@/components/Link'; import { Link } from '@/components/Link';
import { useAccounts, useStringGetter, useURLConfigs } from '@/hooks';
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@ -25,7 +24,7 @@ export const ChooseWallet = () => {
const displayedWallets = useDisplayedWallets(); const displayedWallets = useDisplayedWallets();
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts(); const { selectWalletType, selectedWalletType, selectedWalletError } = useWalletConnection();
return ( return (
<> <>

View File

@ -86,13 +86,7 @@ export const GenerateKeys = ({
].includes(status); ].includes(status);
const signTypedData = getSignTypedData(selectedNetwork); const signTypedData = getSignTypedData(selectedNetwork);
const { signTypedDataAsync } = useSignTypedData({ const { signTypedDataAsync } = useSignTypedData();
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const staticEncryptionKey = import.meta.env.VITE_PK_ENCRYPTION_KEY; const staticEncryptionKey = import.meta.env.VITE_PK_ENCRYPTION_KEY;
@ -103,7 +97,13 @@ export const GenerateKeys = ({
// 1. First signature // 1. First signature
setStatus(EvmDerivedAccountStatus.Deriving); setStatus(EvmDerivedAccountStatus.Deriving);
const signature = await signTypedDataAsync(); const signature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const { wallet: dydxWallet } = await getWalletFromEvmSignature({ signature }); const { wallet: dydxWallet } = await getWalletFromEvmSignature({ signature });
// 2. Ensure signature is deterministic // 2. Ensure signature is deterministic
@ -121,7 +121,13 @@ export const GenerateKeys = ({
setStatus(EvmDerivedAccountStatus.EnsuringDeterminism); setStatus(EvmDerivedAccountStatus.EnsuringDeterminism);
// Second signature // Second signature
const additionalSignature = await signTypedDataAsync(); const additionalSignature = await signTypedDataAsync({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
if (signature !== additionalSignature) { if (signature !== additionalSignature) {
throw new Error( throw new Error(