init
This commit is contained in:
commit
44f31f8b9f
402 changed files with 47865 additions and 0 deletions
17
modules/account/Page.vue
Normal file
17
modules/account/Page.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div class="account">
|
||||
<Settings />
|
||||
<NoteAccount />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Settings, NoteAccount } from './components'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Settings,
|
||||
NoteAccount
|
||||
}
|
||||
}
|
||||
</script>
|
94
modules/account/components/Control/Actions.vue
Normal file
94
modules/account/components/Control/Actions.vue
Normal file
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="action">
|
||||
<Statistic />
|
||||
<div v-for="action in actions" :key="action.description" class="action-item">
|
||||
<b-icon :icon="action.icon" size="is-large" />
|
||||
<div class="desc">
|
||||
{{ $t(action.description) }}
|
||||
</div>
|
||||
<b-button type="is-primary" outlined @mousedown.prevent @click="action.onClick">
|
||||
{{ $t(action.button) }}
|
||||
</b-button>
|
||||
</div>
|
||||
<div class="action-item has-switch">
|
||||
<b-icon icon="account-file" size="is-large" />
|
||||
<div class="desc">
|
||||
{{ $t('account.control.fileDesc') }}
|
||||
</div>
|
||||
<b-switch :value="isEnabledSaveFile" size="is-medium" @input="handleEnabledSaveFile" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { openDecryptModal, openShowRecoverKeyModal, openRemoveAccountModal } from '../../modals'
|
||||
import { controlComputed, controlMethods } from '../../injectors'
|
||||
import Statistic from './Statistic'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Statistic
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: [
|
||||
{
|
||||
icon: 'account-notes',
|
||||
onClick: this.getEncryptedNotes,
|
||||
button: 'account.control.loadAll',
|
||||
description: 'account.control.loadAllDesc'
|
||||
},
|
||||
{
|
||||
icon: 'account-key',
|
||||
onClick: this.openRecoverKeyModal,
|
||||
button: 'account.control.showRecoveryKey',
|
||||
description: 'account.control.showRecoveryKeyDesc'
|
||||
},
|
||||
{
|
||||
icon: 'account-remove',
|
||||
button: 'account.control.remove',
|
||||
onClick: this.handleRemoveAccount,
|
||||
description: 'account.control.removeDesc'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...controlComputed
|
||||
},
|
||||
methods: {
|
||||
...controlMethods,
|
||||
handleEnabledSaveFile() {
|
||||
this.enabledSaveFile()
|
||||
},
|
||||
async getEncryptedNotes() {
|
||||
const props = await this.decryptNotes()
|
||||
|
||||
if (props) {
|
||||
openDecryptModal({ ...props, parent: this })
|
||||
}
|
||||
},
|
||||
handleRemoveAccount() {
|
||||
const onConfirm = () => {
|
||||
this.addNoticeWithInterval({
|
||||
notice: {
|
||||
title: 'accountHasBeenDeleted',
|
||||
type: 'info'
|
||||
},
|
||||
interval: 2000
|
||||
})
|
||||
this.removeAccount()
|
||||
}
|
||||
|
||||
openRemoveAccountModal({ i18n: this.$i18n, onConfirm })
|
||||
},
|
||||
async openRecoverKeyModal() {
|
||||
const recoveryKey = await this.getRecoveryKey()
|
||||
|
||||
if (recoveryKey) {
|
||||
openShowRecoverKeyModal({ recoveryKey, parent: this })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
23
modules/account/components/Control/Control.vue
Normal file
23
modules/account/components/Control/Control.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="account-box">
|
||||
<Header />
|
||||
<Actions />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from './Header'
|
||||
import Actions from './Actions'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header,
|
||||
Actions
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
27
modules/account/components/Control/Header.vue
Normal file
27
modules/account/components/Control/Header.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="address">
|
||||
<div class="address-item">
|
||||
<div class="label">{{ $t('account.account') }}</div>
|
||||
<div class="value">{{ accounts.encrypt }}</div>
|
||||
</div>
|
||||
<div class="address-item">
|
||||
<div class="label">{{ $t('account.backedUpWith') }}</div>
|
||||
<div class="value is-small">{{ accounts.backup }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { headerComputed } from '../../injectors'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
...headerComputed
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
48
modules/account/components/Control/Statistic.vue
Normal file
48
modules/account/components/Control/Statistic.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="action-item">
|
||||
<b-icon icon="account-balance" size="is-large" />
|
||||
<i18n path="account.control.balance" tag="div" class="desc">
|
||||
<template v-if="hasBalances" v-slot:value>
|
||||
<p class="balance">
|
||||
<span v-for="(item, index) in getBalance" :key="item.currency" class="balance-item"
|
||||
><NumberFormat :value="item.amount" /> {{ getSymbol(item.currency)
|
||||
}}{{ index !== getBalance.length - 1 ? ',' : '' }}</span
|
||||
>
|
||||
</p>
|
||||
</template>
|
||||
</i18n>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { statisticComputed } from '../../injectors'
|
||||
import { NumberFormat } from '../../dependencies'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NumberFormat
|
||||
},
|
||||
computed: {
|
||||
...statisticComputed,
|
||||
getBalance() {
|
||||
const balances = this.statistic.reduce((acc, { currency, amount }) => {
|
||||
if (acc[currency]) {
|
||||
acc[currency] += Number(amount)
|
||||
} else {
|
||||
acc[currency] = Number(amount)
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
return Object.keys(balances).map((k) => {
|
||||
return {
|
||||
currency: k,
|
||||
amount: balances[k]
|
||||
}
|
||||
})
|
||||
},
|
||||
hasBalances() {
|
||||
return this.getBalance && this.getBalance.length
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
1
modules/account/components/Control/index.js
Normal file
1
modules/account/components/Control/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as Control } from './Control'
|
52
modules/account/components/Indicator/Indicator.vue
Normal file
52
modules/account/components/Indicator/Indicator.vue
Normal file
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<b-tooltip position="is-bottom" type="is-dark-tooltip" :triggers="[]">
|
||||
<template v-slot:content>
|
||||
<template v-if="isSetupAccount">
|
||||
<p>{{ $t('accountConnected') }}</p>
|
||||
<a @click="onCopy">{{ shortAddress(accounts.encrypt) }}</a>
|
||||
<p><NumberFormat :value="noteAccountBalance" /> {{ currency }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>{{ $t('notConnected') }}</p>
|
||||
<b-button type="is-primary-link mb-0" @click="redirectToAccount">{{ $t('connectAccount') }}</b-button>
|
||||
</template>
|
||||
</template>
|
||||
<b-button type="is-nav-icon" icon-left="wallet" :class="{ tornado: isSetupAccount }"></b-button>
|
||||
</b-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { indicatorComputed, indicatorMethods } from '../../injectors'
|
||||
import { NumberFormat } from '../../dependencies'
|
||||
import { sliceAddress } from '@/utils'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NumberFormat
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...indicatorComputed
|
||||
},
|
||||
methods: {
|
||||
...indicatorMethods,
|
||||
shortAddress(address) {
|
||||
return sliceAddress(address)
|
||||
},
|
||||
async onCopy() {
|
||||
await this.$copyText(this.accounts.encrypt)
|
||||
this.$store.dispatch('notice/addNoticeWithInterval', {
|
||||
notice: {
|
||||
title: 'copied',
|
||||
type: 'info'
|
||||
},
|
||||
interval: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
1
modules/account/components/Indicator/index.js
Normal file
1
modules/account/components/Indicator/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as Indicator } from './Indicator'
|
67
modules/account/components/NoteAccount/NoteAccount.vue
Normal file
67
modules/account/components/NoteAccount/NoteAccount.vue
Normal file
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div ref="note" class="note-account" :class="{ 'is-active': isActive }">
|
||||
<h2 class="title">
|
||||
<!-- <b-icon icon="astronaut" size="is-large" /> -->
|
||||
{{ $t('account.title') }}
|
||||
</h2>
|
||||
<b-notification class="main-notification" type="is-info">
|
||||
{{ $t('account.description') }}
|
||||
</b-notification>
|
||||
<Setup v-if="!isSetupAccount" />
|
||||
<Control v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { noteComputed, noteMethods } from '../../injectors'
|
||||
|
||||
import { Setup } from '../Setup'
|
||||
import { Control } from '../Control'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Setup,
|
||||
Control
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...noteComputed
|
||||
},
|
||||
watch: {
|
||||
isInitialized(isInitialized) {
|
||||
if (isInitialized) {
|
||||
this.checkExistAccount()
|
||||
}
|
||||
},
|
||||
isHighlightedNoteAccount: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.scrollOnHiglight()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.checkExistAccount()
|
||||
},
|
||||
methods: {
|
||||
...noteMethods,
|
||||
scrollOnHiglight() {
|
||||
setTimeout(() => {
|
||||
this.isActive = true
|
||||
this.$refs.note.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' })
|
||||
}, 100)
|
||||
|
||||
setTimeout(() => {
|
||||
this.isActive = false
|
||||
this.highlightNoteAccount({ isHighlighted: false })
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
1
modules/account/components/NoteAccount/index.js
Normal file
1
modules/account/components/NoteAccount/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as NoteAccount } from './NoteAccount'
|
46
modules/account/components/Settings/Actions.vue
Normal file
46
modules/account/components/Settings/Actions.vue
Normal file
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="action">
|
||||
<div class="action-item">
|
||||
<b-icon icon="account-wallet" size="is-large" />
|
||||
<div class="desc">
|
||||
{{ isLoggedIn ? $t('account.wallet.disconnect') : $t('account.wallet.desc') }}
|
||||
</div>
|
||||
<b-button v-if="isLoggedIn" type="is-primary" outlined @mousedown.prevent @click="onLogOut">
|
||||
{{ $t('account.wallet.logout') }}
|
||||
</b-button>
|
||||
<connect-button v-else outlined action-text="account.wallet.connectWeb3" />
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<b-icon icon="account-rpc" size="is-large" />
|
||||
<div class="desc">
|
||||
{{ $t('account.wallet.rpcDesc') }}
|
||||
</div>
|
||||
<b-button type="is-primary" outlined @click="onSettings">{{ $t('account.wallet.changeRpc') }}</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { walletComputed, walletActions } from '../../injectors'
|
||||
import { openSettingsModal } from '../../modals'
|
||||
|
||||
import { ConnectButton } from '@/components/web3Connect'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ConnectButton
|
||||
},
|
||||
computed: {
|
||||
...walletComputed
|
||||
},
|
||||
methods: {
|
||||
...walletActions,
|
||||
onSettings() {
|
||||
openSettingsModal({
|
||||
parent: this,
|
||||
netId: this.netId
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
18
modules/account/components/Settings/Header.vue
Normal file
18
modules/account/components/Settings/Header.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<div class="address">
|
||||
<div class="address-item">
|
||||
<div class="label">{{ $t('account.wallet.label') }}</div>
|
||||
<div class="value">{{ ethAccount || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { walletComputed } from '../../injectors'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...walletComputed
|
||||
}
|
||||
}
|
||||
</script>
|
23
modules/account/components/Settings/Settings.vue
Normal file
23
modules/account/components/Settings/Settings.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="wallet-account">
|
||||
<h2 class="title">
|
||||
{{ $t('wallet') }}
|
||||
</h2>
|
||||
<div class="account-box">
|
||||
<Header />
|
||||
<Actions />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from './Header'
|
||||
import Actions from './Actions'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header,
|
||||
Actions
|
||||
}
|
||||
}
|
||||
</script>
|
1
modules/account/components/Settings/index.js
Normal file
1
modules/account/components/Settings/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as Settings } from './Settings'
|
94
modules/account/components/Setup/Actions.vue
Normal file
94
modules/account/components/Setup/Actions.vue
Normal file
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="action">
|
||||
<div class="action-item">
|
||||
<b-icon icon="account-setup" size="is-large" />
|
||||
<div class="desc">
|
||||
{{ $t('account.setup.desc') }}
|
||||
</div>
|
||||
<b-tooltip :active="isAccountDisabled" :label="$t(setupAccountTooltip)" multilined size="is-large">
|
||||
<b-button :disabled="isAccountDisabled" outlined type="is-primary" @click="showSetupModal">{{
|
||||
$t('account.setup.account')
|
||||
}}</b-button>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<b-icon icon="account-recover" size="is-large" />
|
||||
<div class="desc">
|
||||
{{ $t('account.setup.recoverDesc') }}
|
||||
</div>
|
||||
<b-tooltip :active="isRecoverDisabled" :label="$t(recoverAccountTooltip)" multilined size="is-large">
|
||||
<b-button type="is-primary" outlined :disabled="isRecoverDisabled" @click="handleRecoverAccount">
|
||||
{{ $t('account.setup.recover') }}
|
||||
</b-button>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<b-icon icon="account-raw" size="is-large" />
|
||||
<div class="desc">
|
||||
{{ $t('account.setup.enterRawDesc') }}
|
||||
</div>
|
||||
<b-button type="is-primary" outlined @click="showRecoverKeyModal">{{
|
||||
$t('account.setup.enterRaw')
|
||||
}}</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setupComputed, setupMethods } from '../../injectors'
|
||||
import { openDecryptModal, openRecoverAccountModal, openSetupAccountModal } from '../../modals'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...setupComputed,
|
||||
isAccountDisabled() {
|
||||
return this.isExistAccount || !this.isLoggedIn
|
||||
},
|
||||
isRecoverDisabled() {
|
||||
return !this.isExistAccount || !this.isLoggedIn || this.isPartialSupport
|
||||
},
|
||||
recoverAccountTooltip() {
|
||||
if (this.isPartialSupport) {
|
||||
return 'mobileWallet.actions.disabled'
|
||||
}
|
||||
return this.isLoggedIn ? 'account.setup.recTooltip' : 'connectYourWalletFirst'
|
||||
},
|
||||
setupAccountTooltip() {
|
||||
return this.isLoggedIn ? 'account.setup.setTooltip' : 'connectYourWalletFirst'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...setupMethods,
|
||||
async getEncryptedNotes() {
|
||||
const props = await this.decryptNotes()
|
||||
|
||||
if (props) {
|
||||
openDecryptModal({ ...props, parent: this })
|
||||
}
|
||||
},
|
||||
showRecoverKeyModal() {
|
||||
openRecoverAccountModal({ parent: this, getNotes: this.getEncryptedNotes })
|
||||
},
|
||||
showSetupModal() {
|
||||
openSetupAccountModal({ parent: this })
|
||||
},
|
||||
async handleRecoverAccount() {
|
||||
try {
|
||||
this.enable({ message: this.$t('account.setup.decrypt') })
|
||||
await this.recoverAccountFromChain()
|
||||
await this.getEncryptedNotes()
|
||||
} catch {
|
||||
this.addNoticeWithInterval({
|
||||
notice: {
|
||||
title: 'decryptFailed',
|
||||
type: 'danger'
|
||||
},
|
||||
interval: 5000
|
||||
})
|
||||
} finally {
|
||||
this.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
24
modules/account/components/Setup/Header.vue
Normal file
24
modules/account/components/Setup/Header.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<div class="address">
|
||||
<div class="address-item">
|
||||
<div class="label">{{ $t('account.account') }}</div>
|
||||
<div class="value">{{ accounts.backup }}</div>
|
||||
</div>
|
||||
<div class="address-item">
|
||||
<div class="label">{{ $t('account.backedUpWith') }}</div>
|
||||
<div class="value">{{ accounts.encrypt }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { headerComputed } from '../../injectors'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...headerComputed
|
||||
},
|
||||
watch: {},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
18
modules/account/components/Setup/Setup.vue
Normal file
18
modules/account/components/Setup/Setup.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<div class="account-box">
|
||||
<Header />
|
||||
<Actions />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from './Header'
|
||||
import Actions from './Actions'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header,
|
||||
Actions
|
||||
}
|
||||
}
|
||||
</script>
|
1
modules/account/components/Setup/index.js
Normal file
1
modules/account/components/Setup/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as Setup } from './Setup'
|
5
modules/account/components/index.js
Normal file
5
modules/account/components/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export { Setup } from './Setup'
|
||||
export { Control } from './Control'
|
||||
export { Settings } from './Settings'
|
||||
export { Indicator } from './Indicator'
|
||||
export { NoteAccount } from './NoteAccount'
|
4
modules/account/dependencies/index.js
Normal file
4
modules/account/dependencies/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import Settings from '@/components/Settings'
|
||||
import NumberFormat from '@/components/NumberFormat'
|
||||
|
||||
export { Settings, NumberFormat }
|
2
modules/account/index.js
Normal file
2
modules/account/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './shared'
|
||||
export { default as AccountPage } from './Page'
|
10
modules/account/injectors/index.js
Normal file
10
modules/account/injectors/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
// components
|
||||
export * from './noteInjectors'
|
||||
export * from './setupInjectors'
|
||||
export * from './walletInjectors'
|
||||
export * from './сontrolInjectors'
|
||||
export * from './indicatorInjectors'
|
||||
// modals
|
||||
export * from './setupAccountInjectors'
|
||||
export * from './recoverAccountInjectors'
|
||||
export * from './showRecoveryKeyInjectors'
|
10
modules/account/injectors/indicatorInjectors.js
Normal file
10
modules/account/injectors/indicatorInjectors.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { mapGetters, mapActions } from 'vuex'
|
||||
|
||||
export const indicatorMethods = {
|
||||
...mapActions('encryptedNote', ['highlightNoteAccount', 'redirectToAccount'])
|
||||
}
|
||||
|
||||
export const indicatorComputed = {
|
||||
...mapGetters('metamask', ['currency']),
|
||||
...mapGetters('encryptedNote', ['accounts', 'isSetupAccount', 'noteAccountBalance'])
|
||||
}
|
11
modules/account/injectors/noteInjectors.js
Normal file
11
modules/account/injectors/noteInjectors.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { mapActions, mapGetters, mapState } from 'vuex'
|
||||
|
||||
export const noteMethods = {
|
||||
...mapActions('encryptedNote', ['checkExistAccount', 'highlightNoteAccount'])
|
||||
}
|
||||
|
||||
export const noteComputed = {
|
||||
...mapGetters('encryptedNote', ['isSetupAccount']),
|
||||
...mapState('metamask', ['isInitialized', 'netId']),
|
||||
...mapGetters('encryptedNote', ['isHighlightedNoteAccount'])
|
||||
}
|
7
modules/account/injectors/recoverAccountInjectors.js
Normal file
7
modules/account/injectors/recoverAccountInjectors.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export const recoverAccountMethods = mapActions('encryptedNote', ['clearState', 'recoverAccountFromKey'])
|
||||
|
||||
export const recoverAccountComputed = {
|
||||
...mapGetters('encryptedNote', ['recoverAccountFromKeyRequest'])
|
||||
}
|
10
modules/account/injectors/setupAccountInjectors.js
Normal file
10
modules/account/injectors/setupAccountInjectors.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export const setupAccountMethods = {
|
||||
...mapActions('notice', ['addNoticeWithInterval']),
|
||||
...mapActions('encryptedNote', ['clearState', 'setupAccount'])
|
||||
}
|
||||
|
||||
export const setupAccountComputed = {
|
||||
...mapGetters('encryptedNote', ['setupAccountRequest'])
|
||||
}
|
20
modules/account/injectors/setupInjectors.js
Normal file
20
modules/account/injectors/setupInjectors.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { mapActions, mapGetters, mapState } from 'vuex'
|
||||
|
||||
export const setupMethods = {
|
||||
...mapActions('loading', ['enable', 'disable']),
|
||||
...mapActions('notice', ['addNoticeWithInterval']),
|
||||
...mapActions('encryptedNote', [
|
||||
'clearState',
|
||||
'decryptNotes',
|
||||
'setupAccount',
|
||||
'recoverAccountFromKey',
|
||||
'saveRecoveryKeyOnFile',
|
||||
'recoverAccountFromChain'
|
||||
])
|
||||
}
|
||||
|
||||
export const setupComputed = {
|
||||
...mapState('metamask', ['isInitialized', 'providerName']),
|
||||
...mapGetters('metamask', ['isLoggedIn', 'isPartialSupport']),
|
||||
...mapGetters('encryptedNote', ['isExistAccount', 'setupAccountRequest'])
|
||||
}
|
5
modules/account/injectors/showRecoveryKeyInjectors.js
Normal file
5
modules/account/injectors/showRecoveryKeyInjectors.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { mapActions } from 'vuex'
|
||||
|
||||
export const showRecoveryKeyMethods = {
|
||||
...mapActions('notice', ['addNoticeWithInterval'])
|
||||
}
|
10
modules/account/injectors/walletInjectors.js
Normal file
10
modules/account/injectors/walletInjectors.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { mapGetters, mapState, mapActions } from 'vuex'
|
||||
|
||||
export const walletComputed = {
|
||||
...mapState('metamask', ['ethAccount']),
|
||||
...mapGetters('metamask', ['netId', 'isLoggedIn'])
|
||||
}
|
||||
|
||||
export const walletActions = {
|
||||
...mapActions('metamask', ['onLogOut'])
|
||||
}
|
19
modules/account/injectors/сontrolInjectors.js
Normal file
19
modules/account/injectors/сontrolInjectors.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export const controlMethods = {
|
||||
...mapActions('notice', ['addNoticeWithInterval']),
|
||||
...mapActions('encryptedNote', ['decryptNotes', 'removeAccount', 'enabledSaveFile', 'getRecoveryKey'])
|
||||
}
|
||||
|
||||
export const controlComputed = {
|
||||
...mapGetters('encryptedNote', ['isEnabledSaveFile', 'isSetupAccount'])
|
||||
}
|
||||
|
||||
export const statisticComputed = {
|
||||
...mapGetters('encryptedNote', ['statistic']),
|
||||
...mapGetters('token', ['getSymbol'])
|
||||
}
|
||||
|
||||
export const headerComputed = {
|
||||
...mapGetters('encryptedNote', ['accounts'])
|
||||
}
|
84
modules/account/modals/DecryptInfo.vue
Normal file
84
modules/account/modals/DecryptInfo.vue
Normal file
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="modal-card box box-modal">
|
||||
<header class="box-modal-header is-spaced">
|
||||
<div class="box-modal-title">{{ $parent.$t('account.modals.decryptInfo.title') }}</div>
|
||||
<button type="button" class="delete" @click="$emit('close')" />
|
||||
</header>
|
||||
<div class="note">{{ $parent.$t('account.modals.decryptInfo.description') }}</div>
|
||||
<div class="account-decrypt-info">
|
||||
<div class="item">
|
||||
{{ $parent.$t('account.modals.decryptInfo.spent') }}
|
||||
<span class="has-text-weight-bold mr-3">{{ spent }}</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
{{ $parent.$t('account.modals.decryptInfo.unSpent') }}
|
||||
<span class="has-text-weight-bold mr-3">{{ unSpent }}</span>
|
||||
</div>
|
||||
<template v-for="(instances, currency) in getStatistic">
|
||||
<div v-for="(amount, instance) in instances" :key="`${amount}_${currency}_${instance}`" class="item">
|
||||
{{ instance }} {{ getSymbol(currency) }}:
|
||||
<span class="has-text-weight-bold mr-3">{{ amount }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="buttons buttons__halfwidth mt-3">
|
||||
<b-button type="is-primary" outlined @click="onClose">
|
||||
{{ $parent.$t('account.modals.decryptInfo.close') }}
|
||||
</b-button>
|
||||
<b-button type="is-primary" @click="handleRedirect">
|
||||
{{ $parent.$t('account.modals.decryptInfo.redirect') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { statisticComputed } from '../injectors'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
all: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
spent: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
unSpent: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
...statisticComputed,
|
||||
getStatistic() {
|
||||
const balance = this.statistic.reduce((acc, { currency, amount }) => {
|
||||
if (acc[currency] && acc[currency][amount]) {
|
||||
acc[currency][amount] += 1
|
||||
} else {
|
||||
acc[currency] = {
|
||||
...acc[currency],
|
||||
[amount]: 1
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return balance
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
handleRedirect() {
|
||||
this.$router.push('/')
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
90
modules/account/modals/RecoverAccount.vue
Normal file
90
modules/account/modals/RecoverAccount.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div class="modal-card box box-modal">
|
||||
<header class="box-modal-header is-spaced">
|
||||
<div class="box-modal-title">{{ $t('account.modals.recoverAccount.title') }}</div>
|
||||
<button type="button" class="delete" @click="$emit('close')" />
|
||||
</header>
|
||||
<div class="note">
|
||||
{{ $t('account.modals.recoverAccount.description') }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-input
|
||||
v-model="recoveryKey"
|
||||
type="textarea"
|
||||
class="is-disabled-resize"
|
||||
rows="2"
|
||||
:placeholder="$t('enterRecoveryKey')"
|
||||
:class="{ 'is-warning': hasAndValidKey }"
|
||||
@input="onInput"
|
||||
></b-input>
|
||||
<p v-show="hasAndValidKey" class="help is-warning">
|
||||
{{ $t('account.modals.recoverAccount.warning') }}
|
||||
</p>
|
||||
</div>
|
||||
<b-notification
|
||||
v-if="recoverAccountFromKeyRequest.isError"
|
||||
class="main-notification"
|
||||
type="is-warning"
|
||||
:closable="false"
|
||||
>
|
||||
{{ recoverAccountFromKeyRequest.errorMessage }}
|
||||
</b-notification>
|
||||
<b-button
|
||||
type="is-primary is-fullwidth"
|
||||
:disabled="hasAndValidKey"
|
||||
:loading="recoverAccountFromKeyRequest.isFetching"
|
||||
@click="handleRecoverAccount"
|
||||
>
|
||||
{{ $t('account.modals.recoverAccount.connect') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { recoverAccountComputed, recoverAccountMethods } from '../injectors'
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
getNotes: {
|
||||
required: true,
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
recoveryKey: '',
|
||||
isValidRecoveryKey: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...recoverAccountComputed,
|
||||
hasAndValidKey() {
|
||||
return this.recoveryKey && !this.isValidRecoveryKey
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearState({ key: 'recoverAccountFromKey' })
|
||||
},
|
||||
methods: {
|
||||
...recoverAccountMethods,
|
||||
async handleRecoverAccount() {
|
||||
await this.recoverAccountFromKey({ recoveryKey: this.recoveryKey })
|
||||
this.$emit('close')
|
||||
await this.getNotes()
|
||||
},
|
||||
onInput(recoveryKey) {
|
||||
this.clearState({ key: 'recoverAccountFromKey' })
|
||||
debounce(this.checkPrivateKey, recoveryKey)
|
||||
},
|
||||
checkPrivateKey(recoveryKey) {
|
||||
try {
|
||||
this.$provider.web3.eth.accounts.privateKeyToAccount(recoveryKey)
|
||||
this.isValidRecoveryKey = true
|
||||
} catch {
|
||||
this.isValidRecoveryKey = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
71
modules/account/modals/SessionUpdate.vue
Normal file
71
modules/account/modals/SessionUpdate.vue
Normal file
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<div class="modal-card-title">{{ $parent.$t('account.modals.checkRecoveryKey.title') }}</div>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="media">
|
||||
<div class="media-content">
|
||||
{{
|
||||
isShow
|
||||
? $parent.$t('account.modals.checkRecoveryKey.inactiveDescription')
|
||||
: $parent.$t('account.modals.checkRecoveryKey.description')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer v-if="isShow" class="modal-card-foot">
|
||||
<b-button type="is-primary" outlined @click="$emit('close')">
|
||||
{{ $parent.$t('close') }}
|
||||
</b-button>
|
||||
</footer>
|
||||
<footer v-else class="modal-card-foot">
|
||||
<b-button type="is-primary" outlined @click="_onCancel">
|
||||
{{ $parent.$t('account.modals.checkRecoveryKey.no') }}
|
||||
</b-button>
|
||||
<b-button type="is-primary" @click="_onConfirm">
|
||||
{{ $parent.$t('account.modals.checkRecoveryKey.yes') }}
|
||||
</b-button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
onCancel: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
onConfirm: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
isShow: false
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearTimeout(this.timer)
|
||||
},
|
||||
mounted() {
|
||||
this.timer = setTimeout(() => {
|
||||
this.onCancel()
|
||||
this.isShow = true
|
||||
}, 1000 * 60)
|
||||
},
|
||||
methods: {
|
||||
_onCancel() {
|
||||
this.onCancel()
|
||||
this.$emit('close')
|
||||
},
|
||||
_onConfirm() {
|
||||
this.onConfirm()
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
134
modules/account/modals/SetupAccount.vue
Normal file
134
modules/account/modals/SetupAccount.vue
Normal file
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<div class="modal-card box box-modal">
|
||||
<header class="box-modal-header is-spaced">
|
||||
<div class="box-modal-title">{{ $t('account.modals.setupAccount.title') }}</div>
|
||||
<button type="button" class="delete" @click="$emit('close')" />
|
||||
</header>
|
||||
<div class="note">{{ $t('account.modals.setupAccount.description') }}</div>
|
||||
<div class="field">
|
||||
<div class="label-with-buttons">
|
||||
<div class="label">{{ $t('account.modals.setupAccount.label') }}</div>
|
||||
<b-button v-clipboard:copy="recoveryKey" v-clipboard:success="onCopy" type="is-primary-text">
|
||||
{{ $t('copy') }}
|
||||
</b-button>
|
||||
</div>
|
||||
<div class="notice is-recovery-key">
|
||||
<div class="notice__p">{{ recoveryKey }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<b-notification class="main-notification" type="is-info" :closable="false">
|
||||
{{ $t('account.modals.setupAccount.isNotSupportedWithHw') }}
|
||||
</b-notification>
|
||||
<b-checkbox v-model="isSaveOnChain" :disabled="isPartialSupport">{{
|
||||
$t('account.modals.setupAccount.saveOnChain')
|
||||
}}</b-checkbox>
|
||||
<b-checkbox v-if="!isSaveOnChain" v-model="isBackuped">{{
|
||||
$t('account.modals.setupAccount.backedUp')
|
||||
}}</b-checkbox>
|
||||
<b-notification
|
||||
v-if="!isSaveOnChain && warningMessage"
|
||||
class="main-notification"
|
||||
type="is-warning"
|
||||
:closable="false"
|
||||
>
|
||||
{{ warningMessage }}
|
||||
</b-notification>
|
||||
<b-notification
|
||||
v-if="setupAccountRequest.isError"
|
||||
class="main-notification"
|
||||
type="is-warning"
|
||||
:closable="false"
|
||||
>
|
||||
{{ setupAccountRequest.errorMessage }}
|
||||
</b-notification>
|
||||
<b-button
|
||||
v-if="!isBackuped && isSaveOnChain"
|
||||
type="is-primary is-fullwidth"
|
||||
:loading="setupAccountRequest.isFetching"
|
||||
@click="onSetupAccount"
|
||||
>
|
||||
{{ $t('account.modals.setupAccount.setupAccount') }}
|
||||
</b-button>
|
||||
<b-button v-else type="is-primary is-fullwidth" :disabled="!isBackuped" @click="setAccount">
|
||||
{{ $t('account.modals.setupAccount.setAccount') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setupMethods, setupComputed } from '../injectors'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
recoveryKey: '',
|
||||
isBackuped: false,
|
||||
isSaveOnChain: true,
|
||||
warningMessage: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...setupComputed
|
||||
},
|
||||
watch: {
|
||||
isSaveOnChain() {
|
||||
if (this.isSaveOnChain) {
|
||||
this.isBackuped = false
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUpdate() {
|
||||
if (this.setupAccountRequest.isSuccess) {
|
||||
this.$parent.close()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.recoveryKey = this.$provider.web3.eth.accounts.create().privateKey.slice(2)
|
||||
|
||||
if (this.isPartialSupport) {
|
||||
this.isSaveOnChain = false
|
||||
}
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
this.saveRecoveryKeyOnFile({ recoveryKey: this.recoveryKey })
|
||||
}, 1500)
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearTimeout(this.timer)
|
||||
this.clearState({ key: 'setupAccount' })
|
||||
},
|
||||
methods: {
|
||||
...setupMethods,
|
||||
onCopy() {
|
||||
this.addNoticeWithInterval({
|
||||
notice: {
|
||||
title: 'copied',
|
||||
type: 'info'
|
||||
},
|
||||
interval: 2000
|
||||
})
|
||||
},
|
||||
async setAccount() {
|
||||
try {
|
||||
await this.recoverAccountFromKey({ recoveryKey: this.recoveryKey })
|
||||
this.$emit('close')
|
||||
} catch (err) {
|
||||
this.warningMessage = err.message
|
||||
}
|
||||
},
|
||||
async onSetupAccount() {
|
||||
if (!this.isSaveOnChain) {
|
||||
this.warningMessage = this.$t('account.modals.setupAccount.yourRecoveryKeyWontBeSaved')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.setupAccount({ privateKey: this.recoveryKey })
|
||||
} catch (err) {
|
||||
this.warningMessage = err.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
55
modules/account/modals/ShowRecoverKey.vue
Normal file
55
modules/account/modals/ShowRecoverKey.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="modal-card box box-modal">
|
||||
<header class="box-modal-header is-spaced">
|
||||
<div class="box-modal-title">{{ $t('account.modals.showRecoveryKey.title') }}</div>
|
||||
<button type="button" class="delete" @click="$emit('close')" />
|
||||
</header>
|
||||
<div class="note">{{ $t('account.modals.showRecoveryKey.description') }}</div>
|
||||
<div class="field">
|
||||
<div class="label-with-buttons">
|
||||
<div class="label"></div>
|
||||
<b-button v-clipboard:copy="recoveryKey" v-clipboard:success="onCopy" type="is-primary-text">
|
||||
{{ $t('copy') }}
|
||||
</b-button>
|
||||
</div>
|
||||
<div class="notice is-recovery-key">
|
||||
<div class="notice__p">{{ recoveryKey }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<b-button type="is-primary" outlined @click="onClose">
|
||||
{{ $t('account.modals.showRecoveryKey.close') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { showRecoveryKeyMethods } from '../injectors'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
recoveryKey: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
...showRecoveryKeyMethods,
|
||||
onClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
onCopy() {
|
||||
this.addNoticeWithInterval({
|
||||
notice: {
|
||||
title: 'copied',
|
||||
type: 'info'
|
||||
},
|
||||
interval: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
66
modules/account/modals/index.js
Normal file
66
modules/account/modals/index.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { ModalProgrammatic, DialogProgrammatic } from 'buefy'
|
||||
|
||||
import { Settings } from '../dependencies'
|
||||
|
||||
import DecryptInfo from './DecryptInfo.vue'
|
||||
import SetupAccount from './SetupAccount.vue'
|
||||
import SessionUpdate from './SessionUpdate.vue'
|
||||
import RecoverAccount from './RecoverAccount.vue'
|
||||
import ShowRecoverKey from './ShowRecoverKey.vue'
|
||||
|
||||
const openSettingsModal = ({ parent, ...props }) => {
|
||||
createModal({ props, parent, component: Settings })
|
||||
}
|
||||
|
||||
const openSetupAccountModal = ({ parent, ...props }) => {
|
||||
createModal({ props, parent, component: SetupAccount, canCancel: false })
|
||||
}
|
||||
|
||||
const openDecryptModal = ({ parent, ...props }) => {
|
||||
createModal({ props, parent, component: DecryptInfo })
|
||||
}
|
||||
|
||||
const openRecoverAccountModal = ({ parent, ...props }) => {
|
||||
createModal({ props, parent, component: RecoverAccount })
|
||||
}
|
||||
|
||||
const openShowRecoverKeyModal = ({ parent, ...props }) => {
|
||||
createModal({ props, parent, component: ShowRecoverKey })
|
||||
}
|
||||
|
||||
function createModal({ component, props, parent, ...rest }) {
|
||||
ModalProgrammatic.open({
|
||||
props,
|
||||
parent,
|
||||
component,
|
||||
width: 440,
|
||||
hasModalCard: true,
|
||||
customClass: 'is-pinned',
|
||||
...rest
|
||||
})
|
||||
}
|
||||
|
||||
const openRemoveAccountModal = ({ i18n, onConfirm }) => {
|
||||
DialogProgrammatic.confirm({
|
||||
onConfirm,
|
||||
title: i18n.t('account.modals.removeAccount.title'),
|
||||
type: 'is-primary is-outlined',
|
||||
message: i18n.t('account.modals.removeAccount.description'),
|
||||
cancelText: i18n.t('account.modals.removeAccount.cancel'),
|
||||
confirmText: i18n.t('account.modals.removeAccount.remove')
|
||||
})
|
||||
}
|
||||
|
||||
const openConfirmModal = ({ parent, ...props }) => {
|
||||
createModal({ props, parent, component: SessionUpdate, customClass: 'dialog' })
|
||||
}
|
||||
|
||||
export {
|
||||
openDecryptModal,
|
||||
openConfirmModal,
|
||||
openSettingsModal,
|
||||
openSetupAccountModal,
|
||||
openRemoveAccountModal,
|
||||
openShowRecoverKeyModal,
|
||||
openRecoverAccountModal
|
||||
}
|
2
modules/account/shared/index.js
Normal file
2
modules/account/shared/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { Indicator } from '../components'
|
||||
export { openConfirmModal } from '../modals'
|
77
modules/account/store/actions/checkExistAccount.js
Normal file
77
modules/account/store/actions/checkExistAccount.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { graph } from '@/services'
|
||||
|
||||
export async function checkExistAccount({ getters, dispatch, rootState, rootGetters }) {
|
||||
const { ethAccount, netId } = rootState.metamask
|
||||
|
||||
if (!ethAccount) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const rpc = rootGetters['settings/currentRpc']
|
||||
const web3 = this.$provider.getWeb3(rpc.url)
|
||||
const currentBlockNumber = await web3.eth.getBlockNumber()
|
||||
|
||||
const isExist = await getEventsFromBlockPart(
|
||||
{ getters, dispatch, rootState, rootGetters },
|
||||
{ netId, currentBlockNumber, address: ethAccount }
|
||||
)
|
||||
console.log('isExist', isExist)
|
||||
|
||||
dispatch('createMutation', {
|
||||
type: 'CHECK_ACCOUNT',
|
||||
payload: { isExist }
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error(`Method checkExistAccount has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getEventsFromBlockPart(
|
||||
{ getters, rootGetters, dispatch },
|
||||
{ address, currentBlockNumber, netId }
|
||||
) {
|
||||
try {
|
||||
const { events: graphEvents, lastSyncBlock } = await graph.getNoteAccounts({ address, netId })
|
||||
|
||||
const blockDifference = Math.ceil(currentBlockNumber - lastSyncBlock)
|
||||
let blockRange = 1
|
||||
|
||||
if (Number(netId) === 56) {
|
||||
blockRange = 4950
|
||||
}
|
||||
|
||||
let numberParts = blockDifference === 0 ? 1 : Math.ceil(blockDifference / blockRange)
|
||||
const part = Math.ceil(blockDifference / numberParts)
|
||||
|
||||
let events = []
|
||||
|
||||
let fromBlock = lastSyncBlock
|
||||
let toBlock = lastSyncBlock + part
|
||||
|
||||
if (toBlock >= currentBlockNumber) {
|
||||
toBlock = 'latest'
|
||||
numberParts = 1
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberParts; i++) {
|
||||
const partOfEvents = await getters.echoContract.getEvents({
|
||||
fromBlock,
|
||||
toBlock,
|
||||
address
|
||||
})
|
||||
if (partOfEvents) {
|
||||
events = events.concat(partOfEvents)
|
||||
}
|
||||
fromBlock = toBlock
|
||||
toBlock += part
|
||||
}
|
||||
|
||||
events = graphEvents.concat(events)
|
||||
|
||||
return Boolean(Array.isArray(events) && Boolean(events.length))
|
||||
} catch (err) {
|
||||
console.log(`getEventsFromBlock has error: ${err.message}`)
|
||||
return false
|
||||
}
|
||||
}
|
8
modules/account/store/actions/checkRecoveryKey.js
Normal file
8
modules/account/store/actions/checkRecoveryKey.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export function checkRecoveryKey({ getters, dispatch }) {
|
||||
const { encrypt: address } = getters.accounts
|
||||
const recoveryKey = this.$sessionStorage.getItem(address)
|
||||
|
||||
if (!recoveryKey && !getters.encryptedPrivateKey) {
|
||||
dispatch('removeAccount')
|
||||
}
|
||||
}
|
16
modules/account/store/actions/decryptNote.js
Normal file
16
modules/account/store/actions/decryptNote.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { decrypt } from 'eth-sig-util'
|
||||
|
||||
import { unpackEncryptedMessage } from '@/utils'
|
||||
|
||||
export async function decryptNote({ dispatch }, encryptedNote) {
|
||||
try {
|
||||
const recoveryKey = await dispatch('getRecoveryKey')
|
||||
|
||||
const unpackedMessage = unpackEncryptedMessage(encryptedNote)
|
||||
const [, note] = decrypt(unpackedMessage, recoveryKey).split('-')
|
||||
|
||||
return note
|
||||
} catch (err) {
|
||||
console.warn(`Method decryptNote has error: ${err.message}`)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export function _checkCurrentTx({ rootGetters }, transactions) {
|
||||
const currentTransactions = rootGetters['txHashKeeper/allTxsHash']
|
||||
const newTransactions = transactions.filter((event) => !currentTransactions.includes(event.txHash))
|
||||
|
||||
return newTransactions
|
||||
}
|
207
modules/account/store/actions/decryptNotes/encryptFormatTx.js
Normal file
207
modules/account/store/actions/decryptNotes/encryptFormatTx.js
Normal file
|
@ -0,0 +1,207 @@
|
|||
import { decrypt } from 'eth-sig-util'
|
||||
import { isAddress } from 'web3-utils'
|
||||
|
||||
import { eventsType } from '@/constants'
|
||||
import { parseHexNote, getInstanceByAddress, unpackEncryptedMessage } from '@/utils'
|
||||
|
||||
export async function _encryptFormatTx({ dispatch, getters, rootGetters }, { events, privateKey }) {
|
||||
let result = []
|
||||
|
||||
const netId = rootGetters['metamask/netId']
|
||||
const eventsInterface = rootGetters['application/eventsInterface']
|
||||
|
||||
dispatch('loading/changeText', { message: this.app.i18n.t('decryptingNotes') }, { root: true })
|
||||
|
||||
const encryptedEvents = decryptEvents({ events, privateKey })
|
||||
|
||||
dispatch(
|
||||
'loading/changeText',
|
||||
{ message: this.app.i18n.t('getAndValidateEvents', { name: this.app.i18n.t('deposit') }) },
|
||||
{ root: true }
|
||||
)
|
||||
|
||||
const instances = encryptedEvents.reduce((acc, curr) => {
|
||||
const instance = getInstanceByAddress({ netId, address: curr.address })
|
||||
if (!instance) {
|
||||
return acc
|
||||
}
|
||||
const name = `${instance.amount}${instance.currency}`
|
||||
if (!acc[name]) {
|
||||
const service = eventsInterface.getService({ netId, ...instance })
|
||||
acc[name] = { ...instance, service }
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
await Promise.all(
|
||||
[].concat(
|
||||
Object.values(instances).map((instance) => instance.service.updateEvents(eventsType.DEPOSIT)),
|
||||
Object.values(instances).map((instance) => instance.service.updateEvents(eventsType.WITHDRAWAL))
|
||||
)
|
||||
)
|
||||
|
||||
const eventBatches = getBatches(encryptedEvents)
|
||||
|
||||
for await (const batch of eventBatches) {
|
||||
try {
|
||||
const depositPromises = batch.map((event) => {
|
||||
const instance = getInstanceByAddress({ netId, address: event.address })
|
||||
if (!instance) {
|
||||
return
|
||||
}
|
||||
const { service } = instances[`${instance.amount}${instance.currency}`]
|
||||
return getDeposit({ event, netId, service, instance })
|
||||
})
|
||||
|
||||
const proceedDeposits = await Promise.all(depositPromises)
|
||||
console.log({ proceedDeposits })
|
||||
|
||||
dispatch(
|
||||
'loading/changeText',
|
||||
{ message: this.app.i18n.t('getAndValidateEvents', { name: this.app.i18n.t('withdrawal') }) },
|
||||
{ root: true }
|
||||
)
|
||||
|
||||
const proceedEvents = await Promise.all(
|
||||
proceedDeposits.map(([event, deposit]) => proceedEvent({ event, getters, deposit, netId, dispatch }))
|
||||
)
|
||||
|
||||
result = result.concat(proceedEvents)
|
||||
} catch (e) {
|
||||
console.error('_encryptFormatTx', e)
|
||||
}
|
||||
}
|
||||
|
||||
return formattingEvents(result)
|
||||
}
|
||||
|
||||
function decryptEvents({ privateKey, events }) {
|
||||
const encryptEvents = []
|
||||
|
||||
for (const event of events) {
|
||||
try {
|
||||
const unpackedMessage = unpackEncryptedMessage(event.encryptedNote)
|
||||
|
||||
const [address, note] = decrypt(unpackedMessage, privateKey).split('-')
|
||||
encryptEvents.push({ address, note, ...event })
|
||||
} catch {
|
||||
// decryption may fail for foreign notes
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return encryptEvents
|
||||
}
|
||||
|
||||
function formattingEvents(proceedEvents) {
|
||||
const result = []
|
||||
const statistic = []
|
||||
let unSpent = 0
|
||||
|
||||
proceedEvents.forEach((transaction) => {
|
||||
if (transaction) {
|
||||
if (!transaction.isSpent) {
|
||||
unSpent += 1
|
||||
statistic.push({
|
||||
amount: transaction.amount,
|
||||
currency: transaction.currency
|
||||
})
|
||||
}
|
||||
|
||||
result.push(transaction)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
unSpent,
|
||||
statistic,
|
||||
transactions: result
|
||||
}
|
||||
}
|
||||
|
||||
async function getDeposit({ netId, event, service, instance }) {
|
||||
const { commitmentHex, nullifierHex } = parseHexNote(event.note)
|
||||
|
||||
const foundEvent = await service.findEvent({
|
||||
eventName: 'commitment',
|
||||
eventToFind: commitmentHex,
|
||||
type: eventsType.DEPOSIT
|
||||
})
|
||||
|
||||
if (!foundEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
const isSpent = await service.findEvent({
|
||||
eventName: 'nullifierHash',
|
||||
eventToFind: nullifierHex,
|
||||
type: eventsType.WITHDRAWAL
|
||||
})
|
||||
|
||||
const deposit = {
|
||||
leafIndex: foundEvent.leafIndex,
|
||||
timestamp: foundEvent.timestamp,
|
||||
txHash: foundEvent.transactionHash,
|
||||
depositBlock: foundEvent.blockNumber
|
||||
}
|
||||
|
||||
return [
|
||||
event,
|
||||
{
|
||||
nullifierHex,
|
||||
commitmentHex,
|
||||
amount: instance.amount,
|
||||
isSpent: Boolean(isSpent),
|
||||
currency: instance.currency,
|
||||
prefix: `tornado-${instance.currency}-${instance.amount}-${netId}`,
|
||||
...deposit
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async function proceedEvent({ dispatch, getters, deposit, netId, event: { note, address, ...event } }) {
|
||||
const { encrypt, backup } = getters.accounts
|
||||
|
||||
try {
|
||||
const { depositBlock, ...rest } = deposit
|
||||
|
||||
const transaction = {
|
||||
...rest,
|
||||
netId,
|
||||
status: 2,
|
||||
type: 'Deposit',
|
||||
txHash: event.txHash,
|
||||
owner: isAddress(encrypt) ? encrypt : '',
|
||||
backupAccount: isAddress(backup) ? backup : '',
|
||||
index: deposit.leafIndex,
|
||||
storeType: 'encryptedTxs',
|
||||
blockNumber: event.blockNumber,
|
||||
note: event.encryptedNote
|
||||
}
|
||||
|
||||
if (deposit && deposit.isSpent) {
|
||||
const withdrawEvent = await dispatch(
|
||||
'application/loadWithdrawalEvent',
|
||||
{ withdrawNote: `${deposit.prefix}-${note}` },
|
||||
{ root: true }
|
||||
)
|
||||
if (withdrawEvent) {
|
||||
transaction.txHash = withdrawEvent.txHash
|
||||
transaction.depositBlock = depositBlock
|
||||
transaction.blockNumber = withdrawEvent.blockNumber
|
||||
}
|
||||
}
|
||||
|
||||
return transaction
|
||||
} catch (err) {
|
||||
console.log('err', err.message)
|
||||
}
|
||||
}
|
||||
|
||||
function getBatches(arr, batchSize = 100) {
|
||||
const batches = []
|
||||
while (arr.length) {
|
||||
batches.push(arr.splice(0, batchSize))
|
||||
}
|
||||
return batches
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
export async function decryptNotes({ commit, dispatch }) {
|
||||
try {
|
||||
dispatch('loading/enable', { message: this.app.i18n.t('startDecryptingNotes') }, { root: true })
|
||||
|
||||
const privateKey = await dispatch('getRecoveryKey', false)
|
||||
|
||||
if (!privateKey) {
|
||||
return
|
||||
}
|
||||
|
||||
const events = await dispatch('application/getEncryptedNotes', {}, { root: true })
|
||||
|
||||
const { transactions, statistic, unSpent } = await dispatch('_encryptFormatTx', { events, privateKey })
|
||||
|
||||
const checkedTxs = await dispatch('_checkCurrentTx', transactions)
|
||||
|
||||
checkedTxs.forEach((tx) => {
|
||||
commit('txHashKeeper/SAVE_TX_HASH', tx, { root: true })
|
||||
})
|
||||
|
||||
dispatch('createMutation', { type: 'SET_STATISTIC', payload: { statistic } })
|
||||
|
||||
return {
|
||||
unSpent,
|
||||
spent: checkedTxs.length ? checkedTxs.length - unSpent : 0,
|
||||
all: events.length ? events.length - 1 : 0
|
||||
}
|
||||
} catch (err) {
|
||||
dispatch('createMutation', {
|
||||
type: 'SET_DOMAIN_FAILED',
|
||||
payload: { key: 'decryptNotes', errorMessage: err.message }
|
||||
})
|
||||
} finally {
|
||||
dispatch('loading/disable', {}, { root: true })
|
||||
}
|
||||
}
|
4
modules/account/store/actions/decryptNotes/index.js
Normal file
4
modules/account/store/actions/decryptNotes/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
export { decryptNotes } from './getDecryptNotes'
|
||||
// helpers
|
||||
export { _checkCurrentTx } from './checkCurrentTx'
|
||||
export { _encryptFormatTx } from './encryptFormatTx'
|
6
modules/account/store/actions/enabledSaveFile.js
Normal file
6
modules/account/store/actions/enabledSaveFile.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export function enabledSaveFile({ dispatch, getters }) {
|
||||
dispatch('createMutation', {
|
||||
type: 'ENABLED_SAVE_FILE',
|
||||
payload: { isEnabled: !getters.isEnabledSaveFile }
|
||||
})
|
||||
}
|
22
modules/account/store/actions/getEncryptedAccount.js
Normal file
22
modules/account/store/actions/getEncryptedAccount.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { encrypt, getEncryptionPublicKey } from 'eth-sig-util'
|
||||
|
||||
export function getEncryptedAccount(_, { privateKey, pubKey }) {
|
||||
try {
|
||||
const { address } = this.$provider.web3.eth.accounts.privateKeyToAccount(privateKey)
|
||||
const keyWithOutPrefix = privateKey.slice(0, 2) === '0x' ? privateKey.replace('0x', '') : privateKey
|
||||
|
||||
const publicKey = getEncryptionPublicKey(keyWithOutPrefix)
|
||||
|
||||
const encryptedData = encrypt(pubKey, { data: keyWithOutPrefix }, 'x25519-xsalsa20-poly1305')
|
||||
const hexPrivateKey = Buffer.from(JSON.stringify(encryptedData)).toString('hex')
|
||||
|
||||
return {
|
||||
address,
|
||||
publicKey,
|
||||
hexPrivateKey,
|
||||
encryptedData
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Method getEncryptedAccount has error: ${err.message}`)
|
||||
}
|
||||
}
|
19
modules/account/store/actions/getEncryptedNote.js
Normal file
19
modules/account/store/actions/getEncryptedNote.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { encrypt } from 'eth-sig-util'
|
||||
|
||||
import { packEncryptedMessage } from '@/utils'
|
||||
|
||||
export function getEncryptedNote({ getters }, { data }) {
|
||||
try {
|
||||
const encryptedPublicKey = getters.encryptedPublicKey
|
||||
|
||||
if (!encryptedPublicKey) {
|
||||
return
|
||||
}
|
||||
|
||||
const encryptedData = encrypt(encryptedPublicKey, { data }, 'x25519-xsalsa20-poly1305')
|
||||
|
||||
return packEncryptedMessage(encryptedData)
|
||||
} catch (err) {
|
||||
throw new Error(`Method getEncryptedNote has error: ${err.message}`)
|
||||
}
|
||||
}
|
71
modules/account/store/actions/getRecoveryKey.js
Normal file
71
modules/account/store/actions/getRecoveryKey.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { isAddress } from 'web3-utils'
|
||||
import { sliceAddress } from '@/utils'
|
||||
|
||||
export async function getRecoveryKey({ dispatch, getters, rootState }, enableLoader = true) {
|
||||
try {
|
||||
const { encrypt: address } = getters.accounts
|
||||
const recoverKey = this.$sessionStorage.getItem(address)
|
||||
|
||||
if (recoverKey) {
|
||||
return recoverKey.data
|
||||
}
|
||||
|
||||
const hasError = _checkBackupAccount({ rootState, dispatch, getters, i18n: this.app.i18n })
|
||||
|
||||
if (hasError) {
|
||||
return
|
||||
}
|
||||
|
||||
const encryptedPrivateKey = getters.encryptedPrivateKey
|
||||
dispatch('loading/enable', { message: this.app.i18n.t('decryptNote') }, { root: true })
|
||||
const privateKey = await dispatch('metamask/ethDecrypt', encryptedPrivateKey, { root: true })
|
||||
|
||||
this.$sessionStorage.setItem(address, privateKey)
|
||||
|
||||
return privateKey
|
||||
} catch (err) {
|
||||
const isRejected = err.message.includes('MetaMask Decryption: User denied message decryption.')
|
||||
|
||||
const notice = {
|
||||
title: 'decryptFailed',
|
||||
type: 'danger'
|
||||
}
|
||||
|
||||
if (isRejected) {
|
||||
notice.title = 'rejectedRequest'
|
||||
notice.description = rootState.metamask.walletName
|
||||
}
|
||||
|
||||
dispatch('notice/addNoticeWithInterval', { notice, interval: 5000 }, { root: true })
|
||||
} finally {
|
||||
if (enableLoader) {
|
||||
dispatch('loading/disable', {}, { root: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _checkBackupAccount(ctx) {
|
||||
const { ethAccount } = ctx.rootState.metamask
|
||||
|
||||
if (!ethAccount) {
|
||||
const { backup, encrypt } = ctx.getters.accounts
|
||||
|
||||
if (isAddress(backup)) {
|
||||
ctx.dispatch(
|
||||
'notice/addNoticeWithInterval',
|
||||
{
|
||||
notice: {
|
||||
untranslatedTitle: ctx.i18n.t('noteAccountKey', {
|
||||
address: sliceAddress(backup),
|
||||
noteAddress: sliceAddress(encrypt)
|
||||
}),
|
||||
type: 'danger'
|
||||
},
|
||||
interval: 10000
|
||||
},
|
||||
{ root: true }
|
||||
)
|
||||
return 'error'
|
||||
}
|
||||
}
|
||||
}
|
3
modules/account/store/actions/highlightNoteAccount.js
Normal file
3
modules/account/store/actions/highlightNoteAccount.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function highlightNoteAccount({ dispatch }, { isHighlighted }) {
|
||||
dispatch('createMutation', { type: 'SET_HIGHLIGHT_NOTE_ACCOUNT', payload: { isHighlighted } })
|
||||
}
|
51
modules/account/store/actions/index.js
Normal file
51
modules/account/store/actions/index.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { decryptNote } from './decryptNote'
|
||||
import { decryptNotes, _encryptFormatTx, _checkCurrentTx } from './decryptNotes'
|
||||
|
||||
import { saveAccount } from './saveAccount'
|
||||
import { removeAccount } from './removeAccount'
|
||||
import { getRecoveryKey } from './getRecoveryKey'
|
||||
import { enabledSaveFile } from './enabledSaveFile'
|
||||
import { checkRecoveryKey } from './checkRecoveryKey'
|
||||
|
||||
import { setupAccount, saveEncryptedAccount } from './setupAccount'
|
||||
import { recoverAccountFromChain, decryptAccount, getAccountFromAddress } from './recoverAccountFromChain'
|
||||
|
||||
import { checkExistAccount } from './checkExistAccount'
|
||||
import { getEncryptedNote } from './getEncryptedNote'
|
||||
import { getEncryptedAccount } from './getEncryptedAccount'
|
||||
import { recoverAccountFromKey } from './recoverAccountFromKey'
|
||||
|
||||
import { redirectToAccount } from './redirectToAccount'
|
||||
import { highlightNoteAccount } from './highlightNoteAccount'
|
||||
import { saveRecoveryKeyOnFile } from './saveRecoveryKeyOnFile'
|
||||
|
||||
import { createMutation, clearState } from './utils'
|
||||
|
||||
export const actions = {
|
||||
// utils
|
||||
clearState,
|
||||
createMutation,
|
||||
// actions
|
||||
saveAccount,
|
||||
decryptNote,
|
||||
decryptNotes,
|
||||
setupAccount,
|
||||
removeAccount,
|
||||
decryptAccount,
|
||||
getRecoveryKey,
|
||||
enabledSaveFile,
|
||||
checkRecoveryKey,
|
||||
getEncryptedNote,
|
||||
redirectToAccount,
|
||||
checkExistAccount,
|
||||
getEncryptedAccount,
|
||||
highlightNoteAccount,
|
||||
saveEncryptedAccount,
|
||||
getAccountFromAddress,
|
||||
recoverAccountFromKey,
|
||||
recoverAccountFromChain,
|
||||
saveRecoveryKeyOnFile,
|
||||
// private actions
|
||||
_encryptFormatTx,
|
||||
_checkCurrentTx
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { getEncryptionPublicKey } from 'eth-sig-util'
|
||||
|
||||
export async function decryptAccount({ dispatch }, encryptedAccount) {
|
||||
try {
|
||||
const privateKey = await dispatch('metamask/ethDecrypt', encryptedAccount, { root: true })
|
||||
const publicKey = getEncryptionPublicKey(privateKey)
|
||||
|
||||
const { address } = await this.$provider.web3.eth.accounts.privateKeyToAccount(privateKey)
|
||||
|
||||
return { address, publicKey, privateKey }
|
||||
} catch (err) {
|
||||
throw new Error(`Method decryptAccount has error: ${err.message}`)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import { graph } from '@/services'
|
||||
import { unpackEncryptedMessage } from '@/utils'
|
||||
|
||||
export async function getAccountFromAddress({ getters, rootGetters }, address) {
|
||||
try {
|
||||
const netId = rootGetters['metamask/netId']
|
||||
|
||||
const rpc = rootGetters['settings/currentRpc']
|
||||
const web3 = this.$provider.getWeb3(rpc.url)
|
||||
const currentBlockNumber = await web3.eth.getBlockNumber()
|
||||
|
||||
const events = await getEventsFromBlockPart({ getters }, { netId, currentBlockNumber, address })
|
||||
|
||||
const [lastEvent] = events.slice(-1)
|
||||
|
||||
if (!lastEvent) {
|
||||
throw new Error(`Please setup account, account doesn't exist for this address`)
|
||||
}
|
||||
|
||||
const data = lastEvent.encryptedAccount ? lastEvent.encryptedAccount : lastEvent.returnValues.data
|
||||
const backup = lastEvent.address ? lastEvent.address : lastEvent.returnValues.who
|
||||
|
||||
const encryptedMessage = unpackEncryptedMessage(data)
|
||||
const encryptedKey = Buffer.from(JSON.stringify(encryptedMessage)).toString('hex')
|
||||
|
||||
return {
|
||||
backup,
|
||||
encryptedKey
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Method getAccountFromAddress has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getEventsFromBlockPart({ getters }, { address, currentBlockNumber, netId }) {
|
||||
try {
|
||||
const { events: graphEvents, lastSyncBlock } = await graph.getNoteAccounts({ address, netId })
|
||||
|
||||
const blockDifference = Math.ceil(currentBlockNumber - lastSyncBlock)
|
||||
|
||||
let blockRange = 1
|
||||
|
||||
if (Number(netId) === 56) {
|
||||
blockRange = 4950
|
||||
}
|
||||
|
||||
let numberParts = blockDifference === 0 ? 1 : Math.ceil(blockDifference / blockRange)
|
||||
const part = Math.ceil(blockDifference / numberParts)
|
||||
|
||||
let events = []
|
||||
|
||||
let fromBlock = lastSyncBlock
|
||||
let toBlock = lastSyncBlock + part
|
||||
|
||||
if (toBlock >= currentBlockNumber) {
|
||||
toBlock = 'latest'
|
||||
numberParts = 1
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberParts; i++) {
|
||||
const partOfEvents = await getters.echoContract.getEvents({
|
||||
fromBlock,
|
||||
toBlock,
|
||||
address
|
||||
})
|
||||
if (partOfEvents) {
|
||||
events = events.concat(partOfEvents)
|
||||
}
|
||||
fromBlock = toBlock
|
||||
toBlock += part
|
||||
}
|
||||
|
||||
events = graphEvents.concat(events)
|
||||
|
||||
return events
|
||||
} catch (err) {
|
||||
console.log(`getEventsFromBlock has error: ${err.message}`)
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export { decryptAccount } from './decryptAccount'
|
||||
export { getAccountFromAddress } from './getAccountFromAddress'
|
||||
export { recoverAccountFromChain } from './recoverAccountFromChain'
|
|
@ -0,0 +1,13 @@
|
|||
export async function recoverAccountFromChain({ dispatch, rootState }) {
|
||||
const { ethAccount } = rootState.metamask
|
||||
try {
|
||||
const { encryptedKey, backup } = await dispatch('getAccountFromAddress', ethAccount)
|
||||
const { address, publicKey, privateKey } = await dispatch('decryptAccount', encryptedKey)
|
||||
|
||||
this.$sessionStorage.setItem(address, privateKey)
|
||||
|
||||
dispatch('saveAccount', { account: { publicKey, privateKey: encryptedKey }, address, backup })
|
||||
} catch (err) {
|
||||
throw new Error(`Method recoverAccountFromChain has error: ${err.message}`)
|
||||
}
|
||||
}
|
23
modules/account/store/actions/recoverAccountFromKey.js
Normal file
23
modules/account/store/actions/recoverAccountFromKey.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { getEncryptionPublicKey } from 'eth-sig-util'
|
||||
|
||||
export function recoverAccountFromKey({ dispatch }, { recoveryKey }) {
|
||||
try {
|
||||
dispatch('createMutation', { type: 'SET_DOMAIN_REQUEST', payload: { key: 'recoverAccountFromKey' } })
|
||||
|
||||
const publicKey = getEncryptionPublicKey(recoveryKey)
|
||||
|
||||
const { address } = this.$provider.web3.eth.accounts.privateKeyToAccount(recoveryKey)
|
||||
const keyWithOutPrefix = recoveryKey.slice(0, 2) === '0x' ? recoveryKey.replace('0x', '') : recoveryKey
|
||||
|
||||
this.$sessionStorage.setItem(address, keyWithOutPrefix)
|
||||
|
||||
dispatch('saveAccount', { account: { publicKey, privateKey: '' }, address })
|
||||
|
||||
dispatch('createMutation', { type: 'SET_DOMAIN_SUCCESS', payload: { key: 'recoverAccountFromKey' } })
|
||||
} catch (err) {
|
||||
dispatch('createMutation', {
|
||||
type: 'SET_DOMAIN_FAILED',
|
||||
payload: { key: 'recoverAccountFromKey', errorMessage: err.message }
|
||||
})
|
||||
}
|
||||
}
|
4
modules/account/store/actions/redirectToAccount.js
Normal file
4
modules/account/store/actions/redirectToAccount.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
export function redirectToAccount({ dispatch }) {
|
||||
dispatch('highlightNoteAccount', { isHighlighted: true })
|
||||
this.$router.push({ path: '/account' })
|
||||
}
|
19
modules/account/store/actions/removeAccount.js
Normal file
19
modules/account/store/actions/removeAccount.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
export function removeAccount({ dispatch }) {
|
||||
try {
|
||||
dispatch('createMutation', { type: 'SET_DOMAIN_REQUEST', payload: { key: 'removeAccount' } })
|
||||
|
||||
dispatch('createMutation', { type: 'REMOVE_ADDRESSES' })
|
||||
dispatch('createMutation', { type: 'REMOVE_KEY' })
|
||||
dispatch('createMutation', { type: 'ENABLED_SAVE_FILE', payload: { isEnabled: true } })
|
||||
dispatch('createMutation', { type: 'REMOVE_STATISTIC' })
|
||||
|
||||
this.$sessionStorage.clear()
|
||||
|
||||
dispatch('createMutation', { type: 'SET_DOMAIN_SUCCESS', payload: { key: 'removeAccount' } })
|
||||
} catch (err) {
|
||||
dispatch('createMutation', {
|
||||
type: 'SET_DOMAIN_FAILED',
|
||||
payload: { key: 'removeAccount', errorMessage: err.message }
|
||||
})
|
||||
}
|
||||
}
|
19
modules/account/store/actions/saveAccount.js
Normal file
19
modules/account/store/actions/saveAccount.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
export function saveAccount({ dispatch, rootState }, { account, address, backup }) {
|
||||
const { ethAccount } = rootState.metamask
|
||||
|
||||
dispatch('createMutation', {
|
||||
type: 'SET_ENCRYPTED_ACCOUNT',
|
||||
payload: account
|
||||
})
|
||||
|
||||
dispatch('createMutation', {
|
||||
type: 'SET_ADDRESSES',
|
||||
payload: {
|
||||
addresses: {
|
||||
encrypt: address,
|
||||
backup: backup || '-',
|
||||
connect: ethAccount
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
13
modules/account/store/actions/saveRecoveryKeyOnFile.js
Normal file
13
modules/account/store/actions/saveRecoveryKeyOnFile.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { saveAsFile } from '@/utils'
|
||||
|
||||
export function saveRecoveryKeyOnFile(_, { recoveryKey }) {
|
||||
try {
|
||||
const { address } = this.$provider.web3.eth.accounts.privateKeyToAccount(recoveryKey)
|
||||
|
||||
const data = new Blob([`${recoveryKey}`], { type: 'text/plain;charset=utf-8' })
|
||||
|
||||
saveAsFile(data, `backup-note-account-key-${address.slice(0, 10)}.txt`)
|
||||
} catch (err) {
|
||||
console.error('saveFile', err.message)
|
||||
}
|
||||
}
|
2
modules/account/store/actions/setupAccount/index.js
Normal file
2
modules/account/store/actions/setupAccount/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { setupAccount } from './setupAccount'
|
||||
export { saveEncryptedAccount } from './saveEncryptedAccount'
|
|
@ -0,0 +1,32 @@
|
|||
import { numberToHex } from 'web3-utils'
|
||||
import { packEncryptedMessage } from '@/utils'
|
||||
|
||||
export async function saveEncryptedAccount({ getters, dispatch }, { from, encryptedData, callback }) {
|
||||
try {
|
||||
const contract = getters.echoContract
|
||||
|
||||
const data = packEncryptedMessage(encryptedData)
|
||||
|
||||
const callData = contract.getCallData(data)
|
||||
const gas = await contract.estimateGas({ from, data })
|
||||
|
||||
const callParams = {
|
||||
method: 'eth_sendTransaction',
|
||||
params: {
|
||||
data: callData,
|
||||
to: contract.address,
|
||||
gas: numberToHex(gas + 10000)
|
||||
},
|
||||
watcherParams: {
|
||||
title: 'accountSaving',
|
||||
successTitle: 'accountSaved',
|
||||
onSuccess: callback
|
||||
},
|
||||
isSaving: false
|
||||
}
|
||||
|
||||
await dispatch('metamask/sendTransaction', callParams, { root: true })
|
||||
} catch (err) {
|
||||
throw new Error(err.message)
|
||||
}
|
||||
}
|
62
modules/account/store/actions/setupAccount/setupAccount.js
Normal file
62
modules/account/store/actions/setupAccount/setupAccount.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
export async function setupAccount({ dispatch, commit, getters, rootState }, { privateKey }) {
|
||||
try {
|
||||
dispatch('createMutation', { type: 'SET_DOMAIN_REQUEST', payload: { key: 'setupAccount' } })
|
||||
|
||||
await dispatch('checkExistAccount')
|
||||
|
||||
if (getters.isExistAccount) {
|
||||
throw new Error(this.app.i18n.t('haveAccountSetupWithWallet'))
|
||||
}
|
||||
|
||||
dispatch('loading/enable', { message: this.app.i18n.t('pleaseConfirmInWallet') }, { root: true })
|
||||
|
||||
const { ethAccount } = rootState.metamask
|
||||
const pubKey = await dispatch('metamask/getEncryptionPublicKey', {}, { root: true })
|
||||
const account = await dispatch('getEncryptedAccount', { privateKey, pubKey })
|
||||
|
||||
const { address, publicKey, hexPrivateKey, encryptedData } = account
|
||||
|
||||
const callback = () => {
|
||||
dispatch('createMutation', {
|
||||
type: 'CHECK_ACCOUNT',
|
||||
payload: { isExist: true }
|
||||
})
|
||||
|
||||
dispatch('saveAccount', {
|
||||
address,
|
||||
backup: ethAccount,
|
||||
account: { publicKey, privateKey: hexPrivateKey }
|
||||
})
|
||||
|
||||
dispatch(
|
||||
'notice/addNoticeWithInterval',
|
||||
{
|
||||
notice: {
|
||||
title: 'account.modals.setupAccount.successfulNotice',
|
||||
type: 'info'
|
||||
},
|
||||
interval: 10000
|
||||
},
|
||||
{ root: true }
|
||||
)
|
||||
}
|
||||
|
||||
await dispatch('saveEncryptedAccount', {
|
||||
encryptedData,
|
||||
from: ethAccount,
|
||||
callback
|
||||
})
|
||||
|
||||
this.$sessionStorage.setItem(address, privateKey)
|
||||
|
||||
dispatch('createMutation', { type: 'SET_DOMAIN_SUCCESS', payload: { key: 'setupAccount' } })
|
||||
} catch (err) {
|
||||
console.log('createMutation', err)
|
||||
dispatch('createMutation', {
|
||||
type: 'SET_DOMAIN_FAILED',
|
||||
payload: { key: 'setupAccount', errorMessage: err.message }
|
||||
})
|
||||
} finally {
|
||||
dispatch('loading/disable', {}, { root: true })
|
||||
}
|
||||
}
|
14
modules/account/store/actions/utils.js
Normal file
14
modules/account/store/actions/utils.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
function createMutation({ commit, rootState }, { type, payload }) {
|
||||
const { netId } = rootState.metamask
|
||||
|
||||
commit(type, { ...payload, netId })
|
||||
}
|
||||
|
||||
function clearState({ dispatch }, { key }) {
|
||||
dispatch('createMutation', {
|
||||
type: 'CLEAR_STATE',
|
||||
payload: { key }
|
||||
})
|
||||
}
|
||||
|
||||
export { clearState, createMutation }
|
57
modules/account/store/getters/Contract.js
Normal file
57
modules/account/store/getters/Contract.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import Web3 from 'web3'
|
||||
|
||||
const ABI = [
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{ indexed: true, internalType: 'address', name: 'who', type: 'address' },
|
||||
{ indexed: false, internalType: 'bytes', name: 'data', type: 'bytes' }
|
||||
],
|
||||
name: 'Echo',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: 'bytes', name: '_data', type: 'bytes' }],
|
||||
name: 'echo',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
export class EchoContract {
|
||||
constructor({ rpcUrl, address }) {
|
||||
this.web3 = new Web3(rpcUrl)
|
||||
|
||||
this.contract = new this.web3.eth.Contract(ABI, address)
|
||||
this.address = this.contract._address
|
||||
}
|
||||
|
||||
async getEvents({ address, fromBlock = 0, toBlock = 'latest' }) {
|
||||
try {
|
||||
return await this.contract.getPastEvents('Echo', {
|
||||
toBlock,
|
||||
fromBlock,
|
||||
filter: { who: address }
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error(`Method getEvents has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async estimateGas({ from, data }) {
|
||||
try {
|
||||
return await this.contract.methods.echo(data).estimateGas({ from })
|
||||
} catch (err) {
|
||||
throw new Error(`Method estimateGas has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
getCallData(data) {
|
||||
try {
|
||||
return this.contract.methods.echo(data).encodeABI()
|
||||
} catch (err) {
|
||||
throw new Error(`Method getCallData has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
}
|
81
modules/account/store/getters/index.js
Normal file
81
modules/account/store/getters/index.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { EchoContract } from './Contract'
|
||||
|
||||
import networkConfig from '@/networkConfig'
|
||||
|
||||
export const getters = {
|
||||
echoContract: (state, getters, rootState, rootGetters) => {
|
||||
const netId = rootState.metamask.netId
|
||||
const { url } = rootGetters['settings/currentRpc']
|
||||
const address = networkConfig[`netId${netId}`].echoContractAccount
|
||||
|
||||
return new EchoContract({ rpcUrl: url, address })
|
||||
},
|
||||
// selectors
|
||||
selectUi: (state, getters, rootState) => (key) => {
|
||||
const { netId } = rootState.metamask
|
||||
return state.ui[`netId${netId}`][key]
|
||||
},
|
||||
selectDomain: (state, getters, rootState) => (key) => {
|
||||
const { netId } = rootState.metamask
|
||||
return state.domain[`netId${netId}`][key]
|
||||
},
|
||||
// ui store
|
||||
isExistAccount: (state, getters) => {
|
||||
return getters.selectUi('isExistAccount')
|
||||
},
|
||||
accounts: (state, getters) => {
|
||||
return getters.selectUi('addresses')
|
||||
},
|
||||
statistic: (state, getters) => {
|
||||
const data = getters.selectUi('statistic')
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
noteAccountBalance: (state, getters, rootState, rootGetters) => {
|
||||
let balance = 0
|
||||
const nativeCurrency = rootGetters['metamask/nativeCurrency']
|
||||
|
||||
getters.statistic.forEach(({ currency, amount }) => {
|
||||
if (currency === nativeCurrency) {
|
||||
balance += Number(amount)
|
||||
}
|
||||
})
|
||||
|
||||
return balance
|
||||
},
|
||||
isSetupAccount: (state, getters) => {
|
||||
return Boolean(getters.selectUi('encryptedPublicKey'))
|
||||
},
|
||||
encryptedPublicKey: (state, getters) => {
|
||||
return getters.selectUi('encryptedPublicKey')
|
||||
},
|
||||
encryptedPrivateKey: (state, getters) => {
|
||||
return getters.selectUi('encryptedPrivateKey')
|
||||
},
|
||||
isEnabledSaveFile: (state, getters) => {
|
||||
return getters.selectUi('isEnabledSaveFile')
|
||||
},
|
||||
isHighlightedNoteAccount: (state, getters) => {
|
||||
return getters.selectUi('isHighlightedNoteAccount')
|
||||
},
|
||||
// domain store
|
||||
setupAccountRequest: (state, getters) => {
|
||||
return getters.selectDomain('setupAccount')
|
||||
},
|
||||
recoverAccountRequest: (state, getters) => {
|
||||
return getters.selectDomain('recoverAccountFromChain')
|
||||
},
|
||||
removeAccountRequest: (state, getters) => {
|
||||
return getters.selectDomain('removeAccount')
|
||||
},
|
||||
decryptNotesRequest: (state, getters) => {
|
||||
return getters.selectDomain('decryptNotes')
|
||||
},
|
||||
recoverAccountFromKeyRequest: (state, getters) => {
|
||||
return getters.selectDomain('recoverAccountFromKey')
|
||||
}
|
||||
}
|
6
modules/account/store/index.js
Normal file
6
modules/account/store/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { state } from './state'
|
||||
import { actions } from './actions'
|
||||
import { getters } from './getters'
|
||||
import { mutations } from './mutations'
|
||||
|
||||
export { actions, mutations, getters, state }
|
10
modules/account/store/mutations/Addresses.js
Normal file
10
modules/account/store/mutations/Addresses.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { initialUiState } from '../state/ui'
|
||||
|
||||
export const addresses = {
|
||||
SET_ADDRESSES(state, { netId, addresses }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'addresses', addresses)
|
||||
},
|
||||
REMOVE_ADDRESSES(state, { netId }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'addresses', initialUiState.addresses)
|
||||
}
|
||||
}
|
34
modules/account/store/mutations/Domain.js
Normal file
34
modules/account/store/mutations/Domain.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
export const domain = {
|
||||
CLEAR_STATE(state, { netId, key }) {
|
||||
this._vm.$set(state.domain[`netId${netId}`], key, {
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
isFetching: false,
|
||||
errorMessage: ''
|
||||
})
|
||||
},
|
||||
SET_DOMAIN_REQUEST(state, { netId, key }) {
|
||||
this._vm.$set(state.domain[`netId${netId}`], key, {
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
isFetching: true,
|
||||
errorMessage: ''
|
||||
})
|
||||
},
|
||||
SET_DOMAIN_FAILED(state, { netId, key, errorMessage }) {
|
||||
this._vm.$set(state.domain[`netId${netId}`], key, {
|
||||
errorMessage,
|
||||
isError: true,
|
||||
isSuccess: false,
|
||||
isFetching: false
|
||||
})
|
||||
},
|
||||
SET_DOMAIN_SUCCESS(state, { netId, key }) {
|
||||
this._vm.$set(state.domain[`netId${netId}`], key, {
|
||||
isError: false,
|
||||
isSuccess: true,
|
||||
isFetching: false,
|
||||
errorMessage: ''
|
||||
})
|
||||
}
|
||||
}
|
5
modules/account/store/mutations/EnabledSaveFile.js
Normal file
5
modules/account/store/mutations/EnabledSaveFile.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const enabledSaveFile = {
|
||||
ENABLED_SAVE_FILE(state, { netId, isEnabled }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'isEnabledSaveFile', isEnabled)
|
||||
}
|
||||
}
|
16
modules/account/store/mutations/EncryptedAccount.js
Normal file
16
modules/account/store/mutations/EncryptedAccount.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
export const encryptedAccount = {
|
||||
SET_ENCRYPTED_ACCOUNT(state, { netId, publicKey, privateKey }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'encryptedPublicKey', publicKey)
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'encryptedPrivateKey', privateKey)
|
||||
},
|
||||
CHECK_ACCOUNT(state, { netId, isExist }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'isExistAccount', isExist)
|
||||
},
|
||||
REMOVE_KEY(state, { netId }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'encryptedPublicKey', '')
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'encryptedPrivateKey', '')
|
||||
},
|
||||
SET_HIGHLIGHT_NOTE_ACCOUNT(state, { netId, isHighlighted }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'isHighlightedNoteAccount', isHighlighted)
|
||||
}
|
||||
}
|
8
modules/account/store/mutations/Statistic.js
Normal file
8
modules/account/store/mutations/Statistic.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export const statistic = {
|
||||
SET_STATISTIC(state, { netId, statistic }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'statistic', statistic)
|
||||
},
|
||||
REMOVE_STATISTIC(state, { netId }) {
|
||||
this._vm.$set(state.ui[`netId${netId}`], 'statistic', {})
|
||||
}
|
||||
}
|
13
modules/account/store/mutations/index.js
Normal file
13
modules/account/store/mutations/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { domain } from './Domain'
|
||||
import { statistic } from './Statistic'
|
||||
import { addresses } from './Addresses'
|
||||
import { enabledSaveFile } from './EnabledSaveFile'
|
||||
import { encryptedAccount } from './EncryptedAccount'
|
||||
|
||||
export const mutations = {
|
||||
...domain,
|
||||
...addresses,
|
||||
...statistic,
|
||||
...enabledSaveFile,
|
||||
...encryptedAccount
|
||||
}
|
18
modules/account/store/state/domain.js
Normal file
18
modules/account/store/state/domain.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { createChainIdState } from '@/utils'
|
||||
|
||||
const requestState = {
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
isFetching: false,
|
||||
errorMessage: ''
|
||||
}
|
||||
|
||||
const initialDomainState = {
|
||||
setupAccount: Object.assign({}, requestState),
|
||||
decryptNotes: Object.assign({}, requestState),
|
||||
removeAccount: Object.assign({}, requestState),
|
||||
recoverAccountFromKey: Object.assign({}, requestState),
|
||||
recoverAccountFromChain: Object.assign({}, requestState)
|
||||
}
|
||||
|
||||
export const domain = createChainIdState(initialDomainState)
|
11
modules/account/store/state/index.js
Normal file
11
modules/account/store/state/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { ui } from './ui'
|
||||
import { domain } from './domain'
|
||||
|
||||
export const state = () => {
|
||||
return {
|
||||
ui,
|
||||
domain
|
||||
}
|
||||
}
|
||||
|
||||
export * from './ui'
|
17
modules/account/store/state/ui.js
Normal file
17
modules/account/store/state/ui.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { createChainIdState } from '@/utils'
|
||||
|
||||
export const initialUiState = {
|
||||
addresses: {
|
||||
backup: '-',
|
||||
connect: '-',
|
||||
encrypt: '-'
|
||||
},
|
||||
isExistAccount: false,
|
||||
encryptedPublicKey: '',
|
||||
encryptedPrivateKey: '',
|
||||
isEnabledSaveFile: true,
|
||||
statistic: [],
|
||||
isHighlightedNoteAccount: false
|
||||
}
|
||||
|
||||
export const ui = createChainIdState(initialUiState)
|
1
modules/index.js
Normal file
1
modules/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { AccountPage } from './account'
|
Loading…
Add table
Add a link
Reference in a new issue