dev: 1.0.1

This commit is contained in:
Andrei O 2022-10-07 20:07:59 +03:00
parent 3e97999011
commit 9932a1a522
No known key found for this signature in database
GPG Key ID: B961E5B68389457E
49 changed files with 8715 additions and 32540 deletions

29
index.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" style="width:400px;height:450px">
<head>
<meta charset="utf-8" />
<title>Clear Wallet</title>
<base href="/" />
<meta name="color-scheme" content="light dark" />
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Clear Wallet" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

32166
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,46 +3,47 @@
"version": "0.0.1",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint"
"dev": "vite",
"build": "tsc --out src/extension/inject.js src/extension/inject.ts && vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@capacitor/app": "4.0.1",
"@capacitor/core": "4.1.0",
"@capacitor/haptics": "4.0.1",
"@capacitor/keyboard": "4.0.1",
"@capacitor/status-bar": "4.0.1",
"@ionic/vue": "^6.0.0",
"@ionic/vue-router": "^6.0.0",
"core-js": "^3.6.5",
"vue": "^3.2.21",
"vue-router": "^4.0.12"
"@capacitor/app": "^4.0.1",
"@capacitor/core": "^4.3.0",
"@capacitor/haptics": "^4.0.1",
"@capacitor/keyboard": "^4.0.1",
"@capacitor/status-bar": "^4.0.1",
"@ionic/vue": "^6.3.0",
"@ionic/vue-router": "^6.3.0",
"@types/chrome": "^0.0.197",
"core-js": "^3.25.2",
"ethers": "^5.7.1",
"vue": "^3.2.39",
"vue-router": "^4.1.5"
},
"devDependencies": {
"@capacitor/cli": "4.1.0",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"@vue/cli-plugin-babel": "~5.0.0-rc.1",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-rc.1",
"@vue/cli-plugin-eslint": "~5.0.0-rc.1",
"@vue/cli-plugin-router": "~5.0.0-rc.1",
"@vue/cli-plugin-typescript": "~5.0.0-rc.1",
"@vue/cli-plugin-unit-jest": "~5.0.0-rc.1",
"@vue/cli-service": "~5.0.0-rc.1",
"@vue/eslint-config-typescript": "^9.1.0",
"@vue/test-utils": "^2.0.0-rc.16",
"@vue/vue3-jest": "^27.0.0-alpha.3",
"babel-jest": "^27.3.1",
"cypress": "^8.7.0",
"eslint": "^8.4.1",
"eslint-plugin-vue": "^8.2.0",
"jest": "^27.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.3.5"
"@capacitor/cli": "^4.3.0",
"@crxjs/vite-plugin": "^1.0.14",
"@types/jest": "^29.0.3",
"@types/node": "^18.7.19",
"@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0",
"@vitejs/plugin-vue": "^3.1.0",
"@vue/eslint-config-typescript": "^11.0.2",
"eslint": "^8.23.1",
"eslint-plugin-vue": "^9.5.1",
"http-browserify": "^1.7.0",
"https-browserify": "^1.0.0",
"jest": "^29.0.3",
"rollup-plugin-polyfill-node": "^0.10.2",
"sass": "^1.55.0",
"stream-browserify": "^3.0.0",
"ts-jest": "^29.0.1",
"typescript": "^4.8.3",
"util": "^0.12.4",
"vite": "^3.1.3",
"vue-tsc": "^0.40.13",
"yarn-upgrade-all": "^0.7.1"
},
"description": "An Ionic project"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="width:400px;height:450px">
<head>
<meta charset="utf-8" />
<title>Ionic App</title>
<title>Clear Wallet</title>
<base href="/" />
@ -14,16 +14,17 @@
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<link rel="shortcut icon" type="image/png" href="<%= BASE_URL %>assets/icon/favicon.png" />
<!-- <link rel="shortcut icon" type="image/png" href="<%= BASE_URL %>assets/icon/favicon.png" /> -->
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Ionic App" />
<meta name="apple-mobile-web-app-title" content="Clear Wallet" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,248 +1,50 @@
<template>
<ion-app>
<ion-split-pane content-id="main-content">
<ion-menu content-id="main-content" type="overlay">
<ion-content>
<ion-list id="inbox-list">
<ion-list-header>Inbox</ion-list-header>
<ion-note>hi@ionicframework.com</ion-note>
<ion-menu-toggle auto-hide="false" v-for="(p, i) in appPages" :key="i">
<ion-item @click="selectedIndex = i" router-direction="root" :router-link="p.url" lines="none" detail="false" class="hydrated" :class="{ selected: selectedIndex === i }">
<ion-icon slot="start" :ios="p.iosIcon" :md="p.mdIcon"></ion-icon>
<ion-label>{{ p.title }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
<ion-list id="labels-list">
<ion-list-header>Labels</ion-list-header>
<ion-item v-for="(label, index) in labels" lines="none" :key="index">
<ion-icon slot="start" :ios="bookmarkOutline" :md="bookmarkSharp"></ion-icon>
<ion-label>{{ label }}</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</ion-split-pane>
<ion-router-outlet />
</ion-app>
</template>
<script lang="ts">
import { IonApp, IonContent, IonIcon, IonItem, IonLabel, IonList, IonListHeader, IonMenu, IonMenuToggle, IonNote, IonRouterOutlet, IonSplitPane } from '@ionic/vue';
import { defineComponent, ref } from 'vue';
import { useRoute } from 'vue-router';
import { archiveOutline, archiveSharp, bookmarkOutline, bookmarkSharp, heartOutline, heartSharp, mailOutline, mailSharp, paperPlaneOutline, paperPlaneSharp, trashOutline, trashSharp, warningOutline, warningSharp } from 'ionicons/icons';
import { IonApp, IonRouterOutlet } from "@ionic/vue";
import { defineComponent } from "vue";
import { useRoute, useRouter} from "vue-router";
export default defineComponent({
name: 'App',
name: "App",
components: {
IonApp,
IonContent,
IonIcon,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonMenu,
IonMenuToggle,
IonNote,
IonRouterOutlet,
IonSplitPane,
IonApp,
IonRouterOutlet,
},
setup() {
const selectedIndex = ref(0);
const appPages = [
{
title: 'Inbox',
url: '/folder/Inbox',
iosIcon: mailOutline,
mdIcon: mailSharp
},
{
title: 'Outbox',
url: '/folder/Outbox',
iosIcon: paperPlaneOutline,
mdIcon: paperPlaneSharp
},
{
title: 'Favorites',
url: '/folder/Favorites',
iosIcon: heartOutline,
mdIcon: heartSharp
},
{
title: 'Archived',
url: '/folder/Archived',
iosIcon: archiveOutline,
mdIcon: archiveSharp
},
{
title: 'Trash',
url: '/folder/Trash',
iosIcon: trashOutline,
mdIcon: trashSharp
},
{
title: 'Spam',
url: '/folder/Spam',
iosIcon: warningOutline,
mdIcon: warningSharp
}
];
const labels = ['Family', 'Friends', 'Notes', 'Work', 'Travel', 'Reminders'];
const path = window.location.pathname.split('folder/')[1];
if (path !== undefined) {
selectedIndex.value = appPages.findIndex(page => page.title.toLowerCase() === path.toLowerCase());
}
const route = useRoute();
return {
selectedIndex,
appPages,
labels,
archiveOutline,
archiveSharp,
bookmarkOutline,
bookmarkSharp,
heartOutline,
heartSharp,
mailOutline,
mailSharp,
paperPlaneOutline,
paperPlaneSharp,
trashOutline,
trashSharp,
warningOutline,
warningSharp,
isSelected: (url: string) => url === route.path ? 'selected' : ''
}
setup () {
const route = useRoute()
const router = useRouter()
const { param, rid } = route.query;
console.log(route?.query,'zzzzzzzzzzzzzzz')
switch (route?.query?.route ?? "") {
case "sign-msg": {
router.push({
path: `/sign-msg/${rid}/${param}`
});
break;
}
case "sign-tx": {
router.push({
path: `/sign-tx/${rid}/${param}`
});
break;
}
case "switch-network": {
router.push({
path: `/switch-network/${rid}/${param}`
});
break;
}
default: {
router.push({ path: "/", })
}
}
}
});
</script>
<style scoped>
ion-menu ion-content {
--background: var(--ion-item-background, var(--ion-background-color, #fff));
}
ion-menu.md ion-content {
--padding-start: 8px;
--padding-end: 8px;
--padding-top: 20px;
--padding-bottom: 20px;
}
ion-menu.md ion-list {
padding: 20px 0;
}
ion-menu.md ion-note {
margin-bottom: 30px;
}
ion-menu.md ion-list-header,
ion-menu.md ion-note {
padding-left: 10px;
}
ion-menu.md ion-list#inbox-list {
border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
}
ion-menu.md ion-list#inbox-list ion-list-header {
font-size: 22px;
font-weight: 600;
min-height: 20px;
}
ion-menu.md ion-list#labels-list ion-list-header {
font-size: 16px;
margin-bottom: 18px;
color: #757575;
min-height: 26px;
}
ion-menu.md ion-item {
--padding-start: 10px;
--padding-end: 10px;
border-radius: 4px;
}
ion-menu.md ion-item.selected {
--background: rgba(var(--ion-color-primary-rgb), 0.14);
}
ion-menu.md ion-item.selected ion-icon {
color: var(--ion-color-primary);
}
ion-menu.md ion-item ion-icon {
color: #616e7e;
}
ion-menu.md ion-item ion-label {
font-weight: 500;
}
ion-menu.ios ion-content {
--padding-bottom: 20px;
}
ion-menu.ios ion-list {
padding: 20px 0 0 0;
}
ion-menu.ios ion-note {
line-height: 24px;
margin-bottom: 20px;
}
ion-menu.ios ion-item {
--padding-start: 16px;
--padding-end: 16px;
--min-height: 50px;
}
ion-menu.ios ion-item.selected ion-icon {
color: var(--ion-color-primary);
}
ion-menu.ios ion-item ion-icon {
font-size: 24px;
color: #73849a;
}
ion-menu.ios ion-list#labels-list ion-list-header {
margin-bottom: 8px;
}
ion-menu.ios ion-list-header,
ion-menu.ios ion-note {
padding-left: 16px;
padding-right: 16px;
}
ion-menu.ios ion-note {
margin-bottom: 8px;
}
ion-note {
display: inline-block;
font-size: 16px;
color: var(--ion-color-medium-shade);
}
ion-item.selected {
--color: var(--ion-color-primary);
}
</style>

33
src/extension/content.ts Normal file
View File

@ -0,0 +1,33 @@
const allowedMethods = {
'eth_accounts': true,
'eth_requestAccounts' : true,
'eth_chainId': true,
'personal_sign' : true,
'wallet_requestPermissions': true
}
window.addEventListener("message", (event) => {
if (event.source != window)
return;
if (event.data.type && (event.data.type == "CLWALLET_CONTENT")) {
event.data.data.resId = event.data.resId
console.log('data in', event?.data)
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, "*");
})
}
});
(function() {
const script = document.createElement('script')
script.src = chrome.runtime.getURL('src/extension/inject.js')
document.documentElement.appendChild(script)
})()

227
src/extension/inject.js Normal file
View File

@ -0,0 +1,227 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var _this = this;
var listners = {
accountsChanged: new Set(),
connect: new Set(),
disconnect: new Set,
chainChanged: new Set()
};
var promResolvers = {};
var listner = function (event) {
var _a, _b;
if (event.source != window)
return;
if (event.data.type && (event.data.type == "CLWALLET_PAGE")) {
if ((_b = (_a = event === null || event === void 0 ? void 0 : event.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) {
promResolvers[event.data.resId].reject(event.data.data);
}
else {
promResolvers[event.data.resId].resolve(event.data.data);
}
promResolvers[event.data.resId] = undefined;
}
};
window.addEventListener("message", listner);
var sendMessage = function (args) {
return new Promise(function (resolve, reject) {
var resId = crypto.randomUUID();
promResolvers[resId] = { resolve: resolve, reject: reject };
var data = { type: "CLWALLET_CONTENT", data: args, resId: resId };
window.postMessage(data, "*");
});
};
// chainId
// :
// "0x89"
// enable
// :
// ƒ ()
// isMetaMask
// :
// true
// networkVersion
// :
// "137"
// request
// :
// ƒ ()
// selectedAddress
// :
// null
// send
// :
// ƒ ()
// sendAsync
// :
// ƒ ()
// _events
// :
// {connect: ƒ}
// _eventsCount
// :
// 1
// _handleAccountsChanged
// :
// ƒ ()
// _handleChainChanged
// :
// ƒ ()
// _handleConnect
// :
// ƒ ()
// _handleDisconnect
// :
// ƒ ()
// _handleStreamDisconnect
// :
// ƒ ()
// _handleUnlockStateChanged
// :
// ƒ ()
// _jsonRpcConnection
// :
// {events: s, stream: d, middleware: ƒ}
// _log
// :
// u {name: undefined, levels: {…}, methodFactory: ƒ, getLevel: ƒ, setLevel: ƒ, …}
// _maxListeners
// :
// 100
// _metamask
// :
// Proxy {isUnlocked: ƒ, requestBatch: ƒ}
// _rpcEngine
// :
// o {_events: {…}, _eventsCount: 0, _maxListeners: undefined, _middleware: Array(3)}
// _rpcRequest
// :
// ƒ ()
// _sendSync
// :
// ƒ ()
// _sentWarnings
// :
// {enable: false, experimentalMethods: false, send: false, events: {…}}
// _state
// :
// {accounts: Array(0), isConnected: true, isUnlocked: true, initialized: true, isPermanentlyDisconnected: false}
// _warnOfDeprecation
// :
// ƒ (
var eth = new Proxy({
isConnected: function () {
return true;
},
// for maximum compatibility since is cloning the same API
isMetaMask: true,
enable: function () {
return sendMessage({ method: 'eth_requestAccounts', params: Array(0) });
},
request: function (args) {
return sendMessage(args);
},
on: function (eventName, callback) {
switch (eventName) {
case 'accountsChanged':
listners.accountsChanged.add(callback);
break;
case 'connect':
listners.connect.add(callback);
break;
case 'disconnect':
listners.disconnect.add(callback);
break;
case 'chainChanged':
listners.chainChanged.add(callback);
break;
default:
return;
}
},
// Simulate Metamask
_warnOfDeprecation: function () { return null; },
_state: {},
_sentWarnings: function () { return null; },
_rpcRequest: function () { return null; },
_handleAccountsChanged: function () { return null; },
chainId: "0x89",
networkVersion: "137",
selectedAddress: null,
send: function () { return null; },
sendAsync: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/, null];
}); }); },
_events: {},
_eventsCount: 0,
_handleChainChanged: function () { return null; },
_handleConnect: function () { return null; },
_handleDisconnect: function () { return null; },
_handleStreamDisconnect: function () { return null; },
_handleUnlockStateChanged: function () { return null; },
_jsonRpcConnection: {},
_log: {},
_maxListeners: 100,
_metamask: new Proxy({}, {}),
_rpcEngine: {}
}, {
set: function () { return false; },
deleteProperty: function () { return false; }
});
var injectWallet = function (win) {
Object.defineProperty(win, 'ethereum', {
get: function () {
return eth;
},
set: function () {
return;
}
});
window.tttest = 'test';
// Object.defineProperty(window, 'ethereum', 444)
console.log('Clear wallet injected', window.ethereum, win);
};
injectWallet(this);
// 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')});
// }, 3500)
// console.log( (window as any).ethereum.request({method: 'eth_chainId'}))

208
src/extension/inject.ts Normal file
View File

@ -0,0 +1,208 @@
interface RequestArguments {
method: string;
params?: unknown[] | object;
}
const listners = {
accountsChanged: new Set<() => void>(),
connect: new Set<() => void>(),
disconnect: new Set<() => void>,
chainChanged: new Set<() => void>(),
}
const promResolvers = {} as any
const listner = function(event: any) {
if (event.source != window)
return;
if (event.data.type && (event.data.type == "CLWALLET_PAGE")) {
if(event?.data?.data?.error){
promResolvers[event.data.resId].reject(event.data.data)
}else {
promResolvers[event.data.resId].resolve(event.data.data);
}
promResolvers[event.data.resId] = undefined;
}
}
window.addEventListener("message",listner)
const sendMessage = (args: RequestArguments) => {
return new Promise((resolve, reject) => {
const resId = crypto.randomUUID()
promResolvers[resId] = { resolve, reject }
const data = { type: "CLWALLET_CONTENT", data: args, resId};
window.postMessage(data, "*");
})
}
// chainId
// :
// "0x89"
// enable
// :
// ƒ ()
// isMetaMask
// :
// true
// networkVersion
// :
// "137"
// request
// :
// ƒ ()
// selectedAddress
// :
// null
// send
// :
// ƒ ()
// sendAsync
// :
// ƒ ()
// _events
// :
// {connect: ƒ}
// _eventsCount
// :
// 1
// _handleAccountsChanged
// :
// ƒ ()
// _handleChainChanged
// :
// ƒ ()
// _handleConnect
// :
// ƒ ()
// _handleDisconnect
// :
// ƒ ()
// _handleStreamDisconnect
// :
// ƒ ()
// _handleUnlockStateChanged
// :
// ƒ ()
// _jsonRpcConnection
// :
// {events: s, stream: d, middleware: ƒ}
// _log
// :
// u {name: undefined, levels: {…}, methodFactory: ƒ, getLevel: ƒ, setLevel: ƒ, …}
// _maxListeners
// :
// 100
// _metamask
// :
// Proxy {isUnlocked: ƒ, requestBatch: ƒ}
// _rpcEngine
// :
// o {_events: {…}, _eventsCount: 0, _maxListeners: undefined, _middleware: Array(3)}
// _rpcRequest
// :
// ƒ ()
// _sendSync
// :
// ƒ ()
// _sentWarnings
// :
// {enable: false, experimentalMethods: false, send: false, events: {…}}
// _state
// :
// {accounts: Array(0), isConnected: true, isUnlocked: true, initialized: true, isPermanentlyDisconnected: false}
// _warnOfDeprecation
// :
// ƒ (
const eth = new Proxy({
isConnected: () => {
return true
},
// for maximum compatibility since is cloning the same API
isMetaMask: true,
enable: () => {
return sendMessage({ method: 'eth_requestAccounts', params: Array(0)})
},
request: (args: RequestArguments): Promise<unknown> => {
return sendMessage(args)
},
on: (eventName: string, callback: () => void) => {
switch (eventName) {
case 'accountsChanged':
listners.accountsChanged.add(callback)
break
case 'connect':
listners.connect.add(callback)
break;
case 'disconnect':
listners.disconnect.add(callback)
break;
case 'chainChanged':
listners.chainChanged.add(callback)
break;
default:
return
}
},
// Simulate Metamask
_warnOfDeprecation: () => null,
_state: {},
_sentWarnings: () => null,
_rpcRequest: () => null,
_handleAccountsChanged: () => null,
chainId: "0x89",
networkVersion: "137",
selectedAddress: null,
send: () => null,
sendAsync: async () => null,
_events: {},
_eventsCount: 0,
_handleChainChanged: () => null,
_handleConnect: () => null,
_handleDisconnect: () => null,
_handleStreamDisconnect: () => null,
_handleUnlockStateChanged: () => null,
_jsonRpcConnection: {},
_log: {},
_maxListeners: 100,
_metamask: new Proxy({}, {}),
_rpcEngine: {}
}, {
set: () => { return false },
deleteProperty: () => { return false },
})
const injectWallet = (win: any) => {
Object.defineProperty(win, 'ethereum', {
get: function () {
return eth
},
set: function () {
return
}
});
(window as any).tttest = 'test'
// Object.defineProperty(window, 'ethereum', 444)
console.log('Clear wallet injected', (window as any).ethereum, win)
}
injectWallet(this)
// 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')});
// }, 3500)
// console.log( (window as any).ethereum.request({method: 'eth_chainId'}))

