Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
Andrei O | 7d978eada1 | |
Andrei O | 0c33477741 | |
Andrei O | b654910e13 | |
Andrei O | f05c652b0a | |
Andrei O | 0f00a2426b | |
Andrei O | 6ed73738b7 | |
Andrei O | f398cebf2c | |
Andrei O | 5b9d04a66b | |
Andrei O | c88cfc22b8 | |
Andrei O | 97d37021f0 | |
Andrei O | 7a187cd0cf | |
Andrei O | 498629d073 | |
Andrei O | 73b3d6e26d | |
Andrei O | 3102f620b2 | |
Andrei O | 874dd545ab | |
Andrei O | 5279fe10ee | |
Andrei O | a6340ae936 | |
Andrei O | c77a675e43 | |
Andrei O | ec12c64ce4 | |
Andrei O | 78c98dd54b | |
Andrei O | 5dabddc28e | |
Andrei O | fe8e4c273b | |
Andrei O | b52ddd02f0 |
65
CHANGELOG.md
|
@ -1,5 +1,70 @@
|
|||
# Changelog
|
||||
|
||||
## Manifest Version 1.3.7
|
||||
|
||||
- improved add Network pages
|
||||
- upgraded and optimized some dependencies including vite
|
||||
- optimized vite config
|
||||
- added condition to not reinject wallet if already injected for websites that reload injected scripts
|
||||
- optimized throttle fuffilment of requests in case of too many requests
|
||||
- removed uneeded mobile native code
|
||||
|
||||
## Manifest Version 1.3.6
|
||||
|
||||
- better display of blockchain explorer button
|
||||
- updated ethers dependency to latest 6.11.1
|
||||
- better handling of type sigining
|
||||
- changed the password input for unlock to not lose focus
|
||||
- activated focus on password input for unlock on view enter
|
||||
- disabled integration of fire wallet(in cause user has it installed) with type signing due to incompatibility
|
||||
- other misc improvements
|
||||
- added a check when sending native token to check if internet / RPC or Blockchain and show a message to the user
|
||||
- customize testNets icons to show a small dev icon on the top right corner
|
||||
- updated testNets templates to include newer networks
|
||||
- show icons for testNets too in most places
|
||||
|
||||
## Manifest Version 1.3.5
|
||||
|
||||
- added copy button to chainId for easier development
|
||||
- added settings to be able to transfrom address to lower case when copying
|
||||
- added a check in get recepit to return null if hash is missing
|
||||
- added version display to wallet first page
|
||||
|
||||
## Manifest Version 1.3.4
|
||||
|
||||
- bump fake Metamask version signature to 11.0.0
|
||||
- improved compatibility with older deprecated websites
|
||||
- improved mimicking of Metamask API
|
||||
- made the wallet compatible with fire extension on sending transaction( by mimicking new Metamask API)
|
||||
|
||||
## Manifest Version 1.3.3
|
||||
|
||||
- improved eth_call and eth_blockNumber to be more compatible with older websites
|
||||
- better error internal handling
|
||||
- modify the receipt returned to resamble more the one from Metamask
|
||||
- change some notes in about
|
||||
- refactored account name edit to be more user friendly
|
||||
|
||||
## Manifest Version 1.3.2
|
||||
|
||||
- added button to open non kyc exchange, no referral is used to maximize privacy
|
||||
|
||||
## Manifest Version 1.3.1
|
||||
|
||||
- refactored the wallet to use etheres V6
|
||||
- implemented EIP6963Provider
|
||||
- updated all dependencies
|
||||
- added ability to send native tokens
|
||||
- added ability to manage ABIs
|
||||
- added ability to perfrom arbitrary read calls to contracts
|
||||
- added ability to perfrom arbitrary write calls to contracts
|
||||
- added ability to save read or write calls for later use
|
||||
- added sandbox to be able to evaluate JS code in order to pass complex parameters to read or write calls
|
||||
- added base Network to templates class
|
||||
- added Icon for base network
|
||||
- added 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
|
||||
|
||||
## Manifest Version 1.2.8
|
||||
|
||||
- better support for estimate gas
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Eval Sandbox</h1>
|
||||
<script>
|
||||
// console.log('sandbox loaded');
|
||||
window.addEventListener('message', function (event) {
|
||||
// console.log('message received', event);
|
||||
const data = event.data;
|
||||
const execFunc = new Function(
|
||||
'return ' + data.code
|
||||
);
|
||||
const result = execFunc();
|
||||
event.source.postMessage({ result }, event.origin);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="width:400px;height:450px">
|
||||
<html lang="en" style="width:400px;height:500px">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Clear Wallet</title>
|
||||
|
|
71
package.json
|
@ -1,58 +1,61 @@
|
|||
{
|
||||
"name": "clear-wallet",
|
||||
"version": "1.2.8",
|
||||
"version": "1.3.7",
|
||||
"private": true,
|
||||
"description": "Clear Wallet (CLW) is a wallet that helps you manage your Ethereum assets and interact with Ethereum dApps and contracts with the main focus on absolute privacy.",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"inject": "tsc --outFile src/extension/inject.js src/extension/inject.ts",
|
||||
"inject": "tsc --downlevelIteration --outFile src/extension/inject.js src/extension/inject.ts",
|
||||
"content": "tsc --outFile src/extension/content.js src/extension/content.ts",
|
||||
"post-build": "ts-node ./release-scripts/post-build.ts",
|
||||
"post-build": "yarn tsx ./release-scripts/post-build.ts",
|
||||
"build": "yarn inject && yarn content && vue-tsc --noEmit && vite build && yarn post-build",
|
||||
"preview": "vite preview",
|
||||
"release": "yarn config set version-tag-prefix clear-wallet@v && yarn config set version-git-message 'clear-wallet@v%s' && yarn version --patch && yarn postversion",
|
||||
"postversion": "git push",
|
||||
"pub": "yarn build && yarn release && ts-node ./release-scripts/create-release.ts"
|
||||
"pub": "yarn build && yarn release && yarn tsx ./release-scripts/create-release.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/app": "^4.1.1",
|
||||
"@capacitor/core": "^4.7.1",
|
||||
"@capacitor/haptics": "^4.1.0",
|
||||
"@capacitor/keyboard": "^4.1.1",
|
||||
"@capacitor/status-bar": "^4.1.1",
|
||||
"@ionic/vue": "^7.0.0",
|
||||
"@ionic/vue-router": "^7.0.0",
|
||||
"@types/chrome": "^0.0.227",
|
||||
"core-js": "^3.29.1",
|
||||
"ethers": "^5.7.2",
|
||||
"vue": "^3.2.47",
|
||||
"vue-router": "^4.1.6"
|
||||
"@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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^4.7.1",
|
||||
"@crxjs/vite-plugin": "^1.0.14",
|
||||
"@capacitor/cli": "^5.2.3",
|
||||
"@crxjs/vite-plugin": "^2.0.0-beta.23",
|
||||
"@types/archiver": "^5.3.2",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/node": "^18.15.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
"@typescript-eslint/parser": "^5.57.0",
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"@types/chrome": "^0.0.243",
|
||||
"@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",
|
||||
"archiver": "^5.3.1",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-plugin-vue": "^9.10.0",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"http-browserify": "^1.7.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest": "^29.6.2",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
"sass": "^1.60.0",
|
||||
"sass": "^1.65.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tsx": "^4.8.0",
|
||||
"typescript": "^5.1.6",
|
||||
"util": "^0.12.5",
|
||||
"vite": "^4.2.1",
|
||||
"vue-tsc": "^1.2.0",
|
||||
"vite": "^5.2.10",
|
||||
"vue-tsc": "^1.8.8",
|
||||
"yarn-upgrade-all": "^0.7.2"
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
"disabledNativeDependencies": {
|
||||
"@capacitor/app": "^5.0.6",
|
||||
"@capacitor/core": "^5.2.3",
|
||||
"@capacitor/haptics": "^5.0.6",
|
||||
"@capacitor/keyboard": "^5.0.6",
|
||||
"@capacitor/status-bar": "^5.0.6"
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 260 B |
After Width: | Height: | Size: 680 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 7.6 KiB |
|
@ -1 +0,0 @@
|
|||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="width:400px;height:450px">
|
||||
<html lang="en" style="width:400px;height:500px">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Clear Wallet</title>
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
|
||||
(async () => {
|
||||
const CONTENT_BUILD_PATH = 'src/extension/content.js'
|
||||
const METAMASK_STUB_PATH = 'src/extension/metamask-stub.js'
|
||||
const METAMASK_INJECT_PATH = 'src/extension/inject.js'
|
||||
const fs = (await import('fs')).default
|
||||
const path = (await import('path')).default
|
||||
const pkg = JSON.parse(fs.readFileSync('dist/manifest.json').toString());
|
||||
pkg.content_scripts[0].js[0] = CONTENT_BUILD_PATH
|
||||
pkg.content_scripts[1].js[0] = METAMASK_STUB_PATH
|
||||
pkg.content_scripts[1].js[0] = METAMASK_INJECT_PATH
|
||||
fs.writeFileSync('dist/manifest.json', JSON.stringify(pkg, null, 2))
|
||||
// fs.writeFileSync('dist/rules.js', fs.readFileSync('rules.json').toString())
|
||||
fs.writeFileSync('dist/'+ CONTENT_BUILD_PATH, fs.readFileSync('src/extension/content.js').toString())
|
||||
fs.writeFileSync('dist/'+ METAMASK_STUB_PATH, fs.readFileSync('src/extension/metamask-stub.js').toString())
|
||||
fs.writeFileSync('dist/'+ METAMASK_INJECT_PATH, fs.readFileSync('src/extension/inject.js').toString())
|
||||
const directory = 'dist/assets/';
|
||||
fs.readdir(directory, (err, files) => {
|
||||
files.forEach(file => {
|
||||
|
|
59
src/App.vue
|
@ -9,6 +9,8 @@ import { IonApp, IonRouterOutlet } from "@ionic/vue";
|
|||
import { defineComponent, onBeforeMount, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { getSettings } from "@/utils/platform";
|
||||
import { getSelectedAddress } from "@/utils/wallet";
|
||||
import type { RequestArguments } from "@/extension/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "App",
|
||||
|
@ -21,6 +23,56 @@ export default defineComponent({
|
|||
const router = useRouter();
|
||||
const { param, rid } = route.query;
|
||||
|
||||
const pageListener = (
|
||||
message: RequestArguments,
|
||||
sender: any,
|
||||
sendResponse: (a: any) => any
|
||||
) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.info("Error receiving message:", chrome.runtime.lastError);
|
||||
}
|
||||
if (message?.type !== "CLWALLET_PAGE_MSG") {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.info("page listener:", message);
|
||||
|
||||
(async () => {
|
||||
if (!message?.method) {
|
||||
sendResponse({
|
||||
code: 500,
|
||||
message: "Invalid request method",
|
||||
});
|
||||
} else {
|
||||
// ETH API
|
||||
switch (message.method) {
|
||||
case "paste": {
|
||||
const currentAddress = (await getSelectedAddress()) as string[];
|
||||
if (currentAddress.length > 0) {
|
||||
document.execCommand("insertText", false, currentAddress[0]);
|
||||
}
|
||||
sendResponse(true);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
sendResponse({
|
||||
error: true,
|
||||
message:
|
||||
"ClearWallet: Invalid PAGE request method " + (message?.method ?? ""),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
};
|
||||
|
||||
if (chrome?.runtime?.onMessage) {
|
||||
chrome.runtime.onMessage.addListener(pageListener);
|
||||
console.info("page listener set");
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
getSettings().then((settings) => {
|
||||
if (settings.theme !== "system") {
|
||||
|
@ -30,6 +82,13 @@ export default defineComponent({
|
|||
});
|
||||
});
|
||||
|
||||
// onUnmounted(() => {
|
||||
// if (chrome?.runtime?.onMessage) {
|
||||
// chrome.runtime.onMessage.removeListener(pageListener);
|
||||
// console.info("page listener removed");
|
||||
// }
|
||||
// });
|
||||
|
||||
onMounted(() => {
|
||||
switch (route?.query?.route ?? "") {
|
||||
case "sign-msg": {
|
||||
|
|
|
@ -1,28 +1,17 @@
|
|||
|
||||
(() =>{
|
||||
try {
|
||||
const container = document.documentElement;
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('async', "false")
|
||||
script.setAttribute('fetchpriority', "high")
|
||||
script.src = chrome.runtime.getURL('src/extension/inject.js')
|
||||
container.prepend(script)
|
||||
script.addEventListener('load', () => { container.removeChild(script) } )
|
||||
} catch (error) {
|
||||
console.error('MetaMask: Provider injection failed.', error);
|
||||
}
|
||||
})()
|
||||
|
||||
const allowedMethods = {
|
||||
'eth_accounts': true,
|
||||
'eth_requestAccounts' : true,
|
||||
'eth_requestAccounts': true,
|
||||
'eth_chainId': true,
|
||||
'personal_sign' : true,
|
||||
'personal_sign': true,
|
||||
'wallet_requestPermissions': true,
|
||||
'wallet_registerOnboarding': true,
|
||||
'wallet_revokePermissions': true,
|
||||
'eth_gasPrice': true,
|
||||
'eth_getBlockByNumber': true,
|
||||
'eth_blockNumber': true,
|
||||
'eth_estimateGas': true,
|
||||
'eth_syncing': true,
|
||||
'eth_sign': true,
|
||||
'net_version': true,
|
||||
'eth_sendTransaction': true,
|
||||
|
@ -50,42 +39,72 @@ const allowedMethods = {
|
|||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source != window)
|
||||
return;
|
||||
// console.log(event)
|
||||
if (event.data.type && (event.data.type === "CLWALLET_CONTENT")) {
|
||||
event.data.data.resId = event.data.resId
|
||||
event.data.data.type = "CLWALLET_CONTENT_MSG"
|
||||
event.data.data.website = document?.location?.href ?? ''
|
||||
if((event?.data?.data?.method ?? 'x') in allowedMethods) {
|
||||
chrome.runtime.sendMessage(event.data.data, (res) => {
|
||||
const data = { type: "CLWALLET_PAGE", data: res, resId: event.data.resId };
|
||||
// console.log('data back', data)
|
||||
window.postMessage(data, "*");
|
||||
})
|
||||
}
|
||||
else {
|
||||
const data = { type: "CLWALLET_PAGE", data: { error: true, message: 'ClearWallet: Unknown method requested ' + event?.data?.data?.method ?? ''}, resId: event.data.resId };
|
||||
window.postMessage(data, "*");
|
||||
}
|
||||
} else if (event.data.type && (event.data.type === "CLWALLET_PING")) {
|
||||
event.data.data.resId = event.data.resId
|
||||
event.data.data.type = "CLWALLET_CONTENT_MSG"
|
||||
event.data.data.method = "wallet_connect"
|
||||
event.data.data.params = Array(0)
|
||||
chrome.runtime.sendMessage(event.data.data , async (res) => {
|
||||
window.postMessage(res, "*");
|
||||
return;
|
||||
if (event?.data?.type === "CLWALLET_CONTENT") {
|
||||
event.data.data.data.resId = event.data.resId
|
||||
event.data.data.data.type = "CLWALLET_CONTENT_MSG"
|
||||
event.data.data.data.website = document?.location?.href ?? ''
|
||||
if ((event?.data?.data?.method ?? 'x') in allowedMethods) {
|
||||
event.data.data.data.method = event?.data?.data?.method ?? ''
|
||||
chrome?.runtime?.sendMessage(event.data.data.data, (res) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC1: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
const id = Number(event.data.resId.replace(/[A-Za-z]/g, '').slice(0, 10))
|
||||
const data = {
|
||||
target: 'metamask-inpage',
|
||||
type: "CLWALLET_PAGE",
|
||||
resId: event.data.resId,
|
||||
data: { name: 'metamask-provider', data: {
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
result: res,
|
||||
},
|
||||
id,
|
||||
method: event?.data?.data?.data?.method ?? '',
|
||||
params: event?.data?.data?.data?.params ?? [],
|
||||
},
|
||||
}
|
||||
|
||||
// console.info('data out', data)
|
||||
window.postMessage(data, "*");
|
||||
})
|
||||
}
|
||||
else {
|
||||
const data = {
|
||||
type: "CLWALLET_PAGE",
|
||||
data: {
|
||||
data: {
|
||||
result: { error: true, message: 'ClearWallet: Unknown method requested ' + (event?.data?.data?.data?.method ?? '') }
|
||||
} }
|
||||
, resId: event.data.resId };
|
||||
window.postMessage(data, "*");
|
||||
}
|
||||
} else if (event?.data?.type === "CLWALLET_PING") {
|
||||
event.data.data.data.resId = event.data.resId
|
||||
event.data.data.data.type = "CLWALLET_CONTENT_MSG"
|
||||
event.data.data.data.method = "wallet_connect"
|
||||
event.data.data.data.params = Array(0)
|
||||
chrome.runtime.sendMessage(event.data.data.data, async (res) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC2: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
window.postMessage(res, "*");
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
chrome.runtime.onMessage.addListener((message: any , sender, sendResponse) => {
|
||||
if(message.type === "CLWALLET_EXT_LISTNER") {
|
||||
const data = { type: "CLWALLET_PAGE_LISTENER", data: message.data };
|
||||
chrome.runtime.onMessage.addListener((message: any, sender, sendResponse) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("Error receiving message:", chrome.runtime.lastError);
|
||||
}
|
||||
if (message.type === "CLWALLET_EXT_LISTNER") {
|
||||
const data = { type: "CLWALLET_PAGE_LISTENER", data: message.data };
|
||||
// console.log('data listner', data)
|
||||
window.postMessage(data, "*");
|
||||
}
|
||||
return true
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -4,6 +4,46 @@ interface RequestArguments {
|
|||
params?: unknown[] | object;
|
||||
}
|
||||
|
||||
interface EIP6963ProviderInfo {
|
||||
uuid: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
rdns: string;
|
||||
}
|
||||
|
||||
const ProviderInfo: EIP6963ProviderInfo = {
|
||||
uuid: '1fa914a1-f8c9-4c74-8d84-4aa93dc90eec',
|
||||
name: 'Clear Wallet',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAGN0lEQVRoge1ZfUgbZxj/3eXLRK112gmDwWYjo9gVin8UN509aTsGG26ywUb/WrWTVtgK1lLGPurGRoe6rmtBN+r219wfhdaJUFw/ImgrMtuO0NrSIhuibU3RJqaa5HK5G8/rJWh6l1yStVLwBw9JLu897/P9PO8dVrGKVTzd4LSk37p1a/RrDYA9AF4BEFHpScCk0jCADgA98XsODAywT3MCYVqLior2NzU1obS0FLIsM3oS4Hme0djY2I6jR4/umJycbHO5XM2CIDyyu54Htq1bt+7swYMH4XQ68ePHl2HLssBs5R+7+AoAKRQBx3No7qhAMBhEQ0MDJiYmtgM4F12XzAN7Nm3ahPz8fHz1wbCqZ/ixCx+P5jfPorVvO+rr69Ha2nrW7/cXAJhdukzPpJUk/Mn2CXC8GRxvWhECx+O7jy5hy5YtqKiogJqTy6DnASvFuyQSIyUt63EcYM8xIyfPAimSZu4oCnwzYVitVhYNAIqMKiApigKrzQw+RQV4nkPdoZfx/Et2jI+PY3Z2FsSL4zTTLYn8Cmw2B7xeLx4+fEiXvEYVYOB4ftGVKQj/zclyTE1N4cCBL3Hz5k0mBFWUdEGK0/2iKBKHX1NSgOdNSEF+vNtYjDt37qCurg6hUKgJwM9qYUm4TxJEXRcAEEpJAbI+WdXQLhywuaoQLS0tJPx+l8v1vSAIrwJ4QxUi0ybCqzwGAfxpWAHOoPufKbJhYWEBbrebfv5GwjudzqHKykqYTKaMmyCFEfEg/qOjo69HlTAQQsaSeE2BlcUpNR4A8wDKcnNzUV5ejuLiYpYLmYByQZIkxmF0dLTSkAKLIWRsVzmy2P7J2ipfSyAQgMfjwdVRLq0qtBwK3nvfCYvFQldt0b8SK8BRCBljT2vjIEYiEWb5YDDzLm618swD4TDjFUvm5CFkMIl5nXJFCoQlbQUc2RbIcgSBBQlcEkvxJm1RE5c31gcMuiDBOtVqMWzYUIAq4QXcvn0bd+/eh9lsxr//AHM+my4PvYaaJISMK5DIgmoTYti48Vkm/LFjx9Df388qS0lJCaqrq8FxBbjv0fYkx2tXscTSkVCpkJ4CYTFG1dvW4/jx4zh16lTH/Px8YSAQKHS73R19fX0oKFxAMBRctj5K8V40pABVDuYFQ6SfK6IYZpSdbWG94vTp03R5n8vlmiEC8AnNTRMTE8jK4mPr40kLiXOAhDJa/hIqsFg0JGnZdktv4MgA1Dd83nBs/VKYTOmE0P+EaBhMe7xwOByora0lxkcEQSggAvBDWVkZ6BA1MzunGUKiTggl9ICikhEkWrfU/T09V9HY2Egm39Pf308PDFBVVYWamhoMDk5CFCVtQc3aHs5kSjQMsmAUF1zXWV/Yu3cvdu3axa6GQiF0/z6CGzc8uiwt2no9KQ+Iy36fP38NFy5cR36+A7KswOud13u+EIPFkoYHFCiQDaqgaKyLHkb0SuD0tKh5XQuiqJ2uiRVQFCiKsTFYY9q0kvDsOMnLMJsyqxfUH6hjpzTMLSpgzAM0hFJXpQGOKiZNEOQBmkgPffFWjF+6IF6UKykNc2R92eCefp8Eq9WOrKwsalbZAC7Pzc3h4sWLuHLlyv9yHsjLy2MPCtRTWXIFZEWBbDCEZu6H4HAUsVo+MDCw0+VytQuCUDE1NbVyR0qWwoqxTkyRc+2veezevRvDw8NtgiAo6qH+7xU71C+GkPGT1MkuD1o6X0RXVxdNm+1ut7udQkc9paWF6Iylxn9WvBJJ+4DREAJrWMDnDeOob34Ohw8fZglHZ+RMngvZ7XYmfGdnJ3p7ez8E0GlEATNpLgYlKEpqm4fDCjq+nWSznSPHhNw8EyKR9BKY8t43I+Hrn5zIycmhS2sfEVTnXpGsp/ALkBVHWpuT+/xzMvxzmZ2H16w1s07+4MED+jkd/7+eeQfv3bsH4Z0QZFliubASREXnsyPrMTIygqGhIZLrj3hB9TzQcevWrdqxsTHs3LcZPb8Adkc2zDSPZFbODSEclqHIPD5tK2E5dOLECfj9/u3x7wYSKXDO5/O19fb27qenwq+9XQqLxZ9SZ84UdG44c+YMuru7ob5iOpfKK6bo15V+yXdJfcn3SOhEXzGtYhWreJoB4D9CrzrJ8WeKXgAAAABJRU5ErkJggg==',
|
||||
rdns: 'clear-wallet.flashsoft.eu/',
|
||||
}
|
||||
|
||||
const THROTTLE_LEVEL = 20;
|
||||
const THROTTLE_TIMEOUT = 500;
|
||||
const MAX_PROMISES = 50
|
||||
|
||||
function loadEIP1193Provider(provider: any) {
|
||||
|
||||
function announceProvider() {
|
||||
const info: EIP6963ProviderInfo = ProviderInfo
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("eip6963:announceProvider", {
|
||||
detail: Object.freeze({ info, provider }),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"eip6963:requestProvider",
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
(event: any) => {
|
||||
announceProvider();
|
||||
}
|
||||
);
|
||||
|
||||
announceProvider();
|
||||
}
|
||||
|
||||
const listners = {
|
||||
accountsChanged: new Set<(p?: any) => void>(),
|
||||
connect: new Set<(p?: any) => void>(),
|
||||
|
@ -33,27 +73,43 @@ const getListnersCount = (): number => {
|
|||
return count
|
||||
}
|
||||
|
||||
const sendMessage = (args: RequestArguments, ping = false) => {
|
||||
if(Object.values(promResolvers).filter(r=> r).length < 10 ) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const resId = crypto.randomUUID()
|
||||
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 })
|
||||
}
|
||||
|
||||
const resId = [...`${Math.random().toString(16) + Date.now().toString(16)}`].slice(2).join('')
|
||||
promResolvers.set(resId, { resolve, reject })
|
||||
const data = { type: "CLWALLET_CONTENT", data: args, resId};
|
||||
const p = [ "eth_signTypedData", "eth_signTypedData_v3", "eth_signTypedData_v4"]
|
||||
const method = args.method
|
||||
if (p.includes(args.method)) {
|
||||
args.method = undefined as any
|
||||
}
|
||||
const data = {
|
||||
type: "CLWALLET_CONTENT",
|
||||
target: 'metamask-contentscript',
|
||||
data: {
|
||||
method,
|
||||
name: 'metamask-provider', data: args, jsonrpc: '2.0', id: Number(resId.replace(/[A-Za-z]/g, '').slice(0, 10)) },
|
||||
resId,
|
||||
from,
|
||||
}
|
||||
if (ping) {
|
||||
data.type = 'CLWALLET_PING'
|
||||
}
|
||||
// console.log('data in', data)
|
||||
|
||||
// console.info('data in', data)
|
||||
window.postMessage(data, "*");
|
||||
})
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
reject(new Error("You have reached the maximum number of concurent wallet messeges."))
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
class MetaMaskAPI {
|
||||
isMetaMask = true
|
||||
isClWallet = true
|
||||
_state = {accounts: Array(1), isConnected: true, isUnlocked: true, initialized: true, isPermanentlyDisconnected: false}
|
||||
_sentWarnings = {enable: false, experimentalMethods: false, send: false, events: {}}
|
||||
// Deprecated - hardcoded for now, websites should not access this directly since is deprecated for a long time
|
||||
|
@ -80,7 +136,7 @@ class MetaMaskAPI {
|
|||
_events: {}, _eventsCount: 0, _maxListeners: undefined, _middleware: Array(4)
|
||||
}
|
||||
isConnected() {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
// for maximum compatibility since is cloning the same API
|
||||
|
||||
|
@ -89,25 +145,26 @@ class MetaMaskAPI {
|
|||
}
|
||||
|
||||
request(args: RequestArguments): Promise<unknown> {
|
||||
return sendMessage(args)
|
||||
return sendMessage(args) as Promise<unknown>
|
||||
}
|
||||
// Deprecated
|
||||
sendAsync (arg1: any, arg2: any): void | Promise<unknown> {
|
||||
// return this.send(arg1, arg2) as any
|
||||
if( typeof arg1 === 'string' ) {
|
||||
return sendMessage({
|
||||
method: arg1,
|
||||
params: arg2 as object
|
||||
})
|
||||
}, false , 'sendAsync') as Promise<unknown>
|
||||
}else if (typeof arg2 === 'function'){
|
||||
sendMessage(arg1 as RequestArguments).then(result => {
|
||||
((sendMessage(arg1 as RequestArguments, false, 'sendAsync') as Promise<unknown>).then(result => {
|
||||
(arg2 as (e?: any, r?: any) => any )(undefined, {
|
||||
id: (arg1 as RequestArguments)?.id,
|
||||
jsonrpc: '2.0',
|
||||
method: (arg1 as RequestArguments).method,
|
||||
result
|
||||
}
|
||||
)
|
||||
}).catch( e => {
|
||||
)
|
||||
}) as Promise<unknown>).catch( e => {
|
||||
(arg2 as (er?: any, r?: any) => any )(new Error(e), {
|
||||
id: (arg1 as RequestArguments)?.id,
|
||||
jsonrpc: '2.0',
|
||||
|
@ -116,45 +173,44 @@ class MetaMaskAPI {
|
|||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return sendMessage(arg1 as RequestArguments, false, 'sendAsync') as Promise<unknown>
|
||||
}
|
||||
}
|
||||
// Deprecated
|
||||
send (arg1: unknown, arg2: unknown): unknown {
|
||||
const resultFmt = async (result: Promise<any>) => {
|
||||
return {
|
||||
"id": 0,
|
||||
"jsonrpc": "2.0",
|
||||
result: await result
|
||||
}
|
||||
}
|
||||
if (arg2 === undefined) {
|
||||
if( typeof arg1 === 'string' ) {
|
||||
return sendMessage({
|
||||
|
||||
return resultFmt(sendMessage({
|
||||
method: arg1,
|
||||
params: undefined
|
||||
})
|
||||
} else if (typeof arg1 === 'object') {
|
||||
return sendMessage(arg1 as RequestArguments)
|
||||
}, false, 'send'))
|
||||
} else {
|
||||
console.error('Clear Wallet: faulty request')
|
||||
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'))
|
||||
}
|
||||
}else if( typeof arg1 === 'string' ) {
|
||||
return sendMessage({
|
||||
return resultFmt( sendMessage({
|
||||
method: arg1,
|
||||
params: arg2 as object
|
||||
})
|
||||
}, false, 'send'))
|
||||
}else if (typeof arg2 === 'function'){
|
||||
sendMessage(arg1 as RequestArguments).then(result => {
|
||||
(arg2 as (e?: any, r?: any) => any )(undefined, {
|
||||
id: (arg1 as RequestArguments)?.id,
|
||||
jsonrpc: '2.0',
|
||||
method: (arg1 as RequestArguments).method,
|
||||
result
|
||||
}
|
||||
)
|
||||
}).catch( e => {
|
||||
(arg2 as (er?: any, r?: any) => any )(new Error(e), {
|
||||
id: (arg1 as RequestArguments)?.id,
|
||||
jsonrpc: '2.0',
|
||||
method: (arg1 as RequestArguments).method,
|
||||
error: new Error(e)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
return resultFmt( sendMessage(arg1 as RequestArguments, false, 'send'))
|
||||
} else {
|
||||
return resultFmt(sendMessage(arg1 as RequestArguments , false, 'send'))
|
||||
}
|
||||
}
|
||||
on (eventName: string, callback: () => void) {
|
||||
this.addListener(eventName, callback)
|
||||
|
@ -272,92 +328,77 @@ class MetaMaskAPI {
|
|||
_handleStreamDisconnect() { return true }
|
||||
_handleUnlockStateChanged() { return true }
|
||||
_sendSync () {
|
||||
console.error('Clear Wallet: Sync calling is deprecated and not supported')
|
||||
console.warn('ERROR: Clear Wallet: Sync calling is deprecated and not supported')
|
||||
}
|
||||
}
|
||||
|
||||
const eth = new Proxy( new MetaMaskAPI(), {
|
||||
// set: () => { return true },
|
||||
// get: function(target, name, receiver) {
|
||||
// if (typeof (<any>target)[name] == 'function') {
|
||||
// return function (...args: any) {
|
||||
// console.dir({ call: [name, ...args] });
|
||||
// return undefined;
|
||||
// }
|
||||
// }
|
||||
|
||||
// let check = true
|
||||
// setTimeout(() => check = false, 400)
|
||||
// while(check){
|
||||
// // igmore
|
||||
// }
|
||||
// },
|
||||
deleteProperty: () => { return true },
|
||||
// set(obj, prop, value) {
|
||||
// // Reflect.set(obj, prop, value);
|
||||
// return true;
|
||||
// }
|
||||
})
|
||||
|
||||
const listner = function(event: any) {
|
||||
if (event.source != window) return;
|
||||
|
||||
if (event.data.type && (event.data.type === "CLWALLET_PAGE")) {
|
||||
if(!['CLWALLET_PAGE', 'CLWALLET_PAGE_LISTENER'].includes(event?.data?.type)) return;
|
||||
const eventData = event?.data
|
||||
const eventDataData = event?.data?.data
|
||||
const eventDataDataData = event?.data?.data?.data
|
||||
const resId = eventData?.resId
|
||||
const result = eventDataDataData?.result
|
||||
if (eventData?.type === "CLWALLET_PAGE") {
|
||||
try {
|
||||
if(event?.data?.data?.error){
|
||||
promResolvers.get(event.data.resId)?.reject(event.data.data);
|
||||
console.error(event?.data?.data)
|
||||
if(result?.error){
|
||||
promResolvers.get(resId).reject(result);
|
||||
}else {
|
||||
promResolvers.get(event.data.resId)?.resolve(event.data.data);
|
||||
promResolvers.get(resId).resolve(result);
|
||||
}
|
||||
promResolvers.delete(event.data.resId)
|
||||
} catch (e) {
|
||||
// console.log('Failed to connect resolve msg', e)
|
||||
// console.error('Failed to connect resolve msg', e)
|
||||
promResolvers.get(resId)?.reject({code: -32000, message: 'Failed to connect resolve msg', error: true });
|
||||
}
|
||||
} else if( event.data.type && (event.data.type === "CLWALLET_PAGE_LISTENER")) {
|
||||
if((event?.data?.data?.listner ?? 'x') in listners ) {
|
||||
} else if(eventData?.type === "CLWALLET_PAGE_LISTENER") {
|
||||
if((eventDataData?.listner ?? 'x') in listners ) {
|
||||
try {
|
||||
const listnerName = event?.data?.data?.listner as ('accountsChanged' | 'connect' | 'disconnect' | 'chainChanged')
|
||||
if( listnerName === 'connect' && event?.data?.data?.data) {
|
||||
(<any>eth).networkVersion = event?.data?.data?.data?.chainId?.toString(10) ?? '137';
|
||||
(<any>eth).chainId = event?.data?.data?.data?.chainId ?? '0x89';
|
||||
(<any>eth).selectedAddress = event?.data?.data?.address ?? null;
|
||||
const listnerName = eventDataData.listner as ('accountsChanged' | 'connect' | 'disconnect' | 'chainChanged')
|
||||
if( listnerName === 'connect' && eventDataData) {
|
||||
(<any>eth).networkVersion = String(parseInt(eventDataDataData?.chainId ?? "0x89", 16));
|
||||
(<any>eth).chainId = eventDataDataData?.chainId ?? '0x89';
|
||||
(<any>eth).selectedAddress = eventDataData?.address?.[0] ?? null;
|
||||
(<any>eth).accounts = eventDataData.address?.[0] ? [eventDataData.address?.[0]] : [];
|
||||
(<any>eth).isConnected = () => true;
|
||||
} else if( listnerName === 'chainChanged' ) {
|
||||
// console.log(event?.data?.data?.data);
|
||||
(<any>eth).networkVersion = event?.data?.data?.data.toString(10) ?? '137';
|
||||
(<any>eth).chainId = event?.data?.data?.data ?? '0x89';
|
||||
(<any>eth).networkVersion = String(parseInt(eventDataDataData ?? "0x89", 16));
|
||||
(<any>eth).chainId = eventDataData ?? '0x89';
|
||||
} else if ( listnerName === 'accountsChanged' ) {
|
||||
(<any>eth).selectedAddress = event?.data?.data?.data?.address ?? 'dummy-string';
|
||||
(<any>eth).accounts = eventDataData?.[0] ? [eventDataData?.[0]] : [];
|
||||
(<any>eth).selectedAddress = eventDataData?.[0] ?? '';
|
||||
}
|
||||
listners[listnerName].forEach(listner => listner(event?.data?.data?.data));
|
||||
listners[listnerName].forEach(listner => listner(eventDataDataData));
|
||||
listners.once[listnerName].forEach(listner => {
|
||||
listner(event?.data?.data?.data)
|
||||
listner(eventDataData)
|
||||
listners.once[listnerName].delete(listner)
|
||||
});
|
||||
} catch (e) {
|
||||
// console.error(e)
|
||||
// console.info(e)
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(promResolvers.has(resId)) {
|
||||
promResolvers.delete(resId)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message",listner)
|
||||
|
||||
// const proxy1 = new Proxy({
|
||||
// // on: (event: any, callback:any) => { if (event === 'message') {
|
||||
// // debugger;
|
||||
// // callback(true, true)
|
||||
// // } }
|
||||
// }, {
|
||||
// get: function(target, name, receiver) {
|
||||
// if (typeof (<any>target)[name] == 'function') {
|
||||
// return function (...args: any) {
|
||||
// console.dir({ call: [name, ...args] });
|
||||
// }
|
||||
// }
|
||||
|
||||
// console.log('ETH', name.toString() , target, receiver);
|
||||
// return undefined
|
||||
// }
|
||||
// })
|
||||
Object.defineProperties(eth, {
|
||||
selectedAddress: { enumerable: false },
|
||||
chainId: { enumerable: false },
|
||||
networkVersion: { enumerable: false },
|
||||
});
|
||||
|
||||
const web3Shim = {
|
||||
currentProvider: eth,
|
||||
|
@ -365,7 +406,12 @@ const web3Shim = {
|
|||
}
|
||||
|
||||
const injectWallet = (win: any) => {
|
||||
Object.defineProperty(win, 'ethereum', {
|
||||
const ethKey = 'ethereum'
|
||||
if (win[ethKey]?.isClWallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(win, ethKey, {
|
||||
value: eth,
|
||||
});
|
||||
Object.defineProperty(win, 'web3', {
|
||||
|
@ -374,28 +420,81 @@ Object.defineProperty(win, 'web3', {
|
|||
sendMessage({
|
||||
method: 'wallet_ready'
|
||||
}, true)
|
||||
// console.log('Clear wallet injected', (window as any).ethereum, win)
|
||||
}
|
||||
|
||||
injectWallet(this);
|
||||
loadEIP1193Provider(eth)
|
||||
|
||||
|
||||
// HELPERS TO CLONE METAMASK API
|
||||
|
||||
// window.addEventListener("message" , (event) => {
|
||||
// console.log('event', JSON.stringify(event?.data?.data, null, 2), JSON.stringify(event?.data, null, 2))
|
||||
// })
|
||||
|
||||
// setTimeout(() => {
|
||||
// // console.log('Metamask clone test');
|
||||
// // (<any>window).ethereum.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x89"}]}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum2.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x89"}]}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.on('connect', ((a: any, b: any) => console.log('connect', a, b)));
|
||||
// // (<any>window).ethereum.on('accountsChanged', ((a: any, b: any) => console.log('accountsChanged', a, b)));
|
||||
// // (<any>window).ethereum.on('chainChanged', ((a: any) => console.log('chainChanged', a, typeof a)));
|
||||
// // console.log((<any>window).ethereum.on('message', (a: any, b:any) => console.log(a,b)))
|
||||
// console.log((<any>window).ethereum.toString())
|
||||
// console.log((<any>window).ethereum2.toString())
|
||||
// console.log((<any>window).ethereum.Symbold)
|
||||
// console.log('Metamask clone test');
|
||||
// console.log((<any>window).ethereum.send({
|
||||
// "jsonrpc": "2.0",
|
||||
// "method": "eth_accounts",
|
||||
// "params": [],
|
||||
// "id": 0
|
||||
// }))
|
||||
// console.log((<any>window).ethereum.request({
|
||||
// "jsonrpc": "2.0",
|
||||
// "method": "eth_accounts",
|
||||
// "params": [],
|
||||
// "id": 0
|
||||
// }))
|
||||
// }, 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')});
|
||||
|
||||
// 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')});
|
||||
|
||||
// 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')});
|
||||
|
||||
// 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')});
|
||||
|
||||
// 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')});
|
||||
|
||||
// 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')});
|
||||
|
||||
// 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')});
|
||||
|
||||
// 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)));
|
||||
|
||||
// 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)));
|
||||
|
||||
// }, 3500)
|
||||
|
||||
// console.log( (window as any).ethereum.request({method: 'eth_chainId'}))
|
|
@ -2,10 +2,15 @@ import type { listnerType } from '@/extension/types'
|
|||
|
||||
export const triggerListner = ( type: listnerType, listnerData: any ) => {
|
||||
const data = { type: "CLWALLET_EXT_LISTNER", data: { listner: type, data: listnerData } }
|
||||
chrome.tabs.query({}, (tabs) => tabs.forEach( tab =>
|
||||
{
|
||||
chrome.tabs.query({}, (tabs) => tabs.forEach( async tab =>
|
||||
{
|
||||
if (tab?.id) {
|
||||
chrome.tabs.sendMessage(tab.id, data)
|
||||
try {
|
||||
await chrome.tabs.sendMessage(tab.id, data)
|
||||
} catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"name": "__MSG_appName__",
|
||||
"description": "__MSG_appDesc__",
|
||||
"default_locale": "en",
|
||||
"version": "1.2.8",
|
||||
"version_name": "1.2.8",
|
||||
"version": "1.3.7",
|
||||
"version_name": "1.3.7",
|
||||
"icons": {
|
||||
"16": "assets/extension-icon/wallet_16.png",
|
||||
"32": "assets/extension-icon/wallet_32.png",
|
||||
|
@ -23,11 +23,14 @@
|
|||
"minimum_chrome_version": "103",
|
||||
"permissions": [
|
||||
"notifications",
|
||||
"activeTab",
|
||||
"storage",
|
||||
"alarms",
|
||||
"unlimitedStorage",
|
||||
"clipboardRead",
|
||||
"clipboardWrite"
|
||||
"clipboardWrite",
|
||||
"contextMenus",
|
||||
"scripting"
|
||||
],
|
||||
"host_permissions": [
|
||||
"*://*/*"
|
||||
|
@ -44,7 +47,7 @@
|
|||
],
|
||||
"all_frames": true,
|
||||
"run_at": "document_start",
|
||||
"js": ["/src/extension/content.ts"]
|
||||
"js": ["/src/extension/content.js"]
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
|
@ -53,12 +56,17 @@
|
|||
],
|
||||
"all_frames": true,
|
||||
"run_at": "document_start",
|
||||
"js": ["/src/extension/metamask-stub.js"],
|
||||
"js": ["/src/extension/inject.js"],
|
||||
"world": "MAIN"
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": [{
|
||||
"resources": ["src/extension/inject.js"],
|
||||
"matches": ["<all_urls>"]
|
||||
}]
|
||||
}],
|
||||
"sandbox": {
|
||||
"pages": [
|
||||
"eval-sandbox.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,102 @@
|
|||
import { getSelectedAccount, getSelectedNetwork, smallRandomString, getSettings, clearPk, openTab, getUrl, addToHistory, getNetworks, strToHex, numToHexStr } from '@/utils/platform';
|
||||
import { userApprove, userReject, rIdWin, rIdData } from '@/extension/userRequest'
|
||||
import { signMsg, getBalance, getBlockNumber, estimateGas, sendTransaction, getGasPrice, getBlockByNumber, evmCall, getTxByHash, getTxReceipt, signTypedData, getCode, getTxCount } from '@/utils/wallet'
|
||||
import {
|
||||
CLW_CONTEXT_MENU_ID,
|
||||
getSelectedAccount,
|
||||
getSelectedNetwork,
|
||||
smallRandomString,
|
||||
getSettings,
|
||||
clearPk,
|
||||
openTab,
|
||||
getUrl,
|
||||
addToHistory,
|
||||
getNetworks,
|
||||
strToHex,
|
||||
numToHexStr,
|
||||
enableRightClickVote,
|
||||
} from '@/utils/platform';
|
||||
import {
|
||||
userApprove,
|
||||
userReject,
|
||||
rIdWin,
|
||||
rIdData,
|
||||
} from '@/extension/userRequest'
|
||||
import {
|
||||
signMsg,
|
||||
getBalance,
|
||||
getBlockNumber,
|
||||
estimateGas,
|
||||
sendTransaction,
|
||||
getGasPrice,
|
||||
getBlockByNumber,
|
||||
evmCall,
|
||||
getTxByHash,
|
||||
getTxReceipt,
|
||||
signTypedData,
|
||||
getCode,
|
||||
getTxCount,
|
||||
getSelectedAddress
|
||||
} from '@/utils/wallet'
|
||||
import type { RequestArguments } from '@/extension/types'
|
||||
import { rpcError } from '@/extension/rpcConstants'
|
||||
import { updatePrices } from '@/utils/gecko'
|
||||
import { mainNets, testNets } from '@/utils/networks'
|
||||
import { allTemplateNets } from '@/utils/networks'
|
||||
|
||||
// const METAMAKS_EXTENSION_ID = 'nkbihfbeogaeaoehlefnkodbefgpgknn'
|
||||
|
||||
let notificationUrl: string
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
console.log('Service worker installed');
|
||||
enableRightClickVote()
|
||||
console.info('Service worker installed');
|
||||
})
|
||||
|
||||
chrome.runtime.onStartup.addListener(() => {
|
||||
console.log('Service worker startup');
|
||||
console.info('Service worker startup');
|
||||
enableRightClickVote();
|
||||
if(chrome.runtime.lastError) {
|
||||
console.warn("Whoops.. " + chrome.runtime.lastError.message);
|
||||
}
|
||||
})
|
||||
|
||||
chrome.runtime.onSuspend.addListener(() => {
|
||||
console.log('Service worker suspend');
|
||||
console.info('Service worker suspend');
|
||||
if(chrome.runtime.lastError) {
|
||||
console.warn("Whoops.. " + chrome.runtime.lastError.message);
|
||||
}
|
||||
})
|
||||
|
||||
async function pasteAddress() {
|
||||
const currentAddress = (await (window as any).ethereum?.request({
|
||||
method: 'eth_accounts',
|
||||
params: []
|
||||
}))
|
||||
if(currentAddress.length > 0) {
|
||||
document.execCommand("insertText", false, currentAddress[0]);
|
||||
}
|
||||
}
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
||||
const extensionId = chrome.runtime.id
|
||||
const isOwnExtension = info?.pageUrl?.startsWith(`chrome-extension://${extensionId}`)
|
||||
|
||||
if (info.menuItemId === CLW_CONTEXT_MENU_ID && tab?.id && !isOwnExtension) {
|
||||
try {
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
world: 'MAIN',
|
||||
func: pasteAddress
|
||||
});
|
||||
} catch {
|
||||
// igonre
|
||||
}
|
||||
} else if(isOwnExtension) {
|
||||
chrome.runtime.sendMessage({ method: 'paste', type: 'CLWALLET_PAGE_MSG' }, (r) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC3: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
return r
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
chrome.alarms.create('updatePrices', {
|
||||
periodInMinutes: 1
|
||||
|
@ -34,7 +105,9 @@ chrome.alarms.create('updatePrices', {
|
|||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if(alarm.name === 'updatePrices') {
|
||||
updatePrices().then(() => {
|
||||
console.log('Prices updated')
|
||||
console.info('Prices updated')
|
||||
}).catch((err) => {
|
||||
console.warn('Prices update failed', err)
|
||||
})
|
||||
}
|
||||
getSettings().then((settings) => {
|
||||
|
@ -77,9 +150,13 @@ if (!chrome.notifications.onButtonClicked.hasListener(viewTxListner)){
|
|||
}
|
||||
|
||||
const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: any) => any) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.info("Error receiving message:", chrome.runtime.lastError);
|
||||
}
|
||||
if(message?.type !== "CLWALLET_CONTENT_MSG") {
|
||||
return true
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (!(message?.method)) {
|
||||
sendResponse({
|
||||
|
@ -90,24 +167,36 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
// ETH API
|
||||
switch (message.method) {
|
||||
case 'eth_call': {
|
||||
sendResponse(await evmCall(message?.params?.[0]))
|
||||
try {
|
||||
sendResponse(await evmCall(message?.params ?? []))
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_call', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eth_getBlockByNumber': {
|
||||
try {
|
||||
const params = message?.params?.[0] as any
|
||||
const block = await getBlockByNumber(params) as any
|
||||
block.gasLimit = block.gasLimit.toHexString()
|
||||
block.gasUsed = block.gasUsed.toHexString()
|
||||
block.baseFeePerGas = block.baseFeePerGas.toHexString()
|
||||
block._difficulty = block._difficulty.toHexString()
|
||||
sendResponse(block)
|
||||
} catch {
|
||||
const newBlock = {...block}
|
||||
newBlock.gasLimit = numToHexStr(block.gasLimit)
|
||||
newBlock.gasUsed = numToHexStr(block.gasUsed)
|
||||
newBlock.baseFeePerGas = numToHexStr(block.baseFeePerGas)
|
||||
newBlock._difficulty = numToHexStr(block.difficulty)
|
||||
newBlock.difficulty = block._difficulty
|
||||
sendResponse(newBlock)
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_getBlockByNumber', e)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -118,85 +207,93 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
}else {
|
||||
sendResponse(numToHexStr(Number(await getTxCount(message?.params?.[0] as string))))
|
||||
}
|
||||
} catch {
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
|
||||
console.warn('Error: eth_getTransactionCount', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eth_getTransactionByHash': {
|
||||
try {
|
||||
sendResponse(await getTxByHash(message?.params?.[0] as string))
|
||||
} catch {
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_getTransactionByHash', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eth_getTransactionReceipt':{
|
||||
try {
|
||||
sendResponse(await getTxReceipt(message?.params?.[0] as string))
|
||||
} catch {
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_getTransactionReceipt', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eth_gasPrice': {
|
||||
try {
|
||||
sendResponse((await getGasPrice()).toHexString())
|
||||
} catch {
|
||||
sendResponse(numToHexStr(BigInt(Math.trunc(await getGasPrice() * 1e9))))
|
||||
} catch(e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_gasPrice', e)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'eth_getBalance': {
|
||||
try {
|
||||
sendResponse(await getBalance())
|
||||
} catch {
|
||||
const balance = await getBalance()
|
||||
const balanceHex = numToHexStr(balance ?? 0n)
|
||||
sendResponse(balanceHex)
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_getBalance', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eth_getCode': {
|
||||
try {
|
||||
sendResponse(await getCode(message?.params?.[0] as string))
|
||||
} catch {
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_getCode', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eth_blockNumber': {
|
||||
try {
|
||||
sendResponse(await getBlockNumber())
|
||||
} catch {
|
||||
sendResponse(numToHexStr(await getBlockNumber()))
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_blockNumber', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -217,7 +314,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
data: params?.data ?? '',
|
||||
value: params?.value ?? '0x0'
|
||||
})
|
||||
const gasHex = gas?._hex ? gas?._hex : gas
|
||||
const gasHex = numToHexStr(gas ?? 0n)
|
||||
sendResponse(gasHex)
|
||||
} catch(err) {
|
||||
if(String(err).includes('UNPREDICTABLE_GAS_LIMIT')) {
|
||||
|
@ -232,28 +329,28 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
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_accounts': {
|
||||
try {
|
||||
// give only the selected address for better privacy
|
||||
const account = await getSelectedAccount()
|
||||
const address = account?.address ? [account?.address] : []
|
||||
sendResponse(address)
|
||||
} catch {
|
||||
sendResponse(await getSelectedAddress())
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_accounts', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -262,12 +359,13 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
const network = await getSelectedNetwork()
|
||||
const chainId = network?.chainId ?? 0
|
||||
sendResponse(`0x${chainId.toString(16)}`)
|
||||
} catch {
|
||||
} catch (e) {
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
message: 'No network or user selected'
|
||||
})
|
||||
console.warn('Error: eth_chainId', e)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -303,13 +401,6 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
}
|
||||
params.from = account.address
|
||||
const serializeParams = strToHex(JSON.stringify(params)) ?? ''
|
||||
const pEstimateGas = estimateGas({
|
||||
to: params?.to ?? '',
|
||||
from: params?.from ?? '',
|
||||
data: params?.data ?? '',
|
||||
value: params?.value ?? '0x0'
|
||||
})
|
||||
const pGasPrice = getGasPrice()
|
||||
let gWin: any
|
||||
await new Promise((resolve, reject) => {
|
||||
chrome.windows.create({
|
||||
|
@ -327,7 +418,9 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
|
||||
})
|
||||
try {
|
||||
const tx = await sendTransaction({...params, ...(rIdData?.[String(gWin?.id ?? 0)] ?? {}) }, pEstimateGas, pGasPrice)
|
||||
// console.log('waiting for user to approve or reject')
|
||||
// console.log(rIdData?.[String(gWin?.id ?? 0)])
|
||||
const tx = await sendTransaction({...params, ...(rIdData?.[String(gWin?.id ?? 0)] ?? {}) } )
|
||||
sendResponse(tx.hash)
|
||||
const buttons = {} as any
|
||||
const network = await getSelectedNetwork()
|
||||
|
@ -385,7 +478,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
} as any)
|
||||
}
|
||||
} catch(err) {
|
||||
// console.log(err)
|
||||
console.warn('Error: eth_sendTransaction', err)
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
|
@ -452,7 +545,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
clearPk()
|
||||
}
|
||||
} catch (e) {
|
||||
// console.error(e)
|
||||
console.warn('Error: signTypedData', e)
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
|
@ -473,7 +566,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
break
|
||||
}
|
||||
case 'web3_clientVersion': {
|
||||
sendResponse("MetaMask/v10.20.0")
|
||||
sendResponse("MetaMask/v11.0.0")
|
||||
break
|
||||
}
|
||||
case 'wallet_getPermissions':
|
||||
|
@ -492,9 +585,21 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
}])
|
||||
break
|
||||
}
|
||||
case 'wallet_revokePermissions': {
|
||||
sendResponse(null)
|
||||
break
|
||||
}
|
||||
case 'wallet_registerOnboarding': {
|
||||
sendResponse(true)
|
||||
break
|
||||
}
|
||||
case 'eth_syncing': {
|
||||
sendResponse(false)
|
||||
break
|
||||
}
|
||||
case 'net_version': {
|
||||
const network = await getSelectedNetwork()
|
||||
const chainId = network?.chainId ?? 0
|
||||
const chainId = String(network?.chainId ?? 1)
|
||||
sendResponse(chainId)
|
||||
break
|
||||
}
|
||||
|
@ -529,7 +634,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
}
|
||||
case 'wallet_addEthereumChain': {
|
||||
const userNetworks = await getNetworks()
|
||||
const networks = {...mainNets, ...testNets, ...userNetworks}
|
||||
const networks = {...allTemplateNets, ...userNetworks}
|
||||
const chainId = Number(message?.params?.[0]?.chainId ?? '0')
|
||||
if(!chainId) {
|
||||
sendResponse({
|
||||
|
@ -570,7 +675,7 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
})
|
||||
sendResponse(null)
|
||||
} catch (err) {
|
||||
console.log('err')
|
||||
console.error('err')
|
||||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.USER_REJECTED,
|
||||
|
@ -588,7 +693,8 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
const [network, account] = await Promise.all([pNetwork, pAccount])
|
||||
const address = account?.address ? [account?.address] : []
|
||||
const chainId = `0x${(network?.chainId ?? 0).toString(16)}`
|
||||
const data = { type: "CLWALLET_PAGE_LISTENER", data: {
|
||||
const data = {
|
||||
type: "CLWALLET_PAGE_LISTENER", data: {
|
||||
listner: 'connect',
|
||||
data: {
|
||||
chainId
|
||||
|
@ -612,7 +718,8 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
}
|
||||
case 'wallet_send_data': {
|
||||
if(String(sender.tab?.windowId) in rIdData){
|
||||
rIdData[String(sender?.tab?.windowId ?? '')] = (message as any)?.data ?? {}
|
||||
const intData = rIdData[String(sender?.tab?.windowId ?? '')] ?? {}
|
||||
rIdData[String(sender?.tab?.windowId ?? '')] = {...intData, ...(message?.data ?? {})}
|
||||
sendResponse(true)
|
||||
}
|
||||
break
|
||||
|
@ -631,12 +738,13 @@ const mainListner = (message: RequestArguments, sender:any, sendResponse: (a: an
|
|||
sendResponse({
|
||||
error: true,
|
||||
code: rpcError.INVALID_PARAM,
|
||||
message: 'ClearWallet: Invalid request method ' + message?.method ?? ''
|
||||
message: 'ClearWallet: Invalid request method ' + (message?.method ?? '')
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
)();
|
||||
return true;
|
||||
|
|
|
@ -8,9 +8,12 @@ export interface Network {
|
|||
explorer?: string
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
export interface Contact {
|
||||
name: string
|
||||
address: string
|
||||
}
|
||||
|
||||
export interface Account extends Contact {
|
||||
pk: string
|
||||
encPk: string
|
||||
}
|
||||
|
@ -54,6 +57,7 @@ export interface Settings {
|
|||
theme: 'system' | 'light' | 'dark'
|
||||
lastLock: number
|
||||
lockOutBlocked: boolean
|
||||
copyLowerCaseAddress?: boolean
|
||||
}
|
||||
|
||||
export type listnerType = 'accountsChanged' | 'connect' | 'disconnect' | 'chainChanged'
|
||||
|
@ -65,3 +69,15 @@ export interface HistoryItem {
|
|||
webiste?: string
|
||||
txHash: string
|
||||
}
|
||||
|
||||
export interface ContractAction {
|
||||
name: string
|
||||
contract: string
|
||||
abi: string
|
||||
functionName: string
|
||||
params: any[]
|
||||
}
|
||||
|
||||
export interface ContractActions {
|
||||
[key: string] : ContractAction
|
||||
}
|
||||
|
|
|
@ -4,12 +4,20 @@ export const rIdWin = {} as Record<string, string | undefined>
|
|||
export const rIdData = {} as Record<string, any | undefined>
|
||||
|
||||
export const approve = (rId: string) => {
|
||||
chrome.runtime.sendMessage({ method: 'wallet_approve', resId: rId, type: 'CLWALLET_CONTENT_MSG' })
|
||||
chrome.runtime.sendMessage({ method: 'wallet_approve', resId: rId, type: 'CLWALLET_CONTENT_MSG' }, (r) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC4: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
return r
|
||||
})
|
||||
}
|
||||
|
||||
export const walletSendData = (rId: string, data: any) => {
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({ method: 'wallet_send_data', resId: rId, data, type: 'CLWALLET_CONTENT_MSG' }, (r) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC5: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
resolve(r)
|
||||
})
|
||||
})
|
||||
|
@ -18,6 +26,25 @@ export const walletSendData = (rId: string, data: any) => {
|
|||
export const walletGetData = (rId: string) => {
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({ method: 'wallet_get_data', resId: rId, type: 'CLWALLET_CONTENT_MSG' }, (r) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC6: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
resolve(r)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const walletPromptSendTx = (tx: any) => {
|
||||
const rId = [...`${Math.random().toString(16) + Date.now().toString(16)}`].slice(2).join('')
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({ method: 'eth_sendTransaction', resId: rId,
|
||||
params: [
|
||||
tx
|
||||
]
|
||||
, type: 'CLWALLET_CONTENT_MSG' }, (r) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC7: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
resolve(r)
|
||||
})
|
||||
})
|
||||
|
@ -26,6 +53,9 @@ export const walletGetData = (rId: string) => {
|
|||
export const walletPing = () => {
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({ method: 'wallet_ping', type: 'CLWALLET_CONTENT_MSG' }, (r) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("LOC8: Error sending message:", chrome.runtime.lastError);
|
||||
}
|
||||
resolve(r)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createRouter, createWebHistory } from '@ionic/vue-router';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import AppTabs from '@/views/AppTabs.vue'
|
||||
import HomeTab from '@/views/HomeTab.vue'
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
|
@ -41,7 +42,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||
},
|
||||
{
|
||||
path: 'home',
|
||||
component: () => import('@/views/HomeTab.vue'),
|
||||
component: HomeTab,
|
||||
},
|
||||
{
|
||||
path: 'networks',
|
||||
|
@ -79,6 +80,19 @@ const routes: Array<RouteRecordRaw> = [
|
|||
path: 'add-network/edit/:chainId',
|
||||
component: () => import('@/views/AddNetwork.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: 'send-token',
|
||||
component: () => import('@/views/SendToken.vue'),
|
||||
},
|
||||
{
|
||||
path: 'read-contract',
|
||||
component: () => import('@/views/ReadContract.vue'),
|
||||
},
|
||||
{
|
||||
path: 'write-contract',
|
||||
component: () => import('@/views/WriteContract.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
@ -8,4 +8,4 @@ export const exportFile = (fileName: string, content: string, type = 'json') =>
|
|||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export const mainNets: {[key: number]: Network} = {
|
|||
},
|
||||
100: {
|
||||
name: 'Gnosis',
|
||||
rpc: 'https://rpc.gnosischain.com/',
|
||||
rpc: 'https://rpc.gnosischain.com',
|
||||
chainId: 100,
|
||||
explorer: 'https://gnosisscan.io',
|
||||
icon:'xdai.webp',
|
||||
|
@ -56,60 +56,78 @@ export const mainNets: {[key: number]: Network} = {
|
|||
symbol: 'ETH',
|
||||
priceId: 'ethereum'
|
||||
},
|
||||
8453: {
|
||||
name: 'Base Mainnet',
|
||||
rpc: 'https://base.publicnode.com',
|
||||
chainId: 8453,
|
||||
explorer: 'https://basescan.org',
|
||||
icon: 'base.webp',
|
||||
symbol: 'ETH',
|
||||
priceId: 'ethereum'
|
||||
}
|
||||
}
|
||||
|
||||
export const testNets = {
|
||||
5: {
|
||||
name: 'TESTNET Ethereum Goerli',
|
||||
rpc: 'https://rpc.ankr.com/eth_goerli',
|
||||
chainId: 5,
|
||||
explorer: 'https://goerli.etherscan.io',
|
||||
icon: 'eth.webp'
|
||||
11155111: {
|
||||
name: 'TESTNET Ethereum Sepolia',
|
||||
rpc: 'https://ethereum-sepolia-rpc.publicnode.com',
|
||||
chainId: 11155111,
|
||||
explorer: 'https://sepolia.etherscan.io',
|
||||
icon: 'eth_t.webp'
|
||||
},
|
||||
4: {
|
||||
name: 'TESTNET Ethereum Rinkeby',
|
||||
rpc: 'https://rpc.ankr.com/eth_rinkeby',
|
||||
chainId: 4,
|
||||
explorer: 'https://rinkeby.etherscan.io',
|
||||
icon: 'eth.webp'
|
||||
84532: {
|
||||
name: 'TESTNET Base Sepolia',
|
||||
rpc: 'https://sepolia.base.org',
|
||||
chainId: 84532,
|
||||
explorer: 'https://sepolia.basescan.org/',
|
||||
icon: 'base_t.webp'
|
||||
},
|
||||
80001: {
|
||||
name: 'TESTNET Polygon',
|
||||
rpc: 'https://rpc.ankr.com/polygon_mumbai',
|
||||
chainId: 80001,
|
||||
explorer: 'https://mumbai.polygonscan.com/',
|
||||
icon:'polygon.webp'
|
||||
80002: {
|
||||
name: 'TESTNET Polygon Amoy',
|
||||
rpc: 'https://rpc-amoy.polygon.technology',
|
||||
chainId: 80002,
|
||||
explorer: 'https://oklink.com/amoy',
|
||||
icon:'polygon_t.webp'
|
||||
},
|
||||
100100: {
|
||||
100200: {
|
||||
name: 'TESTNET Gnosis Chiado',
|
||||
rpc: 'https://gnosis-mainnet.public.blastapi.io',
|
||||
chainId: 100100,
|
||||
explorer: '',
|
||||
icon:'xdai.webp'
|
||||
rpc: 'https://rpc.chiadochain.net',
|
||||
chainId: 100200,
|
||||
explorer: 'https://gnosis-chiado.blockscout.com',
|
||||
icon:'xdai_t.webp'
|
||||
},
|
||||
420: {
|
||||
name: 'TESTNET Optimism Goreli',
|
||||
rpc: 'https://goerli.optimism.io/',
|
||||
chainId: 420,
|
||||
explorer: 'https://goerli.etherscan.io/',
|
||||
icon: 'optimism.webp'
|
||||
explorer: 'https://goerli.etherscan.io',
|
||||
icon: 'optimism_t.webp'
|
||||
},
|
||||
11155420 : {
|
||||
name: 'TESTNET Optimism Sepolia',
|
||||
rpc: 'https://sepolia.optimism.io',
|
||||
chainId: 11155420 ,
|
||||
explorer: 'https://sepolia-optimistic.etherscan.io/',
|
||||
icon: 'optimism_t.webp'
|
||||
},
|
||||
97: {
|
||||
name: 'TESTNET BSC',
|
||||
rpc: 'https://bsctestapi.terminet.io/rpc',
|
||||
rpc: 'https://bsc-testnet-rpc.publicnode.com',
|
||||
chainId: 97,
|
||||
explorer: 'https://testnet.bscscan.com/',
|
||||
icon: 'binance.webp'
|
||||
explorer: 'https://testnet.bscscan.com',
|
||||
icon: 'binance_t.webp'
|
||||
},
|
||||
421613: {
|
||||
name: 'TESTNET Arbitrum One',
|
||||
rpc: 'https://goerli-rollup.arbitrum.io/rpc/',
|
||||
chainId: 421613,
|
||||
explorer: 'https://testnet.arbiscan.io/',
|
||||
icon: 'arbitrum.webp'
|
||||
421614: {
|
||||
name: 'TESTNET Arbitrum Sepolia',
|
||||
rpc: 'https://sepolia-rollup.arbitrum.io/rpc',
|
||||
chainId: 421614,
|
||||
explorer: 'https://sepolia.arbiscan.io/',
|
||||
icon: 'arbitrum_t.webp'
|
||||
},
|
||||
}
|
||||
|
||||
export const allTemplateNets = {...mainNets, ...testNets}
|
||||
|
||||
export const chainIdToPriceId = (chainId: number): string => {
|
||||
return mainNets?.[chainId]?.priceId ?? 'x'
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import type { Network, Account, Prices, Settings, Networks, HistoryItem } from '@/extension/types'
|
||||
import type { Network, Account, Prices, Settings, Networks, HistoryItem, ContractActions, ContractAction, Contact } from '@/extension/types'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
const pottentialMissingSettings = ['copyLowerCaseAddress']
|
||||
|
||||
const defaultSettings = {
|
||||
enableStorageEnctyption: false,
|
||||
encryptAfterEveryTx: false,
|
||||
|
@ -8,9 +10,16 @@ const defaultSettings = {
|
|||
lockOutPeriod: 2,
|
||||
lockOutBlocked: false,
|
||||
theme: 'system',
|
||||
lastLock: Date.now()
|
||||
lastLock: Date.now(),
|
||||
copyLowerCaseAddress: false
|
||||
}
|
||||
|
||||
const defaultAbis = {} as {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export const CLW_CONTEXT_MENU_ID = 'clw-paste-address'
|
||||
|
||||
export const storageSave = async (key: string, value: any): Promise<void> =>{
|
||||
await chrome.storage.local.set({ [key]: value })
|
||||
}
|
||||
|
@ -39,6 +48,7 @@ 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
|
||||
}
|
||||
|
||||
|
@ -48,11 +58,23 @@ export const saveSelectedNetwork = async (selectedNetwork: Network ): Promise<v
|
|||
}
|
||||
|
||||
|
||||
export const getContacts = async (): Promise<Contact[]> => {
|
||||
return (await storageGet('contacts')).contacts ?? [] as Contact[]
|
||||
}
|
||||
|
||||
export const saveContact = async (contact: Contact): Promise<void> => {
|
||||
const savedContacts = await getContacts()
|
||||
await storageSave('contacts', [contact, ...savedContacts])
|
||||
}
|
||||
|
||||
export const replaceContacts = async (contacts: Contact[]): Promise<void> => {
|
||||
await storageSave('contacts', contacts)
|
||||
}
|
||||
|
||||
export const getAccounts = async (): Promise<Account[]> => {
|
||||
return (await storageGet('accounts')).accounts ?? [] as Account[]
|
||||
}
|
||||
|
||||
|
||||
export const saveAccount = async (account: Account): Promise<void> => {
|
||||
const savedAccounts = await getAccounts()
|
||||
await storageSave('accounts', [account, ...savedAccounts])
|
||||
|
@ -62,6 +84,7 @@ export const replaceAccounts = async (accounts: Account[]): Promise<void> => {
|
|||
await storageSave('accounts', accounts)
|
||||
}
|
||||
|
||||
|
||||
export const getSelectedAccount = async (): Promise<Account> => {
|
||||
return (await storageGet('selectedAccount'))?.selectedAccount ?? null as unknown as Account
|
||||
}
|
||||
|
@ -99,13 +122,93 @@ export const wipeHistory = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
export const getSettings = async (): Promise<Settings> => {
|
||||
return (await storageGet('settings'))?.settings ?? defaultSettings as unknown as Settings
|
||||
const settings = (await storageGet('settings'))?.settings ?? defaultSettings as unknown as Settings
|
||||
pottentialMissingSettings.forEach( (s: string) => {
|
||||
if(settings[s] === undefined) {
|
||||
settings[s as keyof Settings] = defaultSettings[s as keyof Settings]
|
||||
}
|
||||
})
|
||||
return settings
|
||||
}
|
||||
|
||||
export const setSettings = async (settings: Settings): Promise<void> => {
|
||||
await storageSave('settings', settings )
|
||||
}
|
||||
|
||||
export const getAllAbis = async (): Promise<{ [key: string]: string }> => {
|
||||
return ((await storageGet('abis'))?.abis) ?? defaultAbis
|
||||
}
|
||||
|
||||
export const getAbis = async (name: string): Promise<string> => {
|
||||
return (await getAllAbis())?.[name] ?? ''
|
||||
}
|
||||
|
||||
export const setAbi = async ({
|
||||
name ,
|
||||
content
|
||||
}: {
|
||||
name: string
|
||||
content: string
|
||||
}): Promise<void> => {
|
||||
const abis = await getAllAbis() || defaultAbis
|
||||
await storageSave('abis', { ...abis, [name]: content })
|
||||
}
|
||||
|
||||
export const setAbis = async (abis: { [key: string]: string }): Promise<void> => {
|
||||
await storageSave('abis', abis)
|
||||
}
|
||||
|
||||
export const removeAllAbis = async (): Promise<void> => {
|
||||
await storageSave('abis', defaultAbis)
|
||||
}
|
||||
|
||||
|
||||
export const readCAGetAll = async (): Promise<ContractActions> => {
|
||||
return ((await storageGet('read-actions'))?.['read-actions'] ?? {}) as ContractActions
|
||||
}
|
||||
|
||||
export const readCAGet = async (action: string): Promise<ContractAction | undefined> => {
|
||||
return ((await readCAGetAll())?.[action]) as ContractAction
|
||||
}
|
||||
|
||||
export const readCASet = async (action: ContractAction): Promise<void> => {
|
||||
const actions = await readCAGetAll()
|
||||
await storageSave('read-actions', { ...actions, [action.name]: action })
|
||||
}
|
||||
|
||||
export const readCARemove = async (action: string): Promise<void> => {
|
||||
const actions = await readCAGetAll()
|
||||
delete actions[action]
|
||||
await storageSave('read-actions', actions)
|
||||
}
|
||||
|
||||
export const readCAWipe = async (): Promise<void> => {
|
||||
await storageSave('read-actions', {})
|
||||
}
|
||||
|
||||
export const writeCAGetAll = async (): Promise<ContractActions> => {
|
||||
return ((await storageGet('write-actions'))?.['write-actions'] ?? {}) as ContractActions
|
||||
}
|
||||
|
||||
export const writeCAGet = async (action: string): Promise<ContractAction | undefined> => {
|
||||
return ((await writeCAGetAll())?.[action]) as ContractAction
|
||||
}
|
||||
|
||||
export const writeCASet = async (action: ContractAction): Promise<void> => {
|
||||
const actions = await writeCAGetAll()
|
||||
await storageSave('write-actions', { ...actions, [action.name]: action })
|
||||
}
|
||||
|
||||
export const writeCARemove = async (action: string): Promise<void> => {
|
||||
const actions = await writeCAGetAll()
|
||||
delete actions[action]
|
||||
await storageSave('write-actions', actions)
|
||||
}
|
||||
|
||||
export const writeCAWipe = async (): Promise<void> => {
|
||||
await storageSave('write-actions', {})
|
||||
}
|
||||
|
||||
export const blockLockout = async (): Promise<Settings> => {
|
||||
const settings = await getSettings()
|
||||
settings.lockOutBlocked = true
|
||||
|
@ -128,15 +231,6 @@ export const setBalanceCache = async (balance: string): Promise<void> => {
|
|||
await storageSave('balance', balance )
|
||||
}
|
||||
|
||||
export const getRandomPk = () => {
|
||||
const array = new Uint32Array(10);
|
||||
crypto.getRandomValues(array)
|
||||
return array.reduce(
|
||||
(pv, cv) => `${pv}${cv.toString(16)}`,
|
||||
'0x'
|
||||
).substring(0, 66)
|
||||
}
|
||||
|
||||
export const smallRandomString = (size = 7) => {
|
||||
if(size <= 7) {
|
||||
return (Math.random() + 1).toString(36).substring(0,7);
|
||||
|
@ -176,11 +270,11 @@ export const hexTostr = (hexStr: string) =>
|
|||
return hexStr
|
||||
}
|
||||
|
||||
export const strToHex = (str: string) => `0x${str.split('').map( s => s.charCodeAt(0).toString(16) ).join('')}`
|
||||
export const strToHex = (str: string) => `0x${str.split('').map( s => s.charCodeAt(0).toString(16)).join('')}`
|
||||
|
||||
export const numToHexStr = (num: number) => `0x${num.toString(16)}`
|
||||
export const numToHexStr = (num: number | bigint) => `0x${num.toString(16)}`
|
||||
|
||||
export const copyAddress = async (address: string, toastRef: Ref<boolean>) => {
|
||||
export const copyText = async (address: string, toastRef: Ref<boolean>) => {
|
||||
await navigator.clipboard.writeText(address)
|
||||
toastRef.value = true
|
||||
}
|
||||
|
@ -195,8 +289,31 @@ export const paste = (id: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const enableRightClickVote = async () => {
|
||||
try {
|
||||
await chrome.contextMenus.removeAll();
|
||||
await chrome.contextMenus.create({
|
||||
id: CLW_CONTEXT_MENU_ID,
|
||||
title: "Paste Current Address",
|
||||
contexts: ["editable"],
|
||||
});
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export const pasteToFocused = () => {
|
||||
const el = document.activeElement as HTMLInputElement
|
||||
if(el){
|
||||
el?.focus();
|
||||
(document as any)?.execCommand('paste')
|
||||
}
|
||||
}
|
||||
|
||||
export const openTab = (url: string) => {
|
||||
chrome.tabs.create({
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
export const getVersion = () => chrome?.runtime?.getManifest()?.version ?? ''
|
||||
|
|
|
@ -1,90 +1,141 @@
|
|||
import { getSelectedAccount, getSelectedNetwork } from '@/utils/platform';
|
||||
import { BigNumber, ethers } from "ethers"
|
||||
import { getSelectedAccount, getSelectedNetwork, numToHexStr } from '@/utils/platform';
|
||||
import { ethers } from "ethers"
|
||||
|
||||
const convertReceipt = (receipt: ethers.TransactionReceipt | null) => {
|
||||
if(!receipt) return null
|
||||
const newReceipt = {...receipt} as any
|
||||
newReceipt.transactionHash = newReceipt.hash
|
||||
newReceipt.blockNumber = numToHexStr(newReceipt.blockNumber)
|
||||
newReceipt.index = numToHexStr(newReceipt.index)
|
||||
newReceipt.transactionIndex = newReceipt.index
|
||||
newReceipt.cumulativeGasUsed = numToHexStr(newReceipt.cumulativeGasUsed)
|
||||
newReceipt.gasUsed = numToHexStr(newReceipt.gasUsed)
|
||||
newReceipt.gasPrice = numToHexStr(newReceipt.gasPrice)
|
||||
newReceipt.type = "0x2"
|
||||
newReceipt.status = numToHexStr(newReceipt.status)
|
||||
newReceipt.logs = receipt?.logs?.map((log: any) => {
|
||||
return {
|
||||
...log,
|
||||
blockNumber: numToHexStr(log.blockNumber),
|
||||
logIndex: numToHexStr(log.index),
|
||||
transactionIndex: numToHexStr(log.transactionIndex),
|
||||
removed: false
|
||||
}
|
||||
})
|
||||
return newReceipt
|
||||
}
|
||||
|
||||
|
||||
export const signMsg = async (msg: string) => {
|
||||
const account = await getSelectedAccount()
|
||||
const wallet = new ethers.Wallet(account.pk)
|
||||
return await wallet.signMessage( msg.startsWith('0x') ? ethers.utils.arrayify(msg): msg)
|
||||
return await wallet.signMessage( msg.startsWith('0x') ? ethers.getBytes(msg): msg)
|
||||
}
|
||||
|
||||
export const signTypedData = async (msg: string) => {
|
||||
const account = await getSelectedAccount()
|
||||
const wallet = new ethers.Wallet(account.pk)
|
||||
const parsedMsg = JSON.parse(msg)
|
||||
if(parsedMsg?.primaryType) {
|
||||
if(parsedMsg.primaryType in parsedMsg.types){
|
||||
parsedMsg.types = {
|
||||
[parsedMsg.primaryType]: parsedMsg.types[parsedMsg.primaryType]
|
||||
}
|
||||
const types = {} as Record<string, any>
|
||||
for (const key in parsedMsg.types) {
|
||||
if (key !== 'EIP712Domain') {
|
||||
types[key] = parsedMsg.types[key]
|
||||
}
|
||||
}
|
||||
return await wallet._signTypedData(parsedMsg.domain, parsedMsg.types, parsedMsg.message)
|
||||
parsedMsg.types = types
|
||||
const args = [parsedMsg.domain, parsedMsg.types, parsedMsg.message]
|
||||
return await wallet.signTypedData(args[0], args[1], args[2])
|
||||
}
|
||||
|
||||
export const getBalance = async () =>{
|
||||
const account = await getSelectedAccount()
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
return await provider.getBalance(account.address)
|
||||
}
|
||||
|
||||
export const getGasPrice = async () => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
return await provider.getGasPrice()
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
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.providers.JsonRpcProvider(network.rpc)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
return await provider.getBlockNumber()
|
||||
}
|
||||
|
||||
export const getBlockByNumber = async (blockNum: number) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
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.providers.JsonRpcProvider(network.rpc)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
return await provider.estimateGas({to, from, data, value})
|
||||
}
|
||||
|
||||
export const evmCall = async ({to = '', from = '', data = '', value = '0x0' }: {to: string, from: string, data: string, value: string}) => {
|
||||
export const evmCall = async (params: any[]) => {
|
||||
const tx = {} as {to: string, from: string, data: string, value: string, blockTag: string}
|
||||
const param1 = params[0] as any
|
||||
if(param1.to) tx.to = param1.to
|
||||
if(param1.from) tx.from = param1.from
|
||||
if(param1.data) tx.data = param1.data
|
||||
if(param1.value) tx.value = param1.value
|
||||
const param2 = params[1] as string
|
||||
if (param2.startsWith('0x')) {
|
||||
tx.blockTag = param2
|
||||
} else {
|
||||
tx.blockTag = 'latest'
|
||||
}
|
||||
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
return await provider.call({to, from, data, value})
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const result = await provider.call(tx)
|
||||
return result
|
||||
}
|
||||
|
||||
export const getTxByHash = async (hash: string) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
return await provider.getTransaction(hash)
|
||||
}
|
||||
|
||||
export const getTxReceipt = async (hash: string) => {
|
||||
try {
|
||||
if (!hash) return null
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
return await provider.getTransactionReceipt(hash)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
const receipt = await provider.getTransactionReceipt(hash)
|
||||
|
||||
return convertReceipt(receipt)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const getCode = async (addr: string) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
return await provider.getCode(addr)
|
||||
}
|
||||
|
||||
export const getFromMemonic = (memonic: string, index: number) => {
|
||||
export const getFromMnemonic = (mnemonic: string, index: number) => {
|
||||
const path = `m/44'/60'/0'/0/${index}`
|
||||
const wallet = ethers.Wallet.fromMnemonic(memonic, path)
|
||||
const mnemonicInst = ethers.Mnemonic.fromPhrase(mnemonic)
|
||||
const wallet = ethers.HDNodeWallet.fromMnemonic(mnemonicInst, path)
|
||||
return wallet.privateKey
|
||||
}
|
||||
|
||||
export const getTxCount = async (addr: string, block: null | string = null) => {
|
||||
const network = await getSelectedNetwork()
|
||||
const provider = new ethers.providers.JsonRpcProvider(network.rpc)
|
||||
const provider = new ethers.JsonRpcProvider(network.rpc)
|
||||
if(block){
|
||||
return await provider.getTransactionCount(addr, block)
|
||||
} else {
|
||||
|
@ -92,33 +143,47 @@ export const getTxCount = async (addr: string, block: null | string = null) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', value='0x0', gasPrice='0x0'}:
|
||||
{to: string, from: string, data: string, value: string, gas: string, gasPrice: string},
|
||||
gasEstimate: Promise<BigNumber> | null = null, pGasPrice : Promise<BigNumber> | null) => {
|
||||
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.providers.JsonRpcProvider(network.rpc))
|
||||
if(gas === '0x0') {
|
||||
if(!gasEstimate){
|
||||
throw new Error('No gas estimate available')
|
||||
}else {
|
||||
gas = (await gasEstimate).toString()
|
||||
}
|
||||
}
|
||||
const wallet = new ethers.Wallet(account.pk, new ethers.JsonRpcProvider(network.rpc))
|
||||
const gasPriceInt = BigInt(gasPrice)
|
||||
const gasInt = BigInt(gas)
|
||||
|
||||
if(gasPrice === '0x0') {
|
||||
if(!pGasPrice){
|
||||
throw new Error('No gas estimate available')
|
||||
}else {
|
||||
gasPrice = (await pGasPrice).toString()
|
||||
}
|
||||
if(gas === '0x0' || gasPrice === '0x0') {
|
||||
throw new Error('No gas estimate available')
|
||||
}
|
||||
return await wallet.sendTransaction({to, from, data, value, gasLimit: gas, gasPrice})
|
||||
return await wallet.sendTransaction({
|
||||
to,
|
||||
from,
|
||||
data: data ? data : null,
|
||||
value: value ? value : null,
|
||||
gasLimit: gasInt,
|
||||
gasPrice: null,
|
||||
maxFeePerGas: gasPriceInt,
|
||||
})
|
||||
}
|
||||
|
||||
export const formatBalance = (balance: string) => {
|
||||
Intl.NumberFormat('en-US', {
|
||||
notation: 'compact',
|
||||
maximumFractionDigits: 6
|
||||
}).format(Number(ethers.utils.parseEther(balance)))
|
||||
}).format(Number(ethers.parseEther(balance)))
|
||||
}
|
||||
|
||||
export const getSelectedAddress = async () => {
|
||||
// give only the selected address for better privacy
|
||||
const account = await getSelectedAccount()
|
||||
const address = account?.address ? [account?.address] : []
|
||||
return address
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="onCancel">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title v-if="!isEdit">Add Abi</ion-title>
|
||||
<ion-title v-else>Edit Abi</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-input
|
||||
label="Abi Name(*)"
|
||||
labelPlacement="stacked"
|
||||
v-model="name"
|
||||
:readonly="isEdit"
|
||||
:style="`${isEdit ? 'opacity: 0.6;' : ''}}`"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-textarea
|
||||
label="Content:"
|
||||
labelPlacement="stacked"
|
||||
style="overflow-y: scroll"
|
||||
:rows="10"
|
||||
:cols="40"
|
||||
v-model="content"
|
||||
></ion-textarea>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="onCancel">Cancel</ion-button>
|
||||
<ion-button @click="onAddAbi">{{ isEdit ? "Edit ABI" : "Add ABI" }}</ion-button>
|
||||
</ion-item>
|
||||
<ion-alert
|
||||
:is-open="alertOpen"
|
||||
header="Error"
|
||||
:message="alertMsg"
|
||||
:buttons="['OK']"
|
||||
@didDismiss="alertOpen = false"
|
||||
></ion-alert>
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="4000"
|
||||
:key="`k${loading}`"
|
||||
@didDismiss="loading = false"
|
||||
>
|
||||
</ion-loading>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, PropType } from "vue";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
// IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonAlert,
|
||||
// IonIcon,
|
||||
onIonViewWillEnter,
|
||||
// modalController,
|
||||
// IonModal,
|
||||
IonButtons,
|
||||
IonLoading,
|
||||
IonTextarea,
|
||||
modalController,
|
||||
} from "@ionic/vue";
|
||||
// import { ethers } from "ethers";
|
||||
import { paste, setAbi } from "@/utils/platform";
|
||||
// import { useRoute } from "vue-router";
|
||||
|
||||
import { clipboardOutline } from "ionicons/icons";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
// IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonAlert,
|
||||
// IonIcon,
|
||||
// IonModal,
|
||||
IonButtons,
|
||||
IonTextarea,
|
||||
IonLoading,
|
||||
},
|
||||
props: {
|
||||
abi: {
|
||||
type: Object as PropType<{ [key: string]: string } | null>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup: (props) => {
|
||||
const isEdit = ref(props.abi !== null);
|
||||
const name = ref(props.abi?.name ?? "");
|
||||
const content = ref(props.abi?.content ?? "");
|
||||
const alertOpen = ref(false);
|
||||
|
||||
const alertMsg = ref("");
|
||||
const loading = ref(false);
|
||||
|
||||
onIonViewWillEnter(async () => {});
|
||||
|
||||
const onAddAbi = async () => {
|
||||
if (!name.value) {
|
||||
alertMsg.value = "Please input name.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (!content.value) {
|
||||
alertMsg.value = "Please input content.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(content.value);
|
||||
} catch (e) {
|
||||
alertMsg.value = "Invalid content, must be json format.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
await setAbi({
|
||||
name: name.value,
|
||||
content: content.value,
|
||||
});
|
||||
|
||||
modalController.dismiss(
|
||||
{
|
||||
name: name.value,
|
||||
content: content.value,
|
||||
},
|
||||
"confirm"
|
||||
);
|
||||
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
try {
|
||||
modalController.dismiss(null, "cancel");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name,
|
||||
content,
|
||||
onAddAbi,
|
||||
onCancel,
|
||||
alertOpen,
|
||||
alertMsg,
|
||||
clipboardOutline,
|
||||
paste,
|
||||
isEdit,
|
||||
loading,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="close">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Select Abi</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-button expand="block" @click="openModal">Add New ABI</ion-button>
|
||||
|
||||
<ion-radio-group :value="selectedAbi">
|
||||
<ion-list-header>
|
||||
<ion-label>Saved ABIs</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-list class="ion-padding" v-for="item of Object.keys(abis ?? {})" :key="item">
|
||||
<ion-item>
|
||||
<ion-radio
|
||||
@click="changeSelected(item)"
|
||||
slot="start"
|
||||
:value="item"
|
||||
:aria-label="item"
|
||||
>
|
||||
{{ item }}
|
||||
</ion-radio>
|
||||
<ion-button @click="onEdit(item)">Edit</ion-button>
|
||||
<ion-button @click="onDelete(item)">Delete</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-radio-group>
|
||||
|
||||
<ion-list v-if="!!!Object.keys(abis ?? {}).length">
|
||||
<ion-item class="ion-padding">
|
||||
<ion-label>No Abis found, please add at least one</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="4000"
|
||||
:key="`k${loading}`"
|
||||
@didDismiss="loading = false"
|
||||
>
|
||||
</ion-loading>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonList,
|
||||
IonItem,
|
||||
modalController,
|
||||
IonRadio,
|
||||
IonButtons,
|
||||
IonListHeader,
|
||||
IonRadioGroup,
|
||||
IonLabel,
|
||||
IonLoading,
|
||||
} from "@ionic/vue";
|
||||
import AbiAdd from "./AbiAdd.vue";
|
||||
import { ref, onMounted, Ref } from "vue";
|
||||
import {
|
||||
getAllAbis,
|
||||
setAbis,
|
||||
// removeAllAbis
|
||||
} from "@/utils/platform";
|
||||
|
||||
const loading = ref(false);
|
||||
const abis = ref() as Ref<{ [key: string]: string }>;
|
||||
const selectedAbi = ref("");
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
// await removeAllAbis();
|
||||
abis.value = (await getAllAbis()) ?? {};
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
const changeSelected = (item: string) => {
|
||||
selectedAbi.value = item;
|
||||
modalController.dismiss(
|
||||
{
|
||||
name: item,
|
||||
content: abis.value[item],
|
||||
},
|
||||
"confirm"
|
||||
);
|
||||
};
|
||||
|
||||
const openModal = async (id = "") => {
|
||||
const modal = await modalController.create({
|
||||
component: AbiAdd,
|
||||
componentProps: {
|
||||
abi: abis.value?.[id] ? { name: id, content: abis.value?.[id] } : null,
|
||||
},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
|
||||
if (role === "confirm") {
|
||||
selectedAbi.value = data.name;
|
||||
abis.value = {
|
||||
...(abis.value ?? {}),
|
||||
[data.name]: data.content,
|
||||
};
|
||||
if (Object.keys(abis.value ?? {}).length === 1) {
|
||||
changeSelected(data.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onEdit = (id: string) => {
|
||||
openModal(id);
|
||||
};
|
||||
|
||||
const onDelete = async (id: string) => {
|
||||
loading.value = true;
|
||||
if (abis.value?.[id]) {
|
||||
delete abis.value[id];
|
||||
await setAbis({ ...(abis.value ?? {}) });
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
try {
|
||||
modalController.dismiss(null, "cancel");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="close">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Select Function</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-searchbar placeholder="Search" @ionInput="onSearch"></ion-searchbar>
|
||||
</ion-item>
|
||||
|
||||
<ion-radio-group :value="selectedAbi">
|
||||
<ion-list-header>
|
||||
<ion-label>Functions</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-list class="ion-padding" v-for="(item, index) in refFunctions" :key="item">
|
||||
<ion-item>
|
||||
<ion-radio
|
||||
@click="changeSelected(Number(index))"
|
||||
slot="start"
|
||||
:value="item"
|
||||
:aria-label="item"
|
||||
>
|
||||
{{ item }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-radio-group>
|
||||
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="4000"
|
||||
:key="`k${loading}`"
|
||||
@didDismiss="loading = false"
|
||||
>
|
||||
</ion-loading>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonList,
|
||||
IonItem,
|
||||
modalController,
|
||||
IonRadio,
|
||||
IonListHeader,
|
||||
IonRadioGroup,
|
||||
IonLabel,
|
||||
IonLoading,
|
||||
IonSearchbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
} from "@ionic/vue";
|
||||
import { ref, onMounted, Ref, PropType } from "vue";
|
||||
// import {
|
||||
// getAllAbis,
|
||||
// setAbis,
|
||||
// // removeAllAbis
|
||||
// } from "@/utils/platform";
|
||||
|
||||
const props = defineProps({
|
||||
functions: {
|
||||
type: Array as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const refFunctions = ref(props.functions) as Ref<string[]>;
|
||||
|
||||
const loading = ref(false);
|
||||
const selectedAbi = ref("");
|
||||
|
||||
const onSearch = (e: any) => {
|
||||
const text = e.target.value;
|
||||
if (text) {
|
||||
refFunctions.value = props.functions.filter((item) =>
|
||||
item.toLowerCase().includes(text.toLowerCase())
|
||||
);
|
||||
} else {
|
||||
refFunctions.value = props.functions;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
// await removeAllAbis();
|
||||
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
const changeSelected = (item: number) => {
|
||||
modalController.dismiss(refFunctions.value[item], "confirm");
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
try {
|
||||
modalController.dismiss(null, "cancel");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -4,72 +4,81 @@
|
|||
<ion-toolbar>
|
||||
<ion-buttons slot="end">
|
||||
<router-link to="/tabs/add-account">
|
||||
<ion-button>
|
||||
<ion-icon slot="icon-only" :icon="addCircleOutline"></ion-icon>
|
||||
</ion-button>
|
||||
</router-link>
|
||||
</ion-buttons>
|
||||
<ion-button>
|
||||
<ion-icon slot="icon-only" :icon="addCircleOutline"></ion-icon>
|
||||
</ion-button>
|
||||
</router-link>
|
||||
</ion-buttons>
|
||||
<ion-title>Accounts</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-toast
|
||||
:is-open="toastState"
|
||||
@didDismiss="toastState=false"
|
||||
message="Copied to clipboard"
|
||||
:duration="1500"
|
||||
></ion-toast>
|
||||
<ion-toast
|
||||
position="top"
|
||||
:is-open="toastState"
|
||||
@didDismiss="toastState = false"
|
||||
message="Copied to clipboard"
|
||||
:duration="1500"
|
||||
></ion-toast>
|
||||
<ion-item v-if="loading || accounts.length < 1">
|
||||
<ion-label>No EVM accounts found</ion-label>
|
||||
<ion-button @click="goToAddAccount">Add Account</ion-button>
|
||||
</ion-item>
|
||||
<ion-list v-for="account of accounts" :key="account.address">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-list v-for="account of accounts" :key="account.address">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
{{ account.name }}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item @click="copyAddress(account.address, getToastRef())">
|
||||
<p style="font-size:0.7rem">{{ account.address }}</p><ion-icon :icon="copyOutline"></ion-icon>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item @click="copyText(account.address, getToastRef())">
|
||||
<p style="font-size: 0.7rem">{{ account.address }}</p>
|
||||
<ion-icon :icon="copyOutline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-chip @click="viewPk(account.address)">View Pk</ion-chip>
|
||||
<ion-chip @click="deleteAccount(account.address)">Delete</ion-chip>
|
||||
<ion-chip @click="editAccount(account.address)">Edit Name</ion-chip>
|
||||
<ion-chip @click="viewPk(account.address)">View Pk</ion-chip>
|
||||
<ion-chip @click="deleteAccount(account.address)">Delete</ion-chip>
|
||||
<ion-chip @click="editAccount(account.address)">Edit Name</ion-chip>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-list>
|
||||
|
||||
|
||||
<ion-modal
|
||||
:is-open="pkModal"
|
||||
@didDismiss="shownPk=''"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="pkModal=false">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>View PK</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item @click="copyAddress(shownPk, getToastRef())" button>
|
||||
<ion-icon style="margin-right: 0.5rem;" :icon="copyOutline" />
|
||||
<ion-label button>PK</ion-label>
|
||||
<ion-input id="pastePk" v-model="shownPk" readonly></ion-input>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
<ion-modal :is-open="pkModal" @didDismiss="shownPk = ''">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="pkModal = false">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>View PK</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item @click="copyText(shownPk, getToastRef())" button>
|
||||
<ion-icon style="margin-right: 0.5rem" :icon="copyOutline" />
|
||||
<ion-label button>PK</ion-label>
|
||||
<ion-input
|
||||
aria-label="pk"
|
||||
id="pastePk"
|
||||
v-model="shownPk"
|
||||
readonly
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
|
||||
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, Ref } from "vue";
|
||||
import { getAccounts, copyAddress, replaceAccounts, getSettings, clearPk, getSelectedAccount, saveSelectedAccount } from "@/utils/platform"
|
||||
import {
|
||||
getAccounts,
|
||||
copyText,
|
||||
replaceAccounts,
|
||||
getSettings,
|
||||
clearPk,
|
||||
getSelectedAccount,
|
||||
saveSelectedAccount,
|
||||
} from "@/utils/platform";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
|
@ -87,14 +96,14 @@ import {
|
|||
IonToast,
|
||||
modalController,
|
||||
IonInput,
|
||||
IonModal
|
||||
IonModal,
|
||||
} from "@ionic/vue";
|
||||
|
||||
import { addCircleOutline, copyOutline } from "ionicons/icons";
|
||||
import router from "@/router";
|
||||
import UnlockModal from '@/views/UnlockModal.vue'
|
||||
import UnlockModal from "@/views/UnlockModal.vue";
|
||||
|
||||
import type { Account, Settings } from '@/extension/types'
|
||||
import type { Account, Settings } from "@/extension/types";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -109,120 +118,118 @@ export default defineComponent({
|
|||
IonLabel,
|
||||
IonChip,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonToast,
|
||||
IonInput,
|
||||
IonModal
|
||||
IonButton,
|
||||
IonToast,
|
||||
IonInput,
|
||||
IonModal,
|
||||
},
|
||||
setup () {
|
||||
const accounts = ref([]) as Ref<Account[]>
|
||||
const loading = ref(true)
|
||||
const toastState = ref(false)
|
||||
const shownPk = ref('')
|
||||
const pkModal = ref(false)
|
||||
const settings = ref({}) as Ref<Settings>
|
||||
setup() {
|
||||
const accounts = ref([]) as Ref<Account[]>;
|
||||
const loading = ref(true);
|
||||
const toastState = ref(false);
|
||||
const shownPk = ref("");
|
||||
const pkModal = ref(false);
|
||||
const settings = ref({}) as Ref<Settings>;
|
||||
|
||||
const getToastRef = () => toastState;
|
||||
|
||||
const getToastRef = () => toastState
|
||||
|
||||
const loadData = () => {
|
||||
const pAccounts = getAccounts()
|
||||
const pGetSettings = getSettings()
|
||||
Promise.all([pAccounts, pGetSettings]).then(( res ) => {
|
||||
accounts.value = res[0]
|
||||
settings.value = res[1]
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
const pAccounts = getAccounts();
|
||||
const pGetSettings = getSettings();
|
||||
Promise.all([pAccounts, pGetSettings]).then((res) => {
|
||||
accounts.value = res[0];
|
||||
settings.value = res[1];
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAccount = async (address: string) => {
|
||||
loading.value = true
|
||||
if(settings.value.enableStorageEnctyption) {
|
||||
const modalR = await openModal('delAcc')
|
||||
if(!modalR){
|
||||
return
|
||||
}
|
||||
loading.value = true;
|
||||
if (settings.value.enableStorageEnctyption) {
|
||||
const modalR = await openModal("delAcc");
|
||||
if (!modalR) {
|
||||
return;
|
||||
}
|
||||
const findIndex = accounts.value.findIndex(a => a.address === address)
|
||||
const selectedAccount = await getSelectedAccount()
|
||||
const pArr: Array<Promise<void>> = []
|
||||
if (findIndex !== -1) {
|
||||
accounts.value.splice(findIndex, 1)
|
||||
pArr.push(replaceAccounts([...accounts.value]))
|
||||
}
|
||||
if(selectedAccount.address === address) {
|
||||
pArr.push(saveSelectedAccount({ name: '', pk: '', encPk: '', address: ''}))
|
||||
}
|
||||
await Promise.all(pArr)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
const findIndex = accounts.value.findIndex((a) => a.address === address);
|
||||
const selectedAccount = await getSelectedAccount();
|
||||
const pArr: Array<Promise<void>> = [];
|
||||
if (findIndex !== -1) {
|
||||
accounts.value.splice(findIndex, 1);
|
||||
pArr.push(replaceAccounts([...accounts.value]));
|
||||
}
|
||||
if (selectedAccount.address === address) {
|
||||
pArr.push(saveSelectedAccount({ name: "", pk: "", encPk: "", address: "" }));
|
||||
}
|
||||
await Promise.all(pArr);
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const editAccount = (address: string) => {
|
||||
router.push(`add-account/edit/${address}`)
|
||||
}
|
||||
router.push(`add-account/edit/${address}`);
|
||||
};
|
||||
|
||||
const goToAddAccount = () => {
|
||||
router.push("/tabs/add-account");
|
||||
};
|
||||
|
||||
onIonViewWillEnter(() => {
|
||||
loadData()
|
||||
})
|
||||
loadData();
|
||||
});
|
||||
|
||||
const openModal = async (type: string) => {
|
||||
const modal = await modalController.create({
|
||||
component: UnlockModal,
|
||||
componentProps: {
|
||||
unlockType: type
|
||||
const openModal = async (type: string) => {
|
||||
const modal = await modalController.create({
|
||||
component: UnlockModal,
|
||||
componentProps: {
|
||||
unlockType: type,
|
||||
},
|
||||
});
|
||||
modal.present();
|
||||
const { role } = await modal.onWillDismiss();
|
||||
if (role === "confirm") return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const viewPk = async (addr: string) => {
|
||||
let pk = "";
|
||||
const account = accounts.value.find((a) => a.address === addr);
|
||||
if (settings.value.enableStorageEnctyption) {
|
||||
if (account?.encPk) {
|
||||
const modalR = await openModal("viewPk");
|
||||
if (modalR) {
|
||||
const account = (await getAccounts()).find((a) => a.address === addr);
|
||||
pk = account?.pk ?? "";
|
||||
}
|
||||
|
||||
});
|
||||
modal.present();
|
||||
const { role } = await modal.onWillDismiss();
|
||||
if(role === 'confirm') return true
|
||||
return false
|
||||
}
|
||||
|
||||
const viewPk = async (addr: string) => {
|
||||
let pk = ''
|
||||
const account = accounts.value.find(a => a.address === addr)
|
||||
if(settings.value.enableStorageEnctyption) {
|
||||
if(account?.encPk) {
|
||||
const modalR = await openModal('viewPk')
|
||||
if(modalR){
|
||||
const account = (await getAccounts()).find(a => a.address === addr)
|
||||
pk = account?.pk ?? ''
|
||||
}
|
||||
}else {
|
||||
pk = account?.pk ?? ''
|
||||
}
|
||||
}else {
|
||||
pk = account?.pk ?? ''
|
||||
} else {
|
||||
pk = account?.pk ?? "";
|
||||
}
|
||||
if(pk) {
|
||||
shownPk.value = pk
|
||||
if(settings.value.encryptAfterEveryTx) {
|
||||
clearPk()
|
||||
}
|
||||
pkModal.value = true
|
||||
} else {
|
||||
pk = account?.pk ?? "";
|
||||
}
|
||||
if (pk) {
|
||||
shownPk.value = pk;
|
||||
if (settings.value.encryptAfterEveryTx) {
|
||||
clearPk();
|
||||
}
|
||||
pkModal.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
accounts,
|
||||
addCircleOutline,
|
||||
copyOutline,
|
||||
toastState,
|
||||
copyAddress,
|
||||
getToastRef,
|
||||
deleteAccount,
|
||||
editAccount,
|
||||
loading,
|
||||
goToAddAccount,
|
||||
viewPk,
|
||||
pkModal,
|
||||
shownPk
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
accounts,
|
||||
addCircleOutline,
|
||||
copyOutline,
|
||||
toastState,
|
||||
copyText,
|
||||
getToastRef,
|
||||
deleteAccount,
|
||||
editAccount,
|
||||
loading,
|
||||
goToAddAccount,
|
||||
viewPk,
|
||||
pkModal,
|
||||
shownPk,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-label>Name</ion-label>
|
||||
<ion-input v-model="name"></ion-input>
|
||||
<ion-input label="Name" labelPlacement="stacked" v-model="name"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Get Random Name</ion-label>
|
||||
|
@ -23,8 +22,12 @@
|
|||
:icon="clipboardOutline"
|
||||
button
|
||||
/>
|
||||
<ion-label button>PK</ion-label>
|
||||
<ion-input id="pastePk" v-model="pk"></ion-input>
|
||||
<ion-input
|
||||
label="PK"
|
||||
labelPlacement="stacked"
|
||||
id="pastePk"
|
||||
v-model="pk"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<template v-if="!isEdit">
|
||||
<ion-item>
|
||||
|
@ -33,15 +36,22 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="mnemonicModal = true" expand="full"
|
||||
>Extarct From A Mnemonic</ion-button
|
||||
>Extract From A Mnemonic</ion-button
|
||||
>
|
||||
</ion-item>
|
||||
</template>
|
||||
<ion-item>
|
||||
<ion-button @click="onCancel">Cancel</ion-button>
|
||||
<ion-button @click="onAddAccount">{{
|
||||
isEdit ? "Edit Account" : "Add Account"
|
||||
}}</ion-button>
|
||||
<ion-button
|
||||
@click="
|
||||
() => {
|
||||
isEdit ? onEditAccount() : onAddAccount();
|
||||
}
|
||||
"
|
||||
expand="full"
|
||||
color="primary"
|
||||
>{{ isEdit ? "Edit Account" : "Add Account" }}</ion-button
|
||||
>
|
||||
</ion-item>
|
||||
<ion-alert
|
||||
:is-open="alertOpen"
|
||||
|
@ -67,6 +77,7 @@
|
|||
<ion-item>
|
||||
<ion-textarea
|
||||
style="overflow-y: scroll"
|
||||
aria-label="Enter mnemonic"
|
||||
:rows="10"
|
||||
:cols="10"
|
||||
v-model="mnemonic"
|
||||
|
@ -74,9 +85,12 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Enter Index (default: 0)</ion-label>
|
||||
<ion-input v-model="mnemonicIndex"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input aria-label="mnemonic index" v-model="mnemonicIndex"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="mnemonicModal = false" color="light">Close</ion-button>
|
||||
<ion-button @click="extractMnemonic">Extract</ion-button>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
|
@ -110,10 +124,10 @@ import {
|
|||
saveSelectedAccount,
|
||||
getAccounts,
|
||||
saveAccount,
|
||||
getRandomPk,
|
||||
smallRandomString,
|
||||
paste,
|
||||
getSettings,
|
||||
replaceAccounts,
|
||||
} from "@/utils/platform";
|
||||
import router from "@/router";
|
||||
import { useRoute } from "vue-router";
|
||||
|
@ -122,7 +136,7 @@ import UnlockModal from "@/views/UnlockModal.vue";
|
|||
import { encrypt, getCryptoParams } from "@/utils/webCrypto";
|
||||
|
||||
import { clipboardOutline } from "ionicons/icons";
|
||||
import { getFromMemonic } from "@/utils/wallet";
|
||||
import { getFromMnemonic, getRandomPk } from "@/utils/wallet";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -186,8 +200,48 @@ export default defineComponent({
|
|||
}
|
||||
});
|
||||
|
||||
const deleteAccount = async (address: string, accounts: Account[]) => {
|
||||
const findIndex = accounts.findIndex((a) => a.address === address);
|
||||
const pArr: Array<Promise<void>> = [];
|
||||
if (findIndex !== -1) {
|
||||
accounts.splice(findIndex, 1);
|
||||
pArr.push(replaceAccounts([...accounts]));
|
||||
}
|
||||
await Promise.all(pArr);
|
||||
};
|
||||
|
||||
const onEditAccount = async () => {
|
||||
if (name.value.length < 1) {
|
||||
alertMsg.value = "Name cannot be empty.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
const accounts = (await accountsProm) as Account[];
|
||||
const account = accounts.find((acc) => acc.address === paramAddress);
|
||||
if (!account) {
|
||||
alertMsg.value = "Account not found.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
const savedAcc = {
|
||||
address: account.address,
|
||||
name: name.value,
|
||||
pk: account.pk,
|
||||
encPk: account.encPk,
|
||||
};
|
||||
await deleteAccount(account.address, accounts);
|
||||
|
||||
await saveAccount(savedAcc);
|
||||
router.push("/tabs/accounts");
|
||||
};
|
||||
|
||||
const onAddAccount = async () => {
|
||||
let p1 = Promise.resolve();
|
||||
if (name.value.length < 1) {
|
||||
alertMsg.value = "Name cannot be empty.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (pk.value.length === 64) {
|
||||
pk.value = `0x${pk.value.trim()}`;
|
||||
}
|
||||
|
@ -294,7 +348,7 @@ export default defineComponent({
|
|||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
pk.value = getFromMemonic(mnemonic.value, mnemonicIndex.value);
|
||||
pk.value = getFromMnemonic(mnemonic.value, mnemonicIndex.value);
|
||||
mnemonicModal.value = false;
|
||||
};
|
||||
|
||||
|
@ -314,6 +368,7 @@ export default defineComponent({
|
|||
mnemonic,
|
||||
mnemonicIndex,
|
||||
extractMnemonic,
|
||||
onEditAccount,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title v-if="!isEdit">Add Contact</ion-title>
|
||||
<ion-title v-else>Edit Contact</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-input label="Name" labelPlacement="stacked" v-model="localName"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
style="margin-right: 0.5rem"
|
||||
@click="paste('address')"
|
||||
:icon="clipboardOutline"
|
||||
button
|
||||
/>
|
||||
<ion-input
|
||||
label="Address"
|
||||
labelPlacement="stacked"
|
||||
id="address"
|
||||
v-model="localAddress"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="close">Cancel</ion-button>
|
||||
<ion-button @click="onAddContact">{{
|
||||
isEdit ? "Edit Contact" : "Add Contact"
|
||||
}}</ion-button>
|
||||
</ion-item>
|
||||
<ion-alert
|
||||
:is-open="alertOpen"
|
||||
header="Error"
|
||||
:message="alertMsg"
|
||||
:buttons="['OK']"
|
||||
@didDismiss="alertOpen = false"
|
||||
></ion-alert>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonAlert,
|
||||
IonIcon,
|
||||
onIonViewWillEnter,
|
||||
modalController,
|
||||
} from "@ionic/vue";
|
||||
import { paste, saveContact } from "@/utils/platform";
|
||||
|
||||
import { clipboardOutline } from "ionicons/icons";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonAlert,
|
||||
IonIcon,
|
||||
},
|
||||
|
||||
props: {
|
||||
address: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup: (props) => {
|
||||
const localName = ref(props.name);
|
||||
const localAddress = ref(props.address);
|
||||
const localIsEdit = ref(props.isEdit);
|
||||
const alertOpen = ref(false);
|
||||
const alertMsg = ref("");
|
||||
|
||||
const resetFields = () => {
|
||||
localName.value = "";
|
||||
localAddress.value = "";
|
||||
};
|
||||
|
||||
onIonViewWillEnter(async () => {});
|
||||
|
||||
const onAddContact = async () => {
|
||||
if (!localName.value) {
|
||||
alertMsg.value = "Name is required.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
if (!localAddress.value) {
|
||||
alertMsg.value = "Address is required.";
|
||||
alertOpen.value = true;
|
||||
return;
|
||||
}
|
||||
await saveContact({
|
||||
name: localName.value,
|
||||
address: localAddress.value,
|
||||
});
|
||||
resetFields();
|
||||
modalController.dismiss(
|
||||
{
|
||||
name: localName.value,
|
||||
address: localAddress.value,
|
||||
},
|
||||
"confirm"
|
||||
);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
try {
|
||||
modalController.dismiss(null, "cancel");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
localName,
|
||||
localAddress,
|
||||
onAddContact,
|
||||
close,
|
||||
alertOpen,
|
||||
alertMsg,
|
||||
clipboardOutline,
|
||||
paste,
|
||||
localIsEdit,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -10,31 +10,46 @@
|
|||
>Add from popular chain list</ion-button
|
||||
>
|
||||
<ion-item>
|
||||
<ion-label>Name(*)</ion-label>
|
||||
<ion-input v-model="name" placeholder="ex: Polygon"></ion-input>
|
||||
<ion-input
|
||||
label="Name(*)"
|
||||
labelPlacement="stacked"
|
||||
v-model="name"
|
||||
placeholder="ex: Polygon"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>ChainId(*)</ion-label>
|
||||
<ion-input v-model="chainId" placeholder="137" type="number"></ion-input>
|
||||
<ion-input
|
||||
label="ChainId(*)"
|
||||
labelPlacement="stacked"
|
||||
v-model="chainId"
|
||||
placeholder="137"
|
||||
type="number"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item button>
|
||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteRpc')" />
|
||||
<ion-label>RPC URL(*)</ion-label>
|
||||
<ion-input
|
||||
label="RPC URL(*)"
|
||||
labelPlacement="stacked"
|
||||
id="pasteRpc"
|
||||
placeholder="https://polygon-mainnet.g.alchemy.com/..."
|
||||
v-model="rpc"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item button>
|
||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteRpc')" />
|
||||
<ion-label>Native Token Symbol(?)</ion-label>
|
||||
<ion-input id="pasteRpc" placeholder="MATIC" v-model="symbol"></ion-input>
|
||||
<ion-input
|
||||
label="Native Token Symbol"
|
||||
labelPlacement="stacked"
|
||||
id="native-token"
|
||||
placeholder="MATIC"
|
||||
v-model="symbol"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item button>
|
||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteExplorer')" />
|
||||
<ion-label>Explorer(?)</ion-label>
|
||||
<ion-input
|
||||
label="Explorer"
|
||||
labelPlacement="stacked"
|
||||
id="pasteExplorer"
|
||||
placeholder="https://polygonscan.com"
|
||||
v-model="explorer"
|
||||
|
@ -144,6 +159,7 @@ import {
|
|||
import {
|
||||
getNetworks,
|
||||
saveSelectedNetwork,
|
||||
getSelectedNetwork,
|
||||
getUrl,
|
||||
paste,
|
||||
replaceNetworks,
|
||||
|
@ -188,7 +204,6 @@ export default defineComponent({
|
|||
const route = useRoute();
|
||||
const isEdit = route.path.includes("/edit");
|
||||
const paramChainId = route.params.chainId ?? "";
|
||||
let networksProm: Promise<Networks | undefined>;
|
||||
|
||||
const fillNetworkInputs = (network: Network) => {
|
||||
name.value = network.name;
|
||||
|
@ -200,8 +215,7 @@ export default defineComponent({
|
|||
|
||||
onIonViewWillEnter(async () => {
|
||||
if (isEdit && paramChainId) {
|
||||
networksProm = getNetworks();
|
||||
const networks = (await networksProm) as Networks;
|
||||
const networks = (await getNetworks()) as Networks;
|
||||
fillNetworkInputs(networks[Number(paramChainId)]);
|
||||
}
|
||||
});
|
||||
|
@ -234,10 +248,13 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
let p1 = Promise.resolve();
|
||||
if (!networksProm) {
|
||||
networksProm = getNetworks();
|
||||
}
|
||||
const networks = (await networksProm) as Networks;
|
||||
const networksProm = getNetworks();
|
||||
const selectedNetworkProm = getSelectedNetwork();
|
||||
|
||||
const allNetworks = await Promise.all([networksProm, selectedNetworkProm]);
|
||||
const networks = allNetworks[0] as Networks;
|
||||
const selectedNetwork = allNetworks[1] as Network;
|
||||
|
||||
const network = {
|
||||
name: name.value,
|
||||
chainId: chainId.value,
|
||||
|
@ -245,7 +262,10 @@ export default defineComponent({
|
|||
...(symbol.value ? { symbol: symbol.value } : {}),
|
||||
...(explorer.value ? { explorer: explorer.value } : {}),
|
||||
};
|
||||
if ((Object.keys(networks).length ?? 0) < 1) {
|
||||
if (
|
||||
(Object.keys(networks).length ?? 0) < 1 ||
|
||||
selectedNetwork.chainId === chainId.value
|
||||
) {
|
||||
p1 = saveSelectedNetwork(network);
|
||||
} else {
|
||||
if (chainId.value in networks && !isEdit) {
|
||||
|
|
|
@ -35,6 +35,23 @@
|
|||
<ion-label>Settings</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="send-token" href="/tabs/send-token">
|
||||
<ion-icon :icon="sendOutline"></ion-icon>
|
||||
<ion-label>Send Tokens</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="read-contract" href="/tabs/read-contract">
|
||||
<ion-icon :icon="glassesOutline"></ion-icon>
|
||||
<ion-label>Read Contract</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="write-contract" href="/tabs/write-contract">
|
||||
<ion-icon :icon="pushOutline"></ion-icon>
|
||||
<ion-label>Write Contracts</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
|
@ -50,9 +67,19 @@ import {
|
|||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonLabel,
|
||||
IonIcon
|
||||
IonIcon,
|
||||
} from "@ionic/vue";
|
||||
import { personCircle, walletOutline, diamondOutline, cogOutline, receiptOutline, gitNetworkOutline } from "ionicons/icons";
|
||||
import {
|
||||
personCircle,
|
||||
walletOutline,
|
||||
diamondOutline,
|
||||
cogOutline,
|
||||
receiptOutline,
|
||||
gitNetworkOutline,
|
||||
sendOutline,
|
||||
glassesOutline,
|
||||
pushOutline,
|
||||
} from "ionicons/icons";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -63,7 +90,7 @@ export default defineComponent({
|
|||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonLabel,
|
||||
IonIcon
|
||||
IonIcon,
|
||||
},
|
||||
name: "AppTabs",
|
||||
setup() {
|
||||
|
@ -81,8 +108,11 @@ export default defineComponent({
|
|||
cogOutline,
|
||||
receiptOutline,
|
||||
gitNetworkOutline,
|
||||
sendOutline,
|
||||
beforeTabChange,
|
||||
afterTabChange,
|
||||
glassesOutline,
|
||||
pushOutline,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
>
|
||||
</ion-loading>
|
||||
<ion-toast
|
||||
position="top"
|
||||
:is-open="toastState"
|
||||
@didDismiss="toastState = false"
|
||||
message="Copied to clipboard"
|
||||
|
@ -25,7 +26,7 @@
|
|||
<ion-item>
|
||||
<ion-label>Assests for Account: {{ selectedAccount?.name }}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button @click="copyAddress(selectedAccount?.address, getToastRef())">
|
||||
<ion-item button @click="copyText(selectedAccount?.address, getToastRef())">
|
||||
<p style="font-size: 0.7rem">{{ selectedAccount?.address }}</p>
|
||||
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
|
||||
</ion-item>
|
||||
|
@ -33,7 +34,11 @@
|
|||
Assets info could not be retrieved because of an http error, API down or
|
||||
conectivity issues.
|
||||
</template>
|
||||
<template v-else-if="noAssets"> No assets found for this wallet address. </template>
|
||||
<template v-else-if="noAssets">
|
||||
<p class="padding: 1rem;">
|
||||
No know assets found for this wallet address.
|
||||
</p></template
|
||||
>
|
||||
<template v-else>
|
||||
<template v-if="ethTokens.length || polyTokens.length">
|
||||
<template v-if="ethTokens.length">
|
||||
|
@ -42,12 +47,12 @@
|
|||
<ion-item v-for="token of ethTokens" :key="token.address">
|
||||
<ion-avatar
|
||||
v-if="token?.image"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="token?.name"
|
||||
:src="token?.image"
|
||||
@error="token.image = getUrl('assets/randomGrad.svg')"
|
||||
@error="token.image = getUrl('assets/chain-icons/eth.webp')"
|
||||
/>
|
||||
</ion-avatar>
|
||||
<ion-label
|
||||
|
@ -66,7 +71,7 @@
|
|||
<ion-item v-for="token of polyTokens" :key="token.address">
|
||||
<ion-avatar
|
||||
v-if="token?.image"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="token?.name"
|
||||
|
@ -91,7 +96,7 @@
|
|||
<ion-item v-for="nft of ethNfts" :key="nft.address">
|
||||
<ion-avatar
|
||||
v-if="nft?.imageURI"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="nft?.collectionName"
|
||||
|
@ -115,7 +120,7 @@
|
|||
<ion-item v-for="nft of polyNfts" :key="nft.address">
|
||||
<ion-avatar
|
||||
v-if="nft?.imageURI"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="nft?.collectionName"
|
||||
|
@ -139,7 +144,7 @@
|
|||
<ion-item v-for="nft of poaps" :key="nft.eventId">
|
||||
<ion-avatar
|
||||
v-if="nft?.image"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="nft?.title"
|
||||
|
@ -179,7 +184,7 @@ import {
|
|||
IonLoading,
|
||||
IonIcon,
|
||||
} from "@ionic/vue";
|
||||
import { getSelectedAccount, copyAddress, getUrl } from "@/utils/platform";
|
||||
import { getSelectedAccount, copyText, getUrl } from "@/utils/platform";
|
||||
import type { Account } from "@/extension/types";
|
||||
|
||||
import { copyOutline } from "ionicons/icons";
|
||||
|
@ -274,7 +279,7 @@ export default defineComponent({
|
|||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch web3 profiles", error);
|
||||
console.info("ERROR: Failed to fetch web3 profiles", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@ -559,7 +564,7 @@ export default defineComponent({
|
|||
isError,
|
||||
noAssets,
|
||||
getToastRef,
|
||||
copyAddress,
|
||||
copyText,
|
||||
copyOutline,
|
||||
ethTokens,
|
||||
polyTokens,
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="close">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Select Contact</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-button @click="openModalAddContact()" expand="block">Add contact</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-searchbar placeholder="Search" @ionInput="onSearch"></ion-searchbar>
|
||||
</ion-item>
|
||||
|
||||
<ion-radio-group :value="selectedContact">
|
||||
<ion-list-header>
|
||||
<ion-label>Contacts</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-list class="ion-padding" v-for="(item, index) in contacts" :key="index">
|
||||
<ion-item>
|
||||
<ion-radio
|
||||
@click="changeSelected(item.address)"
|
||||
slot="start"
|
||||
:value="item"
|
||||
:aria-label="item"
|
||||
>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>{{ item.name }}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label style="font-size: 0.75rem">{{ item.address }}</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="openModalAddContact(item.address)" expand="block"
|
||||
>Edit contact</ion-button
|
||||
>
|
||||
<ion-button @click="deleteContact(item.address)" expand="block"
|
||||
>Delete contact</ion-button
|
||||
>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-list v-if="!!!contacts.length">
|
||||
<ion-item class="ion-padding">
|
||||
<ion-label>No contacts found, please add at least one</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-radio-group>
|
||||
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="4000"
|
||||
:key="`k${loading}`"
|
||||
@didDismiss="loading = false"
|
||||
>
|
||||
</ion-loading>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonList,
|
||||
IonItem,
|
||||
modalController,
|
||||
IonRadio,
|
||||
IonListHeader,
|
||||
IonRadioGroup,
|
||||
IonLabel,
|
||||
IonLoading,
|
||||
IonSearchbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
} from "@ionic/vue";
|
||||
import { ref, onMounted, Ref } from "vue";
|
||||
import AddContact from "@/views/AddContact.vue";
|
||||
import { getContacts, replaceContacts } from "@/utils/platform";
|
||||
import type { Contact } from "@/extension/types";
|
||||
|
||||
const loading = ref(false);
|
||||
let intialContacts = [] as Contact[];
|
||||
const contacts = ref([]) as Ref<Contact[]>;
|
||||
const selectedContact = ref(null) as Ref<Contact | null>;
|
||||
|
||||
const onSearch = (e: any) => {
|
||||
const text = e.target.value;
|
||||
if (text) {
|
||||
contacts.value = contacts.value.filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(text.toLowerCase()) ||
|
||||
item.address.toLowerCase().includes(text.toLowerCase())
|
||||
);
|
||||
} else {
|
||||
contacts.value = intialContacts;
|
||||
}
|
||||
};
|
||||
|
||||
const loadContacts = async () => {
|
||||
loading.value = true;
|
||||
intialContacts = await getContacts();
|
||||
contacts.value = intialContacts;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
loadContacts();
|
||||
});
|
||||
|
||||
const openModalAddContact = async (address = "") => {
|
||||
let modal: Awaited<ReturnType<typeof modalController.create>>;
|
||||
if (address) {
|
||||
const contact = contacts.value.find((item) => item.address === address);
|
||||
modal = await modalController.create({
|
||||
component: AddContact,
|
||||
componentProps: {
|
||||
name: contact?.name,
|
||||
address: contact?.address,
|
||||
isEdit: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
modal = await modalController.create({
|
||||
component: AddContact,
|
||||
componentProps: {},
|
||||
});
|
||||
}
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
if (role === "confirm") {
|
||||
selectedContact.value = data;
|
||||
loadContacts();
|
||||
}
|
||||
};
|
||||
|
||||
const deleteContact = async (address: string) => {
|
||||
loading.value = true;
|
||||
const newContacts = contacts.value.filter((item) => item.address !== address) ?? [];
|
||||
await replaceContacts(newContacts);
|
||||
contacts.value = newContacts;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const changeSelected = (address: string) => {
|
||||
const contact = contacts.value.find((item) => item.address === address);
|
||||
modalController.dismiss(contact, "confirm");
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
try {
|
||||
modalController.dismiss(null, "cancel");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -12,12 +12,12 @@
|
|||
>
|
||||
<ion-item>
|
||||
<ion-avatar
|
||||
v-if="(mainNets as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
v-if="(allTemplateNets as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="selectedNetwork?.name"
|
||||
:src="getUrl('assets/chain-icons/' + (mainNets as any)[selectedNetwork?.chainId]?.icon)"
|
||||
:src="getUrl('assets/chain-icons/' + (allTemplateNets as any)[selectedNetwork?.chainId]?.icon)"
|
||||
/>
|
||||
</ion-avatar>
|
||||
<ion-label>Network ID: {{ selectedNetwork?.chainId }}</ion-label>
|
||||
|
@ -33,6 +33,7 @@
|
|||
<ion-label>Error From Contract:</ion-label>
|
||||
<ion-textarea
|
||||
style="overflow-y: scroll"
|
||||
aria-label="Error"
|
||||
:rows="10"
|
||||
:cols="20"
|
||||
:value="error"
|
||||
|
@ -74,7 +75,7 @@ import {
|
|||
import { useRoute } from "vue-router";
|
||||
import { getSelectedNetwork, getUrl, hexTostr } from "@/utils/platform";
|
||||
import type { Network } from "@/extension/types";
|
||||
import { mainNets } from "@/utils/networks";
|
||||
import { allTemplateNets } from "@/utils/networks";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -111,7 +112,7 @@ export default defineComponent({
|
|||
contract,
|
||||
loading,
|
||||
selectedNetwork,
|
||||
mainNets,
|
||||
allTemplateNets,
|
||||
getUrl,
|
||||
error,
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
><b style="margin-right: 0.5rem">Date:</b>
|
||||
{{ new Date(item.date).toDateString() }}</ion-item
|
||||
>
|
||||
<ion-item button @click="copyAddress(item.txHash, getToastRef())">
|
||||
<ion-item button @click="copyText(item.txHash, getToastRef())">
|
||||
<p style="font-size: 0.7rem">
|
||||
<b style="margin-right: 0.5rem"
|
||||
><ion-icon
|
||||
|
@ -62,6 +62,7 @@
|
|||
>
|
||||
</ion-loading>
|
||||
<ion-toast
|
||||
position="top"
|
||||
:is-open="toastState"
|
||||
@didDismiss="toastState = false"
|
||||
message="Copied to clipboard"
|
||||
|
@ -87,7 +88,7 @@ import {
|
|||
IonButton,
|
||||
IonIcon,
|
||||
} from "@ionic/vue";
|
||||
import { getHistory, copyAddress, wipeHistory, openTab } from "@/utils/platform";
|
||||
import { getHistory, copyText, wipeHistory, openTab } from "@/utils/platform";
|
||||
import type { HistoryItem } from "@/extension/types";
|
||||
|
||||
import { copyOutline } from "ionicons/icons";
|
||||
|
@ -129,7 +130,7 @@ export default defineComponent({
|
|||
return {
|
||||
history,
|
||||
loading,
|
||||
copyAddress,
|
||||
copyText,
|
||||
getToastRef,
|
||||
toastState,
|
||||
copyOutline,
|
||||
|
|
|
@ -2,7 +2,29 @@
|
|||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Wallet</ion-title>
|
||||
<ion-title>
|
||||
<ion-avatar
|
||||
style="margin: 0.3rem; width: 1.6rem; height: 1.6rem; display: inline-flex"
|
||||
>
|
||||
<img alt="clw" :src="getUrl('assets/extension-icon/wallet_32.png')" />
|
||||
</ion-avatar>
|
||||
<span style="position: absolute; top: 0.45rem; margin-left: 0.3rem"
|
||||
>CL Wallet</span
|
||||
>
|
||||
<span
|
||||
v-if="version"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0.3rem;
|
||||
right: 1.1rem;
|
||||
margin-left: 0.3rem;
|
||||
color: coral;
|
||||
font-weight: bold;
|
||||
font-size: 0.65rem;
|
||||
"
|
||||
>Version: {{ version }}</span
|
||||
>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
|
@ -13,10 +35,28 @@
|
|||
<ion-list v-else>
|
||||
<ion-item>
|
||||
<ion-label>Selected Account: {{ selectedAccount?.name }}</ion-label>
|
||||
<ion-button @click="accountsModal = true">Select</ion-button>
|
||||
<ion-button
|
||||
@click="
|
||||
() => {
|
||||
accountsModal = true;
|
||||
toastState = false;
|
||||
}
|
||||
"
|
||||
>Select</ion-button
|
||||
>
|
||||
</ion-item>
|
||||
<ion-item button @click="copyAddress(selectedAccount?.address, getToastRef())">
|
||||
<p style="font-size: 0.7rem">{{ selectedAccount?.address }}</p>
|
||||
<ion-item
|
||||
button
|
||||
@click="
|
||||
copyText(
|
||||
settings?.copyLowerCaseAddress
|
||||
? selectedAccount?.address?.toLowerCase()
|
||||
: selectedAccount?.address,
|
||||
getToastRef()
|
||||
)
|
||||
"
|
||||
>
|
||||
<p style="font-size: 0.7rem; color: coral">{{ selectedAccount?.address }}</p>
|
||||
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
|
@ -31,10 +71,15 @@
|
|||
)
|
||||
)
|
||||
"
|
||||
class="ion-text-wrap"
|
||||
expand="block"
|
||||
style="margin: auto; width: 98%; font-size: 0.8rem; padding: 0.6rem"
|
||||
>View Address on
|
||||
{{
|
||||
`${selectedNetwork.explorer}`.replace("https://", "").replace("http://", "")
|
||||
`${selectedNetwork.explorer}`
|
||||
.replace("https://", "")
|
||||
.replace("http://", "")
|
||||
.replace(/\/.*/, "")
|
||||
}}
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
@ -43,18 +88,50 @@
|
|||
<ion-label>No EVM Networks found</ion-label>
|
||||
<ion-button @click="goToAddNetwork">Add Network</ion-button>
|
||||
</ion-item>
|
||||
<ion-item v-else>
|
||||
<ion-item style="font-size: 0.86rem" v-else>
|
||||
<ion-avatar
|
||||
v-if="(mainNets as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
v-if="(allTemplateNets as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="selectedNetwork?.name"
|
||||
:src="getUrl('assets/chain-icons/' + (mainNets as any)[selectedNetwork?.chainId]?.icon)"
|
||||
:src="getUrl('assets/chain-icons/' + (allTemplateNets as any)[selectedNetwork?.chainId]?.icon)"
|
||||
/>
|
||||
</ion-avatar>
|
||||
<ion-label>Selected Network ID: {{ selectedNetwork?.chainId }}</ion-label>
|
||||
<ion-button @click="networksModal = true">Select</ion-button>
|
||||
<ion-label
|
||||
button
|
||||
@click="copyText(String(selectedNetwork?.chainId), getToastRef())"
|
||||
style="cursor: pointer"
|
||||
>Selected Network ID:
|
||||
<span style="color: coral; font-weight: bold">{{
|
||||
selectedNetwork?.chainId
|
||||
}}</span>
|
||||
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
@click="
|
||||
() => {
|
||||
networksModal = true;
|
||||
toastState = false;
|
||||
}
|
||||
"
|
||||
>Select</ion-button
|
||||
>
|
||||
</ion-item>
|
||||
<ion-item style="margin-top: 0.3rem">
|
||||
<div class="display: flex; flex-direction: column">
|
||||
<img
|
||||
alt="stealthex"
|
||||
@click="openTab('https://stealthex.io')"
|
||||
id="exchange-btn"
|
||||
:src="getUrl('assets/exchange-btn-min.svg')"
|
||||
class="exchange-btn"
|
||||
style=""
|
||||
/>
|
||||
<p style="font-size: 0.75rem; opacity: 0.8; padding: 0.2rem">
|
||||
This button does not contain any referral to maximize privacy.
|
||||
</p>
|
||||
</div>
|
||||
</ion-item>
|
||||
|
||||
<ion-loading
|
||||
|
@ -67,6 +144,7 @@
|
|||
>
|
||||
</ion-loading>
|
||||
<ion-toast
|
||||
position="top"
|
||||
:is-open="toastState"
|
||||
@didDismiss="toastState = false"
|
||||
message="Copied to clipboard"
|
||||
|
@ -98,11 +176,17 @@
|
|||
button
|
||||
>
|
||||
<ion-item>
|
||||
<ion-radio slot="start" :value="account.address" />
|
||||
<ion-label>{{ account.name }}</ion-label>
|
||||
<ion-radio
|
||||
:aria-label="account.name"
|
||||
slot="start"
|
||||
:value="account.address"
|
||||
>{{ account.name }}</ion-radio
|
||||
>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-text style="font-size: 0.8rem">{{ account.address }}</ion-text>
|
||||
<ion-text style="font-size: 0.7rem; color: coral">{{
|
||||
account.address
|
||||
}}</ion-text>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-radio-group>
|
||||
|
@ -135,11 +219,18 @@
|
|||
@click="changeSelectedNetwork(network.chainId)"
|
||||
slot="start"
|
||||
:value="network.chainId"
|
||||
/>
|
||||
<ion-label>{{ network.name }}</ion-label>
|
||||
:aria-label="network.name"
|
||||
>
|
||||
<span style="opacity: 0.7; font-size: 0.8rem">
|
||||
ID: {{ network.chainId }} ->
|
||||
</span>
|
||||
{{ network.name }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-text>{{ network.rpc }}</ion-text>
|
||||
<ion-text style="opacity: 0.8; font-size: 0.85rem">{{
|
||||
network.rpc
|
||||
}}</ion-text>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-radio-group>
|
||||
|
@ -180,20 +271,23 @@ import {
|
|||
saveSelectedAccount,
|
||||
replaceAccounts,
|
||||
getSelectedNetwork,
|
||||
copyAddress,
|
||||
copyText,
|
||||
replaceNetworks,
|
||||
getUrl,
|
||||
saveSelectedNetwork,
|
||||
numToHexStr,
|
||||
openTab,
|
||||
getSettings,
|
||||
getVersion,
|
||||
} from "@/utils/platform";
|
||||
import type { Network, Account, Networks } from "@/extension/types";
|
||||
import { mainNets } from "@/utils/networks";
|
||||
import { allTemplateNets } from "@/utils/networks";
|
||||
import router from "@/router";
|
||||
import { triggerListner } from "@/extension/listners";
|
||||
|
||||
import { copyOutline } from "ionicons/icons";
|
||||
|
||||
const version = getVersion();
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonContent,
|
||||
|
@ -225,6 +319,7 @@ export default defineComponent({
|
|||
const selectedAccount = (ref(null) as unknown) as Ref<Account>;
|
||||
const selectedNetwork = (ref(null) as unknown) as Ref<Network>;
|
||||
const toastState = ref(false);
|
||||
const settings = ref({}) as Ref<Awaited<ReturnType<typeof getSettings>>>;
|
||||
|
||||
const getToastRef = () => toastState;
|
||||
|
||||
|
@ -234,15 +329,21 @@ export default defineComponent({
|
|||
const pNetworks = getNetworks();
|
||||
const pSelectedAccount = getSelectedAccount();
|
||||
const pSelectedNetwork = getSelectedNetwork();
|
||||
Promise.all([pAccounts, pNetworks, pSelectedAccount, pSelectedNetwork]).then(
|
||||
(res) => {
|
||||
accounts.value = res[0];
|
||||
networks.value = res[1];
|
||||
selectedAccount.value = res[2];
|
||||
selectedNetwork.value = res[3];
|
||||
loading.value = false;
|
||||
}
|
||||
);
|
||||
const pSettings = getSettings();
|
||||
Promise.all([
|
||||
pAccounts,
|
||||
pNetworks,
|
||||
pSelectedAccount,
|
||||
pSelectedNetwork,
|
||||
pSettings,
|
||||
]).then((res) => {
|
||||
accounts.value = res[0];
|
||||
networks.value = res[1];
|
||||
selectedAccount.value = res[2];
|
||||
selectedNetwork.value = res[3];
|
||||
settings.value = res[4];
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onIonViewWillEnter(() => {
|
||||
|
@ -272,10 +373,7 @@ export default defineComponent({
|
|||
accounts.value.splice(0, 0, selectedAccount.value);
|
||||
const newAccounts = [...accounts.value];
|
||||
await replaceAccounts(newAccounts);
|
||||
triggerListner(
|
||||
"accountsChanged",
|
||||
newAccounts.map((a) => a.address)
|
||||
);
|
||||
triggerListner("accountsChanged", [newAccounts.map((a) => a.address)?.[0]]);
|
||||
}
|
||||
accountsModal.value = false;
|
||||
loading.value = false;
|
||||
|
@ -306,15 +404,33 @@ export default defineComponent({
|
|||
selectedNetwork,
|
||||
changeSelectedAccount,
|
||||
changeSelectedNetwork,
|
||||
copyAddress,
|
||||
copyText,
|
||||
copyOutline,
|
||||
toastState,
|
||||
getToastRef,
|
||||
networksModal,
|
||||
mainNets,
|
||||
allTemplateNets,
|
||||
getUrl,
|
||||
openTab,
|
||||
settings,
|
||||
version,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.exchange-btn {
|
||||
height: 2rem;
|
||||
margin-top: 0.3rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.exchange-btn:hover {
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<ion-toolbar>
|
||||
<ion-buttons slot="end">
|
||||
<router-link to="/tabs/add-network">
|
||||
<ion-button>
|
||||
<ion-icon slot="icon-only" :icon="addCircleOutline"></ion-icon>
|
||||
</ion-button>
|
||||
</router-link>
|
||||
</ion-buttons>
|
||||
<ion-button>
|
||||
<ion-icon slot="icon-only" :icon="addCircleOutline"></ion-icon>
|
||||
</ion-button>
|
||||
</router-link>
|
||||
</ion-buttons>
|
||||
<ion-title>Networks</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
@ -19,30 +19,34 @@
|
|||
<ion-button @click="goToAddNetwork">Add Network</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-list v-for="network of networks" :key="network.chainId">
|
||||
<ion-item>
|
||||
<ion-avatar v-if="(mainNets as any)[network.chainId]?.icon" style="margin-right: 1rem; width: 1.8rem; height:1.8rem;">
|
||||
<img :alt="network.name" :src="getUrl('assets/chain-icons/' + (mainNets as any)[network.chainId].icon)" />
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
{{ network.name }}
|
||||
</ion-label>
|
||||
<ion-label>
|
||||
ID: {{ network.chainId }}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-list v-for="network of networks" :key="network.chainId">
|
||||
<ion-item>
|
||||
<ion-chip @click="editNetwork(network.chainId)" button>Edit</ion-chip>
|
||||
<ion-chip @click="deleteNetwork(network.chainId)" button>Delete</ion-chip>
|
||||
<ion-avatar
|
||||
v-if="(allTemplateNets as any)[network.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="network.name"
|
||||
:src="getUrl('assets/chain-icons/' + (allTemplateNets as any)[network.chainId].icon)"
|
||||
/>
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
{{ network.name }}
|
||||
</ion-label>
|
||||
<ion-label> ID: {{ network.chainId }} </ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-item>
|
||||
<ion-chip @click="editNetwork(network.chainId)" button>Edit</ion-chip>
|
||||
<ion-chip @click="deleteNetwork(network.chainId)" button>Delete</ion-chip>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, Ref } from "vue";
|
||||
import { getNetworks, copyAddress, getUrl, replaceNetworks } from "@/utils/platform"
|
||||
import { getNetworks, copyText, getUrl, replaceNetworks } from "@/utils/platform";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
|
@ -57,12 +61,12 @@ import {
|
|||
IonButtons,
|
||||
IonButton,
|
||||
onIonViewWillEnter,
|
||||
IonAvatar
|
||||
IonAvatar,
|
||||
} from "@ionic/vue";
|
||||
import { mainNets } from "@/utils/networks"
|
||||
import { allTemplateNets } from "@/utils/networks";
|
||||
import { addCircleOutline, copyOutline } from "ionicons/icons";
|
||||
import router from '@/router/index'
|
||||
import type { Networks } from '@/extension/types'
|
||||
import router from "@/router/index";
|
||||
import type { Networks } from "@/extension/types";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -77,60 +81,57 @@ export default defineComponent({
|
|||
IonLabel,
|
||||
IonChip,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonAvatar
|
||||
IonButton,
|
||||
IonAvatar,
|
||||
},
|
||||
setup () {
|
||||
const networks = ref({}) as Ref<Networks>
|
||||
const loading = ref(true)
|
||||
const toastState = ref(false)
|
||||
setup() {
|
||||
const networks = ref({}) as Ref<Networks>;
|
||||
const loading = ref(true);
|
||||
const toastState = ref(false);
|
||||
|
||||
const getToastRef = () => toastState;
|
||||
|
||||
const getToastRef = () => toastState
|
||||
|
||||
const loadData = () => {
|
||||
const pAccounts = getNetworks()
|
||||
Promise.all([pAccounts]).then(( res ) => {
|
||||
networks.value = res[0]
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
const pAccounts = getNetworks();
|
||||
Promise.all([pAccounts]).then((res) => {
|
||||
networks.value = res[0];
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteNetwork = async (chainId: number) => {
|
||||
loading.value = true
|
||||
delete networks.value[chainId]
|
||||
await replaceNetworks(networks.value)
|
||||
loading.value = false
|
||||
}
|
||||
loading.value = true;
|
||||
delete networks.value[chainId];
|
||||
await replaceNetworks(networks.value);
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const editNetwork = (chainId: number) => {
|
||||
router.push(`add-network/edit/${chainId}`)
|
||||
}
|
||||
|
||||
router.push(`add-network/edit/${chainId}`);
|
||||
};
|
||||
|
||||
const goToAddNetwork = () => {
|
||||
router.push("/tabs/add-network");
|
||||
};
|
||||
|
||||
|
||||
onIonViewWillEnter(() => {
|
||||
loadData()
|
||||
})
|
||||
loadData();
|
||||
});
|
||||
|
||||
return {
|
||||
networks,
|
||||
addCircleOutline,
|
||||
copyOutline,
|
||||
toastState,
|
||||
copyAddress,
|
||||
getToastRef,
|
||||
getUrl,
|
||||
mainNets,
|
||||
deleteNetwork,
|
||||
editNetwork,
|
||||
loading,
|
||||
goToAddNetwork
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
networks,
|
||||
addCircleOutline,
|
||||
copyOutline,
|
||||
toastState,
|
||||
copyText,
|
||||
getToastRef,
|
||||
getUrl,
|
||||
allTemplateNets,
|
||||
deleteNetwork,
|
||||
editNetwork,
|
||||
loading,
|
||||
goToAddNetwork,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,637 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Read From Contract</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-button @click="selectSavedAction" expand="block"
|
||||
>Load saved read action</ion-button
|
||||
>
|
||||
<ion-item>
|
||||
<template v-if="selectedAbi">
|
||||
<p>Selected Abi: {{ selectedAbi }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>No Abi selected</p>
|
||||
</template>
|
||||
|
||||
<ion-button @click="openAbiListModal()" expand="block">Load Abi</ion-button>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteContract')" />
|
||||
<ion-input
|
||||
label="Contract Address(*)"
|
||||
label-placement="stacked"
|
||||
v-model="contractAddress"
|
||||
id="pasteContract"
|
||||
placeholder="0x..."
|
||||
type="text"
|
||||
style="font-size: 0.8rem"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item button>
|
||||
<ion-button @click="openModalAddContact()">
|
||||
Load address from contacts
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<ion-item button>
|
||||
<template v-if="!functions.length">
|
||||
<p>Select Abi with functions to enable function selection</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="functionName">
|
||||
<p>Selected Function: {{ functionName }}</p>
|
||||
<ion-button @click="selectFunction()" expand="block">Change</ion-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>No Function selected</p>
|
||||
<ion-button @click="selectFunction()" expand="block">Select</ion-button>
|
||||
</template>
|
||||
</template>
|
||||
<!-- <ion-input
|
||||
aria-label="function signature"
|
||||
placeholder="exists(uint256)"
|
||||
v-model="functionName"
|
||||
></ion-input> -->
|
||||
</ion-item>
|
||||
<template v-if="functionName">
|
||||
<ion-item>
|
||||
<ion-label>PARAMS NOTES:</ion-label>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-item
|
||||
>Will be evaluated in sandbox using js eval in order to pass complex types
|
||||
like [1,2 [1,0x...]]</ion-item
|
||||
>
|
||||
<ion-item
|
||||
>Strings must be passed using qoutes example '0x3...1A2', or ['param1',
|
||||
'param2'] for multiple params.</ion-item
|
||||
>
|
||||
<ion-item
|
||||
>Params are sent exactly as they are, numbers are not parsed to UINT256
|
||||
format.
|
||||
</ion-item>
|
||||
<ion-item>SET PARAMS: </ion-item>
|
||||
<ion-list v-for="(param, index) in params" :key="index" class="param-list">
|
||||
<ion-item>
|
||||
<ion-label style="font-size: 0.85rem"
|
||||
>P:{{ Number(index) + 1 }} name: {{ param.name }} type: ({{
|
||||
param.type
|
||||
}})</ion-label
|
||||
>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
aria-label="value"
|
||||
v-model="param.value"
|
||||
placeholder="ex: 1 or 0x22 or 'hello' or [1, 2] etc "
|
||||
type="text"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-item v-if="!params?.length">
|
||||
<ion-label>Function has no params</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</template>
|
||||
<ion-item>
|
||||
<ion-textarea
|
||||
label="Result"
|
||||
label-placement="stacked"
|
||||
style="overflow-y: scroll"
|
||||
:rows="10"
|
||||
:cols="20"
|
||||
:value="result"
|
||||
readonly
|
||||
></ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-button @click="saveActionInStorage">Save Action</ion-button>
|
||||
<ion-button @click="executeAction">Execute Action</ion-button>
|
||||
</ion-item>
|
||||
<ion-alert
|
||||
:is-open="alertOpen"
|
||||
:header="alertHeader"
|
||||
:message="alertMsg"
|
||||
:buttons="['OK']"
|
||||
@didDismiss="alertOpen = false"
|
||||
></ion-alert>
|
||||
|
||||
<iframe
|
||||
@load="sandboxLoaded = true"
|
||||
ref="evalFrame"
|
||||
src="eval-sandbox.html"
|
||||
style="display: none"
|
||||
></iframe>
|
||||
</ion-content>
|
||||
|
||||
<ion-modal :is-open="saveActionModal" @will-dismiss="saveActionModal = false">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="saveActionModal = false">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Select</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-list style="margin-bottom: 4rem">
|
||||
<ion-item>
|
||||
<ion-input
|
||||
label="Name(*)"
|
||||
label-placement="stacked"
|
||||
v-model="name"
|
||||
placeholder="ex: Get lens hande from id"
|
||||
type="text"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="saveActionModal = false">Cancel</ion-button>
|
||||
<ion-button @click="saveAction">Save</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, defineComponent, ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonTextarea,
|
||||
modalController,
|
||||
IonList,
|
||||
IonAlert,
|
||||
IonModal,
|
||||
IonButtons,
|
||||
} from "@ionic/vue";
|
||||
import { paste, readCASet, getAbis } from "@/utils/platform";
|
||||
import { clipboardOutline } from "ionicons/icons";
|
||||
import type { ContractAction } from "@/extension/types";
|
||||
import { ethers } from "ethers";
|
||||
import { getCurrentProvider } from "@/utils/wallet";
|
||||
import AbiList from "./AbiList.vue";
|
||||
import AbiSelectFunction from "./AbiSelectFunction.vue";
|
||||
import SavedReadWriteActionList from "./SavedReadWriteActionList.vue";
|
||||
import SelectedContacts from "./ContactsSelect.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonTextarea,
|
||||
IonModal,
|
||||
IonButtons,
|
||||
IonList,
|
||||
IonAlert,
|
||||
},
|
||||
setup: () => {
|
||||
const savedModalState = ref(false);
|
||||
const saveActionModal = ref(false);
|
||||
const alertOpen = ref(false);
|
||||
const alertMsg = ref("");
|
||||
const name = ref("");
|
||||
const contractAddress = ref("");
|
||||
const functionName = ref("");
|
||||
const params = ref([]) as Ref<{ value: string; type: string; name: "" }[]>;
|
||||
const result = ref("");
|
||||
const evalFrame = ref() as Ref<HTMLIFrameElement>;
|
||||
let messagePromiseResolve: (v: unknown) => void = () => {};
|
||||
const sandboxLoaded = ref(false);
|
||||
const abiContent = ref("");
|
||||
const selectedAbi = ref("");
|
||||
const alertHeader = ref("");
|
||||
let parsedAbi: any;
|
||||
const functions = ref([]) as Ref<string[]>;
|
||||
|
||||
const openAbiListModal = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: AbiList,
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
|
||||
if (role === "confirm") {
|
||||
abiContent.value = data.content;
|
||||
selectedAbi.value = data.name;
|
||||
parsedAbi = JSON.parse(abiContent.value);
|
||||
functions.value = parsedAbi
|
||||
.filter((fn: any) => fn.type === "function")
|
||||
.map((fn: any) => fn.name);
|
||||
}
|
||||
};
|
||||
|
||||
const selectFunction = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: AbiSelectFunction,
|
||||
componentProps: {
|
||||
functions: functions.value,
|
||||
},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
|
||||
if (role === "confirm") {
|
||||
functionName.value = data;
|
||||
params.value = parsedAbi
|
||||
.find((fn: any) => fn.name === data)
|
||||
.inputs.map((input: any) => {
|
||||
return { value: "", type: input.type, name: input.name };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const selectSavedAction = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: SavedReadWriteActionList,
|
||||
componentProps: {
|
||||
type: "read",
|
||||
},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
const { data, role } = (await modal.onWillDismiss()) as {
|
||||
data: ContractAction;
|
||||
role: string;
|
||||
};
|
||||
|
||||
if (role === "confirm") {
|
||||
const content = await getAbis(data.abi);
|
||||
if (!content) {
|
||||
alertMsg.value =
|
||||
"Abi not found in storage, be sure Abi with name " + data.abi + " exists.";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
abiContent.value = content;
|
||||
functionName.value = data.functionName;
|
||||
params.value = Object.values(data.params);
|
||||
contractAddress.value = data.contract;
|
||||
selectedAbi.value = data.abi;
|
||||
parsedAbi = JSON.parse(abiContent.value);
|
||||
|
||||
functions.value = parsedAbi
|
||||
.filter((fn: any) => fn.type === "function")
|
||||
.map((fn: any) => fn.name);
|
||||
}
|
||||
};
|
||||
|
||||
const saveActionInStorage = () => {
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
if (abiContent.value === "") {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
saveActionModal.value = true;
|
||||
};
|
||||
|
||||
const executeAction = async () => {
|
||||
if (sandboxLoaded.value === false) {
|
||||
alertMsg.value = "Sandbox for eval not loaded yet, please wait";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
if (!parsedAbi) {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
alertHeader.value = "Error";
|
||||
|
||||
const provider = await getCurrentProvider();
|
||||
const encodeParamsTypes = [];
|
||||
|
||||
let evalParams: any[] = [];
|
||||
try {
|
||||
evalParams = await Promise.all(
|
||||
params.value.map(async (param) => await getEvalValue(param.value))
|
||||
);
|
||||
} catch {
|
||||
alertMsg.value = "Error parsing params, check params types";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
try {
|
||||
if (functionName.value?.includes("(")) {
|
||||
const paramsTypes = functionName.value
|
||||
.split("(")[1]
|
||||
.split(")")[0]
|
||||
.split(",")
|
||||
.map((param) => param.trim());
|
||||
if (paramsTypes.length !== evalParams.length) {
|
||||
alertMsg.value = "Params count mismatch";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
encodeParamsTypes.push(...paramsTypes);
|
||||
}
|
||||
} catch {
|
||||
alertMsg.value =
|
||||
"Function Siganture wrong format (ex: 'functionName(uint256,string)')";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
const fnName = functionName.value.includes("(")
|
||||
? functionName.value.split("(")[0]
|
||||
: functionName.value;
|
||||
|
||||
const contract = new ethers.Contract(contractAddress.value, parsedAbi, provider);
|
||||
|
||||
try {
|
||||
const res = await contract[fnName](...evalParams);
|
||||
result.value = res.toString();
|
||||
|
||||
alertMsg.value = "Value from contract fetched check result area!";
|
||||
alertHeader.value = "OK";
|
||||
return (alertOpen.value = true);
|
||||
} catch (e) {
|
||||
alertMsg.value = "Function call failed, check params, contract address and ABI";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
};
|
||||
|
||||
const saveAction = async () => {
|
||||
if (!name.value) {
|
||||
alertMsg.value = "Name is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
const action = {
|
||||
name: name.value,
|
||||
contract: contractAddress.value,
|
||||
functionName: functionName.value,
|
||||
params: params.value,
|
||||
abi: selectedAbi.value,
|
||||
};
|
||||
|
||||
await readCASet(action);
|
||||
saveActionModal.value = false;
|
||||
alertMsg.value = "Action saved successfully";
|
||||
alertHeader.value = "OK";
|
||||
return (alertOpen.value = true);
|
||||
};
|
||||
|
||||
const messageHandler = (event: any) => {
|
||||
messagePromiseResolve(event?.data?.result);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("message", messageHandler);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("message", messageHandler);
|
||||
});
|
||||
|
||||
const getEvalValue = (evalString: string) => {
|
||||
return new Promise((resolve) => {
|
||||
if (!evalFrame.value?.contentWindow?.postMessage) {
|
||||
return;
|
||||
}
|
||||
messagePromiseResolve = resolve;
|
||||
evalFrame.value?.contentWindow?.postMessage({ code: evalString }, "*");
|
||||
});
|
||||
};
|
||||
|
||||
// const rpc = ref("");
|
||||
// const symbol = ref("");
|
||||
// const explorer = ref("");
|
||||
// const templateModal = ref(false);
|
||||
// const currentSegment = ref("mainnets");
|
||||
|
||||
// const route = useRoute();
|
||||
// const isEdit = route.path.includes("/edit");
|
||||
// const paramChainId = route.params.chainId ?? "";
|
||||
// let networksProm: Promise<Networks | undefined>;
|
||||
|
||||
// const fillNetworkInputs = (network: Network) => {
|
||||
// name.value = network.name;
|
||||
// chainId.value = network.chainId;
|
||||
// rpc.value = network.rpc;
|
||||
// symbol.value = network.symbol ?? "";
|
||||
// explorer.value = network.explorer ?? "";
|
||||
// };
|
||||
|
||||
// onIonViewWillEnter(async () => {
|
||||
// if (isEdit && paramChainId) {
|
||||
// networksProm = getNetworks();
|
||||
// const networks = (await networksProm) as Networks;
|
||||
// fillNetworkInputs(networks[Number(paramChainId)]);
|
||||
// }
|
||||
// });
|
||||
|
||||
// const resetFields = () => {
|
||||
// name.value = "";
|
||||
// chainId.value = 0;
|
||||
// rpc.value = "";
|
||||
// };
|
||||
|
||||
// const onAddNetwork = async () => {
|
||||
// if (Number(chainId.value) < 1) {
|
||||
// alertMsg.value = "Chain Id must be a valid decimal integer";
|
||||
// return (alertOpen.value = true);
|
||||
// }
|
||||
// if (name.value.length < 2) {
|
||||
// alertMsg.value = "Name must have at least 2 characters";
|
||||
// return (alertOpen.value = true);
|
||||
// }
|
||||
// if (name.value.length > 99) {
|
||||
// alertMsg.value = "Name must be less than 100 characters";
|
||||
// return (alertOpen.value = true);
|
||||
// }
|
||||
// if (name.value.length > 99) {
|
||||
// try {
|
||||
// new URL(rpc.value);
|
||||
// } catch {
|
||||
// alertMsg.value = "RPC must be a valid URL";
|
||||
// return (alertOpen.value = true);
|
||||
// }
|
||||
// }
|
||||
// let p1 = Promise.resolve();
|
||||
// if (!networksProm) {
|
||||
// networksProm = getNetworks();
|
||||
// }
|
||||
// const networks = (await networksProm) as Networks;
|
||||
// const network = {
|
||||
// name: name.value,
|
||||
// chainId: chainId.value,
|
||||
// rpc: rpc.value,
|
||||
// ...(symbol.value ? { symbol: symbol.value } : {}),
|
||||
// ...(explorer.value ? { explorer: explorer.value } : {}),
|
||||
// };
|
||||
// if ((Object.keys(networks).length ?? 0) < 1) {
|
||||
// p1 = saveSelectedNetwork(network);
|
||||
// } else {
|
||||
// if (chainId.value in networks && !isEdit) {
|
||||
// alertMsg.value = "Network already exists.";
|
||||
// return (alertOpen.value = true);
|
||||
// }
|
||||
// }
|
||||
// networks[chainId.value] = network;
|
||||
// const p2 = replaceNetworks(networks);
|
||||
// await Promise.all([p1, p2]);
|
||||
// if (isEdit) {
|
||||
// router.push("/tabs/networks");
|
||||
// } else {
|
||||
// router.push("/tabs/home");
|
||||
// }
|
||||
// resetFields();
|
||||
// };
|
||||
|
||||
// const segmentChange = (value: any) => {
|
||||
// currentSegment.value = value.detail.value;
|
||||
// };
|
||||
|
||||
// const onCancel = () => {
|
||||
// if (isEdit) {
|
||||
// router.push("/tabs/networks");
|
||||
// } else {
|
||||
// router.push("/tabs/home");
|
||||
// }
|
||||
// };
|
||||
|
||||
// const fillTemplate = (network: typeof mainNets[1]) => {
|
||||
// fillNetworkInputs(network);
|
||||
// modalController?.dismiss(null, "cancel");
|
||||
// };
|
||||
|
||||
// document.addEventListener('DOMContentLoaded', () => {
|
||||
// document.getElementById('reset').addEventListener('click', function () {
|
||||
// counter = 0;
|
||||
// document.querySelector('#result').innerHTML = '';
|
||||
// });
|
||||
|
||||
// document.getElementById('sendMessage').addEventListener('click', function () {
|
||||
// counter++;
|
||||
// let message = {
|
||||
// command: 'render',
|
||||
// templateName: 'sample-template-' + counter,
|
||||
// context: { counter: counter }
|
||||
// };
|
||||
// document.getElementById('theFrame').contentWindow.postMessage(message, '*');
|
||||
// });
|
||||
|
||||
// // on result from sandboxed frame:
|
||||
// window.addEventListener('message', function () {
|
||||
// document.querySelector('#result').innerHTML =
|
||||
// event.data.result || 'invalid result';
|
||||
// });
|
||||
// });
|
||||
|
||||
const openModalAddContact = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: SelectedContacts,
|
||||
componentProps: {},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
if (role === "confirm") {
|
||||
contractAddress.value = data.address;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFnChange = (event: any) => {
|
||||
functionName.value = event.detail.value;
|
||||
};
|
||||
|
||||
return {
|
||||
saveActionModal,
|
||||
handleFnChange,
|
||||
clipboardOutline,
|
||||
evalFrame,
|
||||
alertOpen,
|
||||
alertMsg,
|
||||
alertHeader,
|
||||
functionName,
|
||||
paste,
|
||||
savedModalState,
|
||||
name,
|
||||
contractAddress,
|
||||
params,
|
||||
// addParam,
|
||||
// removeParam,
|
||||
saveActionInStorage,
|
||||
executeAction,
|
||||
result,
|
||||
sandboxLoaded,
|
||||
openAbiListModal,
|
||||
selectedAbi,
|
||||
functions,
|
||||
selectFunction,
|
||||
saveAction,
|
||||
selectSavedAction,
|
||||
openModalAddContact,
|
||||
// onAddNetwork,
|
||||
// rpc,
|
||||
// onCancel,
|
||||
// templateModal,
|
||||
// currentSegment,
|
||||
// mainNets,
|
||||
// testNets,
|
||||
// segmentChange,
|
||||
// getUrl,
|
||||
// fillTemplate,
|
||||
|
||||
// symbol,
|
||||
// explorer,
|
||||
// isEdit,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.param-list {
|
||||
--border-width: 1px; /* Set your desired border width */
|
||||
}
|
||||
|
||||
.param-list ion-item {
|
||||
border-top: var(--border-width) solid #0ece6e;
|
||||
}
|
||||
|
||||
.param-list ion-item:last-child {
|
||||
border-bottom: var(--border-width) solid #0ece6e;
|
||||
}
|
||||
</style>
|
|
@ -22,6 +22,7 @@
|
|||
<ion-item>
|
||||
<ion-label>Name:</ion-label>
|
||||
<ion-input
|
||||
aria-label="Name"
|
||||
style="margin-left: 0.5rem"
|
||||
v-model="name"
|
||||
readonly
|
||||
|
@ -31,6 +32,7 @@
|
|||
<ion-item>
|
||||
<ion-label>ChainId: </ion-label>
|
||||
<ion-input
|
||||
aria-label="ChainId"
|
||||
style="margin-left: 0.5rem"
|
||||
v-model="chainId"
|
||||
readonly
|
||||
|
@ -40,6 +42,7 @@
|
|||
<ion-item button>
|
||||
<ion-label>RPC URL: </ion-label>
|
||||
<ion-input
|
||||
aria-label="RPC URL"
|
||||
style="margin-left: 0.5rem"
|
||||
readonly
|
||||
placeholder="https://polygon-mainnet.g.alchemy.com/..."
|
||||
|
@ -49,6 +52,7 @@
|
|||
<ion-item button>
|
||||
<ion-label>Native Token Symbol: </ion-label>
|
||||
<ion-input
|
||||
aria-label="Native Token Symbol"
|
||||
style="margin-left: 0.5rem"
|
||||
readonly
|
||||
placeholder="MATIC"
|
||||
|
@ -58,6 +62,7 @@
|
|||
<ion-item button>
|
||||
<ion-label>Explorer: </ion-label>
|
||||
<ion-input
|
||||
aria-label="Explorer"
|
||||
style="margin-left: 0.5rem"
|
||||
readonly
|
||||
placeholder="https://polygonscan.com"
|
||||
|
@ -107,7 +112,6 @@ import {
|
|||
import { useRoute } from "vue-router";
|
||||
import { getUrl, saveSelectedNetwork, saveNetwork, hexTostr } from "@/utils/platform";
|
||||
import type { Network } from "@/extension/types";
|
||||
import { mainNets, testNets } from "@/utils/networks";
|
||||
import { approve, walletPing } from "@/extension/userRequest";
|
||||
import { triggerListner } from "@/extension/listners";
|
||||
|
||||
|
@ -132,7 +136,6 @@ export default defineComponent({
|
|||
const rid = (route?.params?.rid as string) ?? "";
|
||||
const networkData = hexTostr((route.params?.param as string) ?? "");
|
||||
const alertOpen = ref(false);
|
||||
const templateNetworks = Object.assign({}, mainNets, testNets) ?? {};
|
||||
const timerReject = ref(140);
|
||||
let interval: any;
|
||||
const website = ref("");
|
||||
|
@ -203,7 +206,6 @@ export default defineComponent({
|
|||
onCancel,
|
||||
alertOpen,
|
||||
loading,
|
||||
templateNetworks,
|
||||
getUrl,
|
||||
onAddSwitch,
|
||||
timerReject,
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="close">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Select Action</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-searchbar placeholder="Search" @ionInput="onSearch"></ion-searchbar>
|
||||
</ion-item>
|
||||
|
||||
<ion-radio-group :value="selectedAbi">
|
||||
<ion-list-header>
|
||||
<ion-label>Actions</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-list class="ion-padding" v-for="key in Object.keys(actions)" :key="key">
|
||||
<ion-item>
|
||||
<ion-radio
|
||||
@click="changeSelected(key)"
|
||||
slot="start"
|
||||
:value="key"
|
||||
:aria-label="key"
|
||||
>
|
||||
{{ key }} on ABI {{ actions[key].abi }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="onDelete(key)">Delete</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-radio-group>
|
||||
|
||||
<ion-list v-if="!!!Object.keys(actions ?? {}).length">
|
||||
<ion-item class="ion-padding">
|
||||
<ion-label>No Actions found, please save at least one</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="4000"
|
||||
:key="`k${loading}`"
|
||||
@didDismiss="loading = false"
|
||||
>
|
||||
</ion-loading>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonList,
|
||||
IonItem,
|
||||
modalController,
|
||||
IonRadio,
|
||||
IonListHeader,
|
||||
IonRadioGroup,
|
||||
IonLabel,
|
||||
IonLoading,
|
||||
IonSearchbar,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
} from "@ionic/vue";
|
||||
import { ref, onMounted, Ref } from "vue";
|
||||
import {
|
||||
readCAGetAll,
|
||||
readCARemove,
|
||||
writeCAGetAll,
|
||||
writeCARemove,
|
||||
} from "@/utils/platform";
|
||||
import { ContractActions } from "@/extension/types";
|
||||
// import {
|
||||
// getAllAbis,
|
||||
// setAbis,
|
||||
// // removeAllAbis
|
||||
// } from "@/utils/platform";
|
||||
|
||||
const props = defineProps(["type"]);
|
||||
|
||||
const type = props?.type ?? "read";
|
||||
|
||||
const actions = ref({}) as Ref<ContractActions>;
|
||||
const intialActions = ref({}) as Ref<ContractActions>;
|
||||
|
||||
const loading = ref(false);
|
||||
const selectedAbi = ref("");
|
||||
|
||||
const onSearch = (e: any) => {
|
||||
const text = e.target.value;
|
||||
if (text) {
|
||||
const keys = Object.keys(intialActions.value).filter((key) =>
|
||||
key.toLowerCase().includes(text.toLowerCase())
|
||||
);
|
||||
actions.value = keys.reduce((acc, key) => {
|
||||
acc[key] = intialActions.value[key];
|
||||
return acc;
|
||||
}, {} as ContractActions);
|
||||
} else {
|
||||
actions.value = { ...intialActions.value };
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
actions.value = type === "read" ? await readCAGetAll() : await writeCAGetAll();
|
||||
intialActions.value = { ...actions.value };
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
const onDelete = async (key: string) => {
|
||||
delete actions.value[key];
|
||||
type === "read" ? await readCARemove(key) : await writeCARemove(key);
|
||||
intialActions.value = { ...actions.value };
|
||||
};
|
||||
|
||||
const changeSelected = (item: string) => {
|
||||
modalController.dismiss(actions.value?.[item], "confirm");
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
try {
|
||||
modalController.dismiss(null, "cancel");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,308 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Send Native Token</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-label>Current Network</ion-label>
|
||||
</ion-item>
|
||||
<template v-if="selectedNetwork?.name">
|
||||
<ion-item>
|
||||
Name: <b>{{ selectedNetwork.name }}</b>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
ID: <b>{{ selectedNetwork.chainId }}</b>
|
||||
</ion-item>
|
||||
</template>
|
||||
<hr />
|
||||
<ion-item>
|
||||
<ion-label>Current Address</ion-label>
|
||||
</ion-item>
|
||||
<ion-item v-if="selectedAccount?.address">
|
||||
<b style="font-size: 0.8rem">{{ selectedAccount?.address }}</b>
|
||||
</ion-item>
|
||||
<hr />
|
||||
<ion-item>
|
||||
<ion-label>Current Balance</ion-label>
|
||||
</ion-item>
|
||||
<ion-item v-if="currentBalance">
|
||||
<b>{{ currentBalance.toFixed(8) }}</b>
|
||||
</ion-item>
|
||||
<hr />
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Send To Address:</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-input
|
||||
aria-label="address"
|
||||
style="font-size: 0.8rem"
|
||||
id="pasteAddress"
|
||||
v-model="sendTo"
|
||||
></ion-input>
|
||||
<ion-icon
|
||||
style="margin-right: 0.5rem"
|
||||
@click="paste('pasteAddress')"
|
||||
:icon="clipboardOutline"
|
||||
button
|
||||
/>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button>
|
||||
<ion-button @click="openModalAddContact()">
|
||||
Load address from contacts
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Amount (e.g. 1.2):</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-input
|
||||
aria-label="Amount (e.g. 1.2)"
|
||||
type="number"
|
||||
id="amount"
|
||||
v-model="amount"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-button @click="promptTransaction">Prompt Transaction</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-alert
|
||||
:is-open="alertOpen"
|
||||
:header="alertTitle"
|
||||
:message="alertMsg"
|
||||
:buttons="['OK']"
|
||||
@didDismiss="alertOpen = false"
|
||||
></ion-alert>
|
||||
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="loadingSend ? 0 : 4000"
|
||||
:key="`k${loading}`"
|
||||
@didDismiss="loading = false"
|
||||
>
|
||||
</ion-loading>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, Ref } from "vue";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonAlert,
|
||||
IonIcon,
|
||||
onIonViewWillEnter,
|
||||
IonLoading,
|
||||
modalController,
|
||||
// IonModal,
|
||||
// IonButtons,
|
||||
// IonTextarea,
|
||||
} from "@ionic/vue";
|
||||
// import { ethers } from "ethers";
|
||||
import {
|
||||
// saveSelectedAccount,
|
||||
// getAccounts,
|
||||
// saveAccount,
|
||||
// getRandomPk,
|
||||
// smallRandomString,
|
||||
paste,
|
||||
getSelectedNetwork,
|
||||
getSelectedAccount,
|
||||
// getSettings,
|
||||
} from "@/utils/platform";
|
||||
// import router from "@/router";
|
||||
// import UnlockModal from "@/views/UnlockModal.vue";
|
||||
// import { encrypt, getCryptoParams } from "@/utils/webCrypto";
|
||||
|
||||
import { clipboardOutline } from "ionicons/icons";
|
||||
import type { Network, Account } from "@/extension/types";
|
||||
import { walletPromptSendTx } from "@/extension/userRequest";
|
||||
import { isAddress, formatEther, parseEther } from "ethers";
|
||||
import { getTxCount, getBalance } from "@/utils/wallet";
|
||||
import SelectedContacts from "./ContactsSelect.vue";
|
||||
|
||||
// import { getFromMnemonic } from "@/utils/wallet";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonAlert,
|
||||
IonIcon,
|
||||
IonLoading,
|
||||
// IonModal,
|
||||
// IonButtons,
|
||||
// IonTextarea,
|
||||
},
|
||||
setup: () => {
|
||||
// const supportedNetworksIds = [1, 3, 4, 5, 42, 56, 97, 137, 80001];
|
||||
|
||||
const name = ref("");
|
||||
const sendTo = ref("");
|
||||
const alertOpen = ref(false);
|
||||
const alertMsg = ref("");
|
||||
const alertTitle = ref("Error");
|
||||
const loading = ref(true);
|
||||
const amount = ref(0);
|
||||
const selectedNetwork = (ref(null) as unknown) as Ref<Network>;
|
||||
const selectedAccount = (ref(null) as unknown) as Ref<Account>;
|
||||
const currentBalance = ref(0);
|
||||
const loadingSend = ref(false);
|
||||
|
||||
// let accountsProm: Promise<Account[] | undefined>;
|
||||
// let settingsProm: Promise<Settings | undefined>;
|
||||
|
||||
// const resetFields = () => {
|
||||
// name.value = "";
|
||||
// pk.value = "";
|
||||
// };
|
||||
|
||||
// const openModal = async () => {
|
||||
// const modal = await modalController.create({
|
||||
// component: UnlockModal,
|
||||
// componentProps: {
|
||||
// unlockType: "addAccount",
|
||||
// },
|
||||
// });
|
||||
// modal.present();
|
||||
// const { role, data } = await modal.onWillDismiss();
|
||||
// if (role === "confirm") return data;
|
||||
// return false;
|
||||
// };
|
||||
|
||||
onIonViewWillEnter(async () => {
|
||||
try {
|
||||
selectedNetwork.value = await getSelectedNetwork();
|
||||
selectedAccount.value = await getSelectedAccount();
|
||||
currentBalance.value = Number(formatEther((await getBalance()).toString()));
|
||||
} catch (e) {
|
||||
alertOpen.value = true;
|
||||
alertMsg.value =
|
||||
"Error getting network & balance Internet or RPC or blockchain may be down";
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
const promptTransaction = async () => {
|
||||
alertTitle.value = "Error";
|
||||
if (
|
||||
sendTo.value?.toLocaleLowerCase() ===
|
||||
selectedAccount.value.address?.toLocaleLowerCase()
|
||||
) {
|
||||
alertOpen.value = true;
|
||||
alertMsg.value = "Cannot send to self";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAddress(sendTo.value)) {
|
||||
alertOpen.value = true;
|
||||
alertMsg.value = "Invalid send address";
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount.value <= 0) {
|
||||
alertOpen.value = true;
|
||||
alertMsg.value = "Amount must be greater than 0";
|
||||
return;
|
||||
}
|
||||
|
||||
const value = parseEther(amount.value.toString()).toString();
|
||||
|
||||
if (Number(value) >= Number(parseEther(currentBalance.value.toString()))) {
|
||||
alertOpen.value = true;
|
||||
alertMsg.value = "Insufficient balance";
|
||||
return;
|
||||
}
|
||||
|
||||
const nonce = (await getTxCount(selectedAccount.value.address)) + 1;
|
||||
|
||||
loading.value = true;
|
||||
loadingSend.value = true;
|
||||
|
||||
const tx = {
|
||||
from: selectedAccount.value.address,
|
||||
to: sendTo.value,
|
||||
value,
|
||||
nonce,
|
||||
gasLimit: "0x0",
|
||||
gasPrice: "0x0",
|
||||
};
|
||||
const result = (await walletPromptSendTx(tx)) as {
|
||||
error?: string;
|
||||
};
|
||||
if (result?.error) {
|
||||
alertOpen.value = true;
|
||||
alertMsg.value = "Error sending transaction to chain";
|
||||
loading.value = false;
|
||||
return;
|
||||
} else {
|
||||
alertTitle.value = "OK";
|
||||
alertOpen.value = true;
|
||||
alertMsg.value = "Transaction sent successfully";
|
||||
}
|
||||
|
||||
loadingSend.value = false;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const openModalAddContact = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: SelectedContacts,
|
||||
componentProps: {},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
if (role === "confirm") {
|
||||
sendTo.value = data.address;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name,
|
||||
sendTo,
|
||||
alertOpen,
|
||||
alertMsg,
|
||||
alertTitle,
|
||||
clipboardOutline,
|
||||
loadingSend,
|
||||
paste,
|
||||
loading,
|
||||
amount,
|
||||
promptTransaction,
|
||||
currentBalance,
|
||||
selectedAccount,
|
||||
selectedNetwork,
|
||||
openModalAddContact,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -20,6 +20,7 @@
|
|||
<ion-item>
|
||||
<ion-label>Enable Storage Encryption</ion-label>
|
||||
<ion-toggle
|
||||
aria-label="Enable Storage Encryption"
|
||||
:key="updateKey"
|
||||
@ion-change="changeEncryption"
|
||||
slot="end"
|
||||
|
@ -33,6 +34,7 @@
|
|||
<ion-item :disabled="!settings.s.enableStorageEnctyption">
|
||||
<ion-label>Enable Auto Lock</ion-label>
|
||||
<ion-toggle
|
||||
aria-label="Enable Auto Lock"
|
||||
:key="updateKey"
|
||||
@ion-change="changeAutoLock"
|
||||
slot="end"
|
||||
|
@ -70,6 +72,7 @@
|
|||
<ion-item>
|
||||
<ion-label>Permanent Lock</ion-label>
|
||||
<ion-toggle
|
||||
aria-label="Permanent Lock"
|
||||
@ion-change="changePermaLock"
|
||||
:key="updateKey"
|
||||
slot="end"
|
||||
|
@ -86,7 +89,7 @@
|
|||
</ion-accordion>
|
||||
<ion-accordion value="2">
|
||||
<ion-item slot="header" color="light">
|
||||
<ion-label>Theme</ion-label>
|
||||
<ion-label>Theme & Misc</ion-label>
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">
|
||||
<ion-list>
|
||||
|
@ -104,6 +107,18 @@
|
|||
<ion-label>Light</ion-label>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
<ion-item>
|
||||
<ion-label style="font-size: 0.7rem"
|
||||
>Convert Address to lowercase on copy</ion-label
|
||||
>
|
||||
<ion-toggle
|
||||
aria-label="Convert Address to Lowercase on Copy"
|
||||
@ion-change="changeCopyLowerCaseAddress"
|
||||
:key="updateKey"
|
||||
slot="end"
|
||||
:checked="settings.s.copyLowerCaseAddress"
|
||||
></ion-toggle>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
</ion-accordion>
|
||||
|
@ -117,13 +132,12 @@
|
|||
and Ethers.
|
||||
</p>
|
||||
<p>
|
||||
It emulates Metamask Wallet and can be used as a drop-in replacement, right
|
||||
now if you have both extensions, CLW will overwrite Metamask.
|
||||
</p>
|
||||
<p>
|
||||
Main philosophy of the wallet is: no trackers, full control, export/import
|
||||
JSONs with accounts, fast generate new accounts, and wipe everything with
|
||||
one click.
|
||||
Unlike most wallets, this wallet has no ads, no analytics, no trackers, no
|
||||
bloatware, no telemetry, no data collection, no sponsored content, no
|
||||
sponsored Dapps, no sponsored tokens, no sponsored NFTs, no sponsored
|
||||
anything. It is a clean wallet with no revenue model, made by a single
|
||||
developer, if you want to support this project financially you can donate at
|
||||
andrei0x309.eth.
|
||||
</p>
|
||||
<p>
|
||||
Github Repo:
|
||||
|
@ -131,6 +145,10 @@
|
|||
>LINK</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
Docs Website:
|
||||
<a href="#" @click="openTab('https://clear-wallet.flashsoft.eu')">LINK</a>
|
||||
</p>
|
||||
<br />
|
||||
<p style="margin-bottom: 0.2rem">Places you can check me out:</p>
|
||||
<p>
|
||||
|
@ -145,10 +163,6 @@
|
|||
Blog Flashsoft
|
||||
<a href="#" @click="openTab('https://blog.flashsoft.eu')">LINK</a>
|
||||
</p>
|
||||
<p>
|
||||
Crypto-Leftists Discord
|
||||
<a href="#" @click="openTab('https://discord.gg/gzA4bTCdhb')">LINK</a>
|
||||
</p>
|
||||
</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="4">
|
||||
|
@ -180,6 +194,7 @@
|
|||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
<ion-toast
|
||||
position="top"
|
||||
:is-open="toastState"
|
||||
@didDismiss="toastState = false"
|
||||
:message="toastMsg"
|
||||
|
@ -228,7 +243,11 @@
|
|||
<ion-label>Old Password</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input v-model="mpPass" type="password"></ion-input>
|
||||
<ion-input
|
||||
aria-label="password"
|
||||
v-model="mpPass"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<div v-else>
|
||||
|
@ -237,7 +256,11 @@
|
|||
<ion-label>New Password</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input v-model="mpPass" type="password"></ion-input>
|
||||
<ion-input
|
||||
aria-label="password"
|
||||
v-model="mpPass"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
|
@ -245,7 +268,11 @@
|
|||
<ion-label>Confirm</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input v-model="mpConfirm" type="password"></ion-input>
|
||||
<ion-input
|
||||
aria-label="password"
|
||||
v-model="mpConfirm"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
@ -393,6 +420,12 @@ export default defineComponent({
|
|||
defaultAccordionOpen.value = "1";
|
||||
};
|
||||
|
||||
const changeCopyLowerCaseAddress = async () => {
|
||||
settings.s.copyLowerCaseAddress = !settings.s?.copyLowerCaseAddress;
|
||||
await saveSettings();
|
||||
defaultAccordionOpen.value = "2";
|
||||
};
|
||||
|
||||
const changeTheme = async (theme: "system" | "light" | "dark") => {
|
||||
document.body.classList.remove(radioTheme.value);
|
||||
document.body.classList.add(theme);
|
||||
|
@ -682,6 +715,7 @@ export default defineComponent({
|
|||
openTab,
|
||||
radioTheme,
|
||||
changePermaLock,
|
||||
changeCopyLowerCaseAddress,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
>
|
||||
<ion-item>
|
||||
<ion-avatar
|
||||
v-if="(mainNets as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
v-if="(allTemplateNets as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="selectedNetwork?.name"
|
||||
:src="getUrl('assets/chain-icons/' + (mainNets as any)[selectedNetwork?.chainId]?.icon)"
|
||||
:src="getUrl('assets/chain-icons/' + (allTemplateNets as any)[selectedNetwork?.chainId]?.icon)"
|
||||
/>
|
||||
</ion-avatar>
|
||||
<ion-label>Network ID: {{ selectedNetwork?.chainId }}</ion-label>
|
||||
|
@ -66,6 +66,7 @@
|
|||
<ion-item>
|
||||
<ion-label>Raw TX:</ion-label>
|
||||
<ion-textarea
|
||||
aria-label="raw tx"
|
||||
style="overflow-y: scroll"
|
||||
:rows="10"
|
||||
:cols="20"
|
||||
|
@ -119,7 +120,11 @@
|
|||
<ion-label>Limit in units</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input v-model="inGasLimit" type="number"></ion-input>
|
||||
<ion-input
|
||||
aria-label="gas limit"
|
||||
v-model="inGasLimit"
|
||||
type="number"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="setGasLimit">Set Price</ion-button>
|
||||
|
@ -143,7 +148,11 @@
|
|||
<ion-label>Price in gwei</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input v-model="inGasPrice" type="number"></ion-input>
|
||||
<ion-input
|
||||
aria-label="price in gwei"
|
||||
v-model="inGasPrice"
|
||||
type="number"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="setGasPrice">Set Price</ion-button>
|
||||
|
@ -192,7 +201,7 @@ import {
|
|||
} from "@/utils/platform";
|
||||
import { getBalance, getGasPrice, estimateGas } from "@/utils/wallet";
|
||||
import type { Network } from "@/extension/types";
|
||||
import { mainNets, chainIdToPriceId } from "@/utils/networks";
|
||||
import { allTemplateNets, chainIdToPriceId } from "@/utils/networks";
|
||||
import UnlockModal from "@/views/UnlockModal.vue";
|
||||
import router from "@/router";
|
||||
|
||||
|
@ -252,7 +261,12 @@ export default defineComponent({
|
|||
if (!decodedParam) {
|
||||
isError = true;
|
||||
} else {
|
||||
signTxData.value = JSON.stringify(params, null, 2);
|
||||
const paramsWithoutZeros = Object.fromEntries(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
Object.entries(params).filter(([_, v]) => v !== "0x0")
|
||||
);
|
||||
|
||||
signTxData.value = JSON.stringify(paramsWithoutZeros, null, 2);
|
||||
}
|
||||
|
||||
const openModal = async () => {
|
||||
|
@ -300,16 +314,22 @@ export default defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
const newGasData = () => {
|
||||
const newGasData = async () => {
|
||||
await walletSendData(rid, {
|
||||
gas: numToHexStr(gasLimit.value),
|
||||
});
|
||||
|
||||
await walletSendData(rid, {
|
||||
gasPrice: numToHexStr(BigInt(Math.trunc(gasPrice.value * 1e9))),
|
||||
});
|
||||
gasFee.value = Number(
|
||||
ethers.utils.formatUnits(String(gasLimit.value * gasPrice.value), "gwei")
|
||||
ethers.formatUnits(Math.trunc(gasLimit.value * gasPrice.value), "gwei")
|
||||
);
|
||||
txValue.value = Number(ethers.utils.formatEther(params?.value ?? "0x0"));
|
||||
txValue.value = Number(ethers.formatEther(params?.value ?? "0x0"));
|
||||
totalCost.value = gasFee.value + txValue.value;
|
||||
};
|
||||
|
||||
onIonViewWillEnter(async () => {
|
||||
console.log(params.value);
|
||||
(window as any)?.resizeTo?.(600, 800);
|
||||
const pEstimateGas = estimateGas({
|
||||
to: params?.to ?? "",
|
||||
|
@ -323,13 +343,11 @@ export default defineComponent({
|
|||
const pGetPrices = getPrices();
|
||||
selectedNetwork.value = await getSelectedNetwork();
|
||||
userBalance.value = Number(
|
||||
ethers.utils.formatEther((await pBalance).toString() ?? "0x0")
|
||||
ethers.formatEther((await pBalance).toString() ?? "0x0")
|
||||
);
|
||||
|
||||
gasPrice.value = parseInt(
|
||||
ethers.utils.formatUnits((await pGasPrice).toString() ?? "0x0", "gwei"),
|
||||
10
|
||||
);
|
||||
gasPrice.value = parseFloat((await pGasPrice).toString() ?? 0.1);
|
||||
|
||||
try {
|
||||
gasLimit.value = parseInt((await pEstimateGas).toString(), 10);
|
||||
} catch (err) {
|
||||
|
@ -341,16 +359,13 @@ export default defineComponent({
|
|||
inGasPrice.value = gasPrice.value;
|
||||
inGasLimit.value = gasLimit.value;
|
||||
|
||||
// console.log( 'test', ethers.utils.formatUnits((await pGasPrice).toString(), "gwei"), ethers.utils.formatUnits(ethers.utils.parseUnits(gasPrice.value.toString(), "gwei"), "gwei") )
|
||||
|
||||
newGasData();
|
||||
if (userBalance.value < totalCost.value) {
|
||||
insuficientBalance.value = true;
|
||||
}
|
||||
const prices = await pGetPrices;
|
||||
dollarPrice.value =
|
||||
prices[chainIdToPriceId(selectedNetwork.value?.chainId ?? 0)]?.usd ?? 0;
|
||||
|
||||
await newGasData();
|
||||
loading.value = false;
|
||||
|
||||
interval = setInterval(async () => {
|
||||
|
@ -363,11 +378,8 @@ export default defineComponent({
|
|||
if (timerFee.value <= 0) {
|
||||
timerFee.value = 20;
|
||||
loading.value = true;
|
||||
gasPrice.value = parseInt(
|
||||
ethers.utils.formatUnits((await getGasPrice()).toString(), "gwei"),
|
||||
10
|
||||
);
|
||||
newGasData();
|
||||
gasPrice.value = parseFloat((await getGasPrice()).toString() ?? 0.1);
|
||||
await newGasData();
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
@ -380,9 +392,6 @@ export default defineComponent({
|
|||
|
||||
const setGasLimit = () => {
|
||||
gasLimit.value = inGasLimit.value;
|
||||
walletSendData(rid, {
|
||||
gas: numToHexStr(gasLimit.value),
|
||||
});
|
||||
newGasData();
|
||||
gasLimitModal.value = false;
|
||||
};
|
||||
|
@ -390,9 +399,6 @@ export default defineComponent({
|
|||
const setGasPrice = () => {
|
||||
gasPrice.value = inGasPrice.value;
|
||||
gasPriceReFetch.value = false;
|
||||
walletSendData(rid, {
|
||||
gasPrice: ethers.utils.parseUnits(gasPrice.value.toString(), "gwei"),
|
||||
});
|
||||
newGasData();
|
||||
gasPriceModal.value = false;
|
||||
};
|
||||
|
@ -418,7 +424,7 @@ export default defineComponent({
|
|||
bars,
|
||||
loading,
|
||||
selectedNetwork,
|
||||
mainNets,
|
||||
allTemplateNets,
|
||||
getUrl,
|
||||
setGasLimit,
|
||||
setGasPrice,
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
<ion-item>Network Name: {{ selectedNetwork?.name }}</ion-item>
|
||||
<ion-item>
|
||||
<ion-avatar
|
||||
v-if="(templateNetworks as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
v-if="(allTemplateNets as any)[selectedNetwork?.chainId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="selectedNetwork?.name"
|
||||
:src="getUrl('assets/chain-icons/' + (templateNetworks as any)[selectedNetwork?.chainId]?.icon)"
|
||||
:src="getUrl('assets/chain-icons/' + (allTemplateNets as any)[selectedNetwork?.chainId]?.icon)"
|
||||
/>
|
||||
</ion-avatar>
|
||||
<ion-label>Network ID: {{ selectedNetwork?.chainId }}</ion-label>
|
||||
|
@ -42,7 +42,7 @@
|
|||
<ion-item>
|
||||
<ion-avatar
|
||||
v-if="(existingNetworks as any)[networkId]?.icon"
|
||||
style="margin-right: 1rem; width: 1.8rem; height: 1.8rem"
|
||||
style="margin-right: 1rem; width: 1.6rem; height: 1.6rem"
|
||||
>
|
||||
<img
|
||||
:alt="(existingNetworks as any)[networkId]?.name"
|
||||
|
@ -132,7 +132,7 @@ import {
|
|||
numToHexStr,
|
||||
} from "@/utils/platform";
|
||||
import type { Network, Networks } from "@/extension/types";
|
||||
import { mainNets, testNets } from "@/utils/networks";
|
||||
import { allTemplateNets } from "@/utils/networks";
|
||||
import { approve, walletPing } from "@/extension/userRequest";
|
||||
import { triggerListner } from "@/extension/listners";
|
||||
|
||||
|
@ -162,7 +162,6 @@ export default defineComponent({
|
|||
const alertMsg = ref("");
|
||||
const networkCase = ref("");
|
||||
let pnetworks: Promise<Networks>;
|
||||
const templateNetworks = Object.assign({}, mainNets, testNets) ?? {};
|
||||
const addChainUrl = `${chainListPage}${networkId.value}`;
|
||||
const timerReject = ref(140);
|
||||
let interval: any;
|
||||
|
@ -183,12 +182,11 @@ export default defineComponent({
|
|||
(window as any)?.resizeTo?.(600, 600);
|
||||
pnetworks = getNetworks();
|
||||
selectedNetwork.value = await getSelectedNetwork();
|
||||
console.log(networkId.value);
|
||||
existingNetworks.value = await pnetworks;
|
||||
if ((networkId.value ?? "0") in existingNetworks.value ?? {}) {
|
||||
if ((networkId.value ?? "0") in (existingNetworks?.value ?? {})) {
|
||||
networkCase.value = "exists";
|
||||
} else if ((networkId.value ?? "0") in templateNetworks) {
|
||||
existingNetworks.value = templateNetworks;
|
||||
} else if ((networkId.value ?? "0") in allTemplateNets) {
|
||||
existingNetworks.value = allTemplateNets;
|
||||
networkCase.value = "inTemplates";
|
||||
} else {
|
||||
networkCase.value = "doesNotExist";
|
||||
|
@ -218,9 +216,10 @@ export default defineComponent({
|
|||
|
||||
const onSwitchTemplates = async () => {
|
||||
loading.value = true;
|
||||
selectedNetwork.value = templateNetworks[Number(networkId.value)];
|
||||
await saveNetwork(templateNetworks[Number(networkId.value)]);
|
||||
await saveSelectedNetwork(templateNetworks[Number(networkId.value)]);
|
||||
const nId = Number(networkId.value) as keyof typeof allTemplateNets;
|
||||
selectedNetwork.value = allTemplateNets[nId];
|
||||
await saveNetwork(allTemplateNets[nId]);
|
||||
await saveSelectedNetwork(allTemplateNets[nId]);
|
||||
triggerListner("chainChanged", numToHexStr(selectedNetwork.value?.chainId ?? 0));
|
||||
approve(rid);
|
||||
loading.value = false;
|
||||
|
@ -241,7 +240,7 @@ export default defineComponent({
|
|||
loading,
|
||||
networkCase,
|
||||
selectedNetwork,
|
||||
templateNetworks,
|
||||
allTemplateNets,
|
||||
getUrl,
|
||||
onSwitchTemplates,
|
||||
onSwitchNotExisting,
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="close">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Unlock to Proceed</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button @click="close" color="primary">Close</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
|
@ -30,9 +30,29 @@
|
|||
<ion-item>
|
||||
<ion-label>Unlock Password</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input v-model="mpPass" type="password"></ion-input>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
aria-label="password"
|
||||
placeholder=""
|
||||
class="password-input"
|
||||
type="password"
|
||||
@ion-input="mpPass = String($event.target.value)"
|
||||
fill="solid"
|
||||
ref="ionInput"
|
||||
></ion-input>
|
||||
|
||||
<!-- <ion-input
|
||||
label="Password"
|
||||
label-placement="floating"
|
||||
fill="outline"
|
||||
placeholder=""
|
||||
type="password"
|
||||
ref="ionInput"
|
||||
@ion-input="mpPass = String($event.target.value)"
|
||||
></ion-input> -->
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-list>
|
||||
<ion-item>
|
||||
<ion-button @click="unlock">Confirm</ion-button>
|
||||
|
@ -57,7 +77,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { defineComponent, ref, onMounted } from "vue";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
|
@ -133,6 +153,14 @@ export default defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
const el = document?.querySelector(
|
||||
".password-input .native-input"
|
||||
) as HTMLInputElement;
|
||||
el?.focus?.();
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
unlock,
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
<ion-label>Operation Aborted</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Error:</ion-label>
|
||||
<ion-textarea
|
||||
label="Error:"
|
||||
labelPlacement="stacked"
|
||||
style="overflow-y: scroll"
|
||||
:rows="10"
|
||||
:cols="20"
|
||||
|
|
|
@ -0,0 +1,494 @@
|
|||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Contract Write Action</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-button @click="selectSavedAction" expand="block"
|
||||
>Load saved wite action</ion-button
|
||||
>
|
||||
<ion-item>
|
||||
<template v-if="selectedAbi">
|
||||
<p>Selected Abi: {{ selectedAbi }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>No Abi selected</p>
|
||||
</template>
|
||||
|
||||
<ion-button @click="openAbiListModal()" expand="block">Load Abi</ion-button>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-icon :icon="clipboardOutline" @click="paste('pasteContract')" />
|
||||
<ion-input
|
||||
label="Contract Address(*)"
|
||||
label-placement="stacked"
|
||||
v-model="contractAddress"
|
||||
id="pasteContract"
|
||||
placeholder="0x..."
|
||||
type="text"
|
||||
style="font-size: 0.8rem"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item button>
|
||||
<ion-button @click="openModalAddContact()">
|
||||
Load address from contacts
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<ion-item button>
|
||||
<template v-if="!functions.length">
|
||||
<p>Select Abi with functions to enable function selection</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="functionName">
|
||||
<p>Selected Function: {{ functionName }}</p>
|
||||
<ion-button @click="selectFunction()" expand="block">Change</ion-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>No Function selected</p>
|
||||
<ion-button @click="selectFunction()" expand="block">Select</ion-button>
|
||||
</template>
|
||||
</template>
|
||||
</ion-item>
|
||||
<template v-if="functionName">
|
||||
<ion-item>
|
||||
<ion-label>PARAMS NOTES:</ion-label>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-item
|
||||
>Will be evaluated in sandbox using js eval in order to pass complex types
|
||||
like [1,2 [1,0x...]]</ion-item
|
||||
>
|
||||
<ion-item
|
||||
>Strings must be passed using qoutes example '0x3...1A2', or ['param1',
|
||||
'param2'] for multiple params.</ion-item
|
||||
>
|
||||
<ion-item
|
||||
>Params are sent exactly as they are, numbers are not parsed to UINT256
|
||||
format.
|
||||
</ion-item>
|
||||
<ion-item>SET PARAMS: </ion-item>
|
||||
<ion-list v-for="(param, index) in params" :key="index" class="param-list">
|
||||
<ion-item>
|
||||
<ion-label style="font-size: 0.85rem"
|
||||
>P:{{ Number(index) + 1 }} name: {{ param.name }} type: ({{
|
||||
param.type
|
||||
}})</ion-label
|
||||
>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
aria-label="value"
|
||||
v-model="param.value"
|
||||
placeholder="ex: 1 or 0x22 or 'hello' or [1, 2] etc "
|
||||
type="text"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-item v-if="!params?.length">
|
||||
<ion-label>Function has no params</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</template>
|
||||
<ion-item>
|
||||
<ion-button @click="saveActionInStorage">Save Action</ion-button>
|
||||
<ion-button @click="executeAction">Execute Action</ion-button>
|
||||
</ion-item>
|
||||
<ion-alert
|
||||
:is-open="alertOpen"
|
||||
:header="alertHeader"
|
||||
:message="alertMsg"
|
||||
:buttons="['OK']"
|
||||
@didDismiss="alertOpen = false"
|
||||
></ion-alert>
|
||||
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="loadingSend ? 0 : 4000"
|
||||
:key="`k${loading}`"
|
||||
@didDismiss="loading = false"
|
||||
/>
|
||||
|
||||
<iframe
|
||||
@load="sandboxLoaded = true"
|
||||
ref="evalFrame"
|
||||
src="eval-sandbox.html"
|
||||
style="display: none"
|
||||
></iframe>
|
||||
</ion-content>
|
||||
|
||||
<ion-modal :is-open="saveActionModal" @will-dismiss="saveActionModal = false">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="saveActionModal = false">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Select</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-list style="margin-bottom: 4rem">
|
||||
<ion-item>
|
||||
<ion-input
|
||||
label="Name(*)"
|
||||
label-placement="stacked"
|
||||
v-model="name"
|
||||
placeholder="ex: Get lens hande from id"
|
||||
type="text"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="saveActionModal = false">Cancel</ion-button>
|
||||
<ion-button @click="saveAction">Save</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, defineComponent, ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
modalController,
|
||||
IonList,
|
||||
IonAlert,
|
||||
IonLoading,
|
||||
IonButtons,
|
||||
IonModal,
|
||||
} from "@ionic/vue";
|
||||
import { paste, writeCASet, getAbis } from "@/utils/platform";
|
||||
import { clipboardOutline } from "ionicons/icons";
|
||||
import type { ContractAction } from "@/extension/types";
|
||||
import { ethers } from "ethers";
|
||||
import { getSelectedAddress } from "@/utils/wallet";
|
||||
import AbiList from "./AbiList.vue";
|
||||
import AbiSelectFunction from "./AbiSelectFunction.vue";
|
||||
import SavedReadWriteActionList from "./SavedReadWriteActionList.vue";
|
||||
import { walletPromptSendTx } from "@/extension/userRequest";
|
||||
import SelectedContacts from "./ContactsSelect.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonList,
|
||||
IonAlert,
|
||||
IonLoading,
|
||||
IonButtons,
|
||||
IonModal,
|
||||
},
|
||||
setup: () => {
|
||||
const savedModalState = ref(false);
|
||||
const saveActionModal = ref(false);
|
||||
const alertOpen = ref(false);
|
||||
const alertMsg = ref("");
|
||||
const name = ref("");
|
||||
const loadingSend = ref(false);
|
||||
const contractAddress = ref("");
|
||||
const functionName = ref("");
|
||||
const params = ref([]) as Ref<{ value: string; type: string; name: string }[]>;
|
||||
const evalFrame = ref() as Ref<HTMLIFrameElement>;
|
||||
let messagePromiseResolve: (v: unknown) => void = () => {};
|
||||
const sandboxLoaded = ref(false);
|
||||
const abiContent = ref("");
|
||||
const selectedAbi = ref("");
|
||||
const alertHeader = ref("");
|
||||
let parsedAbi: any;
|
||||
const functions = ref([]) as Ref<string[]>;
|
||||
const loading = ref(false);
|
||||
|
||||
const openAbiListModal = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: AbiList,
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
|
||||
if (role === "confirm") {
|
||||
abiContent.value = data.content;
|
||||
selectedAbi.value = data.name;
|
||||
parsedAbi = JSON.parse(abiContent.value);
|
||||
functions.value = parsedAbi
|
||||
.filter((fn: any) => fn.type === "function")
|
||||
.map((fn: any) => fn.name);
|
||||
}
|
||||
};
|
||||
|
||||
const selectFunction = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: AbiSelectFunction,
|
||||
componentProps: {
|
||||
functions: functions.value,
|
||||
},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
|
||||
if (role === "confirm") {
|
||||
functionName.value = data;
|
||||
params.value = parsedAbi
|
||||
.find((fn: any) => fn.name === data)
|
||||
.inputs.map((input: any) => {
|
||||
return { value: "", type: input.type, name: input.name };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const selectSavedAction = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: SavedReadWriteActionList,
|
||||
componentProps: {
|
||||
type: "write",
|
||||
},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
const { data, role } = (await modal.onWillDismiss()) as {
|
||||
data: ContractAction;
|
||||
role: string;
|
||||
};
|
||||
|
||||
if (role === "confirm") {
|
||||
const content = await getAbis(data.abi);
|
||||
if (!content) {
|
||||
alertMsg.value =
|
||||
"Abi not found in storage, be sure Abi with name " + data.abi + " exists.";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
abiContent.value = content;
|
||||
functionName.value = data.functionName;
|
||||
params.value = Object.values(data.params);
|
||||
contractAddress.value = data.contract;
|
||||
selectedAbi.value = data.abi;
|
||||
parsedAbi = JSON.parse(abiContent.value);
|
||||
functions.value = parsedAbi
|
||||
.filter((fn: any) => fn.type === "function")
|
||||
.map((fn: any) => fn.name);
|
||||
}
|
||||
};
|
||||
|
||||
const saveActionInStorage = () => {
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
if (abiContent.value === "") {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
saveActionModal.value = true;
|
||||
};
|
||||
|
||||
const executeAction = async () => {
|
||||
if (sandboxLoaded.value === false) {
|
||||
alertMsg.value = "Sandbox for eval not loaded yet, please wait";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
if (!contractAddress.value) {
|
||||
alertMsg.value = "Contract Address is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
if (!functionName.value) {
|
||||
alertMsg.value = "Function Name is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
if (!parsedAbi) {
|
||||
alertMsg.value = "Abi is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
alertHeader.value = "Error";
|
||||
|
||||
// const provider = await getCurrentProvider();
|
||||
const encodeParamsTypes = [];
|
||||
const evalParams = await Promise.all(
|
||||
params.value.map(async (param) => await getEvalValue(param.value))
|
||||
);
|
||||
|
||||
try {
|
||||
if (functionName.value?.includes("(")) {
|
||||
const paramsTypes = functionName.value
|
||||
.split("(")[1]
|
||||
.split(")")[0]
|
||||
.split(",")
|
||||
.map((param) => param.trim());
|
||||
if (paramsTypes.length !== evalParams.length) {
|
||||
alertMsg.value = "Params count mismatch";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
encodeParamsTypes.push(...paramsTypes);
|
||||
}
|
||||
} catch {
|
||||
alertMsg.value =
|
||||
"Function Siganture wrong format (ex: 'functionName(uint256,string)')";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
|
||||
const fnName = functionName.value.includes("(")
|
||||
? functionName.value.split("(")[0]
|
||||
: functionName.value;
|
||||
|
||||
const iface = new ethers.Interface(parsedAbi);
|
||||
|
||||
try {
|
||||
loadingSend.value = true;
|
||||
loading.value = true;
|
||||
const data = iface.encodeFunctionData(fnName, evalParams);
|
||||
|
||||
const tx = {
|
||||
from: [await getSelectedAddress()][0],
|
||||
to: contractAddress.value,
|
||||
data,
|
||||
gasLimit: "0x0",
|
||||
gasPrice: "0x0",
|
||||
};
|
||||
|
||||
const result = (await walletPromptSendTx(tx)) as {
|
||||
error?: string;
|
||||
};
|
||||
if (result?.error) {
|
||||
console.error(result);
|
||||
alertOpen.value = true;
|
||||
alertMsg.value = "Error sending transaction to chain";
|
||||
loading.value = false;
|
||||
return;
|
||||
} else {
|
||||
alertHeader.value = "OK";
|
||||
alertMsg.value = "Transaction sent successfully";
|
||||
alertOpen.value = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alertMsg.value = "Function call failed, check params, contract address and ABI";
|
||||
loadingSend.value = false;
|
||||
loading.value = false;
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
loadingSend.value = false;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const saveAction = async () => {
|
||||
if (!name.value) {
|
||||
alertMsg.value = "Name is required";
|
||||
return (alertOpen.value = true);
|
||||
}
|
||||
const action = {
|
||||
name: name.value,
|
||||
contract: contractAddress.value,
|
||||
functionName: functionName.value,
|
||||
params: params.value,
|
||||
abi: selectedAbi.value,
|
||||
};
|
||||
|
||||
await writeCASet(action);
|
||||
saveActionModal.value = false;
|
||||
alertMsg.value = "Action saved successfully";
|
||||
alertHeader.value = "OK";
|
||||
return (alertOpen.value = true);
|
||||
};
|
||||
|
||||
const messageHandler = (event: any) => {
|
||||
messagePromiseResolve(event?.data?.result);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("message", messageHandler);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("message", messageHandler);
|
||||
});
|
||||
|
||||
const getEvalValue = (evalString: string) => {
|
||||
return new Promise((resolve) => {
|
||||
if (!evalFrame.value?.contentWindow?.postMessage) {
|
||||
return;
|
||||
}
|
||||
messagePromiseResolve = resolve;
|
||||
evalFrame.value?.contentWindow?.postMessage({ code: evalString }, "*");
|
||||
});
|
||||
};
|
||||
|
||||
const handleFnChange = (event: any) => {
|
||||
functionName.value = event.detail.value;
|
||||
};
|
||||
|
||||
const openModalAddContact = async () => {
|
||||
const modal = await modalController.create({
|
||||
component: SelectedContacts,
|
||||
componentProps: {},
|
||||
});
|
||||
|
||||
modal.present();
|
||||
|
||||
const { data, role } = await modal.onWillDismiss();
|
||||
if (role === "confirm") {
|
||||
contractAddress.value = data.address;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
saveActionModal,
|
||||
handleFnChange,
|
||||
clipboardOutline,
|
||||
evalFrame,
|
||||
alertOpen,
|
||||
alertMsg,
|
||||
alertHeader,
|
||||
functionName,
|
||||
paste,
|
||||
savedModalState,
|
||||
name,
|
||||
contractAddress,
|
||||
params,
|
||||
saveActionInStorage,
|
||||
executeAction,
|
||||
sandboxLoaded,
|
||||
openAbiListModal,
|
||||
selectedAbi,
|
||||
functions,
|
||||
selectFunction,
|
||||
saveAction,
|
||||
selectSavedAction,
|
||||
loading,
|
||||
loadingSend,
|
||||
openModalAddContact,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -9,7 +9,7 @@
|
|||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"sourceMap": false,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"chrome",
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { fileURLToPath, URL } from 'url'
|
||||
import nodePolyfills from 'rollup-plugin-polyfill-node'
|
||||
import { crx } from '@crxjs/vite-plugin'
|
||||
import manifest from './src/extension/manifest.json'
|
||||
|
||||
const production = process.env.NODE_ENV === 'production'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
publicDir: './public',
|
||||
|
@ -25,7 +22,16 @@ export default defineConfig({
|
|||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
plugins: [nodePolyfills()]
|
||||
// plugins: [nodePolyfills()],
|
||||
onwarn: (warning) => {
|
||||
if (warning.message.includes('comment will be removed')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
input: {
|
||||
['eval-sandbox']: 'eval-sandbox.html',
|
||||
},
|
||||
},
|
||||
sourcemap: false,
|
||||
chunkSizeWarningLimit: 1000,
|
||||
|
@ -33,12 +39,11 @@ export default defineConfig({
|
|||
transformMixedEsModules: true
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
legalComments: 'none',
|
||||
},
|
||||
plugins: [
|
||||
!production &&
|
||||
nodePolyfills({
|
||||
include: ['node_modules/**/*.js', new RegExp('node_modules/.vite/.*js')]
|
||||
}),
|
||||
crx({ manifest }),
|
||||
crx({ manifest: manifest as any }),
|
||||
vue()
|
||||
],
|
||||
server: {
|
||||
|
|