mirror of
https://github.com/andrei0x309/clear-wallet.git
synced 2024-12-19 00:00:45 +00:00
chore: changes for 1.3.9
This commit is contained in:
parent
8b624ea8a2
commit
d3aeecc7a1
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
- simplified wallet switching
|
||||
- added sonarCloud badge to README.md
|
||||
|
||||
## Manifest Version 1.3.8
|
||||
|
||||
- improved sign message display to better accomodate SIWE & other messages
|
||||
|
@ -12,6 +12,10 @@ For more info you can check [docs website](https://clear-wallet.flashsoft.eu)
|
||||
|
||||
[![Clear EVM Wallet (CLW) - Open source EVM wallet that implements meta mask API. | Product Hunt](https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=381026&theme=dark)](https://www.producthunt.com/posts/clear-evm-wallet-clw?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-clear-evm-wallet-clw)
|
||||
|
||||
### Badges
|
||||
|
||||
[![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=andrei0x309_clear-wallet)](https://sonarcloud.io/summary/new_code?id=andrei0x309_clear-wallet)
|
||||
|
||||
### Extended article about this repo
|
||||
|
||||
[Article on Mirror](https://mirror.xyz/andrei0x309.eth/9nc8UXrGIGOvz694ZY2gouS1JM9L8-Z8ITLNtirqD6Q)
|
||||
|
@ -1,13 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en" style="width:400px;height:500px">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Clear Wallet SandBox</title>
|
||||
|
||||
<base href="/" />
|
||||
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="Clear Wallet" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Eval Sandbox</h1>
|
||||
<script>
|
||||
// console.log('sandbox loaded');
|
||||
console.info('sandbox loaded');
|
||||
window.addEventListener('message', function (event) {
|
||||
// console.log('message received', event);
|
||||
const data = event.data;
|
||||
const execFunc = new Function(
|
||||
'return ' + data.code
|
||||
|
41
package.json
41
package.json
@ -16,39 +16,38 @@
|
||||
"pub": "yarn build && yarn release && yarn tsx ./release-scripts/create-release.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/vue": "^7.2.3",
|
||||
"@ionic/vue-router": "^7.2.3",
|
||||
"core-js": "^3.32.0",
|
||||
"ethers": "^6.11.1",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
"@ionic/vue": "^7.8.6",
|
||||
"@ionic/vue-router": "^7.8.6",
|
||||
"core-js": "^3.37.1",
|
||||
"ethers": "^6.13.1",
|
||||
"vue": "^3.4.29",
|
||||
"vue-router": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^5.2.3",
|
||||
"@crxjs/vite-plugin": "^2.0.0-beta.23",
|
||||
"@types/archiver": "^5.3.2",
|
||||
"@types/chrome": "^0.0.243",
|
||||
"@types/archiver": "^5.3.4",
|
||||
"@types/chrome": "^0.0.268",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/node": "^20.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@types/node": "^20.14.5",
|
||||
"@typescript-eslint/eslint-plugin": "^7.13.0",
|
||||
"@typescript-eslint/parser": "^7.13.1",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"archiver": "^5.3.1",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-vue": "^9.26.0",
|
||||
"http-browserify": "^1.7.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"jest": "^29.6.2",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
"sass": "^1.65.1",
|
||||
"sass": "^1.77.6",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tsx": "^4.8.0",
|
||||
"typescript": "^5.1.6",
|
||||
"tsx": "^4.15.6",
|
||||
"typescript": "^5.4.5",
|
||||
"util": "^0.12.5",
|
||||
"vite": "^5.2.10",
|
||||
"vue-tsc": "^1.8.8",
|
||||
"vite": "^5.3.1",
|
||||
"vue-tsc": "^2.0.21",
|
||||
"yarn-upgrade-all": "^0.7.2"
|
||||
},
|
||||
"disabledNativeDependencies": {
|
||||
|
@ -14,8 +14,6 @@
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<!-- <link rel="shortcut icon" type="image/png" href="<%= BASE_URL %>assets/icon/favicon.png" /> -->
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="Clear Wallet" />
|
||||
|
@ -47,7 +47,6 @@ async function ghRelease(changes: string[]) {
|
||||
const p = cps.spawn('gh', ['release', 'create', `v${pkg.version}`, `./${outputPath}`, '-F', `./${changeLogPath}`], {
|
||||
shell: true,
|
||||
});
|
||||
// const p = spawn('pwd');
|
||||
let result = '';
|
||||
p.stdout.on('data', (data) => (result += data.toString()));
|
||||
p.stderr.on('data', (data) => (result += data.toString()));
|
||||
|
@ -65,8 +65,10 @@ window.addEventListener("message", (event) => {
|
||||
params: event?.data?.data?.data?.params ?? [],
|
||||
},
|
||||
}
|
||||
if(event?.data?.data?.data?.method !== 'eth_chainId') {
|
||||
// console.info('data out', data)
|
||||
}
|
||||
|
||||
// console.info('data out', data)
|
||||
window.postMessage(data, "*");
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
interface RequestArguments {
|
||||
id?: string | undefined
|
||||
id?: string
|
||||
method: string;
|
||||
params?: unknown[] | object;
|
||||
}
|
||||
@ -18,8 +18,6 @@ const ProviderInfo: EIP6963ProviderInfo = {
|
||||
rdns: 'clear-wallet.flashsoft.eu/',
|
||||
}
|
||||
|
||||
const THROTTLE_LEVEL = 20;
|
||||
const THROTTLE_TIMEOUT = 500;
|
||||
const MAX_PROMISES = 50
|
||||
|
||||
function loadEIP1193Provider(provider: any) {
|
||||
@ -74,16 +72,18 @@ const getListnersCount = (): number => {
|
||||
}
|
||||
|
||||
const sendMessage = (args: RequestArguments, ping = false, from = 'request'): Promise<unknown> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if(promResolvers.size < MAX_PROMISES && promResolvers.size > THROTTLE_LEVEL) {
|
||||
await new Promise((res) => setTimeout(res, THROTTLE_TIMEOUT))
|
||||
} else if(promResolvers.size > MAX_PROMISES) {
|
||||
reject({code: -32000, message: 'ClearWallet: Too many requests', error: true })
|
||||
}
|
||||
|
||||
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)) {
|
||||
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 })
|
||||
const p = [ "eth_signTypedData", "eth_signTypedData_v3", "eth_signTypedData_v4"]
|
||||
|
||||
const method = args.method
|
||||
if (p.includes(args.method)) {
|
||||
args.method = undefined as any
|
||||
@ -100,11 +100,13 @@ const sendMessage = (args: RequestArguments, ping = false, from = 'request'): Pr
|
||||
if (ping) {
|
||||
data.type = 'CLWALLET_PING'
|
||||
}
|
||||
if(method!== 'eth_chainId') {
|
||||
// console.info('data in', data)
|
||||
}
|
||||
|
||||
// console.info('data in', data)
|
||||
window.postMessage(data, "*");
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
class MetaMaskAPI {
|
||||
@ -187,30 +189,27 @@ class MetaMaskAPI {
|
||||
}
|
||||
if (arg2 === undefined) {
|
||||
if( typeof arg1 === 'string' ) {
|
||||
|
||||
return resultFmt(sendMessage({
|
||||
method: arg1,
|
||||
params: undefined
|
||||
}, false, 'send'))
|
||||
} else {
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
}
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
} else if (typeof arg1 === 'object') {
|
||||
if( typeof arg1 === 'string' ) {
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
} else {
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
return resultFmt(sendMessage({
|
||||
method: arg1,
|
||||
params: undefined
|
||||
}, false, 'send'))
|
||||
}
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
}else if( typeof arg1 === 'string' ) {
|
||||
return resultFmt( sendMessage({
|
||||
method: arg1,
|
||||
params: arg2 as object
|
||||
}, false, 'send'))
|
||||
}else if (typeof arg2 === 'function'){
|
||||
return resultFmt( sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
} else {
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments , false, 'send'))
|
||||
}
|
||||
}
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
}
|
||||
on (eventName: string, callback: () => void) {
|
||||
this.addListener(eventName, callback)
|
||||
@ -405,16 +404,16 @@ const web3Shim = {
|
||||
__isMetaMaskShim__: true
|
||||
}
|
||||
|
||||
const injectWallet = (win: any) => {
|
||||
const injectWallet = () => {
|
||||
const ethKey = 'ethereum'
|
||||
if (win[ethKey]?.isClWallet) {
|
||||
if ((window as any)[ethKey]?.isClWallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(win, ethKey, {
|
||||
Object.defineProperty((window as any), ethKey, {
|
||||
value: eth,
|
||||
});
|
||||
Object.defineProperty(win, 'web3', {
|
||||
Object.defineProperty((window as any), 'web3', {
|
||||
value: web3Shim
|
||||
});
|
||||
sendMessage({
|
||||
@ -422,12 +421,52 @@ sendMessage({
|
||||
}, true)
|
||||
}
|
||||
|
||||
injectWallet(this);
|
||||
injectWallet();
|
||||
loadEIP1193Provider(eth)
|
||||
|
||||
|
||||
// HELPERS TO CLONE METAMASK API
|
||||
|
||||
|
||||
// const MMReflect = async () => {
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
// const originalRequest = (window as any).ethereum.request
|
||||
// const originalSend = (window as any).ethereum.send
|
||||
// const originalSendAsync = (window as any).ethereum.sendAsync
|
||||
|
||||
// const methods = [originalRequest, originalSend, originalSendAsync]
|
||||
// const originalMethods = ['request', 'send', 'sendAsync']
|
||||
|
||||
// for(const [index, method] of methods.entries()) {
|
||||
// const methodName = originalMethods[index];
|
||||
// (window as any).ethereum[methodName] = new Proxy(method, {
|
||||
// apply(target, thisArg, argsList) {
|
||||
// const isEthChainId = argsList[0]?.method === 'eth_chainId'
|
||||
|
||||
// const result = Reflect.apply(target, thisArg, argsList) as Promise<unknown>
|
||||
// const resultCLW = Reflect.apply(sendMessage, thisArg, argsList) as Promise<unknown>
|
||||
|
||||
// if(!isEthChainId) {
|
||||
// result?.then((res: any) => {
|
||||
// resultCLW?.then((resCLW: any) => {
|
||||
// console.log(`window.ethereum.${methodName} ${JSON.stringify(argsList, null, 2)} result:`, res, resCLW);
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// console.log('Reflecting Metamask API')
|
||||
// }
|
||||
|
||||
|
||||
// MMReflect()
|
||||
|
||||
// window.addEventListener("message" , (event) => {
|
||||
// console.log('event', JSON.stringify(event?.data?.data, null, 2), JSON.stringify(event?.data, null, 2))
|
||||
// })
|
||||
@ -449,52 +488,52 @@ loadEIP1193Provider(eth)
|
||||
// }, 5000)
|
||||
|
||||
// setTimeout(async () => {
|
||||
// console.log('Metamask clone test');
|
||||
// (<any>window).ethereum.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_requestAccounts')});
|
||||
// (<any>window).ethereum2.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_requestAccounts')});
|
||||
// console.log('Metamask clone test');
|
||||
// // (<any>window).ethereum.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_requestAccounts')});
|
||||
// // (<any>window).ethereum2.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_requestAccounts')});
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// (<any>window).ethereum.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_accounts')});
|
||||
// (<any>window).ethereum2.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_accounts')});
|
||||
// // (<any>window).ethereum.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_accounts')});
|
||||
// // (<any>window).ethereum2.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_accounts')});
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// (<any>window).ethereum.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_chainId')});
|
||||
// (<any>window).ethereum2.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_chainId')});
|
||||
// (<any>window).ethereum.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_chainId')});
|
||||
// (<any>window).ethereum2.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_chainId')});
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
|
||||
// (<any>window).ethereum.request({method: 'eth_blockNumber', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_chainId')});
|
||||
// (<any>window).ethereum2.request({method: 'eth_blockNumber', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_chainId')});
|
||||
// // (<any>window).ethereum.request({method: 'eth_blockNumber', params: Array(0)}).then((res: any) => { console.log(res, 'MT: eth_chainId')});
|
||||
// // (<any>window).ethereum2.request({method: 'eth_blockNumber', params: Array(0)}).then((res: any) => { console.log(res, 'CW: eth_chainId')});
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
|
||||
// (<any>window).ethereum.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, 'MT: wallet_requestPermissions')});
|
||||
// (<any>window).ethereum2.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, 'CW: wallet_requestPermissions')});
|
||||
// // (<any>window).ethereum.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, 'MT: wallet_requestPermissions')});
|
||||
// // (<any>window).ethereum2.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, 'CW: wallet_requestPermissions')});
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// (<any>window).ethereum.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, 'MT: net_version')});
|
||||
// (<any>window).ethereum2.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, 'CW: net_version')});
|
||||
// // (<any>window).ethereum.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, 'MT: net_version')});
|
||||
// // (<any>window).ethereum2.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, 'CW: net_version')});
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// (<any>window).ethereum.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x89"}]}).then((res: any) => { console.log(res, 'MT: wallet_switchEthereumChain')});
|
||||
// (<any>window).ethereum2.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x89"}]}).then((res: any) => { console.log(res, 'CW: wallet_switchEthereumChain')});
|
||||
// // (<any>window).ethereum.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x89"}]}).then((res: any) => { console.log(res, 'MT: wallet_switchEthereumChain')});
|
||||
// // (<any>window).ethereum2.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x89"}]}).then((res: any) => { console.log(res, 'CW: wallet_switchEthereumChain')});
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// (<any>window).ethereum.on('connect', ((a: any, b: any) => console.log('connect MT', a, b)));
|
||||
// (<any>window).ethereum.on('accountsChanged', ((a: any, b: any) => console.log('accountsChanged MT', a, b)));
|
||||
// (<any>window).ethereum.on('chainChanged', ((a: any) => console.log('chainChanged MT', a, typeof a)));
|
||||
// // (<any>window).ethereum.on('connect', ((a: any, b: any) => console.log('connect MT', a, b)));
|
||||
// // (<any>window).ethereum.on('accountsChanged', ((a: any, b: any) => console.log('accountsChanged MT', a, b)));
|
||||
// // (<any>window).ethereum.on('chainChanged', ((a: any) => console.log('chainChanged MT', a, typeof a)));
|
||||
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// // await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// (<any>window).ethereum2.on('connect', ((a: any, b: any) => console.log('connect CW', a, b)));
|
||||
// (<any>window).ethereum2.on('accountsChanged', ((a: any, b: any) => console.log('accountsChanged CW', a, b)));
|
||||
// (<any>window).ethereum2.on('chainChanged', ((a: any) => console.log('chainChanged CW', a, typeof a)));
|
||||
// // (<any>window).ethereum2.on('connect', ((a: any, b: any) => console.log('connect CW', a, b)));
|
||||
// // (<any>window).ethereum2.on('accountsChanged', ((a: any, b: any) => console.log('accountsChanged CW', a, b)));
|
||||
// // (<any>window).ethereum2.on('chainChanged', ((a: any) => console.log('chainChanged CW', a, typeof a)));
|
||||
|
||||
// }, 3500)
|
||||
|
@ -3,8 +3,8 @@
|
||||
"name": "__MSG_appName__",
|
||||
"description": "__MSG_appDesc__",
|
||||
"default_locale": "en",
|
||||
"version": "1.3.8",
|
||||
"version_name": "1.3.8",
|
||||
"version": "1.3.9",
|
||||
"version_name": "1.3.9",
|
||||
"icons": {
|
||||
"16": "assets/extension-icon/wallet_16.png",
|
||||
"32": "assets/extension-icon/wallet_32.png",
|
||||
|
@ -44,6 +44,8 @@ import { allTemplateNets } from '@/utils/networks'
|
||||
|
||||
let notificationUrl: string
|
||||
|
||||
const chainIdThrottle: {[key: string]: number} = {}
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
enableRightClickVote()
|
||||
console.info('Service worker installed');
|
||||
@ -149,6 +151,30 @@ if (!chrome.notifications.onButtonClicked.hasListener(viewTxListner)){
|
||||
chrome.notifications.onButtonClicked.addListener(viewTxListner)
|
||||
}
|
||||
|
||||
const chainIdThrottleFn = async (website: string) => {
|
||||
let urlKey
|
||||
try {
|
||||
const url = new URL(website)
|
||||
urlKey = url.hostname
|
||||
} catch {
|
||||
urlKey = 'invalid'
|
||||
}
|
||||
if(chainIdThrottle[urlKey] === undefined) {
|
||||
chainIdThrottle[urlKey] = 0
|
||||
}
|
||||
chainIdThrottle[urlKey] += 1
|
||||
|
||||
if( chainIdThrottle[urlKey] > 3) {
|
||||
await new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve(null)
|
||||
}, 450)
|
||||
})
|
||||
// console.log('throttling', chainIdThrottle)
|
||||
}
|
||||
return urlKey
|
||||
}
|
||||
|
||||
const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: any) => any) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.info("Error receiving message:", chrome.runtime.lastError);
|
||||
@ -158,6 +184,8 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// console.info('Message:', message)
|
||||
|
||||
if (!(message?.method)) {
|
||||
sendResponse({
|
||||
code: 500,
|
||||
@ -354,11 +382,16 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eth_chainId': {
|
||||
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 ?? 0
|
||||
sendResponse(`0x${chainId.toString(16)}`)
|
||||
const chainId = network?.chainId ?? 1
|
||||
sendResponse(isNetVersion ? chainId.toString() : `0x${chainId.toString(16)}`)
|
||||
chainIdThrottle[urlKey] -= 1
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
@ -711,7 +744,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
||||
try {
|
||||
chrome.windows.remove(sender.tab?.windowId ?? 0)
|
||||
}catch (e) {
|
||||
console.log(e)
|
||||
console.info(e)
|
||||
// ignore
|
||||
}
|
||||
break
|
||||
|
@ -48,7 +48,6 @@ export const saveNetwork = async (network: Network): Promise<void> => {
|
||||
|
||||
|
||||
export const getSelectedNetwork = async (): Promise<Network > => {
|
||||
console.info('network', (await (storageGet('selectedNetwork')))?.selectedNetwork)
|
||||
return (await storageGet('selectedNetwork'))?.selectedNetwork ?? null as unknown as Network
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,21 @@
|
||||
import { getSelectedAccount, getSelectedNetwork, numToHexStr } from '@/utils/platform';
|
||||
import { ethers } from "ethers"
|
||||
|
||||
let provider: ethers.JsonRpcProvider | null = null
|
||||
|
||||
export const getCurrentProvider = async () => {
|
||||
const network = await getSelectedNetwork()
|
||||
if (provider) {
|
||||
// check if the network has changed
|
||||
if (provider._getConnection().url !== network.rpc) {
|
||||
provider = new ethers.JsonRpcProvider(network.rpc, ethers.Network.from(network.chainId), { staticNetwork: true, batchMaxCount: 6, polling: false })
|
||||
}
|
||||
return {provider, network}
|
||||
}
|
||||
provider = new ethers.JsonRpcProvider(network.rpc, ethers.Network.from(network.chainId), { staticNetwork: true, batchMaxCount: 6, polling: false })
|
||||
return {provider, network}
|
||||
}
|
||||
|
||||
const convertReceipt = (receipt: ethers.TransactionReceipt | null) => {
|
||||
if(!receipt) return null
|
||||
const newReceipt = {...receipt} as any
|
||||
@ -49,34 +64,29 @@ export const signTypedData = async (msg: string) => {
|
||||
|
||||
export const getBalance = async () =>{
|
||||
const account = await getSelectedAccount()
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
return await provider.getBalance(account.address)
|
||||
}
|
||||
|
||||
export const getGasPrice = async () => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
const feed = await provider.getFeeData()
|
||||
const gasPrice = feed.maxFeePerGas ?? feed.gasPrice ?? 0n
|
||||
return Number(gasPrice) / 1e9
|
||||
}
|
||||
|
||||
export const getBlockNumber = async () => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
return await provider.getBlockNumber()
|
||||
}
|
||||
|
||||
export const getBlockByNumber = async (blockNum: number) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
return await provider.getBlock(blockNum)
|
||||
}
|
||||
|
||||
export const estimateGas = async ({to = '', from = '', data = '', value = '0x0' }: {to: string, from: string, data: string, value: string}) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
return await provider.estimateGas({to, from, data, value})
|
||||
}
|
||||
|
||||
@ -94,23 +104,20 @@ export const evmCall = async (params: any[]) => {
|
||||
tx.blockTag = 'latest'
|
||||
}
|
||||
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
const result = await provider.call(tx)
|
||||
return result
|
||||
}
|
||||
|
||||
export const getTxByHash = async (hash: string) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
return await provider.getTransaction(hash)
|
||||
}
|
||||
|
||||
export const getTxReceipt = async (hash: string) => {
|
||||
try {
|
||||
if (!hash) return null
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
const receipt = await provider.getTransactionReceipt(hash)
|
||||
|
||||
return convertReceipt(receipt)
|
||||
@ -121,8 +128,7 @@ export const getTxReceipt = async (hash: string) => {
|
||||
}
|
||||
|
||||
export const getCode = async (addr: string) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
return await provider.getCode(addr)
|
||||
}
|
||||
|
||||
@ -134,8 +140,7 @@ export const getFromMnemonic = (mnemonic: string, index: number) => {
|
||||
}
|
||||
|
||||
export const getTxCount = async (addr: string, block: null | string = null) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const { provider } = await getCurrentProvider()
|
||||
if(block){
|
||||
return await provider.getTransactionCount(addr, block)
|
||||
} else {
|
||||
@ -147,16 +152,11 @@ export const getRandomPk = () => {
|
||||
return ethers.Wallet.createRandom().privateKey
|
||||
}
|
||||
|
||||
export const getCurrentProvider = async () => {
|
||||
const network = await getSelectedNetwork()
|
||||
return new ethers.JsonRpcProvider(network.rpc)
|
||||
}
|
||||
|
||||
export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', value='', gasPrice='0x0'}:
|
||||
{to: string, from: string, data: string, value: string, gas: string, gasPrice: string}) => {
|
||||
const account = await getSelectedAccount()
|
||||
const network = await getSelectedNetwork()
|
||||
const wallet = new ethers.Wallet(account.pk, new ethers.JsonRpcProvider(network.rpc))
|
||||
const { provider } = await getCurrentProvider()
|
||||
const wallet = new ethers.Wallet(account.pk, provider)
|
||||
const gasPriceInt = BigInt(gasPrice)
|
||||
const gasInt = BigInt(gas)
|
||||
|
||||
|
@ -76,7 +76,7 @@
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-textarea
|
||||
style="overflow-y: scroll"
|
||||
style="overflow-y: scroll; width: 100%"
|
||||
aria-label="Enter mnemonic"
|
||||
:rows="10"
|
||||
:cols="10"
|
||||
@ -263,7 +263,7 @@ export default defineComponent({
|
||||
if (settings.enableStorageEnctyption) {
|
||||
const pass = await openModal();
|
||||
if (!pass) {
|
||||
alertMsg.value = "Cannot add account with encryption password.";
|
||||
alertMsg.value = "Cannot add account without encryption password.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
@ -278,7 +278,8 @@ export default defineComponent({
|
||||
} else {
|
||||
if (accounts.find((account) => account.address === wallet.address)) {
|
||||
alertMsg.value = "Account already exists.";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const p2 = saveAccount({
|
||||
@ -299,7 +300,8 @@ export default defineComponent({
|
||||
} else {
|
||||
if (accounts.find((account) => account.address === wallet.address)) {
|
||||
alertMsg.value = "Account already exists.";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const p2 = saveAccount({
|
||||
|
@ -229,22 +229,26 @@ export default defineComponent({
|
||||
const onAddNetwork = async () => {
|
||||
if (Number(chainId.value) < 1) {
|
||||
alertMsg.value = "Chain Id must be a valid decimal integer";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (name.value.length < 2) {
|
||||
alertMsg.value = "Name must have at least 2 characters";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (name.value.length > 99) {
|
||||
alertMsg.value = "Name must be less than 100 characters";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (name.value.length > 99) {
|
||||
try {
|
||||
new URL(rpc.value);
|
||||
} catch {
|
||||
alertMsg.value = "RPC must be a valid URL";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
let p1 = Promise.resolve();
|
||||
@ -270,7 +274,8 @@ export default defineComponent({
|
||||
} else {
|
||||
if (chainId.value in networks && !isEdit) {
|
||||
alertMsg.value = "Network already exists.";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
networks[chainId.value] = network;
|
||||
|
@ -35,10 +35,14 @@
|
||||
conectivity issues.
|
||||
</template>
|
||||
<template v-else-if="noAssets">
|
||||
<p class="padding: 1rem;">
|
||||
<!-- <p class="padding: 1rem;">
|
||||
No know assets found for this wallet address.
|
||||
</p></template
|
||||
>
|
||||
</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 v-else>
|
||||
<template v-if="ethTokens.length || polyTokens.length">
|
||||
<template v-if="ethTokens.length">
|
||||
|
@ -367,12 +367,13 @@ export default defineComponent({
|
||||
const findIndex = accounts.value.findIndex((a) => a.address == address);
|
||||
if (findIndex > -1) {
|
||||
selectedAccount.value = accounts.value[findIndex];
|
||||
await saveSelectedAccount(selectedAccount.value);
|
||||
// console.log(({ [address]: accounts.value[address], ...accounts.value}))
|
||||
accounts.value.splice(findIndex, 1);
|
||||
accounts.value.splice(0, 0, selectedAccount.value);
|
||||
accounts.value = accounts.value.filter((a) => a.address !== address);
|
||||
accounts.value.unshift(selectedAccount.value);
|
||||
const newAccounts = [...accounts.value];
|
||||
await replaceAccounts(newAccounts);
|
||||
await Promise.all([
|
||||
saveSelectedAccount(selectedAccount.value),
|
||||
replaceAccounts(newAccounts),
|
||||
]);
|
||||
triggerListner("accountsChanged", [newAccounts.map((a) => a.address)?.[0]]);
|
||||
}
|
||||
accountsModal.value = false;
|
||||
|
@ -121,6 +121,7 @@
|
||||
></ion-alert>
|
||||
|
||||
<iframe
|
||||
title="eval-sandbox"
|
||||
@load="sandboxLoaded = true"
|
||||
ref="evalFrame"
|
||||
src="eval-sandbox.html"
|
||||
@ -285,7 +286,8 @@ export default defineComponent({
|
||||
if (!content) {
|
||||
alertMsg.value =
|
||||
"Abi not found in storage, be sure Abi with name " + data.abi + " exists.";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
abiContent.value = content;
|
||||
@ -304,15 +306,18 @@ export default defineComponent({
|
||||
const saveActionInStorage = () => {
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (abiContent.value === "") {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
saveActionModal.value = true;
|
||||
};
|
||||
@ -320,22 +325,26 @@ export default defineComponent({
|
||||
const executeAction = async () => {
|
||||
if (sandboxLoaded.value === false) {
|
||||
alertMsg.value = "Sandbox for eval not loaded yet, please wait";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parsedAbi) {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
alertHeader.value = "Error";
|
||||
@ -350,7 +359,8 @@ export default defineComponent({
|
||||
);
|
||||
} catch {
|
||||
alertMsg.value = "Error parsing params, check params types";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -362,14 +372,16 @@ export default defineComponent({
|
||||
.map((param) => param.trim());
|
||||
if (paramsTypes.length !== evalParams.length) {
|
||||
alertMsg.value = "Params count mismatch";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
encodeParamsTypes.push(...paramsTypes);
|
||||
}
|
||||
} catch {
|
||||
alertMsg.value =
|
||||
"Function Siganture wrong format (ex: 'functionName(uint256,string)')";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const fnName = functionName.value.includes("(")
|
||||
@ -384,17 +396,20 @@ export default defineComponent({
|
||||
|
||||
alertMsg.value = "Value from contract fetched check result area!";
|
||||
alertHeader.value = "OK";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
} catch (e) {
|
||||
alertMsg.value = "Function call failed, check params, contract address and ABI";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const saveAction = async () => {
|
||||
if (!name.value) {
|
||||
alertMsg.value = "Name is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
const action = {
|
||||
name: name.value,
|
||||
@ -408,7 +423,8 @@ export default defineComponent({
|
||||
saveActionModal.value = false;
|
||||
alertMsg.value = "Action saved successfully";
|
||||
alertHeader.value = "OK";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
};
|
||||
|
||||
const messageHandler = (event: any) => {
|
||||
|
@ -53,11 +53,14 @@ import {
|
||||
modalController,
|
||||
onIonViewWillEnter,
|
||||
} from "@ionic/vue";
|
||||
// import { ethers } from "ethers";
|
||||
import { hexTostr } from "@/utils/platform";
|
||||
import { approve, walletPing } from "@/extension/userRequest";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getSelectedAccount, unBlockLockout, blockLockout } from "@/utils/platform";
|
||||
import {
|
||||
getSelectedAccount,
|
||||
unBlockLockout,
|
||||
blockLockout,
|
||||
hexTostr,
|
||||
} from "@/utils/platform";
|
||||
import UnlockModal from "@/views/UnlockModal.vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -113,6 +113,7 @@
|
||||
/>
|
||||
|
||||
<iframe
|
||||
title="eval-sandbox"
|
||||
@load="sandboxLoaded = true"
|
||||
ref="evalFrame"
|
||||
src="eval-sandbox.html"
|
||||
@ -279,7 +280,8 @@ export default defineComponent({
|
||||
if (!content) {
|
||||
alertMsg.value =
|
||||
"Abi not found in storage, be sure Abi with name " + data.abi + " exists.";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
abiContent.value = content;
|
||||
@ -297,15 +299,18 @@ export default defineComponent({
|
||||
const saveActionInStorage = () => {
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (abiContent.value === "") {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
saveActionModal.value = true;
|
||||
};
|
||||
@ -313,22 +318,26 @@ export default defineComponent({
|
||||
const executeAction = async () => {
|
||||
if (sandboxLoaded.value === false) {
|
||||
alertMsg.value = "Sandbox for eval not loaded yet, please wait";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parsedAbi) {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
alertHeader.value = "Error";
|
||||
@ -348,14 +357,16 @@ export default defineComponent({
|
||||
.map((param) => param.trim());
|
||||
if (paramsTypes.length !== evalParams.length) {
|
||||
alertMsg.value = "Params count mismatch";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
encodeParamsTypes.push(...paramsTypes);
|
||||
}
|
||||
} catch {
|
||||
alertMsg.value =
|
||||
"Function Siganture wrong format (ex: 'functionName(uint256,string)')";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const fnName = functionName.value.includes("(")
|
||||
@ -396,7 +407,8 @@ export default defineComponent({
|
||||
alertMsg.value = "Function call failed, check params, contract address and ABI";
|
||||
loadingSend.value = false;
|
||||
loading.value = false;
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
loadingSend.value = false;
|
||||
loading.value = false;
|
||||
@ -405,7 +417,8 @@ export default defineComponent({
|
||||
const saveAction = async () => {
|
||||
if (!name.value) {
|
||||
alertMsg.value = "Name is required";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
const action = {
|
||||
name: name.value,
|
||||
@ -419,7 +432,7 @@ export default defineComponent({
|
||||
saveActionModal.value = false;
|
||||
alertMsg.value = "Action saved successfully";
|
||||
alertHeader.value = "OK";
|
||||
return (alertOpen.value = true);
|
||||
alertOpen.value = true;
|
||||
};
|
||||
|
||||
const messageHandler = (event: any) => {
|
||||
|
Loading…
Reference in New Issue
Block a user