From a4ad84e058db4078bc5115a00cbd16cddc7a6d6b Mon Sep 17 00:00:00 2001 From: Andrei O Date: Fri, 26 Jul 2024 00:01:24 +0300 Subject: [PATCH] chore: changes for 1.4.0 --- .github/bun-workflow.yaml | 1 - .github/workflows/main.yaml | 1 - CHANGELOG.md | 104 +++-- CI/index.ts | 23 +- src/components/icons/ArrowDown.vue | 22 + src/components/icons/ArrowUp.vue | 22 + src/components/icons/Bridge.vue | 22 + src/components/icons/GitHub.vue | 16 + src/extension/content.ts | 83 ++-- src/extension/inject.ts | 19 +- src/extension/manifest.json | 4 +- src/extension/serviceWorker.ts | 707 +++++++++++++++-------------- src/extension/types.ts | 61 +++ src/utils/networks.ts | 2 +- src/utils/wallet.ts | 32 +- src/views/AssetsTab.vue | 664 ++++++++------------------- src/views/HomeTab.vue | 25 +- src/views/SignMessage.vue | 21 +- src/views/SignTx.vue | 16 +- src/views/SwitchNetwork.vue | 4 +- src/views/UnlockModal.vue | 20 +- 21 files changed, 944 insertions(+), 925 deletions(-) create mode 100644 src/components/icons/ArrowDown.vue create mode 100644 src/components/icons/ArrowUp.vue create mode 100644 src/components/icons/Bridge.vue create mode 100644 src/components/icons/GitHub.vue diff --git a/.github/bun-workflow.yaml b/.github/bun-workflow.yaml index 3fc5846..d07fdac 100644 --- a/.github/bun-workflow.yaml +++ b/.github/bun-workflow.yaml @@ -1,4 +1,3 @@ -# Not supported due to compatibility issues with Bun Http2 stdlib name: Bun Main Workflow on: diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 3fc5846..d07fdac 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,4 +1,3 @@ -# Not supported due to compatibility issues with Bun Http2 stdlib name: Bun Main Workflow on: diff --git a/CHANGELOG.md b/CHANGELOG.md index 01fbeaa..0bdafb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,83 +1,95 @@ # Changelog +## Manifest Version 1.4.0 + +- added bun workflow to announce changes & new versions +- nicer display of type sign messages +- added reinjecting extension in case of context invalidation +- added button to community ERC20 Bridge +- changed the assets page to use another provider for fetching assets +- changed the display of assets to be more focused on tokens +- added Github link icon to the header of the first wallet page +- improved compatibility with non-EIP1159 networks +- minimal changes to switch network displays + ## Manifest Version 1.3.9 - add an additional throttle on 'eth_chainId' to prevent websites from spamming the wallet with requests - change inject throttle to only affect UI requests - updated some core dependencies -- optimized performance for json rpc calls -- disabled assets fetch until new provider is found before yup.io was used +- optimized performance for JSON RPC calls +- disabled assets fetch until a new provider is found before yup.io was used - simplified wallet switching - added sonarCloud badge to README.md ## Manifest Version 1.3.8 -- improved sign message display to better accomodate SIWE & other messages +- improved sign message display to better accommodate SIWE & other messages ## Manifest Version 1.3.7 - improved add Network pages -- upgraded and optimized some dependencies including vite -- optimized vite config +- upgraded and optimized some dependencies including Vite +- optimized site config - added condition to not reinject wallet if already injected for websites that reload injected scripts -- optimized throttle fuffilment of requests in case of too many requests -- removed uneeded mobile native code +- optimized throttle fulfillment of requests in case of too many requests +- removed unneeded mobile native code ## Manifest Version 1.3.6 -- better display of blockchain explorer button -- updated ethers dependency to latest 6.11.1 -- better handling of type sigining +- better display of the blockchain explorer button +- updated ethers dependency to the latest 6.11.1 +- better handling of type signing - changed the password input for unlock to not lose focus - activated focus on password input for unlock on view enter -- disabled integration of fire wallet(in cause user has it installed) with type signing due to incompatibility +- disabled integration of fire wallet(in case user has it installed) with type signing due to incompatibility - other misc improvements -- added a check when sending native token to check if internet / RPC or Blockchain and show a message to the user -- customize testNets icons to show a small dev icon on the top right corner -- updated testNets templates to include newer networks -- show icons for testNets too in most places +- added a check when sending native tokens to check if internet / RPC or Blockchain and show a message to the user +- customize test-Nets icons to show a small dev icon on the top right corner +- updated test-Nets templates to include newer networks +- show icons for test-Nets too in most places ## Manifest Version 1.3.5 -- added copy button to chainId for easier development -- added settings to be able to transfrom address to lower case when copying -- added a check in get recepit to return null if hash is missing -- added version display to wallet first page +- added copy button to ChainId for easier development +- added settings to be able to transform address to lowercase when copying +- added a check in get receipt to return null if the hash is missing +- added version display to the wallet on the first page ## Manifest Version 1.3.4 - bump fake Metamask version signature to 11.0.0 - improved compatibility with older deprecated websites - improved mimicking of Metamask API -- made the wallet compatible with fire extension on sending transaction( by mimicking new Metamask API) +- made the wallet compatible with fire extension on sending transactions ( by mimicking the new Metamask API) ## Manifest Version 1.3.3 - improved eth_call and eth_blockNumber to be more compatible with older websites - better error internal handling -- modify the receipt returned to resamble more the one from Metamask +- modify the receipt returned to resemble the one from Metamask - change some notes in about -- refactored account name edit to be more user friendly +- refactored account name edit to be more user-friendly ## Manifest Version 1.3.2 -- added button to open non kyc exchange, no referral is used to maximize privacy +- added button to navigate to non-KYC exchange, no referral is used to maximize privacy ## Manifest Version 1.3.1 -- refactored the wallet to use etheres V6 +- refactored the wallet to use ethers V6 - implemented EIP6963Provider - updated all dependencies - added ability to send native tokens - added ability to manage ABIs -- added ability to perfrom arbitrary read calls to contracts -- added ability to perfrom arbitrary write calls to contracts +- added ability to perform arbitrary read calls to contracts +- added ability to perform arbitrary write calls to contracts - added ability to save read or write calls for later use - added sandbox to be able to evaluate JS code in order to pass complex parameters to read or write calls - added base Network to templates class -- added Icon for base network +- added icon for base network - added ability to add contacts and load them in Read contract and Write and Send token pages -- added ability to paste current selected address to both webpages and insde wallet itself +- added the ability to paste the current selected address to both web pages and inside the wallet itself ## Manifest Version 1.2.8 @@ -91,15 +103,15 @@ ## Manifest Version 1.2.6 - upgrade ionic to v7 and update dependencies - + ## Manifest Version 1.2.5 -- improve post build script - +- improve post-build script + ## Manifest Version 1.2.4 -- updated showing assets page to use new api -- removed yup score from assets page +- updated showing assets page to use the new API +- removed YUP score from the assets page - change the info modal in settings ## Manifest Version 1.2.3 @@ -114,15 +126,15 @@ ## Manifest Version 1.2.1 -- added support fro eth_getTransactionCount method +- added support from eth_getTransactionCount method ## Manifest Version 1.1.9 -- added proxy in intial stub for send, request, sendAsync for better compatibility +- added proxy in initial stub for send, request, sendAsync for better compatibility ## Manifest Version 1.1.8 -- added support to extract private key from seed when adding account +- added support to extract the private key from the seed when adding an account ## Manifest Version 1.1.7 @@ -137,18 +149,18 @@ ## Manifest Version 1.1.5 -- Added multiple new multiple implementations of MetamaskAPI including request to add a network by a website -- Injecting in sync mode stub wallet to increese compatibility with websites that expect a walled defined at the lowest point of page load -- Modifing CSP requests to allow sync injecting of stub +- Added multiple new implementations of MetamaskAPI including a request to add a network by a website +- Injecting in sync mode stub wallet to increase compatibility with websites that expect a walled defined at the lowest point of page load +- Modifying CSP requests to allow sync injecting of stub - Added Web3 Shim for compatibility with older websites -- Tested new websites and TX's -- Refactoring the 10 maximum conqurent messages limit -- Added support for most of listners and improve emiting them -- Added a post buil script -- Switch the content script to load initialy without a wrapper module +- Tested new websites and TXs +- Refactoring the 10 maximum concurrent messages limit +- Added support for most of the listeners and improved emitting them +- Added a post-build script +- Switch the content script to load initially without a wrapper module -## Manifest Version: 1.1.4 +## Manifest Version 1.1.4 - Added max 10 allowed concurrent messages to the wallet to prevent abusive websites from sending too many messages. - Added explorer-button to main wallet page for easier viewing of the selected address on the blockchain explorer. -- Show the price converted in dollars also besides the native token price on transaction view for networks: 1(Ethereum), 137(Polygon), 100(Gnosis), 10(Optimism), 56(BSC), 42161(Arbitrum One) +- Show the price converted in dollars besides the native token price on transaction view for networks: 1(Ethereum), 137(Polygon), 100(Gnosis), 10(Optimism), 56(BSC), 42161(Arbitrum One) diff --git a/CI/index.ts b/CI/index.ts index 21ad9fa..88cae6e 100644 --- a/CI/index.ts +++ b/CI/index.ts @@ -59,19 +59,30 @@ const main = async () => { if(action === 'update') { const VERSION = GithubEvent.inputs.version; - const message = `Github ClearWallet new version ${VERSION} has been released!\n - ChromeStore: https://bit.ly/clw-evm \n - Github: https://github.com/andrei0x309/clear-wallet - `; - + const message = `Clear Wallet - New version ${VERSION} released! \n + ChangeLog: https://bit.ly/clw-cl \n + ChromeStore: https://bit.ly/clw-evm \n + ` if(ENABLED) { await yupAPI.sendPost({ content: message, platforms: ['twitter', 'threads', 'bsky', 'lens'] }) - await fchubUtils.createFarcasterPost({ + const fcPost = await fchubUtils.createFarcasterPost({ content: message, }) + const fcPostHash = Buffer.from(fcPost).toString('hex'); + if(fcPostHash) { + await new Promise((resolve) => setTimeout(resolve, 3000)); + const launchCasterMessage = `@launch` + + await fchubUtils.createFarcasterPost({ content: launchCasterMessage, replyTo: { + hash: fcPostHash, + fid: String(USER_FID) + } }) + + } + } else { console.log('No action required') } diff --git a/src/components/icons/ArrowDown.vue b/src/components/icons/ArrowDown.vue new file mode 100644 index 0000000..84390b6 --- /dev/null +++ b/src/components/icons/ArrowDown.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/icons/ArrowUp.vue b/src/components/icons/ArrowUp.vue new file mode 100644 index 0000000..0c71343 --- /dev/null +++ b/src/components/icons/ArrowUp.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/icons/Bridge.vue b/src/components/icons/Bridge.vue new file mode 100644 index 0000000..a8791a9 --- /dev/null +++ b/src/components/icons/Bridge.vue @@ -0,0 +1,22 @@ + diff --git a/src/components/icons/GitHub.vue b/src/components/icons/GitHub.vue new file mode 100644 index 0000000..8281999 --- /dev/null +++ b/src/components/icons/GitHub.vue @@ -0,0 +1,16 @@ + diff --git a/src/extension/content.ts b/src/extension/content.ts index 6132722..117fa63 100644 --- a/src/extension/content.ts +++ b/src/extension/content.ts @@ -46,40 +46,49 @@ window.addEventListener("message", (event) => { event.data.data.data.website = document?.location?.href ?? '' if ((event?.data?.data?.method ?? 'x') in allowedMethods) { event.data.data.data.method = event?.data?.data?.method ?? '' - chrome?.runtime?.sendMessage(event.data.data.data, (res) => { - if (chrome.runtime.lastError) { - console.warn("LOC1: Error sending message:", chrome.runtime.lastError); - } - const id = Number(event.data.resId.replace(/[A-Za-z]/g, '').slice(0, 10)) - const data = { - target: 'metamask-inpage', - type: "CLWALLET_PAGE", - resId: event.data.resId, - data: { name: 'metamask-provider', data: { - jsonrpc: '2.0', + try { + chrome?.runtime?.sendMessage(event.data.data.data, (res) => { + if (chrome.runtime.lastError) { + console.warn("LOC1: Error sending message:", chrome.runtime.lastError); + } + const id = Number(event.data.resId.replace(/[A-Za-z]/g, '').slice(0, 10)) + const data = { + target: 'metamask-inpage', + type: "CLWALLET_PAGE", + resId: event.data.resId, + data: { + name: 'metamask-provider', data: { + jsonrpc: '2.0', + id, + result: res, + }, id, - result: res, - }, - id, - method: event?.data?.data?.data?.method ?? '', - params: event?.data?.data?.data?.params ?? [], - }, - } - if(event?.data?.data?.data?.method !== 'eth_chainId') { - console.info('data out', data) - } + method: event?.data?.data?.data?.method ?? '', + params: event?.data?.data?.data?.params ?? [], + }, + } + if (event?.data?.data?.data?.method !== 'eth_chainId') { + // console.info('data out', data) + } - window.postMessage(data, "*"); - }) + window.postMessage(data, "*"); + }) + } catch (e) { + if ((e as Error)?.message === 'Extension context invalidated') { + console.info('Error: Extension context invalidated. Ignoring.'); + } + } } else { - const data = { - type: "CLWALLET_PAGE", + const data = { + type: "CLWALLET_PAGE", data: { - data: { - result: { error: true, message: 'ClearWallet: Unknown method requested ' + (event?.data?.data?.data?.method ?? '') } - } } - , resId: event.data.resId }; + data: { + result: { error: true, message: 'ClearWallet: Unknown method requested ' + (event?.data?.data?.data?.method ?? '') } + } + } + , resId: event.data.resId + }; window.postMessage(data, "*"); } } else if (event?.data?.type === "CLWALLET_PING") { @@ -87,12 +96,18 @@ window.addEventListener("message", (event) => { event.data.data.data.type = "CLWALLET_CONTENT_MSG" event.data.data.data.method = "wallet_connect" event.data.data.data.params = Array(0) - chrome.runtime.sendMessage(event.data.data.data, async (res) => { - if (chrome.runtime.lastError) { - console.warn("LOC2: Error sending message:", chrome.runtime.lastError); + try { + chrome.runtime.sendMessage(event.data.data.data, async (res) => { + if (chrome.runtime.lastError) { + console.warn("LOC2: Error sending message:", chrome.runtime.lastError); + } + window.postMessage(res, "*"); + }) + } catch (e) { + if ((e as Error)?.message === 'Extension context invalidated') { + console.info('Error: Extension context invalidated. Ignoring.'); } - window.postMessage(res, "*"); - }) + } } }); diff --git a/src/extension/inject.ts b/src/extension/inject.ts index f379f6c..fc69815 100644 --- a/src/extension/inject.ts +++ b/src/extension/inject.ts @@ -56,6 +56,7 @@ const listners = { } const promResolvers = new Map() +const UIpromResolvers = new Map() const getListnersCount = (): number => { let count = 0 @@ -75,14 +76,20 @@ const sendMessage = (args: RequestArguments, ping = false, from = 'request'): Pr return new Promise((resolve, reject) => { const p = [ "eth_signTypedData", "eth_signTypedData_v3", "eth_signTypedData_v4"] const throttledMethods = [...p, 'eth_sign', 'personal_sign', 'eth_sendTransaction'] - - if(promResolvers.size > MAX_PROMISES && throttledMethods.includes(args.method)) { + + const isThrottled = throttledMethods.includes(args.method) + + if(UIpromResolvers.size > MAX_PROMISES && isThrottled) { reject({code: -32000, message: 'ClearWallet: Too many requests', error: true }) return } const resId = [...`${Math.random().toString(16) + Date.now().toString(16)}`].slice(2).join('') - promResolvers.set(resId, { resolve, reject }) + if(isThrottled) { + UIpromResolvers.set(resId, { resolve, reject }) + } + promResolvers.set(resId, { resolve, reject }) + const method = args.method if (p.includes(args.method)) { @@ -101,7 +108,7 @@ const sendMessage = (args: RequestArguments, ping = false, from = 'request'): Pr data.type = 'CLWALLET_PING' } if(method!== 'eth_chainId') { - console.info('data in', data) + // console.info('data in', data) } window.postMessage(data, "*"); @@ -389,6 +396,10 @@ const listner = function(event: any) { if(promResolvers.has(resId)) { promResolvers.delete(resId) } + if(UIpromResolvers.has(resId)) { + UIpromResolvers.get(resId).resolve(result) + UIpromResolvers.delete(resId) + } } window.addEventListener("message",listner) diff --git a/src/extension/manifest.json b/src/extension/manifest.json index cce683a..3130607 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.3.9", - "version_name": "1.3.9", + "version": "1.4.0", + "version_name": "1.4.0", "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 3ac7c10..ec2e57e 100644 --- a/src/extension/serviceWorker.ts +++ b/src/extension/serviceWorker.ts @@ -1,37 +1,37 @@ -import { +import { CLW_CONTEXT_MENU_ID, - getSelectedAccount, - getSelectedNetwork, - smallRandomString, - getSettings, - clearPk, - openTab, - getUrl, - addToHistory, - getNetworks, - strToHex, + getSelectedAccount, + getSelectedNetwork, + smallRandomString, + getSettings, + clearPk, + openTab, + getUrl, + addToHistory, + getNetworks, + strToHex, numToHexStr, enableRightClickVote, } from '@/utils/platform'; -import { - userApprove, - userReject, - rIdWin, +import { + userApprove, + userReject, + rIdWin, rIdData, } from '@/extension/userRequest' -import { - signMsg, - getBalance, - getBlockNumber, - estimateGas, - sendTransaction, - getGasPrice, - getBlockByNumber, - evmCall, - getTxByHash, - getTxReceipt, - signTypedData, - getCode, +import { + signMsg, + getBalance, + getBlockNumber, + estimateGas, + sendTransaction, + getGasPrice, + getBlockByNumber, + evmCall, + getTxByHash, + getTxReceipt, + signTypedData, + getCode, getTxCount, getSelectedAddress } from '@/utils/wallet' @@ -44,35 +44,65 @@ import { allTemplateNets } from '@/utils/networks' let notificationUrl: string -const chainIdThrottle: {[key: string]: number} = {} +const chainIdThrottle: { [key: string]: number } = {} + +const reInjectContentScripts = async () => { + const cts = chrome.runtime.getManifest().content_scripts ?? [] + for (const cs of cts) { + const tabs = await chrome.tabs.query({ url: cs.matches }) + for (const tab of tabs) { + if (!tab?.id || !cs.js || !tab.url) { + continue; + } + if (tab.url.match(/(chrome|chrome-extension):\/\//gi)) { + continue; + } + + const isWorldMain = (cs as any)?.world === 'MAIN' + + chrome.scripting.executeScript({ + files: cs.js, + target: { tabId: tab.id, allFrames: cs.all_frames }, + injectImmediately: cs.run_at === 'document_start', + world: isWorldMain ? 'MAIN' : 'ISOLATED' + }).catch((err) => { + console.warn('Error injecting content script', err) + }) + } + } +} chrome.runtime.onInstalled.addListener(() => { enableRightClickVote() + reInjectContentScripts(); console.info('Service worker installed'); + if (chrome.runtime.lastError) { + console.warn("Whoops.. " + chrome.runtime.lastError.message); + } }) chrome.runtime.onStartup.addListener(() => { console.info('Service worker startup'); enableRightClickVote(); - if(chrome.runtime.lastError) { + if (chrome.runtime.lastError) { console.warn("Whoops.. " + chrome.runtime.lastError.message); } }) chrome.runtime.onSuspend.addListener(() => { console.info('Service worker suspend'); - if(chrome.runtime.lastError) { + if (chrome.runtime.lastError) { console.warn("Whoops.. " + chrome.runtime.lastError.message); } }) -async function pasteAddress() { - const currentAddress = (await (window as any).ethereum?.request({ - method: 'eth_accounts', - params: [] - })) - if(currentAddress.length > 0) { - document.execCommand("insertText", false, currentAddress[0]); +async function pasteAddress () { + const currentAddress = (await (window as any).ethereum?.request({ + method: 'eth_accounts', + params: [] + })) + if (currentAddress.length > 0) { + document.execCommand("insertText", false, currentAddress[0]); } } @@ -88,9 +118,9 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { func: pasteAddress }); } catch { - // igonre + // igonre } - } else if(isOwnExtension) { + } else if (isOwnExtension) { chrome.runtime.sendMessage({ method: 'paste', type: 'CLWALLET_PAGE_MSG' }, (r) => { if (chrome.runtime.lastError) { console.warn("LOC3: Error sending message:", chrome.runtime.lastError); @@ -105,23 +135,23 @@ chrome.alarms.create('updatePrices', { }) chrome.alarms.onAlarm.addListener((alarm) => { - if(alarm.name === 'updatePrices') { - updatePrices().then(() => { - console.info('Prices updated') - }).catch((err) => { - console.warn('Prices update failed', err) - }) - } - getSettings().then((settings) => { - if( ((settings.lastLock + settings.lockOutPeriod * 6e4) < Date.now()) && settings.lockOutEnabled && !settings.lockOutBlocked ) { - settings.lastLock = Date.now() - clearPk() + if (alarm.name === 'updatePrices') { + updatePrices().then(() => { + console.info('Prices updated') + }).catch((err) => { + console.warn('Prices update failed', err) + }) } - }) + getSettings().then((settings) => { + if (((settings.lastLock + settings.lockOutPeriod * 6e4) < Date.now()) && settings.lockOutEnabled && !settings.lockOutBlocked) { + settings.lastLock = Date.now() + clearPk() + } + }) }) chrome.windows.onRemoved.addListener(async (winId) => { - if (winId in (userReject ?? {})){ + if (winId in (userReject ?? {})) { userReject[winId]?.() } userReject[winId] = undefined @@ -129,9 +159,9 @@ chrome.windows.onRemoved.addListener(async (winId) => { rIdWin[winId] = undefined rIdData[winId] = undefined const wins = await chrome.windows.getAll() - if(wins.length === 0) { + if (wins.length === 0) { const s = await getSettings() - if(s.enableStorageEnctyption) { + if (s.enableStorageEnctyption) { await clearPk() } } @@ -147,7 +177,7 @@ const viewTxListner = async (id: string) => { } } -if (!chrome.notifications.onButtonClicked.hasListener(viewTxListner)){ +if (!chrome.notifications.onButtonClicked.hasListener(viewTxListner)) { chrome.notifications.onButtonClicked.addListener(viewTxListner) } @@ -159,33 +189,30 @@ const chainIdThrottleFn = async (website: string) => { } catch { urlKey = 'invalid' } - if(chainIdThrottle[urlKey] === undefined) { + if (chainIdThrottle[urlKey] === undefined) { chainIdThrottle[urlKey] = 0 } chainIdThrottle[urlKey] += 1 - if( chainIdThrottle[urlKey] > 3) { + if (chainIdThrottle[urlKey] > 6) { await new Promise((resolve, reject) => { setTimeout(() => { resolve(null) - }, 450) + }, 250) }) - // console.log('throttling', chainIdThrottle) } return urlKey } -const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: any) => any) => { +const mainListner = (message: RequestArguments, sender: any, sendResponse: (a: any) => any) => { if (chrome.runtime.lastError) { console.info("Error receiving message:", chrome.runtime.lastError); } - if(message?.type !== "CLWALLET_CONTENT_MSG") { + if (message?.type !== "CLWALLET_CONTENT_MSG") { return true } - - (async () => { - // console.info('Message:', message) + (async () => { if (!(message?.method)) { sendResponse({ code: 500, @@ -196,7 +223,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an switch (message.method) { case 'eth_call': { try { - sendResponse(await evmCall(message?.params ?? [])) + sendResponse(await evmCall(message?.params ?? [])) } catch (e) { sendResponse({ error: true, @@ -209,15 +236,15 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } case 'eth_getBlockByNumber': { try { - const params = message?.params?.[0] as any - const block = await getBlockByNumber(params) as any - const newBlock = {...block} - newBlock.gasLimit = numToHexStr(block.gasLimit) - newBlock.gasUsed = numToHexStr(block.gasUsed) - newBlock.baseFeePerGas = numToHexStr(block.baseFeePerGas) - newBlock._difficulty = numToHexStr(block.difficulty) - newBlock.difficulty = block._difficulty - sendResponse(newBlock) + const params = message?.params?.[0] as any + const block = await getBlockByNumber(params) as any + const newBlock = { ...block } + newBlock.gasLimit = numToHexStr(block.gasLimit) + newBlock.gasUsed = numToHexStr(block.gasUsed) + newBlock.baseFeePerGas = numToHexStr(block.baseFeePerGas) + newBlock._difficulty = numToHexStr(block.difficulty) + newBlock.difficulty = block._difficulty + sendResponse(newBlock) } catch (e) { sendResponse({ error: true, @@ -230,11 +257,11 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } case 'eth_getTransactionCount': { try { - if(message?.params?.[1]) { - sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string, message?.params?.[1] as string)))) - }else { - sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string)))) - } + if (message?.params?.[1]) { + sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string, message?.params?.[1] as string)))) + } else { + sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string)))) + } } catch (e) { sendResponse({ error: true, @@ -247,7 +274,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } case 'eth_getTransactionByHash': { try { - sendResponse(await getTxByHash(message?.params?.[0] as string)) + sendResponse(await getTxByHash(message?.params?.[0] as string)) } catch (e) { sendResponse({ error: true, @@ -258,9 +285,9 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } break } - case 'eth_getTransactionReceipt':{ + case 'eth_getTransactionReceipt': { try { - sendResponse(await getTxReceipt(message?.params?.[0] as string)) + sendResponse(await getTxReceipt(message?.params?.[0] as string)) } catch (e) { sendResponse({ error: true, @@ -273,8 +300,8 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } case 'eth_gasPrice': { try { - sendResponse(numToHexStr(BigInt(Math.trunc(await getGasPrice() * 1e9)))) - } catch(e) { + sendResponse(numToHexStr(BigInt(Math.trunc((await getGasPrice()).price * 1e9)))) + } catch (e) { sendResponse({ error: true, code: rpcError.USER_REJECTED, @@ -286,9 +313,9 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } case 'eth_getBalance': { try { - const balance = await getBalance() - const balanceHex = numToHexStr(balance ?? 0n) - sendResponse(balanceHex) + const balance = await getBalance() + const balanceHex = numToHexStr(balance ?? 0n) + sendResponse(balanceHex) } catch (e) { sendResponse({ error: true, @@ -301,7 +328,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } case 'eth_getCode': { try { - sendResponse(await getCode(message?.params?.[0] as string)) + sendResponse(await getCode(message?.params?.[0] as string)) } catch (e) { sendResponse({ error: true, @@ -314,7 +341,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an } case 'eth_blockNumber': { try { - sendResponse(numToHexStr(await getBlockNumber())) + sendResponse(numToHexStr(await getBlockNumber())) } catch (e) { sendResponse({ error: true, @@ -323,89 +350,89 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an }) console.warn('Error: eth_blockNumber', e) } - break + break } case 'eth_estimateGas': { - try { - const params = message?.params?.[0] as any - if(!params) { - sendResponse({ - error: true, - code: rpcError.INVALID_PARAM, - message: 'Invalid param for gas estimate' + try { + const params = message?.params?.[0] as any + if (!params) { + sendResponse({ + error: true, + code: rpcError.INVALID_PARAM, + message: 'Invalid param for gas estimate' + }) + break + } + const gas = await estimateGas({ + to: params?.to ?? '', + from: params?.from ?? '', + data: params?.data ?? '', + value: params?.value ?? '0x0' }) - break + const gasHex = numToHexStr(gas ?? 0n) + sendResponse(gasHex) + } catch (err) { + if (String(err).includes('UNPREDICTABLE_GAS_LIMIT')) { + chrome.notifications.create({ + message: 'Gas estimate failed likely due to to many decimals substract 0.00001 form the value you have inpputed and try again.', + title: 'Error', + iconUrl: getUrl('assets/extension-icon/wallet_128.png'), + type: 'basic' + } as any) + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'Gas estimate failed' + }) + } else { + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'No network or user selected' + }) + console.warn('Error: eth_estimateGas', err) + } } - const gas = await estimateGas({ - to: params?.to ?? '', - from: params?.from ?? '', - data: params?.data ?? '', - value: params?.value ?? '0x0' - }) - const gasHex = numToHexStr(gas ?? 0n) - sendResponse(gasHex) - } catch(err) { - if(String(err).includes('UNPREDICTABLE_GAS_LIMIT')) { - chrome.notifications.create({ - message: 'Gas estimate failed likely due to to many decimals substract 0.00001 form the value you have inpputed and try again.', - title: 'Error', - iconUrl: getUrl('assets/extension-icon/wallet_128.png'), - type: 'basic' - } as any) - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'Gas estimate failed' - }) - } else { - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'No network or user selected' - }) - console.warn('Error: eth_estimateGas', err) - } - } - break + break } case 'eth_requestAccounts': case 'eth_accounts': { - try { - sendResponse(await getSelectedAddress()) - } catch (e) { - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'No network or user selected' - }) - console.warn('Error: eth_accounts', e) - } - break + try { + sendResponse(await getSelectedAddress()) + } catch (e) { + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'No network or user selected' + }) + console.warn('Error: eth_accounts', e) + } + break } case 'eth_chainId': - case 'net_version': - { - try { - 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)}`) - chainIdThrottle[urlKey] -= 1 - } catch (e) { - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'No network or user selected' - }) - console.warn('Error: eth_chainId', e) - } - break - } + case 'net_version': + { + try { + 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)}`) + chainIdThrottle[urlKey] -= 1 + } catch (e) { + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'No network or user selected' + }) + console.warn('Error: eth_chainId', e) + } + break + } case 'eth_sendTransaction': { try { const params = message?.params?.[0] as any - if(!params) { + if (!params) { sendResponse({ error: true, code: rpcError.INVALID_PARAM, @@ -414,7 +441,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an break } const [account, network] = await Promise.all([getSelectedAccount(), getSelectedNetwork()]) - if(!account || !('address' in account)) { + if (!account || !('address' in account)) { await chrome.windows.create({ height: 450, width: 400, @@ -423,7 +450,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an }) return } - if(!network || !('chainId' in network)) { + if (!network || !('chainId' in network)) { await chrome.windows.create({ height: 450, width: 400, @@ -436,62 +463,63 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an const serializeParams = strToHex(JSON.stringify(params)) ?? '' let gWin: any await new Promise((resolve, reject) => { - chrome.windows.create({ - height: 450, - width: 400, - url: chrome.runtime.getURL(`index.html?route=sign-tx¶m=${serializeParams}&rid=${String(message?.resId ?? '')}`), - type: 'popup' - }).then((win) => { - gWin = win - userReject[String(win.id)] = reject - userApprove[String(win.id)] = resolve - rIdWin[String(win.id)] = String(message.resId) - rIdData[String(win.id)] = {} - }) + chrome.windows.create({ + height: 450, + width: 400, + url: chrome.runtime.getURL(`index.html?route=sign-tx¶m=${serializeParams}&rid=${String(message?.resId ?? '')}`), + type: 'popup' + }).then((win) => { + gWin = win + userReject[String(win.id)] = reject + userApprove[String(win.id)] = resolve + rIdWin[String(win.id)] = String(message.resId) + rIdData[String(win.id)] = {} + }) }) try { - // console.log('waiting for user to approve or reject') - // console.log(rIdData?.[String(gWin?.id ?? 0)]) - const tx = await sendTransaction({...params, ...(rIdData?.[String(gWin?.id ?? 0)] ?? {}) } ) - sendResponse(tx.hash) - const buttons = {} as any - const network = await getSelectedNetwork() - addToHistory({ - date: Date.now(), - txHash: tx.hash, - chainId: network.chainId, - ...(network.explorer ? {txUrl: `${network.explorer}/tx/${tx.hash}`.replace('//', '/') } : {}), - webiste: (message?.website) - }) - const notificationId = crypto.randomUUID() - if(network?.explorer) { - notificationUrl = `${network.explorer}/tx/${tx.hash}`.replace('//', '/') - buttons.buttons = [{ - title: 'View Transaction', - }] - setTimeout(() => { - try { - chrome.notifications.clear(notificationId) - } catch { - // ignore - } - }, 6e4) - } - chrome.notifications.create(notificationId,{ - message: 'Transaction Confirmed', - title: 'Success', - iconUrl: getUrl('assets/extension-icon/wallet_128.png'), - type: 'basic', - ...(buttons) - } as any) + console.log('waiting for user to approve or reject') + console.log(rIdData?.[String(gWin?.id ?? 0)]) + const tx = await sendTransaction({ ...params, ...(rIdData?.[String(gWin?.id ?? 0)] ?? {}) }) + sendResponse(tx.hash) + const buttons = {} as any + const network = await getSelectedNetwork() + addToHistory({ + date: Date.now(), + txHash: tx.hash, + chainId: network.chainId, + ...(network.explorer ? { txUrl: `${network.explorer}/tx/${tx.hash}`.replace('//', '/') } : {}), + webiste: (message?.website) + }) + const notificationId = crypto.randomUUID() + if (network?.explorer) { + notificationUrl = `${network.explorer}/tx/${tx.hash}`.replace('//', '/') + buttons.buttons = [{ + title: 'View Transaction', + }] + setTimeout(() => { + try { + chrome.notifications.clear(notificationId) + } catch { + // ignore + } + }, 6e4) + } + chrome.notifications.create(notificationId, { + message: 'Transaction Confirmed', + title: 'Success', + iconUrl: getUrl('assets/extension-icon/wallet_128.png'), + type: 'basic', + ...(buttons) + } as any) - const settings = await getSettings() - if(settings.encryptAfterEveryTx) { - clearPk() - } + const settings = await getSettings() + if (settings.encryptAfterEveryTx) { + clearPk() + } } catch (err) { + console.info('Error: eth_sendTransaction', err) sendResponse({ error: true, code: rpcError.USER_REJECTED, @@ -510,73 +538,73 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an type: 'basic' } as any) } - } catch(err) { - console.warn('Error: eth_sendTransaction', err) - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'User Rejected Signature' - }) - } + } catch (err) { + console.warn('Error: eth_sendTransaction', err) + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'User Rejected Signature' + }) + } break } case 'signTypedData': case 'eth_signTypedData': case 'signTypedData_v1': case 'eth_signTypedData_v1': - case 'signTypedData_v3': + case 'signTypedData_v3': case 'eth_signTypedData_v3': case 'signTypedData_v4': case 'eth_signTypedData_v4': case 'personal_sign': case 'eth_sign': { try { - - const account = await getSelectedAccount() - - if(!account || !('address' in account)) { - await chrome.windows.create({ - height: 450, - width: 400, - url: chrome.runtime.getURL(`index.html?route=wallet-error¶m=${strToHex('No account is selected you need to have an account selected before trying sign a message')}&rid=${String(message?.resId ?? '')}`), - type: 'popup' + + const account = await getSelectedAccount() + + if (!account || !('address' in account)) { + await chrome.windows.create({ + height: 450, + width: 400, + url: chrome.runtime.getURL(`index.html?route=wallet-error¶m=${strToHex('No account is selected you need to have an account selected before trying sign a message')}&rid=${String(message?.resId ?? '')}`), + type: 'popup' + }) + return + } + + const isTypedSigned = [ + 'signTypedData', + 'eth_signTypedData', + 'signTypedData_v1', + 'eth_signTypedData_v1', + 'signTypedData_v3', + 'eth_signTypedData_v3', + 'signTypedData_v4', + 'eth_signTypedData_v4'].includes(message?.method); + const signMsgData = isTypedSigned ? String(message?.params?.[1] ?? '') : String(message?.params?.[0] ?? ''); + + await new Promise((resolve, reject) => { + chrome.windows.create({ + height: 510, + width: 480, + url: chrome.runtime.getURL(`index.html?route=sign-msg¶m=${strToHex(signMsgData)}&rid=${String(message?.resId ?? '')}`), + type: 'popup' + }).then((win) => { + userReject[String(win.id)] = reject + userApprove[String(win.id)] = resolve + rIdWin[String(win.id)] = String(message.resId) + }) + }) - return - } - - const isTypedSigned = [ - 'signTypedData', - 'eth_signTypedData', - 'signTypedData_v1', - 'eth_signTypedData_v1', - 'signTypedData_v3', - 'eth_signTypedData_v3', - 'signTypedData_v4', - 'eth_signTypedData_v4'].includes(message?.method); - const signMsgData = isTypedSigned ? String(message?.params?.[1] ?? '' ) : String(message?.params?.[0] ?? '' ); - - await new Promise((resolve, reject) => { - chrome.windows.create({ - height: 510, - width: 480, - url: chrome.runtime.getURL(`index.html?route=sign-msg¶m=${strToHex(signMsgData)}&rid=${String(message?.resId ?? '')}`), - type: 'popup' - }).then((win) => { - userReject[String(win.id)] = reject - userApprove[String(win.id)] = resolve - rIdWin[String(win.id)] = String(message.resId) - }) - - }) - sendResponse( - isTypedSigned ? - await signTypedData(signMsgData): - await signMsg(signMsgData) - ) - const settings = await getSettings() - if(settings.encryptAfterEveryTx) { - clearPk() - } + sendResponse( + isTypedSigned ? + await signTypedData(signMsgData) : + await signMsg(signMsgData) + ) + const settings = await getSettings() + if (settings.encryptAfterEveryTx) { + clearPk() + } } catch (e) { console.warn('Error: signTypedData', e) sendResponse({ @@ -609,9 +637,9 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an sendResponse([{ id: smallRandomString(21), parentCapability: 'eth_accounts', - invoker: message?.website?.split('/').slice(0,3).join('/') ?? '', + invoker: message?.website?.split('/').slice(0, 3).join('/') ?? '', caveats: [{ - type:'restrictReturnedAccounts', + type: 'restrictReturnedAccounts', value: address }], date: Date.now(), @@ -633,127 +661,130 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an case 'wallet_switchEthereumChain': { try { const currentChainId = `0x${((await getSelectedNetwork())?.chainId ?? 0).toString(16)}` - if(currentChainId === String(message?.params?.[0]?.chainId ?? '' )) { + if (currentChainId === String(message?.params?.[0]?.chainId ?? '')) { sendResponse(null) - }else { - await new Promise((resolve, reject) => { - chrome.windows.create({ - height: 450, - width: 400, - url: chrome.runtime.getURL(`index.html?route=switch-network¶m=${String(message?.params?.[0]?.chainId ?? '' )}&rid=${String(message?.resId ?? '')}`), - type: 'popup' - }).then((win) => { - userReject[String(win.id)] = reject - userApprove[String(win.id)] = resolve - rIdWin[String(win.id)] = String(message.resId) - }) - }) - sendResponse(null) - } - } catch { - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'User Rejected chain switch' + } else { + await new Promise((resolve, reject) => { + chrome.windows.create({ + height: 450, + width: 400, + url: chrome.runtime.getURL(`index.html?route=switch-network¶m=${String(message?.params?.[0]?.chainId ?? '')}&rid=${String(message?.resId ?? '')}`), + type: 'popup' + }).then((win) => { + userReject[String(win.id)] = reject + userApprove[String(win.id)] = resolve + rIdWin[String(win.id)] = String(message.resId) + }) }) + sendResponse(null) } + } catch { + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'User Rejected chain switch' + }) + } break } case 'wallet_addEthereumChain': { const userNetworks = await getNetworks() - const networks = {...allTemplateNets, ...userNetworks} + const networks = { ...allTemplateNets, ...userNetworks } const chainId = Number(message?.params?.[0]?.chainId ?? '0') - if(!chainId) { + if (!chainId) { sendResponse({ error: true, code: rpcError.USER_REJECTED, message: 'Invalid Network' }) } - if( chainId in networks ) { - mainListner({...message, method:'wallet_switchEthereumChain', params: [{ - chainId: `0x${(chainId).toString(16)}` - }] }, sender, sendResponse) + if (chainId in networks) { + mainListner({ + ...message, method: 'wallet_switchEthereumChain', params: [{ + chainId: `0x${(chainId).toString(16)}` + }] + }, sender, sendResponse) } else { - if ( !message?.params?.[0]?.chainId || - !message?.params?.[0]?.chainName || - !message?.params?.[0]?.rpcUrls || - !message?.params?.[0]?.blockExplorerUrls || - !message?.params?.[0]?.nativeCurrency?.symbol - ){ - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'Invalid Network params chainId, chainName, rpcUrls, blockExplorerUrls, and nativeCurrency are required' - }) - }else { - try { - await new Promise((resolve, reject) => { + if (!message?.params?.[0]?.chainId || + !message?.params?.[0]?.chainName || + !message?.params?.[0]?.rpcUrls || + !message?.params?.[0]?.blockExplorerUrls || + !message?.params?.[0]?.nativeCurrency?.symbol + ) { + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'Invalid Network params chainId, chainName, rpcUrls, blockExplorerUrls, and nativeCurrency are required' + }) + } else { + try { + await new Promise((resolve, reject) => { chrome.windows.create({ height: 450, width: 400, - url: chrome.runtime.getURL(`index.html?route=request-network¶m=${strToHex(JSON.stringify({...{website: message?.website ?? ''}, ...(message?.params?.[0] ?? {})}) ?? '')}&rid=${String(message?.resId ?? '')}`), + url: chrome.runtime.getURL(`index.html?route=request-network¶m=${strToHex(JSON.stringify({ ...{ website: message?.website ?? '' }, ...(message?.params?.[0] ?? {}) }) ?? '')}&rid=${String(message?.resId ?? '')}`), type: 'popup' }).then((win) => { userReject[String(win.id)] = reject userApprove[String(win.id)] = resolve rIdWin[String(win.id)] = String(message.resId) }) - }) - sendResponse(null) - } catch (err) { - console.error('err') - sendResponse({ - error: true, - code: rpcError.USER_REJECTED, - message: 'User Rejected adding network' - }) - } + }) + sendResponse(null) + } catch (err) { + console.error('err') + sendResponse({ + error: true, + code: rpcError.USER_REJECTED, + message: 'User Rejected adding network' + }) } + } } break } // internal messeges case 'wallet_connect': { - const pNetwork = getSelectedNetwork() - const pAccount = getSelectedAccount() + const pNetwork = getSelectedNetwork() + const pAccount = getSelectedAccount() const [network, account] = await Promise.all([pNetwork, pAccount]) const address = account?.address ? [account?.address] : [] const chainId = `0x${(network?.chainId ?? 0).toString(16)}` - const data = { + const data = { type: "CLWALLET_PAGE_LISTENER", data: { - listner: 'connect', - data: { - chainId - }, - address - }}; + listner: 'connect', + data: { + chainId + }, + address + } + }; sendResponse(data) break } case 'wallet_approve': { - if(String(sender.tab?.windowId) in rIdWin){ + if (String(sender.tab?.windowId) in rIdWin) { userApprove[String(sender.tab?.windowId)]?.(true) } try { chrome.windows.remove(sender.tab?.windowId ?? 0) - }catch (e) { + } catch (e) { console.info(e) // ignore } break } case 'wallet_send_data': { - if(String(sender.tab?.windowId) in rIdData){ + if (String(sender.tab?.windowId) in rIdData) { const intData = rIdData[String(sender?.tab?.windowId ?? '')] ?? {} - rIdData[String(sender?.tab?.windowId ?? '')] = {...intData, ...(message?.data ?? {})} + rIdData[String(sender?.tab?.windowId ?? '')] = { ...intData, ...(message?.data ?? {}) } sendResponse(true) } break } case 'wallet_get_data': { - if(String(sender.tab?.windowId) in rIdData){ - sendResponse( rIdData[String(sender?.tab?.windowId ?? '')] ?? {}) + if (String(sender.tab?.windowId) in rIdData) { + sendResponse(rIdData[String(sender?.tab?.windowId ?? '')] ?? {}) } break } @@ -765,13 +796,13 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an sendResponse({ error: true, code: rpcError.INVALID_PARAM, - message: 'ClearWallet: Invalid request method ' + (message?.method ?? '') + message: 'ClearWallet: Invalid request method ' + (message?.method ?? '') }) break } } } - + } )(); return true; diff --git a/src/extension/types.ts b/src/extension/types.ts index 3198ec2..37eddc0 100644 --- a/src/extension/types.ts +++ b/src/extension/types.ts @@ -81,3 +81,64 @@ export interface ContractAction { export interface ContractActions { [key: string] : ContractAction } + +export interface UniSwapPortfolioResponse { + data: { + portfolios: { + id: string; + tokenBalances: { + id: string; + quantity: number; + denominatedValue: { + value: number; + } + token: { + id: string; + address: string; + chain: string; + symbol: string; + name: string; + decimals: number; + standard: string; + project: { + id: string; + name: string; + logo: null; + safetyLevel: string; + logoUrl: null; + isSpam: boolean; + __typename: string; + }; + __typename: string; + }; + tokenProjectMarket: { + id: string; + pricePercentChange: null; + tokenProject: { + id: string; + logoUrl: null; + isSpam: boolean; + __typename: string; + }; + __typename: string; + }; + __typename: string; + }[]; + __typename: string; + tokensTotalDenominatedValue: { + id: string; + value: number; + } + tokensTotalDenominatedValueChange: { + absolute: { + id: string; + value: number; + } + percentage: { + id: string; + value: number; + } + } + }[]; + }; +} \ No newline at end of file diff --git a/src/utils/networks.ts b/src/utils/networks.ts index 9d1e64c..83a5057 100644 --- a/src/utils/networks.ts +++ b/src/utils/networks.ts @@ -40,7 +40,7 @@ export const mainNets: {[key: number]: Network} = { }, 56: { name: 'BSC Main', - rpc: 'https://bsc-dataseed2.binance.org', + rpc: 'https://bsc-dataseed1.binance.org', chainId: 56, explorer: 'https://bscscan.com', icon: 'binance.webp', diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index eb820ab..3e583d2 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -72,7 +72,10 @@ export const getGasPrice = async () => { const { provider } = await getCurrentProvider() const feed = await provider.getFeeData() const gasPrice = feed.maxFeePerGas ?? feed.gasPrice ?? 0n - return Number(gasPrice) / 1e9 + return { + price: Number(gasPrice) / 1e9, + feed + } } export const getBlockNumber = async () => { @@ -152,8 +155,8 @@ export const getRandomPk = () => { return ethers.Wallet.createRandom().privateKey } -export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', value='', gasPrice='0x0'}: -{to: string, from: string, data: string, value: string, gas: string, gasPrice: string}) => { +export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', value='', gasPrice='0x0', supportsEIP1559=true}: +{to: string, from: string, data: string, value: string, gas: string, gasPrice: string, supportsEIP1559: boolean}) => { const account = await getSelectedAccount() const { provider } = await getCurrentProvider() const wallet = new ethers.Wallet(account.pk, provider) @@ -163,7 +166,7 @@ export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', val if(gas === '0x0' || gasPrice === '0x0') { throw new Error('No gas estimate available') } - return await wallet.sendTransaction({ + return supportsEIP1559 ? await wallet.sendTransaction({ to, from, data: data ? data : null, @@ -171,15 +174,24 @@ export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', val gasLimit: gasInt, gasPrice: null, maxFeePerGas: gasPriceInt, + }) : + await wallet.sendTransaction({ + to, + from, + data: data ? data : null, + value: value ? value : null, + gasLimit: gasInt, + gasPrice: gasPriceInt }) } -export const formatBalance = (balance: string) => { - Intl.NumberFormat('en-US', { - notation: 'compact', - maximumFractionDigits: 6 - }).format(Number(ethers.parseEther(balance))) -} +export const formatNumber = (num: number, digits = 0) => { + return Intl.NumberFormat('en-US', { + notation: 'compact', + maximumFractionDigits: digits + }).format(num) + } + export const getSelectedAddress = async () => { // give only the selected address for better privacy diff --git a/src/views/AssetsTab.vue b/src/views/AssetsTab.vue index 071d902..3991122 100644 --- a/src/views/AssetsTab.vue +++ b/src/views/AssetsTab.vue @@ -24,147 +24,96 @@ > - Assests for Account: {{ selectedAccount?.name }} + Assets for: {{ selectedAccount?.name }}

