Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new user import guide #2639

Merged
merged 30 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added _raw/images/keystone-plug-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _raw/images/ledger-plug-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 108 additions & 3 deletions _raw/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,6 @@
"est-difference": "Est. Difference:",
"loss-tips": "You're losing {{usd}}. Try a different amount.",
"price-impact": "Price Impact",

"showMore": {
"title": "Show More",
"source": "Bridge Source"
Expand Down Expand Up @@ -1041,7 +1040,7 @@
"addressTypeTip": "Imported by {{type}}",
"delete-desc": "Before you delete, keep the following points in mind to understand how to protect your assets.",
"delete-checklist-1": "I understand that if I delete this address, the corresponding Private Key & Seed Phrase of this address will be deleted and Rabby will NOT be able to recover it.",
"delete-checklist-2": "I confirm that I have backuped the private key or Seed Phrase and I'm ready to delete it now.",
"delete-checklist-2": "I confirm that I have backed up the private key or Seed Phrase and I'm ready to delete it now.",
"confirm": "Confirm",
"cancel": "Cancel",
"delete-private-key-modal-title_one": "Delete {{count}} private key address",
Expand Down Expand Up @@ -1846,7 +1845,8 @@
"manage-addresses-under-this-seed-phrase": "Manage addresses under this Seed Phrase",
"safeModuleAddress": "Safe Module Address",
"coboSafeErrorModule": "Address has expired, please delete and import the address again.",
"importedDelegatedAddress": "Imported Delegated address"
"importedDelegatedAddress": "Imported Delegated address",
"manage-addresses-under": "Manage addresses under this {{brand}}"
},
"preferMetamaskDapps": {
"title": "MetaMask Preferred Dapps",
Expand Down Expand Up @@ -2286,6 +2286,111 @@
"safeMessageQueue": {
"loading": "Loading messages",
"noData": "No messages"
},
"newUserImport": {
"guide": {
"title": "Welcome to Rabby Wallet",
"desc": "The game-changing wallet for Ethereum and all EVM chains",
"createNewAddress": "Create a new address",
"importAddress": "I already have a address"
},
"createNewAddress": {
"title": "Create New Seed Phrase",
"desc": "Before you start, please read and keep the following security tips in mind.",
"tip1": "If I lose or share my seed phrase, my assets will be lost forever.",
"tip2": "The seed phrase is only stored on my computer, and Rabby has no access to it. ",
"tip3": "If I uninstall Rabby without backing up the seed phrase, Rabby cannot retrieve it for me. ",
"showSeedPhrase": "Show Seed Phrase"
},
"importList": {
"title": "Select Import Method "
},
"importPrivateKey": {
"title": "Import Private Key",
"pasteCleared": "Pasted and clipboard cleared"
},
"PasswordCard": {
"title": "Set Password",
"form": {
"password": {
"label": "Password",
"placeholder": "8 characters min",
"required": "Please input Password",
"min": "Password must be at least 8 characters long"
},
"confirmPassword": {
"label": "Confirm Password",
"placeholder": "Password",
"required": "Please Confirm Password",
"notMatch": "Passwords do not match"
}
},
"agree": "I agree to the<1/> <2>Terms of Use</2> and <4>Privacy Policy</4>",
"desc": "It will be used to unlock wallet and encrypt data"
},
"successful": {
"create": "Created Successfully",
"import": "Imported Successfully",
"start": "Get Started",
"addMoreAddr": "Add more addresses from this Seed Phrase",
"addMoreFrom": "Add more addresses from {{name}}"
},
"readyToUse": {
"title": "Rabby Wallet is Ready to Use",
"desc": "Pin Rabby Wallet to your toolbar for easy access.",
"pin": "Pin Rabby Wallet",
"extensionTip": "Click <1/> and then <3/>"
},
"importSeedPhrase": {
"title": "Import Seed Phrase"
},
"importOneKey": {
"title": "OneKey",
"tip1": "1. Install <1>OneKey Bridge<1/>",
"tip2": "2. Plug in your OneKey device",
"tip3": "3. Unlock your device",
"connect": "Connect OneKey"
},
"importTrezor": {
"title": "Terzor",
"tip1": "1. Plug in your Trezor device",
"tip2": "2. Unlock your device",
"connect": "Connect Terzor"
},
"ImportGridPlus": {
"title": "GridPlus",
"tip1": "1. Open your GridPlus device",
"tip2": "2. Connect through Lattice Connector",
"connect": "Connect GridPlus"
},
"importLedger": {
"title": "Ledger",
"tip1": "Plug in your Ledger device.",
"tip2": "Enter your PIN to unlock.",
"tip3": "Open the Ethereum app.",
"connect": "Connect Ledger"
},
"importKeystone": {
"qrcode": {
"desc": "Scan the QR code on the Keystone hardware wallet"
},
"usb": {
"desc": "Ensure your Keystone 3 Pro is on the homepage",
"tip1": "Plug in your Keystone device",
"tip2": "Enter your password to unlock",
"tip3": "Approve the connection to your computer",
"connect": "Connect Keystone"
}
},
"importSafe": {
"title": "Add Safe Address",
"error": {
"required": "Please input address",
"invalid": "Not a valid address"
},
"placeholder": "Input safe address",
"loading": "Searching the deployed chain of this address"
}
}
},
"component": {
Expand Down
31 changes: 29 additions & 2 deletions src/background/controller/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ import pRetry from 'p-retry';
import Browser from 'webextension-polyfill';
import SafeApiKit from '@safe-global/api-kit';
import { hashSafeMessage } from '@safe-global/protocol-kit';
import { userGuideService } from '../service/userGuide';

const stashKeyrings: Record<string | number, any> = {};

Expand All @@ -143,6 +144,7 @@ export class WalletController extends BaseController {
/* wallet */
boot = async (password) => {
await keyringService.boot(password);
userGuideService.destroy();
const hasOtherProvider = preferenceService.getHasOtherProvider();
const isDefaultWallet = preferenceService.getIsDefaultWallet();
if (!hasOtherProvider) {
Expand Down Expand Up @@ -2929,6 +2931,20 @@ export class WalletController extends BaseController {
return;
};

validatePrivateKey = async (data: string) => {
const privateKey = ethUtil.stripHexPrefix(data);
const buffer = Buffer.from(privateKey, 'hex');

const error = new Error(t('background.error.invalidPrivateKey'));
try {
if (!ethUtil.isValidPrivate(buffer)) {
throw error;
}
} catch {
throw error;
}
};

importPrivateKey = async (data) => {
const privateKey = ethUtil.stripHexPrefix(data);
const buffer = Buffer.from(privateKey, 'hex');
Expand Down Expand Up @@ -2969,7 +2985,7 @@ export class WalletController extends BaseController {
);
return this._setCurrentAccountFromKeyring(keyring);
};

generateMnemonic = () => keyringService.generateMnemonic();
getPreMnemonics = () => keyringService.getPreMnemonics();
generatePreMnemonic = () => keyringService.generatePreMnemonic();
removePreMnemonics = () => keyringService.removePreMnemonics();
Expand Down Expand Up @@ -3194,6 +3210,10 @@ export class WalletController extends BaseController {
return await keyring.getInfoByAddress(address);
};

validateMnemonic = (mnemonic: string) => {
return HdKeyring.validateMnemonic(mnemonic);
};

generateKeyringWithMnemonic = async (
mnemonic: string,
passphrase: string
Expand Down Expand Up @@ -3435,7 +3455,7 @@ export class WalletController extends BaseController {
}

if (needUnlock) {
await keyring.unlock();
await keyring?.unlock?.();
}

return stashKeyringId;
Expand Down Expand Up @@ -4888,6 +4908,13 @@ export class WalletController extends BaseController {
throw e;
}
};
tryOpenOrActiveUserGuide = async () => {
if (this.isBooted()) {
return false;
}
await userGuideService.activeUserGuide();
return true;
};
}

const wallet = new WalletController();
Expand Down
16 changes: 16 additions & 0 deletions src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { customTestnetService } from './service/customTestnet';
import { findChain } from '@/utils/chain';
import { syncChainService } from './service/syncChain';
import { GasAccountServiceStore } from './service/gasAccount';
import { userGuideService } from './service/userGuide';

Safe.adapter = fetchAdapter as any;

Expand All @@ -77,6 +78,7 @@ Sentry.init({
});

async function restoreAppState() {
await onInstall();
const keyringState = await storage.get('keyringState');
keyringService.loadStore(keyringState);
keyringService.store.subscribe((value) => storage.set('keyringState', value));
Expand Down Expand Up @@ -116,6 +118,10 @@ async function restoreAppState() {
startEnableUser();
walletController.syncMainnetChainList();

if (!keyringService.isBooted()) {
userGuideService.init();
}

eventBus.addEventListener(EVENTS_IN_BG.ON_TX_COMPLETED, ({ address }) => {
if (!address) return;

Expand Down Expand Up @@ -387,3 +393,13 @@ function startEnableUser() {
});
preferenceService.updateSendEnableTime(Date.now());
}

// On first install, open a new tab with Rabby
async function onInstall() {
const storeAlreadyExisted = await userGuideService.isStorageExisted();
// If the store doesn't exist, then this is the first time running this script,
// and is therefore an install
if (!storeAlreadyExisted) {
await userGuideService.openUserGuide();
}
}
3 changes: 3 additions & 0 deletions src/background/service/keyring/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export class KeyringService extends EventEmitter {
}

async boot(password: string) {
if (this.isBooted()) {
throw new Error('is booted');
}
this.password = password;
const encryptBooted = await passwordEncrypt({ data: 'true', password });
this.store.updateState({ booted: encryptBooted });
Expand Down
63 changes: 63 additions & 0 deletions src/background/service/userGuide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import browser from 'webextension-polyfill';

class UserGuideService {
currentTabId: number | undefined = undefined;

constructor() {}

init = () => {
browser.tabs.onRemoved.addListener(this.onTabRemoved);
};

isStorageExisted = async () => {
return Boolean(
Object.keys(await browser.storage.local.get(null)).filter((key) => {
return !['extensionId', 'openapi'].includes(key);
}).length
);
};

openUserGuide = async () => {
const tab = await browser.tabs.create({
active: true,
url: './index.html#/new-user/guide',
});
this.currentTabId = tab.id;
};

activeUserGuide = async () => {
if (this.currentTabId) {
try {
const tab = await browser.tabs.get(this.currentTabId);
if (
!tab.url ||
tab.url.indexOf(browser.runtime.getURL('index.html#/new-user/')) === 0
) {
await browser.tabs.update(this.currentTabId, {
active: true,
});
} else {
this.openUserGuide();
}
} catch (e) {
console.log(e);
this.openUserGuide();
}
} else {
this.openUserGuide();
}
};

onTabRemoved = (tabId: number) => {
if (tabId === this.currentTabId) {
this.currentTabId = undefined;
}
};

destroy = () => {
browser.tabs.onRemoved.removeListener(this.onTabRemoved);
this.currentTabId = undefined;
};
}

export const userGuideService = new UserGuideService();
10 changes: 9 additions & 1 deletion src/ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import Views from './views';
import { Message } from '@/utils/message';
import { getUITypeName } from 'ui/utils';
import { getUiType, getUITypeName, openInTab } from 'ui/utils';
import eventBus from '@/eventBus';
import * as Sentry from '@sentry/react';
import i18n, { addResourceBundle, changeLanguage } from 'src/i18n';
Expand Down Expand Up @@ -131,6 +131,14 @@ const main = () => {
store.dispatch.app.initBizStore();
store.dispatch.chains.init();

if (getUiType().isPop) {
wallet.tryOpenOrActiveUserGuide().then((opened) => {
if (opened) {
window.close();
}
});
}

wallet.getLocale().then((locale) => {
addResourceBundle(locale).then(() => {
changeLanguage(locale);
Expand Down
3 changes: 3 additions & 0 deletions src/ui/assets/icon-checked-success-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/ui/assets/import/arrow-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/ui/assets/import/swipe-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/ui/assets/new-user-import/arrow-down-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/ui/assets/new-user-import/back-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/ui/assets/new-user-import/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading