Compare commits

...

5 Commits
main ... privy

Author SHA1 Message Date
Bill He
b3ec5193a1
undo? 2024-01-18 11:25:21 -08:00
Bill He
7c64301b33
bump privy 2024-01-18 11:23:21 -08:00
Bill He
3ec7fab2f2
debug 2024-01-18 10:37:08 -08:00
Bill He
20585c8ac9
check app id 2024-01-18 10:05:37 -08:00
Bill He
fd181fa745
Privy integration 2024-01-17 16:09:34 -08:00
11 changed files with 1063 additions and 264 deletions

View File

@ -44,6 +44,8 @@
"@dydxprotocol/v4-localization": "^1.1.11", "@dydxprotocol/v4-localization": "^1.1.11",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3", "@js-joda/core": "^5.5.3",
"@privy-io/react-auth": "^1.53.0",
"@privy-io/wagmi-connector": "^0.1.12",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",

1211
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,9 @@ 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 { WagmiConfig } from 'wagmi';
import type { PrivyClientConfig } from '@privy-io/react-auth';
import { PrivyProvider } from '@privy-io/react-auth';
import { PrivyWagmiConnector } from '@privy-io/wagmi-connector';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';
import { GrazProvider } from 'graz'; import { GrazProvider } from 'graz';
@ -32,7 +35,7 @@ import { NotificationsToastArea } from '@/layout/NotificationsToastArea';
import { DialogManager } from '@/layout/DialogManager'; import { DialogManager } from '@/layout/DialogManager';
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog'; import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
import { config } from '@/lib/wagmi'; import { config, privyConfig } from '@/lib/wagmi';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@ -120,8 +123,13 @@ const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
}; };
const providers = [ const providers = [
wrapProvider(PrivyProvider, {
appId: import.meta.env.VITE_PRIVY_APP_ID,
config: privyConfig,
}),
wrapProvider(QueryClientProvider, { client: queryClient }), wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider), wrapProvider(GrazProvider),
wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: config }),
wrapProvider(WagmiConfig, { config }), wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider), wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider), wrapProvider(RestrictionProvider),

View File

@ -31,6 +31,7 @@ import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
export enum WalletConnectionType { export enum WalletConnectionType {
CoinbaseWalletSdk = 'coinbaseWalletSdk', CoinbaseWalletSdk = 'coinbaseWalletSdk',
CosmosSigner = 'CosmosSigner', CosmosSigner = 'CosmosSigner',
Privy = 'Privy',
InjectedEip1193 = 'injectedEip1193', InjectedEip1193 = 'injectedEip1193',
WalletConnect2 = 'walletConnect2', WalletConnect2 = 'walletConnect2',
} }
@ -68,6 +69,9 @@ export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectio
[WalletConnectionType.CosmosSigner]: { [WalletConnectionType.CosmosSigner]: {
name: 'CosmosSigner', name: 'CosmosSigner',
}, },
[WalletConnectionType.Privy]: {
name: 'Privy',
},
}; };
// Wallets // Wallets
@ -90,6 +94,7 @@ export enum WalletType {
WalletConnect2 = 'WALLETCONNECT_2', WalletConnect2 = 'WALLETCONNECT_2',
// TestWallet = 'TEST_WALLET', // TestWallet = 'TEST_WALLET',
OtherWallet = 'OTHER_WALLET', OtherWallet = 'OTHER_WALLET',
Privy = 'PRIVY',
} }
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = { const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
@ -243,6 +248,12 @@ export const wallets: Record<WalletType, WalletConfig> = {
icon: KeplrIcon, icon: KeplrIcon,
connectionTypes: [WalletConnectionType.CosmosSigner], connectionTypes: [WalletConnectionType.CosmosSigner],
}, },
[WalletType.Privy]: {
type: WalletType.Privy,
stringKey: STRING_KEYS.KEPLR,
icon: GenericWalletIcon,
connectionTypes: [WalletConnectionType.Privy],
},
}; };
// Injected EIP-1193 Providers // Injected EIP-1193 Providers

View File

