2022-10-07 17:07:59 +00:00
< template >
< ion -page >
< ion -header >
< ion -toolbar >
< ion -title > Settings < / i o n - t i t l e >
< / i o n - t o o l b a r >
< / i o n - h e a d e r >
< ion -content class = "ion-padding" >
2022-10-15 20:20:33 +00:00
< ion -accordion -group :value ="defaultAccordionOpen" v-if ="!loading" >
2022-10-07 17:07:59 +00:00
< ion -accordion value = "1" >
< ion -item slot = "header" color = "light" >
< ion -label > Security < / i o n - l a b e l >
< / i o n - i t e m >
< div class = "ion-padding" slot = "content" >
< ion -list >
2022-10-13 20:48:07 +00:00
< ion -item v-if ="noAccounts" > You need at least one account to touch this settings < / ion -item >
< ion -list :disabled ="noAccounts" >
2022-10-07 17:07:59 +00:00
< ion -item >
< ion -label > Enable Storage Encryption < / i o n - l a b e l >
< ion -toggle :key ="updateKey" @ion-change ="changeEncryption" slot = "end" :checked ="settings.s.enableStorageEnctyption" > < / i o n - t o g g l e >
< / i o n - i t e m >
< ion -item >
This will require to input an encrypto key when storage is locked .
< / i o n - i t e m >
< / i o n - l i s t >
< ion -item :disabled ="!settings.s.enableStorageEnctyption" >
< ion -label > Enable Auto Lock < / i o n - l a b e l >
2022-10-13 20:48:07 +00:00
< ion -toggle :key ="updateKey" @ion-change ="changeAutoLock" slot = "end" :checked ="settings.s.lockOutEnabled" > < / i o n - t o g g l e >
2022-10-07 17:07:59 +00:00
< / i o n - i t e m >
< ion -list >
2022-10-15 20:20:33 +00:00
< ion -item : disabled = "!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled" >
2022-10-07 17:07:59 +00:00
< ion -label > Auto - lock Period : ( 2 - 120 ) minutes < / i o n - l a b e l >
< / i o n - i t e m >
2022-10-13 20:48:07 +00:00
< ion -item : disabled = "!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled" >
2022-10-07 17:07:59 +00:00
< ion -input :key ="updateKey" v-model ="settings.s.lockOutPeriod" type="number" > < / ion -input >
< / i o n - i t e m >
2022-10-13 20:48:07 +00:00
< ion -item : disabled = "!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled" >
2022-10-07 17:07:59 +00:00
< ion -button @click ="setTime" > Set Auto -lock < / ion -button >
< / i o n - i t e m >
< / i o n - l i s t >
< ion -list >
< ion -item >
< ion -label > Permanent Lock < / i o n - l a b e l >
2022-10-15 20:20:33 +00:00
< ion -toggle @ion-change ="changePermaLock" :key ="updateKey" slot = "end" :disabled ="!settings.s.enableStorageEnctyption" :checked ="settings.s.encryptAfterEveryTx" > < / i o n - t o g g l e >
2022-10-07 17:07:59 +00:00
< / i o n - i t e m >
< ion -item > Will require decrypt pass before any sign or transaction < / i o n - i t e m >
< / i o n - l i s t >
< / i o n - l i s t >
< / div >
< / i o n - a c c o r d i o n >
< ion -accordion value = "2" >
< ion -item slot = "header" color = "light" >
< ion -label > Theme < / i o n - l a b e l >
< / i o n - i t e m >
< div class = "ion-padding" slot = "content" >
< ion -list >
2022-10-15 20:20:33 +00:00
< ion -radio -group :value ="radioTheme" >
2022-10-07 17:07:59 +00:00
< ion -item >
< ion -radio
slot = "start"
value = "system"
2022-10-15 20:20:33 +00:00
@ click = "changeTheme('system')"
2022-10-07 17:07:59 +00:00
/ >
< ion -label > System Default < / i o n - l a b e l >
< / i o n - i t e m >
< ion -item >
< ion -radio
slot = "start"
value = "dark"
2022-10-15 20:20:33 +00:00
@ click = "changeTheme('dark')"
2022-10-07 17:07:59 +00:00
/ >
< ion -label > Dark < / i o n - l a b e l >
< / i o n - i t e m >
< ion -item >
< ion -radio
slot = "start"
value = "light"
2022-10-15 20:20:33 +00:00
@ click = "changeTheme('light')"
2022-10-07 17:07:59 +00:00
/ >
< ion -label > Light < / i o n - l a b e l >
< / i o n - i t e m >
< / i o n - r a d i o - g r o u p >
< / i o n - l i s t >
< / div >
< / i o n - a c c o r d i o n >
< ion -accordion value = "3" >
< ion -item slot = "header" color = "light" >
< ion -label > About < / i o n - l a b e l >
< / i o n - i t e m >
< div class = "ion-padding" slot = "content" >
2022-10-15 20:20:33 +00:00
< p > Clear EVM Wallet ( CLW ) is a fully open - source wallet built with Vue , Ionic , 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 . < / p >
< p > Github Repo : < a href = "#" @click ="openTab('https://github.com/andrei0x309/clear-wallet')" > LINK < / a > < / p >
< br / >
< p style = "margin-bottom: 0.2rem" > Some Web3 Projects I personally appreciate : < / p >
< p > YUP - web3 social platform < a href = "#" @click ="openTab('https://app.yup.io')" > LINK < / a > < / p >
< p > Crypto - Leftists : web3 left - wing crypto community < a href = "#" @click ="openTab('https://discord.gg/gzA4bTCdhb')" > LINK < / a > < / p >
< p > Idena : web3 fully private identity provider blockchain < a href = "#" @click ="openTab('https://www.idena.io/')" > LINK < / a > < / p >
< p > Mirror : web3 publishing platform < a href = "#" @click ="openTab('https://mirror.xyz')" > LINK < / a > < / p >
2022-10-07 17:07:59 +00:00
< / div >
< / i o n - a c c o r d i o n >
< ion -accordion value = "4" >
2022-10-10 00:52:23 +00:00
< ion -item slot = "header" color = "light" >
< ion -label > Import / Export Accounts < / i o n - l a b e l >
< / i o n - i t e m >
< div class = "ion-padding" slot = "content" >
< ion -item >
< ion -label > Import Additional Accounts < / i o n - l a b e l >
2022-10-10 23:01:14 +00:00
< input ref = "importFile" type = "file" accept = ".json" / >
< ion -button color = "warning" @click ="importAcc" > Import < / ion -button >
2022-10-10 00:52:23 +00:00
< / i o n - i t e m >
< ion -item >
< ion -label > Export All Accounts < / i o n - l a b e l >
2022-10-10 23:01:14 +00:00
< ion -button color = "warning" @click ="exportAcc" > Export < / ion -button >
2022-10-10 00:52:23 +00:00
< / i o n - i t e m >
< / div >
< / i o n - a c c o r d i o n >
< ion -accordion value = "5" >
2022-10-07 17:07:59 +00:00
< ion -item slot = "header" color = "light" >
< ion -label > Danger < / i o n - l a b e l >
< / i o n - i t e m >
< div class = "ion-padding" slot = "content" >
< ion -item >
< ion -label > WIPE All DATA < / i o n - l a b e l >
< ion -button color = "danger" @click ="wipeStorage" > PERMA WIPE < / ion -button >
< / i o n - i t e m >
< / div >
< / i o n - a c c o r d i o n >
< / i o n - a c c o r d i o n - g r o u p >
< ion -toast
: is - open = "toastState"
@ didDismiss = "toastState = false"
: message = "toastMsg"
: duration = "1500"
> < / i o n - t o a s t >
< ion -loading
: is - open = "loading"
cssClass = "my-custom-class"
message = "Please wait..."
: duration = "4000"
@ didDismiss = "loading = false"
>
< / i o n - l o a d i n g >
< ion -modal
: is - open = "mpModal"
@ did - dismiss = "mpModal=false;modalDismiss()"
>
< ion -header >
< ion -toolbar >
< ion -buttons slot = "start" >
2022-10-13 20:48:07 +00:00
< ion -button @ click = "modalGetPassword?.reject ? (() => { modalGetPassword.reject(); modalGetPassword = null })() : mpModal=false" > Close < / i o n - b u t t o n >
2022-10-07 17:07:59 +00:00
< / i o n - b u t t o n s >
2022-10-13 20:48:07 +00:00
< ion -title v-if ="!settings.s.enableStorageEnctyption" > Create Encryption Password < / ion -title >
< ion -title v-else > Enter Encryption Password < / ion -title >
2022-10-07 17:07:59 +00:00
< / i o n - t o o l b a r >
< / i o n - h e a d e r >
< ion -content class = "ion-padding" >
< ion -list v-if ="settings.s.enableStorageEnctyption" >
< ion -item >
2022-10-15 20:20:33 +00:00
< ion -label > Old Password < / i o n - l a b e l >
2022-10-07 17:07:59 +00:00
< / i o n - i t e m > < i o n - i t e m >
< ion -input v-model ="mpPass" type="password" > < / ion -input >
< / i o n - i t e m >
< / i o n - l i s t >
< div v-else >
< ion -list >
< ion -item >
< ion -label > New Password < / i o n - l a b e l >
< / i o n - i t e m > < i o n - i t e m >
< ion -input v-model ="mpPass" type="password" > < / ion -input >
< / i o n - i t e m >
< / i o n - l i s t >
< ion -list >
< ion -item >
< ion -label > Confirm < / i o n - l a b e l >
< / i o n - i t e m > < i o n - i t e m >
< ion -input v-model ="mpConfirm" type="password" > < / ion -input >
< / i o n - i t e m >
< / i o n - l i s t >
< / div >
< ion -item >
2022-10-13 20:48:07 +00:00
< ion -button @ click = "modalGetPassword?.resolve ? (() => { modalGetPassword.resolve(); modalGetPassword = null })() : confirmModal()" > Confirm < / i o n - b u t t o n >
2022-10-07 17:07:59 +00:00
< / i o n - i t e m >
< / i o n - c o n t e n t >
< / i o n - m o d a l >
< ion -alert
: is - open = "alertOpen"
2022-10-13 20:48:07 +00:00
: header = "alertHeader"
2022-10-07 17:07:59 +00:00
: message = "alertMsg"
: buttons = "['OK']"
@ didDismiss = "alertOpen=false"
> < / i o n - a l e r t >
< / i o n - c o n t e n t >
< / i o n - p a g e >
< / template >
< script lang = "ts" >
2022-10-10 23:01:14 +00:00
import { defineComponent , ref , reactive , Ref } from "vue" ;
2022-10-15 20:20:33 +00:00
import { storageWipe , getSettings , setSettings , getAccounts , saveSelectedAccount , replaceAccounts , openTab } from "@/utils/platform" ;
2022-10-13 20:48:07 +00:00
import { decrypt , encrypt , getCryptoParams } from "@/utils/webCrypto"
import { Account } from '@/extension/types'
import { exportFile } from '@/utils/misc'
2022-10-07 17:07:59 +00:00
import type { Settings } from "@/extension/types"
import {
IonContent ,
IonHeader ,
IonPage ,
IonTitle ,
IonToolbar ,
IonItem ,
IonLabel ,
IonButton ,
IonLoading ,
onIonViewWillEnter ,
IonList ,
IonToggle ,
IonModal ,
IonInput ,
IonAccordion ,
IonAccordionGroup ,
IonRadioGroup ,
IonRadio ,
IonButtons ,
IonAlert ,
IonToast
} from "@ionic/vue" ;
export default defineComponent ( {
components : {
IonContent ,
IonHeader ,
IonPage ,
IonTitle ,
IonToolbar ,
IonItem ,
IonLabel ,
IonButton ,
IonLoading ,
IonList ,
IonToggle ,
IonModal ,
IonInput ,
IonAccordion ,
IonAccordionGroup ,
IonRadioGroup ,
IonRadio ,
IonButtons ,
IonAlert ,
IonToast
} ,
setup ( ) {
const loading = ref ( true ) ;
const mpModal = ref ( false ) ;
const mpPass = ref ( '' ) ;
const mpConfirm = ref ( '' ) ;
const updateKey = ref ( 0 ) ;
const alertOpen = ref ( false ) ;
const alertMsg = ref ( '' ) ;
const toastState = ref ( false ) ;
const toastMsg = ref ( '' ) ;
2022-10-13 20:48:07 +00:00
const alertHeader = ref ( 'Error' )
2022-10-10 23:01:14 +00:00
const importFile = ref ( null ) as unknown as Ref < HTMLInputElement >
2022-10-13 20:48:07 +00:00
type ModalPromisePassword = null | { resolve : ( ( p ? : unknown ) => void ) , reject : ( ( p ? : unknown ) => void ) }
const modalGetPassword = ref ( null ) as Ref < ModalPromisePassword >
const noAccounts = ref ( true )
2022-10-15 20:20:33 +00:00
const defaultAccordionOpen = ref ( "0" )
const radioTheme = ref ( 'system' ) as Ref < 'system' | 'light' | 'dark' >
2022-10-07 17:07:59 +00:00
const wipeStorage = async ( ) => {
loading . value = true ;
await storageWipe ( ) ;
loading . value = false ;
} ;
const settings = reactive ( {
s : null as unknown as Settings
} ) as { s : Settings }
const saveSettings = async ( ) => {
loading . value = true
await setSettings ( settings . s )
loading . value = false
}
const setEncryptToggle = ( state : boolean ) => {
settings . s . enableStorageEnctyption = state
updateKey . value ++
2022-10-15 20:20:33 +00:00
defaultAccordionOpen . value = "1"
2022-10-07 17:07:59 +00:00
}
2022-10-13 20:48:07 +00:00
const changeAutoLock = async ( ) => {
settings . s . lockOutEnabled = ! settings . s . lockOutEnabled
updateKey . value ++
await saveSettings ( )
2022-10-15 20:20:33 +00:00
defaultAccordionOpen . value = "1"
}
const changePermaLock = async ( ) => {
2022-10-16 22:25:20 +00:00
settings . s . encryptAfterEveryTx = ! settings . s . encryptAfterEveryTx
2022-10-15 20:20:33 +00:00
updateKey . value ++
await saveSettings ( )
defaultAccordionOpen . value = "1"
}
const changeTheme = async ( theme : 'system' | 'light' | 'dark' ) => {
document . body . classList . remove ( radioTheme . value )
document . body . classList . add ( theme )
radioTheme . value = theme
settings . s . theme = theme
await saveSettings ( )
defaultAccordionOpen . value = "2"
2022-10-13 20:48:07 +00:00
}
2022-10-07 17:07:59 +00:00
const changeEncryption = async ( ) => {
loading . value = true
mpModal . value = true
loading . value = false
}
const confirmModal = async ( ) => {
loading . value = true
if ( mpPass . value . length < 3 ) {
loading . value = false
2022-10-13 20:48:07 +00:00
alertHeader . value = 'Error'
2022-10-07 17:07:59 +00:00
alertMsg . value = 'Password is too short. More than 3 characters are required.' ;
alertOpen . value = true
setEncryptToggle ( settings . s . enableStorageEnctyption )
return
}
if ( ! settings . s . enableStorageEnctyption ) {
if ( mpPass . value !== mpConfirm . value ) {
loading . value = false
2022-10-13 20:48:07 +00:00
alertHeader . value = 'Error'
2022-10-07 17:07:59 +00:00
alertMsg . value = 'Password and confirm password do not match' ;
alertOpen . value = true
setEncryptToggle ( settings . s . enableStorageEnctyption )
return
}
let accounts = await getAccounts ( )
2022-10-13 20:48:07 +00:00
const cryptoParams = await getCryptoParams ( mpPass . value )
2022-10-07 17:07:59 +00:00
const accProm = accounts . map ( async a => {
2022-10-13 20:48:07 +00:00
a . encPk = await encrypt ( a . pk , cryptoParams )
2022-10-07 17:07:59 +00:00
a . pk = ''
return a
} )
accounts = await Promise . all ( accProm )
await replaceAccounts ( accounts )
await saveSelectedAccount ( accounts [ 0 ] )
setEncryptToggle ( true )
await setSettings ( settings . s )
mpPass . value = ''
mpConfirm . value = ''
mpModal . value = false
} else {
try {
let accounts = await getAccounts ( )
2022-10-13 20:48:07 +00:00
const cryptoParams = await getCryptoParams ( mpPass . value )
2022-10-07 17:07:59 +00:00
const accProm = accounts . map ( async a => {
if ( a . encPk ) {
2022-10-13 20:48:07 +00:00
a . pk = await decrypt ( a . encPk , cryptoParams )
2022-10-07 17:07:59 +00:00
}
return a
} )
2022-10-13 20:48:07 +00:00
accProm . forEach ( a => a . catch ( e => console . log ( e ) ) )
2022-10-07 17:07:59 +00:00
accounts = await Promise . all ( accProm )
await replaceAccounts ( accounts )
await saveSelectedAccount ( accounts [ 0 ] )
setEncryptToggle ( false )
settings . s . lockOutEnabled = false
settings . s . encryptAfterEveryTx = false
await setSettings ( settings . s )
mpPass . value = ''
mpConfirm . value = ''
mpModal . value = false
2022-10-13 20:48:07 +00:00
} catch ( error ) {
console . log ( error )
2022-10-07 17:07:59 +00:00
loading . value = false
2022-10-13 20:48:07 +00:00
alertHeader . value = 'Error'
2022-10-07 17:07:59 +00:00
alertMsg . value = 'Decryption failed, password is not correct.' ;
alertOpen . value = true
setEncryptToggle ( settings . s . enableStorageEnctyption )
return
}
}
loading . value = false
}
2022-10-10 23:01:14 +00:00
const validateFile = ( ) => {
return new Promise ( ( resolve ) => {
try {
if ( ! importFile . value ? . value ? . length ) {
return resolve ( {
error : 'Import json file is missing'
} )
}
const reader = new FileReader ( ) ;
reader . onload = ( event ) => {
const json = JSON . parse ( event ? . target ? . result as string )
if ( ! json . length ) {
2022-10-13 20:48:07 +00:00
return resolve ( { error : 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}]' } )
2022-10-10 23:01:14 +00:00
}
2022-10-13 20:48:07 +00:00
const test = json . some ( ( e : any ) => ( ! ( 'pk' in e ) || ! ( 'name' in e ) || ! ( 'address' in e ) || ! ( e . pk . length === 66 || e . pk . length === 64 ) ) )
2022-10-10 23:01:14 +00:00
if ( test ) {
2022-10-13 20:48:07 +00:00
return resolve ( { error : 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}], Also PK must be valid (66 || 64 length) !' } )
2022-10-10 23:01:14 +00:00
}
2022-10-13 20:48:07 +00:00
return resolve ( { error : false , json } )
2022-10-10 23:01:14 +00:00
}
reader . readAsText ( importFile . value ? . files ? . [ 0 ] as File ) ;
} catch {
return resolve (
{
error : 'Parsing JSON file'
} )
}
} )
}
2022-10-13 20:48:07 +00:00
const getPassword = ( ) => {
return new Promise ( ( resolve , reject ) => {
modalGetPassword . value = { resolve , reject }
mpModal . value = true
} )
}
const promptForPassword = async ( accounts : Account [ ] ) => {
let isCorectPass = false
do {
try {
await getPassword ( )
modalGetPassword . value = null
} catch {
alertHeader . value = 'Error'
alertMsg . value = "Password is required!"
alertOpen . value = true
mpModal . value = false
return false
}
try {
const cryptoParams = await getCryptoParams ( mpPass . value )
if ( accounts ? . [ 0 ] ? . encPk ) {
await decrypt ( accounts [ 0 ] . encPk , cryptoParams )
}
isCorectPass = true
} catch {
isCorectPass = false
alertHeader . value = 'Error'
alertMsg . value = "Password is wrong!"
alertOpen . value = true
}
} while ( ! isCorectPass ) ;
return true
}
2022-10-10 00:52:23 +00:00
const importAcc = async ( ) => {
2022-10-10 23:01:14 +00:00
const validation = await validateFile ( ) as { error : any }
if ( validation . error ) {
alertMsg . value = validation . error
alertOpen . value = true
return
}
2022-10-13 20:48:07 +00:00
const accounts = await getAccounts ( )
const newAccounts = ( validation as unknown as { json : Account [ ] } ) . json
if ( settings . s . enableStorageEnctyption ) {
const hasPass = await promptForPassword ( accounts )
if ( hasPass ) {
const cryptoParams = await getCryptoParams ( mpPass . value )
const accProm = newAccounts . map ( async a => {
if ( a . pk . length === 64 ) {
a . pk = ` 0x ${ a . pk } `
}
a . encPk = await encrypt ( a . pk , cryptoParams )
return a
} )
const encNewAccounts = await Promise . all ( accProm )
await replaceAccounts ( [ ... accounts , ... encNewAccounts ] )
alertHeader . value = 'Success'
alertMsg . value = "Successfully imported new accounts."
alertOpen . value = true
noAccounts . value = false
}
return false
} else {
await replaceAccounts ( [ ... accounts , ... newAccounts . map ( a => { a . encPk = '' ; return a } ) ] )
alertHeader . value = 'Success'
alertMsg . value = "Successfully imported new accounts."
alertOpen . value = true
noAccounts . value = false
}
2022-10-10 00:52:23 +00:00
}
const exportAcc = async ( ) => {
2022-10-13 20:48:07 +00:00
const accounts = await getAccounts ( )
if ( ! accounts . length ) {
alertMsg . value = "You need at least one account to export."
alertOpen . value = true
}
if ( settings . s . enableStorageEnctyption ) {
const hasPass = await promptForPassword ( accounts )
if ( hasPass ) {
const cryptoParams = await getCryptoParams ( mpPass . value )
const accProm = accounts . map ( async a => {
a . pk = await decrypt ( a . encPk , cryptoParams )
return a
} )
const encNewAccounts = await Promise . all ( accProm )
exportFile ( 'wallet_export.json' , JSON . stringify ( encNewAccounts , null , 2 ) )
}
return false
} else {
exportFile ( 'wallet_export.json' , JSON . stringify ( accounts , null , 2 ) )
}
2022-10-10 00:52:23 +00:00
}
2022-10-07 17:07:59 +00:00
2022-10-13 20:48:07 +00:00
onIonViewWillEnter ( async ( ) => {
await Promise . all ( [ getSettings ( ) . then ( ( storeSettings ) =>
2022-10-07 17:07:59 +00:00
{
settings . s = storeSettings
2022-10-15 20:20:33 +00:00
radioTheme . value = settings . s . theme
2022-10-13 20:48:07 +00:00
} ) ,
getAccounts ( ) . then ( ( accounts ) => {
if ( accounts . length ) {
noAccounts . value = false
}
} ) ] )
loading . value = false
2022-10-07 17:07:59 +00:00
} )
const setTime = async ( ) => {
loading . value = true
if ( settings . s . lockOutPeriod < 2 || settings . s . lockOutPeriod > 120 ) {
loading . value = false
alertMsg . value = 'Auto-lock period must be between 2 and 120' ;
alertOpen . value = true
return
}
settings . s . lockOutPeriod = Math . trunc ( settings . s . lockOutPeriod )
await saveSettings ( )
loading . value = false
toastMsg . value = 'Auto-lock period was set' ;
toastState . value = true
}
const modalDismiss = ( ) => {
setEncryptToggle ( settings . s . enableStorageEnctyption )
}
return {
wipeStorage ,
loading ,
mpModal ,
settings ,
saveSettings ,
changeEncryption ,
mpPass ,
mpConfirm ,
confirmModal ,
updateKey ,
alertOpen ,
alertMsg ,
modalDismiss ,
setTime ,
toastState ,
2022-10-10 00:52:23 +00:00
toastMsg ,
importAcc ,
2022-10-10 23:01:14 +00:00
exportAcc ,
2022-10-13 20:48:07 +00:00
importFile ,
modalGetPassword ,
noAccounts ,
alertHeader ,
2022-10-15 20:20:33 +00:00
changeAutoLock ,
defaultAccordionOpen ,
changeTheme ,
openTab ,
radioTheme ,
changePermaLock
2022-10-07 17:07:59 +00:00
} ;
} ,
} ) ;
< / script >