Initial commit

This commit is contained in:
James Jia - Test
2023-09-08 13:52:13 -07:00
commit 4b86068d8f
584 changed files with 59963 additions and 0 deletions
@@ -0,0 +1,60 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { useAccounts, useStringGetter } from '@/hooks';
import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons';
import { Button } from '@/components/Button';
import { Link } from '@/components/Link';
type ElementProps = {
onContinue?: () => void;
};
export const AcknowledgeTerms = ({ onContinue }: ElementProps) => {
const stringGetter = useStringGetter();
const { saveHasAcknowledgedTerms } = useAccounts();
const onAcknowledgement = () => {
saveHasAcknowledgedTerms(true);
onContinue?.();
};
return (
<>
<span>
{stringGetter({
key: STRING_KEYS.LEGAL_UPDATES_DESCRIPTION,
params: {
TOU: (
<Styled.Link href="https://dydx.exchange/v4-terms">
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
</Styled.Link>
),
PRIVACY_POLICY: (
<Styled.Link href="https://dydx.exchange/privacy">
{stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })}
</Styled.Link>
),
},
})}
</span>
<Button onClick={onAcknowledgement} action={ButtonAction.Primary}>
{stringGetter({ key: STRING_KEYS.I_AGREE })}
</Button>
</>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Link = styled(Link)`
display: inline-block;
color: var(--color-accent);
&:visited {
color: var(--color-accent);
}
`;
@@ -0,0 +1,125 @@
import { useState } from 'react';
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 { AlertMessage } from '@/components/AlertMessage';
import { Button } from '@/components/Button';
import { Icon } from '@/components/Icon';
import { Link } from '@/components/Link';
import { useAccounts, useStringGetter } from '@/hooks';
import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
const aboutWalletsLink = `https://www.dydxacademy.info/educational-video-series/onboarding-to-defi-with-dydx`;
export const ChooseWallet = () => {
const stringGetter = useStringGetter();
const displayedWallets = useDisplayedWallets();
const { selectWalletType, selectedWalletType, selectedWalletError } = useAccounts();
return (
<>
{selectedWalletType && selectedWalletError && (
<Styled.AlertMessage type={AlertType.Error}>
{
<h4>
Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.
</h4>
}
{selectedWalletError}
</Styled.AlertMessage>
)}
<Styled.Wallets>
{displayedWallets.map((walletType) => (
<Styled.WalletButton
action={ButtonAction.Base}
key={walletType}
onClick={() => selectWalletType(walletType)}
slotLeft={<Styled.Icon iconComponent={wallets[walletType].icon} />}
size={ButtonSize.Small}
>
<div>{stringGetter({ key: wallets[walletType].stringKey })}</div>
</Styled.WalletButton>
))}
</Styled.Wallets>
<Styled.Footer>
<Link href={aboutWalletsLink} withIcon>
{stringGetter({ key: STRING_KEYS.ABOUT_WALLETS })}
</Link>
</Styled.Footer>
</>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.AlertMessage = styled(AlertMessage)`
h4 {
font: var(--font-small-medium);
}
`;
Styled.Wallets = styled.div`
gap: 0.5rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
> :last-child:nth-child(odd) {
grid-column: span 2;
}
// Flex layout
/* display: flex;
flex-wrap: wrap;
:after {
content: '';
flex: 2;
} */
`;
Styled.WalletButton = styled(Button)`
justify-content: start;
gap: 0.5rem;
@media ${breakpoints.mobile} {
div {
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
}
`;
Styled.Icon = styled(Icon)`
width: 1.5em;
height: 1.5em;
`;
Styled.Footer = styled.footer`
${layoutMixins.spacedRow}
justify-content: center;
margin-top: auto;
a {
color: var(--color-text-0);
font: var(--font-base-book);
&:hover {
color: var(--color-text-1);
}
}
`;
@@ -0,0 +1,334 @@
import { useState } from 'react';
import { useSignTypedData } from 'wagmi';
import styled, { type AnyStyledComponent, css } from 'styled-components';
import { useSelector } from 'react-redux';
import { AES } from 'crypto-js';
import { EvmDerivedAccountStatus } from '@/constants/account';
import { AlertType } from '@/constants/alerts';
import { AnalyticsEvent } from '@/constants/analytics';
import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { DydxAddress, SIGN_TYPED_DATA } from '@/constants/wallets';
import { useAccounts, useBreakpoints, useDydxClient, useStringGetter } from '@/hooks';
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
import { layoutMixins } from '@/styles/layoutMixins';
import { AlertMessage } from '@/components/AlertMessage';
import { Button } from '@/components/Button';
import { GreenCheckCircle } from '@/components/GreenCheckCircle';
import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
import { Switch } from '@/components/Switch';
import { WithReceipt } from '@/components/WithReceipt';
import { WithTooltip } from '@/components/WithTooltip';
import { getSelectedNetwork } from '@/state/appSelectors';
import { track } from '@/lib/analytics';
import { isTruthy } from '@/lib/isTruthy';
import { log } from '@/lib/telemetry';
import { parseWalletError } from '@/lib/wallet';
type ElementProps = {
status: EvmDerivedAccountStatus;
setStatus: (status: EvmDerivedAccountStatus) => void;
onKeysDerived?: () => void;
};
export const GenerateKeys = ({
status: status,
setStatus,
onKeysDerived = () => {},
}: ElementProps) => {
const stringGetter = useStringGetter();
const { isMobile } = useBreakpoints();
const [shouldRememberMe, setShouldRememberMe] = useState(false);
const { setWalletFromEvmSignature, saveEvmSignature } = useAccounts();
const [error, setError] = useState<string>();
// 1. Switch network
const selectedNetwork = useSelector(getSelectedNetwork);
const chainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId);
const { isMatchingNetwork, matchNetwork, isSwitchingNetwork } = useMatchingEvmNetwork({
chainId,
});
const switchNetwork = async () => {
setError(undefined);
try {
await matchNetwork?.();
} catch (error) {
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
if (message) {
log('GenerateKeys/switchNetwork', error, { walletErrorType });
setError(message);
}
}
};
// 2. Derive keys from EVM account
const { getWalletFromEvmSignature } = useDydxClient();
const { getSubaccounts } = useAccounts();
const isDeriving = ![
EvmDerivedAccountStatus.NotDerived,
EvmDerivedAccountStatus.Derived,
].includes(status);
const { signTypedDataAsync } = useSignTypedData({
...SIGN_TYPED_DATA,
domain: {
...SIGN_TYPED_DATA.domain,
chainId,
},
});
const staticEncryptionKey = import.meta.env.VITE_PK_ENCRYPTION_KEY;
const deriveKeys = async () => {
setError(undefined);
try {
// 1. First signature
setStatus(EvmDerivedAccountStatus.Deriving);
const signature = await signTypedDataAsync();
const { wallet: dydxWallet } = await getWalletFromEvmSignature({ signature });
// 2. Ensure signature is deterministic
// Check if subaccounts exist
const dydxAddress = dydxWallet.address as DydxAddress;
let hasPreviousTransactions = false;
try {
const subaccounts = await getSubaccounts({ dydxAddress });
hasPreviousTransactions = subaccounts.length > 0;
track(AnalyticsEvent.OnboardingAccountDerived, { hasPreviousTransactions });
if (!hasPreviousTransactions) {
setStatus(EvmDerivedAccountStatus.EnsuringDeterminism);
// Second signature
const additionalSignature = await signTypedDataAsync();
if (signature !== additionalSignature) {
throw new Error(
'Your wallet does not support deterministic signing. Please switch to a different wallet provider.'
);
}
}
} catch (error) {
const { message } = parseWalletError({ error, stringGetter });
if (message) {
track(AnalyticsEvent.OnboardingWalletIsNonDeterministic);
setError(message);
}
}
await setWalletFromEvmSignature(signature);
// 3: Remember me (encrypt and store signature)
if (shouldRememberMe && staticEncryptionKey) {
const encryptedSignature = AES.encrypt(signature, staticEncryptionKey).toString();
saveEvmSignature(encryptedSignature);
}
// 4. Done
setStatus(EvmDerivedAccountStatus.Derived);
} catch (error) {
setStatus(EvmDerivedAccountStatus.NotDerived);
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
if (message) {
setError(message);
log('GenerateKeys/deriveKeys', error, { walletErrorType });
}
}
};
return (
<>
<Styled.StatusCardsContainer>
{[
{
status: EvmDerivedAccountStatus.Deriving,
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
},
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
status: EvmDerivedAccountStatus.EnsuringDeterminism,
title: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_COMPATIBILITY }),
description: stringGetter({ key: STRING_KEYS.ENSURES_WALLET_SUPPORT }),
},
]
.filter(isTruthy)
.map((step) => (
<Styled.StatusCard key={step.status} active={status === step.status}>
{status < step.status ? (
<LoadingSpinner disabled />
) : status === step.status ? (
<LoadingSpinner />
) : (
<Styled.GreenCheckCircle />
)}
<div>
<h3>{step.title}</h3>
<p>{step.description}</p>
</div>
</Styled.StatusCard>
))}
</Styled.StatusCardsContainer>
<Styled.Footer>
<Styled.RememberMe htmlFor="remember-me">
<WithTooltip withIcon tooltip="remember-me">
{stringGetter({ key: STRING_KEYS.REMEMBER_ME })}
</WithTooltip>
<Switch
name="remember-me"
disabled={!staticEncryptionKey || isDeriving}
checked={shouldRememberMe}
onCheckedChange={setShouldRememberMe}
/>
</Styled.RememberMe>
{error && <AlertMessage type={AlertType.Error}>{error}</AlertMessage>}
<Styled.WithReceipt
slotReceipt={
<Styled.ReceiptArea>
<span>
{stringGetter({
key: STRING_KEYS.FREE_SIGNING,
params: {
FREE: (
<Styled.Green>
{stringGetter({ key: STRING_KEYS.FREE_TRADING_TITLE_ASTERISK_FREE })}
</Styled.Green>
),
},
})}
</span>
</Styled.ReceiptArea>
}
>
{!isMatchingNetwork ? (
<Button
action={ButtonAction.Primary}
onClick={() => switchNetwork().then(deriveKeys).then(onKeysDerived)}
state={{ isLoading: isSwitchingNetwork }}
>
{stringGetter({ key: STRING_KEYS.SWITCH_NETWORK })}
</Button>
) : (
<Button
action={ButtonAction.Primary}
onClick={() => deriveKeys().then(onKeysDerived)}
state={{
isLoading: isDeriving,
isDisabled: status !== EvmDerivedAccountStatus.NotDerived,
}}
>
{!error
? stringGetter({
key: STRING_KEYS.SEND_REQUEST,
})
: stringGetter({
key: STRING_KEYS.TRY_AGAIN,
})}
</Button>
)}
</Styled.WithReceipt>
<Styled.Disclaimer>
{stringGetter({ key: STRING_KEYS.CHECK_WALLET_FOR_REQUEST })}
</Styled.Disclaimer>
</Styled.Footer>
</>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.StatusCardsContainer = styled.div`
display: grid;
margin-top: 1rem;
gap: 1rem;
`;
Styled.StatusCard = styled.div<{ active?: boolean }>`
${layoutMixins.row}
gap: 1rem;
background-color: var(--color-layer-4);
padding: 1rem;
border-radius: 0.625rem;
${({ active }) =>
active &&
css`
background-color: var(--color-layer-6);
`}
> div {
${layoutMixins.column}
gap: 0.25rem;
h3 {
color: var(--color-text-2);
font: var(--font-base-book);
}
p {
color: var(--color-text-1);
font: var(--font-small-regular);
}
}
`;
Styled.Footer = styled.footer`
${layoutMixins.stickyFooter}
margin-top: auto;
display: grid;
gap: 1rem;
`;
Styled.RememberMe = styled.label`
${layoutMixins.spacedRow}
font: var(--font-base-book);
`;
Styled.WithReceipt = styled(WithReceipt)`
--withReceipt-backgroundColor: var(--color-layer-2);
`;
Styled.ReceiptArea = styled.div`
padding: 1rem;
font: var(--font-small-book);
color: var(--color-text-0);
`;
Styled.Green = styled.span`
color: var(--color-positive);
`;
Styled.GreenCheckCircle = styled(GreenCheckCircle)`
--icon-size: 2.375rem;
`;
Styled.Disclaimer = styled.span`
text-align: center;
color: var(--color-text-0);
font: var(--font-base-book);
`;