View File

@ -0,0 +1,55 @@
{
"manifest_version": 3,
"name": "Clear Wallet EVM",
"version": "1.0.0",
"icons": {
"16": "assets/extension-icon/wallet_16.png",
"32": "assets/extension-icon/wallet_32.png",
"48": "assets/extension-icon/wallet_48.png",
"128": "assets/extension-icon/wallet_128.png"
},
"action": {
"default_popup": "index.html",
"default_icon": {
"16": "assets/extension-icon/wallet_16.png",
"32": "assets/extension-icon/wallet_32.png",
"48": "assets/extension-icon/wallet_48.png",
"128": "assets/extension-icon/wallet_128.png"
}
},
"minimum_chrome_version": "93",
"permissions": [
"scripting",
"webNavigation",
"tabs",
"storage",
"alarms",
"unlimitedStorage",
"clipboardRead",
"clipboardWrite"
],
"host_permissions": [
"http://*/",
"https://*/"
],
"background": {
"service_worker": "/src/extension/serviceWorker.ts",
"type": "module"
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"all_frames": true,
"run_at": "document_start",
"js": ["/src/extension/content.ts"]
}
],
"web_accessible_resources": [{
"resources": ["src/extension/inject.js"],
"matches": ["<all_urls>"]
}]
}

View File

