diff --git a/CHANGELOG.md b/CHANGELOG.md index 916e29b..00c8cf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Manifest Version 1.4.6 + +- added support for 24 words seed phrases besides 12 words +- added templete for Degen network with assets +- added search when selecting accounts and networks +- added small memory cache layer to help with websites that spam the wallet with requests + ## Manifest Version 1.4.5 - improved gas estimation for transactions diff --git a/public/assets/chain-icons/degen.webp b/public/assets/chain-icons/degen.webp new file mode 100644 index 0000000..1237e00 Binary files /dev/null and b/public/assets/chain-icons/degen.webp differ diff --git a/src/extension/inject.ts b/src/extension/inject.ts index fc69815..a78318e 100644 --- a/src/extension/inject.ts +++ b/src/extension/inject.ts @@ -24,6 +24,9 @@ function loadEIP1193Provider(provider: any) { function announceProvider() { const info: EIP6963ProviderInfo = ProviderInfo + if(!provider.accounts) { + return + } window.dispatchEvent( new CustomEvent("eip6963:announceProvider", { detail: Object.freeze({ info, provider }), @@ -40,6 +43,7 @@ function loadEIP1193Provider(provider: any) { ); announceProvider(); + // console.info('EIP-1193 Provider loaded') } const listners = { @@ -119,7 +123,8 @@ const sendMessage = (args: RequestArguments, ping = false, from = 'request'): Pr class MetaMaskAPI { isMetaMask = true isClWallet = true - _state = {accounts: Array(1), isConnected: true, isUnlocked: true, initialized: true, isPermanentlyDisconnected: false} + accounts = [] + _state = {accounts: [], isConnected: false, isUnlocked: true, initialized: true, isPermanentlyDisconnected: false} _sentWarnings = {enable: false, experimentalMethods: false, send: false, events: {}} // Deprecated - hardcoded for now, websites should not access this directly since is deprecated for a long time chainId = "0x89" @@ -145,7 +150,7 @@ class MetaMaskAPI { _events: {}, _eventsCount: 0, _maxListeners: undefined, _middleware: Array(4) } isConnected() { - return true + return false } // for maximum compatibility since is cloning the same API @@ -374,12 +379,15 @@ const listner = function(event: any) { (eth).chainId = eventDataDataData?.chainId ?? '0x89'; (eth).selectedAddress = eventDataData?.address?.[0] ?? null; (eth).accounts = eventDataData.address?.[0] ? [eventDataData.address?.[0]] : []; + (eth)._state.accounts = (eth).accounts; (eth).isConnected = () => true; + loadEIP1193Provider(eth) } else if( listnerName === 'chainChanged' ) { (eth).networkVersion = String(parseInt(eventDataDataData ?? "0x89", 16)); (eth).chainId = eventDataData ?? '0x89'; } else if ( listnerName === 'accountsChanged' ) { (eth).accounts = eventDataData?.[0] ? [eventDataData?.[0]] : []; + (eth)._state.accounts = (eth).accounts; (eth).selectedAddress = eventDataData?.[0] ?? ''; } listners[listnerName].forEach(listner => listner(eventDataDataData)); @@ -433,8 +441,7 @@ sendMessage({ } injectWallet(); -loadEIP1193Provider(eth) - +loadEIP1193Provider(eth); // HELPERS TO CLONE METAMASK API diff --git a/src/extension/manifest.json b/src/extension/manifest.json index a2a1dae..f4447b1 100644 --- a/src/extension/manifest.json +++ b/src/extension/manifest.json @@ -3,8 +3,8 @@ "name": "__MSG_appName__", "description": "__MSG_appDesc__", "default_locale": "en", - "version": "1.4.5", - "version_name": "1.4.5", + "version": "1.4.6", + "version_name": "1.4.6", "icons": { "16": "assets/extension-icon/wallet_16.png", "32": "assets/extension-icon/wallet_32.png", diff --git a/src/extension/serviceWorker.ts b/src/extension/serviceWorker.ts index ec2e57e..089d3df 100644 --- a/src/extension/serviceWorker.ts +++ b/src/extension/serviceWorker.ts @@ -39,12 +39,14 @@ import type { RequestArguments } from '@/extension/types' import { rpcError } from '@/extension/rpcConstants' import { updatePrices } from '@/utils/gecko' import { allTemplateNets } from '@/utils/networks' +import { cyrb64Hash } from '@/utils/misc' // const METAMAKS_EXTENSION_ID = 'nkbihfbeogaeaoehlefnkodbefgpgknn' let notificationUrl: string const chainIdThrottle: { [key: string]: number } = {} +const cache = new Map() const reInjectContentScripts = async () => { const cts = chrome.runtime.getManifest().content_scripts ?? [] @@ -223,7 +225,15 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a switch (message.method) { case 'eth_call': { try { - sendResponse(await evmCall(message?.params ?? [])) + const hash = cyrb64Hash('eth_call' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = await evmCall(message?.params ?? []) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -236,6 +246,14 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a } case 'eth_getBlockByNumber': { try { + + const hash = cyrb64Hash('eth_call' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const params = message?.params?.[0] as any const block = await getBlockByNumber(params) as any const newBlock = { ...block } @@ -245,6 +263,9 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a newBlock._difficulty = numToHexStr(block.difficulty) newBlock.difficulty = block._difficulty sendResponse(newBlock) + + cache.set(hash, { time: Date.now(), data: newBlock }) + } catch (e) { sendResponse({ error: true, @@ -258,9 +279,25 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a case 'eth_getTransactionCount': { try { if (message?.params?.[1]) { - sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string, message?.params?.[1] as string)))) + const hash = cyrb64Hash('eth_getTransactionCount' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = numToHexStr(Number(await getTxCount(message?.params?.[0] as string, message?.params?.[1] as string))) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } else { - sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string)))) + const hash = cyrb64Hash('eth_getTransactionCount' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = numToHexStr(Number(await getTxCount(message?.params?.[0] as string))) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } } catch (e) { sendResponse({ @@ -274,7 +311,15 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a } case 'eth_getTransactionByHash': { try { - sendResponse(await getTxByHash(message?.params?.[0] as string)) + const hash = cyrb64Hash('eth_getTransactionByHash' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = await getTxByHash(message?.params?.[0] as string) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -287,7 +332,15 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a } case 'eth_getTransactionReceipt': { try { - sendResponse(await getTxReceipt(message?.params?.[0] as string)) + const hash = cyrb64Hash('eth_getTransactionByHash' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = await getTxReceipt(message?.params?.[0] as string) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -300,7 +353,15 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a } case 'eth_gasPrice': { try { - sendResponse(numToHexStr(BigInt(Math.trunc((await getGasPrice()).price * 1e9)))) + const hash = cyrb64Hash('eth_gasPrice' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = numToHexStr(BigInt(Math.trunc((await getGasPrice()).price * 1e9))) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -313,9 +374,16 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a } case 'eth_getBalance': { try { + const hash = cyrb64Hash('eth_getBalance' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } const balance = await getBalance() const balanceHex = numToHexStr(balance ?? 0n) sendResponse(balanceHex) + cache.set(hash, { time: Date.now(), data: balanceHex }) } catch (e) { sendResponse({ error: true, @@ -328,7 +396,15 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a } case 'eth_getCode': { try { - sendResponse(await getCode(message?.params?.[0] as string)) + const hash = cyrb64Hash('eth_getCode' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = await getCode(message?.params?.[0] as string) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -341,7 +417,15 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a } case 'eth_blockNumber': { try { - sendResponse(numToHexStr(await getBlockNumber())) + const hash = cyrb64Hash('eth_blockNumber' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } + const resp = numToHexStr(await getBlockNumber()) + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -363,14 +447,22 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a }) break } + const hash = cyrb64Hash('eth_estimateGas' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 3e3) { + sendResponse(cacheItem?.data); + break + } const gas = await estimateGas({ to: params?.to ?? '', from: params?.from ?? '', data: params?.data ?? '', value: params?.value ?? '0x0' }) + const gasHex = numToHexStr(gas ?? 0n) sendResponse(gasHex) + cache.set(hash, { time: Date.now(), data: gasHex }) } catch (err) { if (String(err).includes('UNPREDICTABLE_GAS_LIMIT')) { chrome.notifications.create({ @@ -398,7 +490,15 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a case 'eth_requestAccounts': case 'eth_accounts': { try { - sendResponse(await getSelectedAddress()) + const hash = cyrb64Hash('eth_accounts' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 3e3) { + sendResponse(cacheItem?.data); + break + } + const resp = await getSelectedAddress() + sendResponse(resp) + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -413,12 +513,20 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a case 'net_version': { try { + const hash = cyrb64Hash('eth_chainId' + JSON.stringify(message?.params)) + const cacheItem = cache.get(hash) + if (cacheItem && cacheItem?.time > Date.now() - 5e3) { + sendResponse(cacheItem?.data); + break + } const isNetVersion = message.method === 'net_version' const urlKey = await chainIdThrottleFn(message?.website ?? '') const network = await getSelectedNetwork() const chainId = network?.chainId ?? 1 - sendResponse(isNetVersion ? chainId.toString() : `0x${chainId.toString(16)}`) + const resp = isNetVersion ? chainId.toString() : `0x${chainId.toString(16)}` + sendResponse(resp) chainIdThrottle[urlKey] -= 1 + cache.set(hash, { time: Date.now(), data: resp }) } catch (e) { sendResponse({ error: true, @@ -515,7 +623,7 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a const settings = await getSettings() if (settings.encryptAfterEveryTx) { - clearPk() + await clearPk() } } catch (err) { @@ -603,7 +711,7 @@ const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: a ) const settings = await getSettings() if (settings.encryptAfterEveryTx) { - clearPk() + await clearPk() } } catch (e) { console.warn('Error: signTypedData', e) diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 9cd25ae..504456a 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -10,4 +10,23 @@ export const exportFile = (fileName: string, content: string, type = 'json') => document.body.removeChild(link) } -export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) \ No newline at end of file +export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) + +const cyrb64 = (str: string, seed = 0) => { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for(let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); + return [h2>>>0, h1>>>0]; +}; + +export const cyrb64Hash = (str: string, seed = 0) => { + const [h2, h1] = cyrb64(str, seed); + return h2.toString(36).padStart(7, '0') + h1.toString(36).padStart(7, '0'); +} \ No newline at end of file diff --git a/src/utils/networks.ts b/src/utils/networks.ts index 2b5b793..faff3bd 100644 --- a/src/utils/networks.ts +++ b/src/utils/networks.ts @@ -17,7 +17,7 @@ export const mainNets: {[key: number]: Network} = { chainId: 137, explorer: 'https://polygonscan.com', icon:'polygon.webp', - symbol: 'MATIC', + symbol: 'POL', priceId: 'matic-network' }, 100: { @@ -74,6 +74,15 @@ export const mainNets: {[key: number]: Network} = { symbol: 'ETH', priceId: 'ethereum' }, + 666666666: { + name: 'Degen', + rpc: 'https://rpc.degen.tips', + chainId: 666666666, + explorer: 'https://explorer.degen.tips/', + icon: 'degen.webp', + symbol: 'DEGEN', + priceId: 'degen' + }, } export const testNets = { diff --git a/src/views/AddAccount.vue b/src/views/AddAccount.vue index aae45c6..32a5cf6 100644 --- a/src/views/AddAccount.vue +++ b/src/views/AddAccount.vue @@ -339,8 +339,9 @@ export default defineComponent({ const extractMnemonic = () => { mnemonic.value = mnemonic.value.trim().replace(/\s+/g, " "); mnemonicIndex.value = Number(mnemonicIndex.value); + const wordCount = mnemonic.value.trim().split(" ").length; - if (mnemonic.value.split(" ").length !== 12) { + if (wordCount !== 12 && wordCount !== 24) { alertMsg.value = "Invalid mnemonic."; alertOpen.value = true; return; diff --git a/src/views/HomeTab.vue b/src/views/HomeTab.vue index a5abb76..fa3de52 100644 --- a/src/views/HomeTab.vue +++ b/src/views/HomeTab.vue @@ -187,12 +187,16 @@ Accounts + @@ -228,11 +232,15 @@ Networks + @@ -284,6 +292,7 @@ import { IonToast, IonIcon, IonAvatar, + IonSearchbar, } from "@ionic/vue"; import { getAccounts, @@ -332,9 +341,12 @@ export default defineComponent({ IonIcon, IonAvatar, GitHub, + IonSearchbar, }, setup: () => { const loading = ref(false); + const filtredAccounts = ref([]) as Ref; + const filtredNetworks = ref({}) as Ref; const accounts = ref([]) as Ref; const networks = ref({}) as Ref; const accountsModal = ref(false) as Ref; @@ -362,6 +374,8 @@ export default defineComponent({ ]).then((res) => { accounts.value = res[0]; networks.value = res[1]; + filtredAccounts.value = res[0]; + filtredNetworks.value = res[1]; selectedAccount.value = res[2]; selectedNetwork.value = res[3]; settings.value = res[4]; @@ -425,6 +439,45 @@ export default defineComponent({ loading.value = false; }; + const searchAccount = (e: any) => { + const text = e.target.value; + if (text) { + filtredAccounts.value = accounts.value.filter( + (item) => + item.name.toLowerCase().includes(text.toLowerCase()) || + item.address.toLowerCase().includes(text.toLowerCase()) + ); + } else { + filtredAccounts.value = accounts.value; + } + }; + + const searchNetwork = (e: any) => { + const text = e.target.value; + if (text) { + const filtred = Object.keys(networks.value).reduce( + (acc: Networks, key: string) => { + if ( + networks.value[Number(key)].name + .toLowerCase() + .includes(text.toLowerCase()) || + networks.value[Number(key)].rpc + .toLowerCase() + .includes(text.toLowerCase()) || + networks.value[Number(key)].chainId.toString().includes(text) + ) { + acc[Number(key)] = networks.value[Number(key)]; + } + return acc; + }, + {} as Networks + ); + filtredNetworks.value = filtred; + } else { + filtredNetworks.value = networks.value; + } + }; + return { loading, accounts, @@ -448,6 +501,10 @@ export default defineComponent({ version, goToFarcasterActions, goToPersonalSign, + filtredAccounts, + filtredNetworks, + searchAccount, + searchNetwork, }; }, });