chore: changes for 1.4.0

This commit is contained in:
Andrei O 2024-07-26 00:01:24 +03:00
parent 7eab21188b
commit a4ad84e058
No known key found for this signature in database
GPG Key ID: B961E5B68389457E
21 changed files with 944 additions and 925 deletions

View File

@ -1,4 +1,3 @@
# Not supported due to compatibility issues with Bun Http2 stdlib
name: Bun Main Workflow name: Bun Main Workflow
on: on:

View File

@ -1,4 +1,3 @@
# Not supported due to compatibility issues with Bun Http2 stdlib
name: Bun Main Workflow name: Bun Main Workflow
on: on:

View File

@ -1,83 +1,95 @@
# Changelog # 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 ## Manifest Version 1.3.9
- add an additional throttle on 'eth_chainId' to prevent websites from spamming the wallet with requests - add an additional throttle on 'eth_chainId' to prevent websites from spamming the wallet with requests
- change inject throttle to only affect UI requests - change inject throttle to only affect UI requests
- updated some core dependencies - updated some core dependencies
- optimized performance for json rpc calls - optimized performance for JSON RPC calls
- disabled assets fetch until new provider is found before yup.io was used - disabled assets fetch until a new provider is found before yup.io was used
- simplified wallet switching - simplified wallet switching
- added sonarCloud badge to README.md - added sonarCloud badge to README.md
## Manifest Version 1.3.8 ## 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 ## Manifest Version 1.3.7
- improved add Network pages - improved add Network pages
- upgraded and optimized some dependencies including vite - upgraded and optimized some dependencies including Vite
- optimized vite config - optimized site config
- added condition to not reinject wallet if already injected for websites that reload injected scripts - 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 - optimized throttle fulfillment of requests in case of too many requests
- removed uneeded mobile native code - removed unneeded mobile native code
## Manifest Version 1.3.6 ## Manifest Version 1.3.6
- better display of blockchain explorer button - better display of the blockchain explorer button
- updated ethers dependency to latest 6.11.1 - updated ethers dependency to the latest 6.11.1
- better handling of type sigining - better handling of type signing
- changed the password input for unlock to not lose focus - changed the password input for unlock to not lose focus
- activated focus on password input for unlock on view enter - 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 - other misc improvements
- added a check when sending native token to check if internet / RPC or Blockchain and show a message to the user - added a check when sending native tokens 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 - customize test-Nets icons to show a small dev icon on the top right corner
- updated testNets templates to include newer networks - updated test-Nets templates to include newer networks
- show icons for testNets too in most places - show icons for test-Nets too in most places
## Manifest Version 1.3.5 ## Manifest Version 1.3.5
- added copy button to chainId for easier development - added copy button to ChainId for easier development
- added settings to be able to transfrom address to lower case when copying - added settings to be able to transform address to lowercase when copying
- added a check in get recepit to return null if hash is missing - added a check in get receipt to return null if the hash is missing
- added version display to wallet first page - added version display to the wallet on the first page
## Manifest Version 1.3.4 ## Manifest Version 1.3.4
- bump fake Metamask version signature to 11.0.0 - bump fake Metamask version signature to 11.0.0
- improved compatibility with older deprecated websites - improved compatibility with older deprecated websites
- improved mimicking of Metamask API - 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 ## Manifest Version 1.3.3
- improved eth_call and eth_blockNumber to be more compatible with older websites - improved eth_call and eth_blockNumber to be more compatible with older websites
- better error internal handling - 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 - 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 ## 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 ## Manifest Version 1.3.1
- refactored the wallet to use etheres V6 - refactored the wallet to use ethers V6
- implemented EIP6963Provider - implemented EIP6963Provider
- updated all dependencies - updated all dependencies
- added ability to send native tokens - added ability to send native tokens
- added ability to manage ABIs - added ability to manage ABIs
- added ability to perfrom arbitrary read calls to contracts - added ability to perform arbitrary read calls to contracts
- added ability to perfrom arbitrary write calls to contracts - added ability to perform arbitrary write calls to contracts
- added ability to save read or write calls for later use - 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 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 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 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 ## Manifest Version 1.2.8
@ -91,15 +103,15 @@
## Manifest Version 1.2.6 ## Manifest Version 1.2.6
- upgrade ionic to v7 and update dependencies - upgrade ionic to v7 and update dependencies
## Manifest Version 1.2.5 ## Manifest Version 1.2.5
- improve post build script - improve post-build script
## Manifest Version 1.2.4 ## Manifest Version 1.2.4
- updated showing assets page to use new api - updated showing assets page to use the new API
- removed yup score from assets page - removed YUP score from the assets page
- change the info modal in settings - change the info modal in settings
## Manifest Version 1.2.3 ## Manifest Version 1.2.3
@ -114,15 +126,15 @@
## Manifest Version 1.2.1 ## Manifest Version 1.2.1
- added support fro eth_getTransactionCount method - added support from eth_getTransactionCount method
## Manifest Version 1.1.9 ## 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 ## 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 ## Manifest Version 1.1.7
@ -137,18 +149,18 @@
## Manifest Version 1.1.5 ## Manifest Version 1.1.5
- Added multiple new multiple implementations of MetamaskAPI including request to add a network by a website - Added multiple new implementations of MetamaskAPI including a 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 - Injecting in sync mode stub wallet to increase compatibility with websites that expect a walled defined at the lowest point of page load
- Modifing CSP requests to allow sync injecting of stub - Modifying CSP requests to allow sync injecting of stub
- Added Web3 Shim for compatibility with older websites - Added Web3 Shim for compatibility with older websites
- Tested new websites and TX's - Tested new websites and TXs
- Refactoring the 10 maximum conqurent messages limit - Refactoring the 10 maximum concurrent messages limit
- Added support for most of listners and improve emiting them - Added support for most of the listeners and improved emitting them
- Added a post buil script - Added a post-build script
- Switch the content script to load initialy without a wrapper module - 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 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. - 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)

View File