@ -0,0 +1,5 @@
export const rpcError ={
USER_REJECTED: 4001,
INVALID_PARAM: -32602,
INTERNAL_ERROR: -32603
}

View File

@ -0,0 +1,289 @@
import { getAccounts, getSelectedAccount, getSelectedNetwork, smallRandomString, storageSave} from '@/utils/platform';
import { userApprove, userReject, rIdWin, rIdData } from '@/extension/userRequest'
import { signMsg, getBalance, getBlockNumber, estimateGas, sendTransaction, getGasPrice } from '@/utils/wallet'
import type { RequestArguments } from '@/extension/types'
import type { Account } from '@/extension/types'
import { rpcError } from '@/extension/rpcConstants'
import { updatePrices } from '@/utils/gecko'
chrome.runtime.onInstalled.addListener(() => {
console.log('Service worker installed');
chrome.runtime.connect(null as unknown as string, {
name:'sw-connection'
})
})
chrome.runtime.onConnect.addListener(port => port.onDisconnect.addListener((a) =>
{
console.log('Service worker connected', storageSave('test-d', a));
}))
chrome.runtime.onStartup.addListener(() => {
console.log('Service worker startup');
})
chrome.runtime.onSuspend.addListener(() => {
console.log('Service worker suspend');
})
chrome.alarms.create('updatePrices', {
periodInMinutes: 1
})
chrome.alarms.onAlarm.addListener((alarm) => {
if(alarm.name === 'updatePrices') {
updatePrices().then(() => {
console.log('Prices updated')
})
}
})
chrome.windows.onRemoved.addListener((winId) => {
if (winId in (userReject ?? {})){
userReject[winId]?.()
}
userReject[winId] = undefined
userApprove[winId] = undefined
rIdWin[winId] = undefined
rIdData[winId] = undefined
chrome.windows.getAll().then((wins) => {
if(wins.length === 0) {
storageSave('test-p', 'browser-closed')
}
})
})
chrome.runtime.onMessage.addListener((message: RequestArguments, sender, sendResponse) => {
console.log(message);
(async () => {
if (!('method' in message)) {
sendResponse({
code: 500,
message: 'Invalid request method'
})
} else {
// ETH API
switch (message.method) {
case 'eth_call': {
break
}
case 'eth_getBalance': {
sendResponse(await getBalance())
break
}
case 'eth_blockNumber': {
sendResponse(await getBlockNumber())
break
}
case 'eth_estimateGas': {
const params = message?.params?.[0] as any
if(!params) {
sendResponse({
error: true,
code: rpcError.INVALID_PARAM,
message: 'Invalid param for gas estimate'
})
break
}
sendResponse(await estimateGas({
to: params?.to ?? '',
from: params?.from ?? '',
data: params?.data ?? '',
value: params?.value ?? '0x0'
}))
break
}
case 'eth_accounts': {
const accounts = await getAccounts()
const addresses = accounts.map((a: Account) => a.address) ?? []
sendResponse(addresses)
break
}
case 'eth_requestAccounts': {
const account = await getSelectedAccount()
const address = account?.address ? [account?.address] : []
sendResponse(address)
break
}
case 'eth_chainId': {
const network = await getSelectedNetwork()
console.log(network, 'network')
const chainId = network?.chainId ?? 0
sendResponse(`0x${chainId.toString(16)}`)
break
}
case 'eth_sendTransaction': {
try {
const params = message?.params?.[0] as any
if(!params) {
sendResponse({
error: true,
code: rpcError.INVALID_PARAM,
message: 'Invalid param for send transaction'
})
break
}
const [account, network] = await Promise.all([getSelectedAccount(), getSelectedNetwork()])
if(!account || !network) {
return
}
const serializeParams = encodeURIComponent(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({
height: 450,
width: 400,
url: chrome.runtime.getURL(`index.html?route=sign-tx&param=${serializeParams}&rid=${String(message?.resId ?? '')}`),
type: 'popup'
}).then((win) => {
gWin = win
userReject[String(win.id)] = reject
userApprove[String(win.id)] = resolve
rIdWin[String(win.id)] = String(message.resId)
rIdData[String(win.id)] = {}
})
})
sendResponse(
await sendTransaction({...params, ...(rIdData?.[String(gWin?.id ?? 0)] ?? {}) }, pEstimateGas, pGasPrice)
)
} catch(err) {
console.error(err)
sendResponse({
error: true,
code: rpcError.USER_REJECTED,
message: 'User Rejected Signature'
})
}
break
}
case ('personal_sign' || 'eth_sign'): {
try {
await new Promise((resolve, reject) => {
chrome.windows.create({
height: 450,
width: 400,
url: chrome.runtime.getURL(`index.html?route=sign-msg&param=${String(message?.params?.[0] ?? '' )}&rid=${String(message?.resId ?? '')}`),
type: 'popup'
}).then((win) => {
userReject[String(win.id)] = reject
userApprove[String(win.id)] = resolve
rIdWin[String(win.id)] = String(message.resId)
})
})
sendResponse(
await signMsg(String(message?.params?.[0]) ?? '' )
)
} catch {
sendResponse({
error: true,
code: rpcError.USER_REJECTED,
message: 'User Rejected Signature'
})
}
break
}
// NON Standard metamask API
case 'wallet_requestPermissions': {
const account = await getSelectedAccount()
const address = account?.address ? [account?.address] : []
sendResponse([{
caveats: {
type:'',
value: address
},
invoker: '',
date: Date.now(),
id: smallRandomString(),
parentCapability: Object.keys(message?.params?.[0] ?? {})?.[0] ?? 'unknown'
}])
break
}
case 'net_version': {
const network = await getSelectedNetwork()
const chainId = network?.chainId ?? 0
sendResponse(chainId)
break
}
case 'wallet_switchEthereumChain': {
try {
await new Promise((resolve, reject) => {
chrome.windows.create({
height: 450,
width: 400,
url: chrome.runtime.getURL(`index.html?route=switch-network&param=${String(message?.params?.[0] ?? '' )}&rid=${String(message?.resId ?? '')}`),
type: 'popup'
}).then((win) => {
userReject[String(win.id)] = reject
userApprove[String(win.id)] = resolve
rIdWin[String(win.id)] = String(message.resId)
})
})
sendResponse(
await signMsg(String(message?.params?.[0]) ?? '' )
)
} catch {
sendResponse({
error: true,
code: rpcError.USER_REJECTED,
message: 'User Rejected Signature'
})
}
break
}
// internal messeges
case 'wallet_approve': {
if(String(sender.tab?.windowId) in rIdWin){
userApprove[String(sender.tab?.windowId)]?.(true)
}
try {
chrome.windows.remove(sender.tab?.windowId ?? 0)
}catch{
// ignore
}
break
}
case 'wallet_send_data': {
if(String(sender.tab?.windowId) in rIdData){
rIdData[String(sender?.tab?.windowId ?? '')] = (message as any)?.data ?? {}
sendResponse(true)
}
break
}
case 'wallet_get_data': {
if(String(sender.tab?.windowId) in rIdData){
sendResponse( rIdData[String(sender?.tab?.windowId ?? '')] ?? {})
}
break
}
case 'wallet_ping': {
sendResponse(true)
break
}
default: {
sendResponse({
error: true,
code: rpcError.INVALID_PARAM,
message: 'Invalid request method'
})
break
}
}
}
}
)();
return true;
});

