From 0cd4386b8ced855ef8d0a4ad172af18cce386b94 Mon Sep 17 00:00:00 2001 From: Florian Bouron Date: Sun, 7 Jul 2024 19:25:22 +0200 Subject: [PATCH 1/7] Save the proper algorithm when importing seed in new wallet --- .../CreateNewWallet/CreateNewWallet.tsx | 2 +- .../ImportNewWallet/ImportSeed/ImportSeed.tsx | 35 ++++++++++++++++--- .../contexts/WalletContext/WalletContext.tsx | 22 ++++++++---- packages/extension/src/types/wallet.types.ts | 3 +- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/extension/src/components/pages/AddNewWallet/CreateNewWallet/CreateNewWallet.tsx b/packages/extension/src/components/pages/AddNewWallet/CreateNewWallet/CreateNewWallet.tsx index 2b66a944a..15a16721e 100644 --- a/packages/extension/src/components/pages/AddNewWallet/CreateNewWallet/CreateNewWallet.tsx +++ b/packages/extension/src/components/pages/AddNewWallet/CreateNewWallet/CreateNewWallet.tsx @@ -33,7 +33,7 @@ export const CreateNewWallet: FC = ({ password }) => { useEffect(() => { if (wallet?.seed && activeStep === 2) { try { - importSeed(password, wallet.seed); + importSeed({ password, seed: wallet.seed }); navigate(LIST_WALLETS_PATH); } catch (e) { Sentry.captureException('Cannot save wallet - CreateNewWallet: ' + e); diff --git a/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx b/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx index 0da351901..b67f5498b 100644 --- a/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx +++ b/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx @@ -1,11 +1,13 @@ import { FC, useCallback, useRef, useState } from 'react'; -import { TextField, Typography } from '@mui/material'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import { Checkbox, FormControlLabel, TextField, Tooltip, Typography } from '@mui/material'; import { useNavigate } from 'react-router-dom'; -import { LIST_WALLETS_PATH } from '../../../../../constants'; +import { LIST_WALLETS_PATH, SECONDARY_GRAY } from '../../../../../constants'; import { useWallet } from '../../../../../contexts'; import { PageWithStepper } from '../../../../templates'; +import { ECDSA } from 'xrpl'; export interface ImportSeedProps { activeStep: number; @@ -17,12 +19,17 @@ export const ImportSeed: FC = ({ activeStep, password, handleBa const navigate = useNavigate(); const { importSeed } = useWallet(); const [seedError, setSeedError] = useState(''); + const [isSecp256k1, setSecp256k1] = useState(false); const seedRef = useRef(null); const handleNext = useCallback(() => { const seedValue = seedRef.current?.value; if (seedValue !== undefined) { - const isValidSeed = importSeed(password, seedValue); + const isValidSeed = importSeed({ + password, + seed: seedValue, + algorithm: isSecp256k1 ? ECDSA.secp256k1 : undefined + }); if (isValidSeed) { navigate(LIST_WALLETS_PATH); } else if (isValidSeed === false) { @@ -33,7 +40,7 @@ export const ImportSeed: FC = ({ activeStep, password, handleBa } else { setSeedError('Your seed is empty'); } - }, [importSeed, navigate, password]); + }, [importSeed, isSecp256k1, navigate, password]); return ( = ({ activeStep, password, handleBa style={{ marginTop: '20px' }} autoComplete="off" /> + setSecp256k1(!isSecp256k1)} + name="rememberSession" + color="primary" + style={{ transform: 'scale(0.9)' }} + /> + } + label={ + + Use "secp256k1" algorithm{' '} + + + + + } + style={{ marginTop: '5px' }} + /> ); }; diff --git a/packages/extension/src/contexts/WalletContext/WalletContext.tsx b/packages/extension/src/contexts/WalletContext/WalletContext.tsx index cf1593035..0f5eb9f23 100644 --- a/packages/extension/src/contexts/WalletContext/WalletContext.tsx +++ b/packages/extension/src/contexts/WalletContext/WalletContext.tsx @@ -2,7 +2,7 @@ import { useContext, useState, createContext, FC, useCallback, useEffect } from import * as Sentry from '@sentry/react'; import { useNavigate } from 'react-router-dom'; -import { Wallet } from 'xrpl'; +import { ECDSA, Wallet } from 'xrpl'; import { GEM_WALLET, @@ -27,13 +27,19 @@ import { saveWallet } from '../../utils'; +type ImportSeedProps = { + password: string; + seed: string; + walletName?: string; + algorithm?: ECDSA; +}; export interface WalletContextType { signIn: (password: string, rememberSession?: boolean) => boolean; signOut: () => void; generateWallet: (walletName?: string) => Wallet; selectWallet: (index: number) => void; isValidSeed: (seed: string) => boolean; - importSeed: (password: string, seed: string, walletName?: string) => boolean | undefined; + importSeed: ({ password, seed, walletName, algorithm }: ImportSeedProps) => boolean | undefined; isValidMnemonic: (mnemonic: string) => boolean; importMnemonic: (password: string, mnemonic: string, walletName?: string) => boolean | undefined; isValidNumbers: (numbers: string[]) => boolean; @@ -90,13 +96,13 @@ const WalletProvider: FC = ({ children }) => { }); } - const _wallets = wallets.map(({ name, publicAddress, seed, mnemonic }) => { + const _wallets = wallets.map(({ name, publicAddress, seed, mnemonic, algorithm }) => { if (seed) { return { name, publicAddress, seed, - wallet: Wallet.fromSeed(seed) + wallet: Wallet.fromSeed(seed, { algorithm }) }; } return { @@ -106,6 +112,7 @@ const WalletProvider: FC = ({ children }) => { wallet: Wallet.fromMnemonic(mnemonic!) }; }); + console.log('_____wallets', _wallets); setWallets(_wallets); setPassword(password); return true; @@ -151,15 +158,16 @@ const WalletProvider: FC = ({ children }) => { * undefined: if wallet is already present */ const importSeed = useCallback( - (password: string, seed: string, walletName?: string) => { + ({ password, seed, walletName, algorithm }: ImportSeedProps) => { try { - const wallet = Wallet.fromSeed(seed); + const wallet = Wallet.fromSeed(seed, { algorithm }); if (wallets.filter((w) => w.publicAddress === wallet.address).length > 0) { return undefined; } const _wallet = { publicAddress: wallet.address, - seed + seed, + algorithm }; saveWallet(_wallet, password); setWallets((wallets) => [ diff --git a/packages/extension/src/types/wallet.types.ts b/packages/extension/src/types/wallet.types.ts index 192dcc125..bcd42f7f3 100644 --- a/packages/extension/src/types/wallet.types.ts +++ b/packages/extension/src/types/wallet.types.ts @@ -1,9 +1,10 @@ -import { Wallet as WalletXRPL } from 'xrpl'; +import { ECDSA, Wallet as WalletXRPL } from 'xrpl'; export interface Wallet { name: string; publicAddress: string; seed?: string; mnemonic?: string; + algorithm?: ECDSA; } export interface WalletLedger extends Wallet { From 13d5707fda6e37c1424ba0aba13114280a48ed64 Mon Sep 17 00:00:00 2001 From: Florian Bouron Date: Sun, 7 Jul 2024 20:58:11 +0200 Subject: [PATCH 2/7] Save the proper algorithm when importing seed in wallet --- .../pages/ImportSeed/ImportSeed.test.tsx | 2 +- .../pages/ImportSeed/ImportSeed.tsx | 9 ++--- .../ImportSeed/SecretSeed/SecretSeed.test.tsx | 2 +- .../ImportSeed/SecretSeed/SecretSeed.tsx | 34 ++++++++++++++++--- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/extension/src/components/pages/ImportSeed/ImportSeed.test.tsx b/packages/extension/src/components/pages/ImportSeed/ImportSeed.test.tsx index aafcb041c..57b72cfb0 100644 --- a/packages/extension/src/components/pages/ImportSeed/ImportSeed.test.tsx +++ b/packages/extension/src/components/pages/ImportSeed/ImportSeed.test.tsx @@ -28,7 +28,7 @@ describe('ImportSeed Page', () => { const titleElement = screen.getByRole('heading', { name: 'Secret Seed' }); const subTitleElement = screen.getByRole('heading', { - name: 'Please enter your seed in order to load your wallet to GemWallet.' + name: 'Please enter your seed in order to import your wallet to GemWallet.' }); expect(titleElement).toBeVisible(); expect(subTitleElement).toBeVisible(); diff --git a/packages/extension/src/components/pages/ImportSeed/ImportSeed.tsx b/packages/extension/src/components/pages/ImportSeed/ImportSeed.tsx index 52cd658f7..ad7484e13 100644 --- a/packages/extension/src/components/pages/ImportSeed/ImportSeed.tsx +++ b/packages/extension/src/components/pages/ImportSeed/ImportSeed.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useState } from 'react'; -import { Wallet } from 'xrpl'; +import { ECDSA, Wallet } from 'xrpl'; import { WalletToSave } from '../../../utils'; import { Congratulations } from '../Congratulations'; @@ -17,11 +17,12 @@ export const ImportSeed: FC = () => { setActiveStep((prevActiveStep) => prevActiveStep - 1); }, []); - const handleSecretSeed = useCallback((seed: string) => { - const wallet = Wallet.fromSeed(seed); + const handleSecretSeed = useCallback((seed: string, algorithm: ECDSA | undefined) => { + const wallet = Wallet.fromSeed(seed, { algorithm }); setWallet({ publicAddress: wallet.address, - seed + seed, + algorithm }); setActiveStep(1); }, []); diff --git a/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.test.tsx b/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.test.tsx index b8e9c90e4..0e9cd8891 100644 --- a/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.test.tsx +++ b/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.test.tsx @@ -21,7 +21,7 @@ describe('ImportSeed - SecretSeed Page', () => { ); const titleElement = screen.getByRole('heading', { name: 'Secret Seed' }); const subTitleElement = screen.getByRole('heading', { - name: 'Please enter your seed in order to load your wallet to GemWallet.' + name: 'Please enter your seed in order to import your wallet to GemWallet.' }); expect(titleElement).toBeVisible(); expect(subTitleElement).toBeVisible(); diff --git a/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx b/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx index 35f04f91f..d1e2f28b3 100644 --- a/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx +++ b/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx @@ -1,29 +1,33 @@ import { FC, useCallback, useState } from 'react'; +import { ECDSA } from 'xrpl'; -import { TextField, Typography } from '@mui/material'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import { Checkbox, FormControlLabel, TextField, Tooltip, Typography } from '@mui/material'; import { useWallet } from '../../../../contexts'; import { PageWithStepper } from '../../../templates'; +import { SECONDARY_GRAY } from '../../../../constants'; export interface SecretSeedProps { activeStep: number; steps: number; onBack: () => void; - onNext: (seed: string) => void; + onNext: (seed: string, algorithm: ECDSA | undefined) => void; } export const SecretSeed: FC = ({ activeStep, steps, onBack, onNext }) => { const [seedError, setSeedError] = useState(''); + const [isSecp256k1, setSecp256k1] = useState(false); const { isValidSeed } = useWallet(); const handleNext = useCallback(() => { const seedValue = (document.getElementById('seed') as HTMLInputElement).value; if (isValidSeed(seedValue)) { - onNext(seedValue); + onNext(seedValue, isSecp256k1 ? ECDSA.secp256k1 : undefined); } else { setSeedError('Your seed is invalid'); } - }, [isValidSeed, onNext]); + }, [isSecp256k1, isValidSeed, onNext]); return ( = ({ activeStep, steps, onBack, onN Secret Seed - Please enter your seed in order to load your wallet to GemWallet. + Please enter your seed in order to import your wallet to GemWallet. = ({ activeStep, steps, onBack, onN style={{ marginTop: '20px' }} autoComplete="off" /> + setSecp256k1(!isSecp256k1)} + name="rememberSession" + color="primary" + style={{ transform: 'scale(0.9)' }} + /> + } + label={ + + Use "secp256k1" algorithm{' '} + + + + + } + style={{ marginTop: '5px' }} + /> ); }; From 14370a13eaa88858016151426b9d7fdae9d9c756 Mon Sep 17 00:00:00 2001 From: Florian Bouron Date: Sun, 7 Jul 2024 20:59:39 +0200 Subject: [PATCH 3/7] Remove console.log --- packages/extension/src/contexts/WalletContext/WalletContext.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/extension/src/contexts/WalletContext/WalletContext.tsx b/packages/extension/src/contexts/WalletContext/WalletContext.tsx index 0f5eb9f23..1e25867f8 100644 --- a/packages/extension/src/contexts/WalletContext/WalletContext.tsx +++ b/packages/extension/src/contexts/WalletContext/WalletContext.tsx @@ -112,7 +112,6 @@ const WalletProvider: FC = ({ children }) => { wallet: Wallet.fromMnemonic(mnemonic!) }; }); - console.log('_____wallets', _wallets); setWallets(_wallets); setPassword(password); return true; From 83128cf22484aafca7af2642e871a3ed7b9c295f Mon Sep 17 00:00:00 2001 From: Florian Bouron Date: Sun, 7 Jul 2024 21:46:45 +0200 Subject: [PATCH 4/7] Add Cypress tests --- .../cypress/e2e/wallet_management.cy.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/extension/cypress/e2e/wallet_management.cy.ts b/packages/extension/cypress/e2e/wallet_management.cy.ts index d5f2dc85d..0653bea94 100644 --- a/packages/extension/cypress/e2e/wallet_management.cy.ts +++ b/packages/extension/cypress/e2e/wallet_management.cy.ts @@ -1,7 +1,10 @@ /// +import { XRPLNetwork } from '@gemwallet/constants'; + const PASSWORD = Cypress.env('PASSWORD'); const SEED = Cypress.env('SEED'); +const SEED_SECP256K1 = Cypress.env('SEED_SECP256K1'); const MNEMONIC = Cypress.env('MNEMONIC'); const ERROR_MNEMONIC = 'You need 6 digits'; const URL_WALLET = 'http://localhost:3000/'; @@ -121,6 +124,56 @@ describe('Setup the initial wallet (no previous wallet)', () => { cy.contains('Your gateway to the XRPL').should('be.visible'); }); + it('Import a wallet - Family Seed - secp256k1', () => { + // Go to the import a new wallet page + cy.contains('button', 'Import a wallet').click(); + + // Select import by family seed + cy.contains('button', 'Family Seed').click(); + + // Add the seed to the import + cy.get('input[name="seed"]').type(SEED_SECP256K1); + cy.get('.PrivateSwitchBase-input').click(); + cy.contains('button', 'Next').click(); + + // Set up the proper password + cy.get('input[name="password"]').clear().type(PASSWORD); + cy.get('input[name="confirm-password"]').clear().type(PASSWORD); + cy.contains('button', 'Next').click(); + cy.contains("Woo, you're in!").should('be.visible'); + cy.contains('Follow along with product updates or reach out if you have any questions.').should( + 'be.visible' + ); + + // Redirection to the login page + cy.contains('button', 'Finish').click(); + cy.contains('GemWallet').should('be.visible'); + cy.contains('Your gateway to the XRPL').should('be.visible'); + + // Login + cy.get('input[name="password"]').type(PASSWORD); + cy.contains('button', 'Unlock').click(); + + // Mainnet should be the default network + cy.get('div[data-testid="network-indicator"]').should('have.text', XRPLNetwork.MAINNET); + + // Open the change network window + cy.get('div[data-testid="network-indicator"]').click(); + cy.get('div[data-testid="network-indicator-dialog"]', { timeout: 1500 }) + .find('header') + .should('have.text', 'Change Network'); + + // Select the testnet network + cy.contains('button', XRPLNetwork.TESTNET).click(); + + // Make sure that the network is switched to Testnet + cy.get('div[data-testid="loading"]').should('be.visible'); + cy.get('div[data-testid="network-indicator"]').should('have.text', XRPLNetwork.TESTNET); + + // Make sure that the right wallet is online + cy.contains('14.999988 XRP').should('be.visible'); + }); + it('Import a wallet - Mnemonic', () => { // Go to the import a new wallet page cy.contains('button', 'Import a wallet').click(); From a4a7087967bb02a9d92a404dcc23d0756f335e5d Mon Sep 17 00:00:00 2001 From: Florian Bouron Date: Mon, 8 Jul 2024 07:52:24 +0200 Subject: [PATCH 5/7] Fix the name in the checkboxes --- .../AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx | 2 +- .../src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx b/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx index b67f5498b..918cb34f6 100644 --- a/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx +++ b/packages/extension/src/components/pages/AddNewWallet/ImportNewWallet/ImportSeed/ImportSeed.tsx @@ -73,7 +73,7 @@ export const ImportSeed: FC = ({ activeStep, password, handleBa setSecp256k1(!isSecp256k1)} - name="rememberSession" + name="setSecp256k1" color="primary" style={{ transform: 'scale(0.9)' }} /> diff --git a/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx b/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx index d1e2f28b3..b467cfa93 100644 --- a/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx +++ b/packages/extension/src/components/pages/ImportSeed/SecretSeed/SecretSeed.tsx @@ -59,7 +59,7 @@ export const SecretSeed: FC = ({ activeStep, steps, onBack, onN setSecp256k1(!isSecp256k1)} - name="rememberSession" + name="setSecp256k1" color="primary" style={{ transform: 'scale(0.9)' }} /> From 5475b7d34d6863af2092897f2f00456a26e61d0b Mon Sep 17 00:00:00 2001 From: Florian Bouron Date: Mon, 8 Jul 2024 09:19:43 +0200 Subject: [PATCH 6/7] Fix the env variable --- .github/workflows/integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 90c991f2c..2eb2999b5 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -78,3 +78,4 @@ jobs: CYPRESS_MNEMONIC: ${{vars.CYPRESS_MNEMONIC}} CYPRESS_PASSWORD: ${{vars.CYPRESS_PASSWORD}} CYPRESS_SEED: ${{vars.CYPRESS_SEED}} + CYPRESS_SEED_SECP256K1: ${{vars.CYPRESS_SEED_SECP256K1}} From 999f2b3bdfe84e65f6070d04579fde1043934bac Mon Sep 17 00:00:00 2001 From: Florian Bouron Date: Mon, 8 Jul 2024 20:14:17 +0200 Subject: [PATCH 7/7] Update cypress tests --- .../cypress/e2e/wallet_management.cy.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/extension/cypress/e2e/wallet_management.cy.ts b/packages/extension/cypress/e2e/wallet_management.cy.ts index 0653bea94..3b3a9d6fa 100644 --- a/packages/extension/cypress/e2e/wallet_management.cy.ts +++ b/packages/extension/cypress/e2e/wallet_management.cy.ts @@ -1,7 +1,5 @@ /// -import { XRPLNetwork } from '@gemwallet/constants'; - const PASSWORD = Cypress.env('PASSWORD'); const SEED = Cypress.env('SEED'); const SEED_SECP256K1 = Cypress.env('SEED_SECP256K1'); @@ -154,24 +152,8 @@ describe('Setup the initial wallet (no previous wallet)', () => { cy.get('input[name="password"]').type(PASSWORD); cy.contains('button', 'Unlock').click(); - // Mainnet should be the default network - cy.get('div[data-testid="network-indicator"]').should('have.text', XRPLNetwork.MAINNET); - - // Open the change network window - cy.get('div[data-testid="network-indicator"]').click(); - cy.get('div[data-testid="network-indicator-dialog"]', { timeout: 1500 }) - .find('header') - .should('have.text', 'Change Network'); - - // Select the testnet network - cy.contains('button', XRPLNetwork.TESTNET).click(); - - // Make sure that the network is switched to Testnet - cy.get('div[data-testid="loading"]').should('be.visible'); - cy.get('div[data-testid="network-indicator"]').should('have.text', XRPLNetwork.TESTNET); - // Make sure that the right wallet is online - cy.contains('14.999988 XRP').should('be.visible'); + cy.contains('r9M7...4g5C').should('be.visible'); }); it('Import a wallet - Mnemonic', () => { @@ -360,6 +342,24 @@ describe('Add an additional wallet (with previous wallet)', () => { }); }); + it('Import a wallet - Family Seed - secp256k1', () => { + // Go to the import a new wallet page + cy.contains('button', 'Import a new wallet').click(); + + // Select import by family seed + cy.contains('button', 'Family Seed').click(); + + // Add the seed to the import + cy.get('input[name="seed"]').type(SEED_SECP256K1); + cy.get('.PrivateSwitchBase-input').click(); + cy.contains('button', 'Add Seed').click(); + + // Redirection to the wallets page + cy.contains('Your wallets').should('be.visible'); + cy.get('div[data-testid="wallet-container"]').children().should('have.length', 4); + cy.get('div[data-testid="wallet-container"]').first().contains('r9M7...4g5C'); + }); + it('Add a new wallet - Mnemonic', () => { // Go to the import a new wallet page cy.contains('button', 'Import a new wallet').click();