@ -59,19 +59,30 @@ const main = async () => {
if(action === 'update') { if(action === 'update') {
const VERSION = GithubEvent.inputs.version; const VERSION = GithubEvent.inputs.version;
const message = `Github ClearWallet new version ${VERSION} has been released!\n const message = `Clear Wallet - New version ${VERSION} released! \n
ChromeStore: https://bit.ly/clw-evm \n ChangeLog: https://bit.ly/clw-cl \n
Github: https://github.com/andrei0x309/clear-wallet ChromeStore: https://bit.ly/clw-evm \n
`; `
if(ENABLED) { if(ENABLED) {
await yupAPI.sendPost({ await yupAPI.sendPost({
content: message, content: message,
platforms: ['twitter', 'threads', 'bsky', 'lens'] platforms: ['twitter', 'threads', 'bsky', 'lens']
}) })
await fchubUtils.createFarcasterPost({ const fcPost = await fchubUtils.createFarcasterPost({
content: message, 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 { } else {
console.log('No action required') console.log('No action required')
} }

View File

@ -0,0 +1,22 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="down"
class="Delta__StyledDownArrow-sc-bcba1827-1 fstbcV"
>
<path
d="M10.6979 16.2453L6.31787 9.75247C5.58184 8.66118 6.2058 7 7.35185 7L16.6482 7C17.7942 7 18.4182 8.66243 17.6821 9.75247L13.3021 16.2453C12.623 17.2516 11.377 17.2516 10.6979 16.2453Z"
fill="currentColor"
></path>
</svg>
</template>
<style scoped>
.fstbcV {
color: rgb(255, 95, 82);
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
aria-label="up"
class="Delta__StyledUpArrow-sc-bcba1827-0 hrYaKr"
>
<path
d="M13.3021 7.7547L17.6821 14.2475C18.4182 15.3388 17.7942 17 16.6482 17L7.3518 17C6.2058 17 5.5818 15.3376 6.3179 14.2475L10.6979 7.7547C11.377 6.7484 12.623 6.7484 13.3021 7.7547Z"
fill="currentColor"
></path>
</svg>
</template>
<style scoped>
.hrYaKr {
color: rgb(64, 182, 107);
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<svg enable-background="new 0 0 32 32" viewBox="0 0 32 32">
<g>
<polygon
fill="none"
points="12,3 12,8 31,8 31,14 1,14 "
stroke="currentColor"
stroke-linejoin="round"
stroke-miterlimit="10"
stroke-width="2"
></polygon>
<polygon
fill="none"
points="20,29 20,24 1,24 1,18 31,18 "
stroke="currentColor"
stroke-linejoin="round"
stroke-miterlimit="10"
stroke-width="2"
></polygon>
</g>
</svg>
</template>

View File

@ -0,0 +1,16 @@
<template>
<svg
fill="none"
height="25"
viewBox="0 0 25 25"
width="25"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M15.7481 24.9471C24.0901 24.7061 24.9111 22.9501 24.9111 12.9811C24.9111 1.98108 23.9111 0.981079 12.9111 0.981079C1.91113 0.981079 0.911133 1.98108 0.911133 12.9811C0.911133 22.9761 1.73713 24.7151 10.1391 24.9491C10.2121 24.8581 10.2391 24.7441 10.2391 24.6281C10.2391 24.3781 10.2291 21.8121 10.2241 20.9291C7.18713 21.5681 6.54613 19.5101 6.54613 19.5101C6.04913 18.2881 5.33313 17.9621 5.33313 17.9621C4.34213 17.3061 5.40813 17.3191 5.40813 17.3191C6.50413 17.3941 7.08113 18.4101 7.08113 18.4101C8.05513 20.0271 9.63713 19.5601 10.2591 19.2891C10.3581 18.6061 10.6401 18.1391 10.9521 17.8751C8.52713 17.6081 5.97813 16.7001 5.97813 12.6451C5.97813 11.4901 6.40413 10.5461 7.10213 9.80608C6.98913 9.53808 6.61513 8.46208 7.20913 7.00608C7.20913 7.00608 8.12613 6.72108 10.2121 8.09008C11.0831 7.85508 12.0171 7.73808 12.9461 7.73408C13.8731 7.73808 14.8071 7.85508 15.6801 8.09008C17.7651 6.72108 18.6801 7.00608 18.6801 7.00608C19.2761 8.46208 18.9011 9.53808 18.7881 9.80608C19.4881 10.5461 19.9111 11.4901 19.9111 12.6451C19.9111 16.7101 17.3581 17.6051 14.9251 17.8661C15.3171 18.1931 15.6661 18.8391 15.6661 19.8261C15.6661 20.7721 15.6601 22.4451 15.6561 23.5541C15.6541 24.1031 15.6531 24.5131 15.6531 24.6281C15.6531 24.7371 15.6821 24.8521 15.7481 24.9471V24.9471Z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
</template>

View File

@ -46,40 +46,49 @@ window.addEventListener("message", (event) => {
event.data.data.data.website = document?.location?.href ?? '' event.data.data.data.website = document?.location?.href ?? ''
if ((event?.data?.data?.method ?? 'x') in allowedMethods) { if ((event?.data?.data?.method ?? 'x') in allowedMethods) {
event.data.data.data.method = event?.data?.data?.method ?? '' event.data.data.data.method = event?.data?.data?.method ?? ''
chrome?.runtime?.sendMessage(event.data.data.data, (res) => { try {
if (chrome.runtime.lastError) { chrome?.runtime?.sendMessage(event.data.data.data, (res) => {
console.warn("LOC1: Error sending message:", chrome.runtime.lastError); 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 = { const id = Number(event.data.resId.replace(/[A-Za-z]/g, '').slice(0, 10))
target: 'metamask-inpage', const data = {
type: "CLWALLET_PAGE", target: 'metamask-inpage',
resId: event.data.resId, type: "CLWALLET_PAGE",
data: { name: 'metamask-provider', data: { resId: event.data.resId,
jsonrpc: '2.0', data: {
name: 'metamask-provider', data: {
jsonrpc: '2.0',
id,
result: res,
},
id, id,
result: res, method: event?.data?.data?.data?.method ?? '',
}, params: event?.data?.data?.data?.params ?? [],
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)
} }
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 { else {
const data = { const data = {
type: "CLWALLET_PAGE", type: "CLWALLET_PAGE",
data: { data: {
data: { data: {
result: { error: true, message: 'ClearWallet: Unknown method requested ' + (event?.data?.data?.data?.method ?? '') } result: { error: true, message: 'ClearWallet: Unknown method requested ' + (event?.data?.data?.data?.method ?? '') }
} } }
, resId: event.data.resId }; }
, resId: event.data.resId
};
window.postMessage(data, "*"); window.postMessage(data, "*");
} }
} else if (event?.data?.type === "CLWALLET_PING") { } 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.type = "CLWALLET_CONTENT_MSG"
event.data.data.data.method = "wallet_connect" event.data.data.data.method = "wallet_connect"
event.data.data.data.params = Array(0) event.data.data.data.params = Array(0)
chrome.runtime.sendMessage(event.data.data.data, async (res) => { try {
if (chrome.runtime.lastError) { chrome.runtime.sendMessage(event.data.data.data, async (res) => {
console.warn("LOC2: Error sending message:", chrome.runtime.lastError); 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, "*"); }
})
} }
}); });

View File

@ -56,6 +56,7 @@ const listners = {
} }
const promResolvers = new Map() const promResolvers = new Map()
const UIpromResolvers = new Map()
const getListnersCount = (): number => { const getListnersCount = (): number => {
let count = 0 let count = 0
@ -75,14 +76,20 @@ const sendMessage = (args: RequestArguments, ping = false, from = 'request'): Pr
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const p = [ "eth_signTypedData", "eth_signTypedData_v3", "eth_signTypedData_v4"] const p = [ "eth_signTypedData", "eth_signTypedData_v3", "eth_signTypedData_v4"]
const throttledMethods = [...p, 'eth_sign', 'personal_sign', 'eth_sendTransaction'] 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 }) reject({code: -32000, message: 'ClearWallet: Too many requests', error: true })
return return
} }
const resId = [...`${Math.random().toString(16) + Date.now().toString(16)}`].slice(2).join('') 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 const method = args.method
if (p.includes(args.method)) { if (p.includes(args.method)) {
@ -101,7 +108,7 @@ const sendMessage = (args: RequestArguments, ping = false, from = 'request'): Pr
data.type = 'CLWALLET_PING' data.type = 'CLWALLET_PING'
} }
if(method!== 'eth_chainId') { if(method!== 'eth_chainId') {
console.info('data in', data) // console.info('data in', data)
} }
window.postMessage(data, "*"); window.postMessage(data, "*");
@ -389,6 +396,10 @@ const listner = function(event: any) {
if(promResolvers.has(resId)) { if(promResolvers.has(resId)) {
promResolvers.delete(resId) promResolvers.delete(resId)
} }
if(UIpromResolvers.has(resId)) {
UIpromResolvers.get(resId).resolve(result)
UIpromResolvers.delete(resId)
}
} }
window.addEventListener("message",listner) window.addEventListener("message",listner)

View File

@ -3,8 +3,8 @@
"name": "__MSG_appName__", "name": "__MSG_appName__",
"description": "__MSG_appDesc__", "description": "__MSG_appDesc__",
"default_locale": "en", "default_locale": "en",
"version": "1.3.9", "version": "1.4.0",
"version_name": "1.3.9", "version_name": "1.4.0",
"icons": { "icons": {
"16": "assets/extension-icon/wallet_16.png", "16": "assets/extension-icon/wallet_16.png",
"32": "assets/extension-icon/wallet_32.png", "32": "assets/extension-icon/wallet_32.png",

View File

@ -1,37 +1,37 @@
import { import {
CLW_CONTEXT_MENU_ID, CLW_CONTEXT_MENU_ID,
getSelectedAccount, getSelectedAccount,
getSelectedNetwork, getSelectedNetwork,
smallRandomString, smallRandomString,
getSettings, getSettings,
clearPk, clearPk,
openTab, openTab,
getUrl, getUrl,
addToHistory, addToHistory,
getNetworks, getNetworks,
strToHex, strToHex,
numToHexStr, numToHexStr,
enableRightClickVote, enableRightClickVote,
} from '@/utils/platform'; } from '@/utils/platform';
import { import {
userApprove, userApprove,
userReject, userReject,
rIdWin, rIdWin,
rIdData, rIdData,
} from '@/extension/userRequest' } from '@/extension/userRequest'
import { import {
signMsg, signMsg,
getBalance, getBalance,
getBlockNumber, getBlockNumber,
estimateGas, estimateGas,
sendTransaction, sendTransaction,
getGasPrice, getGasPrice,
getBlockByNumber, getBlockByNumber,
evmCall, evmCall,
getTxByHash, getTxByHash,
getTxReceipt, getTxReceipt,
signTypedData, signTypedData,
getCode, getCode,
getTxCount, getTxCount,
getSelectedAddress getSelectedAddress
} from '@/utils/wallet' } from '@/utils/wallet'
@ -44,35 +44,65 @@ import { allTemplateNets } from '@/utils/networks'
let notificationUrl: string 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(() => { chrome.runtime.onInstalled.addListener(() => {
enableRightClickVote() enableRightClickVote()
reInjectContentScripts();
console.info('Service worker installed'); console.info('Service worker installed');
if (chrome.runtime.lastError) {
console.warn("Whoops.. " + chrome.runtime.lastError.message);
}
}) })
chrome.runtime.onStartup.addListener(() => { chrome.runtime.onStartup.addListener(() => {
console.info('Service worker startup'); console.info('Service worker startup');
enableRightClickVote(); enableRightClickVote();
if(chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.warn("Whoops.. " + chrome.runtime.lastError.message); console.warn("Whoops.. " + chrome.runtime.lastError.message);
} }
}) })
chrome.runtime.onSuspend.addListener(() => { chrome.runtime.onSuspend.addListener(() => {
console.info('Service worker suspend'); console.info('Service worker suspend');
if(chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.warn("Whoops.. " + chrome.runtime.lastError.message); console.warn("Whoops.. " + chrome.runtime.lastError.message);
} }
}) })
async function pasteAddress() { async function pasteAddress () {
const currentAddress = (await (window as any).ethereum?.request({ const currentAddress = (await (window as any).ethereum?.request({
method: 'eth_accounts', method: 'eth_accounts',
params: [] params: []
})) }))
if(currentAddress.length > 0) { if (currentAddress.length > 0) {
document.execCommand("insertText", false, currentAddress[0]); document.execCommand("insertText", false, currentAddress[0]);
} }
} }
@ -88,9 +118,9 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
func: pasteAddress func: pasteAddress
}); });
} catch { } catch {
// igonre // igonre
} }
} else if(isOwnExtension) { } else if (isOwnExtension) {
chrome.runtime.sendMessage({ method: 'paste', type: 'CLWALLET_PAGE_MSG' }, (r) => { chrome.runtime.sendMessage({ method: 'paste', type: 'CLWALLET_PAGE_MSG' }, (r) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.warn("LOC3: Error sending message:", 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) => { chrome.alarms.onAlarm.addListener((alarm) => {
if(alarm.name === 'updatePrices') { if (alarm.name === 'updatePrices') {
updatePrices().then(() => { updatePrices().then(() => {
console.info('Prices updated') console.info('Prices updated')
}).catch((err) => { }).catch((err) => {
console.warn('Prices update failed', 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()
} }
}) 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) => { chrome.windows.onRemoved.addListener(async (winId) => {
if (winId in (userReject ?? {})){ if (winId in (userReject ?? {})) {
userReject[winId]?.() userReject[winId]?.()
} }
userReject[winId] = undefined userReject[winId] = undefined
@ -129,9 +159,9 @@ chrome.windows.onRemoved.addListener(async (winId) => {
rIdWin[winId] = undefined rIdWin[winId] = undefined
rIdData[winId] = undefined rIdData[winId] = undefined
const wins = await chrome.windows.getAll() const wins = await chrome.windows.getAll()
if(wins.length === 0) { if (wins.length === 0) {
const s = await getSettings() const s = await getSettings()
if(s.enableStorageEnctyption) { if (s.enableStorageEnctyption) {
await clearPk() 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) chrome.notifications.onButtonClicked.addListener(viewTxListner)
} }
@ -159,33 +189,30 @@ const chainIdThrottleFn = async (website: string) => {
} catch { } catch {
urlKey = 'invalid' urlKey = 'invalid'
} }
if(chainIdThrottle[urlKey] === undefined) { if (chainIdThrottle[urlKey] === undefined) {
chainIdThrottle[urlKey] = 0 chainIdThrottle[urlKey] = 0
} }
chainIdThrottle[urlKey] += 1 chainIdThrottle[urlKey] += 1
if( chainIdThrottle[urlKey] > 3) { if (chainIdThrottle[urlKey] > 6) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
resolve(null) resolve(null)
}, 450) }, 250)
}) })
// console.log('throttling', chainIdThrottle)
} }
return urlKey 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) { if (chrome.runtime.lastError) {
console.info("Error receiving message:", 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 return true
} }
(async () => {
// console.info('Message:', message)
(async () => {
if (!(message?.method)) { if (!(message?.method)) {
sendResponse({ sendResponse({
code: 500, code: 500,
@ -196,7 +223,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
switch (message.method) { switch (message.method) {
case 'eth_call': { case 'eth_call': {
try { try {
sendResponse(await evmCall(message?.params ?? [])) sendResponse(await evmCall(message?.params ?? []))
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -209,15 +236,15 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
case 'eth_getBlockByNumber': { case 'eth_getBlockByNumber': {
try { try {
const params = message?.params?.[0] as any const params = message?.params?.[0] as any
const block = await getBlockByNumber(params) as any const block = await getBlockByNumber(params) as any
const newBlock = {...block} const newBlock = { ...block }
newBlock.gasLimit = numToHexStr(block.gasLimit) newBlock.gasLimit = numToHexStr(block.gasLimit)
newBlock.gasUsed = numToHexStr(block.gasUsed) newBlock.gasUsed = numToHexStr(block.gasUsed)
newBlock.baseFeePerGas = numToHexStr(block.baseFeePerGas) newBlock.baseFeePerGas = numToHexStr(block.baseFeePerGas)
newBlock._difficulty = numToHexStr(block.difficulty) newBlock._difficulty = numToHexStr(block.difficulty)
newBlock.difficulty = block._difficulty newBlock.difficulty = block._difficulty
sendResponse(newBlock) sendResponse(newBlock)
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -230,11 +257,11 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
case 'eth_getTransactionCount': { case 'eth_getTransactionCount': {
try { try {
if(message?.params?.[1]) { if (message?.params?.[1]) {
sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string, message?.params?.[1] as string)))) sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string, message?.params?.[1] as string))))
}else { } else {
sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string)))) sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string))))
} }
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -247,7 +274,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
case 'eth_getTransactionByHash': { case 'eth_getTransactionByHash': {
try { try {
sendResponse(await getTxByHash(message?.params?.[0] as string)) sendResponse(await getTxByHash(message?.params?.[0] as string))
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -258,9 +285,9 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
break break
} }
case 'eth_getTransactionReceipt':{ case 'eth_getTransactionReceipt': {
try { try {
sendResponse(await getTxReceipt(message?.params?.[0] as string)) sendResponse(await getTxReceipt(message?.params?.[0] as string))
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -273,8 +300,8 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
case 'eth_gasPrice': { case 'eth_gasPrice': {
try { try {
sendResponse(numToHexStr(BigInt(Math.trunc(await getGasPrice() * 1e9)))) sendResponse(numToHexStr(BigInt(Math.trunc((await getGasPrice()).price * 1e9))))
} catch(e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
@ -286,9 +313,9 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
case 'eth_getBalance': { case 'eth_getBalance': {
try { try {
const balance = await getBalance() const balance = await getBalance()
const balanceHex = numToHexStr(balance ?? 0n) const balanceHex = numToHexStr(balance ?? 0n)
sendResponse(balanceHex) sendResponse(balanceHex)
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -301,7 +328,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
case 'eth_getCode': { case 'eth_getCode': {
try { try {
sendResponse(await getCode(message?.params?.[0] as string)) sendResponse(await getCode(message?.params?.[0] as string))
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -314,7 +341,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
} }
case 'eth_blockNumber': { case 'eth_blockNumber': {
try { try {
sendResponse(numToHexStr(await getBlockNumber())) sendResponse(numToHexStr(await getBlockNumber()))
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
@ -323,89 +350,89 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
}) })
console.warn('Error: eth_blockNumber', e) console.warn('Error: eth_blockNumber', e)
} }
break break
} }
case 'eth_estimateGas': { case 'eth_estimateGas': {
try { try {
const params = message?.params?.[0] as any const params = message?.params?.[0] as any
if(!params) { if (!params) {
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.INVALID_PARAM, code: rpcError.INVALID_PARAM,
message: 'Invalid param for gas estimate' 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({ break
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
} }
case 'eth_requestAccounts': case 'eth_requestAccounts':
case 'eth_accounts': { case 'eth_accounts': {
try { try {
sendResponse(await getSelectedAddress()) sendResponse(await getSelectedAddress())
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
message: 'No network or user selected' message: 'No network or user selected'
}) })
console.warn('Error: eth_accounts', e) console.warn('Error: eth_accounts', e)
} }
break break
} }
case 'eth_chainId': case 'eth_chainId':
case 'net_version': case 'net_version':
{ {
try { try {
const isNetVersion = message.method === 'net_version' const isNetVersion = message.method === 'net_version'
const urlKey = await chainIdThrottleFn(message?.website ?? '') const urlKey = await chainIdThrottleFn(message?.website ?? '')
const network = await getSelectedNetwork() const network = await getSelectedNetwork()
const chainId = network?.chainId ?? 1 const chainId = network?.chainId ?? 1
sendResponse(isNetVersion ? chainId.toString() : `0x${chainId.toString(16)}`) sendResponse(isNetVersion ? chainId.toString() : `0x${chainId.toString(16)}`)
chainIdThrottle[urlKey] -= 1 chainIdThrottle[urlKey] -= 1
} catch (e) { } catch (e) {
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
message: 'No network or user selected' message: 'No network or user selected'
}) })
console.warn('Error: eth_chainId', e) console.warn('Error: eth_chainId', e)
} }
break break
} }
case 'eth_sendTransaction': { case 'eth_sendTransaction': {
try { try {
const params = message?.params?.[0] as any const params = message?.params?.[0] as any
if(!params) { if (!params) {
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.INVALID_PARAM, code: rpcError.INVALID_PARAM,
@ -414,7 +441,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
break break
} }
const [account, network] = await Promise.all([getSelectedAccount(), getSelectedNetwork()]) const [account, network] = await Promise.all([getSelectedAccount(), getSelectedNetwork()])
if(!account || !('address' in account)) { if (!account || !('address' in account)) {
await chrome.windows.create({ await chrome.windows.create({
height: 450, height: 450,
width: 400, width: 400,
@ -423,7 +450,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
}) })
return return
} }
if(!network || !('chainId' in network)) { if (!network || !('chainId' in network)) {
await chrome.windows.create({ await chrome.windows.create({
height: 450, height: 450,
width: 400, width: 400,
@ -436,62 +463,63 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
const serializeParams = strToHex(JSON.stringify(params)) ?? '' const serializeParams = strToHex(JSON.stringify(params)) ?? ''
let gWin: any let gWin: any
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
chrome.windows.create({ chrome.windows.create({
height: 450, height: 450,
width: 400, width: 400,
url: chrome.runtime.getURL(`index.html?route=sign-tx&param=${serializeParams}&rid=${String(message?.resId ?? '')}`), url: chrome.runtime.getURL(`index.html?route=sign-tx&param=${serializeParams}&rid=${String(message?.resId ?? '')}`),
type: 'popup' type: 'popup'
}).then((win) => { }).then((win) => {
gWin = win gWin = win
userReject[String(win.id)] = reject userReject[String(win.id)] = reject
userApprove[String(win.id)] = resolve userApprove[String(win.id)] = resolve
rIdWin[String(win.id)] = String(message.resId) rIdWin[String(win.id)] = String(message.resId)
rIdData[String(win.id)] = {} rIdData[String(win.id)] = {}
}) })
}) })
try { try {
// console.log('waiting for user to approve or reject') console.log('waiting for user to approve or reject')
// console.log(rIdData?.[String(gWin?.id ?? 0)]) console.log(rIdData?.[String(gWin?.id ?? 0)])
const tx = await sendTransaction({...params, ...(rIdData?.[String(gWin?.id ?? 0)] ?? {}) } ) const tx = await sendTransaction({ ...params, ...(rIdData?.[String(gWin?.id ?? 0)] ?? {}) })
sendResponse(tx.hash) sendResponse(tx.hash)
const buttons = {} as any const buttons = {} as any
const network = await getSelectedNetwork() const network = await getSelectedNetwork()
addToHistory({ addToHistory({
date: Date.now(), date: Date.now(),
txHash: tx.hash, txHash: tx.hash,
chainId: network.chainId, chainId: network.chainId,
...(network.explorer ? {txUrl: `${network.explorer}/tx/${tx.hash}`.replace('//', '/') } : {}), ...(network.explorer ? { txUrl: `${network.explorer}/tx/${tx.hash}`.replace('//', '/') } : {}),
webiste: (message?.website) webiste: (message?.website)
}) })
const notificationId = crypto.randomUUID() const notificationId = crypto.randomUUID()
if(network?.explorer) { if (network?.explorer) {
notificationUrl = `${network.explorer}/tx/${tx.hash}`.replace('//', '/') notificationUrl = `${network.explorer}/tx/${tx.hash}`.replace('//', '/')
buttons.buttons = [{ buttons.buttons = [{
title: 'View Transaction', title: 'View Transaction',
}] }]
setTimeout(() => { setTimeout(() => {
try { try {
chrome.notifications.clear(notificationId) chrome.notifications.clear(notificationId)
} catch { } catch {
// ignore // ignore
} }
}, 6e4) }, 6e4)
} }
chrome.notifications.create(notificationId,{ chrome.notifications.create(notificationId, {
message: 'Transaction Confirmed', message: 'Transaction Confirmed',
title: 'Success', title: 'Success',
iconUrl: getUrl('assets/extension-icon/wallet_128.png'), iconUrl: getUrl('assets/extension-icon/wallet_128.png'),
type: 'basic', type: 'basic',
...(buttons) ...(buttons)
} as any) } as any)
const settings = await getSettings() const settings = await getSettings()
if(settings.encryptAfterEveryTx) { if (settings.encryptAfterEveryTx) {
clearPk() clearPk()
} }
} catch (err) { } catch (err) {
console.info('Error: eth_sendTransaction', err)
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
@ -510,73 +538,73 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
type: 'basic' type: 'basic'
} as any) } as any)
} }
} catch(err) { } catch (err) {
console.warn('Error: eth_sendTransaction', err) console.warn('Error: eth_sendTransaction', err)
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
message: 'User Rejected Signature' message: 'User Rejected Signature'
}) })
} }
break break
} }
case 'signTypedData': case 'signTypedData':
case 'eth_signTypedData': case 'eth_signTypedData':
case 'signTypedData_v1': case 'signTypedData_v1':
case 'eth_signTypedData_v1': case 'eth_signTypedData_v1':
case 'signTypedData_v3': case 'signTypedData_v3':
case 'eth_signTypedData_v3': case 'eth_signTypedData_v3':
case 'signTypedData_v4': case 'signTypedData_v4':
case 'eth_signTypedData_v4': case 'eth_signTypedData_v4':
case 'personal_sign': case 'personal_sign':
case 'eth_sign': { case 'eth_sign': {
try { try {
const account = await getSelectedAccount() const account = await getSelectedAccount()
if(!account || !('address' in account)) { if (!account || !('address' in account)) {
await chrome.windows.create({ await chrome.windows.create({
height: 450, height: 450,
width: 400, width: 400,
url: chrome.runtime.getURL(`index.html?route=wallet-error&param=${strToHex('No account is selected you need to have an account selected before trying sign a message')}&rid=${String(message?.resId ?? '')}`), url: chrome.runtime.getURL(`index.html?route=wallet-error&param=${strToHex('No account is selected you need to have an account selected before trying sign a message')}&rid=${String(message?.resId ?? '')}`),
type: 'popup' 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&param=${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 sendResponse(
} isTypedSigned ?
await signTypedData(signMsgData) :
const isTypedSigned = [ await signMsg(signMsgData)
'signTypedData', )
'eth_signTypedData', const settings = await getSettings()
'signTypedData_v1', if (settings.encryptAfterEveryTx) {
'eth_signTypedData_v1', clearPk()
'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&param=${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()
}
} catch (e) { } catch (e) {
console.warn('Error: signTypedData', e) console.warn('Error: signTypedData', e)
sendResponse({ sendResponse({
@ -609,9 +637,9 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
sendResponse([{ sendResponse([{
id: smallRandomString(21), id: smallRandomString(21),
parentCapability: 'eth_accounts', parentCapability: 'eth_accounts',
invoker: message?.website?.split('/').slice(0,3).join('/') ?? '', invoker: message?.website?.split('/').slice(0, 3).join('/') ?? '',
caveats: [{ caveats: [{
type:'restrictReturnedAccounts', type: 'restrictReturnedAccounts',
value: address value: address
}], }],
date: Date.now(), date: Date.now(),
@ -633,127 +661,130 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
case 'wallet_switchEthereumChain': { case 'wallet_switchEthereumChain': {
try { try {
const currentChainId = `0x${((await getSelectedNetwork())?.chainId ?? 0).toString(16)}` 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) sendResponse(null)
}else { } else {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
chrome.windows.create({ chrome.windows.create({
height: 450, height: 450,
width: 400, width: 400,
url: chrome.runtime.getURL(`index.html?route=switch-network&param=${String(message?.params?.[0]?.chainId ?? '' )}&rid=${String(message?.resId ?? '')}`), url: chrome.runtime.getURL(`index.html?route=switch-network&param=${String(message?.params?.[0]?.chainId ?? '')}&rid=${String(message?.resId ?? '')}`),
type: 'popup' type: 'popup'
}).then((win) => { }).then((win) => {
userReject[String(win.id)] = reject userReject[String(win.id)] = reject
userApprove[String(win.id)] = resolve userApprove[String(win.id)] = resolve
rIdWin[String(win.id)] = String(message.resId) rIdWin[String(win.id)] = String(message.resId)
}) })
})
sendResponse(null)
}
} catch {
sendResponse({
error: true,
code: rpcError.USER_REJECTED,
message: 'User Rejected chain switch'
}) })
sendResponse(null)
} }
} catch {
sendResponse({
error: true,
code: rpcError.USER_REJECTED,
message: 'User Rejected chain switch'
})
}
break break
} }
case 'wallet_addEthereumChain': { case 'wallet_addEthereumChain': {
const userNetworks = await getNetworks() const userNetworks = await getNetworks()
const networks = {...allTemplateNets, ...userNetworks} const networks = { ...allTemplateNets, ...userNetworks }
const chainId = Number(message?.params?.[0]?.chainId ?? '0') const chainId = Number(message?.params?.[0]?.chainId ?? '0')
if(!chainId) { if (!chainId) {
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
message: 'Invalid Network' message: 'Invalid Network'
}) })
} }
if( chainId in networks ) { if (chainId in networks) {
mainListner({...message, method:'wallet_switchEthereumChain', params: [{ mainListner({
chainId: `0x${(chainId).toString(16)}` ...message, method: 'wallet_switchEthereumChain', params: [{
}] }, sender, sendResponse) chainId: `0x${(chainId).toString(16)}`
}]
}, sender, sendResponse)
} else { } else {
if ( !message?.params?.[0]?.chainId || if (!message?.params?.[0]?.chainId ||
!message?.params?.[0]?.chainName || !message?.params?.[0]?.chainName ||
!message?.params?.[0]?.rpcUrls || !message?.params?.[0]?.rpcUrls ||
!message?.params?.[0]?.blockExplorerUrls || !message?.params?.[0]?.blockExplorerUrls ||
!message?.params?.[0]?.nativeCurrency?.symbol !message?.params?.[0]?.nativeCurrency?.symbol
){ ) {
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
message: 'Invalid Network params chainId, chainName, rpcUrls, blockExplorerUrls, and nativeCurrency are required' message: 'Invalid Network params chainId, chainName, rpcUrls, blockExplorerUrls, and nativeCurrency are required'
}) })
}else { } else {
try { try {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
chrome.windows.create({ chrome.windows.create({
height: 450, height: 450,
width: 400, width: 400,
url: chrome.runtime.getURL(`index.html?route=request-network&param=${strToHex(JSON.stringify({...{website: message?.website ?? ''}, ...(message?.params?.[0] ?? {})}) ?? '')}&rid=${String(message?.resId ?? '')}`), url: chrome.runtime.getURL(`index.html?route=request-network&param=${strToHex(JSON.stringify({ ...{ website: message?.website ?? '' }, ...(message?.params?.[0] ?? {}) }) ?? '')}&rid=${String(message?.resId ?? '')}`),
type: 'popup' type: 'popup'
}).then((win) => { }).then((win) => {
userReject[String(win.id)] = reject userReject[String(win.id)] = reject
userApprove[String(win.id)] = resolve userApprove[String(win.id)] = resolve
rIdWin[String(win.id)] = String(message.resId) rIdWin[String(win.id)] = String(message.resId)
}) })
}) })
sendResponse(null) sendResponse(null)
} catch (err) { } catch (err) {
console.error('err') console.error('err')
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.USER_REJECTED, code: rpcError.USER_REJECTED,
message: 'User Rejected adding network' message: 'User Rejected adding network'
}) })
}
} }
}
} }
break break
} }
// internal messeges // internal messeges
case 'wallet_connect': { case 'wallet_connect': {
const pNetwork = getSelectedNetwork() const pNetwork = getSelectedNetwork()
const pAccount = getSelectedAccount() const pAccount = getSelectedAccount()
const [network, account] = await Promise.all([pNetwork, pAccount]) const [network, account] = await Promise.all([pNetwork, pAccount])
const address = account?.address ? [account?.address] : [] const address = account?.address ? [account?.address] : []
const chainId = `0x${(network?.chainId ?? 0).toString(16)}` const chainId = `0x${(network?.chainId ?? 0).toString(16)}`
const data = { const data = {
type: "CLWALLET_PAGE_LISTENER", data: { type: "CLWALLET_PAGE_LISTENER", data: {
listner: 'connect', listner: 'connect',
data: { data: {
chainId chainId
}, },
address address
}}; }
};
sendResponse(data) sendResponse(data)
break break
} }
case 'wallet_approve': { case 'wallet_approve': {
if(String(sender.tab?.windowId) in rIdWin){ if (String(sender.tab?.windowId) in rIdWin) {
userApprove[String(sender.tab?.windowId)]?.(true) userApprove[String(sender.tab?.windowId)]?.(true)
} }
try { try {
chrome.windows.remove(sender.tab?.windowId ?? 0) chrome.windows.remove(sender.tab?.windowId ?? 0)
}catch (e) { } catch (e) {
console.info(e) console.info(e)
// ignore // ignore
} }
break break
} }
case 'wallet_send_data': { 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 ?? '')] ?? {} 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) sendResponse(true)
} }
break break
} }
case 'wallet_get_data': { case 'wallet_get_data': {
if(String(sender.tab?.windowId) in rIdData){ if (String(sender.tab?.windowId) in rIdData) {
sendResponse( rIdData[String(sender?.tab?.windowId ?? '')] ?? {}) sendResponse(rIdData[String(sender?.tab?.windowId ?? '')] ?? {})
} }
break break
} }
@ -765,13 +796,13 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
sendResponse({ sendResponse({
error: true, error: true,
code: rpcError.INVALID_PARAM, code: rpcError.INVALID_PARAM,
message: 'ClearWallet: Invalid request method ' + (message?.method ?? '') message: 'ClearWallet: Invalid request method ' + (message?.method ?? '')
}) })
break break
} }
} }
} }
} }
)(); )();
return true; return true;

View File

@ -81,3 +81,64 @@ export interface ContractAction {
export interface ContractActions { export interface ContractActions {
[key: string] : ContractAction [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;
}
}
}[];
};
}

View File

@ -40,7 +40,7 @@ export const mainNets: {[key: number]: Network} = {
}, },
56: { 56: {
name: 'BSC Main', name: 'BSC Main',
rpc: 'https://bsc-dataseed2.binance.org', rpc: 'https://bsc-dataseed1.binance.org',
chainId: 56, chainId: 56,
explorer: 'https://bscscan.com', explorer: 'https://bscscan.com',
icon: 'binance.webp', icon: 'binance.webp',

View File

@ -72,7 +72,10 @@ export const getGasPrice = async () => {
const { provider } = await getCurrentProvider() const { provider } = await getCurrentProvider()
const feed = await provider.getFeeData() const feed = await provider.getFeeData()
const gasPrice = feed.maxFeePerGas ?? feed.gasPrice ?? 0n const gasPrice = feed.maxFeePerGas ?? feed.gasPrice ?? 0n
return Number(gasPrice) / 1e9 return {
price: Number(gasPrice) / 1e9,
feed
}
} }
export const getBlockNumber = async () => { export const getBlockNumber = async () => {
@ -152,8 +155,8 @@ export const getRandomPk = () => {
return ethers.Wallet.createRandom().privateKey return ethers.Wallet.createRandom().privateKey
} }
export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', value='', gasPrice='0x0'}: 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}) => { {to: string, from: string, data: string, value: string, gas: string, gasPrice: string, supportsEIP1559: boolean}) => {
const account = await getSelectedAccount() const account = await getSelectedAccount()
const { provider } = await getCurrentProvider() const { provider } = await getCurrentProvider()
const wallet = new ethers.Wallet(account.pk, provider) 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') { if(gas === '0x0' || gasPrice === '0x0') {
throw new Error('No gas estimate available') throw new Error('No gas estimate available')
} }
return await wallet.sendTransaction({ return supportsEIP1559 ? await wallet.sendTransaction({
to, to,
from, from,
data: data ? data : null, data: data ? data : null,
@ -171,15 +174,24 @@ export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', val
gasLimit: gasInt, gasLimit: gasInt,
gasPrice: null, gasPrice: null,
maxFeePerGas: gasPriceInt, maxFeePerGas: gasPriceInt,
}) :
await wallet.sendTransaction({
to,
from,
data: data ? data : null,
value: value ? value : null,
gasLimit: gasInt,
gasPrice: gasPriceInt
}) })
} }
export const formatBalance = (balance: string) => { export const formatNumber = (num: number, digits = 0) => {
Intl.NumberFormat('en-US', { return Intl.NumberFormat('en-US', {
notation: 'compact', notation: 'compact',
maximumFractionDigits: 6 maximumFractionDigits: digits
}).format(Number(ethers.parseEther(balance))) }).format(num)
} }
export const getSelectedAddress = async () => { export const getSelectedAddress = async () => {
// give only the selected address for better privacy // give only the selected address for better privacy

View File

@ -24,147 +24,96 @@
></ion-toast> ></ion-toast>
<ion-item> <ion-item>
<ion-label>Assests for Account: {{ selectedAccount?.name }}</ion-label> <ion-label style="text-align: center"
>Assets for: {{ selectedAccount?.name }}</ion-label
>
</ion-item> </ion-item>
<ion-item button @click="copyText(selectedAccount?.address, getToastRef())"> <ion-item button @click="copyText(selectedAccount?.address, getToastRef())">
<p style="font-size: 0.7rem">{{ selectedAccount?.address }}</p> <p style="font-size: 0.7rem">{{ selectedAccount?.address }}</p>
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon> <ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
</ion-item> </ion-item>
<ion-item v-if="assetsValue?.value">
<ion-list>
<ion-item
><b>Total Value:&nbsp;</b>
<span style="color: #ffcc00; font-size: 0.9rem">
{{ formatNumber(assetsValue?.value, 2) }} $</span
></ion-item
>
<ion-item
><b>24H Change:&nbsp;</b>
<arrow-up v-if="assetsChange.percentage.value > 0" />
<arrow-down v-else />
<span
:style="`font-size: 0.9rem;color: ${
assetsChange.percentage.value > 0 ? '#33cc33' : '#ff5050'
}`"
>
{{ formatNumber(assetsChange.percentage.value, 2) }}%</span
></ion-item
>
</ion-list>
</ion-item>
<ion-item>
<div style="display: flex; flex-direction: column; margin: auto">
<button
alt="ERC20 Bridge"
@click="openTab('https://erc20-bridge.pages.dev/')"
class="bridge-button"
>
<bridge-icon />
Community ERC20 Bridge
</button>
</div>
</ion-item>
<template v-if="isError"> <template v-if="isError">
Assets info could not be retrieved because of an http error, API down or Assets info could not be retrieved because of an http error, API down or
conectivity issues. conectivity issues.
</template> </template>
<template v-else-if="noAssets"> <template v-else-if="noAssets">
<!-- <p class="padding: 1rem;"> <p class="padding: 1rem;">No know assets found for this wallet address.</p>
No know assets found for this wallet address.
</p> -->
<p class="padding: 1rem;">
Assets view temporarily disabled until finding better provider. As old
provider(yup.io) is no longer available.
</p>
</template> </template>
<template v-else> <template v-else>
<template v-if="ethTokens.length || polyTokens.length"> <ion-list>
<template v-if="ethTokens.length"> <ion-item>
<ion-item>Ethereum Tokens</ion-item> <ion-label style="text-align: center">Tokens</ion-label>
<ion-list> </ion-item>
<ion-item v-for="token of ethTokens" :key="token.address">
<ion-avatar
v-if="token?.image"
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
>
<img
:alt="token?.name"
:src="token?.image"
@error="token.image = getUrl('assets/chain-icons/eth.webp')"
/>
</ion-avatar>
<ion-label
><b>{{ token?.symbol }}:</b> {{ token?.balance }}</ion-label
>
</ion-item>
<ion-item v-if="hasMore.ethTokens">
<ion-button @click="loadMore('ethTokens')">Load More</ion-button>
</ion-item>
</ion-list>
</template>
<template v-if="polyTokens.length">
<ion-item>Polygon Tokens</ion-item>
<ion-list>
<ion-item v-for="token of polyTokens" :key="token.address">
<ion-avatar
v-if="token?.image"
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
>
<img
:alt="token?.name"
:src="token?.image"
@error="token.image = getUrl('assets/randomGrad.svg')"
/>
</ion-avatar>
<ion-label
><b>{{ token?.symbol }}:</b> {{ token?.balance }}</ion-label
>
</ion-item>
<ion-item v-if="hasMore.polyTokens">
<ion-button @click="loadMore('polyTokens')">Load More</ion-button>
</ion-item>
</ion-list>
</template>
</template>
<template v-if="ethNfts.length || polyNfts.length">
<template v-if="ethNfts.length">
<ion-item>Ethereum NFTs</ion-item>
<ion-list>
<ion-item v-for="nft of ethNfts" :key="nft.address">
<ion-avatar
v-if="nft?.imageURI"
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
>
<img
:alt="nft?.collectionName"
:src="nft?.imageURI"
@error="nft.imageURI = getUrl('assets/randomGrad.svg')"
/>
</ion-avatar>
<ion-label
><b>{{ nft?.collectionName }}</b></ion-label
>
</ion-item>
<ion-item v-if="hasMore.ethNfts">
<ion-button @click="loadMore('ethNfts')">Load More</ion-button>
</ion-item>
</ion-list>
</template>
<template v-if="polyNfts.length">
<ion-item>Polygon NFTs</ion-item>
<ion-list>
<ion-item v-for="nft of polyNfts" :key="nft.address">
<ion-avatar
v-if="nft?.imageURI"
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
>
<img
:alt="nft?.collectionName"
:src="nft?.imageURI"
@error="nft.imageURI = getUrl('assets/randomGrad.svg')"
/>
</ion-avatar>
<ion-label
><b>{{ nft?.collectionName }}</b></ion-label
>
</ion-item>
<ion-item v-if="hasMore.polyNfts">
<ion-button @click="loadMore('polyNfts')">Load More</ion-button>
</ion-item>
</ion-list>
</template>
</template>
<template v-if="poaps.length">
<ion-item>POAPs</ion-item>
<ion-list> <ion-list>
<ion-item v-for="nft of poaps" :key="nft.eventId"> <ion-item v-for="token of shownTokens" :key="token.token.address">
<ion-avatar <ion-avatar style="margin-right: 1rem; width: 1.6rem; height: 1.6rem">
v-if="nft?.image"
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
>
<img <img
:alt="nft?.title" v-if="token?.token?.project?.logoUrl"
:src="nft?.image" :alt="token?.token?.name"
@error="nft.image = getUrl('assets/randomGrad.svg')" :src="token?.token?.project?.logoUrl"
/>
<img
v-else
:alt="token?.token?.name"
:src="getUrl('assets/randomGrad.svg')"
/> />
</ion-avatar> </ion-avatar>
<ion-label <ion-label class="flex-col flex">
><b>{{ nft?.title }}</b></ion-label <div class="flex">
<b>{{ token?.token?.symbol }}:</b>
{{ formatNumber(token?.quantity, 4) }}
</div>
<div class="flex">
<span style="font-size: 0.8rem; opacity: 0.7">{{
token?.token?.chain
}}</span>
</div> </ion-label
><span style="font-size: 0.8rem; opacity: 0.7"
>{{ formatNumber(token?.denominatedValue?.value, 2) }} $</span
> >
</ion-item> </ion-item>
<ion-item v-if="hasMore.poaps"> <ion-item v-if="alltokens.length > shownTokens.length">
<ion-button @click="loadMore('poaps')">Load More</ion-button> <ion-button
@click="shownTokens = alltokens.slice(0, shownTokens.length + 10)"
>Load More</ion-button
>
</ion-item> </ion-item>
</ion-list> </ion-list>
</template> </ion-list>
</template> </template>
</ion-content> </ion-content>
</ion-page> </ion-page>
@ -188,35 +137,13 @@ import {
IonLoading, IonLoading,
IonIcon, IonIcon,
} from "@ionic/vue"; } from "@ionic/vue";
import { getSelectedAccount, copyText, getUrl } from "@/utils/platform"; import { getSelectedAccount, copyText, getUrl, openTab } from "@/utils/platform";
import type { Account } from "@/extension/types"; 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"; import { copyOutline } from "ionicons/icons";
import BridgeIcon from "@/components/icons/Bridge.vue";
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;
}
export default defineComponent({ export default defineComponent({
components: { components: {
@ -233,6 +160,9 @@ export default defineComponent({
IonToast, IonToast,
IonLoading, IonLoading,
IonIcon, IonIcon,
ArrowDown,
ArrowUp,
BridgeIcon,
}, },
setup: () => { setup: () => {
const selectedAccount = ref({}) as Ref<Account>; const selectedAccount = ref({}) as Ref<Account>;
@ -240,328 +170,103 @@ export default defineComponent({
const isError = ref(false); const isError = ref(false);
const noAssets = ref(false); const noAssets = ref(false);
const toastState = ref(false); const toastState = ref(false);
const ethTokens = ref([]) as Ref<IProfileToken[]>;
const polyTokens = ref([]) as Ref<IProfileToken[]>;
const ethNfts = ref([]) as Ref<IProfileNFT[]>;
const polyNfts = ref([]) as Ref<IProfileNFT[]>;
const poaps = ref([]) as Ref<IProfilePOAP[]>;
const hasMore = reactive({
poaps: true,
ethTokens: true,
polyTokens: true,
ethNfts: true,
polyNfts: true,
});
const getToastRef = () => toastState; 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 assetsValue = ref({}) as Ref<
const chains = ["ethereum", "polygon"]; UniSwapPortfolioResponse["data"]["portfolios"][0]["tokensTotalDenominatedValue"]
>;
const fetchFromWallet = async ({ const assetsChange = ref({}) as Ref<
apiBase = "https://api.yup.io", UniSwapPortfolioResponse["data"]["portfolios"][0]["tokensTotalDenominatedValueChange"]
address, >;
resource = resources[0],
chain = chains[0], const UNISWAP_API_ENDPOINT = "https://interface.gateway.uniswap.org/v1/graphql";
start = 0,
limit = 10, const getUniwapAssets = async (ownerAddress: string) => {
}: { const headers = {
apiBase?: string; "user-agent": "Desktop",
address: string; accept: "*/*",
resource?: string; "accept-encoding": "gzip, deflate, br",
chain?: string; "accept-language": "en-US,en;q=0.9",
start?: number; "content-type": "application/json",
limit?: number; dnt: "1",
}) => { "Content-Type": "application/json",
try { origin: "https://app.uniswap.org",
const res = await fetch( referer: "https://app.uniswap.org/",
`${apiBase}/web3-profiles/${resource}/${address}?chain=${chain}&start=${start}&limit=${limit}` "sec-fetch-dest": "empty",
); "sec-fetch-mode": "cors",
const req = await res.json(); "sec-fetch-site": "same-site",
if (res.ok) { "sec-gpc": "1",
return req; };
} else {
return null; const query = {
} operationName: "PortfolioBalancesWeb",
} catch (error) { variables: {
console.info("ERROR: Failed to fetch web3 profiles", error); 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; return null;
} }
};
const walletLoadArgs = { const res = await req.json();
address: "", return res as UniSwapPortfolioResponse;
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;
}
}; };
onIonViewWillEnter(async () => { onIonViewWillEnter(async () => {
selectedAccount.value = await getSelectedAccount(); selectedAccount.value = await getSelectedAccount();
walletLoadArgs.address = selectedAccount.value.address; const result = await getUniwapAssets(selectedAccount.value.address);
const r = await getProfileWallet(walletLoadArgs);
ethNfts.value = r.ethNfts.slice(0, 10); if (!result) {
if (r.ethNfts.length !== walletLoadArgs.limit) { isError.value = true;
hasMore.ethNfts = false; loading.value = false;
return;
} }
polyNfts.value = r.polyNfts.slice(0, 10);
if (r.polyNfts.length !== walletLoadArgs.limit) { if (result?.data?.portfolios?.length) {
hasMore.polyNfts = false; 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; 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 { return {
selectedAccount, selectedAccount,
loading, loading,
@ -570,16 +275,49 @@ export default defineComponent({
getToastRef, getToastRef,
copyText, copyText,
copyOutline, copyOutline,
ethTokens,
polyTokens,
ethNfts,
poaps,
hasMore,
polyNfts,
loadMore,
toastState, toastState,
getUrl, getUrl,
openTab,
alltokens,
shownTokens,
assetsValue,
assetsChange,
formatNumber,
}; };
}, },
}); });
</script> </script>
<style lang="scss" scoped>
.bridge-button {
padding-top: 0.5rem;
padding-bottom: 0.6rem;
margin-top: 0.25rem;
margin-bottom: 0.25rem;
border-radius: 0.5rem;
width: 100%;
font-size: 0.9rem;
line-height: 1.2rem;
font-weight: 500;
text-align: center;
color: #fff;
background-color: #312e81;
padding-right: 1rem;
padding-left: 1rem;
box-shadow: 0 1px 3px #0000001a, 0 1px 2px #0000000f;
svg {
margin-right: 0.3rem;
top: 0.3rem;
display: inline-block;
width: 1.3rem;
position: relative;
}
}
.bridge-button:hover {
background-color: #37368f;
transform: scale(1.05);
transition: all 0.3s;
}
</style>

View File

@ -15,7 +15,6 @@
v-if="version" v-if="version"
style=" style="
position: absolute; position: absolute;
top: 0.3rem;
right: 1.1rem; right: 1.1rem;
margin-left: 0.3rem; margin-left: 0.3rem;
color: coral; color: coral;
@ -24,6 +23,11 @@
" "
>Version: {{ version }}</span >Version: {{ version }}</span
> >
<span
class="github-icon"
@click="openTab('https://github.com/andrei0x309/clear-wallet/')"
><GitHub
/></span>
</ion-title> </ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@ -285,6 +289,7 @@ import { allTemplateNets } from "@/utils/networks";
import router from "@/router"; import router from "@/router";
import { triggerListner } from "@/extension/listners"; import { triggerListner } from "@/extension/listners";
import { copyOutline } from "ionicons/icons"; import { copyOutline } from "ionicons/icons";
import GitHub from "@/components/icons/GitHub.vue";
const version = getVersion(); const version = getVersion();
@ -309,6 +314,7 @@ export default defineComponent({
IonToast, IonToast,
IonIcon, IonIcon,
IonAvatar, IonAvatar,
GitHub,
}, },
setup: () => { setup: () => {
const loading = ref(false); const loading = ref(false);
@ -434,4 +440,21 @@ export default defineComponent({
transition: opacity 0.2s ease-in-out; transition: opacity 0.2s ease-in-out;
transform: scale(1.05); 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);
}
</style> </style>

View File

@ -11,7 +11,12 @@
<ion-label>Message to Sign</ion-label> <ion-label>Message to Sign</ion-label>
</ion-item> </ion-item>
<ion-item> <ion-item>
<div style="white-space: pre-wrap" disabled>{{ signMsg }}</div> <div
style="white-space: pre-wrap; width: 100%; height: 250px; overflow-y: scroll"
disabled
>
{{ signMsg }}
</div>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-button @click="onCancel">Cancel</ion-button> <ion-button @click="onCancel">Cancel</ion-button>
@ -80,7 +85,19 @@ export default defineComponent({
const route = useRoute(); const route = useRoute();
const loading = ref(false); const loading = ref(false);
const rid = (route?.params?.rid as string) ?? ""; 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 alertOpen = ref(false);
const alertMsg = ref(""); const alertMsg = ref("");
const timerReject = ref(140); const timerReject = ref(140);

View File

@ -67,7 +67,7 @@
<ion-label>Raw TX:</ion-label> <ion-label>Raw TX:</ion-label>
<ion-textarea <ion-textarea
aria-label="raw tx" aria-label="raw tx"
style="overflow-y: scroll" style="overflow-y: scroll; width: 400px; height: 200px"
:rows="10" :rows="10"
:cols="20" :cols="20"
:value="signTxData" :value="signTxData"
@ -250,6 +250,7 @@ export default defineComponent({
const gasPriceModal = ref(false); const gasPriceModal = ref(false);
const inGasPrice = ref(0); const inGasPrice = ref(0);
const inGasLimit = ref(0); const inGasLimit = ref(0);
let gasFeed = {} as Awaited<ReturnType<typeof getGasPrice>>["feed"];
let interval = 0; let interval = 0;
const bars = ref(0); const bars = ref(0);
@ -317,11 +318,10 @@ export default defineComponent({
const newGasData = async () => { const newGasData = async () => {
await walletSendData(rid, { await walletSendData(rid, {
gas: numToHexStr(gasLimit.value), 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( gasFee.value = Number(
ethers.formatUnits(Math.trunc(gasLimit.value * gasPrice.value), "gwei") ethers.formatUnits(Math.trunc(gasLimit.value * gasPrice.value), "gwei")
); );
@ -345,8 +345,10 @@ export default defineComponent({
userBalance.value = Number( userBalance.value = Number(
ethers.formatEther((await pBalance).toString() ?? "0x0") 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 { try {
gasLimit.value = parseInt((await pEstimateGas).toString(), 10); gasLimit.value = parseInt((await pEstimateGas).toString(), 10);
@ -378,7 +380,9 @@ export default defineComponent({
if (timerFee.value <= 0) { if (timerFee.value <= 0) {
timerFee.value = 20; timerFee.value = 20;
loading.value = true; 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(); await newGasData();
loading.value = false; loading.value = false;
} }

View File

@ -41,12 +41,12 @@
> >
<ion-item> <ion-item>
<ion-avatar <ion-avatar
v-if="(existingNetworks as any)[networkId]?.icon" v-if="(allTemplateNets as any)[(existingNetworks as any)[networkId]?.chainId]?.icon"
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem" style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
> >
<img <img
:alt="(existingNetworks as any)[networkId]?.name" :alt="(existingNetworks as any)[networkId]?.name"
:src="getUrl('assets/chain-icons/' + (existingNetworks as any)[networkId].icon)" :src="getUrl('assets/chain-icons/' + (allTemplateNets as any)[(existingNetworks as any)[networkId]?.chainId].icon)"
/> />
</ion-avatar> </ion-avatar>
<ion-label <ion-label

View File

@ -39,7 +39,7 @@
type="password" type="password"
@ion-input="mpPass = String($event.target.value)" @ion-input="mpPass = String($event.target.value)"
fill="solid" fill="solid"
ref="ionInput" ref="passInput"
></ion-input> ></ion-input>
<!-- <ion-input <!-- <ion-input
@ -77,7 +77,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, onMounted } from "vue"; import { defineComponent, ref, Ref, onMounted } from "vue";
import { import {
IonContent, IonContent,
IonHeader, IonHeader,
@ -124,6 +124,7 @@ export default defineComponent({
const loading = ref(false); const loading = ref(false);
const alertOpen = ref(false); const alertOpen = ref(false);
const alertMsg = ref(""); const alertMsg = ref("");
const passInput = ref() as Ref<typeof IonInput>;
const close = () => { const close = () => {
return modalController?.dismiss(null, "cancel"); return modalController?.dismiss(null, "cancel");
@ -153,12 +154,14 @@ export default defineComponent({
} }
}; };
onMounted(async () => { onMounted(() => {
await new Promise((resolve) => setTimeout(resolve, 150)); requestAnimationFrame(async () => {
const el = document?.querySelector( if (passInput.value) {
".password-input .native-input" await new Promise((resolve) => setTimeout(resolve, 50));
) as HTMLInputElement; console.log("passInput.value", passInput.value);
el?.focus?.(); passInput.value.$el.setFocus();
}
});
}); });
return { return {
@ -168,6 +171,7 @@ export default defineComponent({
alertOpen, alertOpen,
alertMsg, alertMsg,
close, close,
passInput,
}; };
}, },
}); });