53
src/extension/types.ts Normal file
View File

@ -0,0 +1,53 @@
export interface Network {
name: string
chainId: number
rpc: string
symbol?: string
icon?: string
priceId?: string
explorer?: string
}
export interface Account {
name: string
address: string
pk: string
encPk: string
}
export interface Accounts {
[key: string]: Account
}
export interface Networks {
[key: number]: Network
}
export interface RequestArguments {
method: string;
params?: unknown[];
resId?: string
}
export interface ProviderRpcError extends Error {
message: string;
code: number;
data?: unknown;
}
export interface Price {
[key: string]: number
}
export interface Prices {
[key: string]: Price
}
export interface Settings {
enableStorageEnctyption: boolean
encryptAfterEveryTx: boolean
lockOutPeriod: number
lockOutEnabled: boolean
theme: 'system' | 'light' | 'dark'
MP: string
}

View File

@ -0,0 +1,32 @@
export const userReject = {} as Record<string, (() => any) | undefined>
export const userApprove = {} as Record<string, ((a: unknown) => any) | undefined>
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 })
}
export const walletSendData = (rId: string, data: any) => {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ method: 'wallet_send_data', resId: rId, data}, (r) => {
resolve(r)
})
})
}
export const walletGetData = (rId: string) => {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ method: 'wallet_get_data', resId: rId }, (r) => {
resolve(r)
})
})
}
export const walletPing = () => {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ method: 'wallet_ping' }, (r) => {
resolve(r)
})
})
}

View File

@ -26,7 +26,7 @@ import './theme/variables.css';
const app = createApp(App)
.use(IonicVue)
.use(router);
router.isReady().then(() => {
app.mount('#app');
});

View File

@ -1,19 +1,70 @@
import { createRouter, createWebHistory } from '@ionic/vue-router';
import { RouteRecordRaw } from 'vue-router';
import AppTabs from '@/views/AppTabs.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '',
redirect: '/folder/Inbox'
path: '/',
redirect: '/tabs/home',
},