{{ selectedAccount?.address }}

+ + + Total Value:  + + {{ formatNumber(assetsValue?.value, 2) }} $ + 24H Change:  + + + + {{ formatNumber(assetsChange.percentage.value, 2) }}% + + + +
+ +
+
@@ -188,35 +137,13 @@ import { IonLoading, IonIcon, } from "@ionic/vue"; -import { getSelectedAccount, copyText, getUrl } from "@/utils/platform"; -import type { Account } from "@/extension/types"; - +import { getSelectedAccount, copyText, getUrl, openTab } from "@/utils/platform"; +import type { Account, UniSwapPortfolioResponse } from "@/extension/types"; +import { formatNumber } from "@/utils/wallet"; +import ArrowDown from "@/components/icons/ArrowDown.vue"; +import ArrowUp from "@/components/icons/ArrowUp.vue"; import { copyOutline } from "ionicons/icons"; - -interface IProfileToken { - address: string; - balance: number; - image: string; - name: string; - symbol: string; -} - -interface IProfileNFT { - address: string; - collectionImageURI: string; - collectionName: string; - imageURI: string; - link: string; - tokenId: number; -} - -interface IProfilePOAP { - description: string; - eventId: string; - image: string; - link: string; - title: string; -} +import BridgeIcon from "@/components/icons/Bridge.vue"; export default defineComponent({ components: { @@ -233,6 +160,9 @@ export default defineComponent({ IonToast, IonLoading, IonIcon, + ArrowDown, + ArrowUp, + BridgeIcon, }, setup: () => { const selectedAccount = ref({}) as Ref; @@ -240,328 +170,103 @@ export default defineComponent({ const isError = ref(false); const noAssets = ref(false); const toastState = ref(false); - const ethTokens = ref([]) as Ref; - const polyTokens = ref([]) as Ref; - const ethNfts = ref([]) as Ref; - const polyNfts = ref([]) as Ref; - const poaps = ref([]) as Ref; - const hasMore = reactive({ - poaps: true, - ethTokens: true, - polyTokens: true, - ethNfts: true, - polyNfts: true, - }); const getToastRef = () => toastState; + const alltokens = ref({}) as Ref< + UniSwapPortfolioResponse["data"]["portfolios"][0]["tokenBalances"] + >; + const shownTokens = ref([]) as Ref< + UniSwapPortfolioResponse["data"]["portfolios"][0]["tokenBalances"] + >; - const resources = ["nfts", "poaps", "tokens"]; - const chains = ["ethereum", "polygon"]; + const assetsValue = ref({}) as Ref< + UniSwapPortfolioResponse["data"]["portfolios"][0]["tokensTotalDenominatedValue"] + >; - const fetchFromWallet = async ({ - apiBase = "https://api.yup.io", - address, - resource = resources[0], - chain = chains[0], - start = 0, - limit = 10, - }: { - apiBase?: string; - address: string; - resource?: string; - chain?: string; - start?: number; - limit?: number; - }) => { - try { - const res = await fetch( - `${apiBase}/web3-profiles/${resource}/${address}?chain=${chain}&start=${start}&limit=${limit}` - ); - const req = await res.json(); - if (res.ok) { - return req; - } else { - return null; - } - } catch (error) { - console.info("ERROR: Failed to fetch web3 profiles", error); + const assetsChange = ref({}) as Ref< + UniSwapPortfolioResponse["data"]["portfolios"][0]["tokensTotalDenominatedValueChange"] + >; + + const UNISWAP_API_ENDPOINT = "https://interface.gateway.uniswap.org/v1/graphql"; + + const getUniwapAssets = async (ownerAddress: string) => { + const headers = { + "user-agent": "Desktop", + accept: "*/*", + "accept-encoding": "gzip, deflate, br", + "accept-language": "en-US,en;q=0.9", + "content-type": "application/json", + dnt: "1", + "Content-Type": "application/json", + origin: "https://app.uniswap.org", + referer: "https://app.uniswap.org/", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-site", + "sec-gpc": "1", + }; + + const query = { + operationName: "PortfolioBalancesWeb", + variables: { + includeSmallBalances: false, + includeSpamTokens: false, + ownerAddress, + chains: [ + "ETHEREUM", + "OPTIMISM", + "BNB", + "POLYGON", + "ZKSYNC", + "BASE", + "ARBITRUM", + "CELO", + "AVALANCHE", + "BLAST", + "ZORA", + ], + }, + query: + "query PortfolioBalancesWeb($ownerAddress: String!, $chains: [Chain!]!, $includeSmallBalances: Boolean = false, $includeSpamTokens: Boolean = false) {\n portfolios(\n ownerAddresses: [$ownerAddress]\n chains: $chains\n valueModifiers: [{ownerAddress: $ownerAddress, includeSpamTokens: $includeSpamTokens, includeSmallBalances: $includeSmallBalances}]\n ) {\n id\n tokensTotalDenominatedValue {\n id\n value\n __typename\n }\n tokensTotalDenominatedValueChange(duration: DAY) {\n absolute {\n id\n value\n __typename\n }\n percentage {\n id\n value\n __typename\n }\n __typename\n }\n tokenBalances {\n ...PortfolioTokenBalanceParts\n __typename\n }\n __typename\n }\n}\n\nfragment PortfolioTokenBalanceParts on TokenBalance {\n id\n quantity\n denominatedValue {\n id\n currency\n value\n __typename\n }\n token {\n ...SimpleTokenDetails\n id\n address\n chain\n symbol\n name\n decimals\n standard\n project {\n id\n name\n logo {\n id\n url\n __typename\n }\n safetyLevel\n logoUrl\n isSpam\n __typename\n }\n __typename\n }\n tokenProjectMarket {\n id\n pricePercentChange(duration: DAY) {\n id\n value\n __typename\n }\n tokenProject {\n id\n logoUrl\n isSpam\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment SimpleTokenDetails on Token {\n id\n address\n chain\n symbol\n name\n decimals\n standard\n project {\n id\n name\n logo {\n id\n url\n __typename\n }\n safetyLevel\n logoUrl\n isSpam\n __typename\n }\n __typename\n}", + }; + + const req = await fetch(UNISWAP_API_ENDPOINT, { + method: "POST", + headers, + body: JSON.stringify(query), + }); + + if (!req.ok) { return null; } - }; - const walletLoadArgs = { - address: "", - start: 0, - limit: 11, - res: resources, - ch: chains, - apiBase: "https://api.yup.io", - }; - - const getProfileWallet = async ({ - address, - start, - limit, - res, - ch, - apiBase, - }: { - address: string; - start: number; - limit: number; - res: string[]; - ch: string[]; - apiBase: string; - }) => { - const r = { - poaps: [] as IProfilePOAP[], - ethNfts: [] as IProfileNFT[], - polyNfts: [] as IProfileNFT[], - ethTokens: [] as IProfileToken[], - polyTokens: [] as IProfileToken[], - }; - try { - const promises = []; - - if (res.includes("poaps")) { - promises.push( - fetchFromWallet({ - apiBase, - address, - start, - limit, - resource: "poaps", - chain: "ethereum", - }).then((rz) => { - r.poaps = rz ?? []; - }) - ); - } - if (res.includes("nfts")) { - if (ch.includes("ethereum")) { - promises.push( - fetchFromWallet({ - apiBase, - address, - start, - limit, - resource: "nfts", - chain: "ethereum", - }).then((rz) => { - r.ethNfts = rz ?? []; - }) - ); - } - if (ch.includes("polygon")) { - promises.push( - fetchFromWallet({ - apiBase, - address, - start, - limit, - resource: "nfts", - chain: "polygon", - }).then((rz) => { - r.polyNfts = rz ?? []; - }) - ); - } - } - if (res.includes("tokens")) { - if (ch.includes("ethereum")) { - promises.push( - fetchFromWallet({ - apiBase, - address, - start, - limit, - resource: "tokens", - chain: "ethereum", - }).then((rz) => { - r.ethTokens = rz ?? []; - }) - ); - } - if (ch.includes("polygon")) { - promises.push( - fetchFromWallet({ - apiBase, - address, - start, - limit, - resource: "tokens", - chain: "polygon", - }).then((rz) => { - r.polyTokens = rz ?? []; - }) - ); - } - } - - await Promise.all(promises); - return r; - } catch { - return r; - } + const res = await req.json(); + return res as UniSwapPortfolioResponse; }; onIonViewWillEnter(async () => { selectedAccount.value = await getSelectedAccount(); - walletLoadArgs.address = selectedAccount.value.address; - const r = await getProfileWallet(walletLoadArgs); - ethNfts.value = r.ethNfts.slice(0, 10); - if (r.ethNfts.length !== walletLoadArgs.limit) { - hasMore.ethNfts = false; + const result = await getUniwapAssets(selectedAccount.value.address); + + if (!result) { + isError.value = true; + loading.value = false; + return; } - polyNfts.value = r.polyNfts.slice(0, 10); - if (r.polyNfts.length !== walletLoadArgs.limit) { - hasMore.polyNfts = false; + + if (result?.data?.portfolios?.length) { + alltokens.value = result.data.portfolios[0].tokenBalances.filter( + (token) => token.denominatedValue && !token.token.project.isSpam + ); + shownTokens.value = alltokens.value.slice(0, 10); + assetsValue.value = result.data.portfolios[0].tokensTotalDenominatedValue; + assetsChange.value = result.data.portfolios[0].tokensTotalDenominatedValueChange; + } else { + noAssets.value = true; } - ethTokens.value = r.ethTokens.slice(0, 10); - if (r.ethTokens.length !== walletLoadArgs.limit) { - hasMore.ethTokens = false; - } - polyTokens.value = r.polyTokens.slice(0, 10); - if (r.polyTokens.length !== walletLoadArgs.limit) { - hasMore.polyTokens = false; - } - poaps.value = r.poaps.slice(0, -1); - if (r.poaps.length !== walletLoadArgs.limit) { - hasMore.poaps = false; - } - noAssets.value = - poaps.value.length && - ethNfts.value.length && - polyNfts.value.length && - ethTokens.value.length && - polyTokens.value.length - ? false - : true; + loading.value = false; - - // const req = await fetch(`${yupAssetsApi}/${selectedAccount.value.address}`); - // if (req.ok) { - // assets.value = (await req.json()) ?? {}; - // if ( - // !("poaps" in assets.value) && - // !("tokens" in assets.value) && - // !("nfts" in assets.value) - // ) { - // noAssets.value = true; - // } - // if ("poaps" in assets.value) { - // poaps.value = assets.value?.poaps?.slice(0, 10) ?? []; - // if (poaps.value.length >= (assets.value?.poaps?.length ?? 0)) { - // hasMore.poaps = false; - // } - // } - // if ("nfts" in assets.value) { - // ethNfts.value = - // assets.value?.nfts?.filter((n) => n.chain === "ethereum").slice(0, 10) ?? []; - // if ( - // ethNfts.value.length >= - // (assets.value?.nfts?.filter((n) => n.chain === "ethereum").length ?? 0) - // ) { - // hasMore.ethNfts = false; - // } - // polyNfts.value = - // assets.value?.nfts?.filter((n) => n.chain === "polygon").slice(0, 10) ?? []; - // if ( - // polyNfts.value.length >= - // (assets.value?.nfts?.filter((n) => n.chain === "polygon").length ?? 0) - // ) { - // hasMore.polyNfts = false; - // } - // } - // if ("tokens" in assets.value) { - // ethTokens.value = - // assets.value?.tokens?.filter((n) => n.chain === "ethereum").slice(0, 10) ?? - // []; - // if ( - // ethTokens.value.length >= - // (assets.value?.tokens?.filter((n) => n.chain === "ethereum").length ?? 0) - // ) { - // hasMore.ethTokens = false; - // } - // polyTokens.value = - // assets.value?.tokens?.filter((n) => n.chain === "polygon").slice(0, 10) ?? []; - // if ( - // polyTokens.value.length >= - // (assets.value?.tokens?.filter((n) => n.chain === "polygon").length ?? 0) - // ) { - // hasMore.polyTokens = false; - // } - // } - // } else { - // isError.value = true; - // } - // loading.value = false; }); - const loadMore = async (type: string) => { - switch (type) { - case "ethTokens": { - walletLoadArgs.start = ethTokens.value.length; - walletLoadArgs.res = ["tokens"]; - walletLoadArgs.ch = ["ethereum"]; - const r = await getProfileWallet(walletLoadArgs); - if (r.ethTokens.length !== walletLoadArgs.limit) { - hasMore.ethTokens = false; - return; - } - ethTokens.value = [...ethTokens.value, ...r.ethTokens.slice(0, 10)]; - break; - } - case "polyTokens": { - walletLoadArgs.start = polyTokens.value.length; - walletLoadArgs.res = ["tokens"]; - walletLoadArgs.ch = ["polygon"]; - const r = await getProfileWallet(walletLoadArgs); - if (r.polyTokens.length !== walletLoadArgs.limit) { - hasMore.polyTokens = false; - return; - } - polyTokens.value = [...polyTokens.value, ...r.polyTokens.slice(0, 10)]; - break; - } - case "ethNfts": { - walletLoadArgs.start = ethNfts.value.length; - walletLoadArgs.res = ["nfts"]; - walletLoadArgs.ch = ["ethereum"]; - const r = await getProfileWallet(walletLoadArgs); - if (r.ethNfts.length !== walletLoadArgs.limit) { - hasMore.ethNfts = false; - return; - } - ethNfts.value = [...ethNfts.value, ...r.ethNfts.slice(0, 10)]; - break; - } - case "polyNfts": { - walletLoadArgs.start = polyNfts.value.length; - walletLoadArgs.res = ["nfts"]; - walletLoadArgs.ch = ["polygon"]; - const r = await getProfileWallet(walletLoadArgs); - if (r.polyNfts.length !== walletLoadArgs.limit) { - hasMore.polyNfts = false; - return; - } - polyNfts.value = [...polyNfts.value, ...r.polyNfts.slice(0, 10)]; - break; - } - case "poaps": { - walletLoadArgs.start = poaps.value.length; - walletLoadArgs.res = ["poaps"]; - walletLoadArgs.ch = ["ethereum"]; - const r = await getProfileWallet(walletLoadArgs); - if (r.poaps.length !== walletLoadArgs.limit) { - hasMore.poaps = false; - return; - } - poaps.value = [...poaps.value, ...r.poaps.slice(0, 10)]; - break; - } - } - }; - return { selectedAccount, loading, @@ -570,16 +275,49 @@ export default defineComponent({ getToastRef, copyText, copyOutline, - ethTokens, - polyTokens, - ethNfts, - poaps, - hasMore, - polyNfts, - loadMore, toastState, getUrl, + openTab, + alltokens, + shownTokens, + assetsValue, + assetsChange, + formatNumber, }; }, }); + + diff --git a/src/views/HomeTab.vue b/src/views/HomeTab.vue index cbe8334..b41f8c5 100644 --- a/src/views/HomeTab.vue +++ b/src/views/HomeTab.vue @@ -15,7 +15,6 @@ v-if="version" style=" position: absolute; - top: 0.3rem; right: 1.1rem; margin-left: 0.3rem; color: coral; @@ -24,6 +23,11 @@ " >Version: {{ version }} + @@ -285,6 +289,7 @@ import { allTemplateNets } from "@/utils/networks"; import router from "@/router"; import { triggerListner } from "@/extension/listners"; import { copyOutline } from "ionicons/icons"; +import GitHub from "@/components/icons/GitHub.vue"; const version = getVersion(); @@ -309,6 +314,7 @@ export default defineComponent({ IonToast, IonIcon, IonAvatar, + GitHub, }, setup: () => { const loading = ref(false); @@ -434,4 +440,21 @@ export default defineComponent({ transition: opacity 0.2s ease-in-out; transform: scale(1.05); } + +.github-icon { + position: absolute; + top: 0.9rem; + right: 2.4rem; + margin-left: 0.3rem; + color: coral; + font-weight: bold; + font-size: 0.65rem; + cursor: pointer; +} + +.github-icon:hover { + opacity: 0.8; + transition: opacity 0.2s ease-in-out; + transform: scale(1.05); +} diff --git a/src/views/SignMessage.vue b/src/views/SignMessage.vue index bc29f09..83501bd 100644 --- a/src/views/SignMessage.vue +++ b/src/views/SignMessage.vue @@ -11,7 +11,12 @@ Message to Sign -
{{ signMsg }}
+
+ {{ signMsg }} +
Cancel @@ -80,7 +85,19 @@ export default defineComponent({ const route = useRoute(); const loading = ref(false); const rid = (route?.params?.rid as string) ?? ""; - const signMsg = ref(hexTostr(hexTostr((route?.params?.param as string) ?? ""))); + + let sigmMsg: string = ""; + + try { + const typeSign = JSON.parse( + hexTostr(hexTostr((route?.params?.param as string) ?? "")) + ); + sigmMsg = JSON.stringify(typeSign, null, 2); + } catch (e) { + sigmMsg = hexTostr(hexTostr((route?.params?.param as string) ?? "")); + } + + const signMsg = ref(sigmMsg); const alertOpen = ref(false); const alertMsg = ref(""); const timerReject = ref(140); diff --git a/src/views/SignTx.vue b/src/views/SignTx.vue index 5edfba0..e09f676 100644 --- a/src/views/SignTx.vue +++ b/src/views/SignTx.vue @@ -67,7 +67,7 @@ Raw TX: >["feed"]; let interval = 0; const bars = ref(0); @@ -317,11 +318,10 @@ export default defineComponent({ const newGasData = async () => { await walletSendData(rid, { gas: numToHexStr(gasLimit.value), + gasPrice: numToHexStr(BigInt(Math.trunc(gasPrice.value * 1e9))), + supportsEIP1559: gasFeed?.maxFeePerGas !== null, }); - await walletSendData(rid, { - gasPrice: numToHexStr(BigInt(Math.trunc(gasPrice.value * 1e9))), - }); gasFee.value = Number( ethers.formatUnits(Math.trunc(gasLimit.value * gasPrice.value), "gwei") ); @@ -345,8 +345,10 @@ export default defineComponent({ userBalance.value = Number( ethers.formatEther((await pBalance).toString() ?? "0x0") ); + const { feed, price } = await pGasPrice; + gasFeed = feed; - gasPrice.value = parseFloat((await pGasPrice).toString() ?? 0.1); + gasPrice.value = parseFloat(price.toString() ?? 0.1); try { gasLimit.value = parseInt((await pEstimateGas).toString(), 10); @@ -378,7 +380,9 @@ export default defineComponent({ if (timerFee.value <= 0) { timerFee.value = 20; loading.value = true; - gasPrice.value = parseFloat((await getGasPrice()).toString() ?? 0.1); + const { feed, price } = await getGasPrice(); + gasFeed = feed; + gasPrice.value = parseFloat(price.toString() ?? 0.1); await newGasData(); loading.value = false; } diff --git a/src/views/SwitchNetwork.vue b/src/views/SwitchNetwork.vue index 5993f80..b81c2a8 100644 --- a/src/views/SwitchNetwork.vue +++ b/src/views/SwitchNetwork.vue @@ -41,12 +41,12 @@ >