changes: for chrome 1.1.4

This commit is contained in:
Andrei O 2022-10-25 02:53:15 +03:00
parent 2642659ae0
commit 8cb68c4a06
No known key found for this signature in database
GPG Key ID: B961E5B68389457E
8 changed files with 299 additions and 231 deletions

7
CHANGELOG.md Normal file
View File

@ -0,0 +1,7 @@
# Changelog:
### Manifest Version: 1.1.4:
- Added max 10 allowed concurrent messages to the wallet to prevent abusive websites from sending too many messages.
- Added explorer-button to main wallet page for easier viewing of the selected address on the blockchain explorer.
- Show the price converted in dollars also besides the native token price on transaction view for networks: 1(Ethereum), 137(Polygon), 100(Gnosis), 10(Optimism), 56(BSC), 42161(Arbitrum One)

View File

@ -8,15 +8,18 @@ Simple EVM wallet chrome extension implementation using ethers, mv3, ionc, vue.
[//]: # Here is an extended article abut this repo:
### FAQ
Q: Why using Ionic?
A: The main idea is to extend the codebase to try to aditional platforms like Desktop and Mobile, and because Ionic has a simple interface that is ready to use with no additional design work.
Q: Is released on Chrome webstore?
A: Not yet but will be probably soon
A: Yes, Link: [https://chrome.google.com/webstore/detail/clear-evm-wallet-clw/djlahdpfkflehaepgohnnodmaajabdlg?hl=en](https://chrome.google.com/webstore/detail/clear-evm-wallet-clw/djlahdpfkflehaepgohnnodmaajabdlg?hl=en)
Q: What are some features?
A: - It assumes some knowlodege about, EVM echosystem it dosen't come with any network, you can add any EVM network you want, and lets you sleect form the templates of some more popular networks.
- You can have the key stored with or without encryption, you can enable or disable autolock, you can force decryption for every message sign or transaction sign & send.
- You can import, export accounts.
@ -27,7 +30,12 @@ A: - It assumes some knowlodege about, EVM echosystem it dosen't come with any
- Prompts only for changing the network, sending/signing transaction, sending message.
Q: Is this ready to use?
A: Currently is under some development but it has a nice set of features that I used, I developed this pretty fast in my free time, you should always backup your keys, and do your own research and only use what you are confortable to use. The software dosen't come with any gurantees and is released as it is. But I definitely recomand this to use for testnets and playing with any kind of experiments.
A: Should work on most modern websites as a Metamask replacement. Currently is under some development but it has a nice set of features that I used, I developed this pretty fast in my free time, you should always backup your keys( since I've seen even well known wallets sometimes render keys inaccessiable), and do your own research and only use what you are confortable to use. The software dosen't come with any gurantees and is released as it is. But I definitely recomand this to use for testnets and playing with any kind of experiments.
Q: Will this prject be heavely mentained.
A: Can't promisse that probably not really :(
## LINKS

View File

@ -39,7 +39,7 @@ window.addEventListener("message", (event) => {
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, website: window?.location?.href ?? '' };
console.log('data back', data)
// console.log('data back', data)
window.postMessage(data, "*");
})
}

View File

@ -13,12 +13,11 @@ const listners = {
const promResolvers = {} as any
const listner = function(event: any) {
if (event.source != window)
return;
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);
// console.log('rejected')
}else {
promResolvers[event.data.resId].resolve(event.data.data);
}
@ -38,16 +37,22 @@ const listner = function(event: any) {
window.addEventListener("message",listner)
const sendMessage = (args: RequestArguments, ping = false) => {
return new Promise((resolve, reject) => {
const resId = crypto.randomUUID()
promResolvers[resId] = { resolve, reject }
const data = { type: "CLWALLET_CONTENT", data: args, resId};
if (ping) {
data.type = 'CLWALLET_PING'
if(Object.values(promResolvers).filter(r=> r).length < 10 ) {
return new Promise((resolve, reject) => {
const resId = crypto.randomUUID()
promResolvers[resId] = { resolve, reject }
const data = { type: "CLWALLET_CONTENT", data: args, resId};
if (ping) {
data.type = 'CLWALLET_PING'
}
// console.log('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."))
})
}
console.log('data in', data)
window.postMessage(data, "*");
})
}
const eth = new Proxy({
@ -61,7 +66,6 @@ const eth = new Proxy({
},
request: (args: RequestArguments): Promise<unknown> => {
return sendMessage(args)
},
// Deprecated
sendAsync: (arg1: RequestArguments, arg2: any): void => {
@ -138,9 +142,9 @@ const eth = new Proxy({
_rpcRequest: () => null,
_handleAccountsChanged: () => null,
// Deprecated - hardcoded for now, websites should not access this directly since is deprecated for a long time
chainId: "0x89",
chainId: "0xa",
// Deprecated - hardcoded for now, websites should not access this directly since is deprecated for a long time
networkVersion: "137",
networkVersion: 10,
selectedAddress: null,
autoRefreshOnNetworkChange: false,
// Internal Simulate Metamask

View File

@ -3,8 +3,8 @@
"name": "__MSG_appName__",
"description": "__MSG_appDesc__",
"default_locale": "en",
"version": "1.1.2",
"version_name": "1.1.2",
"version": "1.1.4",
"version_name": "1.1.4",
"icons": {
"16": "assets/extension-icon/wallet_16.png",
"32": "assets/extension-icon/wallet_32.png",
@ -23,6 +23,7 @@
"minimum_chrome_version": "93",
"permissions": [
"tabs",
"notifications",
"storage",
"alarms",

View File

@ -109,3 +109,7 @@ export const testNets = {
icon: 'arbitrum.webp'
},
}
export const chainIdToPriceId = (chainId: number): string => {
return mainNets?.[chainId]?.priceId ?? 'x'
}

View File

@ -19,6 +19,25 @@
<p style="font-size: 0.7rem">{{ selectedAccount?.address }}</p>
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
</ion-item>
<ion-item
v-if="!loading && selectedNetwork?.explorer && selectedAccount?.address"
>
<ion-button
@click="
openTab(
`${selectedNetwork.explorer}/address/${selectedAccount?.address}`.replace(
'//',
'/'
)
)
"
expand="block"
>View Address on
{{
`${selectedNetwork.explorer}`.replace("https://", "").replace("http://", "")
}}
</ion-button>
</ion-item>
</ion-list>
<ion-item v-if="loading || Object.keys(networks).length < 1">
<ion-label>No EVM Networks found</ion-label>
@ -37,17 +56,6 @@
<ion-label>Selected Network ID: {{ selectedNetwork?.chainId }}</ion-label>
<ion-button @click="networksModal = true">Select</ion-button>
</ion-item>
<ion-item v-if="!loading && selectedNetwork?.explorer && selectedAccount?.address">
<ion-button
@click="
openTab(
`${selectedNetwork.explorer}/${selectedAccount?.address}`.replace('//', '/')
)
"
expand="block"
>View Address on {{ selectedNetwork.explorer }}
</ion-button>
</ion-item>
<ion-loading
:is-open="loading"

View File

@ -7,7 +7,9 @@
</ion-header>
<ion-content class="ion-padding">
<ion-item><ion-label>Network Name: {{ selectedNetwork?.name }}</ion-label></ion-item>
<ion-item
><ion-label>Network Name: {{ selectedNetwork?.name }}</ion-label></ion-item
>
<ion-item>
<ion-avatar
v-if="(mainNets as any)[selectedNetwork?.chainId]?.icon"
@ -24,33 +26,58 @@
<ion-label>Transaction to Sign &amp; Send</ion-label>
</ion-item>
<ion-item>
Last Balance: {{ userBalance }} <span style="opacity:0.7" v-if="dollarPrice > 0">${{ dollarPrice*userBalance }}</span>
Last Balance: {{ userBalance }}
<span
style="font-size: 0.9rem; opacity: 0.7; margin-left: 1rem"
v-if="dollarPrice > 0"
>${{ (dollarPrice * userBalance).toFixed(3) }}</span
>
</ion-item>
<ion-item> Contract: {{ contract }} </ion-item>
<ion-item>
Tx Total Cost: {{ totalCost }}
<span
style="font-size: 0.9rem; opacity: 0.7; margin-left: 1rem"
v-if="dollarPrice > 0"
>${{ (dollarPrice * totalCost).toFixed(3) }}</span
>
</ion-item>
<ion-item>
Contract: {{ contract }}
Gas Fee: {{ gasFee }}
<span
style="font-size: 0.9rem; opacity: 0.7; margin-left: 1rem"
v-if="dollarPrice > 0"
>${{ (dollarPrice * gasFee).toFixed(3) }}</span
>
</ion-item>
<ion-item> Tx value: {{ txValue }} </ion-item>
<ion-item>
Gas Limit: {{ gasLimit }}
<ion-button style="margin-left: 1rem" @click="gasLimitModal = true"
>Set manually</ion-button
>
</ion-item>
<ion-item>
Tx Total Cost: {{ totalCost }} <span style="opacity:0.7" v-if="dollarPrice > 0">${{ dollarPrice*totalCost }}</span>
</ion-item>
<ion-item>
Gas Fee: {{ gasFee }} <span style="opacity:0.7" v-if="dollarPrice > 0">${{ dollarPrice*gasFee }}</span>
</ion-item>
<ion-item>
Tx value: {{ txValue }}
</ion-item>
<ion-item>
Gas Limit: {{ gasLimit }} <ion-button style="margin-left: 1rem" @click="gasLimitModal=true">Set manually</ion-button>
</ion-item>
<ion-item>
Gas Price: {{ gasPrice }} <ion-button style="margin-left: 1rem" @click="gasPriceModal=true">Set manually</ion-button>
Gas Price: {{ gasPrice }}
<ion-button style="margin-left: 1rem" @click="gasPriceModal = true"
>Set manually</ion-button
>
</ion-item>
<ion-item>
<ion-label>Raw TX:</ion-label>
<ion-textarea style="overflow-y: scroll;" :rows="10" :cols="20" :value="signTxData" readonly></ion-textarea>
<ion-textarea
style="overflow-y: scroll"
:rows="10"
:cols="20"
:value="signTxData"
readonly
></ion-textarea>
</ion-item>
<ion-item>
<ion-button @click="onCancel">Cancel</ion-button>
<ion-button :disabled="insuficientBalance" @click="onSign">{{ insuficientBalance ? "Insuficient Balance": "Send" }}</ion-button>
<ion-button :disabled="insuficientBalance" @click="onSign">{{
insuficientBalance ? "Insuficient Balance" : "Send"
}}</ion-button>
</ion-item>
<ion-alert
:is-open="alertOpen"
@ -66,7 +93,7 @@
<ion-list v-if="gasPriceReFetch">
<ion-item>New Fee price Timer: {{ timerFee }}</ion-item>
</ion-list>
<ion-loading
:is-open="loading"
cssClass="my-custom-class"
@ -76,60 +103,54 @@
>
</ion-loading>
<ion-modal
:is-open="gasLimitModal"
>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="gasLimitModal=false">Close</ion-button>
</ion-buttons>
<ion-title>Set Gas Limit</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item>
<ion-label>Limit in units</ion-label>
</ion-item>
<ion-item>
<ion-input v-model="inGasLimit" type="number"></ion-input>
</ion-item>
<ion-item>
<ion-button @click="setGasLimit">Set Price</ion-button>
</ion-item>
</ion-list>
</ion-content>
</ion-modal>
<ion-modal
:is-open="gasPriceModal"
>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="gasPriceModal=false">Close</ion-button>
</ion-buttons>
<ion-title>Set Gas Price</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item>
<ion-label>Price in gwei</ion-label>
</ion-item>
<ion-item>
<ion-input v-model="inGasPrice" type="number"></ion-input>
</ion-item>
<ion-item>
<ion-button @click="setGasPrice">Set Price</ion-button>
</ion-item>
</ion-list>
</ion-content>
</ion-modal>
<ion-modal :is-open="gasLimitModal">
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="gasLimitModal = false">Close</ion-button>
</ion-buttons>
<ion-title>Set Gas Limit</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item>
<ion-label>Limit in units</ion-label>
</ion-item>
<ion-item>
<ion-input v-model="inGasLimit" type="number"></ion-input>
</ion-item>
<ion-item>
<ion-button @click="setGasLimit">Set Price</ion-button>
</ion-item>
</ion-list>
</ion-content>
</ion-modal>
<ion-modal :is-open="gasPriceModal">
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="gasPriceModal = false">Close</ion-button>
</ion-buttons>
<ion-title>Set Gas Price</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-list>
<ion-item>
<ion-label>Price in gwei</ion-label>
</ion-item>
<ion-item>
<ion-input v-model="inGasPrice" type="number"></ion-input>
</ion-item>
<ion-item>
<ion-button @click="setGasPrice">Set Price</ion-button>
</ion-item>
</ion-list>
</ion-content>
</ion-modal>
</ion-content>
</ion-page>
</template>
@ -152,16 +173,25 @@ import {
IonModal,
IonButtons,
IonInput,
modalController
modalController,
} from "@ionic/vue";
import { ethers } from "ethers";
import { approve, walletPing, walletSendData } from "@/extension/userRequest";
import { useRoute } from "vue-router";
import { getSelectedNetwork, getUrl, getPrices, numToHexStr, blockLockout, unBlockLockout, getSelectedAccount, strToHex } from '@/utils/platform'
import { getBalance, getGasPrice, estimateGas } from '@/utils/wallet'
import type { Network } from '@/extension/types'
import { mainNets } from "@/utils/networks";
import UnlockModal from '@/views/UnlockModal.vue'
import {
getSelectedNetwork,
getUrl,
getPrices,
numToHexStr,
blockLockout,
unBlockLockout,
getSelectedAccount,
strToHex,
} from "@/utils/platform";
import { getBalance, getGasPrice, estimateGas } from "@/utils/wallet";
import type { Network } from "@/extension/types";
import { mainNets, chainIdToPriceId } from "@/utils/networks";
import UnlockModal from "@/views/UnlockModal.vue";
import router from "@/router";
export default defineComponent({
@ -180,89 +210,88 @@ export default defineComponent({
IonLoading,
IonModal,
IonButtons,
IonInput
IonInput,
},
setup: () => {
const route = useRoute();
const rid = (route?.params?.rid as string) ?? "";
let isError = false
const decodedParam = decodeURIComponent(route.params?.param as string ?? '')
const params = JSON.parse(decodedParam)
const signTxData = ref('');
let isError = false;
const decodedParam = decodeURIComponent((route.params?.param as string) ?? "");
const params = JSON.parse(decodedParam);
const signTxData = ref("");
const alertOpen = ref(false);
const alertMsg = ref('');
const loading = ref(true)
const contract = params.to
const alertMsg = ref("");
const loading = ref(true);
const contract = params.to;
const gasPrice = ref(0);
const gasLimit = ref(0);
const totalCost = ref(0)
const totalCost = ref(0);
const gasFee = ref(0);
const userBalance = ref(0)
const txValue = ref(0)
const timerReject = ref(140)
const timerFee = ref(20)
const insuficientBalance = ref(false)
const gasPriceReFetch = ref(true)
const userBalance = ref(0);
const txValue = ref(0);
const timerReject = ref(140);
const timerFee = ref(20);
const insuficientBalance = ref(false);
const gasPriceReFetch = ref(true);
const selectedNetwork = (ref(null) as unknown) as Ref<Network>;
const dollarPrice = ref(0)
const gasLimitModal = ref(false)
const gasPriceModal = ref(false)
const inGasPrice = ref(0)
const inGasLimit = ref(0)
const dollarPrice = ref(0);
const gasLimitModal = ref(false);
const gasPriceModal = ref(false);
const inGasPrice = ref(0);
const inGasLimit = ref(0);
let interval = 0
const bars = ref(0)
let interval = 0;
const bars = ref(0);
if(!rid){
if (!rid) {
isError = true;
}
if(!decodedParam){
isError = true
if (!decodedParam) {
isError = true;
} else {
signTxData.value = JSON.stringify( params, null, 2)
signTxData.value = JSON.stringify(params, null, 2);
}
const openModal = async () => {
const modal = await modalController.create({
component: UnlockModal,
componentProps: {
unlockType: 'transaction'
}
});
modal.present();
const { role } = await modal.onWillDismiss();
if(role === 'confirm') return true
return false
}
const modal = await modalController.create({
component: UnlockModal,
componentProps: {
unlockType: "transaction",
},
});
modal.present();
const { role } = await modal.onWillDismiss();
if (role === "confirm") return true;
return false;
};
const onSign = async () => {
loading.value = true;
const selectedAccount = await getSelectedAccount()
loading.value = false
if ((selectedAccount.pk ?? '').length !== 66) {
const modalResult = await openModal()
if(modalResult) {
unBlockLockout()
loading.value = true
approve(rid)
}else {
onCancel()
}
}else {
unBlockLockout()
approve(rid)
loading.value = true;
const selectedAccount = await getSelectedAccount();
loading.value = false;
if ((selectedAccount.pk ?? "").length !== 66) {
const modalResult = await openModal();
if (modalResult) {
unBlockLockout();
loading.value = true;
approve(rid);
} else {
onCancel();
}
loading.value = false
}
} else {
unBlockLockout();
approve(rid);
}
loading.value = false;
};
const onCancel = () => {
window.close();
if(interval) {
if (interval) {
try {
unBlockLockout()
clearInterval(interval)
unBlockLockout();
clearInterval(interval);
} catch {
// ignore
}
@ -270,94 +299,101 @@ export default defineComponent({
};
const newGasData = () => {
gasFee.value = Number(ethers.utils.formatUnits(String(gasLimit.value * gasPrice.value), "gwei"))
txValue.value = Number(ethers.utils.formatEther(params?.value ?? '0x0'))
totalCost.value = gasFee.value + txValue.value
}
gasFee.value = Number(
ethers.utils.formatUnits(String(gasLimit.value * gasPrice.value), "gwei")
);
txValue.value = Number(ethers.utils.formatEther(params?.value ?? "0x0"));
totalCost.value = gasFee.value + txValue.value;
};
onIonViewWillEnter(async () => {
console.log(params.value);
(window as any)?.resizeTo?.(600, 800)
(window as any)?.resizeTo?.(600, 800);
const pEstimateGas = estimateGas({
to: params?.to ?? '',
from: params?.from ?? '',
data: params?.data ?? '',
value: params?.value ?? '0x0'
})
blockLockout()
const pGasPrice = getGasPrice()
const pBalance = getBalance()
const pGetPrices = getPrices()
selectedNetwork.value = await getSelectedNetwork()
userBalance.value = Number(ethers.utils.formatEther((await pBalance).toString() ?? '0x0'))
gasPrice.value = parseInt(ethers.utils.formatUnits((await pGasPrice).toString() ?? '0x0', "gwei"), 10)
to: params?.to ?? "",
from: params?.from ?? "",
data: params?.data ?? "",
value: params?.value ?? "0x0",
});
blockLockout();
const pGasPrice = getGasPrice();
const pBalance = getBalance();
const pGetPrices = getPrices();
selectedNetwork.value = await getSelectedNetwork();
userBalance.value = Number(
ethers.utils.formatEther((await pBalance).toString() ?? "0x0")
);
gasPrice.value = parseInt(
ethers.utils.formatUnits((await pGasPrice).toString() ?? "0x0", "gwei"),
10
);
try {
gasLimit.value = parseInt((await pEstimateGas).toString(), 10)
gasLimit.value = parseInt((await pEstimateGas).toString(), 10);
} catch (err) {
const errorToHex = strToHex(String(err))
router.push(`/contract-error/${rid}/${errorToHex}/${contract}`)
loading.value = false
return
const errorToHex = strToHex(String(err));
router.push(`/contract-error/${rid}/${errorToHex}/${contract}`);
loading.value = false;
return;
}
inGasPrice.value = gasPrice.value
inGasLimit.value = gasLimit.value
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
if ( (selectedNetwork.value?.priceId ?? 'x') in prices ){
dollarPrice.value = prices[(selectedNetwork.value?.priceId ?? 'x')]?.usd ?? 0
newGasData();
if (userBalance.value < totalCost.value) {
insuficientBalance.value = true;
}
const prices = await pGetPrices;
dollarPrice.value =
prices[chainIdToPriceId(selectedNetwork.value?.chainId ?? 0)]?.usd ?? 0;
loading.value=false
loading.value = false;
interval = setInterval(async () => {
if(timerReject.value <= 0) {
onCancel()
if (timerReject.value <= 0) {
onCancel();
return;
}
if( gasPriceReFetch.value ) {
timerFee.value -= 1
if(timerFee.value <= 0) {
timerFee.value = 20
loading.value=true
gasPrice.value = parseInt(ethers.utils.formatUnits((await getGasPrice()).toString(), "gwei"), 10)
newGasData()
loading.value=false
if (gasPriceReFetch.value) {
timerFee.value -= 1;
if (timerFee.value <= 0) {
timerFee.value = 20;
loading.value = true;
gasPrice.value = parseInt(
ethers.utils.formatUnits((await getGasPrice()).toString(), "gwei"),
10
);
newGasData();
loading.value = false;
}
}
timerReject.value -= 1
bars.value++
walletPing()
}, 1000) as any
})
timerReject.value -= 1;
bars.value++;
walletPing();
}, 1000) as any;
});
const setGasLimit = () => {
gasLimit.value = inGasLimit.value
gasLimit.value = inGasLimit.value;
walletSendData(rid, {
gas: numToHexStr(gasLimit.value)
})
newGasData()
gasLimitModal.value = false
}
gas: numToHexStr(gasLimit.value),
});
newGasData();
gasLimitModal.value = false;
};
const setGasPrice = () => {
gasPrice.value = inGasPrice.value
gasPriceReFetch.value = false
gasPrice.value = inGasPrice.value;
gasPriceReFetch.value = false;
walletSendData(rid, {
gasPrice: ethers.utils.parseUnits(gasPrice.value.toString(), "gwei")
})
newGasData()
gasPriceModal.value = false
}
gasPrice: ethers.utils.parseUnits(gasPrice.value.toString(), "gwei"),
});
newGasData();
gasPriceModal.value = false;
};
return {
signTxData,
@ -388,7 +424,7 @@ export default defineComponent({
gasLimitModal,
gasPriceModal,
inGasPrice,
inGasLimit
inGasLimit,
};
},
});