diff --git a/package.json b/package.json index 6eed13031..7ecff5a13 100644 --- a/package.json +++ b/package.json @@ -32,13 +32,13 @@ "prepare": "husky" }, "devDependencies": { - "@commitlint/cli": "^19.5.0", - "@commitlint/config-conventional": "^19.5.0", - "@swc/core": "^1.9.2", - "concurrently": "^9.1.0", - "husky": "^9.1.6", + "@commitlint/cli": "^19.7.1", + "@commitlint/config-conventional": "^19.7.1", + "@swc/core": "^1.10.14", + "concurrently": "^9.1.2", + "husky": "^9.1.7", "node-notifier": "^10.0.1", - "nodemon": "^3.1.7", + "nodemon": "^3.1.9", "ultra-runner": "^3.10.5" }, "resolutions": { diff --git a/packages/extension-bridge/package.json b/packages/extension-bridge/package.json index 439711ba4..d28aba097 100644 --- a/packages/extension-bridge/package.json +++ b/packages/extension-bridge/package.json @@ -39,28 +39,28 @@ ], "dependencies": { "nanoevents": "^9.1.0", - "serialize-error": "11.0.3", + "serialize-error": "12.0.0", "tiny-uid": "^1.1.2", "webextension-polyfill": "^0.12.0" }, "devDependencies": { - "@types/node": "^22.9.0", + "@types/node": "^22.13.1", "@types/webextension-polyfill": "^0.12.1", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", - "bumpp": "^9.8.1", - "eslint": "^9.14.0", + "bumpp": "^10.0.2", + "eslint": "^9.20.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-module-resolver": "^1.5.0", - "prettier": "^3.3.3", + "prettier": "^3.4.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "tsup": "^8.3.5", - "type-fest": "^4.26.1", - "typescript": "^5.6.3", - "typescript-eslint": "8.14.0" + "tsup": "^8.3.6", + "type-fest": "^4.33.0", + "typescript": "^5.7.3", + "typescript-eslint": "8.23.0" } } diff --git a/packages/extension/configs/vite/empty.js b/packages/extension/configs/vite/empty.js new file mode 100644 index 000000000..9e16c961b --- /dev/null +++ b/packages/extension/configs/vite/empty.js @@ -0,0 +1,4 @@ +const readFileSync = () => {}; +const writeFileSync = () => {}; + +export { readFileSync, writeFileSync }; diff --git a/packages/extension/configs/vite/transform-manifest.ts b/packages/extension/configs/vite/transform-manifest.ts index f23411092..ed692bf3a 100644 --- a/packages/extension/configs/vite/transform-manifest.ts +++ b/packages/extension/configs/vite/transform-manifest.ts @@ -1,4 +1,4 @@ -import { CrxPlugin } from '@crxjs/vite-plugin' +import { CrxPlugin } from '@crxjs/vite-plugin'; function transFormManifest(): CrxPlugin { return { @@ -17,7 +17,7 @@ function transFormManifest(): CrxPlugin { js: ['vendor/trezor-content-script.js'], run_at: 'document_start', }, - ] as any + ] as any; if (process.env.BROWSER !== 'opera') { manifest.content_scripts?.push({ matches: ['file://*/*', 'http://*/*', 'https://*/*'], @@ -25,7 +25,7 @@ function transFormManifest(): CrxPlugin { run_at: 'document_start', all_frames: false, world: 'MAIN', - } as any) + } as any); } manifest.web_accessible_resources?.push({ resources: [ @@ -35,9 +35,9 @@ function transFormManifest(): CrxPlugin { ], use_dynamic_url: false, matches: ['file://*/*', 'http://*/*', 'https://*/*'], - }) - return manifest + }); + return manifest; }, - } + }; } -export default transFormManifest +export default transFormManifest; diff --git a/packages/extension/package.json b/packages/extension/package.json index 62ddc771f..d3389c582 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -23,7 +23,7 @@ "watch:firefox": "yarn prebuild && cross-env BROWSER='firefox' vite" }, "dependencies": { - "@amplitude/analytics-browser": "^2.11.9", + "@amplitude/analytics-browser": "^2.11.11", "@enkryptcom/extension-bridge": "workspace:^", "@enkryptcom/hw-wallets": "workspace:^", "@enkryptcom/keyring": "workspace:^", @@ -35,51 +35,52 @@ "@ethereumjs/common": "^4.4.0", "@ethereumjs/tx": "^5.4.0", "@ethereumjs/util": "^9.1.0", - "@kadena/client": "^1.15.0", - "@kadena/pactjs-cli": "^1.15.0", + "@ethereumjs/wallet": "^2.0.4", + "@kadena/client": "^1.16.0", + "@kadena/pactjs-cli": "^1.16.0", "@ledgerhq/hw-transport-webusb": "^6.29.4", - "@metamask/eth-sig-util": "^7.0.3", - "@metaplex-foundation/mpl-bubblegum": "^4.2.1", - "@metaplex-foundation/umi": "^0.9.2", - "@metaplex-foundation/umi-bundle-defaults": "^0.9.2", - "@polkadot/api": "^14.3.1", - "@polkadot/extension-inject": "^0.56.2", - "@polkadot/keyring": "^13.2.3", - "@polkadot/rpc-provider": "^14.3.1", - "@polkadot/types": "^14.3.1", - "@polkadot/types-known": "^14.3.1", - "@polkadot/ui-shared": "^3.11.3", - "@polkadot/util": "^13.2.3", + "@metamask/eth-sig-util": "^8.2.0", + "@metaplex-foundation/mpl-bubblegum": "^4.3.1", + "@metaplex-foundation/umi": "^1.0.0", + "@metaplex-foundation/umi-bundle-defaults": "^1.0.0", + "@polkadot/api": "^15.5.2", + "@polkadot/extension-inject": "^0.58.3", + "@polkadot/keyring": "^13.3.1", + "@polkadot/rpc-provider": "^15.5.2", + "@polkadot/types": "^15.5.2", + "@polkadot/types-known": "^15.5.2", + "@polkadot/ui-shared": "^3.12.1", + "@polkadot/util": "^13.3.1", "@polkadot/wasm-crypto": "^7.4.1", - "@solana-developers/helpers": "^2.5.6", - "@solana/spl-token": "^0.4.9", - "@solana/wallet-standard-features": "^1.2.0", - "@solana/web3.js": "^1.95.4", - "@wallet-standard/base": "^0.0.0-20240703212708", + "@solana-developers/helpers": "^2.7.0", + "@solana/spl-token": "^0.4.12", + "@solana/wallet-standard-features": "^1.3.0", + "@solana/web3.js": "^1.98.0", + "@wallet-standard/base": "^1.1.0", "add": "^2.0.6", "bignumber.js": "^9.1.2", "bip39": "^3.1.0", - "bitcoinjs-lib": "^6.1.6", + "bitcoinjs-lib": "^6.1.7", "bs58": "^6.0.0", - "concurrently": "^9.1.0", - "echarts": "^5.5.1", + "concurrently": "^9.1.2", + "echarts": "^5.6.0", "ethereum-cryptography": "^2.2.1", "ethereumjs-abi": "^0.6.8", - "ethereumjs-wallet": "^1.0.2", "eventemitter3": "^5.0.1", + "jdenticon": "^3.3.0", "lodash": "^4.17.21", "memoize-one": "^6.0.0", "moment": "^2.30.1", "nanoevents": "^9.1.0", "pact-lang-api": "^4.3.6", - "pinia": "^2.2.6", + "pinia": "^2.3.1", "qrcode.vue": "^3.6.0", "switch-ts": "^1.1.1", "url-parse": "^1.5.10", - "uuid": "^10.0.0", - "vue": "^3.5.12", + "uuid": "^11.0.5", + "vue": "^3.5.13", "vue-echarts": "7.0.3", - "vue-router": "^4.4.5", + "vue-router": "^4.5.0", "vue3-lottie": "^3.3.1", "vuedraggable": "^4.1.0", "web3-eth": "^1.10.4", @@ -88,55 +89,55 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@crxjs/vite-plugin": "^2.0.0-beta.26", - "@rollup/plugin-commonjs": "^28.0.1", + "@crxjs/vite-plugin": "^2.0.0-beta.31", + "@rollup/plugin-commonjs": "^28.0.2", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.3.0", - "@rollup/plugin-replace": "^6.0.1", + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^12.1.1", + "@rollup/plugin-typescript": "^12.1.2", "@tsconfig/node20": "^20.1.4", "@types/bs58": "^4.0.4", - "@types/chrome": "^0.0.280", + "@types/chrome": "^0.0.302", "@types/ethereumjs-abi": "^0.6.5", "@types/events": "^3.0.3", "@types/fs-extra": "^11.0.4", - "@types/less": "^3.0.6", - "@types/lodash": "^4.17.13", - "@types/node": "^22.9.0", + "@types/less": "^3.0.8", + "@types/lodash": "^4.17.15", + "@types/node": "^22.13.1", "@types/url-parse": "^1.4.11", "@types/utf-8-validate": "^5.0.2", "@types/uuid": "^10.0.0", "@types/wif": "^2.0.5", "@types/zxcvbn": "^4.4.5", - "@vitejs/plugin-vue": "^5.1.5", - "@vue/eslint-config-prettier": "^10.1.0", - "@vue/eslint-config-typescript": "^14.1.3", - "@vue/tsconfig": "^0.6.0", - "@vueuse/core": "^11.2.0", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.3.0", + "@vue/tsconfig": "^0.7.0", + "@vueuse/core": "^12.5.0", "cross-env": "^7.0.3", - "eslint": "^9.14.0", - "eslint-plugin-vue": "^9.31.0", - "fs-extra": "^11.2.0", - "jsdom": "^25.0.1", - "less": "^4.2.0", + "eslint": "^9.20.0", + "eslint-plugin-vue": "^9.32.0", + "fs-extra": "^11.3.0", + "jsdom": "^26.0.0", + "less": "^4.2.2", "less-loader": "^12.2.0", - "npm-run-all2": "^6.2.6", - "prettier": "^3.3.3", + "npm-run-all2": "^7.0.2", + "prettier": "^3.4.2", "rimraf": "^6.0.1", - "rollup": "^4.25.0", - "rollup-plugin-visualizer": "^5.12.0", - "semver": "^7.6.3", - "systeminformation": "^5.23.5", - "tsup": "^8.3.5", - "typescript": "~5.6.3", + "rollup": "^4.34.6", + "rollup-plugin-visualizer": "^5.14.0", + "semver": "^7.7.1", + "systeminformation": "^5.25.11", + "tsup": "^8.3.6", + "typescript": "~5.7.3", "url": "^0.11.4", - "vite": "^5.4.11", - "vite-plugin-node-polyfills": "0.17.0", - "vite-tsconfig-paths": "^5.1.2", - "vitest": "^2.1.4", - "vue-tsc": "^2.1.10", + "vite": "^6.1.0", + "vite-plugin-node-polyfills": "0.23.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.5", + "vue-tsc": "^2.2.0", "webextension-polyfill": "^0.12.0" }, "installConfig": { diff --git a/packages/extension/src/libs/backup-state/configs.ts b/packages/extension/src/libs/backup-state/configs.ts new file mode 100644 index 000000000..2837d11af --- /dev/null +++ b/packages/extension/src/libs/backup-state/configs.ts @@ -0,0 +1,6 @@ +const BACKUP_URL = 'https://backupstore.enkrypt.com/'; +const HEADERS = { + Accept: 'application/json', + 'Content-Type': 'application/json', +}; +export { BACKUP_URL, HEADERS }; diff --git a/packages/extension/src/libs/backup-state/index.ts b/packages/extension/src/libs/backup-state/index.ts new file mode 100644 index 000000000..346c26739 --- /dev/null +++ b/packages/extension/src/libs/backup-state/index.ts @@ -0,0 +1,363 @@ +import BrowserStorage from '../common/browser-storage'; +import { InternalStorageNamespace } from '@/types/provider'; +import { + BackupData, + BackupResponseType, + BackupType, + IState, + ListBackupType, + StorageKeys, +} from './types'; +import PublicKeyRing from '../keyring/public-keyring'; +import sendUsingInternalMessengers from '../messenger/internal-messenger'; +import { InternalMethods } from '@/types/messenger'; +import EthNetwork from '@/providers/ethereum/networks/eth'; +import { + bufferToHex, + hexToBuffer, + NACL_VERSION, + naclEncrypt, + utf8ToHex, +} from '@enkryptcom/utils'; +import { hashPersonalMessage } from '@ethereumjs/util'; +import { v4 as uuidv4 } from 'uuid'; +import { BACKUP_URL, HEADERS } from './configs'; +import { EnkryptAccount, SignerType, WalletType } from '@enkryptcom/types'; +import KeyRingBase from '../keyring/keyring'; + +class BackupState { + private storage: BrowserStorage; + + constructor() { + this.storage = new BrowserStorage(InternalStorageNamespace.backupState); + } + + async #getSignature( + msgHash: string, + mainWallet: EnkryptAccount, + ): Promise { + return sendUsingInternalMessengers({ + method: InternalMethods.sign, + params: [msgHash, mainWallet], + }).then(res => { + if (res.error) { + console.error(res); + return null; + } else { + return JSON.parse(res.result as string); + } + }); + } + + async getMainWallet(): Promise { + const pkr = new PublicKeyRing(); + const allAccounts = await pkr.getAccounts(); + const mainWallet = allAccounts.find( + acc => + acc.walletType === WalletType.mnemonic && + acc.pathIndex === 0 && + acc.signerType === EthNetwork.signer[0] && + acc.basePath === EthNetwork.basePath, + ); + if (!mainWallet) { + throw new Error('No main wallet found'); + } + return mainWallet; + } + + getListBackupMsgHash(pubkey: string): string { + const now = new Date(); + const messageToSign = `${pubkey}-GET-BACKUPS-${(now.getUTCMonth() + 1).toString().padStart(2, '0')}-${now.getUTCDate().toString().padStart(2, '0')}-${now.getUTCFullYear()}`; + return bufferToHex( + hashPersonalMessage(hexToBuffer(utf8ToHex(messageToSign))), + ); + } + + async listBackups(options?: { + signature: string; + pubkey: string; + }): Promise { + let signature: string = ''; + let pubkey: string = ''; + if (options) { + signature = options.signature; + pubkey = options.pubkey; + } else { + const mainWallet = await this.getMainWallet(); + pubkey = mainWallet.publicKey; + const msgHash = this.getListBackupMsgHash(mainWallet.publicKey); + signature = await this.#getSignature(msgHash, mainWallet); + } + if (!signature) { + console.error('No signature found'); + return []; + } + const rawResponse = await fetch( + `${BACKUP_URL}backups/${pubkey}?signature=${signature}`, + { + method: 'GET', + headers: HEADERS, + }, + ); + const content = (await rawResponse.json()) as { + backups: ListBackupType[]; + }; + return content.backups; + } + + async getBackup(userId: string): Promise { + const mainWallet = await this.getMainWallet(); + const now = new Date(); + const messageToSign = `${userId}-GET-BACKUP-${(now.getUTCMonth() + 1).toString().padStart(2, '0')}-${now.getUTCDate().toString().padStart(2, '0')}-${now.getUTCFullYear()}`; + const msgHash = bufferToHex( + hashPersonalMessage(hexToBuffer(utf8ToHex(messageToSign))), + ); + const signature = await this.#getSignature(msgHash, mainWallet); + const rawResponse = await fetch( + `${BACKUP_URL}backups/${mainWallet.publicKey}/users/${userId}?signature=${signature}`, + { + method: 'GET', + headers: HEADERS, + }, + ); + const content = (await rawResponse.json()) as BackupResponseType; + return content.backup; + } + + async deleteBackup(userId: string): Promise { + const mainWallet = await this.getMainWallet(); + const now = new Date(); + const messageToSign = `${userId}-DELETE-BACKUP-${(now.getUTCMonth() + 1).toString().padStart(2, '0')}-${now.getUTCDate().toString().padStart(2, '0')}-${now.getUTCFullYear()}`; + const msgHash = bufferToHex( + hashPersonalMessage(hexToBuffer(utf8ToHex(messageToSign))), + ); + const signature = await this.#getSignature(msgHash, mainWallet); + if (!signature) { + console.error('No signature found'); + return false; + } + return fetch( + `${BACKUP_URL}backups/${mainWallet.publicKey}/users/${userId}?signature=${signature}`, + { + method: 'DELETE', + headers: HEADERS, + }, + ) + .then(res => res.json()) + .then(content => { + if ((content as { message: string }).message === 'Ok') { + return true; + } + console.error(content); + return false; + }); + } + + async restoreBackup(userId: string, keyringPassword: string): Promise { + const mainWallet = await this.getMainWallet(); + await sendUsingInternalMessengers({ + method: InternalMethods.unlock, + params: [keyringPassword, false], + }); + const backup = await this.getBackup(userId); + if (!backup) { + console.error('No backup found'); + return; + } + await sendUsingInternalMessengers({ + method: InternalMethods.ethereumDecrypt, + params: [backup.payload, mainWallet], + }).then(async res => { + if (res.error) { + console.error(res); + return null; + } else { + const kr = new KeyRingBase(); + await kr.unlock(keyringPassword); + const existingAccounts = await kr.getKeysArray(); + const decryptedBackup: BackupData = JSON.parse( + JSON.parse(res.result as string), + ); + const highestPathIndex: Record = {}; + decryptedBackup.accounts.forEach(acc => { + const id = `${acc.basePath}###${acc.signerType}`; + const idx = acc.pathIndex; + if (!highestPathIndex[id] || highestPathIndex[id] < idx) { + highestPathIndex[id] = idx; + } + }); + const getAccountByIndex = ( + accounts: Omit[], + basePath: string, + signerType: SignerType, + idx: number, + ): EnkryptAccount | null => { + for (const acc of accounts) { + if ( + acc.basePath === basePath && + acc.pathIndex === idx && + acc.signerType === signerType + ) { + return acc as EnkryptAccount; + } + } + return null; + }; + + for (const key of Object.keys(highestPathIndex)) { + const [basePath, signerType] = key.split('###'); + for (let i = 0; i <= highestPathIndex[key]; i++) { + const newAccount = getAccountByIndex( + decryptedBackup.accounts, + basePath, + signerType as SignerType, + i, + ); + const existingAccount = getAccountByIndex( + existingAccounts, + basePath, + signerType as SignerType, + i, + ); + if (existingAccount && newAccount) { + await kr.renameAccount(existingAccount.address, newAccount.name); + continue; + } else if (newAccount) { + await kr.saveNewAccount({ + basePath: newAccount.basePath, + name: newAccount.name, + signerType: newAccount.signerType, + walletType: newAccount.walletType, + }); + } else if (!newAccount) { + await kr.saveNewAccount({ + basePath: basePath, + name: `New Account from backup ${i}`, + signerType: signerType as SignerType, + walletType: WalletType.mnemonic, + }); + } + } + } + } + }); + } + + async backup(firstTime: boolean): Promise { + const state = await this.getState(); + if (firstTime && state.lastBackupTime !== 0) { + return true; + } + if (!state.enabled) { + return true; + } + const pkr = new PublicKeyRing(); + const allAccounts = await pkr.getAccounts(); + const mainWallet = await this.getMainWallet(); + const backupData: BackupData = { + accounts: allAccounts + .filter( + acc => !acc.isTestWallet && acc.walletType !== WalletType.privkey, + ) + .map(acc => { + return { + basePath: acc.basePath, + pathIndex: acc.pathIndex, + name: acc.name, + signerType: acc.signerType, + walletType: acc.walletType, + isHardware: acc.isHardware, + HWOptions: acc.HWOptions, + }; + }), + uuid: state.userId, + }; + const encryptPubKey = await sendUsingInternalMessengers({ + method: InternalMethods.getEthereumEncryptionPublicKey, + params: [mainWallet], + }).then(res => { + if (res.error) { + console.error(res); + return null; + } else { + return JSON.parse(res.result as string); + } + }); + if (!encryptPubKey) { + console.error('No encrypt public key found'); + return false; + } + const encryptedStr = naclEncrypt({ + publicKey: encryptPubKey, + data: JSON.stringify(backupData), + version: NACL_VERSION, + }); + const msgHash = bufferToHex(hashPersonalMessage(hexToBuffer(encryptedStr))); + return this.#getSignature(msgHash, mainWallet).then(async signature => { + const rawResponse = await fetch( + `${BACKUP_URL}backups/${mainWallet.publicKey}/users/${state.userId}?signature=${signature}`, + { + method: 'POST', + headers: HEADERS, + body: JSON.stringify({ + payload: encryptedStr, + }), + }, + ); + const content = (await rawResponse.json()) as { message: string }; + if (content.message === 'Ok') { + await this.setState({ + lastBackupTime: new Date().getTime(), + userId: state.userId, + enabled: state.enabled, + }); + return true; + } + console.error(content); + return false; + }); + } + + async setState(state: IState): Promise { + return this.storage.set(StorageKeys.backupInfo, state); + } + + async getState(): Promise { + const state = await this.storage.get(StorageKeys.backupInfo); + if (!state) { + const newState: IState = { + lastBackupTime: 0, + userId: uuidv4(), + enabled: true, + }; + await this.setState(newState); + return newState; + } + return state; + } + + async getLastUpdatedTime(): Promise { + const state: IState = await this.getState(); + return new Date(state.lastBackupTime); + } + + async getUserId(): Promise { + const state: IState = await this.getState(); + return state.userId; + } + + async disableBackups(): Promise { + const state: IState = await this.getState(); + await this.setState({ ...state, enabled: false }); + } + async enableBackups(): Promise { + const state: IState = await this.getState(); + await this.setState({ ...state, enabled: true }); + } + async isBackupEnabled(): Promise { + const state: IState = await this.getState(); + return state.enabled; + } +} + +export default BackupState; diff --git a/packages/extension/src/libs/backup-state/types.ts b/packages/extension/src/libs/backup-state/types.ts new file mode 100644 index 000000000..ef86ce012 --- /dev/null +++ b/packages/extension/src/libs/backup-state/types.ts @@ -0,0 +1,29 @@ +import { EnkryptAccount } from '@enkryptcom/types'; + +export enum StorageKeys { + backupInfo = 'backup-info', +} + +export interface IState { + lastBackupTime: number; + userId: string; + enabled: boolean; +} + +export interface ListBackupType { + userId: string; + updatedAt: string; +} + +export interface BackupType extends ListBackupType { + payload: string; +} + +export interface BackupResponseType { + backup: BackupType; +} + +export interface BackupData { + accounts: Omit[]; + uuid: string; +} diff --git a/packages/extension/src/libs/keyring/public-keyring.ts b/packages/extension/src/libs/keyring/public-keyring.ts index bdfd73589..e5cbb58b6 100644 --- a/packages/extension/src/libs/keyring/public-keyring.ts +++ b/packages/extension/src/libs/keyring/public-keyring.ts @@ -23,6 +23,7 @@ class PublicKeyRing { signerType: SignerType.secp256k1, walletType: WalletType.mnemonic, isHardware: false, + isTestWallet: true, }; allKeys['0xb1ea5a3e5ea7fa1834d48058ecda26d8c59e8251'] = { address: '0xb1ea5a3e5ea7fa1834d48058ecda26d8c59e8251', //optimism nfts @@ -33,6 +34,7 @@ class PublicKeyRing { signerType: SignerType.secp256k1, walletType: WalletType.mnemonic, isHardware: false, + isTestWallet: true, }; allKeys['0xe5dc07bdcdb8c98850050c7f67de7e164b1ea391'] = { address: '0xe5dc07bdcdb8c98850050c7f67de7e164b1ea391', @@ -43,6 +45,7 @@ class PublicKeyRing { signerType: SignerType.secp256k1, walletType: WalletType.ledger, isHardware: true, + isTestWallet: true, }; allKeys['5E56EZk6jmpq1q3Har3Ms99D9TLN9ra2inFh7Q1Hj6GpUx6D'] = { address: '5E56EZk6jmpq1q3Har3Ms99D9TLN9ra2inFh7Q1Hj6GpUx6D', @@ -53,6 +56,7 @@ class PublicKeyRing { signerType: SignerType.sr25519, walletType: WalletType.ledger, isHardware: true, + isTestWallet: true, }; allKeys['5E56EZk6jmpq1q3Har3Ms99D9TLN9ra2inFh7Q1Hj6GpUx6D'] = { address: '5CFnoCsP3pDK2thhSqYPwKELJFLQ1hBodqzSUypexyh7eHkB', @@ -63,6 +67,7 @@ class PublicKeyRing { signerType: SignerType.sr25519, walletType: WalletType.mnemonic, isHardware: false, + isTestWallet: true, }; allKeys[ 'bc1puzz9tmxawd7zdd7klfgtywrgpma3u22fz5ecxhucd4j8tygqe5ms2vdd9y' @@ -76,6 +81,7 @@ class PublicKeyRing { signerType: SignerType.secp256k1btc, walletType: WalletType.mnemonic, isHardware: false, + isTestWallet: true, }; allKeys['77hREDDaAiimedtD9bR1JDMgYLW3AA5yPvD91pvrueRp'] = { address: '77hREDDaAiimedtD9bR1JDMgYLW3AA5yPvD91pvrueRp', @@ -86,6 +92,7 @@ class PublicKeyRing { signerType: SignerType.ed25519sol, walletType: WalletType.mnemonic, isHardware: false, + isTestWallet: true, }; allKeys['tQvduDby4rvC6VU4rSirhVWuRYxbJz3rvUrVMkUWsZP'] = { address: 'tQvduDby4rvC6VU4rSirhVWuRYxbJz3rvUrVMkUWsZP', @@ -96,6 +103,7 @@ class PublicKeyRing { signerType: SignerType.ed25519sol, walletType: WalletType.mnemonic, isHardware: false, + isTestWallet: true, }; } return allKeys; diff --git a/packages/extension/src/libs/networks-state/index.ts b/packages/extension/src/libs/networks-state/index.ts index 59dbf6cad..40e0e4f2d 100644 --- a/packages/extension/src/libs/networks-state/index.ts +++ b/packages/extension/src/libs/networks-state/index.ts @@ -2,7 +2,6 @@ import BrowserStorage from '../common/browser-storage'; import { POPULAR_NAMES } from '../utils/networks'; import { InternalStorageNamespace } from '@/types/provider'; import { IState, StorageKeys, NetworkStorageElement } from './types'; -import { newNetworks, newSwaps } from '@/providers/common/libs/new-features'; class NetworksState { private storage: BrowserStorage; @@ -53,47 +52,6 @@ class NetworksState { await this.setState(state); } - /** - * Inserts networks with new features. - * - * This method first retrieves the current state and checks if the networks - * have been updated to the latest version. If the state and networks are defined, - * it filters out the networks that are not in the predefined list of networks with new features. - * It then maps the filtered networks to a new network item and inserts them into the valid networks. - * The new networks are inserted at the 6th index, or at the end if there are fewer than 6 networks. - * The state is then updated with the new networks and the latest version. - */ - async insertNetworksWithNewFeatures(): Promise { - const state: IState | undefined = await this.getState(); - if ( - state && - state.networks && - state.newNetworksVersion !== __PACKAGE_VERSION__ - ) { - let validNetworks = state.networks; - const netsWithFeatures = [ - ...new Set([...newNetworks, ...newSwaps]), - ].sort(); - const filteredNets = netsWithFeatures.filter(n => { - for (const vn of validNetworks) if (vn.name === n) return false; - return true; - }); - const fnetworkItem = filteredNets.map(name => { - return { - name, - }; - }); - const insertIdx = validNetworks.length > 5 ? 5 : validNetworks.length; - validNetworks = validNetworks - .slice(0, insertIdx) - .concat(fnetworkItem, validNetworks.slice(insertIdx)); - state.networks = validNetworks; - state.newNetworksVersion = __PACKAGE_VERSION__ as string; - state.newUsedFeatures = { networks: [], swap: [] }; - await this.setState(state); - } - } - async setUsedFeature(feature: 'networks' | 'swap', networkName: string) { const state: IState | undefined = await this.getState(); if (state) { @@ -127,7 +85,6 @@ class NetworksState { * @returns {Promise} A promise that resolves to an array of active network names. */ async getPinnedNetworkNames(): Promise { - await this.insertNetworksWithNewFeatures(); const state: IState | undefined = await this.getState(); if (state && state.networks) { const validNetworks = state.networks; @@ -139,7 +96,6 @@ class NetworksState { } async getEnabledTestNetworks(): Promise { - await this.insertNetworksWithNewFeatures(); const state: IState | undefined = await this.getState(); if (state && state.enabledTestNetworks) { const validNetworks = state.enabledTestNetworks; diff --git a/packages/extension/src/libs/utils/initialize-wallet.ts b/packages/extension/src/libs/utils/initialize-wallet.ts index d5978605a..ff66e07c2 100644 --- a/packages/extension/src/libs/utils/initialize-wallet.ts +++ b/packages/extension/src/libs/utils/initialize-wallet.ts @@ -6,12 +6,23 @@ import KadenaNetworks from '@/providers/kadena/networks'; import SolanaNetworks from '@/providers/solana/networks'; import { NetworkNames, WalletType } from '@enkryptcom/types'; import { getAccountsByNetworkName } from '@/libs/utils/accounts'; +import BackupState from '../backup-state'; export const initAccounts = async (keyring: KeyRing) => { - const secp256k1btc = await getAccountsByNetworkName(NetworkNames.Bitcoin); - const secp256k1 = await getAccountsByNetworkName(NetworkNames.Ethereum); - const sr25519 = await getAccountsByNetworkName(NetworkNames.Polkadot); - const ed25519kda = await getAccountsByNetworkName(NetworkNames.Kadena); - const ed25519sol = await getAccountsByNetworkName(NetworkNames.Solana); + const secp256k1btc = ( + await getAccountsByNetworkName(NetworkNames.Bitcoin) + ).filter(acc => !acc.isTestWallet); + const secp256k1 = ( + await getAccountsByNetworkName(NetworkNames.Ethereum) + ).filter(acc => !acc.isTestWallet); + const sr25519 = ( + await getAccountsByNetworkName(NetworkNames.Polkadot) + ).filter(acc => !acc.isTestWallet); + const ed25519kda = ( + await getAccountsByNetworkName(NetworkNames.Kadena) + ).filter(acc => !acc.isTestWallet); + const ed25519sol = ( + await getAccountsByNetworkName(NetworkNames.Solana) + ).filter(acc => !acc.isTestWallet); if (secp256k1.length == 0) await keyring.saveNewAccount({ basePath: EthereumNetworks.ethereum.basePath, @@ -51,7 +62,31 @@ export const initAccounts = async (keyring: KeyRing) => { export const onboardInitializeWallets = async ( mnemonic: string, password: string, -): Promise => { +): Promise<{ backupsFound: boolean }> => { const kr = new KeyRing(); + const backupsState = new BackupState(); await kr.init(mnemonic, password); + try { + await kr.unlock(password); + const mainAccount = await kr.getNewAccount({ + basePath: EthereumNetworks.ethereum.basePath, + signerType: EthereumNetworks.ethereum.signer[0], + }); + const sigHash = backupsState.getListBackupMsgHash(mainAccount.publicKey); + const signature = await kr.sign(sigHash as `0x${string}`, { + basePath: EthereumNetworks.ethereum.basePath, + signerType: EthereumNetworks.ethereum.signer[0], + pathIndex: 0, + walletType: WalletType.mnemonic, + }); + const backups = await backupsState.listBackups({ + pubkey: mainAccount.publicKey, + signature, + }); + kr.lock(); + return { backupsFound: backups.length > 0 }; + } catch (e) { + console.error(e); + return { backupsFound: false }; + } }; diff --git a/packages/extension/src/providers/common/ui/send-transaction/send-address-item.vue b/packages/extension/src/providers/common/ui/send-transaction/send-address-item.vue index 8c845e78c..beb2fa289 100644 --- a/packages/extension/src/providers/common/ui/send-transaction/send-address-item.vue +++ b/packages/extension/src/providers/common/ui/send-transaction/send-address-item.vue @@ -28,7 +28,7 @@ import { PropType } from 'vue'; import { BaseNetwork } from '@/types/base-network'; import DoneIcon from '@action/icons/common/done_icon.vue'; -import { EnkryptAccount } from '@enkryptcom/types'; + const emit = defineEmits<{ (e: 'selected:account', address: string): void; }>(); diff --git a/packages/extension/src/providers/common/ui/send-transaction/send-contacts-list.vue b/packages/extension/src/providers/common/ui/send-transaction/send-contacts-list.vue index 9826bf675..7800a0c69 100644 --- a/packages/extension/src/providers/common/ui/send-transaction/send-contacts-list.vue +++ b/packages/extension/src/providers/common/ui/send-transaction/send-contacts-list.vue @@ -39,11 +39,7 @@ }" :network="network" v-bind="$attrs" - :is-checked=" - !!address && - network.displayAddress(address) === - network.displayAddress(recentAddress) - " + :is-checked="isChecked(recentAddress)" /> @@ -55,11 +51,7 @@ :account="account" :network="network" v-bind="$attrs" - :is-checked=" - !!address && - network.displayAddress(address) === - network.displayAddress(account.address) - " + :is-checked="isChecked(account.address)" /> @@ -77,11 +69,7 @@ :account="account" :network="network" v-bind="$attrs" - :is-checked=" - !!address && - network.displayAddress(address) === - network.displayAddress(account.address) - " + :is-checked="isChecked(account.address)" /> @@ -129,6 +117,19 @@ const recentlySentAddressesState = new RecentlySentAddressesState(); const recentlySentAddresses = ref(null); +const isChecked = (address: string) => { + try { + return ( + !!props.address && + props.network.displayAddress(props.address) === + props.network.displayAddress(address) + ); + } catch (err) { + console.error('Error checking if address is checked', err); + return false; + } +}; + onMounted(async function () { let timedOut = false; const timeout = setTimeout(function () { diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts index 6d01483c5..ac68039d8 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts @@ -2,56 +2,47 @@ import { TokenBalance } from './types/tokenbalance-mew'; import { NATIVE_TOKEN_ADDRESS } from '../common'; import { numberToHex } from '@enkryptcom/utils'; import { BaseNetwork } from '@/types/base-network'; -import { - Connection, - GetProgramAccountsFilter, - PublicKey, -} from '@solana/web3.js'; -import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; +import { Connection, PublicKey } from '@solana/web3.js'; +import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; import { BNType } from '@/providers/common/types'; import { toBN } from 'web3-utils'; const getBalances = (network: BaseNetwork, address: string) => { const solConnection = new Connection(network.node); - const filters: GetProgramAccountsFilter[] = [ - { - dataSize: 165, - }, - { - memcmp: { - offset: 32, - bytes: address, - }, - }, - ]; - return solConnection - .getParsedProgramAccounts(TOKEN_PROGRAM_ID, { filters: filters }) - .then(accounts => { - const balances: TokenBalance[] = []; - const balanceObj = {} as Record; - accounts.forEach(acc => { - const balance = numberToHex( - (acc.account.data as any).parsed.info.tokenAmount.amount, - ); - if (balance === '0x0') return; - const contract = (acc.account.data as any).parsed.info.mint; - if (!balanceObj[contract]) balanceObj[contract] = toBN(0); - balanceObj[contract] = balanceObj[contract].add(toBN(balance)); - }); - Object.keys(balanceObj).forEach(contract => { - balances.push({ - balance: numberToHex(balanceObj[contract]), - contract, - }); + return Promise.all([ + solConnection.getParsedTokenAccountsByOwner(new PublicKey(address), { + programId: TOKEN_PROGRAM_ID, + }), + solConnection.getParsedTokenAccountsByOwner(new PublicKey(address), { + programId: TOKEN_2022_PROGRAM_ID, + }), + ]).then(([tokenAccounts, tokenAccounts2022]) => { + const accounts = tokenAccounts.value.concat(tokenAccounts2022.value); + const balances: TokenBalance[] = []; + const balanceObj = {} as Record; + accounts.forEach(acc => { + const balance = numberToHex( + (acc.account.data as any).parsed.info.tokenAmount.amount, + ); + if (balance === '0x0') return; + const contract = (acc.account.data as any).parsed.info.mint; + if (!balanceObj[contract]) balanceObj[contract] = toBN(0); + balanceObj[contract] = balanceObj[contract].add(toBN(balance)); + }); + Object.keys(balanceObj).forEach(contract => { + balances.push({ + balance: numberToHex(balanceObj[contract]), + contract, }); - return solConnection.getBalance(new PublicKey(address)).then(balance => { - balances.unshift({ - balance: numberToHex(balance), - contract: NATIVE_TOKEN_ADDRESS, - }); - return balances; + }); + return solConnection.getBalance(new PublicKey(address)).then(balance => { + balances.unshift({ + balance: numberToHex(balance), + contract: NATIVE_TOKEN_ADDRESS, }); + return balances; }); + }); }; export default getBalances; diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue index 17a4f0bd6..145cc20e7 100644 --- a/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue @@ -42,7 +42,12 @@ :address="addressTo" :network="network" @selected:account="selectAccountTo" - @update:paste-from-clipboard="addressInputTo.pasteFromClipboard()" + @update:paste-from-clipboard=" + () => { + addressInputTo.pasteFromClipboard(); + toggleSelectContactTo(false); + } + " @close="toggleSelectContactTo" /> diff --git a/packages/extension/src/providers/ethereum/ui/styles/common-popup.less b/packages/extension/src/providers/ethereum/ui/styles/common-popup.less index bbc677ea1..adacf22c4 100644 --- a/packages/extension/src/providers/ethereum/ui/styles/common-popup.less +++ b/packages/extension/src/providers/ethereum/ui/styles/common-popup.less @@ -4,6 +4,9 @@ width: 100%; height: 100%; padding-left: 30px; + h2 { + word-break: normal !important; + } &__header { padding: 28px 0 8px 0; diff --git a/packages/extension/src/providers/solana/ui/libs/decode-tx.ts b/packages/extension/src/providers/solana/ui/libs/decode-tx.ts index 1febd162b..d0fe33c1d 100644 --- a/packages/extension/src/providers/solana/ui/libs/decode-tx.ts +++ b/packages/extension/src/providers/solana/ui/libs/decode-tx.ts @@ -2,6 +2,7 @@ import { NATIVE_TOKEN_ADDRESS } from '@/providers/ethereum/libs/common'; import { ACCOUNT_SIZE, AccountLayout, + TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, } from '@solana/spl-token'; import { @@ -77,8 +78,9 @@ const decodeTransaction = async ( .accounts!.filter(a => { const data = Buffer.from(a!.data[0], 'base64'); return ( - a!.owner === TOKEN_PROGRAM_ID.toBase58() && - data.length === ACCOUNT_SIZE + (a!.owner === TOKEN_PROGRAM_ID.toBase58() || + a!.owner === TOKEN_2022_PROGRAM_ID.toBase58()) && + data.length >= ACCOUNT_SIZE ); }) .map(a => { diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index 80acc2c6d..6e581975e 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -525,11 +525,24 @@ const updateTransactionFees = async () => { (!isSendToken.value && selectedNft.value.type === NFTType.SolanaToken) ) { const contract = new PublicKey(TxInfo.value.contract); - const associatedTokenTo = getAssociatedTokenAddressSync(contract, to); - const associatedTokenFrom = getAssociatedTokenAddressSync(contract, from); + const programId = await solConnection.value!.web3.getAccountInfo(contract); + const associatedTokenTo = getAssociatedTokenAddressSync( + contract, + to, + undefined, + programId!.owner, + ); + const associatedTokenFrom = getAssociatedTokenAddressSync( + contract, + from, + undefined, + programId!.owner, + ); const validATA = await getAccount( solConnection.value!.web3, associatedTokenTo, + undefined, + programId!.owner, ) .then(() => true) .catch(() => false); @@ -540,6 +553,8 @@ const updateTransactionFees = async () => { associatedTokenTo, from, toBN(TxInfo.value.value).toNumber(), + [], + programId!.owner, ), ); } else { @@ -549,6 +564,7 @@ const updateTransactionFees = async () => { associatedTokenTo, to, contract, + programId!.owner, ), ); transaction.add( @@ -557,6 +573,8 @@ const updateTransactionFees = async () => { associatedTokenTo, from, toBN(TxInfo.value.value).toNumber(), + [], + programId!.owner, ), ); storageFee.value = diff --git a/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue b/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue index 5583c329c..33497b149 100644 --- a/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue +++ b/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue @@ -236,9 +236,6 @@ onBeforeMount(async () => { }), ); } - Tx.value.recentBlockhash = ( - await solConnection.value.web3.getLatestBlockhash() - ).blockhash; } const feeMessage = TxType.value === 'legacy' @@ -282,9 +279,15 @@ const approve = async () => { }); const latestBlockHash = (await solConnection.value!.web3.getLatestBlockhash()) .blockhash; - if (TxType.value === 'legacy') { + if ( + TxType.value === 'legacy' && + !(Tx.value as LegacyTransaction).recentBlockhash + ) { (Tx.value as LegacyTransaction).recentBlockhash = latestBlockHash; - } else { + } else if ( + TxType.value !== 'legacy' && + !(Tx.value as VersionedTransaction).message.recentBlockhash + ) { (Tx.value as VersionedTransaction).message.recentBlockhash = latestBlockHash; } diff --git a/packages/extension/src/types/provider.ts b/packages/extension/src/types/provider.ts index 601e67b47..862fb1c81 100644 --- a/packages/extension/src/types/provider.ts +++ b/packages/extension/src/types/provider.ts @@ -53,6 +53,7 @@ export enum InternalStorageNamespace { rateState = 'RateState', recentlySentAddresses = 'RecentlySentAddresses', updatesState = 'UpdatesState', + backupState = 'BackupState', } export enum EnkryptProviderEventMethods { persistentEvents = 'PersistentEvents', @@ -130,7 +131,7 @@ export abstract class BackgroundProviderInterface extends EventEmitter { export abstract class ProviderAPIInterface { abstract node: string; // eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(node: string, options?: unknown) { } + constructor(node: string, options?: unknown) {} abstract init(): Promise; abstract getBalance(address: string): Promise; abstract getTransactionStatus( diff --git a/packages/extension/src/ui/action/App.vue b/packages/extension/src/ui/action/App.vue index 0ade5518e..bb01b5366 100644 --- a/packages/extension/src/ui/action/App.vue +++ b/packages/extension/src/ui/action/App.vue @@ -192,11 +192,13 @@ import UpdatedIcon from '@/ui/action/icons/updates/updated.vue'; import HeartIcon from '@/ui/action/icons/updates/heart.vue'; import { getLatestEnkryptUpdates } from '@action/utils/browser'; import { Updates } from '@/ui/action/types/updates'; +import BackupState from '@/libs/backup-state'; const domainState = new DomainState(); const networksState = new NetworksState(); const rateState = new RateState(); const updatesState = new UpdatesState(); +const backupState = new BackupState(); const appMenuRef = ref(null); const showDepositWindow = ref(false); const accountHeaderData = ref({ @@ -350,9 +352,7 @@ const setActiveNetworks = async () => { const updateNetworkOrder = (newOrder: BaseNetwork[]) => { if (searchInput.value === '') { - if (activeCategory.value === NetworksCategory.Pinned) - pinnedNetworks.value = newOrder; - else networks.value = newOrder; + networks.value = newOrder; } }; const updateSearchValue = (newval: string) => { @@ -369,7 +369,7 @@ const openBuyPage = () => { case NetworkNames.SyscoinNEVM: case NetworkNames.Rollux: return `${(currentNetwork.value as EvmNetwork).options.buyLink}&address=${currentNetwork.value.displayAddress( - accountHeaderData.value.selectedAccount!.address + accountHeaderData.value.selectedAccount!.address, )}`; case NetworkNames.SyscoinNEVMTest: case NetworkNames.RolluxTest: @@ -408,6 +408,7 @@ const init = async () => { setNetwork(defaultNetwork); } await setActiveNetworks(); + backupState.backup(true).catch(console.error); isLoading.value = false; }; @@ -632,10 +633,22 @@ const displayNetworks = computed(() => { return networks.value.filter(net => net.isTestNetwork ? enabledTestnetworks.value.includes(net.name) : true, ); - case NetworksCategory.Pinned: - return pinnedNetworks.value; - case NetworksCategory.New: - return networks.value.filter(net => newNetworks.includes(net.name)); + case NetworksCategory.Pinned: { + const hasCurrentNetwork = pinnedNetworks.value.some( + net => net.name === currentNetwork.value.name, + ); + return hasCurrentNetwork + ? pinnedNetworks.value + : [...pinnedNetworks.value, currentNetwork.value]; + } + case NetworksCategory.New: { + const hasCurrentNetwork = newNetworks.includes(currentNetwork.value.name); + const newNets = networks.value.filter(net => + newNetworks.includes(net.name), + ); + + return hasCurrentNetwork ? newNets : [...newNets, currentNetwork.value]; + } default: return networks.value; } diff --git a/packages/extension/src/ui/action/components/network-menu/index.vue b/packages/extension/src/ui/action/components/network-menu/index.vue index 3002a6d13..60612be8f 100644 --- a/packages/extension/src/ui/action/components/network-menu/index.vue +++ b/packages/extension/src/ui/action/components/network-menu/index.vue @@ -3,22 +3,25 @@ -
Assets +
Assets
-
Activity +
Activity
-
NFTs +
NFTs
-
DApps +
DApps
@@ -41,6 +45,7 @@ import { PropType } from 'vue'; import DappList from '@/libs/dapp-list'; import { BaseNetwork } from '@/types/base-network'; import { EvmNetwork } from '@/providers/ethereum/types/evm-network'; +import { useRoute } from 'vue-router'; defineProps({ selected: { @@ -52,6 +57,8 @@ defineProps({ default: () => ({}), }, }); + +const route = useRoute(); diff --git a/packages/extension/src/ui/action/components/switch/index.vue b/packages/extension/src/ui/action/components/switch/index.vue index 4eec1d802..0db605bb0 100644 --- a/packages/extension/src/ui/action/components/switch/index.vue +++ b/packages/extension/src/ui/action/components/switch/index.vue @@ -1,25 +1,26 @@ diff --git a/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue b/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue index 3a9b45b2b..0158cb4d8 100644 --- a/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue +++ b/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue @@ -4,7 +4,7 @@ class="network-assets__token-info" :class="{ max: token.priceChangePercentage == 0 }" > - +

{{ token.name }}

{ } img { - max-width: 32px; + width: 32px; + height: 32px; margin-right: 16px; border-radius: 100%; box-shadow: inset 0px 0px 1px rgba(0, 0, 0, 0.16); diff --git a/packages/extension/src/ui/action/views/network-assets/index.vue b/packages/extension/src/ui/action/views/network-assets/index.vue index db42d690c..e75a87546 100644 --- a/packages/extension/src/ui/action/views/network-assets/index.vue +++ b/packages/extension/src/ui/action/views/network-assets/index.vue @@ -15,6 +15,10 @@ + ([]); const isLoading = ref(false); +const isFetchError = ref(false); const { cryptoAmount, fiatAmount } = accountInfoComposable( toRef(props, 'network'), @@ -104,6 +110,7 @@ const { cryptoAmount, fiatAmount } = accountInfoComposable( const selected: string = route.params.id as string; const updateAssets = () => { + isFetchError.value = false; isLoading.value = true; assets.value = []; const currentNetwork = selectedNetworkName.value; @@ -114,6 +121,12 @@ const updateAssets = () => { if (selectedNetworkName.value !== currentNetwork) return; assets.value = _assets; isLoading.value = false; + }) + .catch(() => { + if (selectedNetworkName.value !== currentNetwork) return; + isFetchError.value = true; + isLoading.value = false; + assets.value = []; }); } }; diff --git a/packages/extension/src/ui/action/views/settings/components/settings-inner-header.vue b/packages/extension/src/ui/action/views/settings/components/settings-inner-header.vue index e8dbf5afe..4e95041d2 100644 --- a/packages/extension/src/ui/action/views/settings/components/settings-inner-header.vue +++ b/packages/extension/src/ui/action/views/settings/components/settings-inner-header.vue @@ -8,6 +8,7 @@

Support

Recovery phrase

Reset wallet?

+

Settings backup

@@ -43,6 +44,10 @@ defineProps({ type: Boolean, default: false, }, + isBackups: { + type: Boolean, + default: false, + }, }); diff --git a/packages/extension/src/ui/action/views/settings/components/settings-switch.vue b/packages/extension/src/ui/action/views/settings/components/settings-switch.vue index 240f2c59b..a36e8379d 100644 --- a/packages/extension/src/ui/action/views/settings/components/settings-switch.vue +++ b/packages/extension/src/ui/action/views/settings/components/settings-switch.vue @@ -1,5 +1,8 @@ @@ -50,6 +57,7 @@ import SettingsSupport from './views/settings-support/index.vue'; import SettingsAbout from './views/settings-about/index.vue'; import SettingsRecovery from './views/settings-recovery/index.vue'; import ResetWallet from '@action/views/reset-wallet/index.vue'; +import SettingsBackups from './views/settings-backups/index.vue'; const isStart = ref(true); const isGeneral = ref(false); @@ -57,6 +65,7 @@ const isAbout = ref(false); const isSupport = ref(false); const isPhrase = ref(false); const isReset = ref(false); +const isBackups = ref(false); const mnemonic = ref(''); const emit = defineEmits<{ @@ -72,6 +81,7 @@ const setAllToFalse = () => { isSupport.value = false; isPhrase.value = false; isReset.value = false; + isBackups.value = false; mnemonic.value = ''; }; const recoveryPhraseAction = (phrase: string) => { @@ -103,6 +113,11 @@ const startAction = () => { setAllToFalse(); isStart.value = true; }; + +const backupsAction = () => { + setAllToFalse(); + isBackups.value = true; +}; diff --git a/packages/extension/src/ui/action/views/settings/views/settings-backups/index.vue b/packages/extension/src/ui/action/views/settings/views/settings-backups/index.vue new file mode 100644 index 000000000..defe0eb93 --- /dev/null +++ b/packages/extension/src/ui/action/views/settings/views/settings-backups/index.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/packages/extension/src/ui/action/views/settings/views/settings-general/index.vue b/packages/extension/src/ui/action/views/settings/views/settings-general/index.vue index 330a4c334..819151194 100644 --- a/packages/extension/src/ui/action/views/settings/views/settings-general/index.vue +++ b/packages/extension/src/ui/action/views/settings/views/settings-general/index.vue @@ -49,7 +49,13 @@ information is collected.

- + +
+

+ Save your current list of accounts across all networks, so you don't + need to re-generate them. +

+