@ -1,13 +1,19 @@
import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react'; import { useCallback, useContext, createContext, useEffect, useState, useMemo } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { AES, enc } from 'crypto-js'; import { AES, enc } from 'crypto-js';
import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js'; import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account'; import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage'; import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets'; import {
DydxAddress,
EvmAddress,
PrivateInformation,
WalletConnectionType,
getSignTypedData,
} from '@/constants/wallets';
import { setOnboardingState, setOnboardingGuard } from '@/state/account'; import { setOnboardingState, setOnboardingGuard } from '@/state/account';
import { forceOpenDialog } from '@/state/dialogs'; import { forceOpenDialog } from '@/state/dialogs';
@ -20,6 +26,11 @@ import { useLocalStorage } from './useLocalStorage';
import { useRestrictions } from './useRestrictions'; import { useRestrictions } from './useRestrictions';
import { useWalletConnection } from './useWalletConnection'; import { useWalletConnection } from './useWalletConnection';
import { useSignTypedData } from 'wagmi';
import { getSelectedNetwork } from '@/state/appSelectors';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { usePrivy } from '@privy-io/react-auth';
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined); const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
AccountsContext.displayName = 'Accounts'; AccountsContext.displayName = 'Accounts';
@ -176,6 +187,21 @@ const useAccountsContext = () => {
} }
}, [evmAddress, dydxAddress]); }, [evmAddress, dydxAddress]);
// 1. Switch network
const selectedNetwork = useSelector(getSelectedNetwork);
const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const signTypedData = getSignTypedData(selectedNetwork);
const { signTypedDataAsync } = useSignTypedData({
...signTypedData,
domain: {
...signTypedData.domain,
chainId,
},
});
const { ready, authenticated } = usePrivy();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (connectedDydxAddress && signerGraz) { if (connectedDydxAddress && signerGraz) {
@ -192,7 +218,17 @@ const useAccountsContext = () => {
const evmDerivedAccount = evmDerivedAddresses[evmAddress]; const evmDerivedAccount = evmDerivedAddresses[evmAddress];
if (evmDerivedAccount?.encryptedSignature) { if (walletConnectionType === WalletConnectionType.Privy && authenticated && ready) {
try {
const signature = await signTypedDataAsync();
await setWalletFromEvmSignature(signature);
dispatch(setOnboardingState(OnboardingState.AccountConnected));
} catch (error) {
log('useAccounts/decryptSignature', error);
forgetEvmSignature();
}
} else if (evmDerivedAccount?.encryptedSignature) {
try { try {
const signature = decryptSignature(evmDerivedAccount.encryptedSignature); const signature = decryptSignature(evmDerivedAccount.encryptedSignature);

View File

@ -21,6 +21,7 @@ export const useDisplayedWallets = () => {
// WalletType.BitKeep, // WalletType.BitKeep,
// WalletType.Coin98, // WalletType.Coin98,
WalletType.Privy,
WalletType.OtherWallet, WalletType.OtherWallet,
].filter(isTruthy); ].filter(isTruthy);
}; };

View File

@ -1,5 +1,9 @@
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useNetwork, useSwitchNetwork } from 'wagmi'; import { useNetwork, useSwitchNetwork } from 'wagmi';
import { useSwitchNetwork as useSwitchNetworkPrivy } from '@privy-io/wagmi-connector';
import { WalletConnectionType } from '@/constants/wallets';
import { useWalletConnection } from './useWalletConnection';
export const useMatchingEvmNetwork = ({ export const useMatchingEvmNetwork = ({
chainId, chainId,
@ -11,7 +15,10 @@ export const useMatchingEvmNetwork = ({
onError?: (error: Error) => void; onError?: (error: Error) => void;
}) => { }) => {
const { chain } = useNetwork(); const { chain } = useNetwork();
const { walletConnectionType } = useWalletConnection();
const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError }); const { isLoading, switchNetworkAsync } = useSwitchNetwork({ onError });
const { isLoading: isLoadingPrivy, switchNetworkAsync: switchNetworkAsyncPrivy } =
useSwitchNetworkPrivy({ onError });
// 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 +28,11 @@ export const useMatchingEvmNetwork = ({
const matchNetwork = useCallback(async () => { const matchNetwork = useCallback(async () => {
if (!isMatchingNetwork) { if (!isMatchingNetwork) {
await switchNetworkAsync?.(Number(chainId)); if (walletConnectionType === WalletConnectionType.Privy) {
await switchNetworkAsyncPrivy?.(Number(chainId));
} else {
await switchNetworkAsync?.(Number(chainId));
}
} }
}, [chainId, chain]); }, [chainId, chain]);
@ -34,6 +45,6 @@ export const useMatchingEvmNetwork = ({
return { return {
isMatchingNetwork, isMatchingNetwork,
matchNetwork, matchNetwork,
isSwitchingNetwork: isLoading, isSwitchingNetwork: isLoading || isLoadingPrivy,
}; };
}; };

View File

@ -1,5 +1,6 @@
import { useCallback, useEffect, useState, useMemo } from 'react'; import { useCallback, useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { usePrivy, useLogout } from '@privy-io/react-auth';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
@ -104,8 +105,10 @@ export const useWalletConnection = () => {
[walletConnectConfig, walletType, walletConnectionType] [walletConnectConfig, walletType, walletConnectionType]
); );
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector }) const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
const { suggestAndConnect: connectGraz } = useConnectGraz(); const { suggestAndConnect: connectGraz } = useConnectGraz();
const { login, ready, authenticated } = usePrivy();
const { logout } = useLogout();
const connectWallet = useCallback( const connectWallet = useCallback(
async ({ walletType }: { walletType: WalletType }) => { async ({ walletType }: { walletType: WalletType }) => {
@ -114,6 +117,12 @@ export const useWalletConnection = () => {
try { try {
if (!walletConnection) { if (!walletConnection) {
throw new Error('Onboarding: No wallet connection found.'); throw new Error('Onboarding: No wallet connection found.');
} else if (walletConnection.type === WalletConnectionType.Privy) {
console.log({ isConnectedWagmi, authenticated, ready });
if (!isConnectedWagmi && !authenticated && ready) {
login();
}
} else if (walletConnection.type === WalletConnectionType.CosmosSigner) { } else if (walletConnection.type === WalletConnectionType.CosmosSigner) {
const cosmosWalletType = { const cosmosWalletType = {
[WalletType.Keplr as string]: CosmosWalletType.KEPLR, [WalletType.Keplr as string]: CosmosWalletType.KEPLR,
@ -165,6 +174,7 @@ export const useWalletConnection = () => {
if (isConnectedWagmi) await disconnectWagmi(); if (isConnectedWagmi) await disconnectWagmi();
if (isConnectedGraz) await disconnectGraz(); if (isConnectedGraz) await disconnectGraz();
if (authenticated) await logout();
}, [isConnectedGraz, isConnectedWagmi]); }, [isConnectedGraz, isConnectedWagmi]);
// Wallet selection // Wallet selection

View File

@ -1,5 +1,6 @@
import { createConfig, configureChains, mainnet, Chain } from 'wagmi'; import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
import { goerli } from 'wagmi/chains'; import { goerli } from 'wagmi/chains';
import type { PrivyClientConfig } from '@privy-io/react-auth';
import { import {
arbitrum, arbitrum,
@ -85,6 +86,16 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
kava, kava,
]; ];
export const privyConfig: PrivyClientConfig = {
embeddedWallets: {
createOnLogin: 'users-without-wallets',
requireUserPasswordOnCreate: true,
noPromptOnSignature: true,
},
loginMethods: ['email', 'sms', 'twitter'],
defaultChain: sepolia
};
const { chains, publicClient, webSocketPublicClient } = configureChains( const { chains, publicClient, webSocketPublicClient } = configureChains(
WAGMI_SUPPORTED_CHAINS, WAGMI_SUPPORTED_CHAINS,
[ [

View File

@ -62,6 +62,12 @@ export const getWalletConnection = ({
type: WalletConnectionType.CosmosSigner, type: WalletConnectionType.CosmosSigner,
}; };
} }
case WalletConnectionType.Privy: {
return {
type: WalletConnectionType.Privy,
};
}
} }
} }
}; };

View File

@ -56,7 +56,11 @@ export const ChooseWallet = () => {
slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />} slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />}
size={ButtonSize.Small} size={ButtonSize.Small}
> >
<div>{stringGetter({ key: wallets[walletType].stringKey })}</div> <div>
{walletType !== WalletType.Privy && import.meta.env.VITE_PRIVY_APP_ID
? stringGetter({ key: wallets[walletType].stringKey })
: 'Socials (Email, SMS, etc.)'}
</div>
</Styled.WalletButton> </Styled.WalletButton>
))} ))}
</Styled.Wallets> </Styled.Wallets>